mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-28 19:04:19 +00:00
Merge branch 'main' into meta3AI. remove underline at 90.
This commit is contained in:
@@ -1,130 +1,108 @@
|
||||
export class MultiTabDetector {
|
||||
private focusChanges: number[] = [];
|
||||
private readonly maxFocusChanges: number = 10;
|
||||
private readonly timeWindow: number = 60_000;
|
||||
private readonly punishmentDelays: number[] = [
|
||||
2_000, 3_000, 5_000, 10_000, 30_000, 60_000,
|
||||
];
|
||||
private lastFocusChangeTime: number = 0;
|
||||
private isPunished: boolean = false;
|
||||
private isMonitoring: boolean = false;
|
||||
private startPenaltyCallback?: (duration: number) => void;
|
||||
private readonly tabId = `${Date.now()}-${Math.random()}`;
|
||||
private readonly lockKey = "multi-tab-lock";
|
||||
private readonly heartbeatIntervalMs = 1_000;
|
||||
private readonly staleThresholdMs = 3_000;
|
||||
|
||||
private numPunishmentsGiven = 0;
|
||||
private heartbeatTimer: number | null = null;
|
||||
private isPunished = false;
|
||||
private punishmentCount = 0;
|
||||
private startPenaltyCallback: (duration: number) => void = () => {};
|
||||
|
||||
constructor() {
|
||||
window.addEventListener("storage", this.onStorageEvent.bind(this));
|
||||
window.addEventListener("beforeunload", this.onBeforeUnload.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring for multi-tabbing behavior
|
||||
*
|
||||
* @param startPenalty Callback function when punishment starts
|
||||
*/
|
||||
public startMonitoring(startPenalty: (duration: number) => void): void {
|
||||
if (this.isMonitoring) return;
|
||||
|
||||
this.isMonitoring = true;
|
||||
this.startPenaltyCallback = startPenalty;
|
||||
|
||||
// Event listeners for window focus/blur
|
||||
window.addEventListener("blur", this.handleFocusChange.bind(this));
|
||||
window.addEventListener("focus", this.handleFocusChange.bind(this));
|
||||
|
||||
// Also track visibility changes for tab switching
|
||||
document.addEventListener(
|
||||
"visibilitychange",
|
||||
this.handleVisibilityChange.bind(this),
|
||||
this.writeLock();
|
||||
this.heartbeatTimer = window.setInterval(
|
||||
() => this.heartbeat(),
|
||||
this.heartbeatIntervalMs,
|
||||
);
|
||||
}
|
||||
|
||||
public stopMonitoring(): void {
|
||||
if (!this.isMonitoring) return;
|
||||
|
||||
this.isMonitoring = false;
|
||||
|
||||
// Remove event listeners
|
||||
window.removeEventListener("blur", this.handleFocusChange.bind(this));
|
||||
window.removeEventListener("focus", this.handleFocusChange.bind(this));
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
this.handleVisibilityChange.bind(this),
|
||||
);
|
||||
|
||||
// Clear data
|
||||
this.focusChanges = [];
|
||||
this.isPunished = false;
|
||||
}
|
||||
|
||||
private handleFocusChange(): void {
|
||||
const currentTime = Date.now();
|
||||
|
||||
this.recordFocusChange(currentTime);
|
||||
|
||||
// Check for multi-tabbing when focus is gained
|
||||
if (document.hasFocus() && !this.isPunished) {
|
||||
this.checkForMultiTabbing(currentTime);
|
||||
if (this.heartbeatTimer !== null) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private handleVisibilityChange(): void {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// Record and check regardless of current focus state
|
||||
this.recordFocusChange(currentTime);
|
||||
|
||||
// Only check when tab becomes visible
|
||||
if (document.visibilityState === "visible" && !this.isPunished) {
|
||||
this.checkForMultiTabbing(currentTime);
|
||||
const lock = this.readLock();
|
||||
if (lock?.owner === this.tabId) {
|
||||
localStorage.removeItem(this.lockKey);
|
||||
}
|
||||
window.removeEventListener("storage", this.onStorageEvent.bind(this));
|
||||
window.removeEventListener("beforeunload", this.onBeforeUnload.bind(this));
|
||||
}
|
||||
|
||||
private recordFocusChange(timestamp: number): void {
|
||||
if (Math.abs(this.lastFocusChangeTime - timestamp) < 100) {
|
||||
// Don't count multiple triggers at same time
|
||||
private heartbeat(): void {
|
||||
const now = Date.now();
|
||||
const lock = this.readLock();
|
||||
|
||||
if (
|
||||
!lock ||
|
||||
lock.owner === this.tabId ||
|
||||
now - lock.timestamp > this.staleThresholdMs
|
||||
) {
|
||||
this.writeLock();
|
||||
this.isPunished = false;
|
||||
return;
|
||||
}
|
||||
this.focusChanges.push(timestamp);
|
||||
console.log(`pushing focus change at ${timestamp}`);
|
||||
this.lastFocusChangeTime = timestamp;
|
||||
|
||||
// Keep only recent changes
|
||||
if (this.focusChanges.length > this.maxFocusChanges) {
|
||||
this.focusChanges.shift();
|
||||
if (!this.isPunished) {
|
||||
this.applyPunishment();
|
||||
}
|
||||
}
|
||||
|
||||
private checkForMultiTabbing(currentTime: number): void {
|
||||
// Only if we have enough data points
|
||||
if (this.focusChanges.length >= this.maxFocusChanges) {
|
||||
const oldestChange = this.focusChanges[0];
|
||||
const timeSpan = currentTime - oldestChange;
|
||||
|
||||
// If changes happened within detection window
|
||||
if (timeSpan <= this.timeWindow) {
|
||||
private onStorageEvent(e: StorageEvent): void {
|
||||
if (e.key === this.lockKey && e.newValue) {
|
||||
let other: { owner: string; timestamp: number };
|
||||
try {
|
||||
other = JSON.parse(e.newValue);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse lock", e);
|
||||
return;
|
||||
}
|
||||
if (other.owner !== this.tabId && !this.isPunished) {
|
||||
this.applyPunishment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onBeforeUnload(): void {
|
||||
const lock = this.readLock();
|
||||
if (lock?.owner === this.tabId) {
|
||||
localStorage.removeItem(this.lockKey);
|
||||
}
|
||||
}
|
||||
|
||||
private applyPunishment(): void {
|
||||
// Prevent multiple punishments
|
||||
if (this.isPunished) return;
|
||||
this.isPunished = true;
|
||||
|
||||
let punishmentDelay = 0;
|
||||
if (this.numPunishmentsGiven >= this.punishmentDelays.length) {
|
||||
punishmentDelay = this.punishmentDelays[this.punishmentDelays.length - 1];
|
||||
} else {
|
||||
punishmentDelay = this.punishmentDelays[this.numPunishmentsGiven];
|
||||
}
|
||||
|
||||
this.numPunishmentsGiven++;
|
||||
|
||||
// Call the start penalty callback
|
||||
if (this.startPenaltyCallback) {
|
||||
this.startPenaltyCallback(punishmentDelay);
|
||||
}
|
||||
|
||||
// Remove penalty after delay
|
||||
this.punishmentCount++;
|
||||
const delay = 10_000;
|
||||
this.startPenaltyCallback(delay);
|
||||
setTimeout(() => {
|
||||
this.isPunished = false;
|
||||
}, punishmentDelay);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
private writeLock(): void {
|
||||
localStorage.setItem(
|
||||
this.lockKey,
|
||||
JSON.stringify({ owner: this.tabId, timestamp: Date.now() }),
|
||||
);
|
||||
}
|
||||
|
||||
private readLock(): { owner: string; timestamp: number } | null {
|
||||
const raw = localStorage.getItem(this.lockKey);
|
||||
if (!raw) return null;
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse lock", raw, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getMapsImage } from "../utilities/Maps";
|
||||
export const MapDescription: Record<keyof typeof GameMapType, string> = {
|
||||
World: "World",
|
||||
Europe: "Europe",
|
||||
EuropeClassic: "Europe Classic",
|
||||
Mena: "MENA",
|
||||
NorthAmerica: "North America",
|
||||
Oceania: "Oceania",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { GameEnv } from "../../../core/configuration/Config";
|
||||
import { GameType } from "../../../core/game/Game";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { MultiTabDetector } from "../../MultiTabDetector";
|
||||
@@ -15,6 +16,9 @@ export class MultiTabModal extends LitElement implements Layer {
|
||||
@property({ type: Number }) duration: number = 5000;
|
||||
@state() private countdown: number = 5;
|
||||
@state() private isVisible: boolean = false;
|
||||
@state() private fakeIp: string = "";
|
||||
@state() private deviceFingerprint: string = "";
|
||||
@state() private reported: boolean = true;
|
||||
|
||||
private intervalId?: number;
|
||||
|
||||
@@ -26,7 +30,8 @@ export class MultiTabModal extends LitElement implements Layer {
|
||||
tick() {
|
||||
if (
|
||||
this.game.inSpawnPhase() ||
|
||||
this.game.config().gameConfig().gameType == GameType.Singleplayer
|
||||
this.game.config().gameConfig().gameType == GameType.Singleplayer ||
|
||||
this.game.config().serverConfig().env() == GameEnv.Dev
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -38,6 +43,26 @@ export class MultiTabModal extends LitElement implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.fakeIp = this.generateFakeIp();
|
||||
this.deviceFingerprint = this.generateDeviceFingerprint();
|
||||
this.reported = true;
|
||||
}
|
||||
|
||||
// Generate fake IP in format xxx.xxx.xxx.xxx
|
||||
private generateFakeIp(): string {
|
||||
return Array.from({ length: 4 }, () =>
|
||||
Math.floor(Math.random() * 255),
|
||||
).join(".");
|
||||
}
|
||||
|
||||
// Generate fake device fingerprint (32 character hex)
|
||||
private generateDeviceFingerprint(): string {
|
||||
return Array.from({ length: 32 }, () =>
|
||||
Math.floor(Math.random() * 16).toString(16),
|
||||
).join("");
|
||||
}
|
||||
|
||||
// Show the modal with penalty information
|
||||
public show(duration: number): void {
|
||||
if (!this.game.myPlayer()?.isAlive()) {
|
||||
@@ -98,14 +123,44 @@ export class MultiTabModal extends LitElement implements Layer {
|
||||
<div
|
||||
class="relative p-6 bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-md w-full m-4 transition-all transform"
|
||||
>
|
||||
<h2 class="text-2xl font-bold mb-4 text-red-600 dark:text-red-400">
|
||||
${translateText("multi_tab.warning")}
|
||||
</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-2xl font-bold text-red-600 dark:text-red-400">
|
||||
${translateText("multi_tab.warning")}
|
||||
</h2>
|
||||
<div
|
||||
class="px-2 py-1 bg-red-600 text-white text-xs font-bold rounded-full animate-pulse"
|
||||
>
|
||||
RECORDING
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-4 text-gray-800 dark:text-gray-200">
|
||||
${translateText("multi_tab.detected")}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="mb-4 p-3 bg-gray-100 dark:bg-gray-900 rounded-md text-sm font-mono"
|
||||
>
|
||||
<div class="flex justify-between mb-1">
|
||||
<span class="text-gray-500 dark:text-gray-400">IP:</span>
|
||||
<span class="text-red-600 dark:text-red-400">${this.fakeIp}</span>
|
||||
</div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<span class="text-gray-500 dark:text-gray-400"
|
||||
>Device Fingerprint:</span
|
||||
>
|
||||
<span class="text-red-600 dark:text-red-400"
|
||||
>${this.deviceFingerprint}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">Reported:</span>
|
||||
<span class="text-red-600 dark:text-red-400"
|
||||
>${this.reported ? "TRUE" : "FALSE"}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-4 text-gray-800 dark:text-gray-200">
|
||||
${translateText("multi_tab.please_wait")}
|
||||
<span class="font-bold text-xl">${this.countdown}</span>
|
||||
@@ -124,6 +179,10 @@ export class MultiTabModal extends LitElement implements Layer {
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
${translateText("multi_tab.explanation")}
|
||||
</p>
|
||||
|
||||
<p class="mt-3 text-xs text-red-500 font-semibold">
|
||||
Repeated violations may result in permanent account suspension.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -214,6 +214,8 @@ export class NameLayer implements Layer {
|
||||
troopsDiv.style.marginTop = "-5%";
|
||||
element.appendChild(troopsDiv);
|
||||
|
||||
// TODO: enable this for new meta.
|
||||
|
||||
const shieldDiv = document.createElement("div");
|
||||
shieldDiv.classList.add("player-shield");
|
||||
shieldDiv.style.zIndex = "3";
|
||||
@@ -307,13 +309,8 @@ export class NameLayer implements Layer {
|
||||
shieldNumber.style.fontSize = `${render.fontSize * 0.6}px`;
|
||||
shieldNumber.style.marginTop = `${-render.fontSize * 0.1}px`;
|
||||
shieldNumber.textContent = density;
|
||||
if (parseFloat(density) > 90) {
|
||||
shieldNumber.style.textDecoration = "underline";
|
||||
shieldNumber.style.fontWeight = "bold";
|
||||
} else {
|
||||
shieldNumber.style.textDecoration = "none";
|
||||
shieldNumber.style.fontWeight = "normal";
|
||||
}
|
||||
shieldNumber.style.textDecoration = "none";
|
||||
shieldNumber.style.fontWeight = "normal";
|
||||
}
|
||||
|
||||
// Handle icons
|
||||
|
||||
@@ -184,7 +184,9 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
|
||||
let other = this.g.owner(this.tile);
|
||||
if (!other.isPlayer()) {
|
||||
throw new Error("Tile is not owned by a player");
|
||||
this.hide();
|
||||
console.warn("Tile is not owned by a player");
|
||||
return;
|
||||
}
|
||||
other = other as PlayerView;
|
||||
|
||||
|
||||
@@ -350,9 +350,12 @@ export class RadialMenu implements Layer {
|
||||
actions: PlayerActions,
|
||||
tile: TileRef,
|
||||
) {
|
||||
this.activateMenuElement(Slot.Build, "#ebe250", buildIcon, () => {
|
||||
this.buildMenu.showMenu(tile);
|
||||
});
|
||||
if (!this.g.inSpawnPhase()) {
|
||||
this.activateMenuElement(Slot.Build, "#ebe250", buildIcon, () => {
|
||||
this.buildMenu.showMenu(tile);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.g.hasOwner(tile)) {
|
||||
this.activateMenuElement(Slot.Info, "#64748B", infoIcon, () => {
|
||||
this.playerPanel.show(actions, tile);
|
||||
|
||||
@@ -428,8 +428,13 @@ export class UnitLayer implements Layer {
|
||||
let rel = this.relationship(unit);
|
||||
if (unit.type() == UnitType.TradeShip && unit.dstPortId() != null) {
|
||||
const target = this.game.unit(unit.dstPortId())?.owner();
|
||||
if (this.game.myPlayer() != null && this.game.myPlayer() == target) {
|
||||
rel = Relationship.Self;
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (myPlayer != null && target != null) {
|
||||
if (myPlayer == target) {
|
||||
rel = Relationship.Self;
|
||||
} else if (myPlayer.isFriendly(target)) {
|
||||
rel = Relationship.Ally;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (rel) {
|
||||
|
||||
@@ -208,8 +208,6 @@
|
||||
</header>
|
||||
<div class="bg-image"></div>
|
||||
|
||||
<random-name-button></random-name-button>
|
||||
|
||||
<dark-mode-button></dark-mode-button>
|
||||
<!-- Main container with responsive padding -->
|
||||
<main class="flex justify-center flex-grow">
|
||||
@@ -321,7 +319,11 @@
|
||||
>
|
||||
Wiki
|
||||
</a>
|
||||
<a target="_blank" href="https://discord.gg/openfront" class="t-link">
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://discord.gg/jRpxXvG42t"
|
||||
class="t-link"
|
||||
>
|
||||
<span data-i18n="main.join_discord"> Join the Discord! </span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import australia from "../../../resources/maps/AustraliaThumb.webp";
|
||||
import betweenTwoSeas from "../../../resources/maps/BetweenTwoSeasThumb.webp";
|
||||
import blackSea from "../../../resources/maps/BlackSeaThumb.webp";
|
||||
import britannia from "../../../resources/maps/BritanniaThumb.webp";
|
||||
import europeClassic from "../../../resources/maps/EuropeClassicThumb.webp";
|
||||
import europe from "../../../resources/maps/EuropeThumb.webp";
|
||||
import faroeislands from "../../../resources/maps/FaroeIslandsThumb.webp";
|
||||
import gatewayToTheAtlantic from "../../../resources/maps/GatewayToTheAtlanticThumb.webp";
|
||||
@@ -28,6 +29,8 @@ export function getMapsImage(map: GameMapType): string {
|
||||
return oceania;
|
||||
case GameMapType.Europe:
|
||||
return europe;
|
||||
case GameMapType.EuropeClassic:
|
||||
return europeClassic;
|
||||
case GameMapType.Mena:
|
||||
return mena;
|
||||
case GameMapType.NorthAmerica:
|
||||
|
||||
@@ -90,6 +90,7 @@ export abstract class DefaultServerConfig implements ServerConfig {
|
||||
GameMapType.Oceania,
|
||||
GameMapType.Japan, // Japan at this level because its 2/3 water
|
||||
GameMapType.FaroeIslands,
|
||||
GameMapType.EuropeClassic,
|
||||
].includes(map)
|
||||
) {
|
||||
return Math.random() < 0.3 ? 50 : 25;
|
||||
@@ -148,7 +149,7 @@ export class DefaultConfig implements Config {
|
||||
return 0.5;
|
||||
}
|
||||
traitorDuration(): number {
|
||||
return 15 * 10; // 15 seconds
|
||||
return 30 * 10; // 30 seconds
|
||||
}
|
||||
spawnImmunityDuration(): Tick {
|
||||
return 5 * 10;
|
||||
@@ -392,7 +393,7 @@ export class DefaultConfig implements Config {
|
||||
return 80;
|
||||
}
|
||||
boatMaxNumber(): number {
|
||||
return 9;
|
||||
return 3;
|
||||
}
|
||||
numSpawnPhaseTurns(): number {
|
||||
return this._gameConfig.gameType == GameType.Singleplayer ? 100 : 300;
|
||||
|
||||
@@ -26,7 +26,6 @@ export class TransportShipExecution implements Execution {
|
||||
private mg: Game;
|
||||
private attacker: Player;
|
||||
private target: Player | TerraNullius;
|
||||
private embarkDelay = 0;
|
||||
|
||||
// TODO make private
|
||||
public path: TileRef[];
|
||||
@@ -155,10 +154,6 @@ export class TransportShipExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (this.embarkDelay > 0) {
|
||||
this.embarkDelay--;
|
||||
return;
|
||||
}
|
||||
if (ticks - this.lastMove < this.ticksPerMove) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export enum Team {
|
||||
export enum GameMapType {
|
||||
World = "World",
|
||||
Europe = "Europe",
|
||||
EuropeClassic = "Europe Classic",
|
||||
Mena = "Mena",
|
||||
NorthAmerica = "North America",
|
||||
SouthAmerica = "South America",
|
||||
@@ -76,6 +77,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
|
||||
GameMapType.NorthAmerica,
|
||||
GameMapType.SouthAmerica,
|
||||
GameMapType.Europe,
|
||||
GameMapType.EuropeClassic,
|
||||
GameMapType.Asia,
|
||||
GameMapType.Africa,
|
||||
GameMapType.Oceania,
|
||||
|
||||
@@ -102,13 +102,15 @@ export class GameImpl implements Game {
|
||||
|
||||
if (_config.gameConfig().gameMode === GameMode.Team) {
|
||||
const numPlayerTeams = _config.numPlayerTeams();
|
||||
if (numPlayerTeams < 2) throw new Error("Too few teams!");
|
||||
if (numPlayerTeams < 2)
|
||||
throw new Error(`Too few teams: ${numPlayerTeams}`);
|
||||
if (numPlayerTeams >= 3) this.playerTeams.push(Team.Teal);
|
||||
if (numPlayerTeams >= 4) this.playerTeams.push(Team.Purple);
|
||||
if (numPlayerTeams >= 5) this.playerTeams.push(Team.Yellow);
|
||||
if (numPlayerTeams >= 6) this.playerTeams.push(Team.Orange);
|
||||
if (numPlayerTeams >= 7) this.playerTeams.push(Team.Green);
|
||||
if (numPlayerTeams >= 8) throw new Error("Too many teams!");
|
||||
if (numPlayerTeams >= 8)
|
||||
throw new Error(`Too many teams: ${numPlayerTeams}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -277,7 +277,11 @@ export class GameMapImpl implements GameMap {
|
||||
): Set<TileRef> {
|
||||
const seen = new Set<TileRef>();
|
||||
const q: TileRef[] = [];
|
||||
q.push(tile);
|
||||
if (filter(this, tile)) {
|
||||
seen.add(tile);
|
||||
q.push(tile);
|
||||
}
|
||||
|
||||
while (q.length > 0) {
|
||||
const curr = q.pop();
|
||||
for (const n of this.neighbors(curr)) {
|
||||
|
||||
@@ -42,6 +42,7 @@ const MAP_FILE_NAMES: Record<GameMapType, string> = {
|
||||
[GameMapType.BetweenTwoSeas]: "BetweenTwoSeas",
|
||||
[GameMapType.KnownWorld]: "KnownWorld",
|
||||
[GameMapType.FaroeIslands]: "FaroeIslands",
|
||||
[GameMapType.EuropeClassic]: "EuropeClassic",
|
||||
};
|
||||
|
||||
class GameMapLoader {
|
||||
|
||||
@@ -9,6 +9,7 @@ const maps = [
|
||||
"WorldMap",
|
||||
"BlackSea",
|
||||
"Europe",
|
||||
"EuropeClassic",
|
||||
"Mars",
|
||||
"Mena",
|
||||
"Oceania",
|
||||
|
||||
@@ -95,6 +95,9 @@ export class GameServer {
|
||||
if (gameConfig.gameMode != null) {
|
||||
this.gameConfig.gameMode = gameConfig.gameMode;
|
||||
}
|
||||
if (gameConfig.numPlayerTeams != null) {
|
||||
this.gameConfig.numPlayerTeams = gameConfig.numPlayerTeams;
|
||||
}
|
||||
}
|
||||
|
||||
public addClient(client: Client, lastTurn: number) {
|
||||
|
||||
@@ -94,6 +94,7 @@ export class MapPlaylist {
|
||||
case PlaylistType.SmallMaps:
|
||||
return {
|
||||
World: 4,
|
||||
EuropeClassic: 3,
|
||||
Mena: 2,
|
||||
Pangaea: 1,
|
||||
Asia: 1,
|
||||
|
||||
@@ -165,6 +165,7 @@ export function startWorker() {
|
||||
disableNPCs: req.body.disableNPCs,
|
||||
disableNukes: req.body.disableNukes,
|
||||
gameMode: req.body.gameMode,
|
||||
numPlayerTeams: req.body.numPlayerTeams,
|
||||
});
|
||||
res.status(200).json({ success: true });
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user