mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:10:42 +00:00
Merge branch 'main' into feat/advertise-section
This commit is contained in:
@@ -64,6 +64,14 @@ Licensed under [CC0 1.0.](https://creativecommons.org/publicdomain/zero/1.0/lega
|
||||
[NASA/JPL-Caltech](https://www.jpl.nasa.gov/images/pia10748-our-milky-way-gets-a-makeover-artist-concept/)
|
||||
[Public Domain](https://www.jpl.nasa.gov/jpl-image-use-policy/)
|
||||
|
||||
### Strait Of Malacca Map
|
||||
|
||||
[https://commons.wikimedia.org/wiki/User:Sadalmelik#/media/File:Sumatra_Topography.png](https://commons.wikimedia.org/wiki/File:Sumatra_Topography.png#/media/File:Sumatra_Topography.png)
|
||||
Sadalmelik - Own work
|
||||
Topographic map of Sumatra. Created with GMT from publicly released SRTM data. For locator version, see Image:Sumatra Locator Topography.png
|
||||
CC BY-SA 3.0
|
||||
File:Sumatra Topography.png
|
||||
|
||||
## Icons
|
||||
|
||||
### [The Noun Project](https://thenounproject.com/)
|
||||
|
||||
+18
-2
@@ -1,5 +1,7 @@
|
||||
# API Usage
|
||||
|
||||
> **Warning:** Rate limits are very strict. Join the [Discord](https://discord.gg/K9zernJB5z) to request higher rate limits.
|
||||
|
||||
## Games
|
||||
|
||||
### List Game Metadata
|
||||
@@ -207,10 +209,24 @@ GET https://api.openfront.io/public/clan/:clanTag/sessions
|
||||
|
||||
- `start` (optional): ISO 8601 timestamp
|
||||
- `end` (optional): ISO 8601 timestamp
|
||||
- `page` (optional): Page number, 1-200 (default: 1)
|
||||
- `limit` (optional): Results per page, 1-50 (default: 20)
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [ ... ],
|
||||
"total": 150,
|
||||
"page": 1,
|
||||
"limit": 20
|
||||
}
|
||||
```
|
||||
|
||||
Results are ordered by game start time, newest first.
|
||||
|
||||
**Example**
|
||||
|
||||
```bash
|
||||
curl https://api.openfront.io/public/clan/UN/sessions?start=2025-11-15T00:00:00Z &
|
||||
end=2025-11-18T23:59:59Z
|
||||
curl "https://api.openfront.io/public/clan/UN/sessions?start=2025-11-15T00:00:00Z&end=2025-11-18T23:59:59Z&limit=10&page=1"
|
||||
```
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 379 KiB |
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "straitofmalacca",
|
||||
"nations": [
|
||||
{
|
||||
"coordinates": [1268, 730],
|
||||
"name": "Singapore",
|
||||
"flag": "sg"
|
||||
},
|
||||
{
|
||||
"coordinates": [862, 41],
|
||||
"name": "Thailand",
|
||||
"flag": "th"
|
||||
},
|
||||
{
|
||||
"coordinates": [1328, 1284],
|
||||
"name": "Sumatra",
|
||||
"flag": "id"
|
||||
},
|
||||
{
|
||||
"coordinates": [1141, 1090],
|
||||
"name": "Burmese Pythons",
|
||||
"flag": "mm"
|
||||
},
|
||||
{
|
||||
"coordinates": [210, 243],
|
||||
"name": "Aceh",
|
||||
"flag": "id"
|
||||
},
|
||||
{
|
||||
"coordinates": [1011, 736],
|
||||
"name": "Riau",
|
||||
"flag": "id"
|
||||
},
|
||||
{
|
||||
"coordinates": [600, 565],
|
||||
"name": "Samosir",
|
||||
"flag": "id"
|
||||
},
|
||||
{
|
||||
"coordinates": [827, 999],
|
||||
"name": "Barisan",
|
||||
"flag": "id"
|
||||
},
|
||||
{
|
||||
"coordinates": [1053, 333],
|
||||
"name": "Malaysia",
|
||||
"flag": "my"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -50,6 +50,7 @@ var maps = []struct {
|
||||
{Name: "japan"},
|
||||
{Name: "lisbon"},
|
||||
{Name: "manicouagan"},
|
||||
{Name: "straitofmalacca"},
|
||||
{Name: "mars"},
|
||||
{Name: "mena"},
|
||||
{Name: "montreal"},
|
||||
@@ -86,7 +87,7 @@ var maps = []struct {
|
||||
{Name: "milkyway"},
|
||||
{Name: "mediterranean"},
|
||||
{Name: "greatlakes"},
|
||||
{Name: "dyslexdria"},
|
||||
{Name: "dyslexdria"},
|
||||
{Name: "big_plains", IsTest: true},
|
||||
{Name: "half_land_half_ocean", IsTest: true},
|
||||
{Name: "ocean_and_land", IsTest: true},
|
||||
|
||||
@@ -355,7 +355,8 @@
|
||||
"milkyway": "Milky Way",
|
||||
"mediterranean": "Mediterranean",
|
||||
"dyslexdria": "Dyslexdria",
|
||||
"greatlakes": "Great Lakes"
|
||||
"greatlakes": "Great Lakes",
|
||||
"straitofmalacca": "Strait Of Malacca"
|
||||
},
|
||||
"map_categories": {
|
||||
"featured": "Featured",
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"map": {
|
||||
"height": 1644,
|
||||
"num_land_tiles": 867389,
|
||||
"width": 1832
|
||||
},
|
||||
"map16x": {
|
||||
"height": 411,
|
||||
"num_land_tiles": 51062,
|
||||
"width": 458
|
||||
},
|
||||
"map4x": {
|
||||
"height": 822,
|
||||
"num_land_tiles": 212395,
|
||||
"width": 916
|
||||
},
|
||||
"name": "straitofmalacca",
|
||||
"nations": [
|
||||
{
|
||||
"coordinates": [1268, 730],
|
||||
"flag": "sg",
|
||||
"name": "Singapore"
|
||||
},
|
||||
{
|
||||
"coordinates": [862, 41],
|
||||
"flag": "th",
|
||||
"name": "Thailand"
|
||||
},
|
||||
{
|
||||
"coordinates": [1328, 1284],
|
||||
"flag": "id",
|
||||
"name": "Sumatra"
|
||||
},
|
||||
{
|
||||
"coordinates": [1141, 1090],
|
||||
"flag": "mm",
|
||||
"name": "Burmese Pythons"
|
||||
},
|
||||
{
|
||||
"coordinates": [210, 243],
|
||||
"flag": "id",
|
||||
"name": "Aceh"
|
||||
},
|
||||
{
|
||||
"coordinates": [1011, 736],
|
||||
"flag": "id",
|
||||
"name": "Riau"
|
||||
},
|
||||
{
|
||||
"coordinates": [600, 565],
|
||||
"flag": "id",
|
||||
"name": "Samosir"
|
||||
},
|
||||
{
|
||||
"coordinates": [827, 999],
|
||||
"flag": "id",
|
||||
"name": "Barisan"
|
||||
},
|
||||
{
|
||||
"coordinates": [1053, 333],
|
||||
"flag": "my",
|
||||
"name": "Malaysia"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
@@ -789,23 +789,20 @@ export class HostLobbyModal extends BaseModal {
|
||||
disabledUnits: this.disabledUnits,
|
||||
spawnImmunityDuration: this.spawnImmunity
|
||||
? spawnImmunityTicks
|
||||
: undefined,
|
||||
: null,
|
||||
playerTeams: this.teamCount,
|
||||
nations: sliderToNationsConfig(
|
||||
this.nations,
|
||||
this.defaultNationCount,
|
||||
),
|
||||
maxTimerValue:
|
||||
this.maxTimer === true ? this.maxTimerValue : undefined,
|
||||
maxTimerValue: this.maxTimer === true ? this.maxTimerValue : null,
|
||||
goldMultiplier:
|
||||
this.goldMultiplier === true
|
||||
? this.goldMultiplierValue
|
||||
: undefined,
|
||||
this.goldMultiplier === true ? this.goldMultiplierValue : null,
|
||||
startingGold:
|
||||
this.startingGold === true && this.startingGoldValue !== undefined
|
||||
? Math.round(this.startingGoldValue * 1_000_000)
|
||||
: undefined,
|
||||
disableAlliances: this.disableAlliances || undefined,
|
||||
: null,
|
||||
disableAlliances: this.disableAlliances || null,
|
||||
} satisfies Partial<GameConfig>,
|
||||
},
|
||||
bubbles: true,
|
||||
|
||||
@@ -36,6 +36,7 @@ interface AttackLabel {
|
||||
|
||||
export class AttackingTroopsOverlay implements Layer {
|
||||
private container: HTMLDivElement;
|
||||
private labelTemplate: HTMLDivElement;
|
||||
private labels = new Map<string, AttackLabel>();
|
||||
// Guard against queuing multiple worker requests in the same tick window.
|
||||
private inFlightRequest = false;
|
||||
@@ -63,6 +64,8 @@ export class AttackingTroopsOverlay implements Layer {
|
||||
this.container.style.zIndex = "4";
|
||||
document.body.appendChild(this.container);
|
||||
|
||||
this.labelTemplate = this.createLabelTemplate();
|
||||
|
||||
this.onAlternateView = (e) => {
|
||||
this.isVisible = !e.alternateView;
|
||||
this.container.style.display = this.isVisible ? "" : "none";
|
||||
@@ -235,28 +238,39 @@ export class AttackingTroopsOverlay implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private createLabelElement(
|
||||
attackerTroops: number,
|
||||
defenderTroops: number,
|
||||
isIncoming: boolean,
|
||||
): HTMLDivElement {
|
||||
private createLabelTemplate(): HTMLDivElement {
|
||||
const el = document.createElement("div");
|
||||
el.style.position = "absolute";
|
||||
el.style.display = "none";
|
||||
el.style.alignItems = "center";
|
||||
el.style.gap = "3px";
|
||||
el.style.width = "max-content";
|
||||
el.style.whiteSpace = "nowrap";
|
||||
el.style.fontSize = "11px";
|
||||
el.style.fontWeight = "bold";
|
||||
el.style.fontFamily = this.game.config().theme().font();
|
||||
el.style.padding = "1px 4px";
|
||||
el.style.borderRadius = "3px";
|
||||
el.style.backgroundColor = "rgba(0,0,0,0.55)";
|
||||
el.style.pointerEvents = "none";
|
||||
el.style.lineHeight = "1.3";
|
||||
// Smooth the label to its new position as the front line advances.
|
||||
el.style.transition = "transform 0.2s ease-out";
|
||||
el.style.width = "max-content";
|
||||
const icon = document.createElement("img");
|
||||
icon.style.width = "10px";
|
||||
icon.style.height = "10px";
|
||||
el.appendChild(icon);
|
||||
const span = document.createElement("span");
|
||||
span.style.minWidth = "25px";
|
||||
el.appendChild(span);
|
||||
return el;
|
||||
}
|
||||
|
||||
private createLabelElement(
|
||||
attackerTroops: number,
|
||||
defenderTroops: number,
|
||||
isIncoming: boolean,
|
||||
): HTMLDivElement {
|
||||
const el = this.labelTemplate.cloneNode(true) as HTMLDivElement;
|
||||
el.style.fontFamily = this.game.config().theme().font();
|
||||
this.updateLabelContent(el, attackerTroops, defenderTroops, isIncoming);
|
||||
this.container.appendChild(el);
|
||||
return el;
|
||||
@@ -268,17 +282,8 @@ export class AttackingTroopsOverlay implements Layer {
|
||||
defenderTroops: number,
|
||||
isIncoming: boolean,
|
||||
) {
|
||||
// Reuse existing children to avoid DOM churn on every tick.
|
||||
let icon = el.querySelector("img") as HTMLImageElement | null;
|
||||
let span = el.querySelector("span") as HTMLSpanElement | null;
|
||||
if (!icon || !span) {
|
||||
icon = document.createElement("img");
|
||||
icon.style.width = "10px";
|
||||
icon.style.height = "10px";
|
||||
span = document.createElement("span");
|
||||
el.replaceChildren(icon, span);
|
||||
}
|
||||
|
||||
const icon = el.children[0] as HTMLImageElement;
|
||||
const span = el.children[1] as HTMLSpanElement;
|
||||
if (isIncoming) {
|
||||
icon.src = shieldIcon;
|
||||
span.style.color = troopDefenceColor(attackerTroops, defenderTroops);
|
||||
|
||||
@@ -99,7 +99,10 @@ export class GameRightSidebar extends LitElement implements Layer {
|
||||
const elapsedSeconds = Math.floor(gameTicks / 10); // 10 ticks per second
|
||||
|
||||
if (this.game.inSpawnPhase()) {
|
||||
this.timer = maxTimerValue !== undefined ? maxTimerValue * 60 : 0;
|
||||
this.timer =
|
||||
maxTimerValue !== null && maxTimerValue !== undefined
|
||||
? maxTimerValue * 60
|
||||
: 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,7 +110,7 @@ export class GameRightSidebar extends LitElement implements Layer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxTimerValue !== undefined) {
|
||||
if (maxTimerValue !== null && maxTimerValue !== undefined) {
|
||||
this.timer = Math.max(0, maxTimerValue * 60 - elapsedSeconds);
|
||||
} else {
|
||||
this.timer = elapsedSeconds;
|
||||
@@ -179,6 +182,7 @@ export class GameRightSidebar extends LitElement implements Layer {
|
||||
|
||||
const timerColor =
|
||||
this.game.config().gameConfig().maxTimerValue !== undefined &&
|
||||
this.game.config().gameConfig().maxTimerValue !== null &&
|
||||
this.timer < 60
|
||||
? "text-red-400"
|
||||
: "";
|
||||
|
||||
+5
-5
@@ -247,15 +247,15 @@ export const GameConfigSchema = z.object({
|
||||
infiniteTroops: z.boolean(),
|
||||
instantBuild: z.boolean(),
|
||||
disableNavMesh: z.boolean().optional(),
|
||||
disableAlliances: z.boolean().optional(),
|
||||
disableAlliances: z.boolean().nullable().optional(),
|
||||
randomSpawn: z.boolean(),
|
||||
maxPlayers: z.number().optional(),
|
||||
maxTimerValue: z.number().int().min(1).max(120).optional(), // In minutes
|
||||
spawnImmunityDuration: z.number().int().min(0).optional(), // In ticks
|
||||
maxTimerValue: z.number().int().min(1).max(120).nullable().optional(), // In minutes
|
||||
spawnImmunityDuration: z.number().int().min(0).nullable().optional(), // In ticks
|
||||
disabledUnits: z.enum(UnitType).array().optional(),
|
||||
playerTeams: TeamCountConfigSchema.optional(),
|
||||
goldMultiplier: z.number().min(0.1).max(1000).optional(),
|
||||
startingGold: z.number().int().min(0).max(1000000000).optional(),
|
||||
goldMultiplier: z.number().min(0.1).max(1000).nullable().optional(),
|
||||
startingGold: z.number().int().min(0).max(1000000000).nullable().optional(),
|
||||
});
|
||||
|
||||
export const TeamSchema = z.string();
|
||||
|
||||
@@ -147,6 +147,7 @@ export enum GameMapType {
|
||||
Mediterranean = "Mediterranean",
|
||||
Dyslexdria = "Dyslexdria",
|
||||
GreatLakes = "Great Lakes",
|
||||
StraitOfMalacca = "Strait Of Malacca",
|
||||
}
|
||||
|
||||
export type GameMapName = keyof typeof GameMapType;
|
||||
@@ -200,6 +201,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
|
||||
GameMapType.Aegean,
|
||||
GameMapType.Mediterranean,
|
||||
GameMapType.GreatLakes,
|
||||
GameMapType.StraitOfMalacca,
|
||||
],
|
||||
fantasy: [
|
||||
GameMapType.Pangaea,
|
||||
|
||||
@@ -141,7 +141,7 @@ export class GameServer {
|
||||
this.gameConfig.donateTroops = gameConfig.donateTroops;
|
||||
}
|
||||
if (gameConfig.maxTimerValue !== undefined) {
|
||||
this.gameConfig.maxTimerValue = gameConfig.maxTimerValue;
|
||||
this.gameConfig.maxTimerValue = gameConfig.maxTimerValue ?? undefined;
|
||||
}
|
||||
if (gameConfig.instantBuild !== undefined) {
|
||||
this.gameConfig.instantBuild = gameConfig.instantBuild;
|
||||
@@ -150,7 +150,8 @@ export class GameServer {
|
||||
this.gameConfig.randomSpawn = gameConfig.randomSpawn;
|
||||
}
|
||||
if (gameConfig.spawnImmunityDuration !== undefined) {
|
||||
this.gameConfig.spawnImmunityDuration = gameConfig.spawnImmunityDuration;
|
||||
this.gameConfig.spawnImmunityDuration =
|
||||
gameConfig.spawnImmunityDuration ?? undefined;
|
||||
}
|
||||
if (gameConfig.gameMode !== undefined) {
|
||||
this.gameConfig.gameMode = gameConfig.gameMode;
|
||||
@@ -162,13 +163,14 @@ export class GameServer {
|
||||
this.gameConfig.playerTeams = gameConfig.playerTeams;
|
||||
}
|
||||
if (gameConfig.goldMultiplier !== undefined) {
|
||||
this.gameConfig.goldMultiplier = gameConfig.goldMultiplier;
|
||||
this.gameConfig.goldMultiplier = gameConfig.goldMultiplier ?? undefined;
|
||||
}
|
||||
if (gameConfig.startingGold !== undefined) {
|
||||
this.gameConfig.startingGold = gameConfig.startingGold;
|
||||
this.gameConfig.startingGold = gameConfig.startingGold ?? undefined;
|
||||
}
|
||||
if (gameConfig.disableAlliances !== undefined) {
|
||||
this.gameConfig.disableAlliances = gameConfig.disableAlliances;
|
||||
this.gameConfig.disableAlliances =
|
||||
gameConfig.disableAlliances ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ const frequency: Partial<Record<GameMapName, number>> = {
|
||||
Mediterranean: 6,
|
||||
Dyslexdria: 8,
|
||||
GreatLakes: 6,
|
||||
StraitOfMalacca: 4,
|
||||
};
|
||||
|
||||
const TEAM_WEIGHTS: { config: TeamCountConfig; weight: number }[] = [
|
||||
|
||||
Reference in New Issue
Block a user