diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index 2a1863c12..21334b02c 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -531,17 +531,13 @@ export class NameLayer implements Layer { // Embargo icon let existingEmbargo = iconsDiv.querySelector('[data-icon="embargo"]'); - const hasEmbargo = - myPlayer && - (render.player.hasEmbargoAgainst(myPlayer) || - myPlayer.hasEmbargoAgainst(render.player)); const isThemeEmbargoIcon = existingEmbargo?.getAttribute("dark-mode") === isDarkMode.toString(); const embargoIconImageSrc = isDarkMode ? this.embargoWhiteIconImage.src : this.embargoBlackIconImage.src; - if (myPlayer && hasEmbargo) { + if (myPlayer?.hasEmbargo(render.player)) { // Create new icon to match theme if (existingEmbargo && !isThemeEmbargoIcon) { existingEmbargo.remove(); diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index e104559d2..354a23df8 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -104,10 +104,8 @@ export class TerritoryLayer implements Layer { if (myPlayer) { updates?.[GameUpdateType.BrokeAlliance]?.forEach((update) => { const territory = this.game.playerBySmallID(update.betrayedID); - console.log("betrayedID", update.betrayedID); - console.log("territory", territory); if (territory && territory instanceof PlayerView) { - this.redrawTerritory(territory); + this.redrawBorder(territory); } }); @@ -123,10 +121,23 @@ export class TerritoryLayer implements Layer { : update.request.requestorID; const territory = this.game.playerBySmallID(territoryId); if (territory && territory instanceof PlayerView) { - this.redrawTerritory(territory); + this.redrawBorder(territory); } } }); + updates?.[GameUpdateType.EmbargoEvent]?.forEach((update) => { + const player = this.game.playerBySmallID(update.playerID) as PlayerView; + const embargoed = this.game.playerBySmallID( + update.embargoedID, + ) as PlayerView; + + if ( + player.id() === myPlayer?.id() || + embargoed.id() === myPlayer?.id() + ) { + this.redrawBorder(player, embargoed); + } + }); } const focusedPlayer = this.game.focusedPlayer(); @@ -237,7 +248,7 @@ export class TerritoryLayer implements Layer { if (this.highlightedTerritory) { territories.push(this.highlightedTerritory); } - this.redrawTerritory(territories); + this.redrawBorder(...territories); } } @@ -298,16 +309,15 @@ export class TerritoryLayer implements Layer { }); } - redrawTerritory(territory: PlayerView | PlayerView[]) { - const territories = Array.isArray(territory) ? territory : [territory]; - const territorySet = new Set(territories); - - this.game.forEachTile((t) => { - const owner = this.game.owner(t) as PlayerView; - if (territorySet.has(owner)) { - this.paintTerritory(t); - } - }); + redrawBorder(...players: PlayerView[]) { + return Promise.all( + players.map(async (player) => { + const tiles = await player.borderTiles(); + tiles.borderTiles.forEach((tile: TileRef) => { + this.paintTerritory(tile, true); + }); + }), + ); } initImageData() { @@ -419,12 +429,7 @@ export class TerritoryLayer implements Layer { if (this.game.isBorder(tile)) { const playerIsFocused = owner && this.game.focusedPlayer() === owner; if (myPlayer) { - let alternativeColor = owner.isFriendly(myPlayer) - ? this.theme.allyColor() - : this.theme.enemyColor(); - if (owner.smallID() === myPlayer.smallID()) { - alternativeColor = this.theme.selfColor(); - } + const alternativeColor = this.alternateViewColor(owner); this.paintTile(this.alternativeImageData, tile, alternativeColor, 255); } if ( @@ -449,25 +454,12 @@ export class TerritoryLayer implements Layer { this.paintTile(this.imageData, tile, useBorderColor, 255); } } else { + // Interior tiles const pattern = owner.cosmetics.pattern; const patternsEnabled = this.cachedTerritoryPatternsEnabled ?? false; - if (myPlayer) { - let alternativeColor = owner.isFriendly(myPlayer) - ? this.theme.allyColor() - : this.theme.enemyColor(); - // If the current player is the owner - if (owner.smallID() === myPlayer.smallID()) { - alternativeColor = this.theme.selfColor(); - } - // If the tile is on a ally territory, use the ally color - this.paintTile( - this.alternativeImageData, - tile, - alternativeColor, - isHighlighted ? 150 : 60, - ); - } + // Alternative view only shows borders. + this.clearAlternativeTile(tile); if (pattern === undefined || patternsEnabled === false) { this.paintTile( @@ -490,6 +482,28 @@ export class TerritoryLayer implements Layer { } } + alternateViewColor(other: PlayerView): Colord { + const myPlayer = this.game.myPlayer(); + if (!myPlayer) { + return this.theme.neutralColor(); + } + if (other.smallID() === myPlayer.smallID()) { + return this.theme.selfColor(); + } + if (other.isFriendly(myPlayer)) { + return this.theme.allyColor(); + } + if (!other.hasEmbargo(myPlayer)) { + return this.theme.neutralColor(); + } + return this.theme.enemyColor(); + } + + paintAlternateViewTile(tile: TileRef, other: PlayerView) { + const color = this.alternateViewColor(other); + this.paintTile(this.alternativeImageData, tile, color, 255); + } + paintTile(imageData: ImageData, tile: TileRef, color: Colord, alpha: number) { const offset = tile * 4; imageData.data[offset] = color.rgba.r; @@ -504,6 +518,11 @@ export class TerritoryLayer implements Layer { this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) } + clearAlternativeTile(tile: TileRef) { + const offset = tile * 4; + this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) + } + enqueueTile(tile: TileRef) { this.tileToRenderQueue.push({ tile: tile, diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 16d6e9584..41ac87c37 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -185,6 +185,7 @@ export interface Theme { // unit color for alternate view selfColor(): Colord; allyColor(): Colord; + neutralColor(): Colord; enemyColor(): Colord; spawnHighlightColor(): Colord; } diff --git a/src/core/configuration/PastelTheme.ts b/src/core/configuration/PastelTheme.ts index 2318d6ac3..8677cfe0f 100644 --- a/src/core/configuration/PastelTheme.ts +++ b/src/core/configuration/PastelTheme.ts @@ -31,6 +31,7 @@ export class PastelTheme implements Theme { private _selfColor = colord({ r: 0, g: 255, b: 0 }); private _allyColor = colord({ r: 255, g: 255, b: 0 }); + private _neutralColor = colord({ r: 128, g: 128, b: 128 }); private _enemyColor = colord({ r: 255, g: 0, b: 0 }); private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 }); @@ -159,6 +160,9 @@ export class PastelTheme implements Theme { allyColor(): Colord { return this._allyColor; } + neutralColor(): Colord { + return this._neutralColor; + } enemyColor(): Colord { return this._enemyColor; } diff --git a/src/core/configuration/PastelThemeDark.ts b/src/core/configuration/PastelThemeDark.ts index 73e1e88e7..9866b1d51 100644 --- a/src/core/configuration/PastelThemeDark.ts +++ b/src/core/configuration/PastelThemeDark.ts @@ -31,6 +31,7 @@ export class PastelThemeDark implements Theme { private _selfColor = colord({ r: 0, g: 255, b: 0 }); private _allyColor = colord({ r: 255, g: 255, b: 0 }); + private _neutralColor = colord({ r: 128, g: 128, b: 128 }); private _enemyColor = colord({ r: 255, g: 0, b: 0 }); private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 }); @@ -161,6 +162,9 @@ export class PastelThemeDark implements Theme { allyColor(): Colord { return this._allyColor; } + neutralColor(): Colord { + return this._neutralColor; + } enemyColor(): Colord { return this._enemyColor; } diff --git a/src/core/execution/AttackExecution.ts b/src/core/execution/AttackExecution.ts index 043889910..4b7da9165 100644 --- a/src/core/execution/AttackExecution.ts +++ b/src/core/execution/AttackExecution.ts @@ -69,7 +69,7 @@ export class AttackExecution implements Execution { this._owner.type() !== PlayerType.Bot ) { // Don't let bots embargo since they can't trade anyway. - targetPlayer.addEmbargo(this._owner.id(), true); + targetPlayer.addEmbargo(this._owner, true); } } diff --git a/src/core/execution/EmbargoExecution.ts b/src/core/execution/EmbargoExecution.ts index 67a0664d1..6c19b47e1 100644 --- a/src/core/execution/EmbargoExecution.ts +++ b/src/core/execution/EmbargoExecution.ts @@ -3,6 +3,8 @@ import { Execution, Game, Player, PlayerID } from "../game/Game"; export class EmbargoExecution implements Execution { private active = true; + private target: Player; + constructor( private player: Player, private targetID: PlayerID, @@ -15,11 +17,12 @@ export class EmbargoExecution implements Execution { this.active = false; return; } + this.target = mg.player(this.targetID); } tick(_: number): void { - if (this.action === "start") this.player.addEmbargo(this.targetID, false); - else this.player.stopEmbargo(this.targetID); + if (this.action === "start") this.player.addEmbargo(this.target, false); + else this.player.stopEmbargo(this.target); this.active = false; } diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 0f41f7e86..94bbfa5d9 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -100,12 +100,12 @@ export class FakeHumanExecution implements Execution { player.relation(other) <= Relation.Hostile && !player.hasEmbargoAgainst(other) ) { - player.addEmbargo(other.id(), false); + player.addEmbargo(other, false); } else if ( player.relation(other) >= Relation.Neutral && player.hasEmbargoAgainst(other) ) { - player.stopEmbargo(other.id()); + player.stopEmbargo(other); } }); } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 5e89f06bd..4f6e834db 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -487,7 +487,7 @@ export interface TerraNullius { export interface Embargo { createdAt: Tick; isTemporary: boolean; - target: PlayerID; + target: Player; } export interface Player { @@ -595,10 +595,10 @@ export interface Player { // Embargo hasEmbargoAgainst(other: Player): boolean; tradingPartners(): Player[]; - addEmbargo(other: PlayerID, isTemporary: boolean): void; + addEmbargo(other: Player, isTemporary: boolean): void; getEmbargoes(): Embargo[]; - stopEmbargo(other: PlayerID): void; - endTemporaryEmbargo(other: PlayerID): void; + stopEmbargo(other: Player): void; + endTemporaryEmbargo(other: Player): void; canTrade(other: Player): boolean; // Attacking. diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index b85073e75..e102587e7 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -287,9 +287,9 @@ export class GameImpl implements Game { // Automatically remove embargoes only if they were automatically created if (requestor.hasEmbargoAgainst(recipient)) - requestor.endTemporaryEmbargo(recipient.id()); + requestor.endTemporaryEmbargo(recipient); if (recipient.hasEmbargoAgainst(requestor)) - recipient.endTemporaryEmbargo(requestor.id()); + recipient.endTemporaryEmbargo(requestor); this.addUpdate({ type: GameUpdateType.AllianceRequestReply, diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 565960479..80cac341e 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -45,6 +45,7 @@ export enum GameUpdateType { BonusEvent, RailroadEvent, ConquestEvent, + EmbargoEvent, } export type GameUpdate = @@ -65,7 +66,8 @@ export type GameUpdate = | AllianceExtensionUpdate | BonusEventUpdate | RailroadUpdate - | ConquestUpdate; + | ConquestUpdate + | EmbargoUpdate; export interface BonusEventUpdate { type: GameUpdateType.BonusEvent; @@ -255,3 +257,10 @@ export interface UnitIncomingUpdate { messageType: MessageType; playerID: number; } + +export interface EmbargoUpdate { + type: GameUpdateType.EmbargoEvent; + event: "start" | "stop"; + playerID: number; + embargoedID: number; +} diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 12973a056..9103c4c6c 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -326,6 +326,10 @@ export class PlayerView { return this.data.embargoes.has(other.id()); } + hasEmbargo(other: PlayerView): boolean { + return this.hasEmbargoAgainst(other) || other.hasEmbargoAgainst(this); + } + profile(): Promise { return this.game.worker.playerProfile(this.smallID()); } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index a57419642..a63e14613 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -642,27 +642,40 @@ export class PlayerImpl implements Player { return !embargo && other.id() !== this.id(); } - addEmbargo(other: PlayerID, isTemporary: boolean): void { - const embargo = this.embargoes.get(other); + getEmbargoes(): Embargo[] { + return [...this.embargoes.values()]; + } + + addEmbargo(other: Player, isTemporary: boolean): void { + const embargo = this.embargoes.get(other.id()); if (embargo !== undefined && !embargo.isTemporary) return; - this.embargoes.set(other, { + this.mg.addUpdate({ + type: GameUpdateType.EmbargoEvent, + event: "start", + playerID: this.smallID(), + embargoedID: other.smallID(), + }); + + this.embargoes.set(other.id(), { createdAt: this.mg.ticks(), isTemporary: isTemporary, target: other, }); } - getEmbargoes(): Embargo[] { - return [...this.embargoes.values()]; + stopEmbargo(other: Player): void { + this.embargoes.delete(other.id()); + this.mg.addUpdate({ + type: GameUpdateType.EmbargoEvent, + event: "stop", + playerID: this.smallID(), + embargoedID: other.smallID(), + }); } - stopEmbargo(other: PlayerID): void { - this.embargoes.delete(other); - } - - endTemporaryEmbargo(other: PlayerID): void { - const embargo = this.embargoes.get(other); + endTemporaryEmbargo(other: Player): void { + const embargo = this.embargoes.get(other.id()); if (embargo !== undefined && !embargo.isTemporary) return; this.stopEmbargo(other);