refacto: Changing actions reset windows + display
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"dailyActionQuota": 100,
|
||||
"teamActionQuota": 100,
|
||||
"actionsResetIntervalHours": 24,
|
||||
"databaseWipeoutIntervalSeconds": 604800,
|
||||
"configReloadIntervalSeconds": 30,
|
||||
"elementWorth": {
|
||||
|
||||
+2
-2
@@ -170,8 +170,8 @@
|
||||
<code class="infoVal" id="worldSeedDisplay">—</code>
|
||||
</div>
|
||||
<div class="infoRow">
|
||||
<span class="infoKey muted">Prochaine graine (UTC)</span>
|
||||
<code class="infoVal" id="nextPeriodUtc">—</code>
|
||||
<span class="infoKey muted">Réinitialisation des actions dans</span>
|
||||
<code class="infoVal" id="actionsResetCountdown">--:--:--</code>
|
||||
</div>
|
||||
<div class="infoRow">
|
||||
<span class="infoKey muted">Prochaine graine dans</span>
|
||||
|
||||
+29
-4
@@ -22,6 +22,7 @@ const COLOR_OPPONENT_GREY = "rgba(95, 98, 110, 0.72)";
|
||||
|
||||
export const GAME_CONFIG = {
|
||||
dailyActionQuota: 100,
|
||||
actionsResetIntervalHours: 12,
|
||||
databaseWipeoutIntervalSeconds: 21600,
|
||||
configReloadIntervalSeconds: 30,
|
||||
worldSeed: "",
|
||||
@@ -203,7 +204,7 @@ const countdownEl = document.getElementById("countdown");
|
||||
const countdownWrap = document.getElementById("countdownWrap");
|
||||
const cooldownCfgEl = document.getElementById("cooldownConfig");
|
||||
const seedDisplayEl = document.getElementById("worldSeedDisplay");
|
||||
const nextPeriodEl = document.getElementById("nextPeriodUtc");
|
||||
const actionsResetCountdownEl = document.getElementById("actionsResetCountdown");
|
||||
const resetCountEl = document.getElementById("refreshCountdown");
|
||||
const vpBlueEl = document.getElementById("vpBlue");
|
||||
const vpRedEl = document.getElementById("vpRed");
|
||||
@@ -263,6 +264,9 @@ export function isOwnTile(key) { const m = cellMeta(key); return m !== nul
|
||||
|
||||
export function applyConfigPayload(data) {
|
||||
GAME_CONFIG.dailyActionQuota = Number(data.dailyActionQuota) || 100;
|
||||
if (data.actionsResetIntervalHours) {
|
||||
GAME_CONFIG.actionsResetIntervalHours = Number(data.actionsResetIntervalHours) || 12;
|
||||
}
|
||||
GAME_CONFIG.databaseWipeoutIntervalSeconds = Number(data.databaseWipeoutIntervalSeconds) || 21600;
|
||||
GAME_CONFIG.configReloadIntervalSeconds = Math.max(5, Number(data.configReloadIntervalSeconds) || 30);
|
||||
GAME_CONFIG.worldSeed = String(data.worldSeed ?? "");
|
||||
@@ -287,11 +291,9 @@ export function applyConfigPayload(data) {
|
||||
|
||||
cooldownCfgEl.textContent = String(GAME_CONFIG.dailyActionQuota);
|
||||
seedDisplayEl.textContent = GAME_CONFIG.worldSeed || "—";
|
||||
nextPeriodEl.textContent = GAME_CONFIG.seedPeriodEndsAtUtc
|
||||
? new Date(GAME_CONFIG.seedPeriodEndsAtUtc).toISOString().replace("T", " ").slice(0, 19) + "Z"
|
||||
: "—";
|
||||
|
||||
updateResetCountdown();
|
||||
updateActionsResetCountdown();
|
||||
|
||||
}
|
||||
|
||||
@@ -311,6 +313,29 @@ export function updateResetCountdown() {
|
||||
String(ss).padStart(2, "0");
|
||||
}
|
||||
|
||||
/** Computes time until next actions-reset based on the configured interval and updates the display. */
|
||||
export function updateActionsResetCountdown() {
|
||||
if (!actionsResetCountdownEl) return;
|
||||
const intervalHours = GAME_CONFIG.actionsResetIntervalHours ?? 12;
|
||||
const now = Date.now();
|
||||
const date = new Date(now);
|
||||
const utcHours = date.getUTCHours();
|
||||
const slotsPassed = Math.floor(utcHours / intervalHours);
|
||||
const nextSlotHour = (slotsPassed + 1) * intervalHours;
|
||||
const next = nextSlotHour < 24
|
||||
? Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), nextSlotHour, 0, 0, 0)
|
||||
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1, 0, 0, 0, 0);
|
||||
const diff = next - now;
|
||||
const s = Math.floor(diff / 1000);
|
||||
const hh = Math.floor(s / 3600);
|
||||
const mm = Math.floor((s % 3600) / 60);
|
||||
const ss = s % 60;
|
||||
actionsResetCountdownEl.textContent =
|
||||
String(hh).padStart(2, "0") + ":" +
|
||||
String(mm).padStart(2, "0") + ":" +
|
||||
String(ss).padStart(2, "0");
|
||||
}
|
||||
|
||||
|
||||
// ── Scores ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@ import {
|
||||
GAME_CONFIG,
|
||||
seedStr,
|
||||
updateResetCountdown,
|
||||
updateActionsResetCountdown,
|
||||
fetchConfig,
|
||||
fetchGridForSeed,
|
||||
fetchAndApplyActivePlayers,
|
||||
@@ -221,8 +222,9 @@ async function boot() {
|
||||
draw();
|
||||
|
||||
if (resetTimer) clearInterval(resetTimer);
|
||||
resetTimer = setInterval(updateResetCountdown, 1_000);
|
||||
resetTimer = setInterval(() => { updateResetCountdown(); updateActionsResetCountdown(); }, 1_000);
|
||||
updateResetCountdown();
|
||||
updateActionsResetCountdown();
|
||||
|
||||
startRealtimeFlow();
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ const CONFIG_FILE_PATH =
|
||||
process.env.CONFIG_FILE_PATH ?? path.join(__dirname, "..", "config", "game.settings.json");
|
||||
|
||||
/** @type {{ dailyActionQuota: number, teamActionQuota: number, databaseWipeoutIntervalSeconds: number, configReloadIntervalSeconds: number, resourceWorth: object, militaryPower: object }} */
|
||||
const VALID_RESET_HOURS = new Set([1, 2, 3, 4, 6, 8, 12, 24]);
|
||||
|
||||
let cached = {
|
||||
dailyActionQuota: 100,
|
||||
teamActionQuota: 100,
|
||||
actionsResetIntervalHours: 12,
|
||||
databaseWipeoutIntervalSeconds: 21600,
|
||||
configReloadIntervalSeconds: 30,
|
||||
elementWorth: {},
|
||||
@@ -44,6 +47,10 @@ export function loadConfigFile() {
|
||||
if (typeof j.teamActionQuota === "number" && j.teamActionQuota >= 1) {
|
||||
cached.teamActionQuota = Math.floor(j.teamActionQuota);
|
||||
}
|
||||
if (typeof j.actionsResetIntervalHours === "number") {
|
||||
const v = Math.floor(j.actionsResetIntervalHours);
|
||||
if (VALID_RESET_HOURS.has(v)) cached.actionsResetIntervalHours = v;
|
||||
}
|
||||
if (typeof j.databaseWipeoutIntervalSeconds === "number" && j.databaseWipeoutIntervalSeconds >= 60) {
|
||||
cached.databaseWipeoutIntervalSeconds = j.databaseWipeoutIntervalSeconds;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { pool } from "./pools.js";
|
||||
import { loadConfigFile, getConfig } from "../configLoader.js";
|
||||
import { computeWorldSeedState } from "../worldSeed.js";
|
||||
import { nextNoonUtc, resetAllUserActions, getUsersByIds } from "./usersDb.js";
|
||||
import { nextResetUtc, resetAllUserActions, getUsersByIds } from "./usersDb.js";
|
||||
|
||||
let lastSeedSlot = null;
|
||||
|
||||
@@ -187,7 +187,7 @@ export async function ensureSeedEpoch() {
|
||||
await pool.query("DELETE FROM team_cell_visibility WHERE world_seed != $1", [worldSeed]);
|
||||
// Reset both team and user quotas to server defaults (no bonuses) for the new period
|
||||
const cfg = getConfig();
|
||||
const nextNoon = nextNoonUtc().toISOString();
|
||||
const nextNoon = nextResetUtc(cfg.actionsResetIntervalHours ?? 12).toISOString();
|
||||
await pool.query(
|
||||
`INSERT INTO team_action_quota (team, actions_remaining, quota_reset_at)
|
||||
VALUES ('blue', $1, $2), ('red', $1, $2)
|
||||
|
||||
+20
-4
@@ -29,12 +29,28 @@ export async function initUserActionQuotaSchema() {
|
||||
|
||||
/** Returns the next noon (12:00:00) UTC after the current moment. */
|
||||
export function nextNoonUtc() {
|
||||
return nextResetUtc(12);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next reset timestamp UTC based on a repeating interval.
|
||||
* @param {number} intervalHours - must be a divisor of 24 (1,2,3,4,6,8,12,24)
|
||||
*/
|
||||
export function nextResetUtc(intervalHours) {
|
||||
const now = new Date();
|
||||
const noon = new Date(Date.UTC(
|
||||
now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 12, 0, 0, 0
|
||||
const utcHours = now.getUTCHours();
|
||||
const slotsPassed = Math.floor(utcHours / intervalHours);
|
||||
const nextSlotHour = (slotsPassed + 1) * intervalHours;
|
||||
if (nextSlotHour < 24) {
|
||||
return new Date(Date.UTC(
|
||||
now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(),
|
||||
nextSlotHour, 0, 0, 0
|
||||
));
|
||||
}
|
||||
return new Date(Date.UTC(
|
||||
now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1,
|
||||
0, 0, 0, 0
|
||||
));
|
||||
if (noon <= now) noon.setUTCDate(noon.getUTCDate() + 1);
|
||||
return noon;
|
||||
}
|
||||
|
||||
// ── Queries ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -14,6 +14,7 @@ function makeConfigSignature(cfg) {
|
||||
return JSON.stringify({
|
||||
dailyActionQuota: cfg.dailyActionQuota,
|
||||
teamActionQuota: cfg.teamActionQuota,
|
||||
actionsResetIntervalHours: cfg.actionsResetIntervalHours,
|
||||
databaseWipeoutIntervalSeconds: cfg.databaseWipeoutIntervalSeconds,
|
||||
configReloadIntervalSeconds: cfg.configReloadIntervalSeconds,
|
||||
elementWorth: cfg.elementWorth ?? {},
|
||||
@@ -41,6 +42,7 @@ function scheduleConfigPoll() {
|
||||
config: {
|
||||
dailyActionQuota: cfg.dailyActionQuota,
|
||||
teamActionQuota: cfg.teamActionQuota,
|
||||
actionsResetIntervalHours: cfg.actionsResetIntervalHours,
|
||||
databaseWipeoutIntervalSeconds: cfg.databaseWipeoutIntervalSeconds,
|
||||
configReloadIntervalSeconds: cfg.configReloadIntervalSeconds,
|
||||
elementWorth: cfg.elementWorth ?? {},
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
getTeamVisibleCells,
|
||||
} from "../db/gameDb.js";
|
||||
import {
|
||||
nextNoonUtc,
|
||||
nextResetUtc,
|
||||
getUserActionsRow,
|
||||
resetUserActions,
|
||||
decrementUserActions,
|
||||
@@ -100,6 +100,7 @@ router.get("/config", async (req, res) => {
|
||||
seedPeriodStartsAtUtc: ws.seedPeriodStartsAtUtc,
|
||||
actionsRemaining,
|
||||
teamActionsRemaining,
|
||||
actionsResetIntervalHours: cfg.actionsResetIntervalHours ?? 12,
|
||||
resourceWorth: cfg.resourceWorth ?? { common: {}, rare: {} },
|
||||
elementWorth: cfg.elementWorth ?? {},
|
||||
militaryPower: cfg.militaryPower ?? {},
|
||||
@@ -170,7 +171,7 @@ router.post("/cell/reveal", authMiddleware, async (req, res) => {
|
||||
const now = new Date();
|
||||
const quotaRow = await getUserActionsRow(userId);
|
||||
if (!quotaRow || new Date(quotaRow.quota_reset_at) <= now) {
|
||||
await resetUserActions(userId, effectiveQuota - 1, nextNoonUtc().toISOString());
|
||||
await resetUserActions(userId, effectiveQuota - 1, nextResetUtc(cfg.actionsResetIntervalHours ?? 12).toISOString());
|
||||
} else {
|
||||
const updated = await decrementUserActions(userId);
|
||||
if (!updated) {
|
||||
@@ -247,7 +248,7 @@ router.post("/cell/capture", authMiddleware, async (req, res) => {
|
||||
if (totalActions < cost) {
|
||||
return res.status(429).json({ error: "team_quota_exhausted", cost, teamActionsRemaining: totalActions });
|
||||
}
|
||||
await resetTeamActions(team, totalActions - cost, nextNoonUtc().toISOString());
|
||||
await resetTeamActions(team, totalActions - cost, nextResetUtc(cfg.actionsResetIntervalHours ?? 12).toISOString());
|
||||
} else {
|
||||
if (teamRow.actions_remaining < cost) {
|
||||
return res.status(429).json({ error: "team_quota_exhausted", cost, teamActionsRemaining: teamRow.actions_remaining });
|
||||
|
||||
Reference in New Issue
Block a user