diff --git a/src/client/graphics/layers/ControlPanel2.ts b/src/client/graphics/layers/ControlPanel2.ts index af737c0ae..1da67e2cc 100644 --- a/src/client/graphics/layers/ControlPanel2.ts +++ b/src/client/graphics/layers/ControlPanel2.ts @@ -33,12 +33,8 @@ import { ToggleUpgradeModeEvent } from "../../events/ToggleUpgradeModeEvent"; import { AttackRatioEvent } from "../../InputHandler"; import "../../StatisticsModal"; // ensure statistics modal is registered import { - SendAllianceRequestIntentEvent, SendBomberIntentEvent, - SendBreakAllianceIntentEvent, - SendDeclareWarIntentEvent, SendEmbargoIntentEvent, - SendPeaceRequestIntentEvent, SendSetInvestmentRateEvent, SendSetResearchInvestmentEvent, SendSetRoadInvestmentEvent, @@ -104,8 +100,7 @@ export class ControlPanel2 extends LitElement implements Layer { private init_: boolean = false; @state() - private activeTab: "Build" | "Attack" | "Economy" | "Trade" | "Diplomacy" = - "Build"; + private activeTab: "Build" | "Attack" | "Economy" | "Trade" = "Build"; @state() private _hasAirfields: boolean = false; @@ -862,9 +857,7 @@ export class ControlPanel2 extends LitElement implements Layer { return el; } - private _changeTab( - tab: "Build" | "Attack" | "Economy" | "Trade" | "Diplomacy", - ) { + private _changeTab(tab: "Build" | "Attack" | "Economy" | "Trade") { this.activeTab = tab; if (this.uiState.pendingBuildUnitType) { this.uiState.pendingBuildUnitType = null; @@ -1128,16 +1121,6 @@ export class ControlPanel2 extends LitElement implements Layer { > Trade -
`; @@ -1899,220 +1881,8 @@ export class ControlPanel2 extends LitElement implements Layer { } private renderDiplomacyTab() { - const me = this.game.myPlayer(); - if (!me) return html``; - - const players = this.game - .players() - .filter( - (p) => - p.isAlive() && - p.id() !== me.id() && - (p.type() === PlayerType.Human || p.type() === PlayerType.FakeHuman), - ); - - // Icons and colors reused from radial menu - const warIcon = "/images/waricon.png"; - const peaceIcon = "/images/dove.png"; - const allianceIcon = "/images/AllianceIconWhite.svg"; - const traitorIcon = "/images/TraitorIconWhite.svg"; - - // Colors matching radial menu - const warColor = "#8B0000"; // dark red for declare war - const peaceColor = "#e5e7eb"; // light gray for peace - const allianceColor = "#53ac75"; // green for alliance - const betrayColor = "#c74848"; // red for betray - - const iconBtn = ( - src: string, - bgColor: string, - titleKey: string, - onClick: () => void, - ) => html` - - `; - - const renderName = (p: PlayerView) => html` -
- ${p.name()} -
- `; - - const renderBtn = (btn: ReturnType) => html` -
${btn}
- `; - - const renderEmpty = () => html`
 
