nuke icon (#207)

- **feat: white nuke icon next to name if player nukes you**
![Capture d'écran 2025-03-10
220439](https://github.com/user-attachments/assets/1b717b2d-bffb-45fc-96ea-2feb57d25de0)
- **feat: red nuke icon if player sends nuke towards you**
-
![Capture d'écran 2025-03-10
220358](https://github.com/user-attachments/assets/b755fa06-9510-4bd1-8312-7180dc681d85)
This commit is contained in:
Ilan Schemoul
2025-03-11 23:44:45 +01:00
committed by Evan
parent 48e70ff0e8
commit 5f0df80f57
11 changed files with 192 additions and 42 deletions
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 122.88 122.88"
style="enable-background:new 0 0 122.88 122.88"
xml:space="preserve"
sodipodi:docname="radiation-icon.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs521" /><sodipodi:namedview
id="namedview519"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="7.1044923"
inkscape:cx="61.439999"
inkscape:cy="61.088109"
inkscape:window-width="3072"
inkscape:window-height="1653"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style512">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style><g
id="g516"><path
class="st0"
d="M86.71,104.87c-16.58,8.49-33.27,8.72-50.08-0.02L54.62,74.2c4.09,2.43,8.89,2.45,14.39,0.01L86.71,104.87 L86.71,104.87z M61.44,0c16.97,0,32.33,6.88,43.44,18c11.12,11.12,18,26.48,18,43.44c0,16.97-6.88,32.33-18,43.44 c-11.12,11.12-26.48,18-43.44,18S29.11,116,18,104.88C6.88,93.77,0,78.41,0,61.44C0,44.47,6.88,29.11,18,18 C29.11,6.88,44.47,0,61.44,0L61.44,0z M101.53,21.35C91.27,11.09,77.1,4.74,61.44,4.74c-15.66,0-29.83,6.35-40.09,16.61 C11.09,31.61,4.74,45.78,4.74,61.44c0,15.66,6.35,29.83,16.61,40.09c10.26,10.26,24.43,16.61,40.09,16.61 c15.66,0,29.83-6.35,40.09-16.61c10.26-10.26,16.61-24.43,16.61-40.09C118.14,45.78,111.79,31.61,101.53,21.35L101.53,21.35z M86.45,17.73c15.64,10.11,24.19,24.45,25.02,43.38l-35.55-0.25c0.06-4.76-2.32-8.92-7.18-12.46L86.45,17.73L86.45,17.73z M61.5,52.41c5.33,0,9.65,4.32,9.65,9.65c0,5.33-4.32,9.65-9.65,9.65c-5.33,0-9.65-4.32-9.65-9.65 C51.85,56.73,56.17,52.41,61.5,52.41L61.5,52.41z M11.53,61.33c0.93-18.6,9.08-33.17,25.06-43.36l17.56,30.91 c-4.15,2.33-6.56,6.47-7.2,12.45H11.53L11.53,61.33z"
id="path514" /></g><path
style="fill:#ff0000;stroke-width:0.140756"
d="m 11.70643,59.786116 c 1.139427,-16.160682 8.143123,-29.293513 20.677605,-38.773166 2.197888,-1.662233 4.085146,-2.898655 4.217934,-2.763347 0.06426,0.06548 3.641966,6.326396 7.950456,13.913145 4.30849,7.586749 8.201736,14.43857 8.651657,15.226268 l 0.818038,1.43218 -0.795253,0.52629 c -1.055321,0.698402 -2.583899,2.179565 -3.328121,3.224884 -1.480563,2.079567 -2.376737,4.545088 -2.95463,8.12866 l -0.08512,0.527835 H 29.231852 11.604708 Z"
id="path560" /><path
style="fill:#ff0000;stroke-width:0.140756"
d="M 58.695256,122.72725 C 36.434924,121.78876 16.295865,108.60214 6.3843596,88.47522 2.9646076,81.530857 0.99383178,74.442259 0.28441638,66.534599 c -0.19205572,-2.140793 -0.19205572,-8.048407 0,-10.189201 C 1.8185976,39.244297 9.8992478,24.157946 23.268173,13.435354 31.730429,6.6481749 42.377675,2.0796153 53.065016,0.65001616 59.055158,-0.15125931 65.86367,-0.05227879 71.612327,0.91965251 91.038343,4.2040294 107.77055,16.687064 116.49564,34.404778 c 4.57072,9.281599 6.5967,18.930787 6.22314,29.639207 -0.67949,19.478373 -11.16366,37.908445 -27.733022,48.751735 -8.926925,5.84194 -19.972559,9.47882 -29.993441,9.87562 -1.024785,0.0406 -2.306628,0.0931 -2.848538,0.11677 -0.541911,0.0237 -2.093746,-0.004 -3.448523,-0.0609 z m 8.793164,-4.84428 c 15.657732,-1.66441 29.848125,-9.73259 39.38047,-22.390426 6.20898,-8.244789 10.06209,-18.206707 11.10218,-28.703817 0.23089,-2.330303 0.23513,-8.336725 0.008,-10.627079 C 116.2838,39.109367 107.39787,24.056948 93.391613,14.512281 85.49255,9.1294063 76.588249,5.9141629 66.788727,4.9062761 64.616727,4.6828846 58.274061,4.6821413 56.09127,4.9050224 38.849211,6.66558 23.798697,15.643717 14.291027,29.840274 8.9994852,37.74144 5.8909646,46.435422 4.9050224,56.09127 c -0.2223286,2.17738 -0.2223286,8.520077 0,10.697457 0.814071,7.972623 3.1638225,15.536575 6.9087136,22.23945 8.421597,15.073573 23.128561,25.489873 40.035971,28.355733 2.08142,0.3528 3.724181,0.53572 6.915927,0.77005 1.388495,0.10195 6.794335,-0.066 8.722786,-0.27099 z"
id="path599" /><path
style="fill:#ff0000;stroke-width:0.140756"
d="M 96.417867,60.93955 C 90.185895,60.88839 83.057112,60.837542 80.576127,60.826561 l -4.510881,-0.01996 -0.16711,-1.495427 C 75.4793,55.5631 73.523161,52.191846 70.165484,49.431387 c -1.204,-0.989848 -1.300725,-1.105601 -1.139118,-1.363209 0.09713,-0.154832 4.028948,-6.963904 8.737369,-15.131272 4.708422,-8.167367 8.602166,-14.897231 8.652765,-14.955252 0.114367,-0.131145 2.671012,1.599009 4.687445,3.172127 11.833105,9.231584 18.448885,21.230158 20.018335,36.305857 0.11865,1.139682 0.21572,2.422321 0.21572,2.850309 v 0.778162 l -1.79463,-0.02777 c -0.98706,-0.01527 -6.89353,-0.06963 -13.125503,-0.120792 z"
id="path638" /><path
style="fill:#ff0000;stroke-width:0.140756"
d="m 57.006184,111.03443 c -6.009106,-0.5597 -13.213013,-2.67742 -18.885561,-5.55173 l -1.361438,-0.68985 8.937077,-15.22302 8.937077,-15.22302 1.151234,0.529562 c 1.43384,0.65956 2.470691,0.941584 4.158184,1.131028 2.295162,0.257664 5.385458,-0.226756 7.90164,-1.238623 0.580619,-0.233492 1.09979,-0.382667 1.153717,-0.3315 0.0958,0.0909 16.969067,29.268343 17.374488,30.044153 l 0.205039,0.39237 -1.660231,0.78387 c -6.221845,2.93764 -12.231313,4.67721 -18.393059,5.32429 -2.235759,0.23479 -7.263216,0.2625 -9.518167,0.0525 z"
id="path677" /><path
style="fill:#ff0000;stroke-width:0.140756"
d="m 59.831887,71.501079 c -3.89306,-0.821992 -6.842534,-3.673556 -7.669083,-7.414502 -1.30228,-5.894095 3.287355,-11.579697 9.347573,-11.579697 1.460228,0 2.787111,0.310759 4.134235,0.968248 2.415364,1.178864 4.124027,3.140785 4.975326,5.712774 0.322058,0.973018 0.373139,1.365159 0.375868,2.885499 0.0028,1.564766 -0.04013,1.884048 -0.388055,2.885498 -0.57625,1.658656 -1.132967,2.542035 -2.401404,3.810472 -1.277859,1.277859 -2.170746,1.837568 -3.810472,2.388607 -1.248439,0.419544 -3.426472,0.58328 -4.563988,0.343101 z"
id="path716" /></svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

+52
View File
@@ -2,8 +2,11 @@ import {
AllPlayers,
Cell,
Game,
NukeType,
nukeTypes,
Player,
PlayerType,
UnitType,
} from "../../../core/game/Game";
import { PseudoRandom } from "../../../core/PseudoRandom";
import { Theme } from "../../../core/configuration/Config";
@@ -15,6 +18,8 @@ import allianceRequestIcon from "../../../../resources/images/AllianceRequestIco
import crownIcon from "../../../../resources/images/CrownIcon.svg";
import targetIcon from "../../../../resources/images/TargetIcon.svg";
import embargoIcon from "../../../../resources/images/EmbargoIcon.svg";
import nukeWhiteIcon from "../../../../resources/images/NukeIconWhite.svg";
import nukeRedIcon from "../../../../resources/images/NukeIconRed.svg";
import { ClientID } from "../../../core/Schemas";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { createCanvas, renderTroops } from "../../Utils";
@@ -47,6 +52,8 @@ export class NameLayer implements Layer {
private targetIconImage: HTMLImageElement;
private crownIconImage: HTMLImageElement;
private embargoIconImage: HTMLImageElement;
private nukeWhiteIconImage: HTMLImageElement;
private nukeRedIconImage: HTMLImageElement;
private container: HTMLDivElement;
private myPlayer: PlayerView | null = null;
private firstPlace: PlayerView | null = null;
@@ -69,6 +76,10 @@ export class NameLayer implements Layer {
this.targetIconImage.src = targetIcon;
this.embargoIconImage = new Image();
this.embargoIconImage.src = embargoIcon;
this.nukeWhiteIconImage = new Image();
this.nukeWhiteIconImage.src = nukeWhiteIcon;
this.nukeRedIconImage = new Image();
this.nukeRedIconImage.src = nukeRedIcon;
}
resizeCanvas() {
@@ -405,6 +416,47 @@ export class NameLayer implements Layer {
existingEmbargo.remove();
}
const nukesSentByOtherPlayer = this.game.units().filter((unit) => {
const isSendingNuke = render.player.id() == unit.owner().id();
const notMyPlayer = unit.owner().id() != myPlayer.id();
return (
nukeTypes.includes(unit.type()) &&
isSendingNuke &&
notMyPlayer &&
unit.isActive()
);
});
const isMyPlayerTarget = nukesSentByOtherPlayer.find((unit) => {
const detonationDst = unit.detonationDst();
const targetId = this.game.owner(detonationDst).id();
return targetId == this.myPlayer.id();
});
const existingNuke = iconsDiv.querySelector(
'[data-icon="nuke"]',
) as HTMLImageElement;
if (existingNuke) {
if (nukesSentByOtherPlayer.length == 0) {
existingNuke.remove();
} else if (
isMyPlayerTarget &&
existingNuke.src != this.nukeRedIconImage.src
) {
existingNuke.src = this.nukeRedIconImage.src;
} else if (
!isMyPlayerTarget &&
existingNuke.src != this.nukeWhiteIconImage.src
) {
existingNuke.src = this.nukeWhiteIconImage.src;
}
} else if (myPlayer && nukesSentByOtherPlayer.length > 0) {
if (!existingNuke) {
const icon = isMyPlayerTarget
? this.nukeRedIconImage.src
: this.nukeWhiteIconImage.src;
iconsDiv.appendChild(this.createIconElement(icon, iconSize, "nuke"));
}
}
// Update all icon sizes
const icons = iconsDiv.getElementsByTagName("img");
for (const icon of icons) {
+2 -2
View File
@@ -245,10 +245,10 @@ export class UnitLayer implements Layer {
}
let outerColor = this.theme.territoryColor(unit.owner().info());
if (unit.targetId()) {
if (unit.warshipTargetId()) {
const targetOwner = this.game
.units()
.find((u) => u.id() == unit.targetId())
.find((u) => u.id() == unit.warshipTargetId())
?.owner();
if (targetOwner == this.myPlayer) {
outerColor = colord({ r: 200, b: 0, g: 0 });
+3 -1
View File
@@ -55,7 +55,9 @@ export class NukeExecution implements Execution {
this.active = false;
return;
}
this.nuke = this.player.buildUnit(this.type, 0, spawn);
this.nuke = this.player.buildUnit(this.type, 0, spawn, {
detonationDst: this.dst,
});
if (this.mg.hasOwner(this.dst)) {
const target = this.mg.owner(this.dst) as Player;
if (this.type == UnitType.AtomBomb) {
+3 -6
View File
@@ -47,12 +47,9 @@ export class TradeShipExecution implements Execution {
this.active = false;
return;
}
this.tradeShip = this.origOwner.buildUnit(
UnitType.TradeShip,
0,
spawn,
this._dstPort,
);
this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn, {
dstPort: this._dstPort,
});
}
if (!this.tradeShip.isActive()) {
+5 -5
View File
@@ -79,10 +79,10 @@ export class WarshipExecution implements Execution {
.filter((u) => u != this.warship)
.filter((u) => !u.owner().isAlliedWith(this.warship.owner()))
.filter((u) => !this.alreadySentShell.has(u))
.filter(
(u) =>
u.type() != UnitType.TradeShip || u.dstPort().owner() != this.owner(),
);
.filter((u) => {
const portOwner = u.dstPort() ? u.dstPort().owner() : null;
return u.type() != UnitType.TradeShip || portOwner != this.owner();
});
this.target =
ships.sort((a, b) => {
@@ -110,7 +110,7 @@ export class WarshipExecution implements Execution {
return distSortUnit(this.mg, this.warship)(a, b);
})[0] ?? null;
this.warship.setTarget(this.target);
this.warship.setWarshipTarget(this.target);
if (this.target == null || this.target.type() != UnitType.TradeShip) {
// Patrol unless we are hunting down a tradeship
const result = this.pathfinder.nextTile(
+22 -12
View File
@@ -80,11 +80,14 @@ export enum UnitType {
MIRVWarhead = "MIRV Warhead",
Construction = "Construction",
}
export type NukeType =
| UnitType.AtomBomb
| UnitType.HydrogenBomb
| UnitType.MIRVWarhead
| UnitType.MIRV;
export const nukeTypes = [
UnitType.AtomBomb,
UnitType.HydrogenBomb,
UnitType.MIRVWarhead,
UnitType.MIRV,
] as UnitType[];
export type NukeType = (typeof nukeTypes)[number];
export enum Relation {
Hostile = 0,
@@ -197,6 +200,13 @@ export class PlayerInfo {
) {}
}
// Some units have info specific to them
export interface UnitSpecificInfos {
dstPort?: Unit; // Only for trade ships
detonationDst?: TileRef; // Only for nukes
warshipTarget?: Unit;
}
export interface Unit {
id(): number;
@@ -216,9 +226,12 @@ export interface Unit {
hasHealth(): boolean;
health(): number;
modifyHealth(delta: number): void;
// State for warships (currently)
setTarget(target: Unit): void;
target(): Unit;
setWarshipTarget(target: Unit): void; // warship only
warshipTarget(): Unit;
dstPort(): Unit; // Only for trade ships
detonationDst(): TileRef; // Only for nukes
// Mutations
setTroops(troops: number): void;
@@ -230,9 +243,6 @@ export interface Unit {
// Updates
toUpdate(): UnitUpdate;
// Only for some types, otherwise return null
dstPort(): Unit;
}
export interface TerraNullius {
@@ -290,7 +300,7 @@ export interface Player {
type: UnitType,
troops: number,
tile: TileRef,
dstPort?: Unit,
unitSpecificInfos?: UnitSpecificInfos,
): Unit;
captureUnit(unit: Unit): void;
+3 -1
View File
@@ -70,9 +70,11 @@ export interface UnitUpdate {
pos: TileRef;
lastPos: TileRef;
isActive: boolean;
dstPortId?: number; // Only for trade ships
detonationDst?: TileRef; // Only for nukes
warshipTargetId?: number;
health?: number;
constructionType?: UnitType;
targetId?: number;
}
export interface AttackUpdate {
+18 -2
View File
@@ -2,6 +2,7 @@ import {
GameUpdates,
MapPos,
MessageType,
nukeTypes,
Player,
PlayerActions,
PlayerProfile,
@@ -92,8 +93,23 @@ export class UnitView {
constructionType(): UnitType | undefined {
return this.data.constructionType;
}
targetId() {
return this.data.targetId;
dstPortId(): number {
if (this.type() != UnitType.TradeShip) {
throw Error("Must be a trade ship");
}
return this.data.dstPortId;
}
detonationDst(): TileRef {
if (!nukeTypes.includes(this.type())) {
throw Error("Must be a nuke");
}
return this.data.detonationDst;
}
warshipTargetId(): number {
if (this.type() != UnitType.Warship) {
throw Error("Must be a warship");
}
return this.data.warshipTargetId;
}
}
+3 -2
View File
@@ -18,6 +18,7 @@ import {
EmojiMessage,
PlayerProfile,
Attack,
UnitSpecificInfos,
} from "./Game";
import { AttackUpdate, PlayerUpdate } from "./GameUpdates";
import { GameUpdateType } from "./GameUpdates";
@@ -648,7 +649,7 @@ export class PlayerImpl implements Player {
type: UnitType,
troops: number,
spawnTile: TileRef,
dstPort?: Unit,
unitSpecificInfos: UnitSpecificInfos = {},
): UnitImpl {
const cost = this.mg.unitInfo(type).cost(this);
const b = new UnitImpl(
@@ -658,7 +659,7 @@ export class PlayerImpl implements Player {
troops,
this.mg.nextUnitID(),
this,
dstPort,
unitSpecificInfos,
);
this._units.push(b);
this.removeGold(cost);
+27 -11
View File
@@ -1,4 +1,4 @@
import { MessageType } from "./Game";
import { MessageType, nukeTypes, UnitSpecificInfos } from "./Game";
import { UnitUpdate } from "./GameUpdates";
import { GameUpdateType } from "./GameUpdates";
import { simpleHash, toInt, within, withinInt } from "../Util";
@@ -6,6 +6,7 @@ import { Unit, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
import { GameImpl } from "./GameImpl";
import { PlayerImpl } from "./PlayerImpl";
import { TileRef } from "./GameMap";
import { consolex } from "../Consolex";
export class UnitImpl implements Unit {
private _active = true;
@@ -16,6 +17,10 @@ export class UnitImpl implements Unit {
private _constructionType: UnitType = undefined;
private _dstPort: Unit | null = null; // Only for trade ships
private _detonationDst: TileRef | null = null; // Only for nukes
private _warshipTarget: Unit | null = null;
constructor(
private _type: UnitType,
private mg: GameImpl,
@@ -23,10 +28,13 @@ export class UnitImpl implements Unit {
private _troops: number,
private _id: number,
public _owner: PlayerImpl,
private _dstPort?: Unit,
unitsSpecificInfos: UnitSpecificInfos = {},
) {
this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1);
this._lastTile = _tile;
this._dstPort = unitsSpecificInfos.dstPort;
this._detonationDst = unitsSpecificInfos.detonationDst;
this._warshipTarget = unitsSpecificInfos.warshipTarget;
}
id() {
@@ -34,6 +42,8 @@ export class UnitImpl implements Unit {
}
toUpdate(): UnitUpdate {
const warshipTarget = this.warshipTarget();
const dstPort = this.dstPort();
return {
type: GameUpdateType.Unit,
unitType: this._type,
@@ -45,7 +55,9 @@ export class UnitImpl implements Unit {
lastPos: this._lastTile,
health: this.hasHealth() ? Number(this._health) : undefined,
constructionType: this._constructionType,
targetId: this.target() ? this.target().id() : null,
dstPortId: dstPort ? dstPort.id() : null,
warshipTargetId: warshipTarget ? warshipTarget.id() : null,
detonationDst: this.detonationDst(),
};
}
@@ -153,15 +165,19 @@ export class UnitImpl implements Unit {
return `Unit:${this._type},owner:${this.owner().name()}`;
}
setWarshipTarget(target: Unit) {
this._warshipTarget = target;
}
warshipTarget(): Unit {
return this._warshipTarget;
}
detonationDst(): TileRef {
return this._detonationDst;
}
dstPort(): Unit {
return this._dstPort;
}
setTarget(target: Unit) {
this._target = target;
}
target() {
return this._target;
}
}