import { html, LitElement } from "lit"; import { customElement, state } from "lit/decorators.js"; import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg"; import chatIcon from "../../../../resources/images/ChatIconWhite.svg"; import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg"; import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg"; import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg"; import targetIcon from "../../../../resources/images/TargetIconWhite.svg"; import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg"; import { translateText } from "../../../client/Utils"; import { EventBus } from "../../../core/EventBus"; import { AllPlayers, PlayerActions } from "../../../core/game/Game"; import { TileRef } from "../../../core/game/GameMap"; import { GameView, PlayerView } from "../../../core/game/GameView"; import { flattenedEmojiTable } from "../../../core/Util"; import { CloseViewEvent, MouseUpEvent } from "../../InputHandler"; import { SendAllianceRequestIntentEvent, SendBreakAllianceIntentEvent, SendDonateGoldIntentEvent, SendDonateTroopsIntentEvent, SendEmbargoIntentEvent, SendEmojiIntentEvent, SendTargetPlayerIntentEvent, } from "../../Transport"; import { renderNumber, renderTroops } from "../../Utils"; import { UIState } from "../UIState"; import { ChatModal } from "./ChatModal"; import { EmojiTable } from "./EmojiTable"; import { Layer } from "./Layer"; @customElement("player-panel") export class PlayerPanel extends LitElement implements Layer { public g: GameView; public eventBus: EventBus; public emojiTable: EmojiTable; public uiState: UIState; private actions: PlayerActions | null = null; private tile: TileRef | null = null; @state() public isVisible: boolean = false; @state() private allianceExpiryText: string | null = null; public show(actions: PlayerActions, tile: TileRef) { this.actions = actions; this.tile = tile; this.isVisible = true; this.requestUpdate(); } public hide() { this.isVisible = false; this.requestUpdate(); } private handleClose(e: Event) { e.stopPropagation(); this.hide(); } private handleAllianceClick( e: Event, myPlayer: PlayerView, other: PlayerView, ) { e.stopPropagation(); this.eventBus.emit(new SendAllianceRequestIntentEvent(myPlayer, other)); this.hide(); } private handleBreakAllianceClick( e: Event, myPlayer: PlayerView, other: PlayerView, ) { e.stopPropagation(); this.eventBus.emit(new SendBreakAllianceIntentEvent(myPlayer, other)); this.hide(); } private handleDonateTroopClick( e: Event, myPlayer: PlayerView, other: PlayerView, ) { e.stopPropagation(); this.eventBus.emit( new SendDonateTroopsIntentEvent( other, myPlayer.troops() * this.uiState.attackRatio, ), ); this.hide(); } private handleDonateGoldClick( e: Event, myPlayer: PlayerView, other: PlayerView, ) { e.stopPropagation(); this.eventBus.emit(new SendDonateGoldIntentEvent(other, null)); this.hide(); } private handleEmbargoClick( e: Event, myPlayer: PlayerView, other: PlayerView, ) { e.stopPropagation(); this.eventBus.emit(new SendEmbargoIntentEvent(other, "start")); this.hide(); } private handleStopEmbargoClick( e: Event, myPlayer: PlayerView, other: PlayerView, ) { e.stopPropagation(); this.eventBus.emit(new SendEmbargoIntentEvent(other, "stop")); this.hide(); } private handleEmojiClick(e: Event, myPlayer: PlayerView, other: PlayerView) { e.stopPropagation(); this.emojiTable.showTable((emoji: string) => { if (myPlayer === other) { this.eventBus.emit( new SendEmojiIntentEvent( AllPlayers, flattenedEmojiTable.indexOf(emoji), ), ); } else { this.eventBus.emit( new SendEmojiIntentEvent(other, flattenedEmojiTable.indexOf(emoji)), ); } this.emojiTable.hideTable(); this.hide(); }); } private handleChat(e: Event, sender: PlayerView, other: PlayerView) { this.ctModal.open(sender, other); this.hide(); } private handleTargetClick(e: Event, other: PlayerView) { e.stopPropagation(); this.eventBus.emit(new SendTargetPlayerIntentEvent(other.id())); this.hide(); } createRenderRoot() { return this; } private ctModal; init() { this.eventBus.on(MouseUpEvent, (e: MouseEvent) => this.hide()); this.eventBus.on(CloseViewEvent, (e) => { this.hide(); }); this.ctModal = document.querySelector("chat-modal") as ChatModal; } async tick() { if (this.isVisible && this.tile) { const myPlayer = this.g.myPlayer(); if (myPlayer !== null && myPlayer.isAlive()) { this.actions = await myPlayer.actions(this.tile); if (this.actions?.interaction?.allianceExpiresAt !== undefined) { const expiresAt = this.actions.interaction.allianceExpiresAt; const remainingTicks = expiresAt - this.g.ticks(); if (remainingTicks > 0) { const remainingSeconds = Math.max( 0, Math.floor(remainingTicks / 10), ); // 10 ticks per second this.allianceExpiryText = this.formatDuration(remainingSeconds); } } else { this.allianceExpiryText = null; } this.requestUpdate(); } } } private formatDuration(totalSeconds: number): string { if (totalSeconds <= 0) return "0s"; const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; let time = ""; if (minutes > 0) time += `${minutes}m `; time += `${seconds}s`; return time.trim(); } render() { if (!this.isVisible) { return html``; } const myPlayer = this.g.myPlayer(); if (myPlayer === null) return; if (this.tile === null) return; let other = this.g.owner(this.tile); if (!other.isPlayer()) { this.hide(); console.warn("Tile is not owned by a player"); return; } other = other as PlayerView; const canDonate = this.actions?.interaction?.canDonate; const canSendAllianceRequest = this.actions?.interaction?.canSendAllianceRequest; const canSendEmoji = other === myPlayer ? this.actions?.canSendEmojiAllPlayers : this.actions?.interaction?.canSendEmoji; const canBreakAlliance = this.actions?.interaction?.canBreakAlliance; const canTarget = this.actions?.interaction?.canTarget; const canEmbargo = this.actions?.interaction?.canEmbargo; return html`