`; - - // Build rows for each player - const rows = players.map((p) => { - const atWar = me.isAtWarWith(p); - const allied = me.isAlliedWith(p); - const neutral = !atWar && !allied; - - // At War column cell - let atWarCell; - if (atWar) { - atWarCell = renderName(p); - } else if (neutral) { - atWarCell = renderBtn( - iconBtn( - warIcon, - warColor, - "control_panel2.diplomacy_declare_war_tooltip", - () => this.eventBus.emit(new SendDeclareWarIntentEvent(me, p)), - ), - ); - } else if (allied) { - atWarCell = renderBtn( - iconBtn( - traitorIcon, - betrayColor, - "control_panel2.diplomacy_betray_tooltip", - () => this.eventBus.emit(new SendBreakAllianceIntentEvent(me, p)), - ), - ); - } else { - atWarCell = renderEmpty(); - } - - // Allied column cell - let alliedCell; - if (allied) { - alliedCell = renderName(p); - } else { - // Can request alliance from both neutral and at-war players - alliedCell = renderBtn( - iconBtn( - allianceIcon, - allianceColor, - "control_panel2.diplomacy_request_alliance_tooltip", - () => this.eventBus.emit(new SendAllianceRequestIntentEvent(me, p)), - ), - ); - } - - // Neutral column cell - let neutralCell; - if (neutral) { - neutralCell = renderName(p); - } else if (atWar) { - neutralCell = renderBtn( - iconBtn( - peaceIcon, - peaceColor, - "control_panel2.diplomacy_request_peace_tooltip", - () => this.eventBus.emit(new SendPeaceRequestIntentEvent(me, p)), - ), - ); - } else { - neutralCell = renderEmpty(); - } - - return html` -
-
- ${atWarCell} -
-
- ${alliedCell} -
-
- ${neutralCell} -
-
- `; - }); - - // Bulk action handlers - const declareWarOnAll = () => { - players.forEach((p) => { - if (me.isAlliedWith(p)) { - // Break alliance first (betray), then declare war - this.eventBus.emit(new SendBreakAllianceIntentEvent(me, p)); - } - if (!me.isAtWarWith(p)) { - this.eventBus.emit(new SendDeclareWarIntentEvent(me, p)); - } - }); - }; - - const requestAllianceWithAll = () => { - players.forEach((p) => { - if (!me.isAlliedWith(p)) { - this.eventBus.emit(new SendAllianceRequestIntentEvent(me, p)); - } - }); - }; - - const requestPeaceWithAll = () => { - players.forEach((p) => { - if (me.isAtWarWith(p)) { - this.eventBus.emit(new SendPeaceRequestIntentEvent(me, p)); - } - }); - }; - - // Small icon button for header bulk actions - const headerBtn = ( - icon: string, - bgColor: string, - titleKey: string, - onClick: () => void, - ) => html` - - `; - - return html` -
- -
-
- At War - ${headerBtn( - warIcon, - warColor, - "control_panel2.diplomacy_war_all_tooltip", - declareWarOnAll, - )} -
-
- Allied - ${headerBtn( - allianceIcon, - allianceColor, - "control_panel2.diplomacy_ally_all_tooltip", - requestAllianceWithAll, - )} -
-
- Neutral - ${headerBtn( - peaceIcon, - peaceColor, - "control_panel2.diplomacy_peace_all_tooltip", - requestPeaceWithAll, - )} -
-
- - ${rows} -
- `; + // Diplomacy tab removed - relations now shown via NameLayer icons + return html``; } private _handleEmbargoAll() { diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index 8768bfcba..92a5c3f28 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -1,3 +1,4 @@ +import doveIcon from "../../../../proprietary/images/dove.png"; import allianceIcon from "../../../../resources/images/AllianceIcon.svg"; import allianceRequestBlackIcon from "../../../../resources/images/AllianceRequestBlackIcon.svg"; import allianceRequestWhiteIcon from "../../../../resources/images/AllianceRequestWhiteIcon.svg"; @@ -8,12 +9,18 @@ import embargoWhiteIcon from "../../../../resources/images/EmbargoWhiteIcon.svg" import nukeRedIcon from "../../../../resources/images/NukeIconRed.svg"; import nukeWhiteIcon from "../../../../resources/images/NukeIconWhite.svg"; import shieldIcon from "../../../../resources/images/ShieldIconBlack.svg"; +import swordIconBlack from "../../../../resources/images/SwordIcon.svg"; import targetIcon from "../../../../resources/images/TargetIcon.svg"; import traitorIcon from "../../../../resources/images/TraitorIcon.svg"; import { EventBus } from "../../../core/EventBus"; import { PseudoRandom } from "../../../core/PseudoRandom"; import { Theme } from "../../../core/configuration/Config"; -import { AllPlayers, Cell, nukeTypes } from "../../../core/game/Game"; +import { + AllPlayers, + Cell, + nukeTypes, + PlayerType, +} from "../../../core/game/Game"; import { GameView, PlayerView } from "../../../core/game/GameView"; import { UserSettings } from "../../../core/game/UserSettings"; import { AlternateViewEvent } from "../../InputHandler"; @@ -55,6 +62,8 @@ export class NameLayer implements Layer { private nukeWhiteIconImage: HTMLImageElement; private nukeRedIconImage: HTMLImageElement; private shieldIconImage: HTMLImageElement; + private warIconImage: HTMLImageElement; + private doveIconImage: HTMLImageElement; private container: HTMLDivElement; private firstPlace: PlayerView | null = null; private theme: Theme = this.game.config().theme(); @@ -90,6 +99,10 @@ export class NameLayer implements Layer { this.nukeRedIconImage.src = nukeRedIcon; this.shieldIconImage = new Image(); this.shieldIconImage.src = shieldIcon; + this.warIconImage = new Image(); + this.warIconImage.src = swordIconBlack; + this.doveIconImage = new Image(); + this.doveIconImage.src = doveIcon; } resizeCanvas() { @@ -424,7 +437,16 @@ export class NameLayer implements Layer { // Alliance icon const existingAlliance = iconsDiv.querySelector('[data-icon="alliance"]'); - if (myPlayer !== null && myPlayer.isAlliedWith(render.player)) { + const isSelf = myPlayer !== null && render.player === myPlayer; + const isHumanOrFakeHuman = + render.player.type() === PlayerType.Human || + render.player.type() === PlayerType.FakeHuman; + if ( + !isSelf && + isHumanOrFakeHuman && + myPlayer !== null && + myPlayer.isAlliedWith(render.player) + ) { if (!existingAlliance) { iconsDiv.appendChild( this.createIconElement( @@ -438,6 +460,41 @@ export class NameLayer implements Layer { existingAlliance.remove(); } + // War icon + const existingWar = iconsDiv.querySelector('[data-icon="war"]'); + if ( + !isSelf && + isHumanOrFakeHuman && + myPlayer !== null && + myPlayer.isAtWarWith(render.player) + ) { + if (!existingWar) { + iconsDiv.appendChild( + this.createIconElement(this.warIconImage.src, iconSize, "war"), + ); + } + } else if (existingWar) { + existingWar.remove(); + } + + // Neutral icon + const existingNeutral = iconsDiv.querySelector('[data-icon="neutral"]'); + if ( + !isSelf && + isHumanOrFakeHuman && + myPlayer !== null && + !myPlayer.isAlliedWith(render.player) && + !myPlayer.isAtWarWith(render.player) + ) { + if (!existingNeutral) { + iconsDiv.appendChild( + this.createIconElement(this.doveIconImage.src, iconSize, "neutral"), + ); + } + } else if (existingNeutral) { + existingNeutral.remove(); + } + // Alliance request icon let existingRequestAlliance = iconsDiv.querySelector( '[data-icon="alliance-request"]', @@ -584,12 +641,6 @@ export class NameLayer implements Layer { iconsDiv.appendChild(this.createIconElement(icon, iconSize, "nuke")); } } - // Update all icon sizes - const icons = iconsDiv.getElementsByTagName("img"); - for (const icon of icons) { - icon.style.width = `${iconSize}px`; - icon.style.height = `${iconSize}px`; - } // Position element with scale if (render.location && render.location !== oldLocation) { @@ -606,10 +657,29 @@ export class NameLayer implements Layer { ): HTMLImageElement { const icon = document.createElement("img"); icon.src = src; - icon.style.width = `${size}px`; - icon.style.height = `${size}px`; + + // Make war icon 20% smaller + const actualSize = id === "war" ? size * 0.8 : size; + icon.style.width = `${actualSize}px`; + icon.style.height = `${actualSize}px`; icon.setAttribute("data-icon", id); icon.setAttribute("dark-mode", this.userSettings.darkMode().toString()); + + if (id === "war") { + // Use CSS mask with exact warColor #8B0000 from radial menu + icon.style.backgroundColor = "#8B0000"; + icon.style.webkitMaskImage = `url(${src})`; + icon.style.maskImage = `url(${src})`; + icon.style.webkitMaskSize = "contain"; + icon.style.maskSize = "contain"; + icon.style.webkitMaskRepeat = "no-repeat"; + icon.style.maskRepeat = "no-repeat"; + icon.style.webkitMaskPosition = "center"; + icon.style.maskPosition = "center"; + // Clear the src to prevent img from loading + icon.removeAttribute("src"); + } + if (center) { icon.style.position = "absolute"; icon.style.top = "50%"; diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index 80ab23d01..61d874ffc 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -178,11 +178,24 @@ export class PlayerInfoOverlay extends LitElement implements Layer { let displayRelation = false; let relationClass = ""; let relationName = ""; + // Icons are not shown in overlay; text only if (myPlayer.isFriendly(player)) { relationClass = this.getRelationClass(Relation.Friendly); relationName = translateText("relation.allied"); displayRelation = true; + } else if (myPlayer.isAtWarWith(player)) { + relationClass = "text-red-500"; + relationName = translateText("relation.hostile"); + displayRelation = true; + } else if ( + !myPlayer.isAlliedWith(player) && + !myPlayer.isAtWarWith(player) + ) { + // Neutral + relationClass = "text-yellow-300"; + relationName = translateText("relation.neutral"); + displayRelation = true; } else if (player.type() === PlayerType.FakeHuman) { const relation = this.playerProfile?.relations[myPlayer.smallID()] ?? Relation.Neutral;