Add better-sqlite3 seed script for ghost-roster.db
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* seed-ghosts.js — builds a standalone ghost-roster SQLite DB for Newbury Nights.
|
||||
*
|
||||
* Separate from the live app DB (Free Hunt pool). Read-reference for the hunt
|
||||
* loop until you merge. Re-runnable: drops + rebuilds the roster tables only.
|
||||
*
|
||||
* Usage:
|
||||
* node seed-ghosts.js # writes ./ghost-roster.db
|
||||
* node seed-ghosts.js /path/to.db # custom output path
|
||||
*
|
||||
* Requires: better-sqlite3, and ghosts.json / abilities.json / locations.json
|
||||
* sitting beside this script.
|
||||
*/
|
||||
const Database = require('better-sqlite3');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const here = __dirname;
|
||||
const dbPath = process.argv[2] || path.join(here, 'ghost-roster.db');
|
||||
|
||||
const ghosts = JSON.parse(fs.readFileSync(path.join(here, 'ghosts.json'), 'utf8'));
|
||||
const abilities = JSON.parse(fs.readFileSync(path.join(here, 'abilities.json'), 'utf8'));
|
||||
const locations = JSON.parse(fs.readFileSync(path.join(here, 'locations.json'), 'utf8'));
|
||||
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
// --- schema (roster tables only; namespaced to avoid clashing with app tables) ---
|
||||
db.exec(`
|
||||
DROP TABLE IF EXISTS roster_ghost_locations;
|
||||
DROP TABLE IF EXISTS roster_ghosts;
|
||||
DROP TABLE IF EXISTS roster_abilities;
|
||||
DROP TABLE IF EXISTS roster_locations;
|
||||
|
||||
CREATE TABLE roster_abilities (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK (type IN ('Common','Boss')),
|
||||
charges INTEGER,
|
||||
cooldown_s INTEGER,
|
||||
effect TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE roster_locations (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL, -- plane | image-target | qr
|
||||
backdrop TEXT,
|
||||
qr_code TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE roster_ghosts (
|
||||
id TEXT PRIMARY KEY, -- name slug, e.g. nagging-nathan
|
||||
name TEXT NOT NULL,
|
||||
color TEXT NOT NULL CHECK (color IN ('Red','Blue','Yellow')),
|
||||
rarity TEXT NOT NULL CHECK (rarity IN ('Common','Rare','Epic','Legendary')),
|
||||
rarity_tier INTEGER NOT NULL, -- 1-4
|
||||
speed_pips INTEGER,
|
||||
range_pips INTEGER,
|
||||
charge_pips INTEGER,
|
||||
health_max INTEGER,
|
||||
damage_max INTEGER,
|
||||
ability_id TEXT REFERENCES roster_abilities(id),
|
||||
face_sprite TEXT,
|
||||
fullbody_sprite TEXT,
|
||||
webm_path TEXT,
|
||||
webp_path TEXT,
|
||||
ar_health REAL,
|
||||
haunt_value REAL,
|
||||
is_boss INTEGER,
|
||||
collection_order INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE roster_ghost_locations (
|
||||
ghost_id TEXT NOT NULL REFERENCES roster_ghosts(id) ON DELETE CASCADE,
|
||||
location_id TEXT NOT NULL REFERENCES roster_locations(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (ghost_id, location_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_roster_ghosts_color ON roster_ghosts(color);
|
||||
CREATE INDEX idx_roster_ghosts_rarity ON roster_ghosts(rarity_tier);
|
||||
`);
|
||||
|
||||
// --- inserts (transactional) ---
|
||||
const insAbility = db.prepare(`
|
||||
INSERT INTO roster_abilities (id,name,type,charges,cooldown_s,effect)
|
||||
VALUES (@id,@name,@type,@charges,@cooldownSeconds,@effect)`);
|
||||
|
||||
const insLocation = db.prepare(`
|
||||
INSERT INTO roster_locations (id,name,type,backdrop,qr_code)
|
||||
VALUES (@id,@name,@type,@backdrop,@qrCode)`);
|
||||
|
||||
const insGhost = db.prepare(`
|
||||
INSERT INTO roster_ghosts
|
||||
(id,name,color,rarity,rarity_tier,speed_pips,range_pips,charge_pips,
|
||||
health_max,damage_max,ability_id,face_sprite,fullbody_sprite,
|
||||
webm_path,webp_path,ar_health,haunt_value,is_boss,collection_order)
|
||||
VALUES
|
||||
(@id,@name,@color,@rarity,@rarityTier,@speedPips,@rangePips,@chargePips,
|
||||
@healthMax,@damageMax,@abilityId,@faceSprite,@fullBodySprite,
|
||||
@webmPath,@webpPath,@arHealth,@hauntValue,@isBoss,@collectionOrder)`);
|
||||
|
||||
const insGhostLoc = db.prepare(`
|
||||
INSERT OR IGNORE INTO roster_ghost_locations (ghost_id,location_id)
|
||||
VALUES (?,?)`);
|
||||
|
||||
const seed = db.transaction(() => {
|
||||
for (const a of abilities) insAbility.run(a);
|
||||
for (const l of locations) insLocation.run({ backdrop:null, qrCode:null, ...l });
|
||||
let order = 0;
|
||||
for (const g of ghosts) {
|
||||
insGhost.run({
|
||||
isBoss: g.isBoss == null ? null : (g.isBoss ? 1 : 0),
|
||||
collectionOrder: order++,
|
||||
...g
|
||||
});
|
||||
for (const locId of (g.locations || [])) insGhostLoc.run(g.id, locId);
|
||||
}
|
||||
});
|
||||
seed();
|
||||
|
||||
const n = id => db.prepare(`SELECT COUNT(*) c FROM ${id}`).get().c;
|
||||
console.log(`Seeded ${dbPath}`);
|
||||
console.log(` abilities: ${n('roster_abilities')}`);
|
||||
console.log(` locations: ${n('roster_locations')}`);
|
||||
console.log(` ghosts: ${n('roster_ghosts')}`);
|
||||
console.log(` by color:`, db.prepare(
|
||||
`SELECT color, COUNT(*) c FROM roster_ghosts GROUP BY color`).all());
|
||||
console.log(` by rarity:`, db.prepare(
|
||||
`SELECT rarity, COUNT(*) c FROM roster_ghosts GROUP BY rarity_tier ORDER BY rarity_tier`).all());
|
||||
db.close();
|
||||
Reference in New Issue
Block a user