mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 11:21:20 +00:00
Merge branch 'main' into nuke_trajectory
This commit is contained in:
@@ -112,6 +112,7 @@ export async function createClientGame(
|
||||
const config = await getConfig(
|
||||
lobbyConfig.gameStartInfo.config,
|
||||
userSettings,
|
||||
lobbyConfig.gameRecord != null,
|
||||
);
|
||||
let gameMap: TerrainMapData | null = null;
|
||||
|
||||
@@ -239,9 +240,11 @@ export class ClientGameRunner {
|
||||
this.lobby.gameStartInfo.gameID,
|
||||
this.lobby.clientID,
|
||||
);
|
||||
console.error(gu.stack);
|
||||
this.stop(true);
|
||||
return;
|
||||
}
|
||||
this.transport.turnComplete();
|
||||
gu.updates[GameUpdateType.Hash].forEach((hu: HashUpdate) => {
|
||||
this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash));
|
||||
});
|
||||
|
||||
+37
-13
@@ -16,42 +16,58 @@ import { LobbyConfig } from "./ClientGameRunner";
|
||||
import { getPersistentIDFromCookie } from "./Main";
|
||||
|
||||
export class LocalServer {
|
||||
// All turns from the game record on replay.
|
||||
private replayTurns: Turn[] = [];
|
||||
|
||||
private turns: Turn[] = [];
|
||||
|
||||
private intents: Intent[] = [];
|
||||
private startedAt: number;
|
||||
|
||||
private endTurnIntervalID;
|
||||
|
||||
private paused = false;
|
||||
|
||||
private winner: ClientSendWinnerMessage = null;
|
||||
private allPlayersStats: AllPlayersStats = {};
|
||||
|
||||
private turnsExecuted = 0;
|
||||
private lastTurnCompletedTime = 0;
|
||||
|
||||
private turnCheckInterval: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
private lobbyConfig: LobbyConfig,
|
||||
private clientConnect: () => void,
|
||||
private clientMessage: (message: ServerMessage) => void,
|
||||
private isReplay: boolean,
|
||||
) {}
|
||||
|
||||
start() {
|
||||
this.turnCheckInterval = setInterval(() => {
|
||||
if (this.turnsExecuted == this.turns.length) {
|
||||
if (
|
||||
this.isReplay ||
|
||||
Date.now() >
|
||||
this.lastTurnCompletedTime +
|
||||
this.lobbyConfig.serverConfig.turnIntervalMs()
|
||||
) {
|
||||
this.endTurn();
|
||||
}
|
||||
}
|
||||
}, 5);
|
||||
|
||||
this.startedAt = Date.now();
|
||||
if (!this.lobbyConfig.gameRecord) {
|
||||
this.endTurnIntervalID = setInterval(
|
||||
() => this.endTurn(),
|
||||
this.lobbyConfig.serverConfig.turnIntervalMs(),
|
||||
);
|
||||
}
|
||||
this.clientConnect();
|
||||
if (this.lobbyConfig.gameRecord) {
|
||||
this.turns = decompressGameRecord(this.lobbyConfig.gameRecord).turns;
|
||||
console.log(`loaded turns: ${JSON.stringify(this.turns)}`);
|
||||
this.replayTurns = decompressGameRecord(
|
||||
this.lobbyConfig.gameRecord,
|
||||
).turns;
|
||||
}
|
||||
this.clientMessage(
|
||||
ServerStartGameMessageSchema.parse({
|
||||
type: "start",
|
||||
gameID: this.lobbyConfig.gameStartInfo.gameID,
|
||||
gameStartInfo: this.lobbyConfig.gameStartInfo,
|
||||
turns: this.turns,
|
||||
turns: [],
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -90,7 +106,7 @@ export class LocalServer {
|
||||
return;
|
||||
}
|
||||
// If we are replaying a game then verify hash.
|
||||
const archivedHash = this.turns[clientMsg.turnNumber].hash;
|
||||
const archivedHash = this.replayTurns[clientMsg.turnNumber].hash;
|
||||
if (!archivedHash) {
|
||||
console.warn(
|
||||
`no archived hash found for turn ${clientMsg.turnNumber}, client hash: ${clientMsg.hash}`,
|
||||
@@ -121,10 +137,18 @@ export class LocalServer {
|
||||
}
|
||||
}
|
||||
|
||||
public turnComplete() {
|
||||
this.turnsExecuted++;
|
||||
this.lastTurnCompletedTime = Date.now();
|
||||
}
|
||||
|
||||
private endTurn() {
|
||||
if (this.paused) {
|
||||
return;
|
||||
}
|
||||
if (this.replayTurns.length > 0) {
|
||||
this.intents = this.replayTurns[this.turns.length].intents;
|
||||
}
|
||||
const pastTurn: Turn = {
|
||||
turnNumber: this.turns.length,
|
||||
intents: this.intents,
|
||||
@@ -139,7 +163,7 @@ export class LocalServer {
|
||||
|
||||
public endGame(saveFullGame: boolean = false) {
|
||||
consolex.log("local server ending game");
|
||||
clearInterval(this.endTurnIntervalID);
|
||||
clearInterval(this.turnCheckInterval);
|
||||
const players: PlayerRecord[] = [
|
||||
{
|
||||
ip: null,
|
||||
|
||||
+12
-1
@@ -263,7 +263,12 @@ export class Transport {
|
||||
onconnect: () => void,
|
||||
onmessage: (message: ServerMessage) => void,
|
||||
) {
|
||||
this.localServer = new LocalServer(this.lobbyConfig, onconnect, onmessage);
|
||||
this.localServer = new LocalServer(
|
||||
this.lobbyConfig,
|
||||
onconnect,
|
||||
onmessage,
|
||||
this.lobbyConfig.gameRecord != null,
|
||||
);
|
||||
this.localServer.start();
|
||||
}
|
||||
|
||||
@@ -318,6 +323,12 @@ export class Transport {
|
||||
this.connect(this.onconnect, this.onmessage);
|
||||
}
|
||||
|
||||
public turnComplete() {
|
||||
if (this.isLocal) {
|
||||
this.localServer.turnComplete();
|
||||
}
|
||||
}
|
||||
|
||||
private onSendLogEvent(event: SendLogEvent) {
|
||||
this.sendMsg(
|
||||
JSON.stringify({
|
||||
|
||||
@@ -122,7 +122,8 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
init() {
|
||||
console.log("init called from OptionsMenu");
|
||||
this.showPauseButton =
|
||||
this.game.config().gameConfig().gameType == GameType.Singleplayer;
|
||||
this.game.config().gameConfig().gameType == GameType.Singleplayer ||
|
||||
this.game.config().isReplay();
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ export interface Config {
|
||||
defaultNukeSpeed(): number;
|
||||
nukeDeathFactor(humans: number, tilesOwned: number): number;
|
||||
structureMinDist(): number;
|
||||
isReplay(): boolean;
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
|
||||
@@ -12,15 +12,16 @@ export let cachedSC: ServerConfig = null;
|
||||
export async function getConfig(
|
||||
gameConfig: GameConfig,
|
||||
userSettings: UserSettings | null = null,
|
||||
isReplay: boolean = false,
|
||||
): Promise<Config> {
|
||||
const sc = await getServerConfigFromClient();
|
||||
switch (sc.env()) {
|
||||
case GameEnv.Dev:
|
||||
return new DevConfig(sc, gameConfig, userSettings);
|
||||
return new DevConfig(sc, gameConfig, userSettings, isReplay);
|
||||
case GameEnv.Preprod:
|
||||
case GameEnv.Prod:
|
||||
consolex.log("using prod config");
|
||||
return new DefaultConfig(sc, gameConfig, userSettings);
|
||||
return new DefaultConfig(sc, gameConfig, userSettings, isReplay);
|
||||
default:
|
||||
throw Error(`unsupported server configuration: ${process.env.GAME_ENV}`);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,11 @@ export class DefaultConfig implements Config {
|
||||
private _serverConfig: ServerConfig,
|
||||
private _gameConfig: GameConfig,
|
||||
private _userSettings: UserSettings,
|
||||
private _isReplay: boolean,
|
||||
) {}
|
||||
isReplay(): boolean {
|
||||
return this._isReplay;
|
||||
}
|
||||
|
||||
samHittingChance(): number {
|
||||
return 0.8;
|
||||
|
||||
@@ -41,8 +41,13 @@ export class DevServerConfig extends DefaultServerConfig {
|
||||
}
|
||||
|
||||
export class DevConfig extends DefaultConfig {
|
||||
constructor(sc: ServerConfig, gc: GameConfig, us: UserSettings) {
|
||||
super(sc, gc, us);
|
||||
constructor(
|
||||
sc: ServerConfig,
|
||||
gc: GameConfig,
|
||||
us: UserSettings,
|
||||
isReplay: boolean,
|
||||
) {
|
||||
super(sc, gc, us, isReplay);
|
||||
}
|
||||
|
||||
// numSpawnPhaseTurns(): number {
|
||||
|
||||
@@ -38,7 +38,7 @@ export class CityExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.city = this.player.buildUnit(UnitType.City, 0, spawnTile);
|
||||
this.city = this.player.buildUnit(UnitType.City, spawnTile, {});
|
||||
}
|
||||
if (!this.city.isActive()) {
|
||||
this.active = false;
|
||||
|
||||
@@ -60,8 +60,8 @@ export class ConstructionExecution implements Execution {
|
||||
}
|
||||
this.construction = this.player.buildUnit(
|
||||
UnitType.Construction,
|
||||
0,
|
||||
spawnTile,
|
||||
{},
|
||||
);
|
||||
this.cost = this.mg.unitInfo(this.constructionType).cost(this.player);
|
||||
this.player.removeGold(this.cost);
|
||||
|
||||
@@ -65,7 +65,7 @@ export class DefensePostExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile);
|
||||
this.post = this.player.buildUnit(UnitType.DefensePost, spawnTile, {});
|
||||
}
|
||||
if (!this.post.isActive()) {
|
||||
this.active = false;
|
||||
|
||||
@@ -73,7 +73,7 @@ export class MirvExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.nuke = this.player.buildUnit(UnitType.MIRV, 0, spawn);
|
||||
this.nuke = this.player.buildUnit(UnitType.MIRV, spawn, {});
|
||||
const x = Math.floor(
|
||||
(this.mg.x(this.dst) + this.mg.x(this.mg.x(this.nuke.tile()))) / 2,
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ export class MissileSiloExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, spawn, {
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, spawn, {
|
||||
cooldownDuration: this.mg.config().SiloCooldown(),
|
||||
});
|
||||
|
||||
|
||||
@@ -95,13 +95,12 @@ export class NukeExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
const maxVertex = this.type == UnitType.MIRVWarhead ? 0 : null;
|
||||
this.pathFinder.computeControlPoints(
|
||||
spawn,
|
||||
this.dst,
|
||||
this.type != UnitType.MIRVWarhead,
|
||||
);
|
||||
this.nuke = this.player.buildUnit(this.type, 0, spawn, {
|
||||
this.nuke = this.player.buildUnit(this.type, spawn, {
|
||||
detonationDst: this.dst,
|
||||
});
|
||||
if (this.mg.hasOwner(this.dst)) {
|
||||
|
||||
@@ -45,7 +45,7 @@ export class PortExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.port = player.buildUnit(UnitType.Port, 0, spawn);
|
||||
this.port = player.buildUnit(UnitType.Port, spawn, {});
|
||||
}
|
||||
|
||||
if (!this.port.isActive()) {
|
||||
|
||||
@@ -99,7 +99,7 @@ export class SAMLauncherExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.sam = this.player.buildUnit(UnitType.SAMLauncher, 0, spawnTile, {
|
||||
this.sam = this.player.buildUnit(UnitType.SAMLauncher, spawnTile, {
|
||||
cooldownDuration: this.mg.config().SAMCooldown(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ export class SAMMissileExecution implements Execution {
|
||||
if (this.SAMMissile == null) {
|
||||
this.SAMMissile = this._owner.buildUnit(
|
||||
UnitType.SAMMissile,
|
||||
0,
|
||||
this.spawn,
|
||||
{},
|
||||
);
|
||||
}
|
||||
if (!this.SAMMissile.isActive()) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export class ShellExecution implements Execution {
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.shell == null) {
|
||||
this.shell = this._owner.buildUnit(UnitType.Shell, 0, this.spawn);
|
||||
this.shell = this._owner.buildUnit(UnitType.Shell, this.spawn, {});
|
||||
}
|
||||
if (!this.shell.isActive()) {
|
||||
this.active = false;
|
||||
|
||||
@@ -45,7 +45,7 @@ export class TradeShipExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn, {
|
||||
this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, spawn, {
|
||||
dstPort: this._dstPort,
|
||||
lastSetSafeFromPirates: ticks,
|
||||
});
|
||||
|
||||
@@ -139,11 +139,9 @@ export class TransportShipExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
this.boat = this.attacker.buildUnit(
|
||||
UnitType.TransportShip,
|
||||
this.troops,
|
||||
this.src,
|
||||
);
|
||||
this.boat = this.attacker.buildUnit(UnitType.TransportShip, this.src, {
|
||||
troops: this.troops,
|
||||
});
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
|
||||
@@ -54,11 +54,13 @@ export class WarshipExecution implements Execution {
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.warship.setMoveTarget(null);
|
||||
this.warship.move(this.warship.tile());
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.warship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
this.warship.move(this.warship.tile());
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to target`);
|
||||
@@ -98,11 +100,13 @@ export class WarshipExecution implements Execution {
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = this.randomTile();
|
||||
this.warship.move(this.warship.tile());
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this.warship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
this.warship.move(this.warship.tile());
|
||||
return;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to patrol tile`);
|
||||
@@ -119,7 +123,7 @@ export class WarshipExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.warship = this._owner.buildUnit(UnitType.Warship, 0, spawn);
|
||||
this.warship = this._owner.buildUnit(UnitType.Warship, spawn, {});
|
||||
return;
|
||||
}
|
||||
if (!this.warship.isActive()) {
|
||||
@@ -227,11 +231,13 @@ export class WarshipExecution implements Execution {
|
||||
case PathFindResultType.Completed:
|
||||
this._owner.captureUnit(this.target);
|
||||
this.target = null;
|
||||
this.warship.move(this.warship.tile());
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.warship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
this.warship.move(this.warship.tile());
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to target`);
|
||||
|
||||
+50
-14
@@ -148,6 +148,51 @@ export enum UnitType {
|
||||
Construction = "Construction",
|
||||
}
|
||||
|
||||
export interface UnitParamsMap {
|
||||
[UnitType.TransportShip]: {
|
||||
troops?: number;
|
||||
destination?: TileRef;
|
||||
};
|
||||
|
||||
[UnitType.Warship]: {};
|
||||
|
||||
[UnitType.Shell]: {};
|
||||
|
||||
[UnitType.SAMMissile]: {};
|
||||
|
||||
[UnitType.Port]: {};
|
||||
|
||||
[UnitType.AtomBomb]: {};
|
||||
|
||||
[UnitType.HydrogenBomb]: {};
|
||||
|
||||
[UnitType.TradeShip]: {
|
||||
dstPort: Unit;
|
||||
lastSetSafeFromPirates?: number;
|
||||
};
|
||||
|
||||
[UnitType.MissileSilo]: {
|
||||
cooldownDuration?: number;
|
||||
};
|
||||
|
||||
[UnitType.DefensePost]: {};
|
||||
|
||||
[UnitType.SAMLauncher]: {};
|
||||
|
||||
[UnitType.City]: {};
|
||||
|
||||
[UnitType.MIRV]: {};
|
||||
|
||||
[UnitType.MIRVWarhead]: {};
|
||||
|
||||
[UnitType.Construction]: {};
|
||||
}
|
||||
|
||||
// Type helper to get params type for a specific unit type
|
||||
export type UnitParams<T extends UnitType> = UnitParamsMap[T];
|
||||
|
||||
export type AllUnitParams = UnitParamsMap[keyof UnitParamsMap];
|
||||
|
||||
export const nukeTypes = [
|
||||
UnitType.AtomBomb,
|
||||
UnitType.HydrogenBomb,
|
||||
@@ -276,15 +321,6 @@ export class PlayerInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// Some units have info specific to them
|
||||
export interface UnitSpecificInfos {
|
||||
dstPort?: Unit; // Only for trade ships
|
||||
lastSetSafeFromPirates?: number; // Only for trade ships
|
||||
detonationDst?: TileRef; // Only for nukes
|
||||
warshipTarget?: Unit;
|
||||
cooldownDuration?: number;
|
||||
}
|
||||
|
||||
export interface Unit {
|
||||
id(): number;
|
||||
|
||||
@@ -391,12 +427,12 @@ export interface Player {
|
||||
unitsIncludingConstruction(type: UnitType): Unit[];
|
||||
buildableUnits(tile: TileRef): BuildableUnit[];
|
||||
canBuild(type: UnitType, targetTile: TileRef): TileRef | false;
|
||||
buildUnit(
|
||||
type: UnitType,
|
||||
troops: number,
|
||||
tile: TileRef,
|
||||
unitSpecificInfos?: UnitSpecificInfos,
|
||||
buildUnit<T extends UnitType>(
|
||||
type: T,
|
||||
spawnTile: TileRef,
|
||||
params: UnitParams<T>,
|
||||
): Unit;
|
||||
|
||||
captureUnit(unit: Unit): void;
|
||||
|
||||
// Relations & Diplomacy
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
TerraNullius,
|
||||
Tick,
|
||||
Unit,
|
||||
UnitSpecificInfos,
|
||||
UnitParams,
|
||||
UnitType,
|
||||
} from "./Game";
|
||||
import { GameImpl } from "./GameImpl";
|
||||
@@ -703,11 +703,10 @@ export class PlayerImpl implements Player {
|
||||
);
|
||||
}
|
||||
|
||||
buildUnit(
|
||||
type: UnitType,
|
||||
troops: number,
|
||||
buildUnit<T extends UnitType>(
|
||||
type: T,
|
||||
spawnTile: TileRef,
|
||||
unitSpecificInfos: UnitSpecificInfos = {},
|
||||
params: UnitParams<T>,
|
||||
): UnitImpl {
|
||||
if (this.mg.config().isUnitDisabled(type)) {
|
||||
throw new Error(
|
||||
@@ -720,14 +719,13 @@ export class PlayerImpl implements Player {
|
||||
type,
|
||||
this.mg,
|
||||
spawnTile,
|
||||
troops,
|
||||
this.mg.nextUnitID(),
|
||||
this,
|
||||
unitSpecificInfos,
|
||||
params,
|
||||
);
|
||||
this._units.push(b);
|
||||
this.removeGold(cost);
|
||||
this.removeTroops(troops);
|
||||
this.removeTroops("troops" in params ? params.troops : 0);
|
||||
this.mg.addUpdate(b.toUpdate());
|
||||
this.mg.addUnit(b);
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { simpleHash, toInt, withinInt } from "../Util";
|
||||
import {
|
||||
AllUnitParams,
|
||||
MessageType,
|
||||
Player,
|
||||
Tick,
|
||||
Unit,
|
||||
UnitInfo,
|
||||
UnitSpecificInfos,
|
||||
UnitType,
|
||||
} from "./Game";
|
||||
import { GameImpl } from "./GameImpl";
|
||||
@@ -24,6 +24,7 @@ export class UnitImpl implements Unit {
|
||||
private _lastSetSafeFromPirates: number; // Only for trade ships
|
||||
private _constructionType: UnitType = undefined;
|
||||
|
||||
private _troops: number;
|
||||
private _cooldownTick: Tick | null = null;
|
||||
private _dstPort: Unit | null = null; // Only for trade ships
|
||||
private _detonationDst: TileRef | null = null; // Only for nukes
|
||||
@@ -34,21 +35,22 @@ export class UnitImpl implements Unit {
|
||||
private _type: UnitType,
|
||||
private mg: GameImpl,
|
||||
private _tile: TileRef,
|
||||
private _troops: number,
|
||||
private _id: number,
|
||||
public _owner: PlayerImpl,
|
||||
unitsSpecificInfos: UnitSpecificInfos = {},
|
||||
params: AllUnitParams = {},
|
||||
) {
|
||||
this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1);
|
||||
this._lastTile = _tile;
|
||||
this._dstPort = unitsSpecificInfos.dstPort;
|
||||
this._detonationDst = unitsSpecificInfos.detonationDst;
|
||||
this._warshipTarget = unitsSpecificInfos.warshipTarget;
|
||||
this._cooldownDuration = unitsSpecificInfos.cooldownDuration;
|
||||
this._lastSetSafeFromPirates = unitsSpecificInfos.lastSetSafeFromPirates;
|
||||
this._health = toInt(this.mg.unitInfo(_type).maxHealth ?? 1);
|
||||
this._safeFromPiratesCooldown = this.mg
|
||||
.config()
|
||||
.safeFromPiratesCooldownMax();
|
||||
|
||||
this._troops = "troops" in params ? params.troops : 0;
|
||||
this._dstPort = "dstPort" in params ? params.dstPort : null;
|
||||
this._cooldownDuration =
|
||||
"cooldownDuration" in params ? params.cooldownDuration : null;
|
||||
this._lastSetSafeFromPirates =
|
||||
"lastSetSafeFromPirates" in params ? params.lastSetSafeFromPirates : 0;
|
||||
}
|
||||
|
||||
id() {
|
||||
|
||||
+20
-10
@@ -50,9 +50,9 @@ describe("SAM", () => {
|
||||
});
|
||||
|
||||
test("one sam should take down one nuke", async () => {
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1));
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
|
||||
attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(1, 1));
|
||||
attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 1), {});
|
||||
|
||||
executeTicks(game, 3);
|
||||
|
||||
@@ -60,10 +60,14 @@ describe("SAM", () => {
|
||||
});
|
||||
|
||||
test("sam should only get one nuke at a time", async () => {
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1));
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
|
||||
attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(2, 1));
|
||||
attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(1, 2));
|
||||
attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 1), {
|
||||
detonationDst: game.ref(2, 1),
|
||||
});
|
||||
attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), {
|
||||
detonationDst: game.ref(1, 2),
|
||||
});
|
||||
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(2);
|
||||
|
||||
executeTicks(game, 3);
|
||||
@@ -72,10 +76,12 @@ describe("SAM", () => {
|
||||
});
|
||||
|
||||
test("sam should cooldown as long as configured", async () => {
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1));
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
|
||||
expect(sam.isCooldown()).toBeFalsy();
|
||||
const nuke = attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(1, 2));
|
||||
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), {
|
||||
detonationDst: game.ref(1, 2),
|
||||
});
|
||||
|
||||
executeTicks(game, 3);
|
||||
|
||||
@@ -91,11 +97,15 @@ describe("SAM", () => {
|
||||
});
|
||||
|
||||
test("two sams should not target twice same nuke", async () => {
|
||||
const sam1 = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 1));
|
||||
const sam1 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {
|
||||
cooldownDuration: 10,
|
||||
});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam1));
|
||||
const sam2 = defender.buildUnit(UnitType.SAMLauncher, 0, game.ref(1, 2));
|
||||
const sam2 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 2), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam2));
|
||||
const nuke = attacker.buildUnit(UnitType.AtomBomb, 0, game.ref(2, 2));
|
||||
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 2), {
|
||||
detonationDst: game.ref(2, 2),
|
||||
});
|
||||
|
||||
executeTicks(game, 3);
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ describe("Warship", () => {
|
||||
test("Warship heals only if player has port", async () => {
|
||||
const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth;
|
||||
|
||||
const port = player1.buildUnit(UnitType.Port, 0, game.ref(coastX, 10));
|
||||
const port = player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
0,
|
||||
game.ref(coastX + 1, 10),
|
||||
{},
|
||||
);
|
||||
|
||||
game.executeNextTick();
|
||||
@@ -91,8 +91,10 @@ describe("Warship", () => {
|
||||
// we can obviously directly add it to the player)
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
0,
|
||||
game.ref(coastX + 1, 7),
|
||||
{
|
||||
dstPort: null,
|
||||
},
|
||||
);
|
||||
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
@@ -113,8 +115,10 @@ describe("Warship", () => {
|
||||
// we can obviously directly add it to the player)
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
0,
|
||||
game.ref(coastX + 1, 11),
|
||||
{
|
||||
dstPort: null,
|
||||
},
|
||||
);
|
||||
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
|
||||
+6
-1
@@ -42,7 +42,12 @@ export async function setup(
|
||||
instantBuild: false,
|
||||
..._gameConfig,
|
||||
};
|
||||
const config = new TestConfig(serverConfig, gameConfig, new UserSettings());
|
||||
const config = new TestConfig(
|
||||
serverConfig,
|
||||
gameConfig,
|
||||
new UserSettings(),
|
||||
false,
|
||||
);
|
||||
|
||||
// Create and return the game
|
||||
return createGame(humans, [], gameMap, miniGameMap, config);
|
||||
|
||||
Reference in New Issue
Block a user