From bc3cf0996023110652b9ad17abeb8c786b521936 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Thu, 21 Nov 2024 17:07:09 -0800 Subject: [PATCH 1/2] npcs build ports --- src/client/graphics/layers/UnitLayer.ts | 2 + src/core/configuration/DevConfig.ts | 2 +- src/core/execution/FakeHumanExecution.ts | 63 +++++++++++++++--------- src/core/execution/PortExecution.ts | 45 +++++++++-------- src/core/game/PlayerImpl.ts | 1 + 5 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts index e6b01842a..b13d063ae 100644 --- a/src/client/graphics/layers/UnitLayer.ts +++ b/src/client/graphics/layers/UnitLayer.ts @@ -80,8 +80,10 @@ export class UnitLayer implements Layer { break; case UnitType.TradeShip: this.handleTradeShipEvent(event) + break; case UnitType.AtomBomb: this.handleNuke(event) + break } } diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 985e84e28..f850f741a 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -4,7 +4,7 @@ import { DefaultConfig } from "./DefaultConfig"; export const devConfig = new class extends DefaultConfig { unitInfo(type: UnitType): UnitInfo { const info = super.unitInfo(type) - // info.cost = 0 + info.cost = 0 return info } diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 3cb042824..96a2d85d7 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -1,9 +1,10 @@ -import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tile } from "../game/Game" +import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tile, UnitType } from "../game/Game" import { PseudoRandom } from "../PseudoRandom" import { and, bfs, dist, simpleHash } from "../Util"; import { AttackExecution } from "./AttackExecution"; import { TransportShipExecution } from "./TransportShipExecution"; import { SpawnExecution } from "./SpawnExecution"; +import { PortExecution } from "./PortExecution"; export class FakeHumanExecution implements Execution { @@ -35,7 +36,6 @@ export class FakeHumanExecution implements Execution { } tick(ticks: number) { - if (this.mg.inSpawnPhase()) { if (ticks % this.random.nextInt(5, 30) == 0) { const rl = this.randomLand() @@ -72,27 +72,13 @@ export class FakeHumanExecution implements Execution { return } + if (this.player.troops() > 100_000 && this.player.targetTroopRatio() > .7) { + this.player.setTargetTroopRatio(.7) + } + this.handleAllianceRequests() - - if (ticks % 100 == 0) { - this.enemy = null - } - - if (this.enemy == null) { - this.enemy = this.mg.executions() - .filter(e => e instanceof AttackExecution) - .map(e => e as AttackExecution) - .filter(e => e.targetID() == this.player.id()) - .map(e => e.owner()) - .find(enemy => enemy && enemy.type() == PlayerType.Human) - } - - if (this.enemy) { - if (this.player.sharesBorderWith(this.enemy)) { - this.sendAttack(this.enemy) - } - return - } + this.handleEnemies() + this.handleUnits() if (this.neighborsTerraNullius) { @@ -136,6 +122,39 @@ export class FakeHumanExecution implements Execution { } } + handleEnemies() { + if (this.mg.ticks() % 100 == 0) { + this.enemy = null + } + + if (this.enemy == null) { + this.enemy = this.mg.executions() + .filter(e => e instanceof AttackExecution) + .map(e => e as AttackExecution) + .filter(e => e.targetID() == this.player.id()) + .map(e => e.owner()) + .find(enemy => enemy && enemy.type() == PlayerType.Human) + } + + if (this.enemy) { + if (this.player.sharesBorderWith(this.enemy)) { + this.sendAttack(this.enemy) + } + return + } + + } + + private handleUnits() { + if (this.player.units(UnitType.Port).length == 0 && this.player.gold() > this.mg.unitInfo(UnitType.Port).cost) { + const oceanTiles = Array.from(this.player.borderTiles()).filter(t => t.isOceanShore()) + if (oceanTiles.length > 0) { + const buildTile = this.random.randElement(oceanTiles) + this.mg.addExecution(new PortExecution(this.player.id(), buildTile.cell())) + } + } + } + handleAllianceRequests() { for (const req of this.player.incomingAllianceRequests()) { diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 2915cac30..1bfb8b98e 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -8,7 +8,6 @@ export class PortExecution implements Execution { private active = true private mg: MutableGame - private player: MutablePlayer private port: MutableUnit private random: PseudoRandom private portPaths = new Map() @@ -22,20 +21,20 @@ export class PortExecution implements Execution { init(mg: MutableGame, ticks: number): void { this.mg = mg - this.player = mg.player(this._owner) this.random = new PseudoRandom(mg.ticks()) } tick(ticks: number): void { if (this.port == null) { const tile = this.mg.tile(this.cell) - if (!this.player.canBuild(UnitType.Port, tile)) { - console.warn(`player ${this.player} cannot build port at ${this.cell}`) + const player = this.mg.player(this._owner) + if (!player.canBuild(UnitType.Port, tile)) { + console.warn(`player ${player} cannot build port at ${this.cell}`) this.active = false return } const spawns = Array.from(bfs(tile, dist(tile, 20))) - .filter(t => t.isOceanShore() && t.owner() == this.player) + .filter(t => t.isOceanShore() && t.owner() == player) .sort((a, b) => manhattanDist(a.cell(), tile.cell()) - manhattanDist(b.cell(), tile.cell())) if (spawns.length == 0) { @@ -43,15 +42,14 @@ export class PortExecution implements Execution { this.active = false return } - this.port = this.player.buildUnit(UnitType.Port, 0, spawns[0]) + this.port = player.buildUnit(UnitType.Port, 0, spawns[0]) } - const allPorts = this.mg.units(UnitType.Port) - .filter(u => u.owner() != this.player) - if (allPorts.length == 0) { - return - } - for (const port of allPorts) { + + const alliedPorts = this.player().alliances().map(a => a.other(this.player())).flatMap(p => p.units(UnitType.Port)) + const alliedPortsSet = new Set(alliedPorts) + + for (const port of alliedPorts) { if (this.computingPaths.has(port)) { const aStar = this.computingPaths.get(port) switch (aStar.compute()) { @@ -64,24 +62,25 @@ export class PortExecution implements Execution { console.warn(`path not found to port`) } continue - } - if (!this.portPaths.has(port)) { - this.computingPaths.set(port, new AStar(this.port.tile(), port.tile(), t => t.isWater(), 10_000, 20)) + } else if (!this.portPaths.has(port)) { + this.computingPaths.set(port, new AStar(this.port.tile(), port.tile(), t => t.isWater(), 2000, 100)) continue } } + for (const port of this.portPaths.keys()) { - if (!port.isActive()) { + if (!port.isActive() || !alliedPortsSet.has(port)) { this.portPaths.delete(port) + this.computingPaths.delete(port) } } - const allyPorts = Array.from(this.portPaths.keys()) - .filter(p => this.port.owner().isAlliedWith(p.owner())) - if (allPorts.length > 0 && this.random.chance(50)) { - const port = this.random.randElement(allPorts) + if (alliedPorts.length > 0 && this.random.chance(50)) { + const port = this.random.randElement(alliedPorts) const path = this.portPaths.get(port) - this.mg.addExecution(new TradeShipExecution(this._owner, this.port, port, path)) + if (path != null) { + this.mg.addExecution(new TradeShipExecution(this._owner, this.port, port, path)) + } } } @@ -97,4 +96,8 @@ export class PortExecution implements Execution { return false } + player(): MutablePlayer { + return this.port.owner() + } + } \ No newline at end of file diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 79b9b4aae..3b5fa3e7d 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -328,6 +328,7 @@ export class PlayerImpl implements MutablePlayer { (prev as PlayerImpl)._units = (prev as PlayerImpl)._units.filter(u => u != unit); (unit as UnitImpl)._owner = this this._units.push(unit as UnitImpl) + this.gs.fireUnitUpdateEvent(unit, unit.tile()) } buildUnit(type: UnitType, troops: number, spawnTile: Tile): UnitImpl { From e2fa53dfa77e77fc08ef7d5dcc5e64d4f2fe63f4 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 21 Nov 2024 20:16:27 -0800 Subject: [PATCH 2/2] attempt to fix public lobbies --- TODO.txt | 6 ++++-- src/client/index.html | 2 +- src/server/Server.ts | 30 +++++++++++++++--------------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/TODO.txt b/TODO.txt index 237207082..988623570 100644 --- a/TODO.txt +++ b/TODO.txt @@ -187,9 +187,11 @@ * REFACTOR: move canbuild to player DONE 11/17/2024 * BUG: can build destroyer on land DONE 11/17/2024 * fix pathfinding bug DONE 11/20/2024 -* make two nukes: atom & hydrogen DONE 11/202/2024 -* NPC builds ports +* make two nukes: atom & hydrogen DONE 11/20/2024 +* NPC builds ports DONE 11/20/2024 +* BUG: fix matchmaking * destroyer can capture trade ships +* make NPC difficult scale better (not just start troops) * add battleship * add defense post * add radiation from nuke diff --git a/src/client/index.html b/src/client/index.html index 6308f2e05..94f83e584 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -28,7 +28,7 @@

OpenFront.io

-

(v0.8.0)

+

(v0.8.1)

diff --git a/src/server/Server.ts b/src/server/Server.ts index 06374fffd..42e72d7e1 100644 --- a/src/server/Server.ts +++ b/src/server/Server.ts @@ -1,22 +1,22 @@ -import express, {json} from 'express'; +import express, { json } from 'express'; import http from 'http'; -import {WebSocketServer} from 'ws'; +import { WebSocketServer } from 'ws'; import path from 'path'; -import {fileURLToPath} from 'url'; -import {GameManager} from './GameManager'; -import {ClientMessage, ClientMessageSchema} from '../core/Schemas'; -import {getConfig} from '../core/configuration/Config'; -import {LogSeverity, slog} from './StructuredLog'; -import {Client} from './Client'; -import {GamePhase, GameServer} from './GameServer'; -import {v4 as uuidv4} from 'uuid'; +import { fileURLToPath } from 'url'; +import { GameManager } from './GameManager'; +import { ClientMessage, ClientMessageSchema } from '../core/Schemas'; +import { getConfig } from '../core/configuration/Config'; +import { LogSeverity, slog } from './StructuredLog'; +import { Client } from './Client'; +import { GamePhase, GameServer } from './GameServer'; +import { v4 as uuidv4 } from 'uuid'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const server = http.createServer(app); -const wss = new WebSocketServer({server}); +const wss = new WebSocketServer({ server }); // Serve static files from the 'out' directory app.use(express.static(path.join(__dirname, '../../out'))); @@ -28,12 +28,12 @@ app.get('/lobbies', (req, res) => { const now = Date.now() res.json({ lobbies: gm.gamesByPhase(GamePhase.Lobby) - .map(g => ({id: g.id, msUntilStart: g.startTime() - now, numClients: g.numClients()})) - // .sort((a, b) => a.startTime - b.startTime), + .filter(g => g.isPublic) + .map(g => ({ id: g.id, msUntilStart: g.startTime() - now, numClients: g.numClients() })) + .sort((a, b) => a.msUntilStart - b.msUntilStart), }); }); - app.post('/private_lobby', (req, res) => { const id = gm.createPrivateGame() console.log('creating private lobby with id ${id}') @@ -49,7 +49,7 @@ app.post('/start_private_lobby/:id', (req, res) => { app.put('/private_lobby/:id', (req, res) => { const lobbyID = req.params.id - gm.updateGameConfig(lobbyID, {gameMap: req.body.gameMap, difficulty: req.body.difficulty}) + gm.updateGameConfig(lobbyID, { gameMap: req.body.gameMap, difficulty: req.body.difficulty }) }); app.get('/lobby/:id/exists', (req, res) => {