efadc85195
Add `AND webm_path IS NOT NULL` to the /api/freehunt pool so free-hunt mode only selects ghosts with real media (and their derived webp/image fallbacks), never bare procedural-wisp ghosts.
114 lines
3.6 KiB
JavaScript
114 lines
3.6 KiB
JavaScript
import { Router } from 'express';
|
|
import db from '../db/index.js';
|
|
|
|
const router = Router();
|
|
|
|
// Rarity spawn weights (common -> legendary). Mirrors the project's
|
|
// common(7)/rare(3)/legendary(1) weighting, mapped onto the 4 star tiers.
|
|
const RARITY_WEIGHTS = { 1: 7, 2: 4, 3: 3, 4: 1 };
|
|
|
|
function rowToGhost(g) {
|
|
return {
|
|
id: g.id,
|
|
name: g.name,
|
|
displayName: g.display_name || g.name,
|
|
type: g.type,
|
|
rarity: g.rarity,
|
|
speed: g.speed,
|
|
range: g.range,
|
|
chargeShot: g.charge_shot,
|
|
health: g.health,
|
|
damage: g.damage,
|
|
ability: g.ability,
|
|
isBoss: !!g.is_boss,
|
|
setNumber: g.set_number,
|
|
setName: g.set_name,
|
|
image: g.image_path ? `/uploads/${g.image_path}` : null,
|
|
webm: g.webm_path ? `/uploads/${g.webm_path}` : null,
|
|
webp: g.webp_path ? `/uploads/${g.webp_path}` : null,
|
|
};
|
|
}
|
|
|
|
function weightedPick(ghosts) {
|
|
const weighted = [];
|
|
for (const g of ghosts) {
|
|
const w = RARITY_WEIGHTS[g.rarity] ?? 1;
|
|
for (let i = 0; i < w; i++) weighted.push(g);
|
|
}
|
|
if (!weighted.length) return null;
|
|
return weighted[Math.floor(Math.random() * weighted.length)];
|
|
}
|
|
|
|
// GET /api/scan/:code — no auth. Returns the set's ghost roster + boss.
|
|
router.get('/scan/:code', (req, res) => {
|
|
const set = db
|
|
.prepare('SELECT * FROM sets WHERE code = ? AND enabled = 1')
|
|
.get(req.params.code);
|
|
if (!set) return res.status(404).json({ error: 'unknown or disabled set code' });
|
|
|
|
const roster = db
|
|
.prepare(
|
|
`SELECT g.* FROM ghosts g
|
|
JOIN set_ghosts sg ON sg.ghost_id = g.id
|
|
WHERE sg.set_id = ? AND g.enabled = 1
|
|
ORDER BY g.is_boss DESC, g.rarity DESC, g.name`
|
|
)
|
|
.all(set.id);
|
|
|
|
const boss = set.boss_ghost_id
|
|
? db.prepare('SELECT * FROM ghosts WHERE id = ?').get(set.boss_ghost_id)
|
|
: null;
|
|
|
|
res.json({
|
|
set: {
|
|
code: set.code,
|
|
setNumber: set.set_number,
|
|
setName: set.set_name,
|
|
},
|
|
boss: boss ? rowToGhost(boss) : null,
|
|
roster: roster.map(rowToGhost),
|
|
});
|
|
});
|
|
|
|
// GET /api/freehunt — no auth. Spawns N weighted random enabled non-boss ghosts
|
|
// for free-hunt mode (procedural wisps client-side if no image).
|
|
router.get('/freehunt', (req, res) => {
|
|
const n = Math.min(parseInt(req.query.n, 10) || 3, 10);
|
|
const type = req.query.type; // optional red|yellow|blue filter
|
|
// Free hunt only spawns ghosts with an uploaded video (webm); the webp/image
|
|
// fallbacks are derived from it, so these always render on every platform.
|
|
let q = 'SELECT * FROM ghosts WHERE enabled = 1 AND is_boss = 0 AND webm_path IS NOT NULL';
|
|
const params = [];
|
|
if (type && ['red', 'yellow', 'blue'].includes(type)) {
|
|
q += ' AND type = ?';
|
|
params.push(type);
|
|
}
|
|
const pool = db.prepare(q).all(...params);
|
|
const spawns = [];
|
|
for (let i = 0; i < n && pool.length; i++) {
|
|
const pick = weightedPick(pool);
|
|
if (pick) spawns.push(rowToGhost(pick));
|
|
}
|
|
res.json({ spawns });
|
|
});
|
|
|
|
// GET /api/ghosts — no auth. Public roster browser (enabled only).
|
|
router.get('/ghosts', (req, res) => {
|
|
const { type, rarity, boss } = req.query;
|
|
let q = 'SELECT * FROM ghosts WHERE enabled = 1';
|
|
const params = [];
|
|
if (type) { q += ' AND type = ?'; params.push(type); }
|
|
if (rarity) { q += ' AND rarity = ?'; params.push(parseInt(rarity, 10)); }
|
|
if (boss === '1') q += ' AND is_boss = 1';
|
|
q += ' ORDER BY type, rarity, name';
|
|
res.json({ ghosts: db.prepare(q).all(...params).map(rowToGhost) });
|
|
});
|
|
|
|
// GET /api/abilities — no auth. Reference data.
|
|
router.get('/abilities', (req, res) => {
|
|
const rows = db.prepare('SELECT * FROM abilities ORDER BY kind, name').all();
|
|
res.json({ abilities: rows });
|
|
});
|
|
|
|
export default router;
|