feat(biochem): Фаза 5.5 — ачивки bc_* привязаны к событиям
gamification/service.js (checkPhase3Achievements): новый био-блок — bc_first_molecule (есть сохранённая молекула), bc_5_challenges (>=5 решённых), bc_20_challenges (>=20) из таблиц bio_user_molecules / bio_user_challenges. biochemController.js: после решения задачи и сохранения молекулы вызывается checkAchievements(req.user.id) — раньше начислялся только XP, ачивки не триггерились. Слоты bc_* существовали в _shared.js, но были мёртвыми. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const db = require('../db/db');
|
const db = require('../db/db');
|
||||||
const { awardXP } = require('./gamificationController');
|
const { awardXP, checkAchievements } = require('./gamificationController');
|
||||||
|
|
||||||
/* ── Helpers ─────────────────────────────────────────────────────────── */
|
/* ── Helpers ─────────────────────────────────────────────────────────── */
|
||||||
const MAX_V = { H:1, C:4, N:3, O:2, P:5, S:6, Cl:1, Na:1, Ca:2, K:1, Mg:2, Fe:3, Br:1, I:1, F:1 };
|
const MAX_V = { H:1, C:4, N:3, O:2, P:5, S:6, Cl:1, Na:1, Ca:2, K:1, Mg:2, Fe:3, Br:1, I:1, F:1 };
|
||||||
@@ -178,6 +178,7 @@ function solveChallenge(req, res) {
|
|||||||
return res.status(400).json({ error: 'wrong_answer' });
|
return res.status(400).json({ error: 'wrong_answer' });
|
||||||
stmts.markDone.run(req.user.id, challenge.id);
|
stmts.markDone.run(req.user.id, challenge.id);
|
||||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||||
|
checkAchievements(req.user.id);
|
||||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +191,7 @@ function solveChallenge(req, res) {
|
|||||||
return res.status(400).json({ error: 'wrong_answer' });
|
return res.status(400).json({ error: 'wrong_answer' });
|
||||||
stmts.markDone.run(req.user.id, challenge.id);
|
stmts.markDone.run(req.user.id, challenge.id);
|
||||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||||
|
checkAchievements(req.user.id);
|
||||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +205,7 @@ function solveChallenge(req, res) {
|
|||||||
return res.status(400).json({ error: 'wrong_answer' });
|
return res.status(400).json({ error: 'wrong_answer' });
|
||||||
stmts.markDone.run(req.user.id, challenge.id);
|
stmts.markDone.run(req.user.id, challenge.id);
|
||||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||||
|
checkAchievements(req.user.id);
|
||||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +219,7 @@ function solveChallenge(req, res) {
|
|||||||
return res.status(400).json({ error: 'wrong_answer' });
|
return res.status(400).json({ error: 'wrong_answer' });
|
||||||
stmts.markDone.run(req.user.id, challenge.id);
|
stmts.markDone.run(req.user.id, challenge.id);
|
||||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||||
|
checkAchievements(req.user.id);
|
||||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +234,7 @@ function solveChallenge(req, res) {
|
|||||||
return res.status(400).json({ error: 'wrong_answer' });
|
return res.status(400).json({ error: 'wrong_answer' });
|
||||||
stmts.markDone.run(req.user.id, challenge.id);
|
stmts.markDone.run(req.user.id, challenge.id);
|
||||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||||
|
checkAchievements(req.user.id);
|
||||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +249,7 @@ function solveChallenge(req, res) {
|
|||||||
return res.status(400).json({ error: 'wrong_answer' });
|
return res.status(400).json({ error: 'wrong_answer' });
|
||||||
stmts.markDone.run(req.user.id, challenge.id);
|
stmts.markDone.run(req.user.id, challenge.id);
|
||||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||||
|
checkAchievements(req.user.id);
|
||||||
return res.json({ ok: true, xp: challenge.xp_reward });
|
return res.json({ ok: true, xp: challenge.xp_reward });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +280,7 @@ function solveChallenge(req, res) {
|
|||||||
|
|
||||||
stmts.markDone.run(req.user.id, challenge.id);
|
stmts.markDone.run(req.user.id, challenge.id);
|
||||||
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
awardXP(req.user.id, challenge.xp_reward, `biochem_challenge:${challenge.id}`);
|
||||||
|
checkAchievements(req.user.id);
|
||||||
res.json({ ok: true, xp: challenge.xp_reward });
|
res.json({ ok: true, xp: challenge.xp_reward });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +310,7 @@ function saveMolecule(req, res) {
|
|||||||
JSON.stringify(atoms),
|
JSON.stringify(atoms),
|
||||||
JSON.stringify(bonds),
|
JSON.stringify(bonds),
|
||||||
).lastInsertRowid;
|
).lastInsertRowid;
|
||||||
|
checkAchievements(req.user.id); // bc_first_molecule
|
||||||
res.status(201).json({ id, formula, known: known || null });
|
res.status(201).json({ id, formula, known: known || null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -343,6 +343,19 @@ function checkPhase3Achievements(userId, userRow) {
|
|||||||
if (tb) unlockAchievement(userId, 'tb_first_para');
|
if (tb) unlockAchievement(userId, 'tb_first_para');
|
||||||
} catch (e) { /* table missing */ }
|
} catch (e) { /* table missing */ }
|
||||||
|
|
||||||
|
// ── biochem: решённые задачи + первая собранная молекула ───────
|
||||||
|
try {
|
||||||
|
const bc = db.prepare(`
|
||||||
|
SELECT COUNT(*) AS n FROM bio_user_challenges WHERE user_id = ?
|
||||||
|
`).get(userId)?.n || 0;
|
||||||
|
if (bc >= 5) unlockAchievement(userId, 'bc_5_challenges');
|
||||||
|
if (bc >= 20) unlockAchievement(userId, 'bc_20_challenges');
|
||||||
|
const mol = db.prepare(`
|
||||||
|
SELECT 1 FROM bio_user_molecules WHERE user_id = ? LIMIT 1
|
||||||
|
`).get(userId);
|
||||||
|
if (mol) unlockAchievement(userId, 'bc_first_molecule');
|
||||||
|
} catch (e) { /* bio tables missing on legacy install */ }
|
||||||
|
|
||||||
// ── flashcards: total reviews ──────────────────────────────────
|
// ── flashcards: total reviews ──────────────────────────────────
|
||||||
try {
|
try {
|
||||||
const fc = db.prepare(`
|
const fc = db.prepare(`
|
||||||
|
|||||||
Reference in New Issue
Block a user