Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/core/configuration/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Game,
GameMapType,
GameMode,
GameType,
Gold,
Player,
PlayerInfo,
Expand All @@ -28,6 +29,7 @@ export enum GameEnv {

export interface ServerConfig {
turnIntervalMs(): number;
startDelay(gameType: GameType): number;
gameCreationRate(): number;
lobbyMaxPlayers(
map: GameMapType,
Expand Down Expand Up @@ -72,6 +74,7 @@ export interface NukeMagnitude {
}

export interface Config {
turnIntervalMs(): number;
samHittingChance(): number;
samWarheadHittingChance(): number;
spawnImmunityDuration(): Tick;
Expand All @@ -90,6 +93,12 @@ export interface Config {
instantBuild(): boolean;
isRandomSpawn(): boolean;
numSpawnPhaseTurns(): number;
numGracePeriodTurns(): number;
isSpawnPhase(
ticks: number,
gameType: GameType,
firstHumanSpawnTick?: number,
): boolean;
userSettings(): UserSettings;
playerTeams(): TeamCountConfig;

Expand Down
30 changes: 30 additions & 0 deletions src/core/configuration/DefaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ export abstract class DefaultServerConfig implements ServerConfig {
turnIntervalMs(): number {
return 100;
}
// Start game immediately for single player games, with delay for multiplayer games
startDelay(gameType: GameType): number {
return gameType === GameType.Singleplayer ? 0 : 2000;
}
gameCreationRate(): number {
return 60 * 1000;
}
Expand Down Expand Up @@ -273,6 +277,10 @@ export class DefaultConfig implements Config {
return this._serverConfig;
}

turnIntervalMs(): number {
return this._serverConfig.turnIntervalMs();
}

userSettings(): UserSettings {
if (this._userSettings === null) {
throw new Error("userSettings is null");
Expand Down Expand Up @@ -625,9 +633,31 @@ export class DefaultConfig implements Config {
boatMaxNumber(): number {
return 3;
}

numSpawnPhaseTurns(): number {
return this._gameConfig.gameType === GameType.Singleplayer ? 100 : 300;
}
// Amount of ticks for player to change spawn placement in singleplayer
numGracePeriodTurns(): number {
return 15;
}
Comment on lines +640 to +643
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just start immediately on click? If a play picks a bad spawn, then can just start a new game.

isSpawnPhase(
ticks: number,
gameType: GameType,
firstHumanSpawnTick?: number,
): boolean {
if (ticks > this.numSpawnPhaseTurns()) {
return false;
}
if (gameType !== GameType.Singleplayer) {
return true;
}
if (!firstHumanSpawnTick) {
return true;
}
return ticks <= firstHumanSpawnTick + this.numGracePeriodTurns();
}

numBots(): number {
return this.bots();
}
Expand Down
3 changes: 2 additions & 1 deletion src/core/execution/FakeHumanExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ export class FakeHumanExecution implements Execution {
this.trackTradeShipsAndRetaliate();
}

if (ticks % this.attackRate !== this.attackTick) {
// Tries to spawn nations on the first tick of the match
if (ticks > 1 && ticks % this.attackRate !== this.attackTick) {
return;
}

Expand Down
23 changes: 13 additions & 10 deletions src/core/execution/SpawnExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,21 @@ export class SpawnExecution implements Execution {
player = this.mg.addPlayer(this.playerInfo);
}

player.tiles().forEach((t) => player.relinquish(t));
getSpawnTiles(this.mg, this.tile).forEach((t) => {
player.conquer(t);
});

if (!player.hasSpawned()) {
this.mg.addExecution(new PlayerExecution(player));
if (player.type() === PlayerType.Bot) {
this.mg.addExecution(new BotExecution(player));
if (player) {
player.tiles().forEach((t) => player.relinquish(t));
getSpawnTiles(this.mg, this.tile).forEach((t) => {
player.conquer(t);
});

if (player && !player.hasSpawned()) {
this.mg.addExecution(new PlayerExecution(player));
if (player.type() === PlayerType.Bot) {
this.mg.addExecution(new BotExecution(player));
}

player.setHasSpawned(true);
}
}
player.setHasSpawned(true);
}

isActive(): boolean {
Expand Down
14 changes: 13 additions & 1 deletion src/core/game/GameImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export class GameImpl implements Game {
private _height: number;
_terraNullius: TerraNulliusImpl;

private firstHumanSpawnTick: number;

allianceRequests: AllianceRequestImpl[] = [];
alliances_: AllianceImpl[] = [];

Expand Down Expand Up @@ -338,7 +340,17 @@ export class GameImpl implements Game {
}

inSpawnPhase(): boolean {
return this._ticks <= this.config().numSpawnPhaseTurns();
if (!this.firstHumanSpawnTick) {
const humanSpawned = Array.from(this._players.values()).some(
(p) => p.type() === PlayerType.Human && p.hasSpawned(),
);
if (humanSpawned) this.firstHumanSpawnTick = this._ticks;
}
return this._config.isSpawnPhase(
this.ticks(),
this.config().gameConfig().gameType,
this.firstHumanSpawnTick,
);
Comment on lines +343 to +353
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about instead of this we just create a method called:

forceEndSpawnPhase()

and GameImpl has a field like:

private spawnPhaseEnd = flase

and then:

inSpawnPhase() {
if(this.isSingpleplayer && this.spawnPhaseForceEnd) return false
}

then the spawn execution can check if it's a singplayer game and if a human is spawned in it calls this.game.endSpawnPhase()

}

ticks(): number {
Expand Down
14 changes: 13 additions & 1 deletion src/core/game/GameView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ export class GameView implements GameMap {

private _map: GameMap;

private firstHumanSpawnTick: number;

constructor(
public worker: WorkerClient,
private _config: Config,
Expand Down Expand Up @@ -655,7 +657,17 @@ export class GameView implements GameMap {
return this.lastUpdate.tick;
}
inSpawnPhase(): boolean {
return this.ticks() <= this._config.numSpawnPhaseTurns();
if (!this.firstHumanSpawnTick) {
const humanSpawned = this.playerViews().some(
(p) => p.type() === PlayerType.Human && p.hasSpawned(),
);
if (humanSpawned) this.firstHumanSpawnTick = this.ticks();
}
return this.config().isSpawnPhase(
this.ticks(),
this.config().gameConfig().gameType,
this.firstHumanSpawnTick,
);
Comment on lines +660 to +670
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead let's add a GameStateUpdate interface that has the spawnphase info. we can add more data to in the future

}
config(): Config {
return this._config;
Expand Down
5 changes: 3 additions & 2 deletions src/server/GameManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,15 @@ export class GameManager {
if (!game.hasStarted()) {
// Prestart tells clients to start loading the game.
game.prestart();
// Start game on delay to allow time for clients to connect.
// Start game immediately for single player games, with delay for multiplayer games
const startDelay = this.config.startDelay(game.gameConfig.gameType);
setTimeout(() => {
try {
game.start();
} catch (error) {
this.log.error(`error starting game ${id}: ${error}`);
}
}, 2000);
}, startDelay);
}
}

Expand Down
5 changes: 4 additions & 1 deletion tests/util/TestServerConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JWK } from "jose";
import { GameEnv, ServerConfig } from "../../src/core/configuration/Config";
import { GameMapType } from "../../src/core/game/Game";
import { GameMapType, GameType } from "../../src/core/game/Game";
import { GameID } from "../../src/core/Schemas";

export class TestServerConfig implements ServerConfig {
Expand Down Expand Up @@ -64,6 +64,9 @@ export class TestServerConfig implements ServerConfig {
numWorkers(): number {
throw new Error("Method not implemented.");
}
startDelay(gameType: GameType): number {
throw new Error("Method not implemented.");
}
workerIndex(gameID: GameID): number {
throw new Error("Method not implemented.");
}
Expand Down
Loading