mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-16 12:52:38 +00:00
Enable strictPropertyInitialization (#1909)
## Description: Enable the tsconfig option `strictPropertyInitialization`. Fixes #1907 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced
This commit is contained in:
@@ -100,7 +100,7 @@ export abstract class DefaultServerConfig implements ServerConfig {
|
||||
return process.env.CF_CREDS_PATH ?? "";
|
||||
}
|
||||
|
||||
private publicKey: JWK;
|
||||
private publicKey: JWK | undefined;
|
||||
abstract jwtAudience(): string;
|
||||
jwtIssuer(): string {
|
||||
const audience = this.jwtAudience();
|
||||
|
||||
@@ -23,9 +23,9 @@ export class AttackExecution implements Execution {
|
||||
|
||||
private readonly random = new PseudoRandom(123);
|
||||
|
||||
private target: Player | TerraNullius;
|
||||
private target: Player | TerraNullius | undefined;
|
||||
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
|
||||
private attack: Attack | null = null;
|
||||
|
||||
@@ -171,6 +171,9 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
|
||||
private retreat(malusPercent = 0) {
|
||||
if (this.mg === undefined) {
|
||||
throw new Error("Attack not initialized");
|
||||
}
|
||||
if (this.attack === null) {
|
||||
throw new Error("Attack not initialized");
|
||||
}
|
||||
@@ -189,22 +192,27 @@ export class AttackExecution implements Execution {
|
||||
this.active = false;
|
||||
|
||||
// Not all retreats are canceled attacks
|
||||
if (this.attack.retreated()) {
|
||||
if (this.attack.retreated() && this.target && this.target.isPlayer()) {
|
||||
// Record stats
|
||||
this.mg.stats().attackCancel(this._owner, this.target, survivors);
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (this.mg === undefined) {
|
||||
throw new Error("Attack not initialized");
|
||||
}
|
||||
if (this.target === undefined) {
|
||||
throw new Error("Attack not initialized");
|
||||
}
|
||||
if (this.attack === null) {
|
||||
throw new Error("Attack not initialized");
|
||||
}
|
||||
let troopCount = this.attack.troops(); // cache troop count
|
||||
const targetIsPlayer = this.target.isPlayer(); // cache target type
|
||||
const targetPlayer = targetIsPlayer ? (this.target as Player) : null; // cache target player
|
||||
const targetPlayer: Player | null = this.target.isPlayer() ? this.target : null; // cache target player
|
||||
|
||||
if (this.attack.retreated()) {
|
||||
if (targetIsPlayer) {
|
||||
if (targetPlayer !== null) {
|
||||
this.retreat(malusForRetreat);
|
||||
} else {
|
||||
this.retreat();
|
||||
@@ -222,8 +230,8 @@ export class AttackExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
const alliance = targetPlayer
|
||||
? this._owner.allianceWith(targetPlayer)
|
||||
const alliance = this.target && this.target.isPlayer()
|
||||
? this._owner.allianceWith(this.target)
|
||||
: null;
|
||||
if (this.breakAlliance && alliance !== null) {
|
||||
this.breakAlliance = false;
|
||||
@@ -309,6 +317,9 @@ export class AttackExecution implements Execution {
|
||||
if (this.attack === null) {
|
||||
throw new Error("Attack not initialized");
|
||||
}
|
||||
if (this.mg === undefined) {
|
||||
throw new Error("Attack not initialized");
|
||||
}
|
||||
|
||||
const tickNow = this.mg.ticks(); // cache tick
|
||||
|
||||
@@ -349,6 +360,8 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
|
||||
private handleDeadDefender() {
|
||||
if (!this.mg) return;
|
||||
if (!this.target) return;
|
||||
if (!(this.target.isPlayer() && this.target.numTilesOwned() < 100)) return;
|
||||
|
||||
this.mg.conquerPlayer(this._owner, this.target);
|
||||
@@ -357,7 +370,7 @@ export class AttackExecution implements Execution {
|
||||
for (const tile of this.target.tiles()) {
|
||||
const borders = this.mg
|
||||
.neighbors(tile)
|
||||
.some((t) => this.mg.owner(t) === this._owner);
|
||||
.some((t) => this.mg?.owner(t) === this._owner);
|
||||
if (borders) {
|
||||
this._owner.conquer(tile);
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { simpleHash } from "../Util";
|
||||
export class BotExecution implements Execution {
|
||||
private active = true;
|
||||
private readonly random: PseudoRandom;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private neighborsTerraNullius = true;
|
||||
|
||||
private behavior: BotBehavior | null = null;
|
||||
@@ -42,6 +42,7 @@ export class BotExecution implements Execution {
|
||||
}
|
||||
|
||||
if (this.behavior === null) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
this.behavior = new BotBehavior(
|
||||
this.random,
|
||||
this.mg,
|
||||
@@ -63,7 +64,7 @@ export class BotExecution implements Execution {
|
||||
|
||||
private maybeAttack() {
|
||||
if (this.behavior === null) {
|
||||
throw new Error("not initialized");
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
const toAttack = this.behavior.getNeighborTraitorToAttack();
|
||||
if (toAttack !== null) {
|
||||
@@ -75,6 +76,7 @@ export class BotExecution implements Execution {
|
||||
}
|
||||
|
||||
if (this.neighborsTerraNullius) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.bot.sharesBorderWith(this.mg.terraNullius())) {
|
||||
this.behavior.sendAttack(this.mg.terraNullius());
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TileRef } from "../game/GameMap";
|
||||
import { TrainStationExecution } from "./TrainStationExecution";
|
||||
|
||||
export class CityExecution implements Execution {
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private city: Unit | null = null;
|
||||
private active = true;
|
||||
|
||||
@@ -47,6 +47,7 @@ export class CityExecution implements Execution {
|
||||
|
||||
createStation(): void {
|
||||
if (this.city !== null) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const nearbyFactory = this.mg.hasUnitNearby(
|
||||
this.city.tile(),
|
||||
this.mg.config().trainStationMaxRange(),
|
||||
|
||||
@@ -21,11 +21,11 @@ import { WarshipExecution } from "./WarshipExecution";
|
||||
export class ConstructionExecution implements Execution {
|
||||
private construction: Unit | null = null;
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
|
||||
private ticksUntilComplete: Tick;
|
||||
private ticksUntilComplete: Tick | undefined;
|
||||
|
||||
private cost: Gold;
|
||||
private cost: Gold | undefined;
|
||||
|
||||
constructor(
|
||||
private player: Player,
|
||||
@@ -53,6 +53,7 @@ export class ConstructionExecution implements Execution {
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.construction === null) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const info = this.mg.unitInfo(this.constructionType);
|
||||
if (info.constructionDuration === undefined) {
|
||||
this.completeConstruction();
|
||||
@@ -90,15 +91,18 @@ export class ConstructionExecution implements Execution {
|
||||
this.player = this.construction.owner();
|
||||
this.construction.delete(false);
|
||||
// refund the cost so player has the gold to build the unit
|
||||
if (this.cost === undefined) throw new Error("Not initialized");
|
||||
this.player.addGold(this.cost);
|
||||
this.completeConstruction();
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (this.ticksUntilComplete === undefined) throw new Error("Not initialized");
|
||||
this.ticksUntilComplete--;
|
||||
}
|
||||
|
||||
private completeConstruction() {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const { player } = this;
|
||||
switch (this.constructionType) {
|
||||
case UnitType.AtomBomb:
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ShellExecution } from "./ShellExecution";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class DefensePostExecution implements Execution {
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private post: Unit | null = null;
|
||||
private active = true;
|
||||
|
||||
@@ -24,6 +24,7 @@ export class DefensePostExecution implements Execution {
|
||||
private shoot() {
|
||||
if (this.post === null) return;
|
||||
if (this.target === null) return;
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const shellAttackRate = this.mg.config().defensePostShellAttackRate();
|
||||
if (this.mg.ticks() - this.lastShellAttack > shellAttackRate) {
|
||||
this.lastShellAttack = this.mg.ticks();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Execution, Game, MessageType, Player } from "../game/Game";
|
||||
|
||||
export class DeleteUnitExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly player: Player,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Execution, Game, Gold, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class DonateGoldExecution implements Execution {
|
||||
private recipient: Player;
|
||||
private recipient: Player | undefined;
|
||||
|
||||
private active = true;
|
||||
|
||||
@@ -23,7 +23,8 @@ export class DonateGoldExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.gold === null) throw new Error("not initialized");
|
||||
if (this.gold === null) throw new Error("Not initialized");
|
||||
if (this.recipient === undefined) throw new Error("Not initialized");
|
||||
if (
|
||||
this.sender.canDonateGold(this.recipient) &&
|
||||
this.sender.donateGold(this.recipient, this.gold)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class DonateTroopsExecution implements Execution {
|
||||
private recipient: Player;
|
||||
private recipient: Player | undefined;
|
||||
|
||||
private active = true;
|
||||
|
||||
@@ -26,7 +26,8 @@ export class DonateTroopsExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.troops === null) throw new Error("not initialized");
|
||||
if (this.troops === null) throw new Error("Not initialized");
|
||||
if (this.recipient === undefined) throw new Error("Not initialized");
|
||||
if (
|
||||
this.sender.canDonateTroops(this.recipient) &&
|
||||
this.sender.donateTroops(this.recipient, this.troops)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
export class EmbargoExecution implements Execution {
|
||||
private active = true;
|
||||
|
||||
private target: Player;
|
||||
private target: Player | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly player: Player,
|
||||
@@ -21,6 +21,7 @@ export class EmbargoExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(_: number): void {
|
||||
if (this.target === undefined) throw new Error("Not initialized");
|
||||
if (this.action === "start") this.player.addEmbargo(this.target, false);
|
||||
else this.player.stopEmbargo(this.target);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { flattenedEmojiTable } from "../Util";
|
||||
|
||||
export class EmojiExecution implements Execution {
|
||||
private recipient: Player | typeof AllPlayers;
|
||||
private recipient: Player | typeof AllPlayers | undefined;
|
||||
|
||||
private active = true;
|
||||
|
||||
@@ -33,6 +33,7 @@ export class EmojiExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.recipient === undefined) throw new Error("Not initialized");
|
||||
const emojiString = flattenedEmojiTable[this.emoji];
|
||||
if (emojiString === undefined) {
|
||||
console.warn(
|
||||
|
||||
@@ -5,7 +5,8 @@ import { TrainStationExecution } from "./TrainStationExecution";
|
||||
export class FactoryExecution implements Execution {
|
||||
private factory: Unit | null = null;
|
||||
private active = true;
|
||||
private game: Game;
|
||||
private game: Game | undefined;
|
||||
|
||||
constructor(
|
||||
private player: Player,
|
||||
private readonly tile: TileRef,
|
||||
@@ -46,6 +47,7 @@ export class FactoryExecution implements Execution {
|
||||
|
||||
createStation(): void {
|
||||
if (this.factory !== null) {
|
||||
if (this.game === undefined) throw new Error("Not initialized");
|
||||
const structures = this.game.nearbyUnits(
|
||||
this.factory.tile(),
|
||||
this.game.config().trainStationMaxRange(),
|
||||
|
||||
@@ -30,7 +30,7 @@ export class FakeHumanExecution implements Execution {
|
||||
private active = true;
|
||||
private readonly random: PseudoRandom;
|
||||
private behavior: BotBehavior | null = null;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private player: Player | null = null;
|
||||
|
||||
private readonly attackRate: number;
|
||||
@@ -69,6 +69,7 @@ export class FakeHumanExecution implements Execution {
|
||||
private updateRelationsFromEmbargos() {
|
||||
const { player } = this;
|
||||
if (player === null) return;
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const others = this.mg.players().filter((p) => p.id() !== player.id());
|
||||
|
||||
others.forEach((other: Player) => {
|
||||
@@ -92,6 +93,7 @@ export class FakeHumanExecution implements Execution {
|
||||
private handleEmbargoesToHostileNations() {
|
||||
const { player } = this;
|
||||
if (player === null) return;
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const others = this.mg.players().filter((p) => p.id() !== player.id());
|
||||
|
||||
others.forEach((other: Player) => {
|
||||
@@ -114,6 +116,7 @@ export class FakeHumanExecution implements Execution {
|
||||
tick(ticks: number) {
|
||||
if (ticks % this.attackRate !== this.attackTick) return;
|
||||
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.mg.inSpawnPhase()) {
|
||||
const rl = this.randomLand();
|
||||
if (rl === null) {
|
||||
@@ -164,13 +167,15 @@ export class FakeHumanExecution implements Execution {
|
||||
|
||||
private maybeAttack() {
|
||||
if (this.player === null || this.behavior === null) {
|
||||
throw new Error("not initialized");
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
const game = this.mg;
|
||||
if (game === undefined) throw new Error("Not initialized");
|
||||
const enemyborder = Array.from(this.player.borderTiles())
|
||||
.flatMap((t) => this.mg.neighbors(t))
|
||||
.flatMap((t) => game.neighbors(t))
|
||||
.filter(
|
||||
(t) =>
|
||||
this.mg.isLand(t) && this.mg.ownerID(t) !== this.player?.smallID(),
|
||||
game.isLand(t) && game.ownerID(t) !== this.player?.smallID(),
|
||||
);
|
||||
|
||||
if (enemyborder.length === 0) {
|
||||
@@ -185,10 +190,10 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
const borderPlayers = enemyborder.map((t) =>
|
||||
this.mg.playerBySmallID(this.mg.ownerID(t)),
|
||||
game.playerBySmallID(game.ownerID(t)),
|
||||
);
|
||||
if (borderPlayers.some((o) => !o.isPlayer())) {
|
||||
this.behavior.sendAttack(this.mg.terraNullius());
|
||||
this.behavior.sendAttack(game.terraNullius());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,7 +233,7 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private shouldAttack(other: Player): boolean {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
if (this.player.isOnSameTeam(other)) {
|
||||
return false;
|
||||
}
|
||||
@@ -249,6 +254,7 @@ export class FakeHumanExecution implements Execution {
|
||||
if (other.isTraitor()) {
|
||||
return false;
|
||||
}
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const { difficulty } = this.mg.config().gameConfig();
|
||||
if (
|
||||
difficulty === Difficulty.Hard ||
|
||||
@@ -264,9 +270,10 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private maybeSendEmoji(enemy: Player) {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
if (enemy.type() !== PlayerType.Human) return;
|
||||
const lastSent = this.lastEmojiSent.get(enemy) ?? -300;
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.mg.ticks() - lastSent <= 300) return;
|
||||
this.lastEmojiSent.set(enemy, this.mg.ticks());
|
||||
this.mg.addExecution(
|
||||
@@ -279,7 +286,8 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private maybeSendNuke(other: Player) {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
const silos = this.player.units(UnitType.MissileSilo);
|
||||
if (
|
||||
silos.length === 0 ||
|
||||
@@ -328,6 +336,7 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private removeOldNukeEvents() {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const maxAge = 500;
|
||||
const tick = this.mg.ticks();
|
||||
while (
|
||||
@@ -339,7 +348,8 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private sendNuke(tile: TileRef) {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
const tick = this.mg.ticks();
|
||||
this.lastNukeSent.push([tick, tile]);
|
||||
this.mg.addExecution(
|
||||
@@ -348,10 +358,12 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private nukeTileScore(tile: TileRef, silos: Unit[], targets: Unit[]): number {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const game = this.mg;
|
||||
// Potential damage in a 25-tile radius
|
||||
const dist = euclDistFN(tile, 25, false);
|
||||
let tileValue = targets
|
||||
.filter((unit) => dist(this.mg, unit.tile()))
|
||||
.filter((unit) => dist(game, unit.tile()))
|
||||
.map((unit): number => {
|
||||
switch (unit.type()) {
|
||||
case UnitType.City:
|
||||
@@ -374,7 +386,7 @@ export class FakeHumanExecution implements Execution {
|
||||
50_000 *
|
||||
targets.filter(
|
||||
(unit) =>
|
||||
unit.type() === UnitType.SAMLauncher && dist50(this.mg, unit.tile()),
|
||||
unit.type() === UnitType.SAMLauncher && dist50(game, unit.tile()),
|
||||
).length;
|
||||
|
||||
// Prefer tiles that are closer to a silo
|
||||
@@ -388,7 +400,7 @@ export class FakeHumanExecution implements Execution {
|
||||
|
||||
// Don't target near recent targets
|
||||
tileValue -= this.lastNukeSent
|
||||
.filter(([_tick, tile]) => dist(this.mg, tile))
|
||||
.filter(([_tick, tile]) => dist(game, tile))
|
||||
.map((_) => 1_000_000)
|
||||
.reduce((prev, cur) => prev + cur, 0);
|
||||
|
||||
@@ -396,14 +408,13 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private maybeSendBoatAttack(other: Player) {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
if (this.player.isOnSameTeam(other)) return;
|
||||
const closest = closestTwoTiles(
|
||||
this.mg,
|
||||
Array.from(this.player.borderTiles()).filter((t) =>
|
||||
this.mg.isOceanShore(t),
|
||||
),
|
||||
Array.from(other.borderTiles()).filter((t) => this.mg.isOceanShore(t)),
|
||||
Array.from(this.player.borderTiles()).filter((t) => this.mg?.isOceanShore(t)),
|
||||
Array.from(other.borderTiles()).filter((t) => this.mg?.isOceanShore(t)),
|
||||
);
|
||||
if (closest === null) {
|
||||
return;
|
||||
@@ -430,7 +441,8 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private maybeSpawnStructure(type: UnitType): boolean {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
const owned = this.player.unitsOwned(type);
|
||||
const perceivedCostMultiplier = Math.min(owned + 1, 5);
|
||||
const realCost = this.cost(type);
|
||||
@@ -451,11 +463,11 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private structureSpawnTile(type: UnitType): TileRef | null {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
const tiles =
|
||||
type === UnitType.Port
|
||||
? Array.from(this.player.borderTiles()).filter((t) =>
|
||||
this.mg.isOceanShore(t),
|
||||
this.mg?.isOceanShore(t),
|
||||
)
|
||||
: Array.from(this.player.tiles());
|
||||
if (tiles.length === 0) return null;
|
||||
@@ -490,7 +502,8 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private structureSpawnTileValue(type: UnitType): (tile: TileRef) => number {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
const borderTiles = this.player.borderTiles();
|
||||
const { mg } = this;
|
||||
const otherUnits = this.player.units(type);
|
||||
@@ -547,7 +560,8 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private maybeSpawnWarship(): boolean {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
if (!this.random.chance(50)) {
|
||||
return false;
|
||||
}
|
||||
@@ -577,6 +591,7 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private randTerritoryTile(p: Player): TileRef | null {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const boundingBox = calculateBoundingBox(this.mg, p.borderTiles());
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const randX = this.random.nextInt(boundingBox.min.x, boundingBox.max.x);
|
||||
@@ -594,6 +609,7 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private warshipSpawnTile(portTile: TileRef): TileRef | null {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const radius = 250;
|
||||
for (let attempts = 0; attempts < 50; attempts++) {
|
||||
const randX = this.random.nextInt(
|
||||
@@ -618,14 +634,16 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private cost(type: UnitType): Gold {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
return this.mg.unitInfo(type).cost(this.player);
|
||||
}
|
||||
|
||||
sendBoatRandomly() {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
const oceanShore = Array.from(this.player.borderTiles()).filter((t) =>
|
||||
this.mg.isOceanShore(t),
|
||||
this.mg?.isOceanShore(t),
|
||||
);
|
||||
if (oceanShore.length === 0) {
|
||||
return;
|
||||
@@ -651,6 +669,7 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
randomLand(): TileRef | null {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const delta = 25;
|
||||
let tries = 0;
|
||||
while (tries < 50) {
|
||||
@@ -676,7 +695,8 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
|
||||
private randomBoatTarget(tile: TileRef, dist: number): TileRef | null {
|
||||
if (this.player === null) throw new Error("not initialized");
|
||||
if (this.player === null) throw new Error("Not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const x = this.mg.x(tile);
|
||||
const y = this.mg.y(tile);
|
||||
for (let i = 0; i < 500; i++) {
|
||||
|
||||
@@ -16,20 +16,20 @@ import { simpleHash } from "../Util";
|
||||
export class MirvExecution implements Execution {
|
||||
private active = true;
|
||||
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
|
||||
private nuke: Unit | null = null;
|
||||
|
||||
private readonly mirvRange = 1500;
|
||||
private readonly warheadCount = 350;
|
||||
|
||||
private random: PseudoRandom;
|
||||
private random: PseudoRandom | undefined;
|
||||
|
||||
private pathFinder: ParabolaPathFinder;
|
||||
private pathFinder: ParabolaPathFinder | undefined;
|
||||
|
||||
private targetPlayer: Player | TerraNullius;
|
||||
private targetPlayer: Player | TerraNullius | undefined;
|
||||
|
||||
private separateDst: TileRef;
|
||||
private separateDst: TileRef | undefined;
|
||||
|
||||
private speed = -1;
|
||||
|
||||
@@ -61,6 +61,9 @@ export class MirvExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.pathFinder === undefined) throw new Error("Not initialized");
|
||||
if (this.targetPlayer === undefined) throw new Error("Not initialized");
|
||||
if (this.nuke === null) {
|
||||
const spawn = this.player.canBuild(UnitType.MIRV, this.dst);
|
||||
if (spawn === false) {
|
||||
@@ -98,7 +101,9 @@ export class MirvExecution implements Execution {
|
||||
}
|
||||
|
||||
private separate() {
|
||||
if (this.nuke === null) throw new Error("uninitialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
if (this.nuke === null) throw new Error("Not initialized");
|
||||
const dsts: TileRef[] = [this.dst];
|
||||
let attempts = 1000;
|
||||
while (attempts > 0 && dsts.length < this.warheadCount) {
|
||||
@@ -110,9 +115,10 @@ export class MirvExecution implements Execution {
|
||||
dsts.push(potential);
|
||||
}
|
||||
console.log(`dsts: ${dsts.length}`);
|
||||
const game = this.mg;
|
||||
dsts.sort(
|
||||
(a, b) =>
|
||||
this.mg.manhattanDist(b, this.dst) - this.mg.manhattanDist(a, this.dst),
|
||||
game.manhattanDist(b, this.dst) - game.manhattanDist(a, this.dst),
|
||||
);
|
||||
console.log(`got ${dsts.length} dsts!!`);
|
||||
|
||||
@@ -133,6 +139,8 @@ export class MirvExecution implements Execution {
|
||||
}
|
||||
|
||||
randomLand(ref: TileRef, taken: TileRef[]): TileRef | null {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
let tries = 0;
|
||||
const mirvRange2 = this.mirvRange * this.mirvRange;
|
||||
while (tries < 100) {
|
||||
@@ -168,6 +176,7 @@ export class MirvExecution implements Execution {
|
||||
}
|
||||
|
||||
private proximityCheck(tile: TileRef, taken: TileRef[]): boolean {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
for (const t of taken) {
|
||||
if (this.mg.manhattanDist(tile, t) < 55) {
|
||||
return true;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class MissileSiloExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private silo: Unit | null = null;
|
||||
|
||||
constructor(
|
||||
@@ -38,6 +38,7 @@ export class MissileSiloExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const cooldown =
|
||||
this.mg.config().SiloCooldown() - (this.mg.ticks() - frontTime);
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ const SPRITE_RADIUS = 16;
|
||||
|
||||
export class NukeExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private nuke: Unit | null = null;
|
||||
private tilesToDestroyCache: Set<TileRef> | undefined;
|
||||
private pathFinder: ParabolaPathFinder;
|
||||
private pathFinder: ParabolaPathFinder | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly nukeType: NukeType,
|
||||
@@ -41,6 +41,7 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
public target(): Player | TerraNullius {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
return this.mg.owner(this.dst);
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ export class NukeExecution implements Execution {
|
||||
if (this.nuke === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
|
||||
const rand = new PseudoRandom(this.mg.ticks());
|
||||
const inner2 = magnitude.inner * magnitude.inner;
|
||||
@@ -63,6 +65,7 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
private maybeBreakAlliances(toDestroy: Set<TileRef>) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.nuke === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
@@ -94,6 +97,8 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.pathFinder === undefined) throw new Error("Not initialized");
|
||||
if (this.nuke === null) {
|
||||
const spawn = this.src ?? this.player.canBuild(this.nukeType, this.dst);
|
||||
if (spawn === false) {
|
||||
@@ -179,6 +184,8 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
private getTrajectory(target: TileRef): TrajectoryTile[] {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.pathFinder === undefined) throw new Error("Not initialized");
|
||||
const trajectoryTiles: TrajectoryTile[] = [];
|
||||
const targetRangeSquared =
|
||||
this.mg.config().defaultNukeTargetableRange() ** 2;
|
||||
@@ -199,6 +206,7 @@ export class NukeExecution implements Execution {
|
||||
nukeTile: TileRef,
|
||||
targetRangeSquared: number,
|
||||
): boolean {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
return (
|
||||
this.mg.euclideanDistSquared(nukeTile, targetTile) < targetRangeSquared ||
|
||||
(this.src !== undefined &&
|
||||
@@ -211,6 +219,7 @@ export class NukeExecution implements Execution {
|
||||
if (this.nuke === null || this.nuke.targetTile() === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const targetRangeSquared =
|
||||
this.mg.config().defaultNukeTargetableRange() ** 2;
|
||||
const targetTile = this.nuke.targetTile();
|
||||
@@ -221,6 +230,7 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
private detonate() {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.nuke === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
@@ -304,6 +314,7 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
|
||||
private redrawBuildings(range: number) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const rangeSquared = range * range;
|
||||
for (const unit of this.mg.units()) {
|
||||
if (isStructureType(unit.type())) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import { GameImpl } from "../game/GameImpl";
|
||||
export class PlayerExecution implements Execution {
|
||||
private readonly ticksPerClusterCalc = 20;
|
||||
|
||||
private config: Config;
|
||||
private config: Config | undefined;
|
||||
private lastCalc = 0;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private active = true;
|
||||
|
||||
constructor(private readonly player: Player) {}
|
||||
@@ -26,13 +26,15 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.config === undefined) throw new Error("Not initialized");
|
||||
this.player.decayRelations();
|
||||
this.player.units().forEach((u) => {
|
||||
const tileOwner = this.mg.owner(u.tile());
|
||||
const tileOwner = this.mg?.owner(u.tile());
|
||||
if (u.info().territoryBound) {
|
||||
if (tileOwner.isPlayer()) {
|
||||
if (tileOwner?.isPlayer()) {
|
||||
if (tileOwner !== this.player) {
|
||||
this.mg.player(tileOwner.id()).captureUnit(u);
|
||||
this.mg?.player(tileOwner.id()).captureUnit(u);
|
||||
}
|
||||
} else {
|
||||
u.delete();
|
||||
@@ -98,6 +100,7 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
private removeClusters() {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const clusters = this.calculateClusters();
|
||||
clusters.sort((a, b) => b.size - a.size);
|
||||
|
||||
@@ -117,6 +120,7 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
private surroundedBySamePlayer(cluster: Set<TileRef>): false | Player {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const enemies = new Set<number>();
|
||||
for (const tile of cluster) {
|
||||
const isOceanShore = this.mg.isOceanShore(tile);
|
||||
@@ -151,6 +155,7 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
private isSurrounded(cluster: Set<TileRef>): boolean {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const enemyTiles = new Set<TileRef>();
|
||||
for (const tr of cluster) {
|
||||
if (this.mg.isShore(tr) || this.mg.isOnEdgeOfMap(tr)) {
|
||||
@@ -174,6 +179,7 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
private removeCluster(cluster: Set<TileRef>) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (
|
||||
Array.from(cluster).some(
|
||||
(t) => this.mg?.ownerID(t) !== this.player?.smallID(),
|
||||
@@ -208,6 +214,7 @@ export class PlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
private getCapturingPlayer(cluster: Set<TileRef>): Player | null {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const neighborsIDs = new Set<number>();
|
||||
for (const t of cluster) {
|
||||
for (const neighbor of this.mg.neighbors(t)) {
|
||||
|
||||
@@ -6,10 +6,10 @@ import { TrainStationExecution } from "./TrainStationExecution";
|
||||
|
||||
export class PortExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private port: Unit | null = null;
|
||||
private random: PseudoRandom;
|
||||
private checkOffset: number;
|
||||
private random: PseudoRandom | undefined;
|
||||
private checkOffset: number | undefined;
|
||||
|
||||
constructor(
|
||||
private player: Player,
|
||||
@@ -23,9 +23,9 @@ export class PortExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === null || this.random === null || this.checkOffset === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
if (this.checkOffset === undefined) throw new Error("Not initialized");
|
||||
if (this.port === null) {
|
||||
const { tile } = this;
|
||||
const spawn = this.player.canBuild(UnitType.Port, tile);
|
||||
@@ -77,6 +77,8 @@ export class PortExecution implements Execution {
|
||||
}
|
||||
|
||||
shouldSpawnTradeShip(): boolean {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
const numTradeShips = this.mg.unitCount(UnitType.TradeShip);
|
||||
const spawnRate = this.mg.config().tradeShipSpawnRate(numTradeShips);
|
||||
const level = this.port?.level() ?? 0;
|
||||
@@ -89,6 +91,7 @@ export class PortExecution implements Execution {
|
||||
}
|
||||
|
||||
createStation(): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.port !== null) {
|
||||
const nearbyFactory = this.mg.hasUnitNearby(
|
||||
this.port.tile(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class QuickChatExecution implements Execution {
|
||||
private recipient: Player;
|
||||
private mg: Game;
|
||||
private recipient: Player | undefined;
|
||||
private mg: Game | undefined;
|
||||
|
||||
private active = true;
|
||||
|
||||
@@ -27,6 +27,8 @@ export class QuickChatExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.recipient === undefined) throw new Error("Not initialized");
|
||||
const message = this.getMessageFromKey(this.quickChatKey);
|
||||
|
||||
this.mg.displayChat(
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Railroad } from "../game/Railroad";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class RailroadExecution implements Execution {
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private active = true;
|
||||
private headIndex = 0;
|
||||
private tailIndex = 0;
|
||||
@@ -52,6 +52,7 @@ export class RailroadExecution implements Execution {
|
||||
/* eslint-enable sort-keys */
|
||||
|
||||
private computeExtremityDirection(tile: TileRef, next: TileRef): RailType {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const x = this.mg.x(tile);
|
||||
const y = this.mg.y(tile);
|
||||
const nextX = this.mg.x(next);
|
||||
@@ -75,9 +76,7 @@ export class RailroadExecution implements Execution {
|
||||
current: TileRef,
|
||||
next: TileRef,
|
||||
): RailType {
|
||||
if (this.mg === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
const x1 = this.mg.x(prev);
|
||||
const y1 = this.mg.y(prev);
|
||||
const x2 = this.mg.x(current);
|
||||
@@ -114,9 +113,7 @@ export class RailroadExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (!this.activeSourceOrDestination()) {
|
||||
this.active = false;
|
||||
return;
|
||||
|
||||
@@ -5,8 +5,8 @@ const cancelDelay = 20;
|
||||
export class RetreatExecution implements Execution {
|
||||
private active = true;
|
||||
private retreatOrdered = false;
|
||||
private startTick: number;
|
||||
private mg: Game;
|
||||
private startTick: number | undefined;
|
||||
private mg: Game | undefined;
|
||||
constructor(
|
||||
private readonly player: Player,
|
||||
private readonly attackID: string,
|
||||
@@ -18,6 +18,9 @@ export class RetreatExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.startTick === undefined) throw new Error("Not initialized");
|
||||
|
||||
if (!this.retreatOrdered) {
|
||||
this.player.orderRetreat(this.attackID);
|
||||
this.retreatOrdered = true;
|
||||
|
||||
@@ -126,14 +126,14 @@ class SAMTargetingSystem {
|
||||
}
|
||||
|
||||
export class SAMLauncherExecution implements Execution {
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private active = true;
|
||||
|
||||
// As MIRV go very fast we have to detect them very early but we only
|
||||
// shoot the one targeting very close (MIRVWarheadProtectionRadius)
|
||||
private readonly MIRVWarheadSearchRadius = 400;
|
||||
private readonly MIRVWarheadProtectionRadius = 50;
|
||||
private targetingSystem: SAMTargetingSystem;
|
||||
private targetingSystem: SAMTargetingSystem | undefined;
|
||||
|
||||
private pseudoRandom: PseudoRandom | undefined;
|
||||
|
||||
@@ -156,6 +156,7 @@ export class SAMLauncherExecution implements Execution {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (type === UnitType.MIRVWarhead) {
|
||||
return random < this.mg.config().samWarheadHittingChance();
|
||||
}
|
||||
@@ -164,9 +165,7 @@ export class SAMLauncherExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === null || this.player === null) {
|
||||
throw new Error("Not initialized");
|
||||
}
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.sam === null) {
|
||||
if (this.tile === null) {
|
||||
throw new Error("tile is null");
|
||||
@@ -211,6 +210,7 @@ export class SAMLauncherExecution implements Execution {
|
||||
this.MIRVWarheadSearchRadius,
|
||||
UnitType.MIRVWarhead,
|
||||
({ unit }) => {
|
||||
if (this.mg === undefined) return false;
|
||||
if (!isUnit(unit)) return false;
|
||||
if (unit.owner() === this.player) return false;
|
||||
if (this.player.isFriendly(unit.owner())) return false;
|
||||
|
||||
@@ -13,9 +13,9 @@ import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class SAMMissileExecution implements Execution {
|
||||
private active = true;
|
||||
private pathFinder: AirPathFinder;
|
||||
private pathFinder: AirPathFinder | undefined;
|
||||
private SAMMissile: Unit | undefined;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private speed = 0;
|
||||
|
||||
constructor(
|
||||
@@ -33,6 +33,8 @@ export class SAMMissileExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.pathFinder === undefined) throw new Error("Not initialized");
|
||||
this.SAMMissile ??= this._owner.buildUnit(
|
||||
UnitType.SAMMissile,
|
||||
this.spawn,
|
||||
|
||||
@@ -5,11 +5,11 @@ import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class ShellExecution implements Execution {
|
||||
private active = true;
|
||||
private pathFinder: AirPathFinder;
|
||||
private pathFinder: AirPathFinder | undefined;
|
||||
private shell: Unit | undefined;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private destroyAtTick = -1;
|
||||
private random: PseudoRandom;
|
||||
private random: PseudoRandom | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly spawn: TileRef,
|
||||
@@ -25,6 +25,8 @@ export class ShellExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.pathFinder === undefined) throw new Error("Not initialized");
|
||||
this.shell ??= this._owner.buildUnit(UnitType.Shell, this.spawn, {});
|
||||
if (!this.shell.isActive()) {
|
||||
this.active = false;
|
||||
@@ -62,6 +64,8 @@ export class ShellExecution implements Execution {
|
||||
}
|
||||
|
||||
private effectOnTarget(): number {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
const { damage } = this.mg.config().unitInfo(UnitType.Shell);
|
||||
const baseDamage = damage ?? 250;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getSpawnTiles } from "./Util";
|
||||
|
||||
export class SpawnExecution implements Execution {
|
||||
active = true;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly playerInfo: PlayerInfo,
|
||||
@@ -20,6 +20,7 @@ export class SpawnExecution implements Execution {
|
||||
tick(ticks: number) {
|
||||
this.active = false;
|
||||
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (!this.mg.isValidRef(this.tile)) {
|
||||
console.warn(`SpawnExecution: tile ${this.tile} not valid`);
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class TargetPlayerExecution implements Execution {
|
||||
private target: Player;
|
||||
private target: Player | undefined;
|
||||
|
||||
private active = true;
|
||||
|
||||
@@ -21,6 +21,7 @@ export class TargetPlayerExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.target === undefined) throw new Error("Not initialized");
|
||||
if (this.requestor.canTarget(this.target)) {
|
||||
this.requestor.target(this.target);
|
||||
this.target.updateRelation(this.requestor, -40);
|
||||
|
||||
@@ -14,10 +14,10 @@ import { renderNumber } from "../../client/Utils";
|
||||
|
||||
export class TradeShipExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private tradeShip: Unit | undefined;
|
||||
private wasCaptured = false;
|
||||
private pathFinder: PathFinder;
|
||||
private pathFinder: PathFinder | undefined;
|
||||
private tilesTraveled = 0;
|
||||
|
||||
constructor(
|
||||
@@ -32,6 +32,8 @@ export class TradeShipExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.pathFinder === undefined) throw new Error("Not initialized");
|
||||
if (this.tradeShip === undefined) {
|
||||
const spawn = this.origOwner.canBuild(
|
||||
UnitType.TradeShip,
|
||||
@@ -131,6 +133,7 @@ export class TradeShipExecution implements Execution {
|
||||
}
|
||||
|
||||
private complete() {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.tradeShip === undefined) throw new Error("Not initialized");
|
||||
this.active = false;
|
||||
this.tradeShip.delete(false);
|
||||
|
||||
@@ -4,9 +4,9 @@ import { TrainExecution } from "./TrainExecution";
|
||||
import { TrainStation } from "../game/TrainStation";
|
||||
|
||||
export class TrainStationExecution implements Execution {
|
||||
private mg: Game;
|
||||
private mg: Game | undefined;
|
||||
private active = true;
|
||||
private random: PseudoRandom;
|
||||
private random: PseudoRandom | undefined;
|
||||
private station: TrainStation | null = null;
|
||||
private readonly numCars = 5;
|
||||
private lastSpawnTick = 0;
|
||||
@@ -49,6 +49,8 @@ export class TrainStationExecution implements Execution {
|
||||
}
|
||||
|
||||
private shouldSpawnTrain(clusterSize: number): boolean {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
const spawnRate = this.mg.config().trainSpawnRate(clusterSize);
|
||||
for (let i = 0; i < this.unit.level(); i++) {
|
||||
if (this.random.chance(spawnRate)) {
|
||||
@@ -59,6 +61,8 @@ export class TrainStationExecution implements Execution {
|
||||
}
|
||||
|
||||
private spawnTrain(station: TrainStation, currentTick: number) {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
if (
|
||||
!this.spawnTrains ||
|
||||
currentTick - this.lastSpawnTick < this.ticksCooldown
|
||||
|
||||
@@ -15,23 +15,23 @@ import { TileRef } from "../game/GameMap";
|
||||
import { targetTransportTile } from "../game/TransportShipUtils";
|
||||
|
||||
export class TransportShipExecution implements Execution {
|
||||
private lastMove: number;
|
||||
private lastMove: number | undefined;
|
||||
|
||||
// TODO: make this configurable
|
||||
private readonly ticksPerMove = 1;
|
||||
|
||||
private active = true;
|
||||
|
||||
private mg: Game;
|
||||
private target: Player | TerraNullius;
|
||||
private mg: Game | undefined;
|
||||
private target: Player | TerraNullius | undefined;
|
||||
|
||||
// TODO make private
|
||||
public path: TileRef[];
|
||||
private dst: TileRef | null;
|
||||
public path: TileRef[] | undefined;
|
||||
private dst: TileRef | null = null;
|
||||
|
||||
private boat: Unit;
|
||||
private boat: Unit | undefined;
|
||||
|
||||
private pathFinder: PathFinder;
|
||||
private pathFinder: PathFinder | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly attacker: Player,
|
||||
@@ -158,6 +158,11 @@ export class TransportShipExecution implements Execution {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
if (this.boat === undefined) throw new Error("Not initialized");
|
||||
if (this.lastMove === undefined) throw new Error("Not initialized");
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.target === undefined) throw new Error("Not initialized");
|
||||
if (this.pathFinder === undefined) throw new Error("Not initialized");
|
||||
if (!this.boat.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Execution, Game, Player, Unit } from "../game/Game";
|
||||
|
||||
export class UpgradeStructureExecution implements Execution {
|
||||
private structure: Unit | undefined;
|
||||
private readonly cost: bigint;
|
||||
private readonly cost: bigint | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly player: Player,
|
||||
|
||||
@@ -14,10 +14,10 @@ import { ShellExecution } from "./ShellExecution";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class WarshipExecution implements Execution {
|
||||
private random: PseudoRandom;
|
||||
private warship: Unit;
|
||||
private mg: Game;
|
||||
private pathfinder: PathFinder;
|
||||
private random: PseudoRandom | undefined;
|
||||
private warship: Unit | undefined;
|
||||
private mg: Game | undefined;
|
||||
private pathfinder: PathFinder | undefined;
|
||||
private lastShellAttack = 0;
|
||||
private readonly alreadySentShell = new Set<Unit>();
|
||||
|
||||
@@ -51,6 +51,7 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.warship === undefined) throw new Error("Not initialized");
|
||||
if (this.warship.health() <= 0) {
|
||||
this.warship.delete();
|
||||
return;
|
||||
@@ -75,6 +76,8 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
private findTargetUnit(): Unit | undefined {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.warship === undefined) throw new Error("Not initialized");
|
||||
const hasPort = this.warship.owner().unitCount(UnitType.Port) > 0;
|
||||
const patrolRangeSquared = this.mg.config().warshipPatrolRange() ** 2;
|
||||
|
||||
@@ -152,6 +155,8 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
private shootTarget() {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.warship === undefined) throw new Error("Not initialized");
|
||||
const targetUnit = this.warship.targetUnit();
|
||||
if (targetUnit === undefined) return;
|
||||
const shellAttackRate = this.mg.config().warshipShellAttackRate();
|
||||
@@ -178,6 +183,8 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
private huntDownTradeShip() {
|
||||
if (this.pathfinder === undefined) throw new Error("Not initialized");
|
||||
if (this.warship === undefined) throw new Error("Not initialized");
|
||||
const targetUnit = this.warship.targetUnit();
|
||||
if (targetUnit === undefined) return;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
@@ -207,6 +214,8 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
private patrol() {
|
||||
if (this.pathfinder === undefined) throw new Error("Not initialized");
|
||||
if (this.warship === undefined) throw new Error("Not initialized");
|
||||
let targetTile = this.warship.targetTile();
|
||||
if (targetTile === undefined) {
|
||||
targetTile = this.randomTile();
|
||||
@@ -238,7 +247,7 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.warship?.isActive();
|
||||
return this.warship?.isActive() ?? false;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
@@ -246,6 +255,9 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
|
||||
randomTile(allowShoreline = false): TileRef | undefined {
|
||||
if (this.mg === undefined) throw new Error("Not initialized");
|
||||
if (this.random === undefined) throw new Error("Not initialized");
|
||||
if (this.warship === undefined) throw new Error("Not initialized");
|
||||
let warshipPatrolRange = this.mg.config().warshipPatrolRange();
|
||||
const maxAttemptBeforeExpand = 500;
|
||||
let attempts = 0;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { flattenedEmojiTable } from "../../Util";
|
||||
|
||||
export class BotBehavior {
|
||||
private enemy: Player | null = null;
|
||||
private enemyUpdated: Tick;
|
||||
private enemyUpdated: Tick | undefined;
|
||||
|
||||
private readonly assistAcceptEmoji = flattenedEmojiTable.indexOf("👍");
|
||||
|
||||
@@ -75,6 +75,7 @@ export class BotBehavior {
|
||||
}
|
||||
|
||||
forgetOldEnemies() {
|
||||
if (this.enemyUpdated === undefined) return;
|
||||
// Forget old enemies
|
||||
if (this.game.ticks() - this.enemyUpdated > 100) {
|
||||
this.clearEnemy();
|
||||
|
||||
@@ -283,7 +283,7 @@ export class Nation {
|
||||
}
|
||||
|
||||
export class Cell {
|
||||
public index: number;
|
||||
public index: number | undefined;
|
||||
|
||||
private readonly strRepr: string;
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export class GameImpl implements Game {
|
||||
private updates: GameUpdates = createGameUpdatesMap();
|
||||
private readonly unitGrid: UnitGrid;
|
||||
|
||||
private playerTeams: Team[];
|
||||
private playerTeams: Team[] = [];
|
||||
private readonly botTeam: Team = ColoredTeams.Bot;
|
||||
private readonly _railNetwork: RailNetwork = createRailNetwork(this);
|
||||
|
||||
@@ -125,7 +125,8 @@ export class GameImpl implements Game {
|
||||
if (numPlayerTeams < 2) {
|
||||
throw new Error(`Too few teams: ${numPlayerTeams}`);
|
||||
} else if (numPlayerTeams < 8) {
|
||||
this.playerTeams = [ColoredTeams.Red, ColoredTeams.Blue];
|
||||
this.playerTeams.push(ColoredTeams.Red);
|
||||
this.playerTeams.push(ColoredTeams.Blue);
|
||||
if (numPlayerTeams >= 3) this.playerTeams.push(ColoredTeams.Yellow);
|
||||
if (numPlayerTeams >= 4) this.playerTeams.push(ColoredTeams.Green);
|
||||
if (numPlayerTeams >= 5) this.playerTeams.push(ColoredTeams.Purple);
|
||||
|
||||
@@ -119,7 +119,7 @@ export class PlayerImpl implements Player {
|
||||
this._pseudo_random = new PseudoRandom(simpleHash(this.playerInfo.id));
|
||||
}
|
||||
|
||||
largestClusterBoundingBox: { min: Cell; max: Cell } | null;
|
||||
largestClusterBoundingBox: { min: Cell; max: Cell } | null = null;
|
||||
|
||||
toUpdate(): PlayerUpdate {
|
||||
const outgoingAllianceRequests = this.outgoingAllianceRequests().map((ar) =>
|
||||
|
||||
@@ -87,7 +87,7 @@ export function createTrainStopHandlers(
|
||||
export class TrainStation {
|
||||
private readonly stopHandlers: Partial<Record<UnitType, TrainStopHandler>> =
|
||||
{};
|
||||
private cluster: Cluster | null;
|
||||
private cluster: Cluster | null = null;
|
||||
private readonly railroads: Set<Railroad> = new Set();
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -106,7 +106,7 @@ export class PathFinder {
|
||||
private curr: TileRef | null = null;
|
||||
private dst: TileRef | null = null;
|
||||
private path: TileRef[] | null = null;
|
||||
private aStar: AStar<TileRef>;
|
||||
private aStar: AStar<TileRef> | undefined;
|
||||
private computeFinished = true;
|
||||
|
||||
private constructor(
|
||||
@@ -170,7 +170,7 @@ export class PathFinder {
|
||||
}
|
||||
}
|
||||
|
||||
switch (this.aStar.compute()) {
|
||||
switch (this.aStar?.compute()) {
|
||||
case PathFindResultType.Completed:
|
||||
this.computeFinished = true;
|
||||
this.path = this.aStar.reconstructPath();
|
||||
|
||||
Reference in New Issue
Block a user