mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 00:28:14 +00:00
feat: remove spawn timer on singleplayer (#3199)
Resolves #1041 ## Description: Remove the singleplayer spawn countdown so the game starts when the player spawns, spawn nations immediately after player spawn, and align game timer/max-timer timing with the new start point. Added a singleplayer regression test for spawn-immunity timing (GameImpl.test.ts) and updated spawn-phase loop tests to use gameType: GameType.Public where singleplayer behavior is not under test (e.g. MIRV/AI/Spawn/WinCheck-related suites), eliminating inSpawnPhase() timeout hangs after the new singleplayer start logic. https://github.com/user-attachments/assets/c07a585f-1153-490e-88ca-a91fc7ae5756 ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: aotumuri
This commit is contained in:
@@ -869,10 +869,12 @@ export interface Game extends GameMap {
|
||||
// Immunity timer
|
||||
isSpawnImmunityActive(): boolean;
|
||||
isNationSpawnImmunityActive(): boolean;
|
||||
elapsedGameSeconds(): number;
|
||||
|
||||
// Game State
|
||||
ticks(): Tick;
|
||||
inSpawnPhase(): boolean;
|
||||
endSpawnPhase(): void;
|
||||
executeNextTick(): GameUpdates;
|
||||
drainPackedTileUpdates(): Uint32Array;
|
||||
recordMotionPlan(record: MotionPlanRecord): void;
|
||||
|
||||
@@ -76,6 +76,7 @@ export type CellString = string;
|
||||
|
||||
export class GameImpl implements Game {
|
||||
private _ticks = 0;
|
||||
private startTick: number | null = null;
|
||||
|
||||
private unInitExecs: Execution[] = [];
|
||||
|
||||
@@ -409,7 +410,18 @@ export class GameImpl implements Game {
|
||||
}
|
||||
|
||||
inSpawnPhase(): boolean {
|
||||
return this._ticks <= this.config().numSpawnPhaseTurns();
|
||||
return this.startTick === null;
|
||||
}
|
||||
|
||||
endSpawnPhase(): void {
|
||||
if (this.startTick !== null) {
|
||||
return;
|
||||
}
|
||||
this.startTick = this._ticks;
|
||||
this.addUpdate({
|
||||
type: GameUpdateType.SpawnPhaseEnd,
|
||||
startTick: this.startTick,
|
||||
});
|
||||
}
|
||||
|
||||
ticks(): number {
|
||||
@@ -819,20 +831,30 @@ export class GameImpl implements Game {
|
||||
|
||||
public isSpawnImmunityActive(): boolean {
|
||||
return (
|
||||
this.config().numSpawnPhaseTurns() +
|
||||
this.config().spawnImmunityDuration() >
|
||||
this.ticks()
|
||||
this.inSpawnPhase() ||
|
||||
this.ticksSinceStart() < this.config().spawnImmunityDuration()
|
||||
);
|
||||
}
|
||||
|
||||
public elapsedGameSeconds(): number {
|
||||
return this.ticksSinceStart() / 10;
|
||||
}
|
||||
|
||||
public isNationSpawnImmunityActive(): boolean {
|
||||
return (
|
||||
this.config().numSpawnPhaseTurns() +
|
||||
this.config().nationSpawnImmunityDuration() >
|
||||
this.ticks()
|
||||
this.inSpawnPhase() ||
|
||||
this.ticksSinceStart() < this.config().nationSpawnImmunityDuration()
|
||||
);
|
||||
}
|
||||
|
||||
private ticksSinceStart(): number {
|
||||
if (this.inSpawnPhase()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0, this.ticks() - this.startTick!);
|
||||
}
|
||||
|
||||
sendEmojiUpdate(msg: EmojiMessage): void {
|
||||
this.addUpdate({
|
||||
type: GameUpdateType.Emoji,
|
||||
|
||||
@@ -66,6 +66,7 @@ export enum GameUpdateType {
|
||||
RailroadSnapEvent,
|
||||
ConquestEvent,
|
||||
EmbargoEvent,
|
||||
SpawnPhaseEnd,
|
||||
GamePaused,
|
||||
}
|
||||
|
||||
@@ -90,6 +91,7 @@ export type GameUpdate =
|
||||
| RailroadSnapUpdate
|
||||
| ConquestUpdate
|
||||
| EmbargoUpdate
|
||||
| SpawnPhaseEndUpdate
|
||||
| GamePausedUpdate;
|
||||
|
||||
export interface BonusEventUpdate {
|
||||
@@ -290,6 +292,11 @@ export interface EmbargoUpdate {
|
||||
embargoedID: number;
|
||||
}
|
||||
|
||||
export interface SpawnPhaseEndUpdate {
|
||||
type: GameUpdateType.SpawnPhaseEnd;
|
||||
startTick: Tick;
|
||||
}
|
||||
|
||||
export interface GamePausedUpdate {
|
||||
type: GameUpdateType.GamePaused;
|
||||
paused: boolean;
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
GameUpdateType,
|
||||
GameUpdateViewData,
|
||||
PlayerUpdate,
|
||||
SpawnPhaseEndUpdate,
|
||||
UnitUpdate,
|
||||
} from "./GameUpdates";
|
||||
import { MotionPlanRecord, unpackMotionPlans } from "./MotionPlans";
|
||||
@@ -664,6 +665,7 @@ type TrainPlanState = {
|
||||
|
||||
export class GameView implements GameMap {
|
||||
private lastUpdate: GameUpdateViewData | null;
|
||||
private startTick: Tick | null = null;
|
||||
private smallIDToID = new Map<number, PlayerID>();
|
||||
private _players = new Map<PlayerID, PlayerView>();
|
||||
private _units = new Map<number, UnitView>();
|
||||
@@ -799,6 +801,14 @@ export class GameView implements GameMap {
|
||||
if (gu.updates === null) {
|
||||
throw new Error("lastUpdate.updates not initialized");
|
||||
}
|
||||
|
||||
const spawnPhaseEndUpdate = gu.updates[GameUpdateType.SpawnPhaseEnd][0] as
|
||||
| SpawnPhaseEndUpdate
|
||||
| undefined;
|
||||
if (spawnPhaseEndUpdate) {
|
||||
this.startTick = spawnPhaseEndUpdate.startTick;
|
||||
}
|
||||
|
||||
const myDisplayName = formatPlayerDisplayName(
|
||||
this._myUsername,
|
||||
this._myClanTag,
|
||||
@@ -1215,21 +1225,33 @@ export class GameView implements GameMap {
|
||||
return this.lastUpdate.tick;
|
||||
}
|
||||
inSpawnPhase(): boolean {
|
||||
return this.ticks() <= this._config.numSpawnPhaseTurns();
|
||||
return this.startTick === null;
|
||||
}
|
||||
|
||||
isSpawnImmunityActive(): boolean {
|
||||
return (
|
||||
this._config.numSpawnPhaseTurns() + this._config.spawnImmunityDuration() >
|
||||
this.ticks()
|
||||
this.inSpawnPhase() ||
|
||||
this.ticksSinceStart() < this._config.spawnImmunityDuration()
|
||||
);
|
||||
}
|
||||
isNationSpawnImmunityActive(): boolean {
|
||||
return (
|
||||
this._config.numSpawnPhaseTurns() +
|
||||
this._config.nationSpawnImmunityDuration() >
|
||||
this.ticks()
|
||||
this.inSpawnPhase() ||
|
||||
this.ticksSinceStart() < this._config.nationSpawnImmunityDuration()
|
||||
);
|
||||
}
|
||||
|
||||
elapsedGameSeconds(): number {
|
||||
return this.ticksSinceStart() / 10;
|
||||
}
|
||||
|
||||
ticksSinceStart(): Tick {
|
||||
if (this.inSpawnPhase()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0, this.ticks() - this.startTick!);
|
||||
}
|
||||
config(): Config {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user