mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-03 22:00:57 +00:00
Merge main into nations-nuke
This commit is contained in:
@@ -25,7 +25,7 @@ import { loadTerrainMap } from "../core/game/TerrainMapLoader";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { WorkerClient } from "../core/worker/WorkerClient";
|
||||
import { InputHandler, MouseMoveEvent, MouseUpEvent } from "./InputHandler";
|
||||
import { LocalPersistantStats } from "./LocalPersistantStats";
|
||||
import { endGame, startGame, startTime } from "./LocalPersistantStats";
|
||||
import { getPersistentIDFromCookie } from "./Main";
|
||||
import {
|
||||
SendAttackIntentEvent,
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
import { createCanvas } from "./Utils";
|
||||
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
|
||||
|
||||
export // Is this function needed?
|
||||
function distSortUnitWorld(tile: TileRef, game: GameView) {
|
||||
return (a: Unit | UnitView, b: Unit | UnitView) => {
|
||||
return (
|
||||
@@ -69,10 +70,7 @@ export function joinLobby(
|
||||
);
|
||||
|
||||
const userSettings: UserSettings = new UserSettings();
|
||||
LocalPersistantStats.startGame(
|
||||
lobbyConfig.gameID,
|
||||
lobbyConfig.gameStartInfo?.config,
|
||||
);
|
||||
startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config);
|
||||
|
||||
const transport = new Transport(lobbyConfig, eventBus);
|
||||
|
||||
@@ -158,8 +156,9 @@ export class ClientGameRunner {
|
||||
private hasJoined = false;
|
||||
|
||||
private lastMousePosition: { x: number; y: number } | null = null;
|
||||
private mouseHoverTimer: number | null = null;
|
||||
private readonly HOVER_DELAY = 200;
|
||||
|
||||
private lastMessageTime: number = 0;
|
||||
private connectionCheckInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(
|
||||
private lobby: LobbyConfig,
|
||||
@@ -169,7 +168,9 @@ export class ClientGameRunner {
|
||||
private transport: Transport,
|
||||
private worker: WorkerClient,
|
||||
private gameView: GameView,
|
||||
) {}
|
||||
) {
|
||||
this.lastMessageTime = Date.now();
|
||||
}
|
||||
|
||||
private saveGame(update: WinUpdate) {
|
||||
const players: PlayerRecord[] = [
|
||||
@@ -195,18 +196,25 @@ export class ClientGameRunner {
|
||||
players,
|
||||
// Not saving turns locally
|
||||
[],
|
||||
LocalPersistantStats.startTime(),
|
||||
startTime(),
|
||||
Date.now(),
|
||||
winner,
|
||||
update.winnerType,
|
||||
update.allPlayersStats,
|
||||
);
|
||||
LocalPersistantStats.endGame(record);
|
||||
endGame(record);
|
||||
}
|
||||
|
||||
public start() {
|
||||
consolex.log("starting client game");
|
||||
this.isActive = true;
|
||||
this.lastMessageTime = Date.now();
|
||||
setTimeout(() => {
|
||||
this.connectionCheckInterval = setInterval(
|
||||
() => this.onConnectionCheck(),
|
||||
1000,
|
||||
);
|
||||
}, 20000);
|
||||
this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e));
|
||||
this.eventBus.on(MouseMoveEvent, (e) => this.onMouseMove(e));
|
||||
|
||||
@@ -245,6 +253,7 @@ export class ClientGameRunner {
|
||||
this.transport.joinGame(this.turnsSeen);
|
||||
};
|
||||
const onmessage = (message: ServerMessage) => {
|
||||
this.lastMessageTime = Date.now();
|
||||
if (message.type == "start") {
|
||||
this.hasJoined = true;
|
||||
consolex.log("starting game!");
|
||||
@@ -296,6 +305,10 @@ export class ClientGameRunner {
|
||||
this.worker.cleanup();
|
||||
this.isActive = false;
|
||||
this.transport.leaveGame(saveFullGame);
|
||||
if (this.connectionCheckInterval) {
|
||||
clearInterval(this.connectionCheckInterval);
|
||||
this.connectionCheckInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
private inputEvent(event: MouseUpEvent) {
|
||||
@@ -391,6 +404,20 @@ export class ClientGameRunner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onConnectionCheck() {
|
||||
if (this.transport.isLocal) {
|
||||
return;
|
||||
}
|
||||
const timeSinceLastMessage = Date.now() - this.lastMessageTime;
|
||||
if (timeSinceLastMessage > 5000) {
|
||||
console.log(
|
||||
`No message from server for ${timeSinceLastMessage} ms, reconnecting`,
|
||||
);
|
||||
this.lastMessageTime = Date.now();
|
||||
this.transport.reconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showErrorModal(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
adsbygoogle: any[];
|
||||
adsbygoogle: unknown[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ const isElectron = () => {
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
typeof window.process === "object" &&
|
||||
// @ts-ignore
|
||||
// @ts-expect-error hidden
|
||||
window.process.type === "renderer"
|
||||
) {
|
||||
return true;
|
||||
|
||||
@@ -432,6 +432,7 @@ export class HostLobbyModal extends LitElement {
|
||||
} as GameConfig),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
private getRandomMap(): GameMapType {
|
||||
@@ -460,6 +461,7 @@ export class HostLobbyModal extends LitElement {
|
||||
},
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
private async copyToClipboard() {
|
||||
|
||||
@@ -93,7 +93,7 @@ export class InputHandler {
|
||||
|
||||
private alternateView = false;
|
||||
|
||||
private moveInterval: any = null;
|
||||
private moveInterval: NodeJS.Timeout = null;
|
||||
private activeKeys = new Set<string>();
|
||||
|
||||
private readonly PAN_SPEED = 5;
|
||||
|
||||
+35
-33
@@ -20,10 +20,12 @@ const translations = {
|
||||
es: esTranslations,
|
||||
};
|
||||
|
||||
type Translation = Partial<(typeof translations)[keyof typeof translations]>;
|
||||
|
||||
@customElement("lang-selector")
|
||||
export class LangSelector extends LitElement {
|
||||
@state() public translations: any = {};
|
||||
@state() private defaultTranslations: any = {};
|
||||
@state() public translations: Translation = {};
|
||||
@state() private defaultTranslations = {};
|
||||
@state() private currentLang: string = "en";
|
||||
|
||||
createRenderRoot() {
|
||||
@@ -44,10 +46,10 @@ export class LangSelector extends LitElement {
|
||||
this.translations = await this.loadLanguage(userLang);
|
||||
this.currentLang = userLang;
|
||||
|
||||
this.applyTranslation(this.translations);
|
||||
this.applyTranslation();
|
||||
}
|
||||
|
||||
private async loadLanguage(lang: string): Promise<any> {
|
||||
private async loadLanguage(lang: string): Promise<Translation> {
|
||||
try {
|
||||
const translation = translations[lang as keyof typeof translations];
|
||||
if (!translation) throw new Error(`Language file not found: ${lang}`);
|
||||
@@ -58,7 +60,7 @@ export class LangSelector extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private applyTranslation(translations: any) {
|
||||
private applyTranslation() {
|
||||
const components = [
|
||||
"single-player-modal",
|
||||
"host-lobby-modal",
|
||||
@@ -75,24 +77,14 @@ export class LangSelector extends LitElement {
|
||||
"public-lobby",
|
||||
];
|
||||
|
||||
document.title = translations.main?.title || document.title;
|
||||
const main = this.translations.main;
|
||||
if (main && "title" in main) {
|
||||
document.title = main.title;
|
||||
}
|
||||
|
||||
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
||||
const key = element.getAttribute("data-i18n");
|
||||
const keys = key.split(".");
|
||||
let text = translations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
if (!text && this.defaultTranslations) {
|
||||
let fallback = this.defaultTranslations;
|
||||
for (const k of keys) {
|
||||
fallback = fallback?.[k];
|
||||
if (!fallback) break;
|
||||
}
|
||||
text = fallback;
|
||||
}
|
||||
const text = this.translateText(key);
|
||||
if (text) {
|
||||
element.innerHTML = text;
|
||||
} else {
|
||||
@@ -101,7 +93,7 @@ export class LangSelector extends LitElement {
|
||||
});
|
||||
|
||||
components.forEach((tagName) => {
|
||||
const el = document.querySelector(tagName) as any;
|
||||
const el = document.querySelector(tagName) as LitElement;
|
||||
if (el && typeof el.requestUpdate === "function") {
|
||||
el.requestUpdate();
|
||||
} else {
|
||||
@@ -117,19 +109,13 @@ export class LangSelector extends LitElement {
|
||||
params: Record<string, string | number> = {},
|
||||
): string {
|
||||
const keys = key.split(".");
|
||||
let text: any = this.translations;
|
||||
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
let text = findTranslation(keys, this.translations);
|
||||
if (!text && this.defaultTranslations) {
|
||||
text = findTranslation(keys, this.defaultTranslations);
|
||||
}
|
||||
|
||||
if (!text && this.defaultTranslations) {
|
||||
text = this.defaultTranslations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) return key;
|
||||
}
|
||||
if (text == null || typeof text !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const [param, value] of Object.entries(params)) {
|
||||
@@ -143,7 +129,7 @@ export class LangSelector extends LitElement {
|
||||
localStorage.setItem("lang", lang);
|
||||
this.translations = await this.loadLanguage(lang);
|
||||
this.currentLang = lang;
|
||||
this.applyTranslation(this.translations);
|
||||
this.applyTranslation();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -178,3 +164,19 @@ export class LangSelector extends LitElement {
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function findTranslation(
|
||||
keys: string[],
|
||||
translations: Translation,
|
||||
): string | null {
|
||||
let ptr: unknown = translations;
|
||||
for (const k of keys) {
|
||||
ptr = ptr?.[k];
|
||||
if (!ptr) break;
|
||||
}
|
||||
if (ptr && typeof ptr === "string") {
|
||||
return ptr;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,53 +9,51 @@ export interface LocalStatsData {
|
||||
};
|
||||
}
|
||||
|
||||
export namespace LocalPersistantStats {
|
||||
let _startTime: number;
|
||||
let _startTime: number;
|
||||
|
||||
function getStats(): LocalStatsData {
|
||||
const statsStr = localStorage.getItem("game-records");
|
||||
return statsStr ? JSON.parse(statsStr) : {};
|
||||
}
|
||||
|
||||
function save(stats: LocalStatsData) {
|
||||
// To execute asynchronously
|
||||
setTimeout(
|
||||
() => localStorage.setItem("game-records", JSON.stringify(stats)),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// The user can quit the game anytime so better save the lobby as soon as the
|
||||
// game starts.
|
||||
export function startGame(id: GameID, lobby: GameConfig) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
_startTime = Date.now();
|
||||
const stats = getStats();
|
||||
stats[id] = { lobby };
|
||||
save(stats);
|
||||
}
|
||||
|
||||
export function startTime() {
|
||||
return _startTime;
|
||||
}
|
||||
|
||||
export function endGame(gameRecord: GameRecord) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = getStats();
|
||||
const gameStat = stats[gameRecord.id];
|
||||
|
||||
if (!gameStat) {
|
||||
consolex.log("LocalPersistantStats: game not found");
|
||||
return;
|
||||
}
|
||||
|
||||
gameStat.gameRecord = gameRecord;
|
||||
save(stats);
|
||||
}
|
||||
function getStats(): LocalStatsData {
|
||||
const statsStr = localStorage.getItem("game-records");
|
||||
return statsStr ? JSON.parse(statsStr) : {};
|
||||
}
|
||||
|
||||
function save(stats: LocalStatsData) {
|
||||
// To execute asynchronously
|
||||
setTimeout(
|
||||
() => localStorage.setItem("game-records", JSON.stringify(stats)),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// The user can quit the game anytime so better save the lobby as soon as the
|
||||
// game starts.
|
||||
export function startGame(id: GameID, lobby: GameConfig) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
_startTime = Date.now();
|
||||
const stats = getStats();
|
||||
stats[id] = { lobby };
|
||||
save(stats);
|
||||
}
|
||||
|
||||
export function startTime() {
|
||||
return _startTime;
|
||||
}
|
||||
|
||||
export function endGame(gameRecord: GameRecord) {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = getStats();
|
||||
const gameStat = stats[gameRecord.id];
|
||||
|
||||
if (!gameStat) {
|
||||
consolex.log("LocalPersistantStats: game not found");
|
||||
return;
|
||||
}
|
||||
|
||||
gameStat.gameRecord = gameRecord;
|
||||
save(stats);
|
||||
}
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ class Client {
|
||||
"google-ad",
|
||||
) as NodeListOf<GoogleAdElement>;
|
||||
|
||||
window.addEventListener("beforeunload", (event) => {
|
||||
window.addEventListener("beforeunload", () => {
|
||||
consolex.log("Browser is closing");
|
||||
if (this.gameStop != null) {
|
||||
this.gameStop();
|
||||
@@ -213,7 +213,7 @@ class Client {
|
||||
);
|
||||
}
|
||||
|
||||
private async handleLeaveLobby(event: CustomEvent) {
|
||||
private async handleLeaveLobby(/* event: CustomEvent */) {
|
||||
if (this.gameStop == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+12
-11
@@ -2,6 +2,7 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { translateText } from "../client/Utils";
|
||||
import { consolex } from "../core/Consolex";
|
||||
import { GameMode } from "../core/game/Game";
|
||||
import { GameID, GameInfo } from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
@@ -115,26 +116,26 @@ export class PublicLobby extends LitElement {
|
||||
style="border: 1px solid rgba(255, 255, 255, 0.5)"
|
||||
/>
|
||||
<div
|
||||
class="w-full flex flex-col md:flex-row items-center justify-center gap-4"
|
||||
class="w-full flex flex-col md:flex-row items-center justify-center md:justify-evenly"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="text-md font-medium text-blue-100 mb-4">
|
||||
<!-- ${lobby.gameConfig.gameMap} -->
|
||||
${translateText(
|
||||
`map.${lobby.gameConfig.gameMap.toLowerCase().replace(/\s+/g, "")}`,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.numClients} / ${lobby.gameConfig.maxPlayers}
|
||||
${translateText("public_lobby.waiting")}
|
||||
${lobby.gameConfig.gameMode == GameMode.Team
|
||||
? translateText("game_mode.teams")
|
||||
: translateText("game_mode.ffa")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="min-w-20 text-sm font-medium px-2 py-1 bg-white/10 rounded-xl text-blue-100 text-center"
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="text-md font-medium text-blue-100 mb-2">
|
||||
${lobby.numClients} / ${lobby.gameConfig.maxPlayers}
|
||||
</div>
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${timeDisplay}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,8 +158,7 @@ export class Transport {
|
||||
private onmessage: (msg: ServerMessage) => void;
|
||||
|
||||
private pingInterval: number | null = null;
|
||||
private isLocal: boolean;
|
||||
|
||||
public readonly isLocal: boolean;
|
||||
constructor(
|
||||
private lobbyConfig: LobbyConfig,
|
||||
private eventBus: EventBus,
|
||||
@@ -267,7 +266,7 @@ export class Transport {
|
||||
onmessage: (message: ServerMessage) => void,
|
||||
) {
|
||||
this.startPing();
|
||||
this.maybeKillSocket();
|
||||
this.killExistingSocket();
|
||||
const wsHost = window.location.host;
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const workerPath = this.lobbyConfig.serverConfig.workerPath(
|
||||
@@ -304,11 +303,15 @@ export class Transport {
|
||||
);
|
||||
if (event.code != 1000) {
|
||||
console.log(`reconnecting`);
|
||||
this.connect(onconnect, onmessage);
|
||||
this.reconnect();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public reconnect() {
|
||||
this.connect(this.onconnect, this.onmessage);
|
||||
}
|
||||
|
||||
private onSendLogEvent(event: SendLogEvent) {
|
||||
this.sendMsg(
|
||||
JSON.stringify(
|
||||
@@ -586,7 +589,7 @@ export class Transport {
|
||||
}
|
||||
}
|
||||
|
||||
private maybeKillSocket(): void {
|
||||
private killExistingSocket(): void {
|
||||
if (this.socket == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ export const MapDescription: Record<keyof typeof GameMapType, string> = {
|
||||
Australia: "Australia",
|
||||
Iceland: "Iceland",
|
||||
Japan: "Japan",
|
||||
TwoSeas: "Between Two Seas",
|
||||
KnownWorld: "Known World",
|
||||
};
|
||||
|
||||
@customElement("map-display")
|
||||
|
||||
@@ -40,7 +40,6 @@ export function createRenderer(
|
||||
const startingModal = document.querySelector(
|
||||
"game-starting-modal",
|
||||
) as GameStartingModal;
|
||||
startingModal instanceof GameStartingModal;
|
||||
startingModal.hide();
|
||||
|
||||
// TODO maybe append this to dcoument instead of querying for them?
|
||||
|
||||
@@ -2,16 +2,17 @@ import { LitElement, css, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
const emojiTable: string[][] = [
|
||||
["😀", "😱", "🤡", "😡", "🥺"],
|
||||
["😈", "👏", "🥉", "🥈", "🥇"],
|
||||
["🤙", "🥰", "😇", "😊", "🔥"],
|
||||
["💪", "🏳️", "💀", "😭", "🫡"],
|
||||
["🤦♂️", "👎", "👍", "🥱", "💔"],
|
||||
["😎", "❤️", "💰", "🤝", "🖕"],
|
||||
["💥", "🆘", "🕊️", "➡️", "⬅️"],
|
||||
["↙️", "↖️", "↗️", "⬆️", "↘️"],
|
||||
["⬇️", "❓", "⏳", "☢️", "⚠️"],
|
||||
["😭", "😞", "👋", "🐀", "❌"],
|
||||
["😀", "😊", "🥰", "😇", "😎"],
|
||||
["😞", "🥺", "😭", "😱", "😡"],
|
||||
["😈", "🤡", "🖕", "🥱", "🤦♂️"],
|
||||
["👋", "👏", "🤌", "💪", "🫡"],
|
||||
["👍", "👎", "❓", "🐔", "🐀"],
|
||||
["🤝", "🆘", "🕊️", "🏳️", "⏳"],
|
||||
["🔥", "💥", "💀", "☢️", "⚠️"],
|
||||
["↖️", "⬆️", "↗️", "👑", "🥇"],
|
||||
["⬅️", "🎯", "➡️", "🥈", "🥉"],
|
||||
["↙️", "⬇️", "↘️", "❤️", "💔"],
|
||||
["💰", "⚓", "⛵", "🏡", "🛡️"],
|
||||
];
|
||||
|
||||
@customElement("emoji-table")
|
||||
|
||||
@@ -61,6 +61,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
private events: Event[] = [];
|
||||
@state() private incomingAttacks: AttackUpdate[] = [];
|
||||
@state() private outgoingAttacks: AttackUpdate[] = [];
|
||||
@state() private outgoingLandAttacks: AttackUpdate[] = [];
|
||||
@state() private outgoingBoats: UnitView[] = [];
|
||||
@state() private _hidden: boolean = false;
|
||||
@state() private newEvents: number = 0;
|
||||
@@ -134,6 +135,10 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
.outgoingAttacks()
|
||||
.filter((a) => a.targetID != 0);
|
||||
|
||||
this.outgoingLandAttacks = myPlayer
|
||||
.outgoingAttacks()
|
||||
.filter((a) => a.targetID == 0);
|
||||
|
||||
this.outgoingBoats = myPlayer
|
||||
.units()
|
||||
.filter((u) => u.type() === UnitType.TransportShip);
|
||||
@@ -396,14 +401,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
: event.description;
|
||||
}
|
||||
|
||||
private renderAttacks() {
|
||||
if (
|
||||
this.incomingAttacks.length === 0 &&
|
||||
this.outgoingAttacks.length === 0
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
private renderIncomingAttacks() {
|
||||
return html`
|
||||
${this.incomingAttacks.length > 0
|
||||
? html`
|
||||
@@ -431,6 +429,11 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private renderOutgoingAttacks() {
|
||||
return html`
|
||||
${this.outgoingAttacks.length > 0
|
||||
? html`
|
||||
<tr class="border-t border-gray-700">
|
||||
@@ -467,6 +470,37 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderOutgoingLandAttacks() {
|
||||
return html`
|
||||
${this.outgoingLandAttacks.length > 0
|
||||
? html`
|
||||
<tr class="border-t border-gray-700">
|
||||
<td class="lg:p-3 p-1 text-left text-gray-400">
|
||||
${this.outgoingLandAttacks.map(
|
||||
(landAttack) => html`
|
||||
<button translate="no" class="ml-2">
|
||||
${renderTroops(landAttack.troops)} Wilderness
|
||||
</button>
|
||||
|
||||
${!landAttack.retreating
|
||||
? html`<button
|
||||
${landAttack.retreating ? "disabled" : ""}
|
||||
@click=${() => {
|
||||
this.emitCancelAttackIntent(landAttack.id);
|
||||
}}
|
||||
>
|
||||
❌
|
||||
</button>`
|
||||
: "(retreating...)"}
|
||||
`,
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private renderBoats() {
|
||||
if (this.outgoingBoats.length === 0) {
|
||||
return html``;
|
||||
@@ -497,14 +531,6 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
this.events.length === 0 &&
|
||||
this.incomingAttacks.length === 0 &&
|
||||
this.outgoingAttacks.length === 0 &&
|
||||
this.outgoingBoats.length === 0
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
this.events.sort((a, b) => {
|
||||
const aPrior = a.priority ?? 100000;
|
||||
const bPrior = b.priority ?? 100000;
|
||||
@@ -602,7 +628,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
</tr>
|
||||
`,
|
||||
)}
|
||||
${this.renderAttacks()} ${this.renderBoats()}
|
||||
${this.renderIncomingAttacks()} ${this.renderOutgoingAttacks()}
|
||||
${this.renderOutgoingLandAttacks()} ${this.renderBoats()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -106,6 +106,11 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
this.eventBus.emit(new RefreshGraphicsEvent());
|
||||
}
|
||||
|
||||
private onToggleFocusLockedButtonClick() {
|
||||
this.userSettings.toggleFocusLocked();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onToggleLeftClickOpensMenu() {
|
||||
this.userSettings.toggleLeftClickOpenMenu();
|
||||
}
|
||||
@@ -200,6 +205,15 @@ export class OptionsMenu extends LitElement implements Layer {
|
||||
? "Opens menu"
|
||||
: "Attack"),
|
||||
})}
|
||||
${button({
|
||||
onClick: this.onToggleFocusLockedButtonClick,
|
||||
title: "Lock Focus",
|
||||
children:
|
||||
"🗺: " +
|
||||
(this.userSettings.focusLocked()
|
||||
? "Focus locked"
|
||||
: "Hover focus"),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,34 +1,63 @@
|
||||
import { blue, red } from "../../../core/configuration/Colors";
|
||||
import { GameMode, TeamName } from "../../../core/game/Game";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
export class SpawnTimer implements Layer {
|
||||
private ratio = 0;
|
||||
private leftColor = "rgba(0, 128, 255, 0.7)";
|
||||
private rightColor = "rgba(0, 0, 0, 0.5)";
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
private transformHandler: TransformHandler,
|
||||
) {}
|
||||
|
||||
init() {}
|
||||
tick() {}
|
||||
|
||||
tick() {
|
||||
if (this.game.inSpawnPhase()) {
|
||||
this.ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns();
|
||||
return;
|
||||
}
|
||||
if (this.game.config().gameConfig().gameMode != GameMode.Team) {
|
||||
this.ratio = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const numBlueTiles = this.game
|
||||
.players()
|
||||
.filter((p) => p.teamName() == TeamName.Blue)
|
||||
.reduce((acc, p) => acc + p.numTilesOwned(), 0);
|
||||
|
||||
const numRedTiles = this.game
|
||||
.players()
|
||||
.filter((p) => p.teamName() == TeamName.Red)
|
||||
.reduce((acc, p) => acc + p.numTilesOwned(), 0);
|
||||
|
||||
this.ratio = numBlueTiles / (numBlueTiles + numRedTiles);
|
||||
this.leftColor = blue.toRgbString();
|
||||
this.rightColor = red.toRgbString();
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
renderLayer(context: CanvasRenderingContext2D) {
|
||||
if (!this.game.inSpawnPhase()) {
|
||||
if (this.ratio == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const barHeight = 15;
|
||||
const barHeight = 10;
|
||||
const barBackgroundWidth = this.transformHandler.width();
|
||||
|
||||
const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns();
|
||||
|
||||
// Draw bar background
|
||||
context.fillStyle = "rgba(0, 0, 0, 0.5)";
|
||||
context.fillStyle = this.rightColor;
|
||||
context.fillRect(0, 0, barBackgroundWidth, barHeight);
|
||||
|
||||
context.fillStyle = "rgba(0, 128, 255, 0.7)";
|
||||
context.fillRect(0, 0, barBackgroundWidth * ratio, barHeight);
|
||||
context.fillStyle = this.leftColor;
|
||||
context.fillRect(0, 0, barBackgroundWidth * this.ratio, barHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export class StructureLayer implements Layer {
|
||||
|
||||
private handleUnitRendering(unit: UnitView) {
|
||||
const unitType = unit.constructionType() ?? unit.type();
|
||||
let iconType = unitType;
|
||||
const iconType = unitType;
|
||||
if (!this.isUnitTypeSupported(unitType)) return;
|
||||
|
||||
const config = this.unitConfigs[unitType];
|
||||
|
||||
@@ -12,11 +12,12 @@ import { Layer } from "./Layer";
|
||||
// Add this at the top of your file
|
||||
declare global {
|
||||
interface Window {
|
||||
adsbygoogle: any[];
|
||||
adsbygoogle: unknown[];
|
||||
}
|
||||
}
|
||||
|
||||
// Add this at the top of your file
|
||||
declare let adsbygoogle: any[];
|
||||
declare let adsbygoogle: unknown[];
|
||||
|
||||
@customElement("win-modal")
|
||||
export class WinModal extends LitElement implements Layer {
|
||||
@@ -257,7 +258,7 @@ export class WinModal extends LitElement implements Layer {
|
||||
});
|
||||
}
|
||||
|
||||
renderLayer(context: CanvasRenderingContext2D) {}
|
||||
renderLayer(/* context: CanvasRenderingContext2D */) {}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false;
|
||||
|
||||
@@ -327,6 +327,11 @@ label.option-card:hover {
|
||||
center / cover;
|
||||
}
|
||||
|
||||
#helpModal .sam-launcher-icon {
|
||||
mask: url("../../resources/images/SamLauncherIconWhite.svg") no-repeat
|
||||
center / cover;
|
||||
}
|
||||
|
||||
#helpModal .atom-bomb-icon {
|
||||
mask: url("../../resources/images/NukeIconWhite.svg") no-repeat center / cover;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import europe from "../../../resources/maps/EuropeThumb.webp";
|
||||
import gatewayToTheAtlantic from "../../../resources/maps/GatewayToTheAtlanticThumb.webp";
|
||||
import iceland from "../../../resources/maps/IcelandThumb.webp";
|
||||
import japan from "../../../resources/maps/JapanThumb.webp";
|
||||
import knownworld from "../../../resources/maps/KnownWorldThumb.webp";
|
||||
import mars from "../../../resources/maps/MarsThumb.webp";
|
||||
import mena from "../../../resources/maps/MenaThumb.webp";
|
||||
import northAmerica from "../../../resources/maps/NorthAmericaThumb.webp";
|
||||
import oceania from "../../../resources/maps/OceaniaThumb.webp";
|
||||
import pangaea from "../../../resources/maps/PangaeaThumb.webp";
|
||||
import southAmerica from "../../../resources/maps/SouthAmericaThumb.webp";
|
||||
import twoSeas from "../../../resources/maps/TwoSeasThumb.webp";
|
||||
import world from "../../../resources/maps/WorldMapThumb.webp";
|
||||
|
||||
import { GameMapType } from "../../core/game/Game";
|
||||
@@ -51,6 +53,10 @@ export function getMapsImage(map: GameMapType): string {
|
||||
return iceland;
|
||||
case GameMapType.Japan:
|
||||
return japan;
|
||||
case GameMapType.TwoSeas:
|
||||
return twoSeas;
|
||||
case GameMapType.KnownWorld:
|
||||
return knownworld;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
Difficulty,
|
||||
Game,
|
||||
GameMapType,
|
||||
GameMode,
|
||||
GameType,
|
||||
Gold,
|
||||
Player,
|
||||
@@ -58,15 +59,54 @@ export abstract class DefaultServerConfig implements ServerConfig {
|
||||
return 60 * 1000;
|
||||
}
|
||||
lobbyMaxPlayers(map: GameMapType): number {
|
||||
if (map == GameMapType.World) {
|
||||
return Math.random() < 0.3 ? 150 : 60;
|
||||
}
|
||||
// Maps with ~4 mil pixels
|
||||
if (
|
||||
[GameMapType.Mars, GameMapType.Africa, GameMapType.BlackSea].includes(map)
|
||||
[
|
||||
GameMapType.GatewayToTheAtlantic,
|
||||
GameMapType.SouthAmerica,
|
||||
GameMapType.NorthAmerica,
|
||||
GameMapType.Africa,
|
||||
GameMapType.Europe,
|
||||
].includes(map)
|
||||
) {
|
||||
return Math.random() < 0.3 ? 70 : 50;
|
||||
return Math.random() < 0.2 ? 150 : 70;
|
||||
}
|
||||
return Math.random() < 0.3 ? 60 : 40;
|
||||
// Maps with ~2.5 - ~3.5 mil pixels
|
||||
if (
|
||||
[
|
||||
GameMapType.Australia,
|
||||
GameMapType.Iceland,
|
||||
GameMapType.Britannia,
|
||||
GameMapType.Asia,
|
||||
].includes(map)
|
||||
) {
|
||||
return Math.random() < 0.2 ? 100 : 50;
|
||||
}
|
||||
// Maps with ~2 mil pixels
|
||||
if (
|
||||
[
|
||||
GameMapType.Mena,
|
||||
GameMapType.Mars,
|
||||
GameMapType.Oceania,
|
||||
GameMapType.Japan, // Japan at this level because its 2/3 water
|
||||
].includes(map)
|
||||
) {
|
||||
return Math.random() < 0.2 ? 70 : 40;
|
||||
}
|
||||
// Maps smaller than ~2 mil pixels
|
||||
if (
|
||||
[GameMapType.TwoSeas, GameMapType.BlackSea, GameMapType.Pangaea].includes(
|
||||
map,
|
||||
)
|
||||
) {
|
||||
return Math.random() < 0.2 ? 60 : 35;
|
||||
}
|
||||
// world belongs with the ~2 mils, but these amounts never made sense so I assume the insanity is intended.
|
||||
if (map == GameMapType.World) {
|
||||
return Math.random() < 0.2 ? 150 : 60;
|
||||
}
|
||||
// default return for non specified map
|
||||
return Math.random() < 0.2 ? 85 : 45;
|
||||
}
|
||||
workerIndex(gameID: GameID): number {
|
||||
return simpleHash(gameID) % this.numWorkers();
|
||||
@@ -337,6 +377,9 @@ export class DefaultConfig implements Config {
|
||||
return 600 * 10; // 10 minutes.
|
||||
}
|
||||
percentageTilesOwnedToWin(): number {
|
||||
if (this._gameConfig.gameMode == GameMode.Team) {
|
||||
return 95;
|
||||
}
|
||||
return 80;
|
||||
}
|
||||
boatMaxNumber(): number {
|
||||
|
||||
@@ -190,7 +190,11 @@ export class AttackExecution implements Execution {
|
||||
|
||||
tick(ticks: number) {
|
||||
if (this.attack.retreated()) {
|
||||
this.retreat(malusForRetreat);
|
||||
if (this.attack.target().isPlayer()) {
|
||||
this.retreat(malusForRetreat);
|
||||
} else {
|
||||
this.retreat();
|
||||
}
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ export enum GameMapType {
|
||||
Australia = "Australia",
|
||||
Iceland = "Iceland",
|
||||
Japan = "Japan",
|
||||
TwoSeas = "Between Two Seas",
|
||||
KnownWorld = "Known World",
|
||||
}
|
||||
|
||||
export enum GameType {
|
||||
@@ -135,8 +137,8 @@ export class Cell {
|
||||
private strRepr: string;
|
||||
|
||||
constructor(
|
||||
public readonly x,
|
||||
public readonly y,
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
) {
|
||||
this.strRepr = `Cell[${this.x},${this.y}]`;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export class GameImpl implements Game {
|
||||
}
|
||||
|
||||
addUpdate(update: GameUpdate) {
|
||||
(this.updates[update.type] as any[]).push(update);
|
||||
(this.updates[update.type] as GameUpdate[]).push(update);
|
||||
}
|
||||
|
||||
nextUnitID(): number {
|
||||
@@ -383,7 +383,7 @@ export class GameImpl implements Game {
|
||||
}
|
||||
|
||||
playerByClientID(id: ClientID): Player | null {
|
||||
for (const [pID, player] of this._players) {
|
||||
for (const [, player] of this._players) {
|
||||
if (player.clientID() == id) {
|
||||
return player;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ import {
|
||||
} from "./GameUpdates";
|
||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||
import { UnitGrid } from "./UnitGrid";
|
||||
import { UserSettings } from "./UserSettings";
|
||||
|
||||
const userSettings: UserSettings = new UserSettings();
|
||||
|
||||
export class UnitView {
|
||||
public _wasUpdated = true;
|
||||
@@ -384,6 +387,10 @@ export class GameView implements GameMap {
|
||||
throw Error(`player id ${id} not found`);
|
||||
}
|
||||
|
||||
players(): PlayerView[] {
|
||||
return Array.from(this._players.values());
|
||||
}
|
||||
|
||||
playerBySmallID(id: number): PlayerView | TerraNullius {
|
||||
if (id == 0) {
|
||||
return new TerraNulliusImpl();
|
||||
@@ -542,6 +549,7 @@ export class GameView implements GameMap {
|
||||
}
|
||||
|
||||
focusedPlayer(): PlayerView | null {
|
||||
if (userSettings.focusLocked()) return this.myPlayer();
|
||||
return this._focusedPlayer;
|
||||
}
|
||||
setFocusedPlayer(player: PlayerView | null): void {
|
||||
|
||||
@@ -92,6 +92,7 @@ export class PlayerImpl implements Player {
|
||||
|
||||
public _incomingAttacks: Attack[] = [];
|
||||
public _outgoingAttacks: Attack[] = [];
|
||||
public _outgoingLandAttacks: Attack[] = [];
|
||||
|
||||
constructor(
|
||||
private mg: GameImpl,
|
||||
@@ -1003,7 +1004,7 @@ export class PlayerImpl implements Player {
|
||||
// It's a probability list, so if an element appears twice it's because it's
|
||||
// twice more likely to be picked later.
|
||||
tradingPorts(port: Unit): Unit[] {
|
||||
let ports = this.mg
|
||||
const ports = this.mg
|
||||
.players()
|
||||
.filter((p) => p != port.owner() && p.canTrade(port.owner()))
|
||||
.flatMap((p) => p.units(UnitType.Port))
|
||||
|
||||
@@ -39,6 +39,8 @@ const MAP_FILE_NAMES: Record<GameMapType, string> = {
|
||||
[GameMapType.Australia]: "Australia",
|
||||
[GameMapType.Iceland]: "Iceland",
|
||||
[GameMapType.Japan]: "Japan",
|
||||
[GameMapType.TwoSeas]: "TwoSeas",
|
||||
[GameMapType.KnownWorld]: "KnownWorld",
|
||||
};
|
||||
|
||||
class GameMapLoader {
|
||||
|
||||
@@ -24,10 +24,18 @@ export class UserSettings {
|
||||
return this.get("settings.leftClickOpensMenu", false);
|
||||
}
|
||||
|
||||
focusLocked() {
|
||||
return this.get("settings.focusLocked", false);
|
||||
}
|
||||
|
||||
toggleLeftClickOpenMenu() {
|
||||
this.set("settings.leftClickOpensMenu", !this.leftClickOpensMenu());
|
||||
}
|
||||
|
||||
toggleFocusLocked() {
|
||||
this.set("settings.focusLocked", !this.focusLocked());
|
||||
}
|
||||
|
||||
toggleEmojis() {
|
||||
this.set("settings.emojis", !this.emojis());
|
||||
}
|
||||
|
||||
Vendored
-4
@@ -32,7 +32,3 @@ declare module "*.html" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
declare module "*.json" {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
|
||||
@@ -120,19 +120,49 @@ function processShore(map: Terrain[][]): Coord[] {
|
||||
}
|
||||
|
||||
function processDistToLand(shorelineWaters: Coord[], map: Terrain[][]) {
|
||||
console.log("Setting Water tiles magnitude = distance from land");
|
||||
for (let x = 0; x < map.length; x++) {
|
||||
for (let y = 0; y < map[0].length; y++) {
|
||||
const tile = map[x][y];
|
||||
if (tile.type == TerrainType.Water) {
|
||||
if (shorelineWaters.some((coord) => coord.x == x && coord.y == y)) {
|
||||
tile.magnitude = 0;
|
||||
} else {
|
||||
const dist = shorelineWaters.map(
|
||||
(coord) => Math.abs(x - coord.x) + Math.abs(y - coord.y),
|
||||
);
|
||||
tile.magnitude = Math.min(...dist);
|
||||
}
|
||||
console.log(
|
||||
"Setting Water tiles magnitude = Manhattan distance from nearest land",
|
||||
);
|
||||
|
||||
const width = map.length;
|
||||
const height = map[0].length;
|
||||
|
||||
const visited = Array.from({ length: width }, () =>
|
||||
Array(height).fill(false),
|
||||
);
|
||||
const queue: { x: number; y: number; dist: number }[] = [];
|
||||
|
||||
for (const { x, y } of shorelineWaters) {
|
||||
queue.push({ x, y, dist: 0 });
|
||||
visited[x][y] = true;
|
||||
map[x][y].magnitude = 0;
|
||||
}
|
||||
|
||||
const directions = [
|
||||
{ dx: 0, dy: 1 },
|
||||
{ dx: 1, dy: 0 },
|
||||
{ dx: 0, dy: -1 },
|
||||
{ dx: -1, dy: 0 },
|
||||
];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const { x, y, dist } = queue.shift()!;
|
||||
|
||||
for (const { dx, dy } of directions) {
|
||||
const nx = x + dx;
|
||||
const ny = y + dy;
|
||||
|
||||
if (
|
||||
nx >= 0 &&
|
||||
ny >= 0 &&
|
||||
nx < width &&
|
||||
ny < height &&
|
||||
!visited[nx][ny] &&
|
||||
map[nx][ny].type === TerrainType.Water
|
||||
) {
|
||||
visited[nx][ny] = true;
|
||||
map[nx][ny].magnitude = dist + 1;
|
||||
queue.push({ x: nx, y: ny, dist: dist + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ const maps = [
|
||||
"Australia",
|
||||
"Pangaea",
|
||||
"Iceland",
|
||||
"TwoSeas",
|
||||
"Japan",
|
||||
"KnownWorld",
|
||||
];
|
||||
|
||||
const removeSmall = true;
|
||||
|
||||
@@ -456,7 +456,7 @@ export class GameServer {
|
||||
|
||||
const lastHashTurn = this.turns.length - 10;
|
||||
|
||||
let { mostCommonHash, outOfSyncClients } =
|
||||
const { mostCommonHash, outOfSyncClients } =
|
||||
this.findOutOfSyncClients(lastHashTurn);
|
||||
|
||||
if (outOfSyncClients.length == 0) {
|
||||
@@ -464,11 +464,6 @@ export class GameServer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (outOfSyncClients.length >= Math.floor(this.activeClients.length / 2)) {
|
||||
// If half clients out of sync assume all are out of sync.
|
||||
outOfSyncClients = this.activeClients;
|
||||
}
|
||||
|
||||
const serverDesync = ServerDesyncSchema.safeParse({
|
||||
type: "desync",
|
||||
turn: lastHashTurn,
|
||||
@@ -519,7 +514,7 @@ export class GameServer {
|
||||
}
|
||||
|
||||
// Create a list of clients whose hash doesn't match the most common one
|
||||
const outOfSyncClients: Client[] = [];
|
||||
let outOfSyncClients: Client[] = [];
|
||||
|
||||
for (const client of this.activeClients) {
|
||||
if (client.hashes.has(turnNumber)) {
|
||||
@@ -530,6 +525,11 @@ export class GameServer {
|
||||
}
|
||||
}
|
||||
|
||||
// If half clients out of sync assume all are out of sync.
|
||||
if (outOfSyncClients.length >= Math.floor(this.activeClients.length / 2)) {
|
||||
outOfSyncClients = this.activeClients;
|
||||
}
|
||||
|
||||
return {
|
||||
mostCommonHash,
|
||||
outOfSyncClients,
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface Gatekeeper {
|
||||
// The wrapper for request handlers with optional rate limiting
|
||||
httpHandler: (
|
||||
limiterType: LimiterType,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,
|
||||
) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
|
||||
// The wrapper for WebSocket message handlers with rate limiting
|
||||
@@ -67,8 +67,8 @@ async function getGatekeeper(): Promise<Gatekeeper> {
|
||||
// Use dynamic import for ES modules
|
||||
// Using a type assertion to avoid TypeScript errors for optional modules
|
||||
const module = await import(
|
||||
"./gatekeeper/RealGatekeeper.js" as any
|
||||
).catch(() => import("./gatekeeper/RealGatekeeper.js" as any));
|
||||
"./gatekeeper/RealGatekeeper.js" as string
|
||||
).catch(() => import("./gatekeeper/RealGatekeeper.js" as string));
|
||||
|
||||
if (!module || !module.RealGatekeeper) {
|
||||
console.log(
|
||||
@@ -95,7 +95,7 @@ export class GatekeeperWrapper implements Gatekeeper {
|
||||
|
||||
httpHandler(
|
||||
limiterType: LimiterType,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,
|
||||
) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
@@ -129,7 +129,7 @@ export class NoOpGatekeeper implements Gatekeeper {
|
||||
// Simple pass-through with no rate limiting
|
||||
httpHandler(
|
||||
limiterType: LimiterType,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,
|
||||
) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,7 @@ import http from "http";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
||||
import { Difficulty, GameMapType, GameType } from "../core/game/Game";
|
||||
import { Difficulty, GameMapType, GameMode, GameType } from "../core/game/Game";
|
||||
import { PseudoRandom } from "../core/PseudoRandom";
|
||||
import { GameConfig, GameInfo } from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
@@ -237,6 +237,7 @@ async function schedulePublicGame() {
|
||||
instantBuild: false,
|
||||
disableNPCs: false,
|
||||
disableNukes: false,
|
||||
gameMode: Math.random() < 0.7 ? GameMode.FFA : GameMode.Team,
|
||||
bots: 400,
|
||||
} as GameConfig;
|
||||
|
||||
@@ -280,20 +281,22 @@ function getNextMap(): GameMapType {
|
||||
}
|
||||
|
||||
const frequency = {
|
||||
World: 2,
|
||||
World: 1,
|
||||
Europe: 3,
|
||||
Mena: 2,
|
||||
NorthAmerica: 3,
|
||||
BlackSea: 2,
|
||||
Pangaea: 2,
|
||||
NorthAmerica: 2,
|
||||
BlackSea: 1,
|
||||
Pangaea: 1,
|
||||
Africa: 2,
|
||||
Asia: 1,
|
||||
Mars: 1,
|
||||
Britannia: 2,
|
||||
GatewayToTheAtlantic: 3,
|
||||
GatewayToTheAtlantic: 2,
|
||||
Australia: 2,
|
||||
Iceland: 2,
|
||||
SouthAmerica: 3,
|
||||
Japan: 3,
|
||||
TwoSeas: 3,
|
||||
};
|
||||
|
||||
Object.keys(GameMapType).forEach((key) => {
|
||||
|
||||
@@ -50,7 +50,7 @@ export function setupMetricsServer() {
|
||||
} else if (line.trim() && !line.startsWith("#")) {
|
||||
// Add worker label to each metric line and collect for later
|
||||
const processedLine = line.replace(
|
||||
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9\.e+-]+.*)/,
|
||||
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9.e+-]+.*)/,
|
||||
(match, metricName, existingLabels, valueAndRest) => {
|
||||
if (existingLabels) {
|
||||
return `${metricName}{${existingLabels},worker="master"}${valueAndRest}`;
|
||||
@@ -108,7 +108,7 @@ export function setupMetricsServer() {
|
||||
// Process and collect actual metric values
|
||||
try {
|
||||
const processedLine = line.replace(
|
||||
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9\.e+-]+.*)/,
|
||||
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9.e+-]+.*)/,
|
||||
(match, metricName, existingLabels, valueAndRest) => {
|
||||
if (existingLabels) {
|
||||
return `${metricName}{${existingLabels},worker="worker-${i}"}${valueAndRest}`;
|
||||
@@ -122,7 +122,7 @@ export function setupMetricsServer() {
|
||||
if (processedLine !== line) {
|
||||
allMetricValues.push(processedLine);
|
||||
} else if (
|
||||
line.match(/^[a-z][a-z0-9_]*(?:{[^}]*})?\s+[0-9\.e+-]+.*/)
|
||||
line.match(/^[a-z][a-z0-9_]*(?:{[^}]*})?\s+[0-9.e+-]+.*/)
|
||||
) {
|
||||
// This looks like a metric line but didn't match our regex, try a more general approach
|
||||
const parts = line.split(/({|\s+)/);
|
||||
|
||||
@@ -3,7 +3,13 @@ import { ClientID, GameID, LogSeverity } from "../core/Schemas";
|
||||
export interface slogMsg {
|
||||
logKey: string;
|
||||
msg: string;
|
||||
data?: any;
|
||||
data?: {
|
||||
stack?: unknown;
|
||||
clientID?: unknown;
|
||||
clientIP?: unknown;
|
||||
gameID?: unknown;
|
||||
isRejoin?: unknown;
|
||||
};
|
||||
severity?: LogSeverity;
|
||||
gameID?: GameID;
|
||||
clientID?: ClientID;
|
||||
|
||||
Reference in New Issue
Block a user