From 13b350513fa1c347931550888b467fe396d7bfc8 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Tue, 19 Aug 2025 12:52:54 -0700 Subject: [PATCH 1/9] Improve alliance UX, prevent hung alliance requests (#1868) This PR does two things: 1. Allows you to send an alliance request to approve an existing request, ex: * player A sends req to player B * now player B can send an ally request to player A, which accepts the request from player A. This way even if you lose or don't see the alliance notification, you can still accept the alliance. 2. Have AllianceRequestExecution reject the request if not accepted or rejected. There is a bug where sometimes the EventDisplay does not trigger the delete() function, resulting in hung alliance requests. I couldn't figure out why the delete() function is sometimes not called, but I think it's better design to have core/ itself handle abandoned alliance requests, this was UI bugs can't break the game state. - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [ ] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced regression is found: evan --- eslint.config.js | 2 +- src/client/graphics/layers/EventsDisplay.ts | 14 ++-- src/core/configuration/Config.ts | 1 + src/core/configuration/DefaultConfig.ts | 3 + .../alliance/AllianceRequestExecution.ts | 53 +++++++++---- src/core/game/AllianceRequestImpl.ts | 8 ++ src/core/game/Game.ts | 1 + src/core/game/PlayerImpl.ts | 6 +- tests/AllianceRequestExecution.test.ts | 77 +++++++++++++++++++ 9 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 tests/AllianceRequestExecution.test.ts diff --git a/eslint.config.js b/eslint.config.js index 166d7e035..d2d5df4ec 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -107,7 +107,7 @@ export default [ "function-call-argument-newline": ["error", "consistent"], "max-depth": ["error", { max: 5 }], "max-len": ["error", { code: 120 }], - "max-lines": ["error", { max: 676, skipBlankLines: true, skipComments: true }], + "max-lines": ["error", { max: 677, skipBlankLines: true, skipComments: true }], "max-lines-per-function": ["error", { max: 561 }], "no-loss-of-precision": "error", "no-multi-spaces": "error", diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 193e5f314..66771cfbc 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -65,6 +65,7 @@ type GameEvent = { duration?: Tick; focusID?: number; unitView?: UnitView; + shouldDelete?: (game: GameView) => boolean; }; @customElement("events-display") @@ -196,7 +197,8 @@ export class EventsDisplay extends LitElement implements Layer { let remainingEvents = this.events.filter((event) => { if (this.game === undefined) return; const shouldKeep = - this.game.ticks() - event.createdAt < (event.duration ?? 600); + this.game.ticks() - event.createdAt < (event.duration ?? 600) && + !event.shouldDelete?.(this.game); if (!shouldKeep && event.onDelete) { event.onDelete(); } @@ -464,12 +466,12 @@ export class EventsDisplay extends LitElement implements Layer { highlight: true, type: MessageType.ALLIANCE_REQUEST, createdAt: this.game.ticks(), - onDelete: () => - this.eventBus?.emit( - new SendAllianceReplyIntentEvent(requestor, recipient, false), - ), priority: 0, - duration: 150, + duration: this.game.config().allianceRequestDuration() - 20, // 2 second buffer + shouldDelete: (game) => { + // Recipient sent a separate request, so they became allied without the recipient responding. + return requestor.isAlliedWith(recipient); + }, focusID: update.requestorID, }); } diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 16bf6bf3f..fe1e2525a 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -121,6 +121,7 @@ export type Config = { shellLifetime(): number; boatMaxNumber(): number; allianceDuration(): Tick; + allianceRequestDuration(): Tick; allianceRequestCooldown(): Tick; temporaryEmbargoDuration(): Tick; targetDuration(): Tick; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index ba7ba83b6..eddf481fe 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -533,6 +533,9 @@ export class DefaultConfig implements Config { targetCooldown(): Tick { return 15 * 10; } + allianceRequestDuration(): Tick { + return 20 * 10; + } allianceRequestCooldown(): Tick { return 30 * 10; } diff --git a/src/core/execution/alliance/AllianceRequestExecution.ts b/src/core/execution/alliance/AllianceRequestExecution.ts index ee2e929a3..b73cd50c6 100644 --- a/src/core/execution/alliance/AllianceRequestExecution.ts +++ b/src/core/execution/alliance/AllianceRequestExecution.ts @@ -1,8 +1,15 @@ -import { Execution, Game, Player, PlayerID } from "../../game/Game"; +import { + AllianceRequest, + Execution, + Game, + Player, + PlayerID, +} from "../../game/Game"; export class AllianceRequestExecution implements Execution { + private req: AllianceRequest | null = null; private active = true; - private recipient: Player | null = null; + private mg: Game; constructor( private readonly requestor: Player, @@ -10,29 +17,49 @@ export class AllianceRequestExecution implements Execution { ) {} init(mg: Game, ticks: number): void { + this.mg = mg; if (!mg.hasPlayer(this.recipientID)) { console.warn( `AllianceRequestExecution recipient ${this.recipientID} not found`, ); - this.active = false; return; } - this.recipient = mg.player(this.recipientID); + const recipient = mg.player(this.recipientID); + + if (!this.requestor.canSendAllianceRequest(recipient)) { + console.warn("cannot send alliance request"); + this.active = false; + } else { + const incoming = recipient + .outgoingAllianceRequests() + .find((r) => r.recipient() === this.requestor); + if (incoming) { + // If the recipient already has pending alliance request, + // then accept it instead of creating a new one. + this.active = false; + incoming.accept(); + } else { + this.req = this.requestor.createAllianceRequest(recipient); + } + } } tick(ticks: number): void { - if (this.recipient === null) { - throw new Error("Not initialized"); + if ( + this.req?.status() === "accepted" || + this.req?.status() === "rejected" + ) { + this.active = false; + return; } - if (this.requestor.isFriendly(this.recipient)) { - console.warn("already allied"); - } else if (!this.requestor.canSendAllianceRequest(this.recipient)) { - console.warn("recent or pending alliance request"); - } else { - this.requestor.createAllianceRequest(this.recipient); + if ( + this.mg.ticks() - (this.req?.createdAt() ?? 0) > + this.mg.config().allianceRequestDuration() + ) { + this.req?.reject(); + this.active = false; } - this.active = false; } isActive(): boolean { diff --git a/src/core/game/AllianceRequestImpl.ts b/src/core/game/AllianceRequestImpl.ts index 6fbb7bd0c..591c383c9 100644 --- a/src/core/game/AllianceRequestImpl.ts +++ b/src/core/game/AllianceRequestImpl.ts @@ -3,6 +3,8 @@ import { AllianceRequestUpdate, GameUpdateType } from "./GameUpdates"; import { GameImpl } from "./GameImpl"; export class AllianceRequestImpl implements AllianceRequest { + private status_: "pending" | "accepted" | "rejected" = "pending"; + constructor( private readonly requestor_: Player, private readonly recipient_: Player, @@ -10,6 +12,10 @@ export class AllianceRequestImpl implements AllianceRequest { private readonly game: GameImpl, ) {} + status(): "pending" | "accepted" | "rejected" { + return this.status_; + } + requestor(): Player { return this.requestor_; } @@ -23,9 +29,11 @@ export class AllianceRequestImpl implements AllianceRequest { } accept(): void { + this.status_ = "accepted"; this.game.acceptAllianceRequest(this); } reject(): void { + this.status_ = "rejected"; this.game.rejectAllianceRequest(this); } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 713b46b2c..2486e57ac 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -354,6 +354,7 @@ export type AllianceRequest = { requestor(): Player; recipient(): Player; createdAt(): Tick; + status(): "pending" | "accepted" | "rejected"; }; export type Alliance = { diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 0483b2064..e1716bce1 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -397,9 +397,9 @@ export class PlayerImpl implements Player { return false; } - const hasPending = - this.incomingAllianceRequests().some((ar) => ar.requestor() === other) || - this.outgoingAllianceRequests().some((ar) => ar.recipient() === other); + const hasPending = this.outgoingAllianceRequests().some( + (ar) => ar.recipient() === other, + ); if (hasPending) { return false; diff --git a/tests/AllianceRequestExecution.test.ts b/tests/AllianceRequestExecution.test.ts new file mode 100644 index 000000000..c88edd5a0 --- /dev/null +++ b/tests/AllianceRequestExecution.test.ts @@ -0,0 +1,77 @@ +import { AllianceRequestExecution } from "../src/core/execution/alliance/AllianceRequestExecution"; +import { AllianceRequestReplyExecution } from "../src/core/execution/alliance/AllianceRequestReplyExecution"; +import { Game, Player, PlayerType } from "../src/core/game/Game"; +import { playerInfo, setup } from "./util/Setup"; + +let game: Game; +let player1: Player; +let player2: Player; + +describe("AllianceRequestExecution", () => { + beforeEach(async () => { + game = await setup( + "plains", + { + infiniteGold: true, + instantBuild: true, + infiniteTroops: true, + }, + [ + playerInfo("player1", PlayerType.Human), + playerInfo("player2", PlayerType.Human), + playerInfo("player3", PlayerType.FakeHuman), + ], + ); + + player1 = game.player("player1"); + player1.conquer(game.ref(0, 0)); + + player2 = game.player("player2"); + player2.conquer(game.ref(0, 1)); + + while (game.inSpawnPhase()) { + game.executeNextTick(); + } + }); + + test("Can create alliance by replying", () => { + game.addExecution(new AllianceRequestExecution(player1, player2.id())); + game.executeNextTick(); + + game.addExecution( + new AllianceRequestReplyExecution(player1.id(), player2, true), + ); + game.executeNextTick(); + game.executeNextTick(); + + expect(player1.isAlliedWith(player2)).toBeTruthy(); + expect(player2.isAlliedWith(player1)).toBeTruthy(); + }); + + test("Can create alliance by sending alliance request back", () => { + game.addExecution(new AllianceRequestExecution(player1, player2.id())); + game.executeNextTick(); + + game.addExecution(new AllianceRequestExecution(player2, player1.id())); + game.executeNextTick(); + + expect(player1.isAlliedWith(player2)).toBeTruthy(); + expect(player2.isAlliedWith(player1)).toBeTruthy(); + }); + + test("Alliance request expires", () => { + game.config().allianceRequestDuration = () => 5; + game.addExecution(new AllianceRequestExecution(player1, player2.id())); + game.executeNextTick(); + + expect(player1.outgoingAllianceRequests().length).toBe(1); + + for (let i = 0; i < 6; i++) { + game.executeNextTick(); + } + + expect(player1.outgoingAllianceRequests().length).toBe(0); + expect(player1.isAlliedWith(player2)).toBeFalsy(); + expect(player2.isAlliedWith(player1)).toBeFalsy(); + }); +}); From 087159ae3139fdd5166729597fcb430f10806b80 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Tue, 19 Aug 2025 13:08:19 -0700 Subject: [PATCH 2/9] Update train color to be more like owner color (#1869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: Trains were mostly white, which was jarring, and it was difficult to tell who owned what train. This makes it trains are the color of their owner. Screenshot 2025-08-19 at 12 58 52 PM ## 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: evan --- resources/sprites/trainCarriage.png | Bin 107 -> 213 bytes resources/sprites/trainCarriageLoaded.png | Bin 114 -> 213 bytes resources/sprites/trainEngine.png | Bin 98 -> 199 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/sprites/trainCarriage.png b/resources/sprites/trainCarriage.png index 0040e6f89c2b262bcdf63319ed3e6b74c3e87b73..46da0f7be5df1da2ce116be437da73653712a974 100644 GIT binary patch delta 179 zcmd14$~Zx@p7G{Moq7fa29|V3Uq=Rpjs4tz5?O(Kg=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C-z_@Q5sCVBi)8VMc~ob0mO*>?NMQuIw*4gjx6$ zbeDfS02I>pba4#fxSpJFfJs79LPBC&&Q0A<@@fwrKR$ed<+0B!6V7!)dNZa^Ki(kD Zz_8&0JDZNm*$Y5@44$rjF6*2UngGoeGr<4= delta 72 zcmV-O0Js0u0c(&cLIAGL9O(c603}I8K~xBtV_?7n7#SEC82E$jHb9?NMQuIw*4gjx7Z z9jb)1fkN7zE{-7_*OLhn0dkNjNdT_S9O(c604zyFK~xBtV_?7n7#SEC7~I_4{=)=z?b^l21mo=7 lxf8AuF6i#=j?E+l0064-2L`H`&guXF002ovPDHLkV1g0RAs_$% diff --git a/resources/sprites/trainEngine.png b/resources/sprites/trainEngine.png index 6d79749384e7441340308a6d875823f65c437723..d883c8b5c37d32dbd5c5162fa256ec2ded974cc3 100644 GIT binary patch delta 165 zcmYc~&NxA{p7G{Moq7fa29|V3Uq=Rpjs4tz5?O(Kg=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C-z_@Q5sCVBi)8VMc~ob0mO*>?NMQuIw*4gjodC z1X^>nfI_mKE{-7_*OL$c9*?PI Date: Tue, 19 Aug 2025 14:35:30 -0700 Subject: [PATCH 3/9] unset pattern on logout (#1873) ## Description: During logout, the pattern was still set, so you would fail to join any game. This PR clears & unsets the chosen pattern on logout. ## 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: evan --- src/client/TerritoryPatternsModal.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/TerritoryPatternsModal.ts b/src/client/TerritoryPatternsModal.ts index 6793e7f78..9759912b1 100644 --- a/src/client/TerritoryPatternsModal.ts +++ b/src/client/TerritoryPatternsModal.ts @@ -66,6 +66,10 @@ export class TerritoryPatternsModal extends LitElement { } async onUserMe(userMeResponse: UserMeResponse | null) { + if (userMeResponse === null) { + this.userSettings.setSelectedPatternName(undefined); + this.selectedPattern = undefined; + } this.patterns = await patterns(userMeResponse); this.me = userMeResponse; this.requestUpdate(); From 8965ad53b0a231fd73b41c0f4313f7af0b387cf7 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Thu, 21 Aug 2025 09:04:21 -0700 Subject: [PATCH 4/9] better pattern name (#1885) ## Description: If the translation key isn't found for a pattern, show just the pattern key, not the entire translation key. Since patterns will be added frequently. ## 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: evan --- src/client/TerritoryPatternsModal.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/client/TerritoryPatternsModal.ts b/src/client/TerritoryPatternsModal.ts index 9759912b1..e3dcec8ab 100644 --- a/src/client/TerritoryPatternsModal.ts +++ b/src/client/TerritoryPatternsModal.ts @@ -144,7 +144,7 @@ export class TerritoryPatternsModal extends LitElement { @mouseleave=${() => this.handleMouseLeave()} >
- ${translateText(`territory_patterns.pattern.${pattern.name}`)} + ${translatePatternName("territory_patterns.pattern", pattern.name)}
Date: Thu, 21 Aug 2025 11:55:52 -0700 Subject: [PATCH 5/9] fix team color (#1888) ## Description: Fix color assignment crashing squad games. For squad games, teams are assigned 1,2,3, etc. So the ColorAllocator cannot find them and throws an exception. ## 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: evan --- src/core/configuration/ColorAllocator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/configuration/ColorAllocator.ts b/src/core/configuration/ColorAllocator.ts index 97d24eadf..e4bef8d36 100644 --- a/src/core/configuration/ColorAllocator.ts +++ b/src/core/configuration/ColorAllocator.ts @@ -48,7 +48,7 @@ export class ColorAllocator { case ColoredTeams.Bot: return botTeamColors; default: - throw new Error(`Unknown team color: ${team}`); + return [this.assignColor(team)]; } } From eda49d1ad83d1a4fd36acc21b48c1e56242a0648 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Thu, 21 Aug 2025 15:07:31 -0700 Subject: [PATCH 6/9] fix emoji exploit: sending emoji to self causes game to crash (#1889) ## Description: When emoji is sent to oneself, it triggers: Cannot send emoji to oneself: ${this}, because the canSendEmoji wasn't checking if it was itself. ## 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: evan --- src/core/game/PlayerImpl.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index e1716bce1..bb906acb3 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -557,6 +557,9 @@ export class PlayerImpl implements Player { } canSendEmoji(recipient: Player | typeof AllPlayers): boolean { + if (recipient === this) { + return false; + } const recipientID = recipient === AllPlayers ? AllPlayers : recipient.smallID(); const prevMsgs = this.outgoingEmojis_.filter( From fb038f241917ac1ebd9d5482f15b0f4b558f53d9 Mon Sep 17 00:00:00 2001 From: DevelopingTom Date: Sun, 24 Aug 2025 19:41:51 +0200 Subject: [PATCH 7/9] Create stations regardless of factory ownership (#1904) ## Description: Bug fix: cities and ports would only connect to factories owned by the current player, ignoring those belonging to other players. This update makes the player ID optional when searching for nearby units: if no player ID is provided, unit ownership is disregarded, allowing connections to all factories regardless of ownership. ## 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: IngloriousTom --- src/core/execution/CityExecution.ts | 1 - src/core/execution/PortExecution.ts | 1 - src/core/game/Game.ts | 2 +- src/core/game/GameImpl.ts | 2 +- src/core/game/UnitGrid.ts | 25 +++++++++++++++++++------ 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/core/execution/CityExecution.ts b/src/core/execution/CityExecution.ts index 863ba716e..69d39977d 100644 --- a/src/core/execution/CityExecution.ts +++ b/src/core/execution/CityExecution.ts @@ -52,7 +52,6 @@ export class CityExecution implements Execution { this.city.tile(), this.mg.config().trainStationMaxRange(), UnitType.Factory, - this.player.id(), ); if (nearbyFactory) { this.mg.addExecution(new TrainStationExecution(this.city)); diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index fc8971b2d..5c7dfc566 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -97,7 +97,6 @@ export class PortExecution implements Execution { this.port.tile(), this.mg.config().trainStationMaxRange(), UnitType.Factory, - this.player.id(), ); if (nearbyFactory) { this.mg.addExecution(new TrainStationExecution(this.port)); diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 2486e57ac..2416371da 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -673,7 +673,7 @@ export type Game = { tile: TileRef, searchRange: number, type: UnitType, - playerId: PlayerID, + playerId?: PlayerID, ): boolean; nearbyUnits( tile: TileRef, diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 91e6ff98d..9d1898910 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -752,7 +752,7 @@ export class GameImpl implements Game { tile: TileRef, searchRange: number, type: UnitType, - playerId: PlayerID, + playerId?: PlayerID, ) { return this.unitGrid.hasUnitNearby(tile, searchRange, type, playerId); } diff --git a/src/core/game/UnitGrid.ts b/src/core/game/UnitGrid.ts index 2f17d9ee9..bb8b52d0d 100644 --- a/src/core/game/UnitGrid.ts +++ b/src/core/game/UnitGrid.ts @@ -170,12 +170,28 @@ export class UnitGrid { return nearby; } + private unitIsInRange( + unit: Unit | UnitView, + tile: TileRef, + rangeSquared: number, + playerId?: PlayerID, + ): boolean { + if (!unit.isActive()) { + return false; + } + if (playerId !== undefined && unit.owner().id() !== playerId) { + return false; + } + const distSquared = this.squaredDistanceFromTile(unit, tile); + return distSquared <= rangeSquared; + } + // Return true if it finds an owned specific unit in range hasUnitNearby( tile: TileRef, searchRange: number, type: UnitType, - playerId: PlayerID, + playerId?: PlayerID, ): boolean { const { startGridX, endGridX, startGridY, endGridY } = this.getCellsInRange( tile, @@ -187,11 +203,8 @@ export class UnitGrid { const unitSet = this.grid[cy][cx].get(type); if (unitSet === undefined) continue; for (const unit of unitSet) { - if (unit.owner().id() === playerId && unit.isActive()) { - const distSquared = this.squaredDistanceFromTile(unit, tile); - if (distSquared <= rangeSquared) { - return true; - } + if (this.unitIsInRange(unit, tile, rangeSquared, playerId)) { + return true; } } } From d752902dfe3b7d46e97184cf99c0824c4e85cd9a Mon Sep 17 00:00:00 2001 From: evanpelle Date: Tue, 19 Aug 2025 19:11:55 -0700 Subject: [PATCH 8/9] Remove Yenisei from public multiplayer games (#1874) The map is very large and the game lagged a lot on this map during playtests. So remove it from public lobbies. - [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 regression is found: evan --- src/server/MapPlaylist.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts index ab2b78492..b87dd4c99 100644 --- a/src/server/MapPlaylist.ts +++ b/src/server/MapPlaylist.ts @@ -46,7 +46,6 @@ const frequency: Partial> = { SouthAmerica: 5, StraitOfGibraltar: 5, World: 8, - Yenisei: 6, }; type MapWithMode = { From de3e80154b1061c12903bc41e174b97de2767471 Mon Sep 17 00:00:00 2001 From: Scott Anderson <662325+scottanderson@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:23:11 -0400 Subject: [PATCH 9/9] Fix eslint failure (#1921) ## Description: Fix an eslint failure blocking PRs from being merged. ## 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 --- src/client/TerritoryPatternsModal.ts | 2 +- src/core/execution/alliance/AllianceRequestExecution.ts | 3 ++- tests/AllianceRequestExecution.test.ts | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/TerritoryPatternsModal.ts b/src/client/TerritoryPatternsModal.ts index e3dcec8ab..7a1f086ce 100644 --- a/src/client/TerritoryPatternsModal.ts +++ b/src/client/TerritoryPatternsModal.ts @@ -67,7 +67,7 @@ export class TerritoryPatternsModal extends LitElement { async onUserMe(userMeResponse: UserMeResponse | null) { if (userMeResponse === null) { - this.userSettings.setSelectedPatternName(undefined); + this.userSettings.setSelectedPattern(undefined); this.selectedPattern = undefined; } this.patterns = await patterns(userMeResponse); diff --git a/src/core/execution/alliance/AllianceRequestExecution.ts b/src/core/execution/alliance/AllianceRequestExecution.ts index b73cd50c6..9ae7cbdfb 100644 --- a/src/core/execution/alliance/AllianceRequestExecution.ts +++ b/src/core/execution/alliance/AllianceRequestExecution.ts @@ -9,7 +9,7 @@ import { export class AllianceRequestExecution implements Execution { private req: AllianceRequest | null = null; private active = true; - private mg: Game; + private mg: Game | undefined; constructor( private readonly requestor: Player, @@ -53,6 +53,7 @@ export class AllianceRequestExecution implements Execution { this.active = false; return; } + if (this.mg === undefined) throw new Error("Not initialized"); if ( this.mg.ticks() - (this.req?.createdAt() ?? 0) > this.mg.config().allianceRequestDuration() diff --git a/tests/AllianceRequestExecution.test.ts b/tests/AllianceRequestExecution.test.ts index c88edd5a0..b6df1e657 100644 --- a/tests/AllianceRequestExecution.test.ts +++ b/tests/AllianceRequestExecution.test.ts @@ -1,7 +1,7 @@ -import { AllianceRequestExecution } from "../src/core/execution/alliance/AllianceRequestExecution"; -import { AllianceRequestReplyExecution } from "../src/core/execution/alliance/AllianceRequestReplyExecution"; import { Game, Player, PlayerType } from "../src/core/game/Game"; import { playerInfo, setup } from "./util/Setup"; +import { AllianceRequestExecution } from "../src/core/execution/alliance/AllianceRequestExecution"; +import { AllianceRequestReplyExecution } from "../src/core/execution/alliance/AllianceRequestReplyExecution"; let game: Game; let player1: Player; @@ -64,13 +64,13 @@ describe("AllianceRequestExecution", () => { game.addExecution(new AllianceRequestExecution(player1, player2.id())); game.executeNextTick(); - expect(player1.outgoingAllianceRequests().length).toBe(1); + expect(player1.outgoingAllianceRequests()).toHaveLength(1); for (let i = 0; i < 6; i++) { game.executeNextTick(); } - expect(player1.outgoingAllianceRequests().length).toBe(0); + expect(player1.outgoingAllianceRequests()).toHaveLength(0); expect(player1.isAlliedWith(player2)).toBeFalsy(); expect(player2.isAlliedWith(player1)).toBeFalsy(); });