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/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/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 @@
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 {
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) => {