From 904425cab0df60a3d0dc68aa49d439aefa93b5e1 Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:38:49 +0100 Subject: [PATCH 1/3] =?UTF-8?q?Prevent=20players=20from=20nuking=20their?= =?UTF-8?q?=20teammates=20structures=20=F0=9F=92=A5=20(#3105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: It is possible to hit your teammates while throwing a nuke onto water or enemies. This PR blocks the nuking entirely if you would hit a teammates structure. Because they are valuable. Feature requested by Wonder :) https://github.com/user-attachments/assets/448a3444-cc3d-4e76-acaf-595decab1634 ## 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: FloPinguin --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> --- src/core/game/PlayerImpl.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) 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) => { From a8836f76d33937dbf52f96af177d972f696eb0ee Mon Sep 17 00:00:00 2001 From: FloPinguin <25036848+FloPinguin@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:39:35 +0100 Subject: [PATCH 2/3] Reduce bot farming problem (#2895) ## Description: Explanation of the bot farming strategy in the discord: https://discord.com/channels/1359946986937258015/1359949371956789289/1460928540575928478 "the result is that a player can build unlimited factories for 125 000 gold discount, trade with themselves with each train being worth 50 000 gold. First the 25 000 for neutral trade and then another 25 000 when the bot is harvested." "If you have a minute and ally people around you it should be trivial to get 10 of both cities and factories for 1.25mil" It's debatable if we want to let people do that (close this PR) or see it as an abusive mechanic. Here is the fix, bots try to delete all structures now. You can simply retake them to stop the deletion: https://github.com/user-attachments/assets/ac1ca846-50bd-42fa-8e25-5ac25a6d627e ## 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: FloPinguin Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> --- src/core/execution/BotExecution.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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"); From 41a9bb80c0c5f7d0561e84ef9d98c0724472709b Mon Sep 17 00:00:00 2001 From: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Date: Wed, 4 Feb 2026 18:01:05 +0000 Subject: [PATCH 3/3] Added source for join context (#3116) ## Description: Added source for join context ## 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: w.o.n --- src/client/HostLobbyModal.ts | 1 + src/client/JoinLobbyModal.ts | 3 +++ src/client/Matchmaking.ts | 1 + src/client/SinglePlayerModal.ts | 1 + 4 files changed, 6 insertions(+) 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,