From 28e57a77eef2bbdc24d33e7de8ffa1a7594bd50e Mon Sep 17 00:00:00 2001 From: Aaron Wood Date: Thu, 9 Apr 2026 12:34:08 -0400 Subject: [PATCH] Add dev seed data: auto-creates campaign with Limpie and Brynn on fresh DB --- server/src/index.ts | 3 + server/src/seed-dev-data.ts | 210 ++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 server/src/seed-dev-data.ts diff --git a/server/src/index.ts b/server/src/index.ts index 8f6087a..e5a9972 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -8,6 +8,7 @@ import characterRoutes from "./routes/characters.js"; import gameItemRoutes from "./routes/game-items.js"; import gameTalentRoutes from "./routes/game-talents.js"; import rollRoutes from "./routes/rolls.js"; +import { seedDevData } from "./seed-dev-data.js"; const app = express(); const httpServer = createServer(app); @@ -30,6 +31,8 @@ app.use("/api/game-items", gameItemRoutes); app.use("/api/game-talents", gameTalentRoutes); app.use("/api/campaigns/:campaignId/rolls", rollRoutes); +seedDevData(); + const PORT = process.env.PORT || 3000; httpServer.listen(PORT, () => { console.log(`Shadowdark server running on http://localhost:${PORT}`); diff --git a/server/src/seed-dev-data.ts b/server/src/seed-dev-data.ts new file mode 100644 index 0000000..50d2429 --- /dev/null +++ b/server/src/seed-dev-data.ts @@ -0,0 +1,210 @@ +import db from "./db.js"; + +export function seedDevData() { + const hasCampaigns = ( + db.prepare("SELECT COUNT(*) as c FROM campaigns").get() as { c: number } + ).c; + if (hasCampaigns > 0) return; + + // Create campaign + db.prepare( + "INSERT INTO campaigns (name) VALUES ('Tomb of the Serpent King')", + ).run(); + + const insertChar = db.prepare(` + INSERT INTO characters (campaign_id, name, class, ancestry, level, xp, hp_current, hp_max, ac, alignment, title, background, deity, languages, gp, sp, cp, color) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const insertStat = db.prepare( + "INSERT INTO character_stats (character_id, stat_name, value) VALUES (?, ?, ?)", + ); + + const insertGear = db.prepare( + "INSERT INTO character_gear (character_id, name, type, slot_count, properties, effects, game_item_id) VALUES (?, ?, ?, ?, ?, ?, ?)", + ); + + const insertTalent = db.prepare( + "INSERT INTO character_talents (character_id, name, description, effect, game_talent_id) VALUES (?, ?, ?, ?, ?)", + ); + + // --- Limpie --- + const limpie = insertChar.run( + 1, + "Limpie", + "Thief", + "Goblin", + 1, + 6, + 1, + 1, + 10, + "Lawful", + "Footpad", + "Chirurgeo", + "Madeera the Covenant", + "Common, Goblin", + 8, + 0, + 0, + "hsl(45, 60%, 65%)", + ); + const limpieId = limpie.lastInsertRowid; + + // Limpie stats + const limpieStats: [string, number][] = [ + ["STR", 11], + ["DEX", 11], + ["CON", 8], + ["INT", 10], + ["WIS", 14], + ["CHA", 10], + ]; + for (const [name, value] of limpieStats) { + insertStat.run(limpieId, name, value); + } + + // Limpie gear + insertGear.run( + limpieId, + "Shortsword", + "weapon", + 1, + "{}", + '{"damage":"1d6","melee":true,"stat":"STR"}', + null, + ); + insertGear.run( + limpieId, + "Shortbow", + "weapon", + 1, + "{}", + '{"damage":"1d4","ranged":true,"stat":"DEX","two_handed":true}', + null, + ); + insertGear.run( + limpieId, + "Leather Armor", + "armor", + 1, + "{}", + '{"ac_base":11,"ac_dex":true}', + null, + ); + insertGear.run(limpieId, "Shield", "armor", 1, "{}", '{"ac_bonus":2}', null); + insertGear.run(limpieId, "Arrows (20)", "gear", 1, "{}", "{}", null); + insertGear.run(limpieId, "Torch", "gear", 1, "{}", "{}", null); + insertGear.run(limpieId, "Rations", "gear", 1, "{}", "{}", null); + insertGear.run(limpieId, "Rope (60ft)", "gear", 1, "{}", "{}", null); + insertGear.run(limpieId, "Thieves' Tools", "gear", 1, "{}", "{}", null); + + // Limpie talents + insertTalent.run( + limpieId, + "Backstab", + "Extra 1 + half level (round down) weapon dice of damage with surprise attacks", + '{"damage_bonus_surprise":true}', + null, + ); + insertTalent.run( + limpieId, + "Thievery", + "Trained in climbing, sneaking, hiding, disguise, finding & disabling traps, delicate tasks", + "{}", + null, + ); + insertTalent.run( + limpieId, + "Keen Senses", + "Can't be surprised", + '{"immune_surprise":true}', + null, + ); + + // --- Brynn --- + const brynn = insertChar.run( + 1, + "Brynn", + "Fighter", + "Human", + 2, + 15, + 8, + 10, + 10, + "Neutral", + "the Bold", + "Soldier", + "Gede", + "Common", + 25, + 5, + 10, + "hsl(200, 60%, 65%)", + ); + const brynnId = brynn.lastInsertRowid; + + // Brynn stats + const brynnStats: [string, number][] = [ + ["STR", 16], + ["DEX", 12], + ["CON", 14], + ["INT", 8], + ["WIS", 10], + ["CHA", 11], + ]; + for (const [name, value] of brynnStats) { + insertStat.run(brynnId, name, value); + } + + // Brynn gear + insertGear.run( + brynnId, + "Longsword", + "weapon", + 1, + "{}", + '{"damage":"1d8","melee":true,"stat":"STR"}', + null, + ); + insertGear.run( + brynnId, + "Chainmail", + "armor", + 1, + "{}", + '{"ac_base":13,"ac_dex":true}', + null, + ); + insertGear.run(brynnId, "Shield", "armor", 1, "{}", '{"ac_bonus":2}', null); + insertGear.run( + brynnId, + "Javelin", + "weapon", + 1, + "{}", + '{"damage":"1d4","melee":true,"stat":"STR","thrown":true,"range":"far"}', + null, + ); + insertGear.run(brynnId, "Torch", "gear", 1, "{}", "{}", null); + insertGear.run(brynnId, "Rations", "gear", 1, "{}", "{}", null); + + // Brynn talents + insertTalent.run( + brynnId, + "Weapon Mastery", + "+1 to attack and damage with longswords", + '{"attack_bonus":1,"damage_bonus":1}', + null, + ); + insertTalent.run( + brynnId, + "Grit", + "+2 HP and +1 HP each level", + '{"hp_bonus":2}', + null, + ); + + console.log("Dev data seeded: campaign + 2 characters (Limpie & Brynn)"); +}