refactor: convert to npm-workspaces monorepo (engine/core-public/shared/client/server)

Restructure the single src/ tree into an npm-workspaces monorepo under
packages/, rename core -> engine, extract a types-only core-public layer,
and break the pre-existing engine -> client dependency cycle.

Structure (packages/):
  core-public  public API/wire schemas + shared enums (clean leaf)
  shared       framework-agnostic helpers (clean leaf)
  engine       deterministic simulation (was src/core)
  client       rendering/UI (was src/client)
  server       coordination (was src/server)

Dependency DAG: engine -> {core-public, shared}; client -> {core-public,
shared, engine}; server -> {core-public, engine}.

- npm workspaces: root package.json workspaces + per-package package.json;
  tsconfig.base.json holds shared options + path aliases
  (core-public/* shared/* engine/* client/* server/*) resolved uniformly by
  tsc, Vite (resolve.tsconfigPaths), Vitest, and tsx. Lockfile regenerated.
- core-public: moved Schemas/ApiSchemas/CosmeticSchemas/StatsSchemas/
  ClanApiSchemas/WorkerSchemas/Base64/PatternDecoder; extracted the enums
  (GameTypes), GameEvent type, emoji table, and GraphicsOverrides schema.
  Engine re-exports the moved enums/types so existing imports keep working.
- Broke engine -> client cycle:
  - renderNumber/renderTroops -> shared/format
  - NameBoxCalculator moved into engine
  - username validation returns translation key + params; client translates
  - applyStateUpdate moved to client (operates on the render-only PlayerState)
  - Config/UnitGrid/execution-Util/GameImpl now use structural read
    interfaces (engine/game/ReadViews: PlayerLike/UnitLike/GameLike) instead
    of importing client view classes; client imports view classes from a new
    client/view barrel; deleted the engine/game/GameView re-export shim.
- Build/deploy updated: vite.config, index.html, eslint, Dockerfile
  (copies packages/ + tsconfig.base.json before npm ci), .vscode, tests.

Verified: tsc --noEmit clean; 1364 + 65 tests pass; production vite build
succeeds; engine has zero client/server imports; core-public and shared are
dependency leaves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Evan Pelle
2026-06-10 16:51:03 +00:00
parent cb9cab9aca
commit a7f992e9b0
674 changed files with 1637 additions and 1370 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
"--loader",
"ts-node/esm",
"--experimental-specifier-resolution=node",
"${workspaceFolder}/src/server/Server.ts"
"${workspaceFolder}/packages/server/src/Server.ts"
],
"env": {
"GAME_ENV": "dev"
+8 -3
View File
@@ -5,19 +5,22 @@ WORKDIR /usr/src/app
# Build stage - install ALL dependencies and build
FROM base AS build
ENV HUSKY=0
# Copy package files first for better caching
# Copy package files first for better caching. The per-package package.json
# files are required for the workspace install, so packages/ must be present
# before `npm ci`.
COPY package*.json ./
COPY packages ./packages
RUN --mount=type=cache,target=/root/.npm \
npm ci
# Copy only what's needed for build
COPY tsconfig.json ./
COPY tsconfig.base.json ./
COPY vite.config.ts ./
COPY eslint.config.js ./
COPY index.html ./
COPY resources ./resources
COPY proprietary ./proprietary
COPY src ./src
ARG GIT_COMMIT=unknown
ENV GIT_COMMIT="$GIT_COMMIT"
@@ -28,6 +31,7 @@ FROM base AS prod-deps
ENV HUSKY=0
ENV NPM_CONFIG_IGNORE_SCRIPTS=1
COPY package*.json ./
COPY packages ./packages
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
@@ -66,7 +70,8 @@ COPY resources ./resources
# Remove maps because they are not used by the server.
RUN rm -rf ./resources/maps
COPY tsconfig.json ./
COPY src ./src
COPY tsconfig.base.json ./
COPY packages ./packages
ARG GIT_COMMIT=unknown
+6 -1
View File
@@ -13,7 +13,12 @@ const gitignorePath = path.resolve(__dirname, ".gitignore");
/** @type {import('eslint').Linter.Config[]} */
export default [
includeIgnoreFile(gitignorePath),
{ ignores: ["src/server/gatekeeper/**", "tests/pathfinding/playground/**"] },
{
ignores: [
"packages/server/src/gatekeeper/**",
"tests/pathfinding/playground/**",
],
},
{ files: ["**/*.{js,mjs,cjs,ts}"] },
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
pluginJs.configs.recommended,
+1 -1
View File
@@ -440,7 +440,7 @@
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon='{"token": "03d93e6fefb349c28ee69b408fa25a13"}'
></script>
<script type="module" src="/src/client/Main.ts"></script>
<script type="module" src="/packages/client/src/Main.ts"></script>
<footer>
<script
data-cfasync="false"
+46
View File
@@ -5,6 +5,9 @@
"packages": {
"": {
"name": "openfront-client",
"workspaces": [
"packages/*"
],
"dependencies": {
"@lit-labs/virtualizer": "^2.1.1",
"@opentelemetry/api": "^1.9.1",
@@ -1283,6 +1286,22 @@
"node": ">= 8"
}
},
"node_modules/@openfront/client": {
"resolved": "packages/client",
"link": true
},
"node_modules/@openfront/core-public": {
"resolved": "packages/core-public",
"link": true
},
"node_modules/@openfront/engine": {
"resolved": "packages/engine",
"link": true
},
"node_modules/@openfront/server": {
"resolved": "packages/server",
"link": true
},
"node_modules/@opentelemetry/api": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
@@ -10105,6 +10124,33 @@
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"packages/client": {
"name": "@openfront/client",
"version": "0.0.0",
"dependencies": {
"@openfront/core-public": "*",
"@openfront/engine": "*"
}
},
"packages/core-public": {
"name": "@openfront/core-public",
"version": "0.0.0"
},
"packages/engine": {
"name": "@openfront/engine",
"version": "0.0.0",
"dependencies": {
"@openfront/core-public": "*"
}
},
"packages/server": {
"name": "@openfront/server",
"version": "0.0.0",
"dependencies": {
"@openfront/core-public": "*",
"@openfront/engine": "*"
}
}
}
}
+6 -2
View File
@@ -1,11 +1,15 @@
{
"name": "openfront-client",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build-dev": "concurrently \"tsc --noEmit\" \"vite build --mode development\"",
"build-prod": "concurrently --kill-others-on-fail \"tsc --noEmit\" \"vite build\"",
"start:client": "vite",
"start:server": "tsx src/server/Server.ts",
"start:server-dev": "cross-env GAME_ENV=dev NUM_WORKERS=2 TURNSTILE_SITE_KEY=1x00000000000000000000AA API_KEY=WARNING_DEV_API_KEY_DO_NOT_USE_IN_PRODUCTION DOMAIN=localhost GIT_COMMIT=DEV tsx src/server/Server.ts",
"start:server": "tsx packages/server/src/Server.ts",
"start:server-dev": "cross-env GAME_ENV=dev NUM_WORKERS=2 TURNSTILE_SITE_KEY=1x00000000000000000000AA API_KEY=WARNING_DEV_API_KEY_DO_NOT_USE_IN_PRODUCTION DOMAIN=localhost GIT_COMMIT=DEV tsx packages/server/src/Server.ts",
"dev": "cross-env GAME_ENV=dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
"dev:host": "cross-env GAME_ENV=dev VITE_HOST=lan concurrently \"npm run start:client\" \"npm run start:server-dev\"",
"dev:staging": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
+11
View File
@@ -0,0 +1,11 @@
{
"name": "@openfront/client",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "src/Main.ts",
"dependencies": {
"@openfront/core-public": "*",
"@openfront/engine": "*"
}
}
@@ -1,13 +1,13 @@
import { html, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import { ClientEnv } from "client/ClientEnv";
import {
PlayerGame,
PlayerStatsTree,
UserMeResponse,
} from "../core/ApiSchemas";
import { assetUrl } from "../core/AssetUrls";
import { Cosmetics } from "../core/CosmeticSchemas";
} from "core-public/ApiSchemas";
import { assetUrl } from "engine/AssetUrls";
import { Cosmetics } from "core-public/CosmeticSchemas";
import { fetchPlayerById, getUserMe } from "./Api";
import { discordLogin, logOut, sendMagicLink } from "./Auth";
import "./components/baseComponents/stats/DiscordUserHeader";
@@ -1,6 +1,6 @@
import newsItemsFallback from "resources/news.json";
import { z } from "zod";
import type { NewsItem } from "../core/ApiSchemas";
import type { NewsItem } from "core-public/ApiSchemas";
import {
NewsItemSchema,
PlayerProfile,
@@ -9,8 +9,8 @@ import {
RankedLeaderboardResponseSchema,
UserMeResponse,
UserMeResponseSchema,
} from "../core/ApiSchemas";
import { AnalyticsRecord, AnalyticsRecordSchema } from "../core/Schemas";
} from "core-public/ApiSchemas";
import { AnalyticsRecord, AnalyticsRecordSchema } from "core-public/Schemas";
import { getAuthHeader, logOut, userAuth } from "./Auth";
export async function fetchPlayerById(
@@ -1,8 +1,8 @@
import { decodeJwt } from "jose";
import { UserSettings } from "src/core/game/UserSettings";
import { UserSettings } from "engine/game/UserSettings";
import { z } from "zod";
import { TokenPayload, TokenPayloadSchema } from "../core/ApiSchemas";
import { base64urlToUuid } from "../core/Base64";
import { TokenPayload, TokenPayloadSchema } from "core-public/ApiSchemas";
import { base64urlToUuid } from "core-public/Base64";
import { getApiBase, getAudience } from "./Api";
import { generateCryptoRandomUUID } from "./Utils";
@@ -15,7 +15,7 @@ import {
type ClanRequestsResponse,
ClanRequestsResponseSchema,
JoinClanResponseSchema,
} from "../core/ClanApiSchemas";
} from "core-public/ClanApiSchemas";
import { getApiBase, getUserMe } from "./Api";
import { getAuthHeader } from "./Auth";
@@ -36,7 +36,7 @@ export type {
ClanMemberStats,
ClanMemberWL,
ClanRequestsResponse,
} from "../core/ClanApiSchemas";
} from "core-public/ClanApiSchemas";
async function clanFetch(
path: string,
@@ -1,12 +1,12 @@
import { JWK } from "jose";
import { z } from "zod";
import { GameID } from "../core/Schemas";
import { simpleHash } from "../core/Util";
import { GameID } from "core-public/Schemas";
import { simpleHash } from "engine/Util";
import {
GameEnv,
JwksSchema,
parseGameEnv,
} from "../core/configuration/Config";
} from "engine/configuration/Config";
export class ClientEnv {
private static values: ClientEnvValues | null = null;
@@ -1,6 +1,6 @@
import { Config } from "src/core/configuration/Config";
import { translateText } from "../client/Utils";
import { EventBus } from "../core/EventBus";
import { Config } from "engine/configuration/Config";
import { translateText } from "client/Utils";
import { EventBus } from "engine/EventBus";
import {
ClientID,
GameID,
@@ -10,32 +10,32 @@ import {
PlayerCosmeticRefs,
PlayerRecord,
ServerMessage,
} from "../core/Schemas";
import { createPartialGameRecord, findClosestBy, replacer } from "../core/Util";
} from "core-public/Schemas";
import { createPartialGameRecord, findClosestBy, replacer } from "engine/Util";
import {
BuildableUnit,
PlayerType,
Structures,
UnitType,
} from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { GameMapLoader } from "../core/game/GameMapLoader";
} from "engine/game/Game";
import { TileRef } from "engine/game/GameMap";
import { GameMapLoader } from "engine/game/GameMapLoader";
import {
ErrorUpdate,
GameUpdateType,
GameUpdateViewData,
HashUpdate,
WinUpdate,
} from "../core/game/GameUpdates";
import { GameView, PlayerView } from "../core/game/GameView";
import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader";
} from "engine/game/GameUpdates";
import { GameView, PlayerView } from "client/view";
import { loadTerrainMap, TerrainMapData } from "engine/game/TerrainMapLoader";
import {
DARK_MODE_KEY,
GRAPHICS_KEY,
USER_SETTINGS_CHANGED_EVENT,
UserSettings,
} from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
} from "engine/game/UserSettings";
import { WorkerClient } from "engine/worker/WorkerClient";
import { getPersistentID } from "./Auth";
import {
AutoUpgradeEvent,
@@ -1,5 +1,5 @@
import { assetUrl } from "src/core/AssetUrls";
import { UserMeResponse } from "../core/ApiSchemas";
import { assetUrl } from "engine/AssetUrls";
import { UserMeResponse } from "core-public/ApiSchemas";
import {
ColorPalette,
Cosmetics,
@@ -10,13 +10,13 @@ import {
Product,
Skin,
Subscription,
} from "../core/CosmeticSchemas";
} from "core-public/CosmeticSchemas";
import {
PlayerCosmeticRefs,
PlayerCosmetics,
PlayerPattern,
} from "../core/Schemas";
import { UserSettings } from "../core/game/UserSettings";
} from "core-public/Schemas";
import { UserSettings } from "engine/game/UserSettings";
import {
changeSubscriptionTier,
createCheckoutSession,
@@ -1,11 +1,11 @@
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { FlagName } from "../core/Schemas";
import { FlagName } from "core-public/Schemas";
import {
FLAG_KEY,
USER_SETTINGS_CHANGED_EVENT,
UserSettings,
} from "../core/game/UserSettings";
} from "engine/game/UserSettings";
import { resolveFlagUrl } from "./Cosmetics";
import { translateText } from "./Utils";
@@ -1,10 +1,10 @@
import { html } from "lit";
import { customElement, state } from "lit/decorators.js";
import Countries from "resources/countries.json" with { type: "json" };
import { UserMeResponse } from "src/core/ApiSchemas";
import { assetUrl } from "src/core/AssetUrls";
import { Cosmetics, Flag } from "src/core/CosmeticSchemas";
import { UserSettings } from "src/core/game/UserSettings";
import { UserMeResponse } from "core-public/ApiSchemas";
import { assetUrl } from "engine/AssetUrls";
import { Cosmetics, Flag } from "core-public/CosmeticSchemas";
import { UserSettings } from "engine/game/UserSettings";
import { getUserMe } from "./Api";
import {
fetchCosmetics,
@@ -5,7 +5,7 @@ import {
FriendsListResponseSchema,
type SendFriendRequestResponse,
SendFriendRequestResponseSchema,
} from "../core/ApiSchemas";
} from "core-public/ApiSchemas";
import { getApiBase } from "./Api";
import { getAuthHeader } from "./Auth";
@@ -1,7 +1,7 @@
import { html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { GameEndInfo } from "../core/Schemas";
import { GameMapType } from "../core/game/Game";
import { GameEndInfo } from "core-public/Schemas";
import { GameMapType } from "engine/game/Game";
import { fetchGameById } from "./Api";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
import { renderDuration, translateText } from "./Utils";
@@ -1,6 +1,6 @@
import { html, LitElement, nothing, type TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import { ClientEnv } from "client/ClientEnv";
import {
Duos,
GameMapType,
@@ -8,8 +8,8 @@ import {
HumansVsNations,
Quads,
Trios,
} from "../core/game/Game";
import { PublicGameInfo, PublicGames } from "../core/Schemas";
} from "engine/game/Game";
import { PublicGameInfo, PublicGames } from "core-public/Schemas";
import "./components/IOSAddToHomeScreenBanner";
import { crazyGamesSDK } from "./CrazyGamesSDK";
import { HostLobbyModal } from "./HostLobbyModal";
@@ -1,8 +1,8 @@
import { html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { translateText, TUTORIAL_VIDEO_URL } from "../client/Utils";
import { assetUrl } from "../core/AssetUrls";
import { UserSettings } from "../core/game/UserSettings";
import { translateText, TUTORIAL_VIDEO_URL } from "client/Utils";
import { assetUrl } from "engine/AssetUrls";
import { UserSettings } from "engine/game/UserSettings";
import { BaseModal } from "./components/BaseModal";
import "./components/Difficulties";
import { modalHeader } from "./components/ui/ModalHeader";
@@ -1,16 +1,16 @@
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import { translateText } from "../client/Utils";
import { EventBus } from "../core/EventBus";
import { ClientEnv } from "client/ClientEnv";
import { translateText } from "client/Utils";
import { EventBus } from "engine/EventBus";
import {
Difficulty,
GameMapSize,
GameMapType,
GameMode,
UnitType,
} from "../core/game/Game";
import { UserSettings } from "../core/game/UserSettings";
} from "engine/game/Game";
import { UserSettings } from "engine/game/UserSettings";
import {
ClientInfo,
GameConfig,
@@ -18,8 +18,8 @@ import {
LobbyInfoEvent,
TeamCountConfig,
isValidGameID,
} from "../core/Schemas";
import { generateID } from "../core/Util";
} from "core-public/Schemas";
import { generateID } from "engine/Util";
import { getPlayToken } from "./Auth";
import "./components/baseComponents/Modal";
import { BaseModal } from "./components/BaseModal";
@@ -1,7 +1,7 @@
import { EventBus, GameEvent } from "../core/EventBus";
import { PlayerBuildableUnitType, UnitType } from "../core/game/Game";
import { GameView, UnitView } from "../core/game/GameView";
import { UserSettings } from "../core/game/UserSettings";
import { EventBus, GameEvent } from "engine/EventBus";
import { PlayerBuildableUnitType, UnitType } from "engine/game/Game";
import { GameView, UnitView } from "client/view";
import { UserSettings } from "engine/game/UserSettings";
import { Platform } from "./Platform";
import { UIState } from "./UIState";
import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
@@ -1,6 +1,6 @@
import { html, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import { ClientEnv } from "client/ClientEnv";
import {
calculateServerTimeOffset,
getMapName,
@@ -8,9 +8,9 @@ import {
getServerNow,
renderDuration,
translateText,
} from "../client/Utils";
import { assetUrl } from "../core/AssetUrls";
import { EventBus } from "../core/EventBus";
} from "client/Utils";
import { assetUrl } from "engine/AssetUrls";
import { EventBus } from "engine/EventBus";
import {
ClientInfo,
GAME_ID_REGEX,
@@ -19,14 +19,14 @@ import {
GameRecordSchema,
LobbyInfoEvent,
PublicGameInfo,
} from "../core/Schemas";
} from "core-public/Schemas";
import {
Difficulty,
GameMapSize,
GameMode,
GameType,
HumansVsNations,
} from "../core/game/Game";
} from "engine/game/Game";
import { getApiBase } from "./Api";
import { crazyGamesSDK } from "./CrazyGamesSDK";
import { JoinLobbyEvent } from "./Main";
@@ -1,12 +1,12 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { assetUrl } from "../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
import "./LanguageModal";
import { LanguageModal } from "./LanguageModal";
import { formatDebugTranslation } from "./Utils";
import en from "../../resources/lang/en.json";
import metadata from "../../resources/lang/metadata.json";
import en from "resources/lang/en.json";
import metadata from "resources/lang/metadata.json";
type LanguageMetadata = {
code: string;
@@ -1,7 +1,7 @@
import { html, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { translateText } from "../client/Utils";
import { assetUrl } from "../core/AssetUrls";
import { translateText } from "client/Utils";
import { assetUrl } from "engine/AssetUrls";
import { BaseModal } from "./components/BaseModal";
import { modalHeader } from "./components/ui/ModalHeader";
@@ -1,5 +1,5 @@
import { ClientEnv } from "src/client/ClientEnv";
import { PublicGames, PublicLobbyMessageSchema } from "../core/Schemas";
import { ClientEnv } from "client/ClientEnv";
import { PublicGames, PublicLobbyMessageSchema } from "core-public/Schemas";
interface LobbySocketOptions {
reconnectDelay?: number;
@@ -1,5 +1,5 @@
import { GameConfig, GameID, PartialGameRecord } from "../core/Schemas";
import { replacer } from "../core/Util";
import { GameConfig, GameID, PartialGameRecord } from "core-public/Schemas";
import { replacer } from "engine/Util";
export interface LocalStatsData {
[key: GameID]: {
@@ -1,6 +1,6 @@
import { ClientEnv } from "src/client/ClientEnv";
import { ClientEnv } from "client/ClientEnv";
import { z } from "zod";
import { EventBus } from "../core/EventBus";
import { EventBus } from "engine/EventBus";
import {
AllPlayersStats,
ClientID,
@@ -12,12 +12,12 @@ import {
ServerStartGameMessage,
StampedIntent,
Turn,
} from "../core/Schemas";
} from "core-public/Schemas";
import {
createPartialGameRecord,
decompressGameRecord,
replacer,
} from "../core/Util";
} from "engine/Util";
import { getPersistentID } from "./Auth";
import { LobbyConfig } from "./ClientGameRunner";
import {
@@ -1,22 +1,22 @@
import version from "resources/version.txt?raw";
import { ClientEnv } from "src/client/ClientEnv";
import { UserMeResponse } from "../core/ApiSchemas";
import { assetUrl } from "../core/AssetUrls";
import { EventBus } from "../core/EventBus";
import { ClientEnv } from "client/ClientEnv";
import { UserMeResponse } from "core-public/ApiSchemas";
import { assetUrl } from "engine/AssetUrls";
import { EventBus } from "engine/EventBus";
import {
GAME_ID_REGEX,
GameInfo,
GameRecord,
GameStartInfo,
PublicGameInfo,
} from "../core/Schemas";
import { GameEnv } from "../core/configuration/Config";
import { GameType } from "../core/game/Game";
} from "core-public/Schemas";
import { GameEnv } from "engine/configuration/Config";
import { GameType } from "engine/game/Game";
import {
DARK_MODE_KEY,
USER_SETTINGS_CHANGED_EVENT,
UserSettings,
} from "../core/game/UserSettings";
} from "engine/game/UserSettings";
import "./AccountModal";
import { getUserMe, invalidateUserMe } from "./Api";
import { userAuth } from "./Auth";
@@ -1,7 +1,7 @@
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import { UserMeResponse } from "../core/ApiSchemas";
import { ClientEnv } from "client/ClientEnv";
import { UserMeResponse } from "core-public/ApiSchemas";
import { getUserMe, hasLinkedAccount } from "./Api";
import { getPlayToken } from "./Auth";
import { BaseModal } from "./components/BaseModal";
@@ -2,8 +2,8 @@ import { html, LitElement } from "lit";
import { resolveMarkdown } from "lit-markdown";
import { customElement, property, query } from "lit/decorators.js";
import version from "resources/version.txt?raw";
import { translateText } from "../client/Utils";
import { assetUrl } from "../core/AssetUrls";
import { translateText } from "client/Utils";
import { assetUrl } from "engine/AssetUrls";
import { BaseModal } from "./components/BaseModal";
import { modalHeader } from "./components/ui/ModalHeader";
import { normalizeNewsMarkdown } from "./NewsMarkdown";
@@ -3,8 +3,8 @@ import { customElement, property, state } from "lit/decorators.js";
import {
PATTERN_KEY,
USER_SETTINGS_CHANGED_EVENT,
} from "../core/game/UserSettings";
import { PlayerPattern, PlayerSkin } from "../core/Schemas";
} from "engine/game/UserSettings";
import { PlayerPattern, PlayerSkin } from "core-public/Schemas";
import { renderPatternPreview } from "./components/PatternPreview";
import { getPlayerCosmetics } from "./Cosmetics";
import { crazyGamesSDK } from "./CrazyGamesSDK";
@@ -1,8 +1,8 @@
import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "../client/Utils";
import { UserMeResponse } from "../core/ApiSchemas";
import { assetUrl } from "../core/AssetUrls";
import { translateText } from "client/Utils";
import { UserMeResponse } from "core-public/ApiSchemas";
import { assetUrl } from "engine/AssetUrls";
import {
Difficulty,
GameMapSize,
@@ -10,9 +10,9 @@ import {
GameMode,
GameType,
UnitType,
} from "../core/game/Game";
import { TeamCountConfig } from "../core/Schemas";
import { generateID } from "../core/Util";
} from "engine/game/Game";
import { TeamCountConfig } from "core-public/Schemas";
import { generateID } from "engine/Util";
import { hasLinkedAccount } from "./Api";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
@@ -1,9 +1,9 @@
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators.js";
import { UserMeResponse } from "../core/ApiSchemas";
import { Cosmetics } from "../core/CosmeticSchemas";
import { UserSettings } from "../core/game/UserSettings";
import { UserMeResponse } from "core-public/ApiSchemas";
import { Cosmetics } from "core-public/CosmeticSchemas";
import { UserSettings } from "engine/game/UserSettings";
import { BaseModal } from "./components/BaseModal";
import "./components/CosmeticButton";
import "./components/NotLoggedInWarning";
@@ -0,0 +1,6 @@
import { assetUrl } from "engine/AssetUrls";
import { FetchGameMapLoader } from "engine/game/FetchGameMapLoader";
export const terrainMapFileLoader = new FetchGameMapLoader((path) =>
assetUrl(`maps/${path}`),
);
@@ -1,14 +1,14 @@
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../core/ApiSchemas";
import { Cosmetics, Skin } from "../core/CosmeticSchemas";
import { UserMeResponse } from "core-public/ApiSchemas";
import { Cosmetics, Skin } from "core-public/CosmeticSchemas";
import {
PATTERN_KEY,
USER_SETTINGS_CHANGED_EVENT,
UserSettings,
} from "../core/game/UserSettings";
import { PlayerPattern } from "../core/Schemas";
} from "engine/game/UserSettings";
import { PlayerPattern } from "core-public/Schemas";
import { BaseModal } from "./components/BaseModal";
import "./components/CosmeticButton";
import "./components/NotLoggedInWarning";
@@ -1,6 +1,6 @@
import { EventBus, GameEvent } from "../core/EventBus";
import { Cell } from "../core/game/Game";
import { GameView, PlayerView, UnitView } from "../core/game/GameView";
import { EventBus, GameEvent } from "engine/EventBus";
import { Cell } from "engine/game/Game";
import { GameView, PlayerView, UnitView } from "client/view";
import { CenterCameraEvent, DragEvent, ZoomEvent } from "./InputHandler";
export class GoToPlayerEvent implements GameEvent {
@@ -1,6 +1,6 @@
import { ClientEnv } from "src/client/ClientEnv";
import { ClientEnv } from "client/ClientEnv";
import { z } from "zod";
import { EventBus, GameEvent } from "../core/EventBus";
import { EventBus, GameEvent } from "engine/EventBus";
import {
AllPlayers,
GameType,
@@ -8,9 +8,9 @@ import {
PlayerID,
Tick,
UnitType,
} from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { PlayerView } from "../core/game/GameView";
} from "engine/game/Game";
import { TileRef } from "engine/game/GameMap";
import { PlayerView } from "client/view";
import {
AllPlayersStats,
ClientHashMessage,
@@ -25,8 +25,8 @@ import {
ServerMessage,
ServerMessageSchema,
Winner,
} from "../core/Schemas";
import { replacer } from "../core/Util";
} from "core-public/Schemas";
import { replacer } from "engine/Util";
import { getPlayToken } from "./Auth";
import { LobbyConfig } from "./ClientGameRunner";
import { LocalServer } from "./LocalServer";
@@ -1,6 +1,6 @@
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { assetUrl } from "../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
import { translateText } from "./Utils";
import { BaseModal } from "./components/BaseModal";
import { modalHeader } from "./components/ui/ModalHeader";
@@ -1,4 +1,4 @@
import { PlayerBuildableUnitType } from "../core/game/Game";
import { PlayerBuildableUnitType } from "engine/game/Game";
export interface UIState {
attackRatio: number;
@@ -1,7 +1,7 @@
import { html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { formatKeyForDisplay, translateText } from "../client/Utils";
import { getDefaultKeybinds, UserSettings } from "../core/game/UserSettings";
import { formatKeyForDisplay, translateText } from "client/Utils";
import { getDefaultKeybinds, UserSettings } from "engine/game/UserSettings";
import "./components/baseComponents/setting/SettingKeybind";
import { SettingKeybind } from "./components/baseComponents/setting/SettingKeybind";
import "./components/baseComponents/setting/SettingNumber";
@@ -1,7 +1,7 @@
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { generateCryptoRandomUUID, translateText } from "../client/Utils";
import { sanitizeClanTag } from "../core/Util";
import { generateCryptoRandomUUID, translateText } from "client/Utils";
import { sanitizeClanTag } from "engine/Util";
import {
MAX_CLAN_TAG_LENGTH,
MAX_USERNAME_LENGTH,
@@ -9,7 +9,7 @@ import {
MIN_USERNAME_LENGTH,
validateClanTag,
validateUsername,
} from "../core/validations/username";
} from "engine/validations/username";
import { checkClanTagOwnership } from "./ClanApi";
import { crazyGamesSDK } from "./CrazyGamesSDK";
@@ -239,7 +239,9 @@ export class UsernameInput extends LitElement {
const clanTagResult = validateClanTag(this.clanTag);
if (!clanTagResult.isValid) {
this._isValid = false;
this.validationError = clanTagResult.error ?? "";
this.validationError = clanTagResult.error
? translateText(clanTagResult.error, clanTagResult.errorParams)
: "";
this.emitValidity();
return;
}
@@ -251,7 +253,9 @@ export class UsernameInput extends LitElement {
localStorage.setItem(clanTagKey, this.getClanTag() ?? "");
this.validationError = "";
} else {
this.validationError = result.error ?? "";
this.validationError = result.error
? translateText(result.error, result.errorParams)
: "";
}
this.emitValidity();
}
@@ -8,8 +8,8 @@ import {
Quads,
Team,
Trios,
} from "../core/game/Game";
import { GameConfig } from "../core/Schemas";
} from "engine/game/Game";
import { GameConfig } from "core-public/Schemas";
import type { LangSelector } from "./LangSelector";
import { Platform } from "./Platform";
@@ -251,10 +251,6 @@ export function renderDuration(totalSeconds: number): string {
return parts.join(" ");
}
export function renderTroops(troops: number): string {
return renderNumber(troops / 10);
}
export async function copyToClipboard(
text: string,
onSuccess?: () => void,
@@ -274,31 +270,9 @@ export async function copyToClipboard(
}
}
export function renderNumber(
num: number | bigint,
fixedPoints?: number,
): string {
num = Number(num);
num = Math.max(num, 0);
if (num >= 10_000_000) {
const value = Math.floor(num / 100000) / 10;
return value.toFixed(fixedPoints ?? 1) + "M";
} else if (num >= 1_000_000) {
const value = Math.floor(num / 10000) / 100;
return value.toFixed(fixedPoints ?? 2) + "M";
} else if (num >= 100000) {
return Math.floor(num / 1000) + "K";
} else if (num >= 10000) {
const value = Math.floor(num / 100) / 10;
return value.toFixed(fixedPoints ?? 1) + "K";
} else if (num >= 1000) {
const value = Math.floor(num / 10) / 100;
return value.toFixed(fixedPoints ?? 2) + "K";
} else {
return Math.floor(num).toString();
}
}
// Moved to the shared package; re-exported so existing `client/Utils` importers
// keep working.
export { renderNumber, renderTroops } from "shared/format";
export function formatPercentage(value: number): string {
const perc = value * 100;
@@ -1,9 +1,9 @@
import { Colord } from "colord";
import { base64url } from "jose";
import { assetUrl } from "../core/AssetUrls";
import { decodePatternData } from "../core/PatternDecoder";
import { PlayerType } from "../core/game/Game";
import { GameView } from "../core/game/GameView";
import { assetUrl } from "engine/AssetUrls";
import { decodePatternData } from "core-public/PatternDecoder";
import { PlayerType } from "engine/game/Game";
import { GameView } from "client/view";
import { uploadFrameData } from "./render/frame/Upload";
import {
PlayerStatic,
@@ -1,6 +1,6 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { assetUrl } from "../../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
@customElement("cap-icon")
export class CapIcon extends LitElement {
@@ -1,7 +1,7 @@
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ClientEnv } from "src/client/ClientEnv";
import { UserSettings } from "../../core/game/UserSettings";
import { ClientEnv } from "client/ClientEnv";
import { UserSettings } from "engine/game/UserSettings";
import { crazyGamesSDK } from "../CrazyGamesSDK";
import { copyToClipboard, translateText } from "../Utils";
@@ -6,8 +6,8 @@ import {
Pattern,
Skin,
Subscription,
} from "../../core/CosmeticSchemas";
import { PlayerPattern } from "../../core/Schemas";
} from "core-public/CosmeticSchemas";
import { PlayerPattern } from "core-public/Schemas";
import {
PaymentMethod,
ResolvedCosmetic,
@@ -1,6 +1,6 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Product } from "../../core/CosmeticSchemas";
import { Product } from "core-public/CosmeticSchemas";
import "./PurchaseButton";
import { DEFAULT_DOLLAR_LABEL_KEY } from "./PurchaseButton";
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { assetUrl } from "../../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
import { NavNotificationsController } from "./NavNotificationsController";
@customElement("desktop-nav-bar")
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { assetUrl } from "../../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
@customElement("page-footer")
export class Footer extends LitElement {
@@ -1,6 +1,6 @@
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import type { FriendEntry } from "../../core/ApiSchemas";
import type { FriendEntry } from "core-public/ApiSchemas";
import {
acceptFriendRequest,
deleteFriendRequest,
@@ -16,8 +16,8 @@ import {
Quads,
Trios,
UnitType,
} from "../../core/game/Game";
import { TeamCountConfig } from "../../core/Schemas";
} from "engine/game/Game";
import { TeamCountConfig } from "core-public/Schemas";
import { translateText } from "../Utils";
import "./Difficulties";
import "./FluentSlider";
@@ -11,11 +11,11 @@ import {
Quads,
Team,
Trios,
} from "../../core/game/Game";
import { assignTeamsLobbyPreview } from "../../core/game/TeamAssignment";
import { UserSettings } from "../../core/game/UserSettings";
import { ClientInfo, TeamCountConfig } from "../../core/Schemas";
import { createRandomName, formatPlayerDisplayName } from "../../core/Util";
} from "engine/game/Game";
import { assignTeamsLobbyPreview } from "engine/game/TeamAssignment";
import { UserSettings } from "engine/game/UserSettings";
import { ClientInfo, TeamCountConfig } from "core-public/Schemas";
import { createRandomName, formatPlayerDisplayName } from "engine/Util";
import { Theme } from "../theme/Theme";
import { themeProvider } from "../theme/ThemeProvider";
import { getTranslatedPlayerTeamLabel, translateText } from "../Utils";
@@ -1,6 +1,6 @@
import { html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { assetUrl } from "../../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
import { NavNotificationsController } from "./NavNotificationsController";
@customElement("mobile-nav-bar")
@@ -1,7 +1,7 @@
import { LitElement, html, nothing } from "lit";
import { resolveMarkdown } from "lit-markdown";
import { customElement, state } from "lit/decorators.js";
import type { NewsItem } from "../../core/ApiSchemas";
import type { NewsItem } from "core-public/ApiSchemas";
import { getNews } from "../Api";
import { translateText } from "../Utils";
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../../core/ApiSchemas";
import { UserMeResponse } from "core-public/ApiSchemas";
import { hasLinkedAccount } from "../Api";
@customElement("not-logged-in-warning")
@@ -1,9 +1,9 @@
import { Colord } from "colord";
import { base64url } from "jose";
import { html, TemplateResult } from "lit";
import { DefaultPattern } from "../../core/CosmeticSchemas";
import { PatternDecoder } from "../../core/PatternDecoder";
import { PlayerPattern } from "../../core/Schemas";
import { DefaultPattern } from "core-public/CosmeticSchemas";
import { PatternDecoder } from "core-public/PatternDecoder";
import { PlayerPattern } from "core-public/Schemas";
import { translateText } from "../Utils";
export function renderPatternPreview(
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { assetUrl } from "../../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
import "./NewsBox";
@customElement("play-page")
@@ -1,6 +1,6 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { assetUrl } from "../../core/AssetUrls";
import { assetUrl } from "engine/AssetUrls";
const STYLE_ID = "plutonium-icon-styles";
if (!document.getElementById(STYLE_ID)) {
@@ -1,6 +1,6 @@
import { html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Product } from "../../core/CosmeticSchemas";
import { Product } from "core-public/CosmeticSchemas";
import { translateText } from "../Utils";
import "./CapIcon";
import "./PlutoniumIcon";
@@ -1,6 +1,6 @@
import { html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../../core/ApiSchemas";
import { UserMeResponse } from "core-public/ApiSchemas";
import { getUserMe, hasLinkedAccount } from "../Api";
import { userAuth } from "../Auth";
import { translateText } from "../Utils";
@@ -1,7 +1,7 @@
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { UserSubscription } from "../../core/ApiSchemas";
import { Subscription } from "../../core/CosmeticSchemas";
import { UserSubscription } from "core-public/ApiSchemas";
import { Subscription } from "core-public/CosmeticSchemas";
import {
cancelSubscription,
invalidateUserMe,
@@ -1,4 +1,4 @@
import { AnalyticsRecord, PlayerRecord } from "../../../../core/Schemas";
import { AnalyticsRecord, PlayerRecord } from "core-public/Schemas";
import {
GOLD_INDEX_STEAL,
GOLD_INDEX_TRADE,
@@ -8,7 +8,7 @@ import {
PLAYER_INDEX_BOT,
PLAYER_INDEX_HUMAN,
PLAYER_INDEX_NATION,
} from "../../../../core/StatsSchemas";
} from "core-public/StatsSchemas";
export enum RankType {
ConquestHumans = "ConquestHumans",
@@ -4,8 +4,8 @@ import {
GOLD_INDEX_TRADE,
GOLD_INDEX_TRAIN_OTHER,
GOLD_INDEX_TRAIN_SELF,
} from "src/core/StatsSchemas";
import { assetUrl } from "../../../../core/AssetUrls";
} from "core-public/StatsSchemas";
import { assetUrl } from "engine/AssetUrls";
import { renderNumber } from "../../../Utils";
import { PlayerInfo, RankType } from "./GameInfoRanking";
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { formatKeyForDisplay, translateText } from "../../../../client/Utils";
import { formatKeyForDisplay, translateText } from "client/Utils";
@customElement("setting-keybind")
export class SettingKeybind extends LitElement {
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import type { DiscordUser } from "../../../../core/ApiSchemas";
import type { DiscordUser } from "core-public/ApiSchemas";
import { getDiscordAvatarUrl, translateText } from "../../../Utils";
@customElement("discord-user-header")

Some files were not shown because too many files have changed in this diff Show More