diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts
index c1e8b1e37..4c66905df 100644
--- a/src/client/graphics/layers/PlayerInfoOverlay.ts
+++ b/src/client/graphics/layers/PlayerInfoOverlay.ts
@@ -22,7 +22,7 @@ import { renderNumber, renderTroops } from "../../Utils";
function euclideanDistWorld(
coord: { x: number; y: number },
tileRef: TileRef,
- game: GameView,
+ game: GameView
): number {
const x = game.x(tileRef);
const y = game.y(tileRef);
@@ -71,7 +71,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
init() {
this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) =>
- this.onMouseEvent(e),
+ this.onMouseEvent(e)
);
this._isActive = true;
}
@@ -111,7 +111,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
this.setVisible(true);
} else if (!this.game.isLand(tile)) {
const units = this.game
- .units(UnitType.Destroyer, UnitType.Battleship, UnitType.TradeShip)
+ .units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
.filter((u) => euclideanDistWorld(worldCoord, u.tile(), this.game) < 50)
.sort(distSortUnitWorld(worldCoord, this.game));
diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts
index 9a676f1d0..6700dc073 100644
--- a/src/client/graphics/layers/UnitLayer.ts
+++ b/src/client/graphics/layers/UnitLayer.ts
@@ -36,7 +36,7 @@ export class UnitLayer implements Layer {
constructor(
private game: GameView,
private eventBus: EventBus,
- private clientID: ClientID,
+ private clientID: ClientID
) {
this.theme = game.config().theme();
}
@@ -65,7 +65,7 @@ export class UnitLayer implements Layer {
-this.game.width() / 2,
-this.game.height() / 2,
this.game.width(),
- this.game.height(),
+ this.game.height()
);
}
@@ -103,11 +103,8 @@ export class UnitLayer implements Layer {
case UnitType.TransportShip:
this.handleBoatEvent(unit);
break;
- case UnitType.Destroyer:
- this.handleDestroyerEvent(unit);
- break;
- case UnitType.Battleship:
- this.handleBattleshipEvent(unit);
+ case UnitType.Warship:
+ this.handleWarShipEvent(unit);
break;
case UnitType.Shell:
this.handleShellEvent(unit);
@@ -122,54 +119,13 @@ export class UnitLayer implements Layer {
}
}
- private handleDestroyerEvent(unit: UnitView) {
+ private handleWarShipEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
- euclDistFN(unit.lastTile(), 4),
- )) {
- this.clearCell(this.game.x(t), this.game.y(t));
- }
-
- if (!unit.isActive()) {
- return;
- }
-
- // Paint border
- for (const t of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), 4))) {
- this.paintCell(
- this.game.x(t),
- this.game.y(t),
- rel,
- this.theme.borderColor(unit.owner().info()),
- 255,
- );
- }
-
- // Paint territory
- for (const t of this.game.bfs(
- unit.tile(),
- manhattanDistFN(unit.tile(), 3),
- )) {
- this.paintCell(
- this.game.x(t),
- this.game.y(t),
- rel,
- this.theme.territoryColor(unit.owner().info()),
- 255,
- );
- }
- }
-
- private handleBattleshipEvent(unit: UnitView) {
- const rel = this.relationship(unit);
-
- // Clear previous area
- for (const t of this.game.bfs(
- unit.lastTile(),
- euclDistFN(unit.lastTile(), 6),
+ euclDistFN(unit.lastTile(), 6)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -185,21 +141,21 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
- 255,
+ 255
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
- manhattanDistFN(unit.tile(), 4),
+ manhattanDistFN(unit.tile(), 4)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
- 255,
+ 255
);
}
@@ -210,7 +166,7 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
- 255,
+ 255
);
}
}
@@ -236,14 +192,14 @@ export class UnitLayer implements Layer {
this.game.y(unit.tile()),
rel,
this.theme.borderColor(unit.owner().info()),
- 255,
+ 255
);
this.paintCell(
this.game.x(unit.lastTile()),
this.game.y(unit.lastTile()),
rel,
this.theme.borderColor(unit.owner().info()),
- 255,
+ 255
);
}
@@ -253,7 +209,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
- euclDistFN(unit.lastTile(), 2),
+ euclDistFN(unit.lastTile(), 2)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -266,7 +222,7 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
- 255,
+ 255
);
}
}
@@ -278,7 +234,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
- euclDistFN(unit.lastTile(), 3),
+ euclDistFN(unit.lastTile(), 3)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -287,28 +243,28 @@ export class UnitLayer implements Layer {
// Paint territory
for (const t of this.game.bfs(
unit.tile(),
- manhattanDistFN(unit.tile(), 2),
+ manhattanDistFN(unit.tile(), 2)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
- 255,
+ 255
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
- manhattanDistFN(unit.tile(), 1),
+ manhattanDistFN(unit.tile(), 1)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
- 255,
+ 255
);
}
}
@@ -326,7 +282,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
- manhattanDistFN(unit.lastTile(), 3),
+ manhattanDistFN(unit.lastTile(), 3)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -339,35 +295,35 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
- 150,
+ 150
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
- manhattanDistFN(unit.tile(), 2),
+ manhattanDistFN(unit.tile(), 2)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
- 255,
+ 255
);
}
// Paint territory
for (const t of this.game.bfs(
unit.tile(),
- manhattanDistFN(unit.tile(), 1),
+ manhattanDistFN(unit.tile(), 1)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
- 255,
+ 255
);
}
} else {
@@ -383,7 +339,7 @@ export class UnitLayer implements Layer {
y: number,
relationship: Relationship,
color: Colord,
- alpha: number,
+ alpha: number
) {
this.clearCell(x, y);
if (this.alternateView) {
diff --git a/src/client/graphics/layers/radial/BuildMenu.ts b/src/client/graphics/layers/radial/BuildMenu.ts
index 3e4820ecc..007764b9f 100644
--- a/src/client/graphics/layers/radial/BuildMenu.ts
+++ b/src/client/graphics/layers/radial/BuildMenu.ts
@@ -11,15 +11,13 @@ import {
import { BuildUnitIntentEvent } from "../../../Transport";
import atomBombIcon from "../../../../../resources/images/NukeIconWhite.svg";
import hydrogenBombIcon from "../../../../../resources/images/MushroomCloudIconWhite.svg";
-import destroyerIcon from "../../../../../resources/images/DestroyerIconWhite.svg";
-import battleshipIcon from "../../../../../resources/images/BattleshipIconWhite.svg";
+import warshipIcon from "../../../../../resources/images/BattleshipIconWhite.svg";
import missileSiloIcon from "../../../../../resources/images/MissileSiloIconWhite.svg";
import goldCoinIcon from "../../../../../resources/images/GoldCoinIcon.svg";
import portIcon from "../../../../../resources/images/PortIcon.svg";
import shieldIcon from "../../../../../resources/images/ShieldIconWhite.svg";
import cityIcon from "../../../../../resources/images/CityIconWhite.svg";
import { renderNumber } from "../../../Utils";
-import { ContextMenuEvent } from "../../../InputHandler";
import { GameView, PlayerView } from "../../../../core/game/GameView";
interface BuildItemDisplay {
@@ -31,8 +29,7 @@ const buildTable: BuildItemDisplay[][] = [
[
{ unitType: UnitType.AtomBomb, icon: atomBombIcon },
{ unitType: UnitType.HydrogenBomb, icon: hydrogenBombIcon },
- { unitType: UnitType.Destroyer, icon: destroyerIcon },
- { unitType: UnitType.Battleship, icon: battleshipIcon },
+ { unitType: UnitType.Warship, icon: warshipIcon },
{ unitType: UnitType.Port, icon: portIcon },
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
// { unitType: UnitType.DefensePost, icon: shieldIcon },
@@ -201,7 +198,7 @@ export class BuildMenu extends LitElement {
public onBuildSelected = (item: BuildItemDisplay) => {
this.eventBus.emit(
- new BuildUnitIntentEvent(item.unitType, this.clickedCell),
+ new BuildUnitIntentEvent(item.unitType, this.clickedCell)
);
this.hideMenu();
};
@@ -233,7 +230,7 @@ export class BuildMenu extends LitElement {
? this.game
.unitInfo(item.unitType)
.cost(this.myPlayer)
- : 0,
+ : 0
)}
- `,
+ `
)}
- `,
+ `
)}
`;
diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts
index 76e1f2aae..f4556d551 100644
--- a/src/core/configuration/DefaultConfig.ts
+++ b/src/core/configuration/DefaultConfig.ts
@@ -33,7 +33,7 @@ export abstract class DefaultServerConfig implements ServerConfig {
export class DefaultConfig implements Config {
constructor(
private _serverConfig: ServerConfig,
- private _gameConfig: GameConfig,
+ private _gameConfig: GameConfig
) {}
gameConfig(): GameConfig {
@@ -88,20 +88,12 @@ export class DefaultConfig implements Config {
cost: () => 0,
territoryBound: false,
};
- case UnitType.Destroyer:
+ case UnitType.Warship:
return {
- cost: (p: Player) =>
- (p.units(UnitType.Destroyer).length + 1) * 250_000,
+ cost: (p: Player) => (p.units(UnitType.Warship).length + 1) * 250_000,
territoryBound: false,
maxHealth: 1000,
};
- case UnitType.Battleship:
- return {
- cost: (p: Player) =>
- (p.units(UnitType.Battleship).length + 1) * 500_000,
- territoryBound: false,
- maxHealth: 5000,
- };
case UnitType.Shell:
return {
cost: () => 0,
@@ -113,7 +105,7 @@ export class DefaultConfig implements Config {
cost: (p: Player) =>
Math.min(
1_000_000,
- Math.pow(2, p.units(UnitType.Port).length) * 250_000,
+ Math.pow(2, p.units(UnitType.Port).length) * 250_000
),
territoryBound: true,
};
@@ -142,7 +134,7 @@ export class DefaultConfig implements Config {
cost: (p: Player) =>
Math.min(
250_000,
- (p.units(UnitType.DefensePost).length + 1) * 50_000,
+ (p.units(UnitType.DefensePost).length + 1) * 50_000
),
territoryBound: true,
};
@@ -151,7 +143,7 @@ export class DefaultConfig implements Config {
cost: (p: Player) =>
Math.min(
1_000_000,
- Math.pow(2, p.units(UnitType.City).length) * 125_000,
+ Math.pow(2, p.units(UnitType.City).length) * 125_000
),
territoryBound: true,
};
@@ -207,7 +199,7 @@ export class DefaultConfig implements Config {
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
- tileToConquer: TileRef,
+ tileToConquer: TileRef
): {
attackerTroopLoss: number;
defenderTroopLoss: number;
@@ -271,7 +263,7 @@ export class DefaultConfig implements Config {
tilesPerTickUsed: within(
(2000 * Math.max(10, speed)) / attackTroops,
5,
- 100,
+ 100
),
};
}
@@ -281,7 +273,7 @@ export class DefaultConfig implements Config {
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
- numAdjacentTilesWithEnemy: number,
+ numAdjacentTilesWithEnemy: number
): number {
if (defender.isPlayer()) {
return (
diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts
index ff4e9f11c..8aa48b593 100644
--- a/src/core/configuration/DevConfig.ts
+++ b/src/core/configuration/DevConfig.ts
@@ -29,6 +29,10 @@ export class DevConfig extends DefaultConfig {
return info;
}
+ tradeShipSpawnRate(): number {
+ return 10;
+ }
+
// percentageTilesOwnedToWin(): number {
// return 1
// }
diff --git a/src/core/execution/BattleshipExecution.ts b/src/core/execution/BattleshipExecution.ts
deleted file mode 100644
index b08f75c1d..000000000
--- a/src/core/execution/BattleshipExecution.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import {
- Cell,
- Execution,
- Game,
- Player,
- Unit,
- PlayerID,
- TerrainType,
- UnitType,
-} from "../game/Game";
-import { PathFinder } from "../pathfinding/PathFinding";
-import { PathFindResultType } from "../pathfinding/AStar";
-import { PseudoRandom } from "../PseudoRandom";
-import { distSort, distSortUnit } from "../Util";
-import { ShellExecution } from "./ShellExecution";
-import { consolex } from "../Consolex";
-import { TileRef } from "../game/GameMap";
-
-export class BattleshipExecution implements Execution {
- private random: PseudoRandom;
-
- private _owner: Player;
- private active = true;
- private battleship: Unit = null;
- private mg: Game = null;
-
- private pathfinder: PathFinder;
-
- private patrolTile: TileRef;
-
- // TODO: put in config
- private searchRange = 100;
- private attackRate = 5;
- private lastAttack = 0;
-
- private alreadyTargeted = new Set();
-
- constructor(
- private playerID: PlayerID,
- private patrolCenterTile: TileRef,
- ) {}
-
- init(mg: Game, ticks: number): void {
- this.pathfinder = PathFinder.Mini(mg, 5000, false);
- this._owner = mg.player(this.playerID);
- this.mg = mg;
- this.patrolTile = this.patrolCenterTile;
- this.random = new PseudoRandom(mg.ticks());
- }
-
- tick(ticks: number): void {
- this.alreadyTargeted.forEach((u) => {
- if (!u.isActive()) {
- this.alreadyTargeted.delete(u);
- }
- });
- if (this.battleship == null) {
- const spawn = this._owner.canBuild(UnitType.Battleship, this.patrolTile);
- if (spawn == false) {
- this.active = false;
- return;
- }
- this.battleship = this._owner.buildUnit(UnitType.Battleship, 0, spawn);
- return;
- }
- if (!this.battleship.isActive()) {
- this.active = false;
- return;
- }
-
- if (this.mg.ticks() % 2 == 0) {
- const result = this.pathfinder.nextTile(
- this.battleship.tile(),
- this.patrolTile,
- );
- switch (result.type) {
- case PathFindResultType.Completed:
- this.patrolTile = this.randomTile();
- break;
- case PathFindResultType.NextTile:
- this.battleship.move(result.tile);
- break;
- case PathFindResultType.Pending:
- return;
- case PathFindResultType.PathNotFound:
- consolex.log(`path not found to patrol tile`);
- this.patrolTile = this.randomTile();
- break;
- }
- }
-
- if (this.mg.ticks() - this.lastAttack < this.attackRate) {
- return;
- }
-
- let ships = this.mg
- .units(
- UnitType.TransportShip,
- UnitType.Destroyer,
- UnitType.TradeShip,
- UnitType.Battleship,
- )
- .filter(
- (u) => this.mg.manhattanDist(u.tile(), this.battleship.tile()) < 100,
- )
- .filter((u) => u.owner() != this.battleship.owner())
- .filter((u) => u != this.battleship)
- .filter((u) => !u.owner().isAlliedWith(this.battleship.owner()))
- .filter((u) => !this.alreadyTargeted.has(u))
- .sort(distSortUnit(this.mg, this.battleship));
-
- const friendlyDestroyerNearby =
- this.battleship
- .owner()
- .units(UnitType.Destroyer)
- .filter(
- (d) => this.mg.manhattanDist(d.tile(), this.battleship.tile()) < 120,
- ).length > 0;
-
- if (friendlyDestroyerNearby) {
- // Don't attack trade ships to allow friendly destroyer to capture them
- ships = ships.filter((s) => s.type() != UnitType.TradeShip);
- }
-
- if (ships.length > 0) {
- const toAttack = ships[0];
- if (!toAttack.hasHealth()) {
- // Don't send multiple shells to target if it can be one-shotted.
- this.alreadyTargeted.add(toAttack);
- }
- this.lastAttack = this.mg.ticks();
- this.mg.addExecution(
- new ShellExecution(
- this.battleship.tile(),
- this.battleship.owner(),
- this.battleship,
- toAttack,
- ),
- );
- }
- }
-
- owner(): Player {
- return this._owner;
- }
-
- isActive(): boolean {
- return this.active;
- }
-
- activeDuringSpawnPhase(): boolean {
- return false;
- }
-
- randomTile(): TileRef {
- while (true) {
- const x =
- this.mg.x(this.patrolCenterTile) +
- this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
- const y =
- this.mg.y(this.patrolCenterTile) +
- this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
- if (!this.mg.isValidCoord(x, y)) {
- continue;
- }
- const tile = this.mg.ref(x, y);
- if (!this.mg.isOcean(tile)) {
- continue;
- }
- return tile;
- }
- }
-}
diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts
index fb0c34d37..6851c19e0 100644
--- a/src/core/execution/ExecutionManager.ts
+++ b/src/core/execution/ExecutionManager.ts
@@ -31,10 +31,9 @@ import { EmojiExecution } from "./EmojiExecution";
import { DonateExecution } from "./DonateExecution";
import { NukeExecution } from "./NukeExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
-import { DestroyerExecution } from "./DestroyerExecution";
+import { WarshipExecution } from "./WarshipExecution";
import { PortExecution } from "./PortExecution";
import { MissileSiloExecution } from "./MissileSiloExecution";
-import { BattleshipExecution } from "./BattleshipExecution";
import { DefensePostExecution } from "./DefensePostExecution";
import { CityExecution } from "./CityExecution";
import { TileRef } from "../game/GameMap";
@@ -43,10 +42,7 @@ export class Executor {
// private random = new PseudoRandom(999)
private random: PseudoRandom = null;
- constructor(
- private mg: Game,
- private gameID: GameID,
- ) {
+ constructor(private mg: Game, private gameID: GameID) {
// Add one to avoid id collisions with bots.
this.random = new PseudoRandom(simpleHash(gameID) + 1);
}
@@ -62,7 +58,7 @@ export class Executor {
intent.troops,
intent.attackerID,
intent.targetID,
- null,
+ null
);
}
case "spawn":
@@ -71,16 +67,16 @@ export class Executor {
sanitize(intent.name),
intent.playerType,
intent.clientID,
- intent.playerID,
+ intent.playerID
),
- this.mg.ref(intent.x, intent.y),
+ this.mg.ref(intent.x, intent.y)
);
case "boat":
return new TransportShipExecution(
intent.attackerID,
intent.targetID,
this.mg.ref(intent.x, intent.y),
- intent.troops,
+ intent.troops
);
case "allianceRequest":
return new AllianceRequestExecution(intent.requestor, intent.recipient);
@@ -88,7 +84,7 @@ export class Executor {
return new AllianceRequestReplyExecution(
intent.requestor,
intent.recipient,
- intent.accept,
+ intent.accept
);
case "breakAlliance":
return new BreakAllianceExecution(intent.requestor, intent.recipient);
@@ -98,13 +94,13 @@ export class Executor {
return new EmojiExecution(
intent.sender,
intent.recipient,
- intent.emoji,
+ intent.emoji
);
case "donate":
return new DonateExecution(
intent.sender,
intent.recipient,
- intent.troops,
+ intent.troops
);
case "troop_ratio":
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
@@ -115,37 +111,32 @@ export class Executor {
return new NukeExecution(
intent.unit,
intent.player,
- this.mg.ref(intent.x, intent.y),
+ this.mg.ref(intent.x, intent.y)
);
- case UnitType.Destroyer:
- return new DestroyerExecution(
+ case UnitType.Warship:
+ return new WarshipExecution(
intent.player,
- this.mg.ref(intent.x, intent.y),
- );
- case UnitType.Battleship:
- return new BattleshipExecution(
- intent.player,
- this.mg.ref(intent.x, intent.y),
+ this.mg.ref(intent.x, intent.y)
);
case UnitType.Port:
return new PortExecution(
intent.player,
- this.mg.ref(intent.x, intent.y),
+ this.mg.ref(intent.x, intent.y)
);
case UnitType.MissileSilo:
return new MissileSiloExecution(
intent.player,
- this.mg.ref(intent.x, intent.y),
+ this.mg.ref(intent.x, intent.y)
);
case UnitType.DefensePost:
return new DefensePostExecution(
intent.player,
- this.mg.ref(intent.x, intent.y),
+ this.mg.ref(intent.x, intent.y)
);
case UnitType.City:
return new CityExecution(
intent.player,
- this.mg.ref(intent.x, intent.y),
+ this.mg.ref(intent.x, intent.y)
);
default:
throw Error(`unit type ${intent.unit} not supported`);
@@ -171,14 +162,14 @@ export class Executor {
nation.name,
PlayerType.FakeHuman,
null,
- this.random.nextID(),
+ this.random.nextID()
),
nation.cell,
nation.strength *
this.mg
.config()
- .difficultyModifier(this.mg.config().gameConfig().difficulty),
- ),
+ .difficultyModifier(this.mg.config().gameConfig().difficulty)
+ )
);
}
return execs;
diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts
index 2ac120274..795b4c87e 100644
--- a/src/core/execution/FakeHumanExecution.ts
+++ b/src/core/execution/FakeHumanExecution.ts
@@ -18,8 +18,7 @@ import { AttackExecution } from "./AttackExecution";
import { TransportShipExecution } from "./TransportShipExecution";
import { SpawnExecution } from "./SpawnExecution";
import { PortExecution } from "./PortExecution";
-import { DestroyerExecution } from "./DestroyerExecution";
-import { BattleshipExecution } from "./BattleshipExecution";
+import { WarshipExecution } from "./WarshipExecution";
import { GameID } from "../Schemas";
import { consolex } from "../Consolex";
import { CityExecution } from "./CityExecution";
@@ -321,10 +320,7 @@ export class FakeHumanExecution implements Execution {
2,
(t) => new CityExecution(this.player.id(), t)
);
- if (this.maybeSpawnWarship(UnitType.Destroyer)) {
- return;
- }
- if (this.maybeSpawnWarship(UnitType.Battleship)) {
+ if (this.maybeSpawnWarship()) {
return;
}
this.maybeSpawnStructure(
@@ -359,41 +355,28 @@ export class FakeHumanExecution implements Execution {
this.mg.addExecution(build(tile));
}
- private maybeSpawnWarship(
- shipType: UnitType.Destroyer | UnitType.Battleship
- ): boolean {
+ private maybeSpawnWarship(): boolean {
if (!this.random.chance(50)) {
return false;
}
const ports = this.player.units(UnitType.Port);
- const ships = this.player.units(shipType);
+ const ships = this.player.units(UnitType.Warship);
if (
ports.length > 0 &&
ships.length == 0 &&
- this.player.gold() > this.cost(shipType)
+ this.player.gold() > this.cost(UnitType.Warship)
) {
const port = this.random.randElement(ports);
const targetTile = this.warshipSpawnTile(port.tile());
if (targetTile == null) {
return false;
}
- const canBuild = this.player.canBuild(UnitType.Destroyer, targetTile);
+ const canBuild = this.player.canBuild(UnitType.Warship, targetTile);
if (canBuild == false) {
consolex.warn("cannot spawn destroyer");
return false;
}
- switch (shipType) {
- case UnitType.Destroyer:
- this.mg.addExecution(
- new DestroyerExecution(this.player.id(), targetTile)
- );
- break;
- case UnitType.Battleship:
- this.mg.addExecution(
- new BattleshipExecution(this.player.id(), targetTile)
- );
- break;
- }
+ this.mg.addExecution(new WarshipExecution(this.player.id(), targetTile));
return true;
}
return false;
diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts
index 0f4f91034..b338c7a92 100644
--- a/src/core/execution/PortExecution.ts
+++ b/src/core/execution/PortExecution.ts
@@ -25,10 +25,7 @@ export class PortExecution implements Execution {
private portPaths = new Map();
private computingPaths = new Map();
- constructor(
- private _owner: PlayerID,
- private tile: TileRef,
- ) {}
+ constructor(private _owner: PlayerID, private tile: TileRef) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
@@ -49,7 +46,7 @@ export class PortExecution implements Execution {
.filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
.sort(
(a, b) =>
- this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
+ this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile)
);
if (spawns.length == 0) {
@@ -71,7 +68,7 @@ export class PortExecution implements Execution {
const alliedPortsSet = new Set(alliedPorts);
const allyConnections = new Set(
- Array.from(this.portPaths.keys()).map((p) => p.owner()),
+ Array.from(this.portPaths.keys()).map((p) => p.owner())
);
allyConnections;
@@ -103,7 +100,7 @@ export class PortExecution implements Execution {
port.tile(),
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
10_000,
- 25,
+ 25
);
this.computingPaths.set(port, pf);
}
@@ -124,9 +121,9 @@ export class PortExecution implements Execution {
const port = this.random.randElement(portConnections);
const path = this.portPaths.get(port);
if (path != null) {
- const pf = PathFinder.Mini(this.mg, 10, false);
+ const pf = PathFinder.Mini(this.mg, 10000, false);
this.mg.addExecution(
- new TradeShipExecution(this.player().id(), this.port, port, pf, path),
+ new TradeShipExecution(this.player().id(), this.port, port, pf, path)
);
}
}
diff --git a/src/core/execution/DestroyerExecution.ts b/src/core/execution/WarshipExecution.ts
similarity index 57%
rename from src/core/execution/DestroyerExecution.ts
rename to src/core/execution/WarshipExecution.ts
index 11f1cb88b..810d1ae48 100644
--- a/src/core/execution/DestroyerExecution.ts
+++ b/src/core/execution/WarshipExecution.ts
@@ -14,13 +14,14 @@ import { PseudoRandom } from "../PseudoRandom";
import { distSort, distSortUnit } from "../Util";
import { consolex } from "../Consolex";
import { TileRef } from "../game/GameMap";
+import { ShellExecution } from "./ShellExecution";
-export class DestroyerExecution implements Execution {
+export class WarshipExecution implements Execution {
private random: PseudoRandom;
private _owner: Player;
private active = true;
- private destroyer: Unit = null;
+ private warship: Unit = null;
private mg: Game = null;
private target: Unit = null;
@@ -31,10 +32,12 @@ export class DestroyerExecution implements Execution {
// TODO: put in config
private searchRange = 100;
- constructor(
- private playerID: PlayerID,
- private patrolCenterTile: TileRef,
- ) {}
+ private shellAttackRate = 5;
+ private lastShellAttack = 0;
+
+ private alreadySentShell = new Set();
+
+ constructor(private playerID: PlayerID, private patrolCenterTile: TileRef) {}
init(mg: Game, ticks: number): void {
this.pathfinder = PathFinder.Mini(mg, 5000, false);
@@ -45,16 +48,16 @@ export class DestroyerExecution implements Execution {
}
tick(ticks: number): void {
- if (this.destroyer == null) {
- const spawn = this._owner.canBuild(UnitType.Destroyer, this.patrolTile);
+ if (this.warship == null) {
+ const spawn = this._owner.canBuild(UnitType.Warship, this.patrolTile);
if (spawn == false) {
this.active = false;
return;
}
- this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawn);
+ this.warship = this._owner.buildUnit(UnitType.Warship, 0, spawn);
return;
}
- if (!this.destroyer.isActive()) {
+ if (!this.warship.isActive()) {
this.active = false;
return;
}
@@ -63,34 +66,28 @@ export class DestroyerExecution implements Execution {
}
if (this.target == null) {
const ships = this.mg
- .units(
- UnitType.TransportShip,
- UnitType.Destroyer,
- UnitType.TradeShip,
- UnitType.Battleship,
- )
+ .units(UnitType.TransportShip, UnitType.Warship, UnitType.TradeShip)
.filter(
- (u) => this.mg.manhattanDist(u.tile(), this.destroyer.tile()) < 100,
+ (u) => this.mg.manhattanDist(u.tile(), this.warship.tile()) < 100
)
- .filter(
- (u) =>
- u.type() != UnitType.Destroyer ||
- u.health() < this.destroyer.health(),
- ) // only attack Destroyers weaker than it.
- .filter((u) => u.owner() != this.destroyer.owner())
- .filter((u) => u != this.destroyer)
- .filter((u) => !u.owner().isAlliedWith(this.destroyer.owner()));
- if (ships.length == 0) {
+ .filter((u) => u.owner() != this.warship.owner())
+ .filter((u) => u != this.warship)
+ .filter((u) => !u.owner().isAlliedWith(this.warship.owner()))
+ .filter((u) => !this.alreadySentShell.has(u));
+
+ this.target = ships.sort(distSortUnit(this.mg, this.warship))[0] ?? null;
+ if (this.target == null || this.target.type() != UnitType.TradeShip) {
+ // Patrol unless we are hunting down a tradeship
const result = this.pathfinder.nextTile(
- this.destroyer.tile(),
- this.patrolTile,
+ this.warship.tile(),
+ this.patrolTile
);
switch (result.type) {
case PathFindResultType.Completed:
this.patrolTile = this.randomTile();
break;
case PathFindResultType.NextTile:
- this.destroyer.move(result.tile);
+ this.warship.move(result.tile);
break;
case PathFindResultType.Pending:
return;
@@ -99,42 +96,53 @@ export class DestroyerExecution implements Execution {
this.patrolTile = this.randomTile();
break;
}
- return;
}
- this.target = ships.sort(distSortUnit(this.mg, this.destroyer))[0];
}
- if (!this.target.isActive() || this.target.owner() == this._owner) {
- // Incase another destroyer captured or destroyed target
+ if (
+ this.target == null ||
+ !this.target.isActive() ||
+ this.target.owner() == this._owner
+ ) {
+ // In case another destroyer captured or destroyed target
this.target = null;
return;
}
+ if (this.target.type() != UnitType.TradeShip) {
+ if (this.mg.ticks() - this.lastShellAttack > this.shellAttackRate) {
+ this.lastShellAttack = this.mg.ticks();
+ this.mg.addExecution(
+ new ShellExecution(
+ this.warship.tile(),
+ this.warship.owner(),
+ this.warship,
+ this.target
+ )
+ );
+ if (!this.target.hasHealth()) {
+ // Don't send multiple shells to target that can be oneshotted
+ this.alreadySentShell.add(this.target);
+ this.target = null;
+ return;
+ }
+ }
+ // Only hunt down tradeships
+ return;
+ }
for (let i = 0; i < 2; i++) {
+ // target is trade ship so capture it.
const result = this.pathfinder.nextTile(
- this.destroyer.tile(),
+ this.warship.tile(),
this.target.tile(),
- 5,
+ 5
);
switch (result.type) {
case PathFindResultType.Completed:
- switch (this.target.type()) {
- case UnitType.TransportShip:
- case UnitType.Battleship:
- this.target.delete();
- break;
- case UnitType.TradeShip:
- this.owner().captureUnit(this.target);
- break;
- case UnitType.Destroyer:
- const health = this.target.health();
- this.target.modifyHealth(-this.destroyer.health());
- this.destroyer.modifyHealth(-health);
- break;
- }
+ this.owner().captureUnit(this.target);
this.target = null;
return;
case PathFindResultType.NextTile:
- this.destroyer.move(result.tile);
+ this.warship.move(result.tile);
break;
case PathFindResultType.Pending:
break;
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index a99772144..4fe660954 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -62,8 +62,7 @@ export interface UnitInfo {
export enum UnitType {
TransportShip = "Transport",
- Destroyer = "Destroyer",
- Battleship = "Battleship",
+ Warship = "Warship",
Shell = "Shell",
Port = "Port",
AtomBomb = "Atom Bomb",
diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts
index b6cdbab10..f28e824a8 100644
--- a/src/core/game/GameView.ts
+++ b/src/core/game/GameView.ts
@@ -42,10 +42,7 @@ export class UnitView {
public _wasUpdated = true;
public lastPos: MapPos[] = [];
- constructor(
- private gameView: GameView,
- private data: UnitUpdate,
- ) {
+ constructor(private gameView: GameView, private data: UnitUpdate) {
this.lastPos.push(data.pos);
}
@@ -101,14 +98,14 @@ export class PlayerView {
constructor(
private game: GameView,
public data: PlayerUpdate,
- public nameData: NameViewData,
+ public nameData: NameViewData
) {}
async actions(tile: TileRef): Promise {
return this.game.worker.playerInteraction(
this.id(),
this.game.x(tile),
- this.game.y(tile),
+ this.game.y(tile)
);
}
@@ -151,12 +148,12 @@ export class PlayerView {
}
allies(): PlayerView[] {
return this.data.allies.map(
- (a) => this.game.playerBySmallID(a) as PlayerView,
+ (a) => this.game.playerBySmallID(a) as PlayerView
);
}
targets(): PlayerView[] {
return this.data.targets.map(
- (id) => this.game.playerBySmallID(id) as PlayerView,
+ (id) => this.game.playerBySmallID(id) as PlayerView
);
}
gold(): Gold {
@@ -211,7 +208,7 @@ export class GameView implements GameMap {
public worker: WorkerClient,
private _config: Config,
private _map: GameMap,
- private _myClientID: ClientID,
+ private _myClientID: ClientID
) {
this.lastUpdate = {
tick: 0,
@@ -242,7 +239,7 @@ export class GameView implements GameMap {
} else {
this._players.set(
pu.id,
- new PlayerView(this, pu, gu.playerNameViewData[pu.id]),
+ new PlayerView(this, pu, gu.playerNameViewData[pu.id])
);
}
});
@@ -321,7 +318,12 @@ export class GameView implements GameMap {
return this._config;
}
units(...types: UnitType[]): UnitView[] {
- return Array.from(this._units.values());
+ if (types.length == 0) {
+ return Array.from(this._units.values());
+ }
+ return Array.from(this._units.values()).filter((u) =>
+ types.includes(u.type())
+ );
}
unit(id: number): UnitView {
return this._units.get(id);
@@ -416,7 +418,7 @@ export class GameView implements GameMap {
}
bfs(
tile: TileRef,
- filter: (gm: GameMap, tile: TileRef) => boolean,
+ filter: (gm: GameMap, tile: TileRef) => boolean
): Set {
return this._map.bfs(tile, filter);
}
diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts
index 74c4a3c5b..16abbf6fb 100644
--- a/src/core/game/PlayerImpl.ts
+++ b/src/core/game/PlayerImpl.ts
@@ -567,8 +567,7 @@ export class PlayerImpl implements Player {
return this.nukeSpawn(targetTile);
case UnitType.Port:
return this.portSpawn(targetTile);
- case UnitType.Destroyer:
- case UnitType.Battleship:
+ case UnitType.Warship:
return this.warshipSpawn(targetTile);
case UnitType.Shell:
return targetTile;
diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts
index 6fb7ec90f..f31089e8b 100644
--- a/src/core/game/UnitImpl.ts
+++ b/src/core/game/UnitImpl.ts
@@ -18,7 +18,7 @@ export class UnitImpl implements Unit {
private _tile: TileRef,
private _troops: number,
private _id: number,
- public _owner: PlayerImpl,
+ public _owner: PlayerImpl
) {
// default to half health (or 1 is no health specified)
this._health = (this.mg.unitInfo(_type).maxHealth ?? 2) / 2;
@@ -35,6 +35,7 @@ export class UnitImpl implements Unit {
isActive: this._active,
pos: { x: this.mg.x(this._tile), y: this.mg.y(this._tile) },
lastPos: { x: this.mg.x(this._lastTile), y: this.mg.y(this._lastTile) },
+ health: this.hasHealth() ? this._health : undefined,
};
}
@@ -85,7 +86,7 @@ export class UnitImpl implements Unit {
this.mg.displayMessage(
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
MessageType.ERROR,
- oldOwner.id(),
+ oldOwner.id()
);
}
@@ -104,7 +105,7 @@ export class UnitImpl implements Unit {
this.mg.displayMessage(
`Your ${this.type()} was destroyed`,
MessageType.ERROR,
- this.owner().id(),
+ this.owner().id()
);
}
}