Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@
"alliance_request_sent": "Alliance request sent to {name}.",
"alliance_request_status": "{name} {status} your alliance request",
"atom_bomb_detonated": "{name} - atom bomb detonated",
"atom_bomb_inbound": "{count, plural, one {{name} - atom bomb inbound} other {{name} - {count} atom bombs inbound}}",
"attack_cancelled_retreat": "Attack cancelled, {troops} soldiers killed during retreat",
"attack_request": "{name} requests you attack {target}",
"betrayal_debuff_ends": "{time} seconds left until betrayal debuff ends",
Expand All @@ -471,9 +472,11 @@
"duration_seconds_plural": "{seconds} seconds",
"focus": "Focus",
"hydrogen_bomb_detonated": "{name} - hydrogen bomb detonated",
"hydrogen_bomb_inbound": "{count, plural, one {{name} - hydrogen bomb inbound} other {{name} - {count} hydrogen bombs inbound}}",
"ignore": "Ignore",
"mirv_inbound": "{count, plural, one {⚠️⚠️⚠️ {name} - MIRV INBOUND ⚠️⚠️⚠️} other {⚠️⚠️⚠️ {name} - {count} MIRVs INBOUND ⚠️⚠️⚠️}}",
"mirv_warheads_intercepted": "{count, plural, one {{count} MIRV warhead intercepted} other {{count} MIRV warheads intercepted}}",
"missile_intercepted": "Missile intercepted {unit}",
"missile_intercepted": "{count, plural, one {Missile intercepted {unit}} other {{count} missiles intercepted ({unit})}}",
"no_boats_available": "No boats available, max {max}",
"received_gold_from_captured_ship": "Received {gold} gold from ship captured from {name}",
"received_gold_from_conquest": "Conquered {name}, received {gold} gold",
Expand All @@ -487,7 +490,9 @@
"sent_gold_to_player": "Sent {gold} gold to {name}",
"sent_troops_to_player": "Sent {troops} troops to {name}",
"trade_ship_captured": "Your trade ship was captured by {name}",
"unit_destroyed": "Your {unit} was destroyed",
"unit_captured": "{count, plural, one {Captured {unit} from {name}} other {Captured {count} {unit} from {name}}}",
"unit_destroyed": "{count, plural, one {Your {unit} was destroyed} other {Your {count} {unit} were destroyed}}",
"unit_lost": "{count, plural, one {Your {unit} was captured by {name}} other {Your {count} {unit} were captured by {name}}}",
"unit_voluntarily_deleted": "Unit voluntarily deleted",
"wants_to_renew_alliance": "{name} wants to renew your alliance"
},
Expand Down Expand Up @@ -1409,6 +1414,19 @@
"sam_launcher": "SAM Launcher",
"warship": "Warship"
},
"unit_type_plural": {
"atom_bomb": "Atom Bombs",
"boat": "Boats",
"city": "Cities",
"defense_post": "Defense Posts",
"factory": "Factories",
"hydrogen_bomb": "Hydrogen Bombs",
"mirv": "MIRVs",
"missile_silo": "Missile Silos",
"port": "Ports",
"sam_launcher": "SAM Launchers",
"warship": "Warships"
},
"user_setting": {
"alert_frame_desc": "Toggle the alert frame. When enabled, the frame will be displayed when you are betrayed or attacked over land.",
"alert_frame_label": "Alert Frame",
Expand Down
141 changes: 130 additions & 11 deletions src/client/hud/layers/EventsDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@ import {
translateText,
} from "../../Utils";

const UNIT_TRANSLATION_KEYS: Record<string, string> = {
City: "city",
Port: "port",
"Defense Post": "defense_post",
"SAM Launcher": "sam_launcher",
"Missile Silo": "missile_silo",
Factory: "factory",
Warship: "warship",
Transport: "boat",
"Atom Bomb": "atom_bomb",
"Hydrogen Bomb": "hydrogen_bomb",
MIRV: "mirv",
};

const getTranslatedUnitName = (unitType: string, plural: boolean): string => {
const key = UNIT_TRANSLATION_KEYS[unitType];
if (!key) return unitType;
return translateText(plural ? `unit_type_plural.${key}` : `unit_type.${key}`);
};

const INBOUND_NUKE_KEYS: Record<number, string> = {
[MessageType.NUKE_INBOUND]: "events_display.atom_bomb_inbound",
[MessageType.HYDROGEN_BOMB_INBOUND]: "events_display.hydrogen_bomb_inbound",
[MessageType.MIRV_INBOUND]: "events_display.mirv_inbound",
};

interface GameEvent {
description: string;
unsafeDescription?: boolean;
Expand All @@ -41,6 +67,12 @@ interface GameEvent {
onDelete?: () => void;
focusID?: number;
unitView?: UnitView;
groupKey?: string;
count?: number;
unitType?: string;
targetPlayerName?: string;
messageKey?: string;
messageParams?: Record<string, string | number>;
}

const TIER_1_TYPES: ReadonlySet<MessageType> = new Set([
Expand Down Expand Up @@ -242,6 +274,39 @@ export class EventsDisplay extends LitElement implements Controller {
}

private addEvent(event: GameEvent) {
if (event.groupKey) {
const existing = this.events.find(
(e) =>
e.groupKey === event.groupKey &&
this.game.ticks() - e.createdAt <= 30,
);
if (existing) {
existing.count = (existing.count ?? 1) + 1;
existing.createdAt = this.game.ticks();
if (event.unitView !== undefined) {
existing.unitView = event.unitView;
}
if (event.focusID !== undefined) {
existing.focusID = event.focusID;
}

if (existing.messageKey) {
const params: Record<string, string | number> = {
...existing.messageParams,
count: existing.count,
};
if (existing.messageParams?.unit) {
params.unit = getTranslatedUnitName(
existing.unitType ?? "",
existing.count > 1,
);
}
existing.description = translateText(existing.messageKey, params);
}
this.requestUpdate();
return;
}
}
this.events = [...this.events, event];
this.requestUpdate();
}
Expand All @@ -261,21 +326,48 @@ export class EventsDisplay extends LitElement implements Controller {
return;
}

let description: string = event.message;
if (event.message.startsWith("events_display.")) {
description = translateText(event.message, event.params ?? {});
const params = { ...event.params };
if (params.unit) {
params.unit = getTranslatedUnitName(String(params.unit), false);
}

const description = event.message.startsWith("events_display.")
? translateText(event.message, { ...params, count: 1 })
: event.message;

let groupKey: string | undefined;
const unitType = String(event.params?.unit ?? "");
const targetPlayerName = String(event.params?.name ?? "");

if (event.message === "events_display.unit_destroyed") {
groupKey = `destroyed_${unitType}`;
} else if (event.message === "events_display.unit_captured") {
groupKey = `captured_${unitType}_${targetPlayerName}`;
} else if (event.message === "events_display.unit_lost") {
groupKey = `lost_${unitType}_${targetPlayerName}`;
} else if (event.message === "events_display.missile_intercepted") {
groupKey = `intercepted_${unitType}`;
}

const unitView =
event.unitID !== undefined ? this.game.unit(event.unitID) : undefined;
this.addEvent({
description: description,
description,
createdAt: this.game.ticks(),
highlight: true,
type: event.messageType,
unsafeDescription: true,
unitView: unitView,
unitView:
event.unitID !== undefined ? this.game.unit(event.unitID) : undefined,
focusID: event.focusPlayerID,
groupKey,
count: 1,
unitType,
targetPlayerName,
messageKey: event.message.startsWith("events_display.")
? event.message
: undefined,
messageParams: event.message.startsWith("events_display.")
? event.params
: undefined,
});
}

Expand Down Expand Up @@ -530,20 +622,47 @@ export class EventsDisplay extends LitElement implements Controller {

onUnitIncomingEvent(event: UnitIncomingUpdate) {
const myPlayer = this.game.myPlayer();

if (!myPlayer || myPlayer.smallID() !== event.playerID) {
return;
}

const unitView = this.game.unit(event.unitID);
let description = event.message;
let groupKey: string | undefined;
let unitType: string | undefined;
let messageKey: string | undefined;
let messageParams: Record<string, string | number> | undefined;

if (event.attackerName) {
messageKey = INBOUND_NUKE_KEYS[event.messageType];
if (messageKey) {
messageParams = { name: event.attackerName };
description = translateText(messageKey, { ...messageParams, count: 1 });
if (event.messageType === MessageType.NUKE_INBOUND) {
unitType = "Atom Bomb";
groupKey = `inbound_atom_${event.attackerName}`;
} else if (event.messageType === MessageType.HYDROGEN_BOMB_INBOUND) {
unitType = "Hydrogen Bomb";
groupKey = `inbound_hydrogen_${event.attackerName}`;
} else if (event.messageType === MessageType.MIRV_INBOUND) {
unitType = "MIRV";
groupKey = `inbound_mirv_${event.attackerName}`;
}
}
}

this.addEvent({
description: event.message,
description,
type: event.messageType,
unsafeDescription: false,
highlight: true,
createdAt: this.game.ticks(),
unitView: unitView,
unitView: this.game.unit(event.unitID),
groupKey,
count: 1,
unitType,
targetPlayerName: event.attackerName,
messageKey,
messageParams,
});
}

Expand Down
1 change: 1 addition & 0 deletions src/core/execution/MIRVExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class MirvExecution implements Execution {
`⚠️⚠️⚠️ ${this.player.displayName()} - MIRV INBOUND ⚠️⚠️⚠️`,
MessageType.MIRV_INBOUND,
this.targetPlayer.id(),
this.player.displayName(),
);
}

Expand Down
2 changes: 2 additions & 0 deletions src/core/execution/NukeExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export class NukeExecution implements Execution {
`${this.player.displayName()} - atom bomb inbound`,
MessageType.NUKE_INBOUND,
target.id(),
this.player.displayName(),
);
} else if (this.nukeType === UnitType.HydrogenBomb) {
this.mg.displayIncomingUnit(
Expand All @@ -228,6 +229,7 @@ export class NukeExecution implements Execution {
`${this.player.displayName()} - hydrogen bomb inbound`,
MessageType.HYDROGEN_BOMB_INBOUND,
target.id(),
this.player.displayName(),
);
}

Expand Down
1 change: 1 addition & 0 deletions src/core/execution/TransportShipExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export class TransportShipExecution implements Execution {
`Naval invasion incoming from ${this.attacker.displayName()} (${renderTroops(this.boat.troops())})`,
MessageType.NAVAL_INVASION_INBOUND,
this.target.id(),
this.attacker.displayName(),
);
}

Expand Down
1 change: 1 addition & 0 deletions src/core/game/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ export interface Game extends GameMap {
message: string,
type: MessageType,
playerID: PlayerID | null,
attackerName?: string,
): void;

displayChat(
Expand Down
2 changes: 2 additions & 0 deletions src/core/game/GameImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,7 @@ export class GameImpl implements Game {
message: string,
type: MessageType,
playerID: PlayerID,
attackerName?: string,
): void {
const id = this.player(playerID).smallID();

Expand All @@ -1018,6 +1019,7 @@ export class GameImpl implements Game {
message: message,
messageType: type,
playerID: id,
attackerName: attackerName,
});
}

Expand Down
1 change: 1 addition & 0 deletions src/core/game/GameUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ export interface UnitIncomingUpdate {
message: string;
messageType: MessageType;
playerID: number;
attackerName?: string;
}

export interface EmbargoUpdate {
Expand Down
27 changes: 25 additions & 2 deletions src/core/game/UnitImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AllUnitParams,
MessageType,
Player,
Structures,
Tick,
TrainType,
TrajectoryTile,
Expand Down Expand Up @@ -216,6 +217,24 @@ export class UnitImpl implements Unit {
this.mg.stats().unitLose(this._owner, this._type);
break;
}
if (Structures.has(this._type)) {
this.mg.displayMessage(
"events_display.unit_captured",
MessageType.CAPTURED_ENEMY_UNIT,
newOwner.id(),
undefined,
{ unit: this._type, name: this._owner.displayName() },
this.id(),
);
this.mg.displayMessage(
"events_display.unit_lost",
MessageType.UNIT_DESTROYED,
this._owner.id(),
undefined,
{ unit: this._type, name: newOwner.displayName() },
this.id(),
);
}
this._lastOwner = this._owner;
this._lastOwner._units = this._lastOwner._units.filter((u) => u !== this);
this._owner = newOwner;
Expand Down Expand Up @@ -326,11 +345,15 @@ export class UnitImpl implements Unit {
}

private displayMessageOnDeleted(): void {
// Only warships and transport ships are worth notifying about; everything
if (!this._owner.isAlive()) {
return;
}
// Only warships, transport ships, and structures are worth notifying about; everything
// else is either visible on the map or too low-stakes to surface.
if (
this._type !== UnitType.Warship &&
this._type !== UnitType.TransportShip
this._type !== UnitType.TransportShip &&
!Structures.has(this._type)
) {
return;
}
Expand Down
Loading
Loading