refacto: Changing click cooldown to daily actions for users and teams
This commit is contained in:
@@ -24,8 +24,18 @@ import {
|
||||
recordCellAttack,
|
||||
getCellAttackCount,
|
||||
setTileOwner,
|
||||
getTeamActionsRow,
|
||||
resetTeamActions,
|
||||
decrementTeamActions,
|
||||
} from "../db/gameDb.js";
|
||||
import {
|
||||
nextNoonUtc,
|
||||
getUserActionsRow,
|
||||
resetUserActions,
|
||||
decrementUserActions,
|
||||
} from "../db/usersDb.js";
|
||||
import { computeCell, rowToCellPayload } from "../helpers/cell.js";
|
||||
import { computeTeamMilitaryPower } from "../helpers/economy.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -37,45 +47,53 @@ router.get("/config", async (req, res) => {
|
||||
const rot = cfg.databaseWipeoutIntervalSeconds;
|
||||
const ws = computeWorldSeedState(rot);
|
||||
|
||||
let teamCooldownRemaining = 0;
|
||||
let actionsRemaining = null;
|
||||
let teamActionsRemaining = null;
|
||||
const team = typeof req.query.team === "string" ? req.query.team : undefined;
|
||||
if (team === "blue" || team === "red") {
|
||||
const bonus = await getElementBonus(worldSeed);
|
||||
const teamBonus = bonus[team] ?? 0;
|
||||
const effectiveCooldown = cfg.clickCooldownSeconds / (1 + teamBonus / 100);
|
||||
// Use per-user cooldown if auth token provided, else fall back to team-wide
|
||||
const authHeader = req.headers["authorization"];
|
||||
if (authHeader && authHeader.startsWith("Bearer ")) {
|
||||
try {
|
||||
const payload = jwt.verify(authHeader.slice(7), JWT_SECRET);
|
||||
const row = await getUserCooldown(worldSeed, payload.userId);
|
||||
if (row) {
|
||||
const secondsSince = (Date.now() - new Date(row.last_reveal).getTime()) / 1000;
|
||||
if (secondsSince < effectiveCooldown) {
|
||||
teamCooldownRemaining = Math.ceil(effectiveCooldown - secondsSince);
|
||||
}
|
||||
const bonus = await getElementBonus(worldSeed);
|
||||
const teamBonus = bonus[team] ?? 0;
|
||||
const effectiveQuota = Math.floor(cfg.dailyActionQuota * (1 + teamBonus / 100));
|
||||
const now = new Date();
|
||||
const quotaRow = await getUserActionsRow(payload.userId);
|
||||
if (!quotaRow || new Date(quotaRow.quota_reset_at) <= now) {
|
||||
actionsRemaining = effectiveQuota;
|
||||
} else {
|
||||
actionsRemaining = quotaRow.actions_remaining;
|
||||
}
|
||||
} catch { /* invalid token — return 0 */ }
|
||||
} catch { /* invalid token — return null */ }
|
||||
}
|
||||
|
||||
// Team-wide quota: compute current remaining without consuming
|
||||
const now = new Date();
|
||||
const teamRow = await getTeamActionsRow(team);
|
||||
if (!teamRow || new Date(teamRow.quota_reset_at) <= now) {
|
||||
// Expired or unset: compute what it would be when refreshed
|
||||
const rows = await getGridCells(worldSeed);
|
||||
const milPower = computeTeamMilitaryPower(team, rows, cfg.militaryPower ?? {});
|
||||
const milDeductions = await getMilitaryDeductions(worldSeed);
|
||||
const milNet = milPower - (milDeductions[team] ?? 0);
|
||||
const milBonus = Math.floor(Math.max(0, milNet) / 10000);
|
||||
teamActionsRemaining = 100 + milBonus;
|
||||
} else {
|
||||
const row = await getTeamCooldown(worldSeed, team);
|
||||
if (row) {
|
||||
const secondsSince = (Date.now() - new Date(row.last_reveal).getTime()) / 1000;
|
||||
if (secondsSince < effectiveCooldown) {
|
||||
teamCooldownRemaining = Math.ceil(effectiveCooldown - secondsSince);
|
||||
}
|
||||
}
|
||||
teamActionsRemaining = teamRow.actions_remaining;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
clickCooldownSeconds: cfg.clickCooldownSeconds,
|
||||
dailyActionQuota: cfg.dailyActionQuota,
|
||||
databaseWipeoutIntervalSeconds: rot,
|
||||
debugModeForTeams: cfg.debugModeForTeams,
|
||||
configReloadIntervalSeconds: cfg.configReloadIntervalSeconds,
|
||||
worldSeed: ws.worldSeed,
|
||||
seedPeriodEndsAtUtc: ws.seedPeriodEndsAtUtc,
|
||||
seedPeriodStartsAtUtc: ws.seedPeriodStartsAtUtc,
|
||||
teamCooldownRemaining,
|
||||
actionsRemaining,
|
||||
teamActionsRemaining,
|
||||
resourceWorth: cfg.resourceWorth ?? { common: {}, rare: {} },
|
||||
elementWorth: cfg.elementWorth ?? {},
|
||||
militaryPower: cfg.militaryPower ?? {},
|
||||
@@ -127,19 +145,23 @@ router.post("/cell/reveal", authMiddleware, async (req, res) => {
|
||||
}
|
||||
|
||||
const cfg = getConfig();
|
||||
if (cfg.clickCooldownSeconds > 0) {
|
||||
if (cfg.dailyActionQuota > 0) {
|
||||
const bonus = await getElementBonus(worldSeed);
|
||||
const teamBonus = bonus[team] ?? 0;
|
||||
const effectiveCooldown = cfg.clickCooldownSeconds / (1 + teamBonus / 100);
|
||||
const cooldownRow = await getUserCooldown(worldSeed, userId);
|
||||
if (cooldownRow) {
|
||||
const secondsSince = (Date.now() - new Date(cooldownRow.last_reveal).getTime()) / 1000;
|
||||
if (secondsSince < effectiveCooldown) {
|
||||
const effectiveQuota = Math.floor(cfg.dailyActionQuota * (1 + teamBonus / 100));
|
||||
const now = new Date();
|
||||
const quotaRow = await getUserActionsRow(userId);
|
||||
|
||||
if (!quotaRow || new Date(quotaRow.quota_reset_at) <= now) {
|
||||
// First action of the day (or expired): reset to effectiveQuota - 1 (consuming 1 now)
|
||||
await resetUserActions(userId, effectiveQuota - 1, nextNoonUtc().toISOString());
|
||||
} else {
|
||||
// Quota exists and still valid — try to consume
|
||||
const updated = await decrementUserActions(userId);
|
||||
if (!updated) {
|
||||
return res.status(429).json({
|
||||
error: "cooldown_active",
|
||||
team,
|
||||
remainingSeconds: Math.ceil(effectiveCooldown - secondsSince),
|
||||
cooldownSeconds: effectiveCooldown,
|
||||
error: "quota_exhausted",
|
||||
actionsRemaining: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -149,8 +171,10 @@ router.post("/cell/reveal", authMiddleware, async (req, res) => {
|
||||
const planetJson = cell.planet ? JSON.stringify(cell.planet) : null;
|
||||
const inserted = await insertCell(seed, x, y, cell.exploitable, cell.hasPlanet, planetJson, team);
|
||||
|
||||
// Track activity for active-player count (kept for stat purposes)
|
||||
await upsertUserCooldown(worldSeed, userId, team);
|
||||
|
||||
if (inserted) {
|
||||
await upsertUserCooldown(worldSeed, userId, team);
|
||||
return res.json(rowToCellPayload(inserted));
|
||||
}
|
||||
|
||||
@@ -171,6 +195,35 @@ router.post("/cell/reveal", authMiddleware, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/team-quota?team=blue|red
|
||||
router.get("/team-quota", async (req, res) => {
|
||||
const team = typeof req.query.team === "string" ? req.query.team : "";
|
||||
if (team !== "blue" && team !== "red") {
|
||||
return res.status(400).json({ error: "invalid_team" });
|
||||
}
|
||||
try {
|
||||
const worldSeed = await ensureSeedEpoch();
|
||||
const cfg = getConfig();
|
||||
const now = new Date();
|
||||
const teamRow = await getTeamActionsRow(team);
|
||||
let actionsRemaining;
|
||||
if (!teamRow || new Date(teamRow.quota_reset_at) <= now) {
|
||||
const rows = await getGridCells(worldSeed);
|
||||
const milPower = computeTeamMilitaryPower(team, rows, cfg.militaryPower ?? {});
|
||||
const milDeductions = await getMilitaryDeductions(worldSeed);
|
||||
const milNet = milPower - (milDeductions[team] ?? 0);
|
||||
const milBonus = Math.floor(Math.max(0, milNet) / 10000);
|
||||
actionsRemaining = 100 + milBonus;
|
||||
} else {
|
||||
actionsRemaining = teamRow.actions_remaining;
|
||||
}
|
||||
res.json({ team, actionsRemaining });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: "database_error" });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/econ-scores
|
||||
router.get("/econ-scores", async (_req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user