From ddd5f6ae7291d42a9b691ac50f134cb760e919e2 Mon Sep 17 00:00:00 2001 From: gauvainboiche Date: Wed, 1 Apr 2026 19:14:58 +0200 Subject: [PATCH] feat: Adding animations for map actions + getting rid of debugMode --- README.md | 1 - config/game.settings.json | 2 +- docker-compose.yml | 1 - public/index.html | 1 + public/src/game.js | 28 ++++++++++++++++++++++++++-- public/style.css | 27 +++++++++++++++++++++++++++ server/configLoader.js | 5 +---- server/db/usersDb.js | 7 +++---- server/index.js | 2 -- server/routes/game.js | 1 - 10 files changed, 59 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4ac9d1a..f324991 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ Open `.env` and set every value — see [Environment variables](#environment-var ``` - `POSTGRES_PASSWORD` - `POSTGRES_USERS_PASSWORD` -- `ADMIN_PASSWORD` ### 3 — Review the game config diff --git a/config/game.settings.json b/config/game.settings.json index 36ba2f4..e2520fb 100644 --- a/config/game.settings.json +++ b/config/game.settings.json @@ -1,5 +1,5 @@ { - "dailyActionQuota": 100, + "dailyActionQuota": 1000, "teamActionQuota": 100, "databaseWipeoutIntervalSeconds": 604800, "configReloadIntervalSeconds": 30, diff --git a/docker-compose.yml b/docker-compose.yml index b835261..3f1bb17 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,7 +43,6 @@ services: DATABASE_URL: "postgres://${POSTGRES_USER:-game}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-star_wars_grid}" USERS_DATABASE_URL: "postgres://${POSTGRES_USERS_USER:-users}:${POSTGRES_USERS_PASSWORD}@users_db:5432/${POSTGRES_USERS_DB:-star_wars_users}" JWT_SECRET: ${JWT_SECRET} - ADMIN_PASSWORD: ${ADMIN_PASSWORD} PORT: "${PORT:-8080}" CONFIG_FILE_PATH: /app/config/game.settings.json CORS_ORIGIN: ${CORS_ORIGIN:-*} diff --git a/public/index.html b/public/index.html index 435ee1c..d731b46 100644 --- a/public/index.html +++ b/public/index.html @@ -297,6 +297,7 @@ +
Cliquez sur une tuile. Les stats seront vides à moins de cliquer.
diff --git a/public/src/game.js b/public/src/game.js index 936741d..d5617be 100644 --- a/public/src/game.js +++ b/public/src/game.js @@ -23,7 +23,6 @@ const COLOR_OPPONENT_GREY = "rgba(95, 98, 110, 0.72)"; export const GAME_CONFIG = { dailyActionQuota: 100, databaseWipeoutIntervalSeconds: 21600, - debugModeForTeams: false, configReloadIntervalSeconds: 30, worldSeed: "", seedPeriodEndsAtUtc: "", @@ -237,6 +236,7 @@ const teamQuotaEl = document.getElementById("teamActionsRemaining"); const captorInfoEl = document.getElementById("captorInfo"); const playerListPopupEl = document.getElementById("playerListPopup"); const playerListContentEl = document.getElementById("playerListContent"); +const mapAnimEl = document.getElementById("mapAnim"); // ── Cell helpers ────────────────────────────────────────────────────────────── export function cellKey(x, y) { return `${x},${y}`; } @@ -260,7 +260,6 @@ export function isOwnTile(key) { const m = cellMeta(key); return m !== nul export function applyConfigPayload(data) { GAME_CONFIG.dailyActionQuota = Number(data.dailyActionQuota) || 100; GAME_CONFIG.databaseWipeoutIntervalSeconds = Number(data.databaseWipeoutIntervalSeconds) || 21600; - GAME_CONFIG.debugModeForTeams = Boolean(data.debugModeForTeams); GAME_CONFIG.configReloadIntervalSeconds = Math.max(5, Number(data.configReloadIntervalSeconds) || 30); GAME_CONFIG.worldSeed = String(data.worldSeed ?? ""); GAME_CONFIG.seedPeriodEndsAtUtc = String(data.seedPeriodEndsAtUtc ?? ""); @@ -837,6 +836,29 @@ export function draw() { ctx.restore(); } +// ── Map action animation ───────────────────────────────────────────────────── + +const mapAnimTextByType = { + "reveal-empty": "🔍", + "reveal-planet": "👁️", + "capture": "⚔️", +}; + +/** + * Triggers a spinning emoji animation at the centre of the map zone. + * @param {"reveal-empty"|"reveal-planet"|"capture"} type + */ +function triggerMapAnimation(type) { + if (!mapAnimEl) return; + mapAnimEl.textContent = mapAnimTextByType[type] || "🔍"; + mapAnimEl.classList.remove("mapAnim--active"); + void mapAnimEl.offsetWidth; // force reflow to restart the animation + mapAnimEl.classList.add("mapAnim--active"); + mapAnimEl.addEventListener("animationend", () => { + mapAnimEl.classList.remove("mapAnim--active"); + }, { once: true }); +} + // ── Cursor ──────────────────────────────────────────────────────────────────── function pickCell(ev) { @@ -1040,6 +1062,7 @@ async function onCanvasClick(ev) { } if (!res.ok) throw new Error("reveal"); applyRevealPayload(await res.json()); + triggerMapAnimation(cells.get(key)?.hasPlanet ? "reveal-planet" : "reveal-empty"); startCooldown(); updateEconomyDisplay(); draw(); @@ -1101,6 +1124,7 @@ async function onCanvasClick(ev) { teamActionsRemaining = data.teamActionsRemaining; updateTeamQuotaDisplay(); } + triggerMapAnimation("capture"); hint.textContent = `🏴 Planète (${cell.x},${cell.y}) capturée !`; showLocalSelection(cell.x, cell.y); updateEconomyDisplay(); diff --git a/public/style.css b/public/style.css index 7f2e117..ab366fb 100644 --- a/public/style.css +++ b/public/style.css @@ -1295,4 +1295,31 @@ canvas { .playerListEmpty { color: rgba(233, 238, 246, 0.4); font-style: italic; +} + +/* ── Map action animation ─────────────────────────────────────────────────── */ + +@keyframes mapAnimPop { + 0% { transform: translate(-50%, -50%) scale(0.1); opacity: 0; } + 20% { transform: translate(-50%, -50%) scale(1.1); opacity: 1; } + 80% { transform: translate(-50%, -50%) scale(1); opacity: 1; } + 100% { transform: translate(-50%, -50%) scale(1.2); opacity: 0; } +} + +.mapAnim { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-size: 88px; + line-height: 1; + pointer-events: none; + z-index: 20; + display: none; + user-select: none; +} + +.mapAnim--active { + display: block; + animation: mapAnimPop 0.5s ease-out forwards; } \ No newline at end of file diff --git a/server/configLoader.js b/server/configLoader.js index b767fdf..397bfe8 100644 --- a/server/configLoader.js +++ b/server/configLoader.js @@ -7,12 +7,11 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CONFIG_FILE_PATH = process.env.CONFIG_FILE_PATH ?? path.join(__dirname, "..", "config", "game.settings.json"); -/** @type {{ dailyActionQuota: number, teamActionQuota: number, databaseWipeoutIntervalSeconds: number, debugModeForTeams: boolean, configReloadIntervalSeconds: number, resourceWorth: object, militaryPower: object }} */ +/** @type {{ dailyActionQuota: number, teamActionQuota: number, databaseWipeoutIntervalSeconds: number, configReloadIntervalSeconds: number, resourceWorth: object, militaryPower: object }} */ let cached = { dailyActionQuota: 100, teamActionQuota: 100, databaseWipeoutIntervalSeconds: 21600, - debugModeForTeams: true, configReloadIntervalSeconds: 30, elementWorth: {}, resourceWorth: { common: {}, rare: {} }, @@ -48,8 +47,6 @@ export function loadConfigFile() { if (typeof j.databaseWipeoutIntervalSeconds === "number" && j.databaseWipeoutIntervalSeconds >= 60) { cached.databaseWipeoutIntervalSeconds = j.databaseWipeoutIntervalSeconds; } - const dbg = parseBool(j.debugModeForTeams); - if (dbg !== null) cached.debugModeForTeams = dbg; if (typeof j.configReloadIntervalSeconds === "number" && j.configReloadIntervalSeconds >= 5) { cached.configReloadIntervalSeconds = j.configReloadIntervalSeconds; } diff --git a/server/db/usersDb.js b/server/db/usersDb.js index bd89dc3..b2403f0 100644 --- a/server/db/usersDb.js +++ b/server/db/usersDb.js @@ -9,7 +9,6 @@ export async function initUsersSchema() { username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, team TEXT NOT NULL CHECK (team IN ('blue', 'red')), - role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); `); @@ -44,7 +43,7 @@ export async function createUser(username, passwordHash, team) { const { rows } = await usersPool.query( `INSERT INTO users (username, password_hash, team) VALUES ($1, $2, $3) - RETURNING id, username, team, role`, + RETURNING id, username, team`, [username, passwordHash, team] ); return rows[0]; @@ -52,7 +51,7 @@ export async function createUser(username, passwordHash, team) { export async function getUserByUsername(username) { const { rows } = await usersPool.query( - `SELECT id, username, team, role, password_hash FROM users WHERE username = $1`, + `SELECT id, username, team, password_hash FROM users WHERE username = $1`, [username] ); return rows[0] ?? null; @@ -60,7 +59,7 @@ export async function getUserByUsername(username) { export async function getUserById(id) { const { rows } = await usersPool.query( - `SELECT id, username, team, role FROM users WHERE id = $1`, + `SELECT id, username, team FROM users WHERE id = $1`, [id] ); return rows[0] ?? null; diff --git a/server/index.js b/server/index.js index dad02da..5a5d70d 100644 --- a/server/index.js +++ b/server/index.js @@ -15,7 +15,6 @@ function makeConfigSignature(cfg) { dailyActionQuota: cfg.dailyActionQuota, teamActionQuota: cfg.teamActionQuota, databaseWipeoutIntervalSeconds: cfg.databaseWipeoutIntervalSeconds, - debugModeForTeams: cfg.debugModeForTeams, configReloadIntervalSeconds: cfg.configReloadIntervalSeconds, elementWorth: cfg.elementWorth ?? {}, resourceWorth: cfg.resourceWorth ?? { common: {}, rare: {} }, @@ -43,7 +42,6 @@ function scheduleConfigPoll() { dailyActionQuota: cfg.dailyActionQuota, teamActionQuota: cfg.teamActionQuota, databaseWipeoutIntervalSeconds: cfg.databaseWipeoutIntervalSeconds, - debugModeForTeams: cfg.debugModeForTeams, configReloadIntervalSeconds: cfg.configReloadIntervalSeconds, elementWorth: cfg.elementWorth ?? {}, resourceWorth: cfg.resourceWorth ?? { common: {}, rare: {} }, diff --git a/server/routes/game.js b/server/routes/game.js index bc0eecf..a02e94f 100644 --- a/server/routes/game.js +++ b/server/routes/game.js @@ -94,7 +94,6 @@ router.get("/config", async (req, res) => { res.json({ dailyActionQuota: cfg.dailyActionQuota, databaseWipeoutIntervalSeconds: rot, - debugModeForTeams: cfg.debugModeForTeams, configReloadIntervalSeconds: cfg.configReloadIntervalSeconds, worldSeed, seedPeriodEndsAtUtc: ws.seedPeriodEndsAtUtc,