diff --git a/src/client/Main.ts b/src/client/Main.ts index b9cadc34f..c45057e81 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -63,6 +63,7 @@ declare global { // Extend the global interfaces to include your custom events interface DocumentEventMap { "join-lobby": CustomEvent; + "kick-player": CustomEvent; } } diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index d2fd60c6e..2e8201240 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -4,6 +4,7 @@ import { Executor } from "./execution/ExecutionManager"; import { WinCheckExecution } from "./execution/WinCheckExecution"; import { AllPlayers, + Attack, Cell, Game, GameUpdates, @@ -35,7 +36,7 @@ export async function createGameRunner( gameStart: GameStartInfo, clientID: ClientID, mapLoader: GameMapLoader, - callBack: (gu: GameUpdateViewData) => void, + callBack: (gu: GameUpdateViewData | ErrorUpdate) => void, ): Promise { const config = await getConfig(gameStart.config, null); const gameMap = await loadGameMap(gameStart.config.gameMap, mapLoader); @@ -231,7 +232,7 @@ export class GameRunner { throw new Error(`player with id ${playerID} not found`); } - const condition = (a) => a.id() === attackID; + const condition = (a: Attack) => a.id() === attackID; const attack = player.outgoingAttacks().find(condition) ?? player.incomingAttacks().find(condition); diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 735c2b9fd..0871acf9e 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -171,7 +171,7 @@ const TokenSchema = z .string() .refine( (v) => - PersistentIdSchema.safeParse(v).success || + PersistentIdSchema.safeParse(v).success ?? JwtTokenSchema.safeParse(v).success, { message: "Token must be a valid UUID or JWT", @@ -213,7 +213,11 @@ export const RequiredPatternSchema = z new PatternDecoder(val, base64url.decode); return true; } catch (e) { - console.error(JSON.stringify(e.message, null, 2)); + if (e instanceof Error) { + console.error(JSON.stringify(e.message, null, 2)); + } else { + console.error(String(e)); + } return false; } }, diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index b5920b5f7..828631757 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -350,7 +350,7 @@ export class FakeHumanExecution implements Execution { const dist = euclDistFN(tile, 25, false); let tileValue = targets .filter((unit) => dist(this.mg, unit.tile())) - .map((unit) => { + .map((unit): number => { switch (unit.type()) { case UnitType.City: return 25_000; diff --git a/src/core/execution/PlayerExecution.ts b/src/core/execution/PlayerExecution.ts index 913ea0c1d..db021bd7f 100644 --- a/src/core/execution/PlayerExecution.ts +++ b/src/core/execution/PlayerExecution.ts @@ -1,7 +1,7 @@ import { Config } from "../configuration/Config"; import { Execution, Game, Player, UnitType } from "../game/Game"; import { GameImpl } from "../game/GameImpl"; -import { TileRef } from "../game/GameMap"; +import { GameMap, TileRef } from "../game/GameMap"; import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"; export class PlayerExecution implements Execution { @@ -190,7 +190,11 @@ export class PlayerExecution implements Execution { } const firstTile = cluster.values().next().value; - const filter = (_, t: TileRef): boolean => + if (!firstTile) { + return; + } + + const filter = (_: GameMap, t: TileRef): boolean => this.mg?.ownerID(t) === this.player?.smallID(); const tiles = this.mg.bfs(firstTile, filter); diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index 484028aec..15891db07 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -1,6 +1,7 @@ import { Execution, Game, + isUnit, MessageType, Player, Unit, @@ -80,7 +81,9 @@ class SAMTargetingSystem { [UnitType.AtomBomb, UnitType.HydrogenBomb], ({ unit }) => { return ( - unit.owner() !== this.player && !this.player.isFriendly(unit.owner()) + isUnit(unit) && + unit.owner() !== this.player && + !this.player.isFriendly(unit.owner()) ); }, ); @@ -212,6 +215,7 @@ export class SAMLauncherExecution implements Execution { this.MIRVWarheadSearchRadius, UnitType.MIRVWarhead, ({ unit }) => { + if (!isUnit(unit)) return false; if (unit.owner() === this.player) return false; if (this.player.isFriendly(unit.owner())) return false; const dst = unit.targetTile(); diff --git a/src/core/game/BinaryLoaderGameMapLoader.ts b/src/core/game/BinaryLoaderGameMapLoader.ts index 2107b6faa..31e058026 100644 --- a/src/core/game/BinaryLoaderGameMapLoader.ts +++ b/src/core/game/BinaryLoaderGameMapLoader.ts @@ -31,7 +31,9 @@ export class BinaryLoaderGameMapLoader implements GameMapLoader { return cachedMap; } - const key = Object.keys(GameMapType).find((k) => GameMapType[k] === map); + const key = Object.keys(GameMapType).find( + (k) => GameMapType[k as keyof typeof GameMapType] === map, + ); const fileName = key?.toLowerCase(); const mapData = { diff --git a/src/core/game/FetchGameMapLoader.ts b/src/core/game/FetchGameMapLoader.ts index 5d6c72367..cfde1360f 100644 --- a/src/core/game/FetchGameMapLoader.ts +++ b/src/core/game/FetchGameMapLoader.ts @@ -17,7 +17,9 @@ export class FetchGameMapLoader implements GameMapLoader { return cachedMap; } - const key = Object.keys(GameMapType).find((k) => GameMapType[k] === map); + const key = Object.keys(GameMapType).find( + (k) => GameMapType[k as keyof typeof GameMapType] === map, + ); const fileName = key?.toLowerCase(); if (!fileName) { diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 2a4991afd..6272d4622 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -9,6 +9,7 @@ import { } from "./GameUpdates"; import { RailNetwork } from "./RailNetwork"; import { Stats } from "./Stats"; +import { UnitPredicate } from "./UnitGrid"; export type PlayerID = string; export type Tick = number; @@ -389,9 +390,10 @@ export class PlayerInfo { } } -export function isUnit(unit: Unit | UnitParams): unit is Unit { +export function isUnit(unit: unknown): unit is Unit { return ( - unit !== undefined && + unit && + typeof unit === "object" && "isUnit" in unit && typeof unit.isUnit === "function" && unit.isUnit() @@ -662,12 +664,12 @@ export interface Game extends GameMap { searchRange: number, type: UnitType, playerId: PlayerID, - ); + ): boolean; nearbyUnits( tile: TileRef, searchRange: number, types: UnitType | UnitType[], - predicate?: (value: { unit: Unit; distSquared: number }) => boolean, + predicate?: UnitPredicate, ): Array<{ unit: Unit; distSquared: number }>; addExecution(...exec: Execution[]): void; @@ -703,7 +705,7 @@ export interface Game extends GameMap { addUpdate(update: GameUpdate): void; railNetwork(): RailNetwork; - conquerPlayer(conqueror: Player, conquered: Player); + conquerPlayer(conqueror: Player, conquered: Player): void; } export interface PlayerActions { diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 1d6349608..b85073e75 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -40,7 +40,7 @@ import { Stats } from "./Stats"; import { StatsImpl } from "./StatsImpl"; import { assignTeams } from "./TeamAssignment"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; -import { UnitGrid } from "./UnitGrid"; +import { UnitGrid, UnitPredicate } from "./UnitGrid"; export function createGame( humans: PlayerInfo[], @@ -758,7 +758,7 @@ export class GameImpl implements Game { tile: TileRef, searchRange: number, types: UnitType | UnitType[], - predicate?: (value: { unit: Unit; distSquared: number }) => boolean, + predicate?: UnitPredicate, ): Array<{ unit: Unit; distSquared: number }> { return this.unitGrid.nearbyUnits( tile, diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 4dcf48149..c04eea8d8 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -18,7 +18,7 @@ export interface GameUpdateViewData { tick: number; updates: GameUpdates; packedTileUpdates: BigUint64Array; - playerNameViewData: Record; + playerNameViewData: Record; } export interface ErrorUpdate { diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 70cbcbf52..12973a056 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -34,7 +34,7 @@ import { } from "./GameUpdates"; import { TerrainMapData } from "./TerrainMapLoader"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; -import { UnitGrid } from "./UnitGrid"; +import { UnitGrid, UnitPredicate } from "./UnitGrid"; import { UserSettings } from "./UserSettings"; const userSettings: UserSettings = new UserSettings(); @@ -472,7 +472,7 @@ export class GameView implements GameMap { tile: TileRef, searchRange: number, types: UnitType | UnitType[], - predicate?: (value: { unit: UnitView; distSquared: number }) => boolean, + predicate?: UnitPredicate, ): Array<{ unit: UnitView; distSquared: number }> { return this.unitGrid.nearbyUnits( tile, diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 59f2819ac..3fe210f59 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -211,9 +211,9 @@ export class PlayerImpl implements Player { return this._units.filter((u) => ts.has(u.type())); } - private numUnitsConstructed: number[] = []; + private numUnitsConstructed: Partial> = {}; private recordUnitConstructed(type: UnitType): void { - if (type in this.numUnitsConstructed) { + if (this.numUnitsConstructed[type] !== undefined) { this.numUnitsConstructed[type]++; } else { this.numUnitsConstructed[type] = 1; diff --git a/src/core/game/UnitGrid.ts b/src/core/game/UnitGrid.ts index 027f1b33f..15617c752 100644 --- a/src/core/game/UnitGrid.ts +++ b/src/core/game/UnitGrid.ts @@ -2,6 +2,11 @@ import { PlayerID, Unit, UnitType } from "./Game"; import { GameMap, TileRef } from "./GameMap"; import { UnitView } from "./GameView"; +export type UnitPredicate = (value: { + unit: Unit | UnitView; + distSquared: number; +}) => boolean; + export class UnitGrid { private grid: Map>[][]; private readonly cellSize = 100; @@ -130,11 +135,8 @@ export class UnitGrid { nearbyUnits( tile: TileRef, searchRange: number, - types: UnitType | UnitType[], - predicate?: (value: { - unit: Unit | UnitView; - distSquared: number; - }) => boolean, + types: readonly UnitType[] | UnitType, + predicate?: UnitPredicate, ): Array<{ unit: Unit | UnitView; distSquared: number }> { const nearby: Array<{ unit: Unit | UnitView; distSquared: number }> = []; const { startGridX, endGridX, startGridY, endGridY } = this.getCellsInRange( diff --git a/src/core/utilities/Line.ts b/src/core/utilities/Line.ts index e8c673533..67024e9c6 100644 --- a/src/core/utilities/Line.ts +++ b/src/core/utilities/Line.ts @@ -124,7 +124,7 @@ export class DistanceBasedBezierCurve extends CubicBezierCurve { /** * Precompute all points spaced @p pixelSpacing apart */ - computeAllPoints(pixelSpacing: number, precision): void { + computeAllPoints(pixelSpacing: number, precision: number): void { this.cachedPoints = []; this.totalDistance = 0; this.currentIndex = 0; diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 29e67f214..1014968fb 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -1,7 +1,7 @@ import version from "../../../resources/version.txt"; import { createGameRunner, GameRunner } from "../GameRunner"; import { FetchGameMapLoader } from "../game/FetchGameMapLoader"; -import { GameUpdateViewData } from "../game/GameUpdates"; +import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates"; import { AttackAveragePositionResultMessage, InitializedMessage, @@ -17,7 +17,11 @@ const ctx: Worker = self as any; let gameRunner: Promise | null = null; const mapLoader = new FetchGameMapLoader(`/maps`, version); -function gameUpdate(gu: GameUpdateViewData) { +function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) { + // skip if ErrorUpdate + if (!("updates" in gu)) { + return; + } sendMessage({ type: "game_update", gameUpdate: gu, diff --git a/tests/UnitGrid.test.ts b/tests/UnitGrid.test.ts index 9a0869ace..bd6629910 100644 --- a/tests/UnitGrid.test.ts +++ b/tests/UnitGrid.test.ts @@ -29,7 +29,7 @@ async function nearbyUnits( unitPosX: number, rangeCheck: number, range: number, - unitTypes: UnitType[], + unitTypes: readonly UnitType[], ) { const game = await setup(mapName, { infiniteGold: true, instantBuild: true }); const grid = new UnitGrid(game.map()); @@ -51,7 +51,7 @@ describe("Unit Grid range tests", () => { ["plains", 0, 10, 11, false], // Exactly 1px outside ["big_plains", 0, 198, 42, true], // Inside huge range ["big_plains", 0, 198, 199, false], // Exactly 1px outside huge range - ]; + ] as const; describe("Is unit in range", () => { test.each(hasUnitCases)( @@ -77,25 +77,18 @@ describe("Unit Grid range tests", () => { ["plains", 0, 10, 11, [UnitType.DefensePost], 0], // 1px outside ["big_plains", 0, 198, 42, [UnitType.TradeShip], 1], // Inside huge range ["big_plains", 0, 198, 199, [UnitType.TransportShip], 0], // 1px outside - ]; + ] as const; describe("Retrieve all units in range", () => { test.each(unitsInRangeCases)( "on %p map, look if unit at position %p with a range of %p is in range of %p position, returns %p", - async ( - mapName: string, - unitPosX: number, - range: number, - rangeCheck: number, - units: UnitType[], - expectedResult: number, - ) => { + async (mapName, unitPosX, range, rangeCheck, units, expectedResult) => { const result = await nearbyUnits( mapName, unitPosX, rangeCheck, range, - units, + units, // remove readonly ); expect(result.length).toBe(expectedResult); }, diff --git a/tsconfig.json b/tsconfig.json index 7c73290f3..6de45daab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,8 @@ "resolveJsonModule": true, "strictNullChecks": true, "useDefineForClassFields": false, - "strictPropertyInitialization": false + "strictPropertyInitialization": false, + "strict": true }, "include": [ "src/**/*",