From a2a6654bf5909db5ca504c878a6f0799c610dd6c Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 22 Nov 2024 20:28:58 -0800 Subject: [PATCH] fixed bug where each intent was duplicated destroyers find and capture trade ships ports generate more trade ships --- src/client/ClientGame.ts | 6 +- .../graphics/layers/radial/BuildMenu.ts | 2 + src/core/configuration/Config.ts | 4 +- src/core/configuration/DefaultConfig.ts | 11 +++- src/core/configuration/DevConfig.ts | 11 ++-- src/core/execution/DestroyerExecution.ts | 17 +++++- src/core/execution/PortExecution.ts | 2 +- src/core/execution/TradeShipExecution.ts | 61 ++++++++++++++++--- src/server/GameServer.ts | 16 +++-- 9 files changed, 104 insertions(+), 26 deletions(-) diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index ee40198b1..9a9e77da6 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -118,7 +118,9 @@ export class GameRunner { this.renderer.initialize() this.input.initialize() this.gs.addExecution(...this.executor.spawnBots(this.gs.config().numBots())) - this.gs.addExecution(...this.executor.fakeHumanExecutions()) + if (this.gs.config().spawnNPCs()) { + this.gs.addExecution(...this.executor.fakeHumanExecutions()) + } this.gs.addExecution(new WinCheckExecution(this.eventBus)) this.intervalID = setInterval(() => this.tick(), 10); @@ -164,7 +166,7 @@ export class GameRunner { return } this.isProcessingTurn = true - this.gs.addExecution(...this.executor.createExecs(this.turns[this.currTurn])) + this.gs.addExecution(...this.executor.createExecs(this.turns[this.currTurn])) this.gs.executeNextTick() this.renderer.tick() this.currTurn++ diff --git a/src/client/graphics/layers/radial/BuildMenu.ts b/src/client/graphics/layers/radial/BuildMenu.ts index 1af3e07f8..d6b524cc3 100644 --- a/src/client/graphics/layers/radial/BuildMenu.ts +++ b/src/client/graphics/layers/radial/BuildMenu.ts @@ -162,6 +162,7 @@ export class BuildMenu extends LitElement { break case UnitType.HydrogenBomb: this.eventBus.emit(new BuildUnitIntentEvent(UnitType.HydrogenBomb, this.clickedCell)) + break case UnitType.Destroyer: this.eventBus.emit(new BuildUnitIntentEvent(UnitType.Destroyer, this.clickedCell)) break @@ -170,6 +171,7 @@ export class BuildMenu extends LitElement { break case UnitType.MissileSilo: this.eventBus.emit(new BuildUnitIntentEvent(UnitType.MissileSilo, this.clickedCell)) + break } this.hideMenu() }; diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 8c255b768..85f683d00 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -1,4 +1,4 @@ -import { Player, PlayerID, PlayerInfo, TerraNullius, Tick, Tile, UnitInfo, UnitType } from "../game/Game"; +import { Gold, Player, PlayerID, PlayerInfo, TerraNullius, Tick, Tile, Unit, UnitInfo, UnitType } from "../game/Game"; import { Colord, colord } from "colord"; import { devConfig } from "./DevConfig"; import { defaultConfig } from "./DefaultConfig"; @@ -31,6 +31,7 @@ export interface Config { gameCreationRate(): number lobbyLifetime(): number numBots(): number + spawnNPCs(): boolean numSpawnPhaseTurns(): number startManpower(playerInfo: PlayerInfo): number @@ -57,6 +58,7 @@ export interface Config { donateCooldown(): Tick defaultDonationAmount(sender: Player): number unitInfo(type: UnitType): UnitInfo + tradeShipGold(src: Unit, dst: Unit): Gold } export interface Theme { diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index adb5e5cb8..7c923f13b 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -1,12 +1,19 @@ -import { Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tick, Tile, UnitInfo, UnitType } from "../game/Game"; +import { Gold, Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tick, Tile, Unit, UnitInfo, UnitType } from "../game/Game"; import { GameID } from "../Schemas"; -import { assertNever, simpleHash, within } from "../Util"; +import { assertNever, manhattanDist, simpleHash, within } from "../Util"; import { Config, Theme } from "./Config"; import { pastelTheme } from "./PastelTheme"; export class DefaultConfig implements Config { + spawnNPCs(): boolean { + return true + } + tradeShipGold(src: Unit, dst: Unit): Gold { + const dist = manhattanDist(src.tile().cell(), dst.tile().cell()) + return 10000 + (dist * dist) + } unitInfo(type: UnitType): UnitInfo { switch (type) { case UnitType.TransportShip: diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index f35865ab2..f6cfe152d 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -12,7 +12,7 @@ export const devConfig = new class extends DefaultConfig { return 95 } numSpawnPhaseTurns(): number { - return 40 + return 80 } gameCreationRate(): number { return 20 * 1000 @@ -24,9 +24,12 @@ export const devConfig = new class extends DefaultConfig { return 100 } - // numBots(): number { - // return 400 - // } + numBots(): number { + return 0 + } + spawnNPCs(): boolean { + return false + } // boatMaxDistance(): number { // return 2000 diff --git a/src/core/execution/DestroyerExecution.ts b/src/core/execution/DestroyerExecution.ts index bff83d47f..a2ee21fe2 100644 --- a/src/core/execution/DestroyerExecution.ts +++ b/src/core/execution/DestroyerExecution.ts @@ -52,7 +52,7 @@ export class DestroyerExecution implements Execution { this.target = null } if (this.target == null) { - const ships = this.mg.units(UnitType.TransportShip) + const ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip) .filter(u => manhattanDist(u.tile().cell(), this.destroyer.tile().cell()) < 100) .filter(u => u.owner() != this.destroyer.owner()) .filter(u => u != this.destroyer) @@ -82,7 +82,18 @@ export class DestroyerExecution implements Execution { const result = this.pathfinder.nextTile(this.destroyer.tile(), this.target.tile(), 5) switch (result.type) { case PathFindResultType.Completed: - this.target.delete() + switch (this.target.type()) { + case UnitType.TransportShip: + this.target.delete() + break + case UnitType.TradeShip: + this.owner().captureUnit(this.target) + break + case UnitType.Destroyer: + this.target.delete() + this.destroyer.delete() + break + } this.target = null return case PathFindResultType.NextTile: @@ -98,7 +109,7 @@ export class DestroyerExecution implements Execution { } owner(): MutablePlayer { - return null + return this._owner } isActive(): boolean { diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index c2063c393..dd98343b8 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -86,7 +86,7 @@ export class PortExecution implements Execution { const portConnections = Array.from(this.portPaths.keys()) - if (portConnections.length > 0 && this.random.chance(500 * this.player().units(UnitType.Port).length)) { + if (portConnections.length > 0 && this.random.chance(250 * this.player().units(UnitType.Port).length)) { const port = this.random.randElement(portConnections) const path = this.portPaths.get(port) if (path != null) { diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 37806683c..ffe9d87f5 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -1,17 +1,19 @@ import { MessageType } from "../../client/graphics/layers/EventsDisplay"; import { renderNumber } from "../../client/graphics/Utils"; import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; -import { AStar, PathFinder } from "../PathFinding"; +import { AStar, PathFinder, PathFindResultType } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; -import { bfs, dist, manhattanDist } from "../Util"; +import { bfs, dist, distSortUnit, manhattanDist } from "../Util"; export class TradeShipExecution implements Execution { private active = true private mg: MutableGame - private player: MutablePlayer + private origOwner: MutablePlayer private tradeShip: MutableUnit private index = 0 + private pathFinder: PathFinder = new PathFinder(5_000, t => t.isOcean(), 10) + private wasCaptured = false constructor( private _owner: PlayerID, @@ -24,23 +26,68 @@ export class TradeShipExecution implements Execution { init(mg: MutableGame, ticks: number): void { this.mg = mg - this.player = mg.player(this._owner) + this.origOwner = mg.player(this._owner) } tick(ticks: number): void { if (this.tradeShip == null) { - const spawn = this.player.canBuild(UnitType.TradeShip, this.srcPort.tile()) + const spawn = this.origOwner.canBuild(UnitType.TradeShip, this.srcPort.tile()) if (spawn == false) { console.warn(`cannot build trade ship`) this.active = false return } - this.tradeShip = this.player.buildUnit(UnitType.TradeShip, 0, spawn) + this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn) } + + if (!this.tradeShip.isActive()) { + this.active = false + return + } + + if (this.origOwner != this.tradeShip.owner()) { + // Store as vairable in case ship is recaptured by previous owner + this.wasCaptured = true + } + + if (this.wasCaptured) { + const ports = this.tradeShip.owner().units(UnitType.Port).sort(distSortUnit(this.tradeShip)) + if (ports.length == 0) { + this.tradeShip.delete() + this.active = false + return + } + const dstPort = ports[0] + const result = this.pathFinder.nextTile(this.tradeShip.tile(), dstPort.tile()) + switch (result.type) { + case PathFindResultType.Completed: + const gold = this.mg.config().tradeShipGold(this.srcPort, dstPort) + this.tradeShip.owner().addGold(gold) + this.mg.displayMessage( + `Your trade ship captured from ${this.origOwner.displayName()}, giving you ${renderNumber(gold)} gold`, + MessageType.SUCCESS, + this.tradeShip.owner().id() + ) + this.tradeShip.delete() + break + case PathFindResultType.Pending: + break + case PathFindResultType.NextTile: + this.tradeShip.move(result.tile) + break + case PathFindResultType.PathNotFound: + console.warn('captured trade ship cannot find route') + this.active = false + break + } + return + } + + if (this.index >= this.path.length) { this.active = false const dist = manhattanDist(this.srcPort.tile().cell(), this.dstPort.tile().cell()) - const gold = dist * 100 + const gold = dist * dist this.srcPort.owner().addGold(gold) this.dstPort.owner().addGold(gold) this.mg.displayMessage(`Trade ship from ${this.tradeShip.owner().displayName()} has reached your port, giving you ${renderNumber(gold)} gold`, MessageType.SUCCESS, this.dstPort.owner().id()) diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 392c26f8e..777bbbe84 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -1,8 +1,8 @@ -import {ClientMessage, ClientMessageSchema, GameConfig, Intent, ServerStartGameMessage, ServerStartGameMessageSchema, ServerTurnMessageSchema, Turn} from "../core/Schemas"; -import {Config} from "../core/configuration/Config"; -import {Client} from "./Client"; +import { ClientMessage, ClientMessageSchema, GameConfig, Intent, ServerStartGameMessage, ServerStartGameMessageSchema, ServerTurnMessageSchema, Turn } from "../core/Schemas"; +import { Config } from "../core/configuration/Config"; +import { Client } from "./Client"; import WebSocket from 'ws'; -import {slog} from "./StructuredLog"; +import { slog } from "./StructuredLog"; export enum GamePhase { @@ -30,14 +30,14 @@ export class GameServer { public readonly isPublic: boolean, private config: Config, private gameConfig: GameConfig, - + ) { } public updateGameConfig(gameConfig: GameConfig): void { if (gameConfig.gameMap != null) { this.gameConfig.gameMap = gameConfig.gameMap } - if(gameConfig.difficulty != null) { + if (gameConfig.difficulty != null) { this.gameConfig.difficulty = gameConfig.difficulty } } @@ -51,6 +51,10 @@ export class GameServer { isRejoin: lastTurn > 0 }) // Remove stale client if this is a reconnect + const existing = this.clients.find(c => c.id == client.id) + if (existing != null) { + existing.ws.removeAllListeners('message') + } this.clients = this.clients.filter(c => c.id != client.id) this.clients.push(client) client.ws.on('message', (message: string) => {