diff --git a/resources/lang/debug.json b/resources/lang/debug.json index 5b7c27515b..924720ce31 100644 --- a/resources/lang/debug.json +++ b/resources/lang/debug.json @@ -94,6 +94,7 @@ "disable_nations": "single_modal.disable_nations", "instant_build": "single_modal.instant_build", "infinite_gold": "single_modal.infinite_gold", + "random_spawn": "single_modal.random_spawn", "infinite_troops": "single_modal.infinite_troops", "disable_nukes": "single_modal.disable_nukes", "start": "single_modal.start" @@ -146,6 +147,7 @@ "bots_disabled": "host_modal.bots_disabled", "disable_nations": "host_modal.disable_nations", "instant_build": "host_modal.instant_build", + "random_spawn": "host_modal.random_spawn", "infinite_gold": "host_modal.infinite_gold", "infinite_troops": "host_modal.infinite_troops", "disable_nukes": "host_modal.disable_nukes", @@ -166,6 +168,7 @@ "Impossible": "difficulty.Impossible" }, "heads_up_message": { - "choose_spawn": "heads_up_message.choose_spawn" + "choose_spawn": "heads_up_message.choose_spawn", + "random_spawn": "heads_up_message.random_spawn" } } diff --git a/resources/lang/en.json b/resources/lang/en.json index 12cc0f05f8..052086266a 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -135,6 +135,7 @@ }, "single_modal": { "title": "Single Player", + "random_spawn": "Random spawn", "allow_alliances": "Allow alliances", "options_title": "Options", "bots": "Bots: ", @@ -262,6 +263,7 @@ "player": "Player", "players": "Players", "waiting": "Waiting for players...", + "random_spawn": "Random spawn", "start": "Start Game", "host_badge": "Host" }, @@ -661,10 +663,15 @@ "copy_clipboard": "Copy to clipboard", "copied": "Copied!", "failed_copy": "Failed to copy", + "spawn_failed": { + "title": "Spawn failed", + "description": "Automatic spawn selection failed. You can't play this game." + }, "desync_notice": "You are desynced from other players. What you see might differ from other players." }, "heads_up_message": { - "choose_spawn": "Choose a starting location" + "choose_spawn": "Choose a starting location", + "random_spawn": "Random spawn is enabled. Selecting starting location for you..." }, "territory_patterns": { "title": "Skins", diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 320b8e3d89..d4b8f8a5a1 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -48,6 +48,7 @@ import { } from "./Transport"; import { createCanvas } from "./Utils"; import { createRenderer, GameRenderer } from "./graphics/GameRenderer"; +import { GoToPlayerEvent } from "./graphics/layers/Leaderboard"; import SoundManager from "./sound/SoundManager"; export interface LobbyConfig { @@ -202,6 +203,8 @@ export class ClientGameRunner { private lastMessageTime: number = 0; private connectionCheckInterval: NodeJS.Timeout | null = null; + private goToPlayerTimeout: NodeJS.Timeout | null = null; + private lastTickReceiveTime: number = 0; private currentTickDelay: number | undefined = undefined; @@ -325,6 +328,39 @@ export class ClientGameRunner { if (message.type === "start") { this.hasJoined = true; console.log("starting game!"); + + if (this.gameView.config().isRandomSpawn()) { + const goToPlayer = () => { + const myPlayer = this.gameView.myPlayer(); + + if (this.gameView.inSpawnPhase() && !myPlayer?.hasSpawned()) { + this.goToPlayerTimeout = setTimeout(goToPlayer, 1000); + return; + } + + if (!myPlayer) { + return; + } + + if (!this.gameView.inSpawnPhase() && !myPlayer.hasSpawned()) { + showErrorModal( + "spawn_failed", + translateText("error_modal.spawn_failed.description"), + this.lobby.gameID, + this.lobby.clientID, + true, + false, + translateText("error_modal.spawn_failed.title"), + ); + return; + } + + this.eventBus.emit(new GoToPlayerEvent(myPlayer)); + }; + + goToPlayer(); + } + for (const turn of message.turns) { if (turn.turnNumber < this.turnsSeen) { continue; @@ -402,6 +438,10 @@ export class ClientGameRunner { clearInterval(this.connectionCheckInterval); this.connectionCheckInterval = null; } + if (this.goToPlayerTimeout) { + clearTimeout(this.goToPlayerTimeout); + this.goToPlayerTimeout = null; + } } private inputEvent(event: MouseUpEvent) { @@ -420,7 +460,8 @@ export class ClientGameRunner { if ( this.gameView.isLand(tile) && !this.gameView.hasOwner(tile) && - this.gameView.inSpawnPhase() + this.gameView.inSpawnPhase() && + !this.gameView.config().isRandomSpawn() ) { this.eventBus.emit(new SendSpawnIntentEvent(tile)); return; diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index 4da0867918..ccb01843c8 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -48,6 +48,7 @@ export class HostLobbyModal extends LitElement { @state() private maxTimer: boolean = false; @state() private maxTimerValue: number | undefined = undefined; @state() private instantBuild: boolean = false; + @state() private randomSpawn: boolean = false; @state() private compactMap: boolean = false; @state() private lobbyId = ""; @state() private copySuccess = false; @@ -390,6 +391,22 @@ export class HostLobbyModal extends LitElement { + + + +