diff --git a/TODO.txt b/TODO.txt index a70562bf3..3043dfd36 100644 --- a/TODO.txt +++ b/TODO.txt @@ -131,7 +131,11 @@ * eventbox events dissapear after timeout DONE 9/20/2024 * auto reject alliance when event dissapears DONE 9/20/2024 * first place has crown DONE 9/20/2024 -* make fake humans easier +* can't attack ally +* make alliances expire +* color code events +* add updates to eventbox: boats (max count, too far) +* broken alliances * BUG: FakeHuman don't be enemy if don't share border * BUG: when send boat only captures one pixel * store cookies diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index 879d62502..a49470bcc 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -10,7 +10,7 @@ import {TerrainMap} from "../core/game/TerrainMapLoader"; import {and, bfs, dist, manhattanDist} from "../core/Util"; import {TerrainLayer} from "./graphics/layers/TerrainLayer"; import {WinCheckExecution} from "../core/execution/WinCheckExecution"; -import {SendAllianceRequestUIEvent} from "./graphics/layers/UILayer"; +import {SendAllianceRequestUIEvent, SendBreakAllianceUIEvent} from "./graphics/layers/UILayer"; import {createCanvas} from "./graphics/Utils"; import {AllianceRequestReplyUIEvent as SendAllianceRequestReplyUIEvent} from "./graphics/layers/EventsDisplay"; @@ -127,6 +127,7 @@ export class ClientGame { this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e)) this.eventBus.on(SendAllianceRequestUIEvent, (e) => this.onSendAllianceRequest(e)) this.eventBus.on(SendAllianceRequestReplyUIEvent, (e) => this.onAllianceRequestReplyUIEvent(e)) + this.eventBus.on(SendBreakAllianceUIEvent, (e) => this.onBreakAllianceRequestUIEvent(e)) this.renderer.initialize() this.input.initialize() @@ -294,6 +295,15 @@ export class ClientGame { }) } + private onBreakAllianceRequestUIEvent(event: SendBreakAllianceUIEvent) { + this.sendIntent({ + type: "breakAlliance", + clientID: this.id, + requestor: event.requestor.id(), + recipient: event.recipient.id(), + }) + } + private sendSpawnIntent(cell: Cell) { this.sendIntent({ type: "spawn", diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 10ff5625e..209626d3d 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -14,6 +14,14 @@ export class SendAllianceRequestUIEvent implements GameEvent { ) { } } + +export class SendBreakAllianceUIEvent implements GameEvent { + constructor( + public readonly requestor: Player, + public readonly recipient: Player + ) { } +} + interface MenuOption { label: string; action: () => void; @@ -236,34 +244,48 @@ export class UILayer implements Layer { if (!tile.hasOwner()) { return } + const options: MenuOption[] = [] const owner = tile.owner() as Player if (owner.clientID() == this.clientID) { return } - // TODO: check if already allied with - const myPlayer = this.game.players().find(p => p.clientID() == this.clientID) if (!myPlayer) { console.warn('my player not found') return } - if (myPlayer.alliedWith(owner) || myPlayer.pendingAllianceRequestWith(owner)) { + + if (myPlayer.pendingAllianceRequestWith(owner)) { return } - this.customMenu!.style.display = 'block'; - this.customMenu!.style.left = `${e.x}px`; - this.customMenu!.style.top = `${e.y}px`; - this.populateMenu([ - { + if (myPlayer.alliedWith(owner)) { + options.push({ + label: "Break Alliance", + action: (): void => { + this.eventBus.emit( + new SendBreakAllianceUIEvent(myPlayer, owner) + ) + }, + }) + } else { + options.push({ label: "Request Alliance", action: (): void => { this.eventBus.emit( new SendAllianceRequestUIEvent(myPlayer, owner) ) }, - } - ]) + }) + } + + this.customMenu!.style.display = 'block'; + this.customMenu!.style.left = `${e.x}px`; + this.customMenu!.style.top = `${e.y}px`; + + + + this.populateMenu(options) } private populateMenu(options: MenuOption[]) { diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index e6a0d658e..aa7a5a49a 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -4,7 +4,13 @@ import {PlayerType} from './game/Game'; export type GameID = string export type ClientID = string -export type Intent = SpawnIntent | AttackIntent | BoatAttackIntent | UpdateNameIntent | AllianceRequestIntent | AllianceRequestReplyIntent +export type Intent = SpawnIntent + | AttackIntent + | BoatAttackIntent + | UpdateNameIntent + | AllianceRequestIntent + | AllianceRequestReplyIntent + | BreakAllianceIntent export type AttackIntent = z.infer export type SpawnIntent = z.infer @@ -12,6 +18,7 @@ export type BoatAttackIntent = z.infer export type UpdateNameIntent = z.infer export type AllianceRequestIntent = z.infer export type AllianceRequestReplyIntent = z.infer +export type BreakAllianceIntent = z.infer export type Turn = z.infer @@ -91,13 +98,20 @@ export const AllianceRequestReplyIntentSchema = BaseIntentSchema.extend({ accept: z.boolean(), }) +export const BreakAllianceIntentSchema = BaseIntentSchema.extend({ + type: z.literal('breakAlliance'), + requestor: z.string(), // The one who made the original alliance request + recipient: z.string(), +}) + const IntentSchema = z.union([ AttackIntentSchema, SpawnIntentSchema, BoatAttackIntentSchema, UpdateNameIntentSchema, AllianceRequestIntentSchema, - AllianceRequestReplyIntentSchema + AllianceRequestReplyIntentSchema, + BreakAllianceIntentSchema ]); const TurnSchema = z.object({ diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts index 36f148a72..85eaa3999 100644 --- a/src/core/execution/ExecutionManager.ts +++ b/src/core/execution/ExecutionManager.ts @@ -9,8 +9,9 @@ import {UpdateNameExecution} from "./UpdateNameExecution"; import {FakeHumanExecution} from "./FakeHumanExecution"; import Usernames from '../../../resources/Usernames.txt' import {simpleHash} from "../Util"; -import {AllianceRequestExecution} from "./AllianceRequestExecution"; -import {AllianceRequestReplyExecution} from "./AllianceRequestReplyExecution"; +import {AllianceRequestExecution} from "./alliance/AllianceRequestExecution"; +import {AllianceRequestReplyExecution} from "./alliance/AllianceRequestReplyExecution"; +import {BreakAllianceExecution} from "./alliance/BreakAllianceExecution"; @@ -61,6 +62,8 @@ export class Executor { return new AllianceRequestExecution(intent.requestor, intent.recipient) } else if (intent.type == "allianceRequestReply") { return new AllianceRequestReplyExecution(intent.requestor, intent.recipient, intent.accept) + } else if (intent.type == "breakAlliance") { + return new BreakAllianceExecution(intent.requestor, intent.recipient) } else { throw new Error(`intent type ${intent} not found`) diff --git a/src/core/execution/AllianceRequestExecution.ts b/src/core/execution/alliance/AllianceRequestExecution.ts similarity index 95% rename from src/core/execution/AllianceRequestExecution.ts rename to src/core/execution/alliance/AllianceRequestExecution.ts index 90931ae58..a245c1d30 100644 --- a/src/core/execution/AllianceRequestExecution.ts +++ b/src/core/execution/alliance/AllianceRequestExecution.ts @@ -1,4 +1,4 @@ -import {AllianceRequest, Execution, MutableGame, MutablePlayer, Player, PlayerID} from "../game/Game"; +import {AllianceRequest, Execution, MutableGame, MutablePlayer, Player, PlayerID} from "../../game/Game"; export class AllianceRequestExecution implements Execution { private active = true diff --git a/src/core/execution/AllianceRequestReplyExecution.ts b/src/core/execution/alliance/AllianceRequestReplyExecution.ts similarity index 96% rename from src/core/execution/AllianceRequestReplyExecution.ts rename to src/core/execution/alliance/AllianceRequestReplyExecution.ts index f3a74be73..d1d3d67b0 100644 --- a/src/core/execution/AllianceRequestReplyExecution.ts +++ b/src/core/execution/alliance/AllianceRequestReplyExecution.ts @@ -1,4 +1,4 @@ -import {AllianceRequest, Execution, MutableGame, MutablePlayer, Player, PlayerID} from "../game/Game"; +import {AllianceRequest, Execution, MutableGame, MutablePlayer, Player, PlayerID} from "../../game/Game"; export class AllianceRequestReplyExecution implements Execution { private active = true diff --git a/src/core/execution/alliance/BreakAllianceExecution.ts b/src/core/execution/alliance/BreakAllianceExecution.ts new file mode 100644 index 000000000..d1552091f --- /dev/null +++ b/src/core/execution/alliance/BreakAllianceExecution.ts @@ -0,0 +1,35 @@ +import {AllianceRequest, Execution, MutableGame, MutablePlayer, Player, PlayerID} from "../../game/Game"; + +export class BreakAllianceExecution implements Execution { + private active = true + private requestor: MutablePlayer; + private recipient: MutablePlayer + + constructor(private requestorID: PlayerID, private recipientID: PlayerID) { } + + init(mg: MutableGame, ticks: number): void { + this.requestor = mg.player(this.requestorID) + this.recipient = mg.player(this.recipientID) + } + + tick(ticks: number): void { + if (!this.requestor.alliedWith(this.recipient)) { + console.warn('cant break alliance, not allied') + } else { + this.requestor.breakAllianceWith(this.recipient) + } + this.active = false + } + + owner(): MutablePlayer { + return null + } + + isActive(): boolean { + return this.active + } + + activeDuringSpawnPhase(): boolean { + return false + } +} \ No newline at end of file