//! Starter content, built with the real types so the JSON is always valid: //! a 3-room sample game, the fantasy kit (archetype catalog + prefab //! locations + a connected world), and the archetype loader the editor uses. use crate::assets; use crate::world::{GameManifest, Meaning, ObjDef, RoomDoc, Stroke}; use std::path::Path; #[allow(clippy::too_many_arguments)] fn obj(name: &str, sprite: &str, x: i32, y: i32, look: &str, takeable: bool, needs: &str, use_text: &str, wins: bool, syn: &str) -> ObjDef { ObjDef { name: name.into(), sprite: sprite.into(), x, y, look: look.into(), takeable, synonyms: syn.into(), use_text: use_text.into(), needs: needs.into(), wins, requires_flag: String::new(), sets_flag: String::new(), dialogue: Vec::new(), } } fn write_room(base: &Path, i: usize, room: &RoomDoc) { let path = base.join(format!("rooms/room{}.json", i)); if path.exists() { std::fs::rename(&path, path.with_extension("json.bak")).ok(); } if let Ok(j) = serde_json::to_string_pretty(room) { std::fs::write(&path, j).ok(); } } fn write_manifest(base: &Path, name: &str, intro: &str) { let m = GameManifest { name: name.into(), intro_text: intro.into(), ..Default::default() }; if let Ok(j) = serde_json::to_string_pretty(&m) { std::fs::write(base.join("game.json"), j).ok(); } } /// Write a fun little 3-room sample game into `base` (rooms/ + game.json). pub fn write_sample_world(base: impl AsRef) { let base = base.as_ref(); std::fs::create_dir_all(base.join("rooms")).ok(); assets::ensure_sample_sprites(&base.join("sprites").to_string_lossy()); let wall = |x: i32, y: i32, w: i32, h: i32| Stroke::Rect { x, y, w, h, color: 0, meaning: Meaning::Wall }; let paint = |x: i32, y: i32, w: i32, h: i32, c: u8| Stroke::Rect { x, y, w, h, color: c, meaning: Meaning::None }; let floor = |x: i32, y: i32, w: i32, h: i32, c: u8| Stroke::Rect { x, y, w, h, color: c, meaning: Meaning::Floor }; // Room 0 — the Boot-Up Closet (exit EAST to room 1) let room0 = RoomDoc { strokes: vec![ paint(0, 0, 160, 168, 8), floor(6, 16, 148, 146, 7), wall(0, 0, 160, 16), wall(0, 160, 160, 8), wall(0, 0, 6, 168), wall(154, 0, 6, 70), wall(154, 100, 6, 68), paint(64, 36, 28, 12, 13), ], spawn: (24, 140), exits: [None, Some(1), None, None], objects: vec![ obj("battery", "barrel", 40, 124, "A fat 9-volt, warm to the touch. Still a little zap left in it.", true, "", "", false, "cell power"), obj("crate", "crate", 120, 134, "A crate stamped MONSTER ROBOT PARTY. It rattles when you nudge it.", false, "", "", false, "box"), obj("sign", "sign", 78, 56, "A flickering sign: -> THIS WAY TO THE PARTY.", false, "", "", false, "arrow notice"), ], music: 4, }; // Room 1 — the Party Hall (WEST to 0, NORTH to 2) let room1 = RoomDoc { strokes: vec![ paint(0, 0, 160, 168, 1), floor(6, 16, 148, 146, 8), wall(0, 0, 65, 16), wall(95, 0, 65, 16), wall(0, 160, 160, 8), wall(154, 0, 6, 168), wall(0, 0, 6, 70), wall(0, 100, 6, 68), paint(20, 28, 26, 8, 13), paint(112, 26, 28, 8, 11), paint(66, 22, 28, 6, 14), ], spawn: (80, 140), exits: [Some(2), None, None, Some(0)], objects: vec![ obj("robot", "crate", 60, 124, "Another little robot, vibing hard to a beat only it can hear. It will not be interrupted.", false, "", "", false, "bot dude guy"), obj("speaker", "crate", 122, 118, "A speaker the size of a fridge. Your chassis rattles, pleasantly.", false, "", "", false, "amp boombox"), obj("punch", "barrel", 30, 142, "A barrel of suspicious party punch. You have no mouth, but you respect the commitment.", false, "", "", false, "drink barrel"), ], music: 4, }; // Room 2 — the Exit Hatch (SOUTH back to 1) — the win room let room2 = RoomDoc { strokes: vec![ paint(0, 0, 160, 168, 0), floor(6, 16, 148, 146, 8), wall(0, 0, 160, 16), wall(0, 0, 6, 168), wall(154, 0, 6, 168), wall(0, 160, 65, 8), wall(95, 160, 65, 8), paint(60, 30, 40, 10, 10), ], spawn: (80, 140), exits: [None, None, Some(1), None], objects: vec![obj( "hatch", "sign", 80, 98, "A heavy exit hatch with a dead battery slot. It won't budge without power.", false, "battery", "You slam the battery into the slot. The hatch grinds open and cold night air rushes in. You roll out into the dark. YOU ESCAPED THE PARTY. What a night.", true, "door gate exit", )], music: 2, }; for (i, room) in [room0, room1, room2].iter().enumerate() { write_room(base, i, room); } write_manifest(base, "Monster Robot Party", "You blink awake. Type 'help'."); } /// Build a standard walled room: backdrop + walkable floor + border walls, /// leaving a centered gap on each edge listed in `gaps` (0=N,1=E,2=S,3=W). fn walled(bg: u8, fl: u8, wc: u8, gaps: &[u8]) -> Vec { let w = |x: i32, y: i32, ww: i32, hh: i32| Stroke::Rect { x, y, w: ww, h: hh, color: wc, meaning: Meaning::Wall }; let mut v = vec![ Stroke::Rect { x: 0, y: 0, w: 160, h: 168, color: bg, meaning: Meaning::None }, Stroke::Rect { x: 6, y: 16, w: 148, h: 146, color: fl, meaning: Meaning::Floor }, ]; if gaps.contains(&0) { v.push(w(0, 0, 65, 16)); v.push(w(95, 0, 65, 16)); } else { v.push(w(0, 0, 160, 16)); } if gaps.contains(&2) { v.push(w(0, 160, 65, 8)); v.push(w(95, 160, 65, 8)); } else { v.push(w(0, 160, 160, 8)); } if gaps.contains(&3) { v.push(w(0, 0, 6, 70)); v.push(w(0, 100, 6, 68)); } else { v.push(w(0, 0, 6, 168)); } if gaps.contains(&1) { v.push(w(154, 0, 6, 70)); v.push(w(154, 100, 6, 68)); } else { v.push(w(154, 0, 6, 168)); } v } /// Write the fantasy starter kit into `base`: placeholder sprites, an /// object-archetype catalog (kit/objects.json), prefab locations /// (kit/locations/*.json), and a connected playable world installed to /// rooms/ (existing rooms → .bak). pub fn write_kit(base: impl AsRef) { let base = base.as_ref(); assets::write_kit_sprites(&base.join("sprites").to_string_lossy()); std::fs::create_dir_all(base.join("kit/locations")).ok(); std::fs::create_dir_all(base.join("rooms")).ok(); // Object archetype catalog — copy any of these into a room's `objects`. let catalog: Vec = vec![ obj("sword", "sword", 80, 130, "A notched iron sword. It has Opinions about orcs.", true, "", "", false, "blade weapon"), obj("spellbook", "spellbook", 80, 130, "A spellbook bound in something best not identified. The runes won't hold still.", true, "", "", false, "book tome grimoire"), obj("key", "key", 80, 130, "A heavy iron key, cold and important-looking.", true, "", "", false, "ironkey"), obj("potion", "potion", 80, 130, "A vial of bubbling red liquid. Almost certainly fine.", true, "", "", false, "vial elixir flask"), obj("torch", "torch", 80, 130, "A guttering torch. It pushes the dark back a little.", true, "", "", false, "light flame"), obj("orc", "orc", 80, 120, "A green orc, picking its teeth with a dagger. It hasn't noticed you. Yet.", false, "", "You poke the orc. It looks up. In hindsight, a mistake.", false, "monster brute goblin"), obj("villager", "villager", 80, 120, "A nervous villager. 'You're... not from the cave, are you?'", false, "", "'Sorry, nothing for you,' the villager mumbles.", false, "person man woman npc"), obj("chest", "chest", 80, 135, "A banded wooden chest. Locked.", false, "key", "The key turns; the lid yawns open and old gold spills out.", false, "box trunk"), obj("door", "door", 80, 120, "A stout wooden door, banded with iron.", false, "key", "The lock clunks and the door swings wide.", false, "gate"), obj("altar", "altar", 80, 100, "A stone altar humming with cold light. Something is meant to rest here.", false, "spellbook", "You lay the spellbook on the altar. A doorway tears open in the air. THE END.", true, "shrine pedestal"), obj("tree", "tree", 60, 80, "An ancient tree, bark like old leather.", false, "", "", false, "oak trunk wood"), obj("rock", "rock", 120, 130, "A mossy boulder. Immovable, and a little smug about it.", false, "", "", false, "boulder stone"), obj("sign", "sign", 80, 50, "A weathered signpost, paint long faded.", false, "", "", false, "notice post"), ]; if let Ok(j) = serde_json::to_string_pretty(&catalog) { std::fs::write(base.join("kit/objects.json"), j).ok(); } // Connected world: 0 forest, 1 cave, 2 dungeon, 3 inn. let mut s = walled(2, 10, 6, &[1, 2]); s.push(Stroke::Rect { x: 30, y: 120, w: 18, h: 8, color: 2, meaning: Meaning::None }); s.push(Stroke::Rect { x: 100, y: 60, w: 22, h: 8, color: 2, meaning: Meaning::None }); let forest = RoomDoc { strokes: s, spawn: (24, 130), exits: [None, Some(1), Some(3), None], objects: vec![ obj("tree", "tree", 44, 70, "An ancient tree, bark like old leather.", false, "", "", false, "oak trunk wood"), obj("sword", "sword", 80, 132, "A notched iron sword someone left in the grass. Finders keepers.", true, "", "", false, "blade weapon"), obj("rock", "rock", 120, 134, "A mossy boulder, smug and immovable.", false, "", "", false, "boulder stone"), obj("sign", "sign", 80, 44, "A signpost: CAVE east, INN south.", false, "", "", false, "notice post"), ], music: 1, }; let mut s = walled(0, 8, 0, &[0, 3]); s.push(Stroke::Rect { x: 40, y: 60, w: 10, h: 6, color: 7, meaning: Meaning::None }); s.push(Stroke::Rect { x: 110, y: 80, w: 12, h: 6, color: 7, meaning: Meaning::None }); let cave = RoomDoc { strokes: s, spawn: (80, 130), exits: [Some(2), None, None, Some(0)], objects: vec![ obj("orc", "orc", 92, 118, "A green orc picking its teeth with a dagger. It hasn't noticed you. Yet.", false, "", "You poke the orc. This was a mistake.", false, "monster brute goblin"), obj("spellbook", "spellbook", 122, 132, "A spellbook on a ledge, runes squirming. The orc is guarding it (badly).", true, "", "", false, "book tome grimoire"), obj("torch", "torch", 36, 110, "A guttering wall-torch. Grab it — it's dark deeper in.", true, "", "", false, "light flame"), obj("chest", "chest", 58, 140, "A banded chest, half-buried. Locked.", false, "key", "The key turns; old gold spills out across the cave floor.", false, "box trunk"), ], music: 2, }; let mut s = walled(0, 8, 0, &[2]); s.push(Stroke::Rect { x: 58, y: 30, w: 44, h: 10, color: 13, meaning: Meaning::None }); let dungeon = RoomDoc { strokes: s, spawn: (80, 138), exits: [None, None, Some(1), None], objects: vec![ obj("altar", "altar", 80, 98, "A stone altar humming with cold light. A book-shaped hollow waits in its surface.", false, "spellbook", "You lay the spellbook on the altar. Light pours up, a doorway tears open, and you step through. YOU ESCAPED — adventure complete. THE END.", true, "shrine pedestal"), obj("torch", "torch", 30, 120, "A torch in a rusted bracket.", true, "", "", false, "light flame"), ], music: 3, }; let mut s = walled(6, 7, 6, &[0]); s.push(Stroke::Rect { x: 40, y: 122, w: 80, h: 28, color: 4, meaning: Meaning::None }); s.push(Stroke::Rect { x: 20, y: 24, w: 8, h: 8, color: 14, meaning: Meaning::None }); let inn = RoomDoc { strokes: s, spawn: (80, 138), exits: [Some(0), None, None, None], objects: vec![ obj("villager", "villager", 60, 112, "A nervous barkeep. 'Heading to the cave? Take the key — and my prayers.'", false, "", "'Be careful in there,' the barkeep says, sliding you nothing useful.", false, "barkeep person npc"), obj("key", "key", 44, 140, "An iron key hanging by the door. The barkeep nods at you to take it.", true, "", "", false, "ironkey"), obj("potion", "potion", 112, 132, "A complimentary vial of bubbling red liquid. Probably fine.", true, "", "", false, "vial elixir flask"), obj("barrel", "barrel", 122, 120, "A barrel of inn ale. Smells like courage and mistakes.", false, "", "", false, "ale keg"), ], music: 4, }; let rooms: [(&str, &RoomDoc); 4] = [("forest", &forest), ("cave", &cave), ("dungeon", &dungeon), ("inn", &inn)]; for (name, r) in rooms.iter() { if let Ok(j) = serde_json::to_string_pretty(*r) { std::fs::write(base.join(format!("kit/locations/{}.json", name)), j).ok(); } } for (i, (_n, r)) in rooms.iter().enumerate() { write_room(base, i, r); } write_manifest(base, "The Altar Quest", "You wake in a quiet forest. Type 'help'."); } /// Load every object archetype the kit ships (fantasy `kit/objects.json` + /// every `kit/themes/*/objects.json`) so the editor can stamp them by clicking. pub fn load_archetypes(base: impl AsRef) -> Vec { let base = base.as_ref(); let mut files = vec![base.join("kit/objects.json")]; if let Ok(rd) = std::fs::read_dir(base.join("kit/themes")) { for e in rd.flatten() { let p = e.path().join("objects.json"); if p.exists() { files.push(p); } } } let mut out = Vec::new(); for f in files { if let Ok(s) = std::fs::read_to_string(&f) { if let Ok(v) = serde_json::from_str::>(&s) { out.extend(v); } } } out }