diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 5cb140ef6..db2f00234 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -2,11 +2,14 @@ import { LitElement, html, css } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { EventBus } from "../../../core/EventBus"; import { + AllianceExpiredUpdate, AllianceRequestReplyUpdate, AllianceRequestUpdate, AllPlayers, + BrokeAllianceUpdate, DisplayMessageUpdate, EmojiUpdate, + GameUpdateType, MessageType, TargetPlayerUpdate, } from "../../../core/game/Game"; @@ -139,6 +142,14 @@ export class EventsDisplay extends LitElement implements Layer { } `; + private updateMap = new Map([ + [GameUpdateType.DisplayEvent, u => this.onDisplayMessageEvent(u)], + [GameUpdateType.AllianceRequestReply, u => this.onAllianceRequestReplyEvent(u)], + [GameUpdateType.BrokeAlliance, u => this.onBrokeAllianceEvent(u)], + [GameUpdateType.TargetPlayer, u => this.onTargetPlayerEvent(u)], + [GameUpdateType.EmojiUpdate, u => this.onEmojiMessageEvent(u)] + ]) + constructor() { super(); this.events = []; @@ -148,6 +159,12 @@ export class EventsDisplay extends LitElement implements Layer { } tick() { + const updates = this.game.updatesSinceLastTick() + for (const [ut, fn] of this.updateMap) { + updates[ut]?.forEach(u => fn(u)) + } + + let remainingEvents = this.events.filter(event => { const shouldKeep = this.game.ticks() - event.createdAt < 80; if (!shouldKeep && event.onDelete) { @@ -235,96 +252,102 @@ export class EventsDisplay extends LitElement implements Layer { }); } - // onAllianceRequestReplyEvent(event: AllianceRequestReplyUpdate) { - // const myPlayer = this.game.playerByClientID(this.clientID); - // if (!myPlayer || event.allianceRequest.requestor() !== myPlayer) { - // return; - // } + onAllianceRequestReplyEvent(update: AllianceRequestReplyUpdate) { + const myPlayer = this.game.playerByClientID(this.clientID); + if (!myPlayer || update.request.requestorID !== myPlayer.smallID()) { + return; + } - // this.addEvent({ - // description: `${event.allianceRequest.recipient().name()} ${event.accepted ? "accepted" : "rejected"} your alliance request`, - // type: event.accepted ? MessageType.SUCCESS : MessageType.ERROR, - // highlight: true, - // createdAt: this.game.ticks(), - // }); - // } + const recipient = this.game.playerBySmallID(update.request.recipientID) - // onBrokeAllianceEvent(event: BrokeAllianceEvent) { - // const myPlayer = this.game.playerByClientID(this.clientID); - // if (!myPlayer) return; + this.addEvent({ + description: `${recipient.name()} ${update.accepted ? "accepted" : "rejected"} your alliance request`, + type: update.accepted ? MessageType.SUCCESS : MessageType.ERROR, + highlight: true, + createdAt: this.game.ticks(), + }); + } - // if (!event.betrayed.isTraitor() && event.traitor === myPlayer) { - // this.addEvent({ - // description: `You broke your alliance with ${event.betrayed.name()}, making you a TRAITOR`, - // type: MessageType.ERROR, - // highlight: true, - // createdAt: this.game.ticks(), - // }); - // } else if (event.betrayed === myPlayer) { - // this.addEvent({ - // description: `${event.traitor.name()}, broke their alliance with you`, - // type: MessageType.ERROR, - // highlight: true, - // createdAt: this.game.ticks(), - // }); - // } - // } + onBrokeAllianceEvent(update: BrokeAllianceUpdate) { + const myPlayer = this.game.playerByClientID(this.clientID); + if (!myPlayer) return; - // onAllianceExpiredEvent(event: AllianceExpiredEvent) { - // const myPlayer = this.game.playerByClientID(this.clientID); - // if (!myPlayer) return; + const betrayed = this.game.playerBySmallID(update.betrayedID) + const traitor = this.game.playerBySmallID(update.traitorID) - // const other = event.player1 === myPlayer ? event.player2 : event.player2 === myPlayer ? event.player1 : null; - // if (!other || !myPlayer.isAlive() || !other.isAlive()) return; + if (!betrayed.isTraitor() && traitor === myPlayer) { + this.addEvent({ + description: `You broke your alliance with ${betrayed.name()}, making you a TRAITOR`, + type: MessageType.ERROR, + highlight: true, + createdAt: this.game.ticks(), + }); + } else if (betrayed === myPlayer) { + this.addEvent({ + description: `${traitor.name()}, broke their alliance with you`, + type: MessageType.ERROR, + highlight: true, + createdAt: this.game.ticks(), + }); + } + } - // this.addEvent({ - // description: `Your alliance with ${other.name()} expired`, - // type: MessageType.WARN, - // highlight: true, - // createdAt: this.game.ticks(), - // }); - // } + onAllianceExpiredEvent(update: AllianceExpiredUpdate) { + const myPlayer = this.game.playerByClientID(this.clientID); + if (!myPlayer) return; - // onTargetPlayerEvent(event: TargetPlayerUpdate) { - // const other = this.game.playerBySmallID(event.playerID) - // const myPlayer = this.game.playerByClientID(this.clientID); - // if (!myPlayer || !myPlayer.isAlliedWith(other)) return; + const otherID = update.player1ID === myPlayer.smallID() ? update.player2ID : update.player2ID === myPlayer.smallID() ? update.player1ID : null; + const other = this.game.playerBySmallID(otherID) + if (!other || !myPlayer.isAlive() || !other.isAlive()) return; - // const target = this.game.playerBySmallID(event.targetID) + this.addEvent({ + description: `Your alliance with ${other.name()} expired`, + type: MessageType.WARN, + highlight: true, + createdAt: this.game.ticks(), + }); + } - // this.addEvent({ - // description: `${other.name()} requests you attack ${target.name()}`, - // type: MessageType.INFO, - // highlight: true, - // createdAt: this.game.ticks(), - // }); - // } + onTargetPlayerEvent(event: TargetPlayerUpdate) { + const other = this.game.playerBySmallID(event.playerID) + const myPlayer = this.game.playerByClientID(this.clientID); + if (!myPlayer || !myPlayer.isAlliedWith(other)) return; - // onEmojiMessageEvent(update: EmojiUpdate) { - // const myPlayer = this.game.playerByClientID(this.clientID); - // if (!myPlayer) return; + const target = this.game.playerBySmallID(event.targetID) - // const recipient = update.recipientID == AllPlayers ? AllPlayers : this.game.playerBySmallID(update.recipientID) - // const sender = this.game.playerBySmallID(update.senderID) + this.addEvent({ + description: `${other.name()} requests you attack ${target.name()}`, + type: MessageType.INFO, + highlight: true, + createdAt: this.game.ticks(), + }); + } - // if (recipient == myPlayer) { - // this.addEvent({ - // description: `${sender.displayName()}:${update.message}`, - // unsafeDescription: true, - // type: MessageType.INFO, - // highlight: true, - // createdAt: this.game.ticks(), - // }); - // } else if (sender === myPlayer && recipient !== AllPlayers) { - // this.addEvent({ - // description: `Sent ${recipient.displayName()}: ${update.message}`, - // unsafeDescription: true, - // type: MessageType.INFO, - // highlight: true, - // createdAt: this.game.ticks(), - // }); - // } - // } + onEmojiMessageEvent(update: EmojiUpdate) { + const myPlayer = this.game.playerByClientID(this.clientID); + if (!myPlayer) return; + + const recipient = update.recipientID == AllPlayers ? AllPlayers : this.game.playerBySmallID(update.recipientID) + const sender = this.game.playerBySmallID(update.senderID) + + if (recipient == myPlayer) { + this.addEvent({ + description: `${sender.displayName()}:${update.message}`, + unsafeDescription: true, + type: MessageType.INFO, + highlight: true, + createdAt: this.game.ticks(), + }); + } else if (sender === myPlayer && recipient !== AllPlayers) { + this.addEvent({ + description: `Sent ${recipient.displayName()}: ${update.message}`, + unsafeDescription: true, + type: MessageType.INFO, + highlight: true, + createdAt: this.game.ticks(), + }); + } + } render() { if (this.events.length === 0) { diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index c34372056..4f45dcc7e 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -4,7 +4,7 @@ import { getConfig } from "./configuration/Config"; import { EventBus } from "./EventBus"; import { Executor } from "./execution/ExecutionManager"; import { WinCheckExecution } from "./execution/WinCheckExecution"; -import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, MutableTile, NameViewData, Player, PlayerActions, PlayerID, Tile, TileUpdate, UnitType, UnitUpdate } from "./game/Game"; +import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, MutableTile, NameViewData, Player, PlayerActions, PlayerID, PlayerProfile, Tile, TileUpdate, UnitType, UnitUpdate } from "./game/Game"; import { createGame } from "./game/GameImpl"; import { loadTerrainMap } from "./game/TerrainMapLoader"; import { GameConfig, Turn } from "./Schemas"; @@ -105,6 +105,12 @@ export class GameRunner { return actions } + public playerProfile(playerID: number): PlayerProfile { + return { + relations: this.game.players().filter(p => p.smallID() == playerID)[0]?.allRelationsSorted() + } + } + private canBoat(myPlayer: Player, tile: Tile): boolean { const other = tile.owner() if (myPlayer.units(UnitType.TransportShip).length >= this.game.config().boatMaxNumber()) { diff --git a/src/core/GameView.ts b/src/core/GameView.ts index 64ac806c5..c6f39c222 100644 --- a/src/core/GameView.ts +++ b/src/core/GameView.ts @@ -271,6 +271,10 @@ export class GameView { } } + public updatesSinceLastTick(): GameUpdates { + return this.lastUpdate.updates + } + public update(gu: GameUpdateViewData) { this.lastUpdate = gu diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 17405f42a..e8b24d50f 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -412,6 +412,11 @@ export interface PlayerActions { interaction?: PlayerInteraction } +export interface PlayerProfile { + relations: Record + // TODO: add alliances etc +} + export interface PlayerInteraction { sharedBorder: boolean canSendEmoji: boolean @@ -495,8 +500,8 @@ export interface BrokeAllianceUpdate { export interface AllianceExpiredUpdate { type: GameUpdateType.AllianceExpired - player1: number - player2: number + player1ID: number + player2ID: number } export interface TargetPlayerUpdate { diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index dca2fcd60..ae63d8ea6 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -473,8 +473,8 @@ export class GameImpl implements MutableGame { this.alliances_ = this.alliances_.filter(a => a != alliances[0]) this.addUpdate({ type: GameUpdateType.AllianceExpired, - player1: alliance.requestor().smallID(), - player2: alliance.recipient().smallID() + player1ID: alliance.requestor().smallID(), + player2ID: alliance.recipient().smallID() }) } diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 66cfb0728..9f7ec81ff 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -77,6 +77,24 @@ ctx.addEventListener('message', async (e: MessageEvent) => { throw error; } break; + case 'player_profile': + if (!gameRunner) { + throw new Error('Game runner not initialized'); + } + + try { + const actions = (await gameRunner).playerActions(message.playerID, message.x, message.y) + sendMessage({ + type: 'player_actions_result', + id: message.id, + result: actions + } as PlayerActionsResultMessage); + } catch (error) { + console.error('Failed to check borders:', error); + throw error; + } + break; + default: console.warn('Unknown message :', message); diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index ebb511eed..8e6da0acb 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -1,4 +1,4 @@ -import { PlayerActions, PlayerID, Tile } from "../game/Game"; +import { PlayerActions, PlayerID, PlayerInfo, PlayerProfile, Tile } from "../game/Game"; import { GameUpdateViewData } from "../GameView"; import { GameConfig, GameID, Turn } from "../Schemas"; import { generateID } from "../Util"; @@ -91,6 +91,30 @@ export class WorkerClient { }); } + playerInfo(playerID: number): Promise { + return new Promise((resolve, reject) => { + if (!this.isInitialized) { + reject(new Error('Worker not initialized')); + return; + } + + const messageId = generateID() + + this.messageHandlers.set(messageId, (message) => { + if (message.type === 'player_profile_result' && message.result !== undefined) { + resolve(message.result); + } + }); + + this.worker.postMessage({ + type: 'player_profile', + id: messageId, + playerID: playerID, + }); + + }) + } + playerInteraction(playerID: PlayerID, tile: Tile): Promise { return new Promise((resolve, reject) => { if (!this.isInitialized) { diff --git a/src/core/worker/WorkerMessages.ts b/src/core/worker/WorkerMessages.ts index 2359f32bd..072784848 100644 --- a/src/core/worker/WorkerMessages.ts +++ b/src/core/worker/WorkerMessages.ts @@ -1,6 +1,6 @@ import { GameUpdateViewData } from "../GameView"; import { GameConfig, GameID, Turn } from "../Schemas"; -import { PlayerActions, PlayerID } from "../game/Game"; +import { PlayerActions, PlayerID, PlayerProfile } from "../game/Game"; export type WorkerMessageType = | 'heartbeat' @@ -9,7 +9,9 @@ export type WorkerMessageType = | 'turn' | 'game_update' | 'player_actions' - | 'player_actions_result'; + | 'player_actions_result' + | 'player_profile' + | 'player_profile_result' // Base interface for all messages interface BaseWorkerMessage { @@ -55,8 +57,18 @@ export interface PlayerActionsResultMessage extends BaseWorkerMessage { result: PlayerActions; } +export interface PlayerProfileMessage extends BaseWorkerMessage { + type: 'player_profile' + playerID: number +} + +export interface PlayerProfileResultMessage extends BaseWorkerMessage { + type: 'player_profile_result' + result: PlayerProfile +} + // Union types for type safety -export type MainThreadMessage = HeartbeatMessage | InitMessage | TurnMessage | PlayerActionsMessage +export type MainThreadMessage = HeartbeatMessage | InitMessage | TurnMessage | PlayerActionsMessage | PlayerProfileMessage // Message send from worker -export type WorkerMessage = InitializedMessage | GameUpdateMessage | PlayerActionsResultMessage; \ No newline at end of file +export type WorkerMessage = InitializedMessage | GameUpdateMessage | PlayerActionsResultMessage | PlayerProfileResultMessage \ No newline at end of file