From 0931d4ae4bd0b4ba213ead0613ce28251d3b627c Mon Sep 17 00:00:00 2001 From: Mike Harris Date: Sun, 26 Oct 2025 14:19:16 -0500 Subject: [PATCH 1/6] FIX: Revert Spawn Highlight Color for DUOS, TRIOS, QUADS (#2298) ## Description: This PR addresses issue #2297 - Spawn Color Overload in DUOS, TRIOS, and QUADS game modes. This PR reverts the spawn highlight color behavior for these game modes so that the player can identify their teammates more easily in these modes. See below for example fix. The "self" player and their teammate in this DUOS mode game are the same `light blue` color as shown in issue #2297 and their opponent is still the same `brown` color as shown in that issue. However, now the player's teammate has a green spawn highlight color (same as v0.26.7 and earlier) and the players on other teams have a yellow spawn highlight color (same as v0.26.7 and earlier). Duos Teammate ID Resolution The "self" player's spawn highlight color remains a breathing white ring, consistent with other game modes. ## 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: GlacialDrift --- src/client/graphics/layers/TerritoryLayer.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index 945ac3524..c8ba09200 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -2,7 +2,12 @@ import { PriorityQueue } from "@datastructures-js/priority-queue"; import { Colord } from "colord"; import { Theme } from "../../../core/configuration/Config"; import { EventBus } from "../../../core/EventBus"; -import { Cell, PlayerType, UnitType } from "../../../core/game/Game"; +import { + Cell, + ColoredTeams, + PlayerType, + UnitType, +} from "../../../core/game/Game"; import { euclDistFN, TileRef } from "../../../core/game/GameMap"; import { GameUpdateType } from "../../../core/game/GameUpdates"; import { GameView, PlayerView } from "../../../core/game/GameView"; @@ -170,6 +175,7 @@ export class TerritoryLayer implements Layer { .filter((p) => p.type() === PlayerType.Human); const focusedPlayer = this.game.focusedPlayer(); + const teamColors = Object.values(ColoredTeams); for (const human of humans) { if (human === focusedPlayer) { continue; @@ -191,7 +197,17 @@ export class TerritoryLayer implements Layer { // In Team games, the spawn highlight color becomes that player's team color // Optionally, this could be broken down to teammate or enemy and simplified to green and red, respectively const team = human.team(); - if (team !== null) color = this.theme.teamColor(team); + if (team !== null) { + if (teamColors.includes(team)) { + color = this.theme.teamColor(team); + } else { + if (myPlayer.isFriendly(human)) { + color = this.theme.spawnHighlightTeamColor(); + } else { + color = this.theme.spawnHighlightColor(); + } + } + } } for (const tile of this.game.bfs( From 91f1748d70ad0148d4af50ccf28a64b02518971f Mon Sep 17 00:00:00 2001 From: VariableVince <24507472+VariableVince@users.noreply.github.com> Date: Sun, 26 Oct 2025 22:48:54 +0100 Subject: [PATCH 2/6] Perf: remove redundant code from PlayerExecution (#2299) ## Description: Remove some lines of code that do nothing in surroundedBySamePlayer, gain a small bit of performance back. The code says "if it is an ocean shore tile but not an ocean shore tile" which can never be true. (Why where the lines there and why can they be removed: Before you were able to boat on a lake, you had no escape from your land if it bordered a lake and no ocean. So it was logical, if you bordered a lake with your cluster, to still treat it as fully surrounded annexable land. This is why this code was added in [this commit](https://github.com/openfrontio/OpenFrontIO/commit/ee56d687484131c092599434247b3a036e0e9668) back then: `const isOceanShore = this.mg.isOceanShore(tile); if (this.mg.isShore(tile) && !isOceanShore) { continue; }` But [this newer commit](https://github.com/openfrontio/OpenFrontIO/commit/c1383d76f1f76c5e5f9be3a6f30b8f3010258540#diff-fb1101a2b50dd7c353d082ff7a3351cff5469b8249b3ebca91c10573a3dfaaf1) made it so you could from then on boat on lakes. So you have an escape from your cluster since then. And just like being on the edge of the map or when bordering the ocean, this means your cluster won't get annexed. However, while the updated code for PlayerExecution in the last commit does its work as intended (it does not exclude lake shore tiles anymore)... The code contradicts itself: `const isOceanShore = this.mg.isOceanShore(tile); if (this.mg.isOceanShore(tile) && !isOceanShore) { continue;}` Conclusion: the code on lines 136-138 can be deleted because it literally says "if it is an ocean shore tile but not an ocean shore tile" which can never be true. Also remove the const and check isOceanShore directly in the if statement.) ## 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: tryout33 --- src/core/execution/PlayerExecution.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/core/execution/PlayerExecution.ts b/src/core/execution/PlayerExecution.ts index bc2491a72..a8cc3fb42 100644 --- a/src/core/execution/PlayerExecution.ts +++ b/src/core/execution/PlayerExecution.ts @@ -132,12 +132,8 @@ export class PlayerExecution implements Execution { private surroundedBySamePlayer(cluster: Set): false | Player { const enemies = new Set(); for (const tile of cluster) { - const isOceanShore = this.mg.isOceanShore(tile); - if (this.mg.isOceanShore(tile) && !isOceanShore) { - continue; - } if ( - isOceanShore || + this.mg.isOceanShore(tile) || this.mg.isOnEdgeOfMap(tile) || this.mg.neighbors(tile).some((n) => !this.mg?.hasOwner(n)) ) { From 48909d5d25b9a2a6e6a4205caaa9ad66644d3724 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Mon, 27 Oct 2025 12:45:10 -0700 Subject: [PATCH 3/6] Add API.md to document public api --- API.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 000000000..e23e2b7e5 --- /dev/null +++ b/API.md @@ -0,0 +1,93 @@ +## API Usage + +### List Game Metadata + +Get game IDs and basic metadata for games that started within a specified time range. Results are sorted by start time and paginated. + +**Constraints:** + +- Maximum time range: 2 days +- Maximum limit per request: 1000 games + +**Endpoint:** + +``` +GET https://api.openfront.io/public/games +``` + +**Query Parameters:** + +- `start` (required): ISO 8601 timestamp +- `end` (required): ISO 8601 timestamp +- `limit` (optional): Number of results (max 1000) +- `offset` (optional): Pagination offset + +**Example Request:** + +```bash +curl "https://api.openfront.io/public/games?start=2025-10-25T00:00:00Z&end=2025-10-26T23:59:59Z&limit=10&offset=5" +``` + +**Response:** + +```json +[ + { + "game": "ABSgwin6", + "start": "2025-10-25T00:00:10.526Z", + "end": "2025-10-25T00:19:45.187Z", + "type": "Singleplayer", + "mode": "Free For All", + "difficulty": "Medium" + }, + ... +] +``` + +The response includes a `Content-Range` header indicating pagination (e.g., `games 5-15/399`). + +--- + +### Get Game Info + +Retrieve detailed information about a specific game. + +**Endpoint:** + +``` +GET https://api.openfront.io/public/game/:gameId +``` + +**Query Parameters:** + +- `turns` (optional): Set to `false` to exclude turn data and reduce response size + +**Examples:** + +```bash +# Full game data +curl "https://api.openfront.io/public/game/ABSgwin6" + +# Without turn data +curl "https://api.openfront.io/public/game/ABSgwin6?turns=false" +``` + +**Note:** Public player IDs are stripped from game records for privacy. + +--- + +### Get Player Info + +Retrieve information and stats for a specific player. + +**Endpoint:** + +``` +GET https://api.openfront.io/public/player/:playerId +``` + +**Example:** + +```bash +curl "https://api.openfront.io/public/player/HabCsQYR" +``` From 74197d49e4f7886c73a1240f0bcbdca7cc243b59 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Mon, 27 Oct 2025 13:22:37 -0700 Subject: [PATCH 4/6] update API.md to include type parameter in games/ endpoint --- API.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/API.md b/API.md index e23e2b7e5..0dda65425 100644 --- a/API.md +++ b/API.md @@ -19,13 +19,14 @@ GET https://api.openfront.io/public/games - `start` (required): ISO 8601 timestamp - `end` (required): ISO 8601 timestamp -- `limit` (optional): Number of results (max 1000) +- `type` (optional): Game type, must be one of `[Private, Public, Singleplayer]` +- `limit` (optional): Number of results (max 1000, default 50) - `offset` (optional): Pagination offset **Example Request:** ```bash -curl "https://api.openfront.io/public/games?start=2025-10-25T00:00:00Z&end=2025-10-26T23:59:59Z&limit=10&offset=5" +curl "https://api.openfront.io/public/games?start=2025-10-25T00:00:00Z&end=2025-10-26T23:59:59Z&type=Singleplayer&limit=10&offset=5" ``` **Response:** From 4a9a19631ea950613af44741c7256c247c0e1f52 Mon Sep 17 00:00:00 2001 From: Thomas Cruveilher <38007824+Sorikairox@users.noreply.github.com> Date: Tue, 28 Oct 2025 06:06:12 +0900 Subject: [PATCH 5/6] fix(client): do not spam public lobbies requests by waiting for previous request to end before firing a new one (#2276) ## Description: This PR aims to (quickly, not definitely) address https://github.com/openfrontio/OpenFrontIO/issues/2274 as I believe that's an urgent DDOS problem with the frontend design. I am looking at the backend to understand the underlying issue. ## How was this tested: I added a 2 seconds "sleep" to the endpoint `public_lobbies` to simulate loads. The frontend would do request every seconds, which kept piling up on the server and decreased performances, cf screenshot, instead of resolving every 2 seconds, each subsequent request would take longer than the previous one. Screenshot 2025-10-23 at 23 36 37 After adding the fix to the frontend (which can only trigger 1 request at a time, waiting for the previous one), all request completed in approximatively 2 seconds as expected, cf screenshot. Screenshot 2025-10-23 at 23 36 54 ## 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: sorikairo Co-authored-by: Vivacious Box --- src/client/PublicLobby.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/client/PublicLobby.ts b/src/client/PublicLobby.ts index 3c186be1f..35cd183e6 100644 --- a/src/client/PublicLobby.ts +++ b/src/client/PublicLobby.ts @@ -17,6 +17,7 @@ export class PublicLobby extends LitElement { private currLobby: GameInfo | null = null; private debounceDelay: number = 750; private lobbyIDToStart = new Map(); + private lobbiesFetchInFlight: Promise | null = null; createRenderRoot() { return this; @@ -73,16 +74,26 @@ export class PublicLobby extends LitElement { } async fetchLobbies(): Promise { - try { - const response = await fetch(`/api/public_lobbies`); - if (!response.ok) - throw new Error(`HTTP error! status: ${response.status}`); - const data = await response.json(); - return data.lobbies; - } catch (error) { - console.error("Error fetching lobbies:", error); - throw error; + if (this.lobbiesFetchInFlight) { + return this.lobbiesFetchInFlight; } + + this.lobbiesFetchInFlight = (async () => { + try { + const response = await fetch(`/api/public_lobbies`); + if (!response.ok) + throw new Error(`HTTP error! status: ${response.status}`); + const data = await response.json(); + return data.lobbies as GameInfo[]; + } catch (error) { + console.error("Error fetching lobbies:", error); + throw error; + } finally { + this.lobbiesFetchInFlight = null; + } + })(); + + return this.lobbiesFetchInFlight; } public stop() { From cb744b49fc58f80a53e02e2efa115c1104e03843 Mon Sep 17 00:00:00 2001 From: Lavodan <21205085+Lavodan@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:13:05 +0100 Subject: [PATCH 6/6] Fix incorrect display of ability to upgrade enemy units (#2308) ## Description: Fixes #2135. Adds an owner check to canUpgradeUnit to prevent enemy units from seemingly being able to be upgraded when Ctrl+clicked on ## 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: Lavodan --- 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 b6d199fc5..a83c85bfb 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -902,6 +902,9 @@ export class PlayerImpl implements Player { if (this._gold < this.mg.config().unitInfo(unit.type()).cost(this)) { return false; } + if (unit.owner() !== this) { + return false; + } return true; }