diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index c086354e6..6bca2ca8c 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -655,6 +655,7 @@ export class HostLobbyModal extends BaseModal { detail: { gameID: this.lobbyId, clientID: this.lobbyCreatorClientID, + source: "host", } as JoinLobbyEvent, bubbles: true, composed: true, diff --git a/src/client/JoinLobbyModal.ts b/src/client/JoinLobbyModal.ts index 7cd330d64..0f1c5e2da 100644 --- a/src/client/JoinLobbyModal.ts +++ b/src/client/JoinLobbyModal.ts @@ -336,6 +336,7 @@ export class JoinLobbyModal extends BaseModal { detail: { gameID: lobbyId, clientID: this.currentClientID, + source: "public", } as JoinLobbyEvent, bubbles: true, composed: true, @@ -776,6 +777,7 @@ export class JoinLobbyModal extends BaseModal { detail: { gameID: lobbyId, clientID: this.currentClientID, + source: "private", } as JoinLobbyEvent, bubbles: true, composed: true, @@ -834,6 +836,7 @@ export class JoinLobbyModal extends BaseModal { gameID: lobbyId, gameRecord: parsed.data, clientID: this.currentClientID, + source: "private", } as JoinLobbyEvent, bubbles: true, composed: true, diff --git a/src/client/Matchmaking.ts b/src/client/Matchmaking.ts index 4e53ea6ab..3fa4af738 100644 --- a/src/client/Matchmaking.ts +++ b/src/client/Matchmaking.ts @@ -231,6 +231,7 @@ export class MatchmakingModal extends BaseModal { detail: { gameID: this.gameID, clientID: getClientIDForGame(this.gameID), + source: "matchmaking", } as JoinLobbyEvent, bubbles: true, composed: true, diff --git a/src/client/SinglePlayerModal.ts b/src/client/SinglePlayerModal.ts index 7a10f21aa..1623d996e 100644 --- a/src/client/SinglePlayerModal.ts +++ b/src/client/SinglePlayerModal.ts @@ -967,6 +967,7 @@ export class SinglePlayerModal extends BaseModal { }, lobbyCreatedAt: Date.now(), // ms; server should be authoritative in MP }, + source: "singleplayer", } satisfies JoinLobbyEvent, bubbles: true, composed: true, diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts index 4f9f178f9..85fabb34a 100644 --- a/src/core/execution/BotExecution.ts +++ b/src/core/execution/BotExecution.ts @@ -1,7 +1,8 @@ -import { Execution, Game, Player } from "../game/Game"; +import { Execution, Game, isStructureType, Player } from "../game/Game"; import { PseudoRandom } from "../PseudoRandom"; import { simpleHash } from "../Util"; import { AllianceExtensionExecution } from "./alliance/AllianceExtensionExecution"; +import { DeleteUnitExecution } from "./DeleteUnitExecution"; import { AiAttackBehavior } from "./utils/AiAttackBehavior"; export class BotExecution implements Execution { @@ -58,6 +59,7 @@ export class BotExecution implements Execution { } this.acceptAllAllianceRequests(); + this.deleteAllStructures(); this.maybeAttack(); } @@ -80,6 +82,14 @@ export class BotExecution implements Execution { } } + private deleteAllStructures() { + for (const unit of this.bot.units()) { + if (isStructureType(unit.type()) && this.bot.canDeleteUnit()) { + this.mg.addExecution(new DeleteUnitExecution(this.bot, unit.id())); + } + } + } + private maybeAttack() { if (this.attackBehavior === null) { throw new Error("not initialized"); diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index fae23df8e..64cdf70c8 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -20,6 +20,7 @@ import { ColoredTeams, Embargo, EmojiMessage, + GameMode, Gold, MessageType, MutableAlliance, @@ -29,6 +30,7 @@ import { PlayerProfile, PlayerType, Relation, + StructureTypes, Team, TerraNullius, Tick, @@ -994,10 +996,10 @@ export class PlayerImpl implements Player { if (!this.mg.hasOwner(targetTile)) { return false; } - return this.nukeSpawn(targetTile); + return this.nukeSpawn(targetTile, unitType); case UnitType.AtomBomb: case UnitType.HydrogenBomb: - return this.nukeSpawn(targetTile); + return this.nukeSpawn(targetTile, unitType); case UnitType.MIRVWarhead: return targetTile; case UnitType.Port: @@ -1024,7 +1026,7 @@ export class PlayerImpl implements Player { } } - nukeSpawn(tile: TileRef): TileRef | false { + nukeSpawn(tile: TileRef, nukeType: UnitType): TileRef | false { if (this.mg.isSpawnImmunityActive()) { return false; } @@ -1034,6 +1036,24 @@ export class PlayerImpl implements Player { return false; } } + + // Prevent launching nukes that would hit teammate structures (only in team games) + if ( + this.mg.config().gameConfig().gameMode === GameMode.Team && + nukeType !== UnitType.MIRV + ) { + const magnitude = this.mg.config().nukeMagnitudes(nukeType); + const wouldHitTeammate = this.mg.anyUnitNearby( + tile, + magnitude.outer, + StructureTypes, + (unit) => unit.owner().isPlayer() && this.isOnSameTeam(unit.owner()), + ); + if (wouldHitTeammate) { + return false; + } + } + // only get missilesilos that are not on cooldown and not under construction const spawns = this.units(UnitType.MissileSilo) .filter((silo) => {