mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-14 10:29:20 +00:00
Merge branch 'main' into team-names
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
GameMode,
|
||||
GameType,
|
||||
} from "../src/core/game/Game";
|
||||
import { AnalyticsRecord } from "../src/core/Schemas";
|
||||
import { AnalyticsRecord, GameConfig } from "../src/core/Schemas";
|
||||
import {
|
||||
GOLD_INDEX_STEAL,
|
||||
GOLD_INDEX_TRADE,
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "../src/core/StatsSchemas";
|
||||
|
||||
describe("Ranking class", () => {
|
||||
const mockConfig = {
|
||||
const mockConfig: GameConfig = {
|
||||
gameMap: GameMapType.Montreal,
|
||||
difficulty: Difficulty.Medium,
|
||||
donateGold: false,
|
||||
@@ -27,7 +27,7 @@ describe("Ranking class", () => {
|
||||
gameType: GameType.Public,
|
||||
gameMode: GameMode.FFA,
|
||||
gameMapSize: GameMapSize.Normal,
|
||||
disableNations: true,
|
||||
nations: "disabled",
|
||||
bots: 0,
|
||||
infiniteGold: false,
|
||||
infiniteTroops: false,
|
||||
|
||||
@@ -63,6 +63,7 @@ describe("AllianceBehavior.handleAllianceRequests", () => {
|
||||
numTilesPlayer = 10,
|
||||
numTilesRequestor = 10,
|
||||
alliancesCount = 0,
|
||||
createdAtTick = game.ticks() + 1,
|
||||
} = {}) {
|
||||
if (isTraitor) requestor.markTraitor();
|
||||
|
||||
@@ -86,7 +87,7 @@ describe("AllianceBehavior.handleAllianceRequests", () => {
|
||||
const mockRequest = {
|
||||
requestor: () => requestor,
|
||||
recipient: () => player,
|
||||
createdAt: () => 0 as unknown as Tick,
|
||||
createdAt: () => createdAtTick as unknown as Tick,
|
||||
accept: vi.fn(),
|
||||
reject: vi.fn(),
|
||||
} as unknown as AllianceRequest;
|
||||
@@ -96,9 +97,9 @@ describe("AllianceBehavior.handleAllianceRequests", () => {
|
||||
return mockRequest;
|
||||
}
|
||||
|
||||
test("should reject alliance during spawn phase", () => {
|
||||
vi.spyOn(game, "inSpawnPhase").mockReturnValue(true);
|
||||
const request = setupAllianceRequest({});
|
||||
test("should reject alliance created on first post-spawn tick", () => {
|
||||
const cutoff = game.config().numSpawnPhaseTurns() + 1;
|
||||
const request = setupAllianceRequest({ createdAtTick: cutoff });
|
||||
|
||||
allianceBehavior.handleAllianceRequests();
|
||||
|
||||
|
||||
@@ -29,9 +29,12 @@ describe("PlayerImpl", () => {
|
||||
}
|
||||
|
||||
player = game.player("player_id");
|
||||
player.addGold(BigInt(1000000));
|
||||
other = game.player("other_id");
|
||||
|
||||
player.conquer(game.ref(0, 0));
|
||||
other.conquer(game.ref(50, 50));
|
||||
player.addGold(BigInt(1000000));
|
||||
|
||||
game.config().structureMinDist = () => 10;
|
||||
});
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ function extractDataI18nKeys(content: string): Set<string> {
|
||||
function extractTranslationKeyLikeAttrs(content: string): Set<string> {
|
||||
const keys = new Set<string>();
|
||||
const keyLikeAttrRegex =
|
||||
/\b(?:translationKey|labelKey|disabledKey|titleKey|ariaLabelKey|placeholderKey)\s*=\s*["']([^"']+)["']/g;
|
||||
/\b(?:translationKey|labelKey|defaultLabelKey|disabledKey|titleKey|ariaLabelKey|placeholderKey)\s*=\s*["']([^"']+)["']/g;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = keyLikeAttrRegex.exec(content)) !== null) {
|
||||
keys.add(match[1]);
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
type NavigatorOverride = {
|
||||
userAgent: string;
|
||||
userAgentData?: { platform?: string };
|
||||
maxTouchPoints?: number;
|
||||
};
|
||||
|
||||
const setInnerWidth = (value: number) => {
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
const loadPlatform = async ({
|
||||
userAgent,
|
||||
userAgentData,
|
||||
maxTouchPoints,
|
||||
}: NavigatorOverride) => {
|
||||
vi.resetModules();
|
||||
vi.stubGlobal("navigator", {
|
||||
userAgent,
|
||||
userAgentData,
|
||||
maxTouchPoints,
|
||||
});
|
||||
const { Platform } = await import("../../src/client/Platform");
|
||||
return Platform;
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Platform", () => {
|
||||
it("detects iOS before macOS for iPhone-like user agents", async () => {
|
||||
const platform = await loadPlatform({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
|
||||
});
|
||||
|
||||
expect(platform.os).toBe("iOS");
|
||||
expect(platform.isIOS).toBe(true);
|
||||
expect(platform.isMac).toBe(false);
|
||||
});
|
||||
|
||||
it("detects macOS for Macintosh user agents", async () => {
|
||||
const platform = await loadPlatform({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
|
||||
});
|
||||
|
||||
expect(platform.os).toBe("macOS");
|
||||
expect(platform.isMac).toBe(true);
|
||||
expect(platform.isIOS).toBe(false);
|
||||
});
|
||||
|
||||
it("detects iOS for iPad desktop-mode user agents with touch support", async () => {
|
||||
const platform = await loadPlatform({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
|
||||
maxTouchPoints: 5,
|
||||
});
|
||||
|
||||
expect(platform.os).toBe("iOS");
|
||||
expect(platform.isIOS).toBe(true);
|
||||
expect(platform.isMac).toBe(false);
|
||||
});
|
||||
|
||||
it("uses userAgentData platform when available", async () => {
|
||||
const platform = await loadPlatform({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15",
|
||||
userAgentData: { platform: "Android" },
|
||||
});
|
||||
|
||||
expect(platform.os).toBe("Android");
|
||||
expect(platform.isAndroid).toBe(true);
|
||||
});
|
||||
|
||||
it("normalizes non-canonical userAgentData platform values", async () => {
|
||||
const macPlatform = await loadPlatform({
|
||||
userAgent: "Mozilla/5.0",
|
||||
userAgentData: { platform: "Macintosh" },
|
||||
});
|
||||
|
||||
expect(macPlatform.os).toBe("macOS");
|
||||
expect(macPlatform.isMac).toBe(true);
|
||||
|
||||
const chromeOsPlatform = await loadPlatform({
|
||||
userAgent: "Mozilla/5.0",
|
||||
userAgentData: { platform: "Chrome OS" },
|
||||
});
|
||||
|
||||
expect(chromeOsPlatform.os).toBe("Linux");
|
||||
expect(chromeOsPlatform.isLinux).toBe(true);
|
||||
|
||||
const unknownPlatform = await loadPlatform({
|
||||
userAgent: "Mozilla/5.0",
|
||||
userAgentData: { platform: "PlayStation" },
|
||||
});
|
||||
|
||||
expect(unknownPlatform.os).toBe("Unknown");
|
||||
expect(unknownPlatform.isMac).toBe(false);
|
||||
expect(unknownPlatform.isWindows).toBe(false);
|
||||
expect(unknownPlatform.isIOS).toBe(false);
|
||||
expect(unknownPlatform.isAndroid).toBe(false);
|
||||
expect(unknownPlatform.isLinux).toBe(false);
|
||||
});
|
||||
|
||||
it("reports viewport breakpoint helpers from window.innerWidth", async () => {
|
||||
const platform = await loadPlatform({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15",
|
||||
});
|
||||
|
||||
setInnerWidth(767);
|
||||
expect(platform.isMobileWidth).toBe(true);
|
||||
expect(platform.isTabletWidth).toBe(false);
|
||||
expect(platform.isDesktopWidth).toBe(false);
|
||||
|
||||
setInnerWidth(768);
|
||||
expect(platform.isMobileWidth).toBe(false);
|
||||
expect(platform.isTabletWidth).toBe(true);
|
||||
expect(platform.isDesktopWidth).toBe(false);
|
||||
|
||||
setInnerWidth(1024);
|
||||
expect(platform.isMobileWidth).toBe(false);
|
||||
expect(platform.isTabletWidth).toBe(false);
|
||||
expect(platform.isDesktopWidth).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { createGame as createGameImpl } from "../../../src/core/game/GameImpl";
|
||||
import { GameMapImpl } from "../../../src/core/game/GameMap";
|
||||
import { UserSettings } from "../../../src/core/game/UserSettings";
|
||||
import { GameConfig } from "../../../src/core/Schemas";
|
||||
import { TestConfig } from "../../util/TestConfig";
|
||||
import { TestServerConfig } from "../../util/TestServerConfig";
|
||||
|
||||
@@ -131,13 +132,13 @@ export function createGame(data: TestMapData): Game {
|
||||
);
|
||||
|
||||
const serverConfig = new TestServerConfig();
|
||||
const gameConfig = {
|
||||
const gameConfig: GameConfig = {
|
||||
gameMap: GameMapType.Asia,
|
||||
gameMapSize: GameMapSize.Normal,
|
||||
gameMode: GameMode.FFA,
|
||||
gameType: GameType.Singleplayer,
|
||||
difficulty: Difficulty.Medium,
|
||||
disableNations: false,
|
||||
nations: "default",
|
||||
donateGold: false,
|
||||
donateTroops: false,
|
||||
bots: 0,
|
||||
|
||||
@@ -263,7 +263,7 @@ export async function setupFromPath(
|
||||
gameMode: GameMode.FFA,
|
||||
gameType: GameType.Singleplayer,
|
||||
difficulty: Difficulty.Medium,
|
||||
disableNations: false,
|
||||
nations: "default",
|
||||
donateGold: false,
|
||||
donateTroops: false,
|
||||
bots: 0,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
const STRUCTURE_COUNT = 50000;
|
||||
const LOOKUP_COUNT = 50000;
|
||||
const UPGRADE_LOOKUP_COUNT = 5000;
|
||||
|
||||
interface StructureRenderSample {
|
||||
unitId: number;
|
||||
ownerId: number;
|
||||
level: number;
|
||||
}
|
||||
|
||||
const rendersArray: StructureRenderSample[] = Array.from(
|
||||
{ length: STRUCTURE_COUNT },
|
||||
(_, index) => ({
|
||||
unitId: index + 1,
|
||||
ownerId: (index % 5) + 1,
|
||||
level: (index % 4) + 1,
|
||||
}),
|
||||
);
|
||||
|
||||
const rendersMap = new Map<number, StructureRenderSample>();
|
||||
for (const render of rendersArray) {
|
||||
rendersMap.set(render.unitId, render);
|
||||
}
|
||||
|
||||
const activeLookupIds = Array.from(
|
||||
{ length: LOOKUP_COUNT },
|
||||
(_, index) => ((index * 97) % STRUCTURE_COUNT) + 1,
|
||||
);
|
||||
|
||||
const inactiveLookupIds = Array.from(
|
||||
{ length: LOOKUP_COUNT },
|
||||
(_, index) => ((index * 193) % STRUCTURE_COUNT) + 1,
|
||||
);
|
||||
|
||||
const canUpgradeIds = Array.from(
|
||||
{ length: UPGRADE_LOOKUP_COUNT },
|
||||
(_, index) => ((index * 389) % STRUCTURE_COUNT) + 1,
|
||||
);
|
||||
|
||||
const myOwnerId = 3;
|
||||
const results: string[] = [];
|
||||
|
||||
new Benchmark.Suite()
|
||||
.add("StructureIconsLayer BEFORE (array O(n) lookup/delete)", () => {
|
||||
const localRenders = rendersArray.map((render) => ({ ...render }));
|
||||
|
||||
for (const unitId of activeLookupIds) {
|
||||
const render = localRenders.find((entry) => entry.unitId === unitId);
|
||||
if (render) {
|
||||
render.level = render.level + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const canUpgradeId of canUpgradeIds) {
|
||||
const potentialUpgrade = localRenders.find(
|
||||
(entry) => entry.unitId === canUpgradeId && entry.ownerId === myOwnerId,
|
||||
);
|
||||
if (potentialUpgrade) {
|
||||
potentialUpgrade.level = potentialUpgrade.level + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const unitId of inactiveLookupIds) {
|
||||
const index = localRenders.findIndex((entry) => entry.unitId === unitId);
|
||||
if (index !== -1) {
|
||||
localRenders.splice(index, 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
.add("StructureIconsLayer AFTER (unit-id map O(1) lookup/delete)", () => {
|
||||
const localRenders = new Map<number, StructureRenderSample>();
|
||||
for (const [unitId, render] of rendersMap) {
|
||||
localRenders.set(unitId, { ...render });
|
||||
}
|
||||
|
||||
for (const unitId of activeLookupIds) {
|
||||
const render = localRenders.get(unitId);
|
||||
if (render) {
|
||||
render.level = render.level + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const canUpgradeId of canUpgradeIds) {
|
||||
const potentialUpgrade = localRenders.get(canUpgradeId);
|
||||
if (potentialUpgrade && potentialUpgrade.ownerId === myOwnerId) {
|
||||
potentialUpgrade.level = potentialUpgrade.level + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const unitId of inactiveLookupIds) {
|
||||
localRenders.delete(unitId);
|
||||
}
|
||||
})
|
||||
.on("cycle", (event: Benchmark.Event) => {
|
||||
results.push(String(event.target));
|
||||
})
|
||||
.on("complete", function () {
|
||||
console.log("\n=== StructureIconsLayer Lookup Benchmark Results ===");
|
||||
|
||||
for (const result of results) {
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
const fastest = this.filter("fastest").map("name");
|
||||
console.log(`\nFastest implementation: ${fastest.join(", ")}`);
|
||||
})
|
||||
.run({ async: true });
|
||||
+1
-1
@@ -61,7 +61,7 @@ export async function setup(
|
||||
gameMode: GameMode.FFA,
|
||||
gameType: GameType.Singleplayer,
|
||||
difficulty: Difficulty.Medium,
|
||||
disableNations: false,
|
||||
nations: "default",
|
||||
donateGold: false,
|
||||
donateTroops: false,
|
||||
bots: 0,
|
||||
|
||||
@@ -80,7 +80,12 @@ export class TestServerConfig implements ServerConfig {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
getRandomPublicGameModifiers(): PublicGameModifiers {
|
||||
return { isCompact: false, isRandomSpawn: false, isCrowded: false };
|
||||
return {
|
||||
isCompact: false,
|
||||
isRandomSpawn: false,
|
||||
isCrowded: false,
|
||||
isHardNations: false,
|
||||
};
|
||||
}
|
||||
async supportsCompactMapForTeams(): Promise<boolean> {
|
||||
throw new Error("Method not implemented.");
|
||||
|
||||
Reference in New Issue
Block a user