feat(gameplay): Adding % bonus for planet type
This commit is contained in:
@@ -13,6 +13,7 @@ let cached = {
|
||||
databaseWipeoutIntervalSeconds: 21600,
|
||||
debugModeForTeams: true,
|
||||
configReloadIntervalSeconds: 30,
|
||||
elementWorth: {},
|
||||
resourceWorth: { common: {}, rare: {} },
|
||||
};
|
||||
|
||||
@@ -47,6 +48,9 @@ export function loadConfigFile() {
|
||||
if (typeof j.configReloadIntervalSeconds === "number" && j.configReloadIntervalSeconds >= 5) {
|
||||
cached.configReloadIntervalSeconds = j.configReloadIntervalSeconds;
|
||||
}
|
||||
if (j.elementWorth && typeof j.elementWorth === "object") {
|
||||
cached.elementWorth = j.elementWorth;
|
||||
}
|
||||
if (j.resourceWorth && typeof j.resourceWorth === "object") {
|
||||
cached.resourceWorth = j.resourceWorth;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,31 @@ export async function initGameSchema() {
|
||||
PRIMARY KEY (world_seed, team)
|
||||
);
|
||||
`);
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS team_element_bonus (
|
||||
world_seed TEXT NOT NULL,
|
||||
team TEXT NOT NULL CHECK (team IN ('blue', 'red')),
|
||||
bonus DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (world_seed, team)
|
||||
);
|
||||
`);
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS db_metadata (
|
||||
id SERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
INSERT INTO db_metadata (created_at)
|
||||
SELECT NOW() WHERE NOT EXISTS (SELECT 1 FROM db_metadata);
|
||||
`);
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS victory_points (
|
||||
id SERIAL PRIMARY KEY,
|
||||
awarded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
world_seed TEXT NOT NULL,
|
||||
team TEXT NOT NULL CHECK (team IN ('blue', 'red'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_vp_team ON victory_points (team);
|
||||
`);
|
||||
await pool.query(`
|
||||
ALTER TABLE grid_cells ADD COLUMN IF NOT EXISTS discovered_by TEXT;
|
||||
UPDATE grid_cells SET discovered_by = 'blue' WHERE discovered_by IS NULL;
|
||||
@@ -65,9 +90,30 @@ export async function ensureSeedEpoch() {
|
||||
return worldSeed;
|
||||
}
|
||||
if (seedSlot !== lastSeedSlot) {
|
||||
// Award a victory point to the team with the highest econ score before wiping
|
||||
try {
|
||||
const expiredSeed = `swg-${lastSeedSlot}`;
|
||||
const econRows = await pool.query(
|
||||
`SELECT team, score FROM team_econ_scores WHERE world_seed = $1`,
|
||||
[expiredSeed]
|
||||
);
|
||||
const scores = { blue: 0, red: 0 };
|
||||
for (const row of econRows.rows) scores[row.team] = Number(row.score);
|
||||
if (scores.blue > 0 || scores.red > 0) {
|
||||
const winner = scores.blue >= scores.red ? "blue" : "red";
|
||||
await pool.query(
|
||||
`INSERT INTO victory_points (world_seed, team) VALUES ($1, $2)`,
|
||||
[expiredSeed, winner]
|
||||
);
|
||||
console.log(`[world] VP awarded to ${winner} for seed ${expiredSeed} (blue=${scores.blue.toFixed(3)}, red=${scores.red.toFixed(3)})`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[world] VP award error:", e);
|
||||
}
|
||||
await pool.query("TRUNCATE grid_cells RESTART IDENTITY");
|
||||
await pool.query("DELETE FROM team_cooldowns WHERE world_seed != $1", [worldSeed]);
|
||||
await pool.query("DELETE FROM team_econ_scores WHERE world_seed != $1", [worldSeed]);
|
||||
await pool.query("DELETE FROM team_element_bonus WHERE world_seed != $1", [worldSeed]);
|
||||
console.log(`[world] Slot ${lastSeedSlot} → ${seedSlot}; grid wiped, old cooldowns cleared.`);
|
||||
lastSeedSlot = seedSlot;
|
||||
}
|
||||
@@ -147,6 +193,46 @@ export async function addEconScore(worldSeed, team, delta) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getElementBonus(worldSeed) {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT team, bonus FROM team_element_bonus WHERE world_seed = $1`,
|
||||
[worldSeed]
|
||||
);
|
||||
const result = { blue: 0, red: 0 };
|
||||
for (const row of rows) result[row.team] = Number(row.bonus);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function setElementBonus(worldSeed, team, bonus) {
|
||||
await pool.query(
|
||||
`INSERT INTO team_element_bonus (world_seed, team, bonus)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (world_seed, team) DO UPDATE
|
||||
SET bonus = EXCLUDED.bonus`,
|
||||
[worldSeed, team, bonus]
|
||||
);
|
||||
}
|
||||
|
||||
// ── DB metadata ───────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getDbCreatedAt() {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT created_at FROM db_metadata ORDER BY id ASC LIMIT 1`
|
||||
);
|
||||
return rows[0]?.created_at ?? null;
|
||||
}
|
||||
|
||||
// ── Victory points ────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getVictoryPoints() {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT team, COUNT(*) AS cnt FROM victory_points GROUP BY team`
|
||||
);
|
||||
const result = { blue: 0, red: 0 };
|
||||
for (const row of rows) result[row.team] = Number(row.cnt);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Scores ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getScores(worldSeed) {
|
||||
@@ -157,4 +243,4 @@ export async function getScores(worldSeed) {
|
||||
[worldSeed]
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
getScores,
|
||||
getEconScores,
|
||||
addEconScore,
|
||||
getElementBonus,
|
||||
setElementBonus,
|
||||
getDbCreatedAt,
|
||||
getVictoryPoints,
|
||||
} from "../db/gameDb.js";
|
||||
import { computeCell, rowToCellPayload } from "../helpers/cell.js";
|
||||
|
||||
@@ -46,6 +50,7 @@ router.get("/config", async (req, res) => {
|
||||
seedPeriodStartsAtUtc: ws.seedPeriodStartsAtUtc,
|
||||
teamCooldownRemaining,
|
||||
resourceWorth: cfg.resourceWorth ?? { common: {}, rare: {} },
|
||||
elementWorth: cfg.elementWorth ?? {},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -180,6 +185,58 @@ router.post("/econ-scores/tick", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/element-bonus
|
||||
router.get("/element-bonus", async (_req, res) => {
|
||||
try {
|
||||
const worldSeed = await ensureSeedEpoch();
|
||||
const bonus = await getElementBonus(worldSeed);
|
||||
res.json(bonus);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: "database_error" });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/element-bonus/tick body: { seed, blue, red }
|
||||
router.post("/element-bonus/tick", async (req, res) => {
|
||||
const seed = String(req.body?.seed ?? "");
|
||||
const blue = Number(req.body?.blue ?? 0);
|
||||
const red = Number(req.body?.red ?? 0);
|
||||
try {
|
||||
const worldSeed = await ensureSeedEpoch();
|
||||
if (seed !== worldSeed) return res.status(410).json({ error: "seed_expired", worldSeed });
|
||||
await setElementBonus(worldSeed, "blue", blue);
|
||||
await setElementBonus(worldSeed, "red", red);
|
||||
const bonus = await getElementBonus(worldSeed);
|
||||
res.json(bonus);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: "database_error" });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/db-info
|
||||
router.get("/db-info", async (_req, res) => {
|
||||
try {
|
||||
const createdAt = await getDbCreatedAt();
|
||||
res.json({ createdAt });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: "database_error" });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/victory-points
|
||||
router.get("/victory-points", async (_req, res) => {
|
||||
try {
|
||||
const vp = await getVictoryPoints();
|
||||
res.json(vp);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: "database_error" });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/scores
|
||||
router.get("/scores", async (_req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user