commit 50817ced593e185bc22f091a04fc892ddb1d4552 Author: m3ultra Date: Sun Jul 26 20:49:05 2026 +1000 NOMEANSNO QUEST v1.0 — the first MRPCI game Six rooms of band history: the 1979 basement (Portastudio, Mom, the Castle box), D.O.A. at UVic, the alley wall that named the band, Alternative Tentacles 1987 (Jello signs you), the Roskilde main stage 1994 (MODELBEAST flux matte painting quantized by the engine; ops paint only walkability + cycling footlights), and the Polish field 1997 (the $2,500 van ransom — with one fatal dialogue option to prove checkpoint mercy). 60 points, deterministic playthrough in tests/, zero engine forks: pure game data + rhai. Generated art via tools/gen_sprites.py + gen_rooms.py. Co-Authored-By: Claude Fable 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc1040a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +saves/ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..914a1ac --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# NOMEANSNO QUEST + +*Two brothers. One basement. Sixty thousand lighters. $2,500 in kroner.* + +The first game built on **[MRPCI](../mrpci)** (the SCI-generation Monster +Robot Party engine). You are Rob Wright, Victoria BC, 1979 — play through +the true(ish) history of NoMeansNo: the D.O.A. gig that lit the fuse, the +graffiti that named the band, the Portastudio demo, Jello Biafra's office, +the Roskilde main stage in '94, and a certain van in a certain Polish field +in '97. Six rooms, 60 points, one extremely fatal thing you can say to a +man in a tracksuit. + +A fan tribute, played with love. Read *NoMeansNo: From Obscurity to +Oblivion* (PM Press) for the real thing. + +## Play + +```sh +./run.sh # needs ../mrpci checked out alongside +``` + +Right-click cycles verbs (walk/look/do/talk), or press Enter and just type +(`use blank tape on portastudio`). `I` inventory · F5/F7 save/restore · +F8 is the room editor if you want to remodel the basement. + +## Repo layout + +Pure game data — no engine code lives here (engine changes go to mrpci): + +``` +game.json manifest: Rob as the ego, verbs, 60-point score +rooms/room0-5.json generated by tools/gen_rooms.py (edit that, not these) +scripts/*.rhai story logic: flags, the demo, the show, the ransom +sprites/*.png generated by tools/gen_sprites.py (pixel people + props) +pics/roskilde.png MODELBEAST flux_local matte painting (tools/mb_background.py) +tests/playthrough.jsonl canonical win path — replay with: + mrpci-headless --game . --script tests/playthrough.jsonl +``` + +The Roskilde room is the house art workflow demonstrated: an AI matte +painting supplies the look, the room's ops paint only walkability, depth +and the cycling footlights over it. The engine quantizes the painting to +the room palette at load. + +## History points the game leans on + +- 1979: the brothers, the basement, the 180 lb TEAC Portastudio, Mom's laundry +- 1980: D.O.A. at UVic; the anti-rape slogan on a wall becomes the name +- 1987: Jello Biafra licenses *Sex Mad* to Alternative Tentacles +- 1994: Roskilde main stage, after Peter Gabriel, when Sepultura's van died +- 1997: the gear van stolen in Poland, ransomed back for $2,500 in a field +- 2015/2016: Hall of Fame; retirement after 37 years, no hiatus, no drama diff --git a/game.json b/game.json new file mode 100644 index 0000000..ff865d5 --- /dev/null +++ b/game.json @@ -0,0 +1,19 @@ +{ + "name": "NOMEANSNO QUEST", + "start_room": 0, + "intro_text": "Victoria, BC. 1979. You are Rob Wright, twenty-five, back from Calgary with a bass guitar and no plan. Your kid brother can really drum. The basement is free after laundry hours. (Right-click cycles verbs. Or just type.)", + "max_score": 60, + "ego_sprite": "rob", + "verbs": { + "play": "jam riff shred strum drum", + "listen": "hear" + }, + "defaults": { + "look": "Wood panel, damp concrete, the smell of the Island. 1979 has a texture.", + "do": "Your fingers are for the bass, not for that.", + "talk": "It says nothing. Very post-punk of it.", + "listen": "Somewhere, a hi-hat counts to nine and starts over.", + "smell": "Basement damp and burnt tube amp. Home.", + "play": "You slap out three notes. The room does not applaud. Yet." + } +} diff --git a/pics/roskilde.png b/pics/roskilde.png new file mode 100644 index 0000000..fb2270e Binary files /dev/null and b/pics/roskilde.png differ diff --git a/rooms/room0.json b/rooms/room0.json new file mode 100644 index 0000000..4db6e63 --- /dev/null +++ b/rooms/room0.json @@ -0,0 +1,922 @@ +{ + "name": "The Basement, 1979", + "enter_text": "Wood panel, concrete, one bare bulb. John's kit crowds the rug; the 180-pound Portastudio owns the workbench. Through the wall, Mom's washer keeps better time than most drummers.", + "ops": [ + { + "FbmFill": { + "x": 0, + "y": 0, + "w": 320, + "h": 100, + "ramp": [ + 110, + 110, + 153, + 110 + ], + "scale": 34.0, + "octaves": 2, + "seed": 3, + "ink": { + "color": 0, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 0 + ], + [ + 0, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 26, + 0 + ], + [ + 26, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 52, + 0 + ], + [ + 52, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 78, + 0 + ], + [ + 78, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 104, + 0 + ], + [ + 104, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 130, + 0 + ], + [ + 130, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 156, + 0 + ], + [ + 156, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 182, + 0 + ], + [ + 182, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 208, + 0 + ], + [ + 208, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 234, + 0 + ], + [ + 234, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 260, + 0 + ], + [ + 260, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 286, + 0 + ], + [ + 286, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 312, + 0 + ], + [ + 312, + 99 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 268, + "y": 10, + "w": 34, + "h": 22, + "ink": { + "color": 20, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 271, + "y": 13, + "w": 28, + "h": 16, + "ink": { + "color": 162, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 285, + 13 + ], + [ + 285, + 28 + ] + ], + "ink": { + "color": 20, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "VoronoiFill": { + "x": 0, + "y": 100, + "w": 320, + "h": 90, + "cell": 44, + "colors": [ + 22, + 22, + 21, + 22 + ], + "edge_color": 21, + "edge": 1.0, + "seed": 9, + "ink": { + "color": 0, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 6, + "y": 62, + "w": 30, + "h": 38, + "ink": { + "color": 28, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 10, + "y": 68, + "w": 22, + "h": 22, + "ink": { + "color": 25, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 21, + "cy": 79, + "rx": 8, + "ry": 8, + "ink": { + "color": 126, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 24, + "y": 64, + "w": 6, + "h": 3, + "ink": { + "color": 183, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 4, + "y": 40, + "w": 36, + "h": 6, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 8, + "y": 30, + "w": 8, + "h": 10, + "ink": { + "color": 200, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 20, + "y": 32, + "w": 8, + "h": 8, + "ink": { + "color": 89, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 96, + "cy": 148, + "rx": 44, + "ry": 14, + "ink": { + "color": 68, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 128, + "y": 84, + "w": 66, + "h": 16, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 128, + "y": 84, + "w": 66, + "h": 3, + "ink": { + "color": 153, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 214, + "y": 38, + "w": 26, + "h": 34, + "ink": { + "color": 30, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 217, + "y": 42, + "w": 20, + "h": 8, + "ink": { + "color": 176, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 217, + "y": 54, + "w": 20, + "h": 3, + "ink": { + "color": 19, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 217, + "y": 60, + "w": 14, + "h": 3, + "ink": { + "color": 19, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 292, + "y": 96, + "w": 28, + "h": 8, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 292, + "y": 88, + "w": 28, + "h": 8, + "ink": { + "color": 153, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 292, + "y": 80, + "w": 28, + "h": 8, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 292, + "y": 72, + "w": 28, + "h": 8, + "ink": { + "color": 153, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 292, + "y": 64, + "w": 28, + "h": 8, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 292, + "y": 100, + "w": 28, + "h": 90, + "ink": { + "color": 21, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + } + ], + "scale": { + "horizon_y": 92, + "min_scale": 0.72, + "full_y": 186 + }, + "spawn": [ + 160, + 150 + ], + "exits": [ + null, + 1, + 3, + null + ], + "exit_flags": [ + "", + "", + "demo_done", + "" + ], + "exit_blocked": [ + "", + "", + "No demo, no destiny. The Portastudio is right there.", + "" + ], + "hotspots": [ + { + "name": "gig poster", + "rect": [ + 212, + 36, + 30, + 38 + ], + "msgs": { + "look": "D.O.A. — TONIGHT — UVic. Vancouver's finest, one ferry away. John already circled it.", + "do": "You're going. Obviously you're going. (Walk east, up the stairs.)" + } + }, + { + "name": "washer", + "rect": [ + 4, + 60, + 34, + 42 + ], + "msgs": { + "look": "Mom's washing machine, mid-cycle. A wet 4/4 that never rushes and never drags.", + "listen": "Chunk. Chunk. Chunk. Honestly? Tighter than most bands you've seen.", + "do": "It's Mom's. Some things are sacred." + } + }, + { + "name": "window", + "rect": [ + 266, + 8, + 38, + 26 + ], + "msgs": { + "look": "Grey Victoria daylight. The Island doesn't hurry, and neither does the rain." + } + }, + { + "name": "workbench", + "rect": [ + 126, + 82, + 70, + 20 + ], + "msgs": { + "look": "The workbench holds the TEAC four-track like an altar holds a relic." + } + } + ], + "props": [ + { + "name": "portastudio", + "sprite": "portastudio", + "x": 160, + "y": 104, + "synonyms": "four-track teac tascam recorder deck", + "msgs": { + "look": "The TEAC Portastudio. 180 pounds of overdubbing possibility. It wants tape and it wants a reason.", + "do": "It powers up with a hum like a small appliance dreaming big.", + "listen": "Tape hiss. The sound of potential." + }, + "solid": true + }, + { + "name": "castle box", + "sprite": "castlebox", + "x": 224, + "y": 146, + "synonyms": "box junk castle", + "msgs": { + "look": "A box of gear from Castle, the cover band you both did time in. Cables, setlists nobody needs, and — is that a fresh reel of tape?" + }, + "solid": true + }, + { + "name": "drum kit", + "sprite": "drumkit", + "x": 92, + "y": 136, + "synonyms": "drums kit", + "msgs": { + "look": "John's kit, tuned like a jazz kit because he IS a jazz kid. The high school band has no idea what it's incubating.", + "play": "You are a bassist. There are laws." + }, + "solid": true + } + ], + "npcs": [ + { + "name": "John", + "sprite": "john", + "x": 66, + "y": 148, + "portrait": "john-face", + "wander": 12, + "msgs": { + "look": "Your kid brother. Eight years younger, already swings harder than drummers twice his age." + }, + "dialogue": [ + { + "says": "Sticks spin in his fingers. “So? Are we a band, or are we two guys who own instruments?”", + "choices": [ + { + "text": "We need a sound of our own first.", + "goto": 1 + }, + { + "text": "Heard D.O.A. play UVic tonight.", + "goto": 2 + }, + { + "text": "Later, John.", + "goto": -1 + } + ] + }, + { + "says": "“No guitar player, eh? Fine. You be the guitar. I'll be the horn section.” He plays a bar of 9/8 like it's nothing.", + "choices": [ + { + "text": "That. Exactly that.", + "goto": -1 + } + ] + }, + { + "says": "“Then what are we still doing in the basement? GO. Take notes. Loud ones.”", + "choices": [ + { + "text": "On it.", + "goto": -1 + } + ] + } + ] + }, + { + "name": "Mom", + "sprite": "mom", + "x": 26, + "y": 126, + "portrait": "mom-face", + "wander": 6, + "msgs": { + "look": "Mom, running the laundry room like mission control." + }, + "dialogue": [ + { + "says": "“Play all you like, love — the whites are done at eight.” She pauses. “The loud one in 7/4 was nice.”", + "choices": [ + { + "text": "You could tell it was 7/4?", + "goto": 1 + }, + { + "text": "Thanks, Mom.", + "goto": -1 + } + ] + }, + { + "says": "“I've been listening to that washer for twenty years, Robert. I know time when I hear it.”", + "choices": [ + { + "text": "...Mom might be the best musician in the house.", + "goto": -1 + } + ] + } + ] + } + ], + "music": 1, + "script": "room0.rhai" +} \ No newline at end of file diff --git a/rooms/room1.json b/rooms/room1.json new file mode 100644 index 0000000..5cf92b2 --- /dev/null +++ b/rooms/room1.json @@ -0,0 +1,734 @@ +{ + "name": "UVic — D.O.A., 1980", + "enter_text": "A university hall pretending it's a bunker. D.O.A. detonate on stage. The floor moves like weather.", + "ops": [ + { + "VGradient": { + "x": 0, + "y": 0, + "w": 320, + "h": 96, + "ramp": [ + 33, + 69, + 32 + ], + "ink": { + "color": 0, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Polygon": { + "pts": [ + [ + 70, + 0 + ], + [ + 130, + 0 + ], + [ + 170, + 92 + ], + [ + 110, + 92 + ] + ], + "ink": { + "color": 113, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Polygon": { + "pts": [ + [ + 200, + 0 + ], + [ + 250, + 0 + ], + [ + 210, + 92 + ], + [ + 160, + 92 + ] + ], + "ink": { + "color": 147, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 56, + "y": 58, + "w": 208, + "h": 38, + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 56, + "y": 58, + "w": 208, + "h": 4, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 90, + "y": 40, + "w": 14, + "h": 22, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 220, + "y": 44, + "w": 16, + "h": 18, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 97, + "cy": 38, + "rx": 4, + "ry": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 228, + "cy": 42, + "rx": 4, + "ry": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 58, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 248, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 70, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 249, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 82, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 250, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 94, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 251, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 106, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 252, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 118, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 253, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 130, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 254, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 142, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 255, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 154, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 248, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 166, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 249, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 178, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 250, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 190, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 251, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 202, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 252, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 214, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 253, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 226, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 254, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 238, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 255, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 250, + "y": 92, + "w": 8, + "h": 4, + "ink": { + "color": 248, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "VGradient": { + "x": 0, + "y": 96, + "w": 320, + "h": 94, + "ramp": [ + 18, + 17 + ], + "ink": { + "color": 0, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 16, + "cy": 108, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 40, + "cy": 117, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 64, + "cy": 126, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 88, + "cy": 108, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 112, + "cy": 117, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 136, + "cy": 126, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 160, + "cy": 108, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 184, + "cy": 117, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 208, + "cy": 126, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 232, + "cy": 108, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 256, + "cy": 117, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 280, + "cy": 126, + "rx": 6, + "ry": 5, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Scatter": { + "x": 0, + "y": 96, + "w": 320, + "h": 40, + "colors": [ + 19, + 18 + ], + "density": 14, + "seed": 21, + "ink": { + "color": 0, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + } + ], + "scale": { + "horizon_y": 96, + "min_scale": 0.7, + "full_y": 186 + }, + "spawn": [ + 160, + 164 + ], + "exits": [ + null, + null, + 2, + 0 + ], + "hotspots": [ + { + "name": "stage", + "rect": [ + 56, + 20, + 208, + 76 + ], + "msgs": { + "do": "Security gives you the look that says: no.", + "listen": "Loud enough to reset your heartbeat to theirs." + } + }, + { + "name": "pit", + "rect": [ + 80, + 110, + 160, + 50 + ], + "msgs": { + "look": "The pit. A weather system with elbows." + }, + "trigger": true + } + ], + "props": [ + { + "name": "flyer", + "sprite": "flyer", + "x": 288, + "y": 150, + "synonyms": "handbill paper", + "msgs": { + "look": "A gig flyer, boot-printed. You'll keep it forever and lie about why." + }, + "takeable": true + } + ], + "npcs": [ + { + "name": "Joey", + "sprite": "joey", + "x": 150, + "y": 90, + "fixed_scale": 0.85, + "msgs": { + "look": "Joey Shithead, mid-war-cry. Vancouver hardcore, delivered at terminal velocity.", + "talk": "He can't hear you. Nobody will hear anything until Thursday." + } + } + ], + "music": 3, + "script": "room1.rhai" +} \ No newline at end of file diff --git a/rooms/room2.json b/rooms/room2.json new file mode 100644 index 0000000..6798451 --- /dev/null +++ b/rooms/room2.json @@ -0,0 +1,3076 @@ +{ + "name": "The Alley", + "enter_text": "Behind the venue, rain stitching the puddles. Someone has written three words on the brick, big enough to be a law.", + "ops": [ + { + "FbmFill": { + "x": 0, + "y": 0, + "w": 320, + "h": 112, + "ramp": [ + 111, + 111, + 147 + ], + "scale": 24.0, + "octaves": 2, + "seed": 31, + "ink": { + "color": 0, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 6 + ], + [ + 319, + 6 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 15 + ], + [ + 319, + 15 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 24 + ], + [ + 319, + 24 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 33 + ], + [ + 319, + 33 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 42 + ], + [ + 319, + 42 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 51 + ], + [ + 319, + 51 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 60 + ], + [ + 319, + 60 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 69 + ], + [ + 319, + 69 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 78 + ], + [ + 319, + 78 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 87 + ], + [ + 319, + 87 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 96 + ], + [ + 319, + 96 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 105 + ], + [ + 319, + 105 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Scatter": { + "x": 0, + "y": 0, + "w": 320, + "h": 112, + "colors": [ + 68, + 22 + ], + "density": 8, + "seed": 32, + "ink": { + "color": null, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 46, + 64 + ], + [ + 46, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 47, + 64 + ], + [ + 47, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 46, + 40 + ], + [ + 62, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 47, + 40 + ], + [ + 63, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 62, + 64 + ], + [ + 62, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 63, + 64 + ], + [ + 63, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 70, + 40 + ], + [ + 86, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 71, + 40 + ], + [ + 87, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 86, + 40 + ], + [ + 86, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 87, + 40 + ], + [ + 87, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 86, + 64 + ], + [ + 70, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 87, + 64 + ], + [ + 71, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 70, + 64 + ], + [ + 70, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 71, + 64 + ], + [ + 71, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 106, + 64 + ], + [ + 106, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 107, + 64 + ], + [ + 107, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 106, + 40 + ], + [ + 114, + 52 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 107, + 40 + ], + [ + 115, + 52 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 114, + 52 + ], + [ + 122, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 115, + 52 + ], + [ + 123, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 122, + 40 + ], + [ + 122, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 123, + 40 + ], + [ + 123, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 146, + 40 + ], + [ + 130, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 147, + 40 + ], + [ + 131, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 130, + 40 + ], + [ + 130, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 131, + 40 + ], + [ + 131, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 130, + 64 + ], + [ + 146, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 131, + 64 + ], + [ + 147, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 130, + 52 + ], + [ + 142, + 52 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 131, + 52 + ], + [ + 143, + 52 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 154, + 64 + ], + [ + 162, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 155, + 64 + ], + [ + 163, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 162, + 40 + ], + [ + 170, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 163, + 40 + ], + [ + 171, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 158, + 56 + ], + [ + 166, + 56 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 159, + 56 + ], + [ + 167, + 56 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 178, + 64 + ], + [ + 178, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 179, + 64 + ], + [ + 179, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 178, + 40 + ], + [ + 194, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 179, + 40 + ], + [ + 195, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 194, + 64 + ], + [ + 194, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 195, + 64 + ], + [ + 195, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 218, + 40 + ], + [ + 202, + 44 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 219, + 40 + ], + [ + 203, + 44 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 202, + 44 + ], + [ + 218, + 60 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 203, + 44 + ], + [ + 219, + 60 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 218, + 60 + ], + [ + 202, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 219, + 60 + ], + [ + 203, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 238, + 64 + ], + [ + 238, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 239, + 64 + ], + [ + 239, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 238, + 40 + ], + [ + 254, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 239, + 40 + ], + [ + 255, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 254, + 64 + ], + [ + 254, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 255, + 64 + ], + [ + 255, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 262, + 40 + ], + [ + 278, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 263, + 40 + ], + [ + 279, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 278, + 40 + ], + [ + 278, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 279, + 40 + ], + [ + 279, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 278, + 64 + ], + [ + 262, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 279, + 64 + ], + [ + 263, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 262, + 64 + ], + [ + 262, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 263, + 64 + ], + [ + 263, + 40 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 286, + 40 + ], + [ + 286, + 56 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 287, + 40 + ], + [ + 287, + 56 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 286, + 64 + ], + [ + 286, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 287, + 64 + ], + [ + 287, + 64 + ] + ], + "ink": { + "color": 31, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 47, + 65 + ], + [ + 47, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 48, + 65 + ], + [ + 48, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 47, + 41 + ], + [ + 63, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 48, + 41 + ], + [ + 64, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 63, + 65 + ], + [ + 63, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 64, + 65 + ], + [ + 64, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 71, + 41 + ], + [ + 87, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 72, + 41 + ], + [ + 88, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 87, + 41 + ], + [ + 87, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 88, + 41 + ], + [ + 88, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 87, + 65 + ], + [ + 71, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 88, + 65 + ], + [ + 72, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 71, + 65 + ], + [ + 71, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 72, + 65 + ], + [ + 72, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 107, + 65 + ], + [ + 107, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 108, + 65 + ], + [ + 108, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 107, + 41 + ], + [ + 115, + 53 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 108, + 41 + ], + [ + 116, + 53 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 115, + 53 + ], + [ + 123, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 116, + 53 + ], + [ + 124, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 123, + 41 + ], + [ + 123, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 124, + 41 + ], + [ + 124, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 147, + 41 + ], + [ + 131, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 148, + 41 + ], + [ + 132, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 131, + 41 + ], + [ + 131, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 132, + 41 + ], + [ + 132, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 131, + 65 + ], + [ + 147, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 132, + 65 + ], + [ + 148, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 131, + 53 + ], + [ + 143, + 53 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 132, + 53 + ], + [ + 144, + 53 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 155, + 65 + ], + [ + 163, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 156, + 65 + ], + [ + 164, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 163, + 41 + ], + [ + 171, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 164, + 41 + ], + [ + 172, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 159, + 57 + ], + [ + 167, + 57 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 160, + 57 + ], + [ + 168, + 57 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 179, + 65 + ], + [ + 179, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 180, + 65 + ], + [ + 180, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 179, + 41 + ], + [ + 195, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 180, + 41 + ], + [ + 196, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 195, + 65 + ], + [ + 195, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 196, + 65 + ], + [ + 196, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 219, + 41 + ], + [ + 203, + 45 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 220, + 41 + ], + [ + 204, + 45 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 203, + 45 + ], + [ + 219, + 61 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 204, + 45 + ], + [ + 220, + 61 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 219, + 61 + ], + [ + 203, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 220, + 61 + ], + [ + 204, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 239, + 65 + ], + [ + 239, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 240, + 65 + ], + [ + 240, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 239, + 41 + ], + [ + 255, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 240, + 41 + ], + [ + 256, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 255, + 65 + ], + [ + 255, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 256, + 65 + ], + [ + 256, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 263, + 41 + ], + [ + 279, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 264, + 41 + ], + [ + 280, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 279, + 41 + ], + [ + 279, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 280, + 41 + ], + [ + 280, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 279, + 65 + ], + [ + 263, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 280, + 65 + ], + [ + 264, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 263, + 65 + ], + [ + 263, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 264, + 65 + ], + [ + 264, + 41 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 287, + 41 + ], + [ + 287, + 57 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 288, + 41 + ], + [ + 288, + 57 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 287, + 65 + ], + [ + 287, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 288, + 65 + ], + [ + 288, + 65 + ] + ], + "ink": { + "color": 28, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "VoronoiFill": { + "x": 0, + "y": 112, + "w": 320, + "h": 78, + "cell": 12, + "colors": [ + 20, + 21, + 19, + 76 + ], + "edge_color": 17, + "edge": 1.4, + "seed": 33, + "ink": { + "color": 0, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 120, + "cy": 156, + "rx": 34, + "ry": 7, + "ink": { + "color": 77, + "pri": "Keep", + "ctl": { + "Set": 2 + }, + "hot": null + } + } + }, + { + "Scatter": { + "x": 0, + "y": 112, + "w": 320, + "h": 78, + "colors": [ + 24, + 77 + ], + "density": 6, + "seed": 34, + "ink": { + "color": 0, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + } + ], + "scale": { + "horizon_y": 105, + "min_scale": 0.75, + "full_y": 186 + }, + "spawn": [ + 160, + 160 + ], + "exits": [ + 1, + null, + null, + 0 + ], + "hotspots": [ + { + "name": "graffiti", + "rect": [ + 40, + 30, + 250, + 74 + ], + "msgs": { + "do": "You touch the brick. It's cold. The words stay." + } + }, + { + "name": "puddle", + "rect": [ + 86, + 148, + 70, + 16 + ], + "msgs": { + "look": "The words float upside-down in the puddle, still true both ways." + } + } + ], + "props": [ + { + "name": "dumpster", + "sprite": "dumpster", + "x": 282, + "y": 150, + "msgs": { + "look": "A dumpster keeping the alley's secrets. Every scene in every city has this exact dumpster.", + "smell": "1980 called. It wants nothing back." + }, + "solid": true + } + ], + "npcs": [], + "music": 6, + "weather": 1, + "script": "room2.rhai" +} \ No newline at end of file diff --git a/rooms/room3.json b/rooms/room3.json new file mode 100644 index 0000000..ae7ae75 --- /dev/null +++ b/rooms/room3.json @@ -0,0 +1,881 @@ +{ + "name": "Alternative Tentacles, 1987", + "enter_text": "San Francisco. The office smells of newsprint, coffee, and litigation. Every poster on the wall is somebody's whole life.", + "ops": [ + { + "VGradient": { + "x": 0, + "y": 0, + "w": 320, + "h": 96, + "ramp": [ + 196, + 154 + ], + "ink": { + "color": 0, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 20, + "y": 14, + "w": 60, + "h": 44, + "ink": { + "color": 18, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 24, + "y": 18, + "w": 52, + "h": 36, + "ink": { + "color": 127, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 24, + 40 + ], + [ + 75, + 34 + ] + ], + "ink": { + "color": 182, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 38, + "y": 44, + "w": 4, + "h": 10, + "ink": { + "color": 22, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 56, + "y": 40, + "w": 4, + "h": 14, + "ink": { + "color": 22, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 110, + "y": 12, + "w": 30, + "h": 26, + "ink": { + "color": 176, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 114, + "y": 16, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 148, + "y": 12, + "w": 30, + "h": 26, + "ink": { + "color": 53, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 152, + "y": 16, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 186, + "y": 12, + "w": 30, + "h": 26, + "ink": { + "color": 194, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 190, + "y": 16, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 224, + "y": 12, + "w": 30, + "h": 26, + "ink": { + "color": 107, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 228, + "y": 16, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 262, + "y": 12, + "w": 30, + "h": 26, + "ink": { + "color": 57, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 266, + "y": 16, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 110, + "y": 46, + "w": 30, + "h": 26, + "ink": { + "color": 176, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 114, + "y": 50, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 148, + "y": 46, + "w": 30, + "h": 26, + "ink": { + "color": 53, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 152, + "y": 50, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 186, + "y": 46, + "w": 30, + "h": 26, + "ink": { + "color": 194, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 190, + "y": 50, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 224, + "y": 46, + "w": 30, + "h": 26, + "ink": { + "color": 107, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 228, + "y": 50, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 262, + "y": 46, + "w": 30, + "h": 26, + "ink": { + "color": 57, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 266, + "y": 50, + "w": 22, + "h": 4, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "FbmFill": { + "x": 0, + "y": 96, + "w": 320, + "h": 94, + "ramp": [ + 153, + 110, + 153 + ], + "scale": 22.0, + "octaves": 2, + "seed": 41, + "ink": { + "color": 0, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 104 + ], + [ + 319, + 104 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 116 + ], + [ + 319, + 116 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 128 + ], + [ + 319, + 128 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 140 + ], + [ + 319, + 140 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 152 + ], + [ + 319, + 152 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 164 + ], + [ + 319, + 164 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 176 + ], + [ + 319, + 176 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 188 + ], + [ + 319, + 188 + ] + ], + "ink": { + "color": 110, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Rect": { + "x": 0, + "y": 168, + "w": 90, + "h": 22, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "VoronoiFill": { + "x": 2, + "y": 170, + "w": 86, + "h": 18, + "cell": 6, + "colors": [ + 176, + 36, + 200, + 19, + 51 + ], + "edge_color": null, + "edge": 0.6, + "seed": 42, + "ink": { + "color": null, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Rect": { + "x": 230, + "y": 168, + "w": 90, + "h": 22, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "VoronoiFill": { + "x": 232, + "y": 170, + "w": 86, + "h": 18, + "cell": 6, + "colors": [ + 143, + 48, + 188, + 20 + ], + "edge_color": null, + "edge": 0.6, + "seed": 43, + "ink": { + "color": null, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Rect": { + "x": 150, + "y": 100, + "w": 74, + "h": 22, + "ink": { + "color": 110, + "pri": { + "Set": 10 + }, + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 150, + "y": 100, + "w": 74, + "h": 4, + "ink": { + "color": 153, + "pri": { + "Set": 10 + }, + "ctl": "Keep", + "hot": null + } + } + }, + { + "Rect": { + "x": 158, + "y": 92, + "w": 18, + "h": 8, + "ink": { + "color": 29, + "pri": { + "Set": 10 + }, + "ctl": "Keep", + "hot": null + } + } + } + ], + "scale": { + "horizon_y": 90, + "min_scale": 0.72, + "full_y": 186 + }, + "spawn": [ + 60, + 140 + ], + "exits": [ + null, + 4, + null, + 0 + ], + "exit_flags": [ + "", + "signed", + "", + "" + ], + "exit_blocked": [ + "", + "Europe doesn't book the unsigned. Talk to Jello.", + "", + "" + ], + "hotspots": [ + { + "name": "poster wall", + "rect": [ + 108, + 8, + 200, + 74 + ], + "msgs": { + "look": "Dead Kennedys. D.O.A. Butthole Surfers. A wall of bands that never asked permission.", + "do": "You straighten one thumbtack. History looks marginally tidier." + } + }, + { + "name": "window", + "rect": [ + 18, + 12, + 64, + 48 + ], + "msgs": { + "look": "Fog rolling off the bay, gold light on the bridge. Even the weather here has a record deal." + } + }, + { + "name": "record crates", + "rect": [ + 0, + 160, + 92, + 30 + ], + "msgs": { + "look": "Crates of vinyl heading to distributors on four continents. The machine, fed." + } + }, + { + "name": "desk", + "rect": [ + 148, + 96, + 78, + 30 + ], + "msgs": { + "look": "The desk: contracts, cassettes, and a rubber tarantula used as a paperweight." + } + } + ], + "props": [], + "npcs": [ + { + "name": "Jello", + "sprite": "biafra", + "x": 246, + "y": 132, + "portrait": "biafra-face", + "wander": 14, + "msgs": { + "look": "Jello Biafra, sorting demos like a heron reads a river." + }, + "dialogue": [ + { + "says": "The Canadians. The brothers. The ones who sound like — what did Trouser Press say — Wire on psychotic steroids?", + "choices": [ + { + "text": "Slip him the demo tape.", + "goto": 1, + "requires_flag": "demo_done", + "sets_flag": "signed", + "points": 10 + }, + { + "text": "We're NoMeansNo. And no means no.", + "goto": 2 + }, + { + "text": "Just admiring the tarantula.", + "goto": -1 + } + ] + }, + { + "says": "He plays thirty seconds of Sex Mad. Both eyebrows achieve escape velocity. “Licensed. International. Don't shave, don't die, and tour Europe until Europe apologizes.”", + "choices": [ + { + "text": "Deal.", + "goto": -1 + } + ] + }, + { + "says": "“Good name. Better policy.” He gestures at the demo mountain. “Feed the machine, gentlemen.”", + "choices": [ + { + "text": "(Feed it.)", + "goto": 0 + } + ] + } + ] + } + ], + "music": 4, + "script": "room3.rhai" +} \ No newline at end of file diff --git a/rooms/room4.json b/rooms/room4.json new file mode 100644 index 0000000..9e7f449 --- /dev/null +++ b/rooms/room4.json @@ -0,0 +1,810 @@ +{ + "name": "Roskilde Main Stage, 1994", + "enter_text": "Denmark. Sixty thousand people breathing as one animal in the dark. Sepultura's van died in Germany, Peter Gabriel just walked off, and somebody has to be next.", + "background": "roskilde", + "ops": [ + { + "Rect": { + "x": 0, + "y": 0, + "w": 320, + "h": 112, + "ink": { + "color": null, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 0, + "y": 110, + "w": 320, + "h": 6, + "ink": { + "color": 17, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 4, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 248, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 16, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 249, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 28, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 250, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 40, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 251, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 52, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 252, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 64, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 253, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 76, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 254, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 88, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 255, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 100, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 248, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 112, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 249, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 124, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 250, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 136, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 251, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 148, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 252, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 160, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 253, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 172, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 254, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 184, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 255, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 196, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 248, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 208, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 249, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 220, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 250, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 232, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 251, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 244, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 252, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 256, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 253, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 268, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 254, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 280, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 255, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 292, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 248, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 304, + "y": 113, + "w": 8, + "h": 4, + "ink": { + "color": 249, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "FbmFill": { + "x": 0, + "y": 118, + "w": 320, + "h": 72, + "ramp": [ + 68, + 110, + 68 + ], + "scale": 20.0, + "octaves": 2, + "seed": 54, + "ink": { + "color": 0, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 126 + ], + [ + 319, + 126 + ] + ], + "ink": { + "color": 32, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 136 + ], + [ + 319, + 136 + ] + ], + "ink": { + "color": 32, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 146 + ], + [ + 319, + 146 + ] + ], + "ink": { + "color": 32, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 156 + ], + [ + 319, + 156 + ] + ], + "ink": { + "color": 32, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 166 + ], + [ + 319, + 166 + ] + ], + "ink": { + "color": 32, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 176 + ], + [ + 319, + 176 + ] + ], + "ink": { + "color": 32, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Line": { + "pts": [ + [ + 0, + 186 + ], + [ + 319, + 186 + ] + ], + "ink": { + "color": 32, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Polygon": { + "pts": [ + [ + 70, + 122 + ], + [ + 96, + 122 + ], + [ + 88, + 132 + ], + [ + 78, + 132 + ] + ], + "ink": { + "color": 19, + "pri": { + "Set": 11 + }, + "ctl": "Keep", + "hot": null + } + } + }, + { + "Polygon": { + "pts": [ + [ + 200, + 122 + ], + [ + 226, + 122 + ], + [ + 218, + 132 + ], + [ + 208, + 132 + ] + ], + "ink": { + "color": 19, + "pri": { + "Set": 11 + }, + "ctl": "Keep", + "hot": null + } + } + } + ], + "cycles": [ + { + "start": 248, + "len": 8, + "period": 2, + "reverse": false, + "active": true + } + ], + "scale": { + "horizon_y": 112, + "min_scale": 0.8, + "full_y": 186 + }, + "spawn": [ + 50, + 160 + ], + "exits": [ + null, + 5, + null, + 3 + ], + "exit_flags": [ + "", + "legend", + "", + "" + ], + "exit_blocked": [ + "", + "Not before the encore. Play the show.", + "", + "" + ], + "hotspots": [ + { + "name": "crowd", + "rect": [ + 0, + 40, + 320, + 70 + ], + "msgs": { + "look": "A field of lighters to the horizon. Sixty thousand Danes who came for Sepultura and stayed for whatever this is about to be.", + "listen": "The roar arrives in waves, like surf that learned your name.", + "talk": "You raise one fist. SIXTY THOUSAND PEOPLE RAISE IT BACK." + } + }, + { + "name": "monitors", + "rect": [ + 66, + 118, + 164, + 18 + ], + "msgs": { + "look": "Monitor wedges, mixed for a prog band. John will fix that with volume." + } + } + ], + "props": [ + { + "name": "the rig", + "sprite": "rig", + "x": 276, + "y": 148, + "synonyms": "amps amp stack backline", + "msgs": { + "look": "A borrowed backline the size of a rowhouse. Rob's overdrive will make it honest." + }, + "solid": true + } + ], + "npcs": [ + { + "name": "stage manager", + "sprite": "stagehand", + "x": 96, + "y": 150, + "wander": 20, + "msgs": { + "look": "A Danish stage manager with a clipboard and eleven headaches." + }, + "dialogue": [ + { + "says": "Sepultura is IN A DITCH near Flensburg. I have sixty thousand people and a hole in the schedule after Peter Gabriel. You are either legends tonight, or you are filler.", + "choices": [ + { + "text": "We'll take the main stage.", + "goto": 1, + "sets_flag": "mainstage", + "points": 10 + }, + { + "text": "We're... technically the tent act.", + "goto": 2 + }, + { + "text": "Give us a minute.", + "goto": -1 + } + ] + }, + { + "says": "“Then GO.” He points at the rig with the clipboard. “And whatever a 'jazzcore' is — do it LOUDLY.”", + "choices": [ + { + "text": "Always.", + "goto": -1 + } + ] + }, + { + "says": "“'Were.'” He crosses something out with terrifying finality.", + "choices": [ + { + "text": "(Reconsider.)", + "goto": 0 + } + ] + } + ] + } + ], + "music": 3, + "weather": 3, + "script": "room4.rhai" +} \ No newline at end of file diff --git a/rooms/room5.json b/rooms/room5.json new file mode 100644 index 0000000..3faef6c --- /dev/null +++ b/rooms/room5.json @@ -0,0 +1,290 @@ +{ + "name": "A Field Outside Poznan, 1997", + "enter_text": "Poland. Dawn the color of dishwater. Your van — your entire livelihood with wheels on it — sits in a farm field, and a man in a tracksuit is leaning on it.", + "ops": [ + { + "VGradient": { + "x": 0, + "y": 0, + "w": 320, + "h": 66, + "ramp": [ + 23, + 21, + 22 + ], + "ink": { + "color": 0, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Scatter": { + "x": 0, + "y": 8, + "w": 320, + "h": 30, + "colors": [ + 18 + ], + "density": 2, + "seed": 61, + "ink": { + "color": 0, + "pri": "Keep", + "ctl": "Keep", + "hot": null + } + } + }, + { + "Rect": { + "x": 30, + "y": 44, + "w": 26, + "h": 20, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Polygon": { + "pts": [ + [ + 26, + 46 + ], + [ + 43, + 34 + ], + [ + 60, + 46 + ] + ], + "ink": { + "color": 68, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 84, + "cy": 52, + "rx": 12, + "ry": 12, + "ink": { + "color": 38, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "Rect": { + "x": 82, + "y": 60, + "w": 4, + "h": 8, + "ink": { + "color": 110, + "pri": "Keep", + "ctl": { + "Set": 1 + }, + "hot": null + } + } + }, + { + "FbmFill": { + "x": 0, + "y": 66, + "w": 320, + "h": 124, + "ramp": [ + 80, + 38, + 81, + 116 + ], + "scale": 14.0, + "octaves": 3, + "seed": 62, + "ink": { + "color": 0, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "VoronoiFill": { + "x": 60, + "y": 120, + "w": 200, + "h": 40, + "cell": 16, + "colors": [ + 110, + 68, + 117 + ], + "edge_color": 68, + "edge": 1.2, + "seed": 63, + "ink": { + "color": 0, + "pri": "Band", + "ctl": { + "Set": 0 + }, + "hot": null + } + } + }, + { + "Ellipse": { + "cx": 150, + "cy": 168, + "rx": 30, + "ry": 6, + "ink": { + "color": 76, + "pri": "Keep", + "ctl": { + "Set": 2 + }, + "hot": null + } + } + } + ], + "scale": { + "horizon_y": 70, + "min_scale": 0.55, + "full_y": 186 + }, + "spawn": [ + 30, + 160 + ], + "exits": [ + null, + null, + null, + 4 + ], + "hotspots": [ + { + "name": "farmhouse", + "rect": [ + 24, + 30, + 40, + 36 + ], + "msgs": { + "look": "A farmhouse minding its own business at a professional level." + } + } + ], + "props": [ + { + "name": "van", + "sprite": "van", + "x": 180, + "y": 118, + "synonyms": "tourvan vehicle", + "msgs": { + "look": "The van. Every amp, every pedal, every unwashed sleeping bag — the whole band, parked in the wrong country." + }, + "solid": true + } + ], + "npcs": [ + { + "name": "mobster", + "sprite": "mobster", + "x": 236, + "y": 142, + "portrait": "mobster-face", + "msgs": { + "look": "Tracksuit, flat cap, and the calm of a man who has never once hurried." + }, + "dialogue": [ + { + "says": "“Nice van,” he says, in better English than yours. “Shame about the neighborhood. Storage fees in this district are… irregular.”", + "choices": [ + { + "text": "Name your price.", + "goto": 1 + }, + { + "text": "Do you KNOW who we are?", + "goto": 2, + "kills": true + }, + { + "text": "We'll come back.", + "goto": -1 + } + ] + }, + { + "says": "“Two and a half thousand. American. The van, the amps, the little drum machine — all of it, untouched.” He pats the van gently, like a horse.", + "choices": [ + { + "text": "Strike the deal.", + "goto": 3, + "sets_flag": "deal_struck" + }, + { + "text": "Outrageous.", + "goto": -1 + } + ] + }, + { + "says": "In a field outside a town you cannot pronounce, it turns out that nobody, in fact, knows who you are.", + "choices": [] + }, + { + "says": "“Pleasure doing business with professionals.” He steps away from the van and lights a cigarette against the wind, first try.", + "choices": [ + { + "text": "(Get the money.)", + "goto": -1 + } + ] + } + ] + } + ], + "music": 2, + "weather": 1, + "script": "room5.rhai" +} \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..2d3ef6a --- /dev/null +++ b/run.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Play NOMEANSNO QUEST. Expects the MRPCI engine checked out at ../mrpci. +set -e +DIR="$(cd "$(dirname "$0")" && pwd)" +exec cargo run -p mrpci --manifest-path "$DIR/../mrpci/Cargo.toml" -- --game "$DIR" "$@" diff --git a/scripts/main.rhai b/scripts/main.rhai new file mode 100644 index 0000000..11eb5bb --- /dev/null +++ b/scripts/main.rhai @@ -0,0 +1,8 @@ +// NOMEANSNO QUEST — global helpers, merged under every room script. + +// One-shot latch: true the first time, false forever after. +fn once(name) { + if flag(name) { return false; } + set_flag(name, true); + true +} diff --git a/scripts/room0.rhai b/scripts/room0.rhai new file mode 100644 index 0000000..f50bfd2 --- /dev/null +++ b/scripts/room0.rhai @@ -0,0 +1,60 @@ +// The basement: the four-track, the tape, and the demo that starts it all. + +fn on_enter() { + if once("seen_basement") { + say("The bulb hums. Somewhere upstairs, a kettle agrees."); + } +} + +fn on_verb(verb, noun) { + // The Castle box surrenders its one useful artifact. + if noun == "castle box" && (verb == "do" || verb == "take") { + if !flag("tape_found") { + set_flag("tape_found", true); + give("blank tape"); + say("Under the Castle setlists (RIP): one fresh reel of tape, still in shrink wrap. The cover band's final gift."); + play("pickup"); + } else { + say("Nothing left in there but regret and a capo."); + } + return true; + } + + // Recording the demo — the whole first act funnels here. + if noun == "portastudio" && (verb == "use:blank tape" || verb == "do" || verb == "play") { + if flag("demo_done") { + say("The demo's done. It sits there radiating consequence."); + return true; + } + if verb == "do" || verb == "play" { + if !has("blank tape") { + say("The reels are empty. You need tape — didn't the Castle box have some?"); + return true; + } + } + if !has("blank tape") { + return false; + } + if !flag("inspired") { + say("Tape, machine, hands. But no reason yet. You keep thinking about that D.O.A. poster."); + return true; + } + if !flag("named") { + say("The riff is THERE. But who's it by? A band needs a name before tape makes it real. (Something you saw in the alley keeps circling back.)"); + return true; + } + freeze_ego(true); + say("You thread the reel. John counts in — in NINE — and the basement stops being a basement."); + say("Two hours, eleven takes, one mother banging the ceiling in what is, honestly, pretty solid time."); + say("You mark the box in felt pen: NOMEANSNO."); + remove_item("blank tape"); + give("demo tape"); + set_flag("demo_done", true); + points(10); + play("win"); + freeze_ego(false); + say("(The van is out the south door. California knows nothing yet.)"); + return true; + } + false +} diff --git a/scripts/room1.rhai b/scripts/room1.rhai new file mode 100644 index 0000000..5a8792c --- /dev/null +++ b/scripts/room1.rhai @@ -0,0 +1,23 @@ +// UVic, 1980: watch D.O.A., leave changed. + +fn on_verb(verb, noun) { + if noun == "stage" && verb == "look" { + if once("inspired") { + play("alert"); + say("Joey howls, the floor buckles, and something in your chest un-tightens after twenty-five years."); + say("You look at John. John looks at you. Neither of you says the obvious thing, because you are Canadian."); + points(5); + } else { + say("Still the loudest sermon you've ever attended. The collection plate is a merch table."); + } + return true; + } + false +} + +fn on_trigger(name) { + if name == "pit" && once("pit_survived") { + say("The pit swallows you for eight bars and spits you out with someone else's shoe."); + play("scan"); + } +} diff --git a/scripts/room2.rhai b/scripts/room2.rhai new file mode 100644 index 0000000..e7ebb7a --- /dev/null +++ b/scripts/room2.rhai @@ -0,0 +1,24 @@ +// The alley: three words on brick. + +fn on_enter() { + if once("seen_alley") { + say("Rain, brick, and a sentence someone needed the whole wall for."); + } +} + +fn on_verb(verb, noun) { + if noun == "graffiti" && verb == "look" { + if !flag("inspired") { + say("White paint on brick: NO MEANS NO! Blunt. True. You file it away and think about dinner."); + } else if once("named") { + say("NO MEANS NO. You stand in the rain reading three words like they're a contract."); + say("It isn't a slogan to you anymore. It's a band name. It was always a band name."); + points(5); + play("confirm"); + } else { + say("Your band name, still wet on somebody's wall. You should send this bricklayer a royalty."); + } + return true; + } + false +} diff --git a/scripts/room3.rhai b/scripts/room3.rhai new file mode 100644 index 0000000..9814b11 --- /dev/null +++ b/scripts/room3.rhai @@ -0,0 +1,15 @@ +// Alternative Tentacles: the dialogue does the signing; this is flavor. + +fn on_enter() { + if once("seen_at") { + say("A phone rings. Someone answers it 'ALTERNATIVE TENTACLES' like a war cry."); + } +} + +fn on_verb(verb, noun) { + if noun == "Jello" && verb == "use:demo tape" { + say("Don't hand it over like a subpoena — TALK to him. Showmanship, Rob."); + return true; + } + false +} diff --git a/scripts/room4.rhai b/scripts/room4.rhai new file mode 100644 index 0000000..0ef868f --- /dev/null +++ b/scripts/room4.rhai @@ -0,0 +1,32 @@ +// Roskilde 1994: the main stage, after Peter Gabriel, no pressure. + +fn on_enter() { + if once("seen_roskilde") { + say("Sixty thousand lighters. You can feel the low-end of the crowd's breathing."); + } +} + +fn on_verb(verb, noun) { + if noun == "the rig" && (verb == "do" || verb == "play" || verb == "use") { + if !flag("mainstage") { + say("Not your stage yet. The stage manager holds the schedule, and the schedule is God."); + return true; + } + if flag("legend") { + say("The encore's done. Your ears will ring until Thursday. Worth it."); + return true; + } + freeze_ego(true); + say("You plug in. The overdrive hits the borrowed rig like weather hitting a coastline."); + say("John opens in 7/4. Sixty thousand people who came for Sepultura decide — audibly, in real time — that this will do NICELY."); + say("Forty-five minutes later, two brothers from Victoria walk off the biggest stage in Scandinavia."); + set_flag("legend", true); + points(10); + give("tour money"); + play("win"); + freeze_ego(false); + say("A promoter presses an envelope into your hands: the fee, cash, in kroner. (East of here, in a few years, you're going to need it.)"); + return true; + } + false +} diff --git a/scripts/room5.rhai b/scripts/room5.rhai new file mode 100644 index 0000000..7331a30 --- /dev/null +++ b/scripts/room5.rhai @@ -0,0 +1,39 @@ +// Poland, 1997: the van, the field, the fee. And the ending. + +fn on_enter() { + if once("seen_field") { + say("The van is right there. So is the tracksuit. Nobody is hurrying, which is somehow worse."); + } +} + +fn on_verb(verb, noun) { + if noun == "mobster" && verb == "use:tour money" { + if !flag("deal_struck") { + say("He looks at the envelope, then at you, with professional disappointment. 'First we agree. THEN money.' (Talk to him.)"); + return true; + } + if once("van_back") { + remove_item("tour money"); + points(10); + say("The Roskilde envelope changes hands in a field outside Poznan. He counts it without counting it — a skill."); + say("'The van is yours. The amps are yours. Poland wishes you a pleasant tour.' He walks into the mist like a closing credit."); + play("confirm"); + } + return true; + } + + if noun == "van" && (verb == "do" || verb == "use" || verb == "open") { + if !flag("van_back") { + say("It's technically not yours right now, and the tracksuit has opinions about technicalities."); + return true; + } + freeze_ego(true); + say("Everything's there. Every amp, every pedal, John's kit, the merch box — untouched, like he promised."); + say("John takes the wheel. You take the map. Somewhere behind you: a basement, a wall, an office full of posters, sixty thousand lighters."); + say("Ahead: nine more years, a Hall of Fame, and a retirement announced with no drama at all — 37 years, no hiatus, no encore needed."); + say("ALL ROADS LEAD TO AUSFAHRT."); + win(); + return true; + } + false +} diff --git a/sprites/biafra-face.png b/sprites/biafra-face.png new file mode 100644 index 0000000..e039191 Binary files /dev/null and b/sprites/biafra-face.png differ diff --git a/sprites/biafra.png b/sprites/biafra.png new file mode 100644 index 0000000..a9eb271 Binary files /dev/null and b/sprites/biafra.png differ diff --git a/sprites/castlebox.png b/sprites/castlebox.png new file mode 100644 index 0000000..f061c53 Binary files /dev/null and b/sprites/castlebox.png differ diff --git a/sprites/drumkit.png b/sprites/drumkit.png new file mode 100644 index 0000000..1cf0a00 Binary files /dev/null and b/sprites/drumkit.png differ diff --git a/sprites/dumpster.png b/sprites/dumpster.png new file mode 100644 index 0000000..dac89f4 Binary files /dev/null and b/sprites/dumpster.png differ diff --git a/sprites/flyer.png b/sprites/flyer.png new file mode 100644 index 0000000..e13fa33 Binary files /dev/null and b/sprites/flyer.png differ diff --git a/sprites/joey.png b/sprites/joey.png new file mode 100644 index 0000000..9ed58c6 Binary files /dev/null and b/sprites/joey.png differ diff --git a/sprites/john-face.png b/sprites/john-face.png new file mode 100644 index 0000000..a115923 Binary files /dev/null and b/sprites/john-face.png differ diff --git a/sprites/john.png b/sprites/john.png new file mode 100644 index 0000000..3a596f4 Binary files /dev/null and b/sprites/john.png differ diff --git a/sprites/mobster-face.png b/sprites/mobster-face.png new file mode 100644 index 0000000..db2c2a2 Binary files /dev/null and b/sprites/mobster-face.png differ diff --git a/sprites/mobster.png b/sprites/mobster.png new file mode 100644 index 0000000..48920b1 Binary files /dev/null and b/sprites/mobster.png differ diff --git a/sprites/mom-face.png b/sprites/mom-face.png new file mode 100644 index 0000000..227ae45 Binary files /dev/null and b/sprites/mom-face.png differ diff --git a/sprites/mom.png b/sprites/mom.png new file mode 100644 index 0000000..1d1e381 Binary files /dev/null and b/sprites/mom.png differ diff --git a/sprites/portastudio.png b/sprites/portastudio.png new file mode 100644 index 0000000..79bb8f2 Binary files /dev/null and b/sprites/portastudio.png differ diff --git a/sprites/rig.png b/sprites/rig.png new file mode 100644 index 0000000..7f39a80 Binary files /dev/null and b/sprites/rig.png differ diff --git a/sprites/rob.png b/sprites/rob.png new file mode 100644 index 0000000..e58a09a Binary files /dev/null and b/sprites/rob.png differ diff --git a/sprites/stagehand.png b/sprites/stagehand.png new file mode 100644 index 0000000..7d8b123 Binary files /dev/null and b/sprites/stagehand.png differ diff --git a/sprites/van.png b/sprites/van.png new file mode 100644 index 0000000..467a560 Binary files /dev/null and b/sprites/van.png differ diff --git a/tests/playthrough.jsonl b/tests/playthrough.jsonl new file mode 100644 index 0000000..7003c98 --- /dev/null +++ b/tests/playthrough.jsonl @@ -0,0 +1,54 @@ +# NOMEANSNO QUEST — canonical win path (with one fatal mouth-off in Poland). +{"cmd":"set_seed","seed":1979} +{"cmd":"new_game"} +# basement: raid the Castle box for tape +{"cmd":"parse","text":"open the castle box"} +{"cmd":"tick","n":400} +# up the stairs to the D.O.A. gig +{"cmd":"walk_to","x":316,"y":150} +{"cmd":"parse","text":"look at the stage"} +{"cmd":"tick","n":200} +{"cmd":"parse","text":"pick up the flyer"} +{"cmd":"tick","n":400} +# out back to the alley, read the wall +{"cmd":"walk_to","x":160,"y":188} +{"cmd":"parse","text":"look at the graffiti"} +{"cmd":"tick","n":200} +# home to the basement, record the demo +{"cmd":"walk_to","x":4,"y":150} +{"cmd":"parse","text":"use blank tape on portastudio"} +{"cmd":"tick","n":600} +{"cmd":"query"} +# the van ride south to San Francisco +{"cmd":"walk_to","x":160,"y":188} +{"cmd":"parse","text":"talk to jello"} +{"cmd":"tick","n":500} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +# east to Europe, eventually Roskilde +{"cmd":"walk_to","x":316,"y":140} +{"cmd":"parse","text":"talk to the stage manager"} +{"cmd":"tick","n":500} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +{"cmd":"parse","text":"use the rig"} +{"cmd":"tick","n":600} +{"cmd":"query"} +# east again: Poland, 1997 +{"cmd":"walk_to","x":316,"y":150} +# first: the fatal option (checkpoint mercy demo) +{"cmd":"parse","text":"talk to the mobster"} +{"cmd":"tick","n":500} +{"cmd":"choose","n":2} +{"cmd":"tick","n":100} +# rewound to room entry; now do it properly +{"cmd":"parse","text":"talk to the mobster"} +{"cmd":"tick","n":500} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +{"cmd":"parse","text":"use tour money on mobster"} +{"cmd":"tick","n":400} +{"cmd":"parse","text":"open the van"} +{"cmd":"tick","n":300} +{"cmd":"query"} diff --git a/tools/gen_rooms.py b/tools/gen_rooms.py new file mode 100755 index 0000000..0c22c9a --- /dev/null +++ b/tools/gen_rooms.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python3 +"""Generate NOMEANSNO QUEST rooms as MRPCI RoomDoc JSON. + +Six rooms, one band history: the basement, the D.O.A. gig, the alley wall, +Alternative Tentacles, Roskilde '94, and a Polish farm field in '97. +Run from the repo root: python3 tools/gen_rooms.py +""" + +import json +import os + +OUT = os.path.join(os.path.dirname(__file__), "..", "rooms") + +# --- palette helpers (MRPCI base palette layout) --------------------------- +def cube(r, g, b): # 6x6x6 color cube at 32..248 + return 32 + 36 * r + 6 * g + b + +def gray(i): # 16-step gray ramp at 16..32 + return 16 + min(i, 15) + +EMBER = list(range(248, 256)) + +# --- ink helpers ------------------------------------------------------------ +KEEP = "Keep" + +def ink(color=None, pri=KEEP, ctl=KEEP, hot=None): + return {"color": color, "pri": pri, "ctl": ctl, "hot": hot} + +def look(c): + return ink(color=c) + +def floor(c): + return ink(color=c, pri="Band", ctl={"Set": 0}) + +def wall(c): + return ink(color=c, ctl={"Set": 1}) + +def water(c): + return ink(color=c, ctl={"Set": 2}) + +def depth(c, band): + return ink(color=c, pri={"Set": band}) + +def band_of(y): + return min(15, y * 16 // 190) + +# --- op helpers --------------------------------------------------------------- +def rect(x, y, w, h, i): + return {"Rect": {"x": x, "y": y, "w": w, "h": h, "ink": i}} + +def line(pts, i): + return {"Line": {"pts": pts, "ink": i}} + +def ellipse(cx, cy, rx, ry, i): + return {"Ellipse": {"cx": cx, "cy": cy, "rx": rx, "ry": ry, "ink": i}} + +def poly(pts, i): + return {"Polygon": {"pts": pts, "ink": i}} + +def vgrad(x, y, w, h, ramp, i): + return {"VGradient": {"x": x, "y": y, "w": w, "h": h, "ramp": ramp, "ink": i}} + +def fbm(x, y, w, h, ramp, scale, octaves, seed, i): + return {"FbmFill": {"x": x, "y": y, "w": w, "h": h, "ramp": ramp, "scale": scale, "octaves": octaves, "seed": seed, "ink": i}} + +def voronoi(x, y, w, h, cell, colors, edge_color, edge, seed, i): + return {"VoronoiFill": {"x": x, "y": y, "w": w, "h": h, "cell": cell, "colors": colors, "edge_color": edge_color, "edge": edge, "seed": seed, "ink": i}} + +def scatter(x, y, w, h, colors, density, seed, i): + return {"Scatter": {"x": x, "y": y, "w": w, "h": h, "colors": colors, "density": density, "seed": seed, "ink": i}} + +# Crude 5x7-ish spray letters for the graffiti wall (scaled by s). +SEGS = { + "N": [((0, 6), (0, 0)), ((0, 0), (4, 6)), ((4, 6), (4, 0))], + "O": [((0, 0), (4, 0)), ((4, 0), (4, 6)), ((4, 6), (0, 6)), ((0, 6), (0, 0))], + "M": [((0, 6), (0, 0)), ((0, 0), (2, 3)), ((2, 3), (4, 0)), ((4, 0), (4, 6))], + "E": [((4, 0), (0, 0)), ((0, 0), (0, 6)), ((0, 6), (4, 6)), ((0, 3), (3, 3))], + "A": [((0, 6), (2, 0)), ((2, 0), (4, 6)), ((1, 4), (3, 4))], + "S": [((4, 0), (0, 1)), ((0, 1), (4, 5)), ((4, 5), (0, 6))], + "!": [((0, 0), (0, 4)), ((0, 6), (0, 6))], +} + +def spray(text, x0, y0, s, color): + ops = [] + x = x0 + for ch in text: + if ch == " ": + x += 3 * s + continue + for (a, b) in SEGS.get(ch, []): + pts = [[x + a[0] * s, y0 + a[1] * s], [x + b[0] * s, y0 + b[1] * s]] + ops.append(line(pts, look(color))) + # a second pass one pixel off = fat spray line + ops.append(line([[p[0] + 1, p[1]] for p in pts], look(color))) + x += 6 * s + return ops + +def hotspot(name, r=None, msgs=None, **kw): + h = {"name": name} + if r: + h["rect"] = r + if msgs: + h["msgs"] = msgs + h.update(kw) + return h + +def prop(name, sprite, x, y, **kw): + p = {"name": name, "sprite": sprite, "x": x, "y": y} + p.update(kw) + return p + +def npc(name, sprite, x, y, **kw): + n = {"name": name, "sprite": sprite, "x": x, "y": y} + n.update(kw) + return n + +def node(says, *choices): + return {"says": says, "choices": list(choices)} + +def choice(text, goto, **kw): + c = {"text": text, "goto": goto} + c.update(kw) + return c + + +ROOMS = {} + +# ============================================================================= +# Room 0 — The Basement, Victoria BC, 1979 +# ============================================================================= +ops = [] +# Wood-panel walls with vertical seams. +ops.append(fbm(0, 0, 320, 100, [cube(2, 1, 0), cube(2, 1, 0), cube(3, 2, 1), cube(2, 1, 0)], 34.0, 2, 3, wall(0))) +for x in range(0, 320, 26): + ops.append(line([[x, 0], [x, 99]], ink(color=cube(1, 0, 0), ctl={"Set": 1}))) +# Window well, top right — grey Victoria daylight. +ops.append(rect(268, 10, 34, 22, wall(gray(4)))) +ops.append(rect(271, 13, 28, 16, wall(cube(3, 3, 4)))) +ops.append(line([[285, 13], [285, 28]], wall(gray(4)))) +# Concrete floor. +ops.append(voronoi(0, 100, 320, 90, 44, [gray(6), gray(6), gray(5), gray(6)], gray(5), 1.0, 9, floor(0))) +# Laundry corner, west: washer + a shelf of detergent. +ops.append(rect(6, 62, 30, 38, wall(gray(12)))) +ops.append(rect(10, 68, 22, 22, wall(gray(9)))) +ops.append(ellipse(21, 79, 8, 8, wall(cube(2, 3, 4)))) +ops.append(rect(24, 64, 6, 3, wall(cube(4, 1, 1)))) +ops.append(rect(4, 40, 36, 6, wall(cube(2, 1, 0)))) +ops.append(rect(8, 30, 8, 10, wall(cube(4, 4, 0)))) +ops.append(rect(20, 32, 8, 8, wall(cube(1, 3, 3)))) +# The jam corner rug. +ops.append(ellipse(96, 148, 44, 14, floor(cube(1, 0, 0)))) +# Workbench table along the north wall for the Portastudio. +ops.append(rect(128, 84, 66, 16, wall(cube(2, 1, 0)))) +ops.append(rect(128, 84, 66, 3, wall(cube(3, 2, 1)))) +# Gig poster on the wall. +ops.append(rect(214, 38, 26, 34, wall(gray(14)))) +ops.append(rect(217, 42, 20, 8, wall(cube(4, 0, 0)))) +ops.append(rect(217, 54, 20, 3, wall(gray(3)))) +ops.append(rect(217, 60, 14, 3, wall(gray(3)))) +# Stairs up, east edge. +for i in range(5): + ops.append(rect(292 - 0, 96 - i * 8, 28, 8, wall(cube(2, 1, 0)) if i % 2 == 0 else wall(cube(3, 2, 1)))) +ops.append(rect(292, 100, 28, 90, floor(gray(5)))) + +ROOMS[0] = { + "name": "The Basement, 1979", + "enter_text": "Wood panel, concrete, one bare bulb. John's kit crowds the rug; the 180-pound Portastudio owns the workbench. Through the wall, Mom's washer keeps better time than most drummers.", + "ops": ops, + "scale": {"horizon_y": 92, "min_scale": 0.72, "full_y": 186}, + "spawn": [160, 150], + "exits": [None, 1, 3, None], + "exit_flags": ["", "", "demo_done", ""], + "exit_blocked": ["", "", "No demo, no destiny. The Portastudio is right there.", ""], + "hotspots": [ + hotspot("gig poster", [212, 36, 30, 38], msgs={ + "look": "D.O.A. — TONIGHT — UVic. Vancouver's finest, one ferry away. John already circled it.", + "do": "You're going. Obviously you're going. (Walk east, up the stairs.)", + }), + hotspot("washer", [4, 60, 34, 42], msgs={ + "look": "Mom's washing machine, mid-cycle. A wet 4/4 that never rushes and never drags.", + "listen": "Chunk. Chunk. Chunk. Honestly? Tighter than most bands you've seen.", + "do": "It's Mom's. Some things are sacred.", + }), + hotspot("window", [266, 8, 38, 26], msgs={ + "look": "Grey Victoria daylight. The Island doesn't hurry, and neither does the rain.", + }), + hotspot("workbench", [126, 82, 70, 20], msgs={ + "look": "The workbench holds the TEAC four-track like an altar holds a relic.", + }), + ], + "props": [ + prop("portastudio", "portastudio", 160, 104, + synonyms="four-track teac tascam recorder deck", + msgs={ + "look": "The TEAC Portastudio. 180 pounds of overdubbing possibility. It wants tape and it wants a reason.", + "do": "It powers up with a hum like a small appliance dreaming big.", + "listen": "Tape hiss. The sound of potential.", + }, + solid=True), + prop("castle box", "castlebox", 224, 146, + synonyms="box junk castle", + msgs={ + "look": "A box of gear from Castle, the cover band you both did time in. Cables, setlists nobody needs, and — is that a fresh reel of tape?", + }, + solid=True), + prop("drum kit", "drumkit", 92, 136, + synonyms="drums kit", + msgs={ + "look": "John's kit, tuned like a jazz kit because he IS a jazz kid. The high school band has no idea what it's incubating.", + "play": "You are a bassist. There are laws.", + }, + solid=True), + ], + "npcs": [ + npc("John", "john", 66, 148, portrait="john-face", wander=12, + msgs={"look": "Your kid brother. Eight years younger, already swings harder than drummers twice his age."}, + dialogue=[ + node("Sticks spin in his fingers. “So? Are we a band, or are we two guys who own instruments?”", + choice("We need a sound of our own first.", 1), + choice("Heard D.O.A. play UVic tonight.", 2), + choice("Later, John.", -1)), + node("“No guitar player, eh? Fine. You be the guitar. I'll be the horn section.” He plays a bar of 9/8 like it's nothing.", + choice("That. Exactly that.", -1)), + node("“Then what are we still doing in the basement? GO. Take notes. Loud ones.”", + choice("On it.", -1)), + ]), + npc("Mom", "mom", 26, 126, portrait="mom-face", wander=6, + msgs={"look": "Mom, running the laundry room like mission control."}, + dialogue=[ + node("“Play all you like, love — the whites are done at eight.” She pauses. “The loud one in 7/4 was nice.”", + choice("You could tell it was 7/4?", 1), + choice("Thanks, Mom.", -1)), + node("“I've been listening to that washer for twenty years, Robert. I know time when I hear it.”", + choice("...Mom might be the best musician in the house.", -1)), + ]), + ], + "music": 1, + "script": "room0.rhai", +} + +# ============================================================================= +# Room 1 — D.O.A. at UVic, 1980 +# ============================================================================= +ops = [] +ops.append(vgrad(0, 0, 320, 96, [cube(0, 0, 1), cube(1, 0, 1), cube(0, 0, 0)], wall(0))) +# Spotlight cones. +ops.append(poly([[70, 0], [130, 0], [170, 92], [110, 92]], wall(cube(2, 1, 3)))) +ops.append(poly([[200, 0], [250, 0], [210, 92], [160, 92]], wall(cube(3, 1, 1)))) +# Stage. +ops.append(rect(56, 58, 208, 38, wall(cube(1, 0, 0)))) +ops.append(rect(56, 58, 208, 4, wall(cube(2, 1, 0)))) +# Backline silhouettes (the rest of D.O.A.). +ops.append(rect(90, 40, 14, 22, wall(gray(1)))) +ops.append(rect(220, 44, 16, 18, wall(gray(1)))) +ops.append(ellipse(97, 38, 4, 4, wall(gray(1)))) +ops.append(ellipse(228, 42, 4, 4, wall(gray(1)))) +# Footlight chase strip — the ember cycle makes it run. +for i, x in enumerate(range(58, 262, 12)): + ops.append(rect(x, 92, 8, 4, wall(EMBER[i % 8]))) +# Crowd floor. +ops.append(vgrad(0, 96, 320, 94, [gray(2), gray(1)], floor(0))) +# Heads in the dark (front rows). +for i, x in enumerate(range(16, 300, 24)): + y = 108 + (i % 3) * 9 + ops.append(ellipse(x, y, 6, 5, look(gray(1)))) +ops.append(scatter(0, 96, 320, 40, [gray(3), gray(2)], 14, 21, look(0))) + +ROOMS[1] = { + "name": "UVic — D.O.A., 1980", + "enter_text": "A university hall pretending it's a bunker. D.O.A. detonate on stage. The floor moves like weather.", + "ops": ops, + "scale": {"horizon_y": 96, "min_scale": 0.7, "full_y": 186}, + "spawn": [160, 164], + "exits": [None, None, 2, 0], + "hotspots": [ + hotspot("stage", [56, 20, 208, 76], msgs={ + "do": "Security gives you the look that says: no.", + "listen": "Loud enough to reset your heartbeat to theirs.", + }), + hotspot("pit", [80, 110, 160, 50], trigger=True, msgs={ + "look": "The pit. A weather system with elbows.", + }), + ], + "props": [ + prop("flyer", "flyer", 288, 150, + synonyms="handbill paper", + msgs={"look": "A gig flyer, boot-printed. You'll keep it forever and lie about why."}, + takeable=True), + ], + "npcs": [ + npc("Joey", "joey", 150, 90, fixed_scale=0.85, + msgs={ + "look": "Joey Shithead, mid-war-cry. Vancouver hardcore, delivered at terminal velocity.", + "talk": "He can't hear you. Nobody will hear anything until Thursday.", + }), + ], + "music": 3, + "script": "room1.rhai", +} + +# ============================================================================= +# Room 2 — The alley, and the wall +# ============================================================================= +ops = [] +# Face-on brick wall. +ops.append(fbm(0, 0, 320, 112, [cube(2, 1, 1), cube(2, 1, 1), cube(3, 1, 1)], 24.0, 2, 31, wall(0))) +for y in range(6, 110, 9): + ops.append(line([[0, y], [319, y]], ink(color=cube(1, 0, 0), ctl={"Set": 1}))) +# Drips of old paint and rust. +ops.append(scatter(0, 0, 320, 112, [cube(1, 0, 0), gray(6)], 8, 32, ink(color=None, ctl={"Set": 1}))) +# THE WORDS. White spray, uneven, perfect. +ops += spray("NO MEANS NO!", 46, 40, 4, gray(15)) +ops += spray("NO MEANS NO!", 47, 41, 4, gray(12)) +# Wet pavement. +ops.append(voronoi(0, 112, 320, 78, 12, [gray(4), gray(5), gray(3), cube(1, 1, 2)], gray(1), 1.4, 33, floor(0))) +ops.append(ellipse(120, 156, 34, 7, water(cube(1, 1, 3)))) +ops.append(scatter(0, 112, 320, 78, [gray(8), cube(1, 1, 3)], 6, 34, look(0))) + +ROOMS[2] = { + "name": "The Alley", + "enter_text": "Behind the venue, rain stitching the puddles. Someone has written three words on the brick, big enough to be a law.", + "ops": ops, + "scale": {"horizon_y": 105, "min_scale": 0.75, "full_y": 186}, + "spawn": [160, 160], + "exits": [1, None, None, 0], + "hotspots": [ + hotspot("graffiti", [40, 30, 250, 74], msgs={ + "do": "You touch the brick. It's cold. The words stay.", + }, synonyms=None), + hotspot("puddle", [86, 148, 70, 16], msgs={ + "look": "The words float upside-down in the puddle, still true both ways.", + }), + ], + "props": [ + prop("dumpster", "dumpster", 282, 150, + msgs={ + "look": "A dumpster keeping the alley's secrets. Every scene in every city has this exact dumpster.", + "smell": "1980 called. It wants nothing back.", + }, + solid=True), + ], + "npcs": [], + "music": 6, + "weather": 1, + "script": "room2.rhai", +} + +# ============================================================================= +# Room 3 — Alternative Tentacles, San Francisco, 1987 +# ============================================================================= +ops = [] +ops.append(vgrad(0, 0, 320, 96, [cube(4, 3, 2), cube(3, 2, 2)], wall(0))) +# Bay window with a slice of San Francisco. +ops.append(rect(20, 14, 60, 44, wall(gray(2)))) +ops.append(rect(24, 18, 52, 36, wall(cube(2, 3, 5)))) +ops.append(line([[24, 40], [75, 34]], wall(cube(4, 1, 0)))) # a bridge, roughly +ops.append(rect(38, 44, 4, 10, wall(gray(6)))) +ops.append(rect(56, 40, 4, 14, wall(gray(6)))) +# The poster wall: a decade of Alternative Tentacles in thumbtacks. +px, py = 110, 12 +for i in range(10): + c = [cube(4, 0, 0), cube(0, 3, 3), cube(4, 3, 0), cube(2, 0, 3), cube(0, 4, 1)][i % 5] + ops.append(rect(px + (i % 5) * 38, py + (i // 5) * 34, 30, 26, wall(c))) + ops.append(rect(px + (i % 5) * 38 + 4, py + (i // 5) * 34 + 4, 22, 4, wall(gray(1)))) +# Wood floor. +ops.append(fbm(0, 96, 320, 94, [cube(3, 2, 1), cube(2, 1, 0), cube(3, 2, 1)], 22.0, 2, 41, floor(0))) +for y in range(104, 190, 12): + ops.append(line([[0, y], [319, y]], look(cube(2, 1, 0)))) +# Crates of records, south wall (walk around them). +ops.append(rect(0, 168, 90, 22, ink(color=cube(2, 1, 0), ctl={"Set": 1}))) +ops.append(voronoi(2, 170, 86, 18, 6, [cube(4, 0, 0), cube(0, 0, 4), cube(4, 4, 0), gray(3), cube(0, 3, 1)], None, 0.6, 42, ink(color=None, ctl=KEEP))) +ops.append(rect(230, 168, 90, 22, ink(color=cube(2, 1, 0), ctl={"Set": 1}))) +ops.append(voronoi(232, 170, 86, 18, 6, [cube(3, 0, 3), cube(0, 2, 4), cube(4, 2, 0), gray(4)], None, 0.6, 43, ink(color=None, ctl=KEEP))) +# The desk (Jello's), mid-room, depth-correct. +ops.append(rect(150, 100, 74, 22, ink(color=cube(2, 1, 0), pri={"Set": band_of(122)}, ctl={"Set": 1}))) +ops.append(rect(150, 100, 74, 4, ink(color=cube(3, 2, 1), pri={"Set": band_of(122)}, ctl=KEEP))) +ops.append(rect(158, 92, 18, 8, ink(color=gray(13), pri={"Set": band_of(122)}, ctl=KEEP))) # the demo pile + +ROOMS[3] = { + "name": "Alternative Tentacles, 1987", + "enter_text": "San Francisco. The office smells of newsprint, coffee, and litigation. Every poster on the wall is somebody's whole life.", + "ops": ops, + "scale": {"horizon_y": 90, "min_scale": 0.72, "full_y": 186}, + "spawn": [60, 140], + "exits": [None, 4, None, 0], + "exit_flags": ["", "signed", "", ""], + "exit_blocked": ["", "Europe doesn't book the unsigned. Talk to Jello.", "", ""], + "hotspots": [ + hotspot("poster wall", [108, 8, 200, 74], msgs={ + "look": "Dead Kennedys. D.O.A. Butthole Surfers. A wall of bands that never asked permission.", + "do": "You straighten one thumbtack. History looks marginally tidier.", + }), + hotspot("window", [18, 12, 64, 48], msgs={ + "look": "Fog rolling off the bay, gold light on the bridge. Even the weather here has a record deal.", + }), + hotspot("record crates", [0, 160, 92, 30], msgs={ + "look": "Crates of vinyl heading to distributors on four continents. The machine, fed.", + }), + hotspot("desk", [148, 96, 78, 30], msgs={ + "look": "The desk: contracts, cassettes, and a rubber tarantula used as a paperweight.", + }), + ], + "props": [], + "npcs": [ + npc("Jello", "biafra", 246, 132, portrait="biafra-face", wander=14, + msgs={"look": "Jello Biafra, sorting demos like a heron reads a river."}, + dialogue=[ + node("The Canadians. The brothers. The ones who sound like — what did Trouser Press say — Wire on psychotic steroids?", + choice("Slip him the demo tape.", 1, requires_flag="demo_done", sets_flag="signed", points=10), + choice("We're NoMeansNo. And no means no.", 2), + choice("Just admiring the tarantula.", -1)), + node("He plays thirty seconds of Sex Mad. Both eyebrows achieve escape velocity. “Licensed. International. Don't shave, don't die, and tour Europe until Europe apologizes.”", + choice("Deal.", -1)), + node("“Good name. Better policy.” He gestures at the demo mountain. “Feed the machine, gentlemen.”", + choice("(Feed it.)", 0)), + ]), + ], + "music": 4, + "script": "room3.rhai", +} + +# ============================================================================= +# Room 4 — Roskilde main stage, 1994 +# ============================================================================= +ops = [] +# The MODELBEAST matte painting (pics/roskilde.png) is the sky, the towers +# and sixty thousand lighters. Ops only add what the sim needs: walls where +# the crowd is, and the stage you actually stand on. +ops.append(rect(0, 0, 320, 112, ink(color=None, ctl={"Set": 1}))) +# Stage-edge shadow line over the painting's seam. +ops.append(rect(0, 110, 320, 6, wall(gray(1)))) +# Footlight chase along the stage lip (the ember cycle runs over the art). +for i, x in enumerate(range(4, 316, 12)): + ops.append(rect(x, 113, 8, 4, wall(EMBER[i % 8]))) +# The stage itself — you stand ON it. Boards run toward the crowd. +ops.append(fbm(0, 118, 320, 72, [cube(1, 0, 0), cube(2, 1, 0), cube(1, 0, 0)], 20.0, 2, 54, floor(0))) +for y in range(126, 190, 10): + ops.append(line([[0, y], [319, y]], look(cube(0, 0, 0)))) +# Monitor wedges facing away. +ops.append(poly([[70, 122], [96, 122], [88, 132], [78, 132]], depth(gray(3), band_of(132)))) +ops.append(poly([[200, 122], [226, 122], [218, 132], [208, 132]], depth(gray(3), band_of(132)))) + +ROOMS[4] = { + "name": "Roskilde Main Stage, 1994", + "enter_text": "Denmark. Sixty thousand people breathing as one animal in the dark. Sepultura's van died in Germany, Peter Gabriel just walked off, and somebody has to be next.", + "background": "roskilde", + "ops": ops, + "cycles": [{"start": 248, "len": 8, "period": 2, "reverse": False, "active": True}], + "scale": {"horizon_y": 112, "min_scale": 0.8, "full_y": 186}, + "spawn": [50, 160], + "exits": [None, 5, None, 3], + "exit_flags": ["", "legend", "", ""], + "exit_blocked": ["", "Not before the encore. Play the show.", "", ""], + "hotspots": [ + hotspot("crowd", [0, 40, 320, 70], msgs={ + "look": "A field of lighters to the horizon. Sixty thousand Danes who came for Sepultura and stayed for whatever this is about to be.", + "listen": "The roar arrives in waves, like surf that learned your name.", + "talk": "You raise one fist. SIXTY THOUSAND PEOPLE RAISE IT BACK.", + }), + hotspot("monitors", [66, 118, 164, 18], msgs={ + "look": "Monitor wedges, mixed for a prog band. John will fix that with volume.", + }), + ], + "props": [ + prop("the rig", "rig", 276, 148, + synonyms="amps amp stack backline", + msgs={"look": "A borrowed backline the size of a rowhouse. Rob's overdrive will make it honest."}, + solid=True), + ], + "npcs": [ + npc("stage manager", "stagehand", 96, 150, wander=20, + msgs={"look": "A Danish stage manager with a clipboard and eleven headaches."}, + dialogue=[ + node("Sepultura is IN A DITCH near Flensburg. I have sixty thousand people and a hole in the schedule after Peter Gabriel. You are either legends tonight, or you are filler.", + choice("We'll take the main stage.", 1, sets_flag="mainstage", points=10), + choice("We're... technically the tent act.", 2), + choice("Give us a minute.", -1)), + node("“Then GO.” He points at the rig with the clipboard. “And whatever a 'jazzcore' is — do it LOUDLY.”", + choice("Always.", -1)), + node("“'Were.'” He crosses something out with terrifying finality.", + choice("(Reconsider.)", 0)), + ]), + ], + "music": 3, + "weather": 3, + "script": "room4.rhai", +} + +# ============================================================================= +# Room 5 — A farm field outside Poznan, 1997 +# ============================================================================= +ops = [] +ops.append(vgrad(0, 0, 320, 66, [gray(7), gray(5), gray(6)], wall(0))) +ops.append(scatter(0, 8, 320, 30, [gray(2)], 2, 61, look(0))) # distant crows +# Distant farm. +ops.append(rect(30, 44, 26, 20, wall(cube(2, 1, 0)))) +ops.append(poly([[26, 46], [43, 34], [60, 46]], wall(cube(1, 0, 0)))) +ops.append(ellipse(84, 52, 12, 12, wall(cube(0, 1, 0)))) +ops.append(rect(82, 60, 4, 8, wall(cube(2, 1, 0)))) +# The field. +ops.append(fbm(0, 66, 320, 124, [cube(1, 2, 0), cube(0, 1, 0), cube(1, 2, 1), cube(2, 2, 0)], 14.0, 3, 62, floor(0))) +# Mud track where a van was definitely driven in the dark. +ops.append(voronoi(60, 120, 200, 40, 16, [cube(2, 1, 0), cube(1, 0, 0), cube(2, 2, 1)], cube(1, 0, 0), 1.2, 63, floor(0))) +ops.append(ellipse(150, 168, 30, 6, water(cube(1, 1, 2)))) + +ROOMS[5] = { + "name": "A Field Outside Poznan, 1997", + "enter_text": "Poland. Dawn the color of dishwater. Your van — your entire livelihood with wheels on it — sits in a farm field, and a man in a tracksuit is leaning on it.", + "ops": ops, + "scale": {"horizon_y": 70, "min_scale": 0.55, "full_y": 186}, + "spawn": [30, 160], + "exits": [None, None, None, 4], + "hotspots": [ + hotspot("farmhouse", [24, 30, 40, 36], msgs={ + "look": "A farmhouse minding its own business at a professional level.", + }), + ], + "props": [ + prop("van", "van", 180, 118, + synonyms="tourvan vehicle", + msgs={ + "look": "The van. Every amp, every pedal, every unwashed sleeping bag — the whole band, parked in the wrong country.", + }, + solid=True), + ], + "npcs": [ + npc("mobster", "mobster", 236, 142, portrait="mobster-face", + msgs={"look": "Tracksuit, flat cap, and the calm of a man who has never once hurried."}, + dialogue=[ + node("“Nice van,” he says, in better English than yours. “Shame about the neighborhood. Storage fees in this district are… irregular.”", + choice("Name your price.", 1), + choice("Do you KNOW who we are?", 2, kills=True), + choice("We'll come back.", -1)), + node("“Two and a half thousand. American. The van, the amps, the little drum machine — all of it, untouched.” He pats the van gently, like a horse.", + choice("Strike the deal.", 3, sets_flag="deal_struck"), + choice("Outrageous.", -1)), + node("In a field outside a town you cannot pronounce, it turns out that nobody, in fact, knows who you are.", ), + node("“Pleasure doing business with professionals.” He steps away from the van and lights a cigarette against the wind, first try.", + choice("(Get the money.)", -1)), + ]), + ], + "music": 2, + "weather": 1, + "script": "room5.rhai", +} + + +def main(): + os.makedirs(OUT, exist_ok=True) + for n, doc in ROOMS.items(): + # strip helper keys serde doesn't know + for h in doc.get("hotspots", []): + h.pop("synonyms", None) + path = os.path.join(OUT, f"room{n}.json") + with open(path, "w") as f: + json.dump(doc, f, indent=1, ensure_ascii=False) + print(f" room{n}.json ({len(doc.get('ops', []))} ops)") + + +if __name__ == "__main__": + main() diff --git a/tools/gen_sprites.py b/tools/gen_sprites.py new file mode 100755 index 0000000..0bfe2cd --- /dev/null +++ b/tools/gen_sprites.py @@ -0,0 +1,586 @@ +#!/usr/bin/env python3 +"""Generate NOMEANSNO QUEST sprites as PNGs — stdlib only (zlib + struct). + +Sprites are ASCII grids: each char maps to an RGB in the sprite's legend, +'.' is transparent. MRPCI quantizes them per-room, so colors here are +intent, not law. Run from the repo root: python3 tools/gen_sprites.py +""" + +import os +import struct +import zlib + +OUT = os.path.join(os.path.dirname(__file__), "..", "sprites") + + +def write_png(path, rows, legend, scale=1): + rows = [r for r in rows for _ in range(scale)] + h = len(rows) + w = max(len(r) for r in rows) * scale + raw = b"" + for y in range(h): + raw += b"\x00" # filter: none + row = rows[y].ljust(w // scale, ".") + for ch in row: + px = b"\x00\x00\x00\x00" if ch == "." else struct.pack("BBBB", *legend[ch], 255) + raw += px * scale + + def chunk(tag, data): + c = struct.pack(">I", len(data)) + tag + data + return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) + + png = b"\x89PNG\r\n\x1a\n" + png += chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0)) + png += chunk(b"IDAT", zlib.compress(raw, 9)) + png += chunk(b"IEND", b"") + with open(path, "wb") as f: + f.write(png) + print(f" {os.path.basename(path)} {w}x{h}") + + +S = {} # name -> (rows, legend) + +# --- people ------------------------------------------------------------- +# Rob Wright — the ego. Tall, calm, bass slung low. (Feet = bottom center.) +S["rob"] = ( + [ + "....hhhh....", + "....hhhh....", + "....ffff....", + "....f2f2....", + "....ffff....", + ".....ff.....", + "...ssssss...", + "..ssssssss..", + "..s.ssss.s..", + "..s.ssss.s..", + "..b.ssss....", + ".bbbrssss...", + ".bb.ssss....", + "..b.pppp....", + "....pppp....", + "....p..p....", + "....p..p....", + "....p..p....", + "....p..p....", + "...oo..oo...", + ], + { + "h": (72, 52, 36), # hair + "f": (214, 178, 148),# face + "2": (40, 40, 48), # eyes + "s": (52, 56, 64), # dark shirt + "p": (44, 48, 88), # jeans + "o": (30, 30, 34), # boots + "b": (150, 60, 40), # bass body + "r": (220, 200, 160),# bass neck + }, +) + +# John Wright — behind the kit or beside it; sticks in hand. +S["john"] = ( + [ + "...hhhhh...", + "...hhhhh...", + "...ffff....", + "...f2f2....", + "...ffff....", + "....ff.....", + "..tttttt...", + ".tttttttt..", + ".t.tttt.t..", + ".w.tttt.w..", + "...tttt....", + "...pppp....", + "...p..p....", + "...p..p....", + "...p..p....", + "..oo..oo...", + ], + { + "h": (60, 44, 30), + "f": (216, 182, 152), + "2": (40, 40, 48), + "t": (140, 40, 44), # red tee + "w": (222, 214, 180),# drumsticks + "p": (40, 42, 50), + "o": (30, 30, 34), + }, +) + +S["mom"] = ( + [ + "...hhhhh...", + "..hhhhhhh..", + "...ffff....", + "...f2f2....", + "...ffff....", + "....ff.....", + "..dddddd...", + ".dddddddd..", + ".d.aaaa.d..", + "...aaaa....", + "...aaaa....", + "...dddd....", + "...d..d....", + "..oo..oo...", + ], + { + "h": (168, 160, 150), + "f": (216, 184, 156), + "2": (50, 44, 40), + "d": (90, 110, 140), # housedress + "a": (228, 226, 214),# apron + "o": (60, 50, 44), + }, +) + +# Joey Shithead of D.O.A., on stage. Mohawk not optional. +S["joey"] = ( + [ + "....kk.....", + "...kkkk....", + "....kk.....", + "...ffff....", + "...f2f2....", + "...ffff....", + "....ff.....", + "..lllllll..", + ".lllllllll.", + ".l.lllll.l.", + ".g.lllll...", + ".gg.lll....", + "...pppp....", + "...p..p....", + "...p..p....", + "..oo..oo...", + ], + { + "k": (30, 160, 60), # green mohawk + "f": (210, 172, 140), + "2": (30, 30, 36), + "l": (36, 36, 40), # leather + "g": (170, 120, 60), # guitar neck + "p": (46, 46, 52), + "o": (26, 26, 30), + }, +) + +S["biafra"] = ( + [ + "...hhhhh...", + "...hhhhh...", + "...ffff....", + "...f2f2....", + "...ffff....", + "....ff.....", + "..jjjjjj...", + ".jjjjjjjj..", + ".j.tttt.j..", + "...tttt....", + "...tttt....", + "...pppp....", + "...p..p....", + "...p..p....", + "..oo..oo...", + ], + { + "h": (50, 38, 30), + "f": (214, 178, 148), + "2": (36, 40, 48), + "j": (70, 60, 90), # thrift blazer + "t": (200, 60, 50), # loud tee + "p": (50, 50, 58), + "o": (30, 30, 34), + }, +) + +S["stagehand"] = ( + [ + "...ccccc...", + "...ffff....", + "...f2f2....", + "...ffff....", + "....ff.....", + "..vvvvvv...", + ".vvvvvvvv..", + ".v.vvvv.v..", + "...vvvv....", + "...vvvv....", + "...pppp....", + "...p..p....", + "...p..p....", + "..oo..oo...", + ], + { + "c": (40, 40, 48), # headset cap + "f": (208, 172, 140), + "2": (36, 36, 44), + "v": (240, 180, 40), # hi-vis + "p": (44, 46, 54), + "o": (28, 28, 32), + }, +) + +S["mobster"] = ( + [ + "...bbbbb...", + "...ffff....", + "...f2f2....", + "...ffff....", + "...ffnf....", + "....ff.....", + ".ttttttttt.", + ".tttttttte.", + ".t.tttt.te.", + ".t.tttt.t..", + "...tttt....", + "...tttt....", + "...ssss....", + "...s..s....", + "...s..s....", + "..oo..oo...", + ], + { + "b": (30, 30, 34), # flat cap + "f": (206, 168, 138), + "2": (30, 34, 40), + "n": (150, 110, 90), # that nose + "t": (60, 90, 60), # tracksuit + "e": (220, 200, 80), # gold stripe + "s": (54, 56, 60), + "o": (26, 26, 30), + }, +) + +# --- portraits (24x24, drawn 2x in core windows, 6x in the GUI) ---------- +S["john-face"] = ( + [ + "bbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbbhhhhhhhhhhhhhbbbbbb", + "bbbbhhhhhhhhhhhhhhhbbbbb", + "bbbhhhhhhhhhhhhhhhhhbbbb", + "bbbhhfffffffffffffhhbbbb", + "bbbhffffffffffffffhhbbbb", + "bbbhffffffffffffffhbbbbb", + "bbbhff22ffffff22ffhbbbbb", + "bbbhff22ffffff22ffhbbbbb", + "bbbbffffffffffffffbbbbbb", + "bbbbffffffnnffffffbbbbbb", + "bbbbffffffnnffffffbbbbbb", + "bbbbffffffffffffffbbbbbb", + "bbbbfffmmmmmmmmfffbbbbbb", + "bbbbffffmmmmmmffffbbbbbb", + "bbbbbffffffffffffbbbbbbb", + "bbbbbbffffffffffbbbbbbbb", + "bbbbbbbttttttttbbbbbbbbb", + "bbbbbbttttttttttbbbbbbbb", + "bbbbbttttttttttttbbbbbbb", + "bbbbttttttttttttttbbbbbb", + "bbbbttttttttttttttbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbb", + ], + { + "b": (24, 26, 34), + "h": (60, 44, 30), + "f": (216, 182, 152), + "2": (40, 40, 48), + "n": (190, 150, 122), + "m": (150, 90, 80), + "t": (140, 40, 44), + }, +) + +S["mom-face"] = ( + [ + "bbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbbhhhhhhhhhhhhhbbbbbb", + "bbbhhhhhhhhhhhhhhhhbbbbb", + "bbhhhhhhhhhhhhhhhhhhbbbb", + "bbhhhffffffffffffhhhbbbb", + "bbhhffffffffffffffhhbbbb", + "bbhhffffffffffffffhhbbbb", + "bbhhff22ffffff22ffhhbbbb", + "bbhhff22ffffff22ffhhbbbb", + "bbhhffffffffffffffhhbbbb", + "bbhhffffffnnffffffhhbbbb", + "bbhhffffffffffffffhhbbbb", + "bbhhfffmmmmmmmmfffhhbbbb", + "bbhhffffmmmmmmffffhhbbbb", + "bbbhhffffffffffffhhbbbbb", + "bbbhhhffffffffffhhhbbbbb", + "bbbbhhhhffffffhhhhbbbbbb", + "bbbbbbddddddddddbbbbbbbb", + "bbbbbddddddddddddbbbbbbb", + "bbbbddddddddddddddbbbbbb", + "bbbbddddaaaaaaddddbbbbbb", + "bbbbdddaaaaaaaadddbbbbbb", + "bbbbdddaaaaaaaadddbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbb", + ], + { + "b": (26, 26, 32), + "h": (168, 160, 150), + "f": (216, 184, 156), + "2": (50, 44, 40), + "n": (192, 154, 128), + "m": (160, 100, 90), + "d": (90, 110, 140), + "a": (228, 226, 214), + }, +) + +S["biafra-face"] = ( + [ + "bbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbhhhhhhhhhhhhhhbbbbbb", + "bbbhhhhhhhhhhhhhhhhbbbbb", + "bbbhhhhhhhhhhhhhhhhbbbbb", + "bbbhhffffffffffffhhbbbbb", + "bbbhffffffffffffffhbbbbb", + "bbbhffffffffffffffhbbbbb", + "bbbhffeeffffffeeffhbbbbb", + "bbbhff22ffffff22ffhbbbbb", + "bbbhffffffffffffffhbbbbb", + "bbbhffffffnnffffffhbbbbb", + "bbbhffffffffffffffhbbbbb", + "bbbbffffffffffffffbbbbbb", + "bbbbfffmmmmmmmmfffbbbbbb", + "bbbbffmmmmmmmmmmffbbbbbb", + "bbbbbfffmmmmmmfffbbbbbbb", + "bbbbbbffffffffffbbbbbbbb", + "bbbbbbbjjjjjjjjbbbbbbbbb", + "bbbbbjjjjjjjjjjjjbbbbbbb", + "bbbbjjjjttttttjjjjbbbbbb", + "bbbbjjjttttttttjjjbbbbbb", + "bbbbjjjttttttttjjjbbbbbb", + "bbbbjjjttttttttjjjbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbb", + ], + { + "b": (28, 24, 34), + "h": (50, 38, 30), + "f": (214, 178, 148), + "2": (36, 40, 48), + "e": (90, 90, 110), # that stare, pre-twinkle + "n": (188, 148, 120), + "m": (150, 84, 76), # the grin + "j": (70, 60, 90), + "t": (200, 60, 50), + }, +) + +S["mobster-face"] = ( + [ + "bbbbbbbbbbbbbbbbbbbbbbbb", + "bbbbkkkkkkkkkkkkkkbbbbbb", + "bbbkkkkkkkkkkkkkkkkbbbbb", + "bbbkkkkkkkkkkkkkkkkbbbbb", + "bbbbffffffffffffffbbbbbb", + "bbbfffffffffffffffbbbbbb", + "bbbff2222ffff2222ffbbbbb", + "bbbff2222ffff2222ffbbbbb", + "bbbffffffffffffffffbbbbb", + "bbbfffffnnnnnnfffffbbbbb", + "bbbfffffnnnnnnfffffbbbbb", + "bbbffffffffffffffffbbbbb", + "bbbbffffffffffffffbbbbbb", + "bbbbfffmmmmmmmmfffbbbbbb", + "bbbbffffffffffffffbbbbbb", + "bbbbbffffffffffffbbbbbbb", + "bbbbbbffffffffffbbbbbbbb", + "bbbbbbbttttttttbbbbbbbbb", + "bbbbbttttttttttttbbbbbbb", + "bbbbtttteeeeeetttbbbbbbb", + "bbbbttttttttttttttbbbbbb", + "bbbbttttttttttttttbbbbbb", + "bbbbttttttttttttttbbbbbb", + "bbbbbbbbbbbbbbbbbbbbbbbb", + ], + { + "b": (24, 28, 26), + "k": (30, 30, 34), + "f": (206, 168, 138), + "2": (30, 34, 40), + "n": (150, 110, 90), + "m": (120, 70, 64), + "t": (60, 90, 60), + "e": (220, 200, 80), + }, +) + +# --- props ---------------------------------------------------------------- +S["portastudio"] = ( + [ + "gggggggggggggggggggggggggg", + "gssssssssssssssssssssssssg", + "gsrrrrrssgggggggssrrrrrssg", + "gsr...rssgddddggssr...rssg", + "gsr.o.rssgddddggssr.o.rssg", + "gsr...rssgggggggssr...rssg", + "gsrrrrrssssssssssssrrrrssg", + "gsssssssskkkkkkkksssssssg.", + "gsswwswwsswwsswwsswwswwsg.", + "gsswwswwsswwsswwsswwswwsg.", + "gggggggggggggggggggggggg..", + ], + { + "g": (70, 72, 78), + "s": (98, 100, 108), + "r": (50, 50, 56), + "o": (200, 190, 170), + "d": (180, 60, 50), # VU meters + "k": (36, 36, 40), + "w": (210, 208, 200),# faders + }, +) + +S["castlebox"] = ( + [ + "cccccccccccccccccccccc", + "cttttttttttttttttttttc", + "ct__CASTLE__(RIP)___tc", + "cttttttttttttttttttttc", + "cccccccccccccccccccccc", + "cccccccccccccccccccccc", + "cccccccccccccccccccccc", + "cccccccccccccccccccccc", + ], + { + "c": (150, 110, 66), + "t": (120, 86, 50), + "_": (120, 86, 50), + "C": (60, 44, 30), + "A": (60, 44, 30), + "S": (60, 44, 30), + "T": (60, 44, 30), + "L": (60, 44, 30), + "E": (60, 44, 30), + "R": (60, 44, 30), + "I": (60, 44, 30), + "P": (60, 44, 30), + "(": (60, 44, 30), + ")": (60, 44, 30), + }, +) + +S["flyer"] = ( + [ + "wwwwwwwwww", + "wkkkkkkkkw", + "wwwwwwwwww", + "wkkkwwkkww", + "wwwwwwwwww", + "wkkkkkwwww", + "wwwwwwwwww", + ], + {"w": (226, 222, 210), "k": (40, 40, 44)}, +) + +S["drumkit"] = ( + [ + "........cc..........cc........", + ".........c..........c.........", + "....hhhhhhhhh..hhhhhhhhh......", + "....h.......h..h.......h......", + "....hhhhhhhhh..hhhhhhhhh......", + "..bbbbbbbbbbbbbbbbbbbbbbbb....", + ".bbwwwwwwwwwwwwwwwwwwwwwwbb...", + ".bbwwwwwwwwwwwwwwwwwwwwwwbb...", + ".bbwwwwwwwwwwwwwwwwwwwwwwbb...", + ".bbbbbbbbbbbbbbbbbbbbbbbbbb...", + "...ss...ss........ss...ss.....", + "..ss.....ss......ss.....ss....", + ], + { + "c": (210, 190, 120), # cymbals + "h": (120, 60, 50), # toms + "b": (140, 40, 44), # kick shell + "w": (230, 226, 214), # kick head + "s": (140, 140, 150), + }, +) + +S["rig"] = ( + [ + "kkkkkkkkkkkkkkkkkkkkkkkkkkkk", + "kggggggggggggkkggggggggggggk", + "kg..........gkkg..........gk", + "kg.oooooooo.gkkg.oooooooo.gk", + "kg.oooooooo.gkkg.oooooooo.gk", + "kg..........gkkg..........gk", + "kkkkkkkkkkkkkkkkkkkkkkkkkkkk", + "kggggggggggggkkggggggggggggk", + "kg.oooooooo.gkkg.oooooooo.gk", + "kg.oooooooo.gkkg.oooooooo.gk", + "kg..........gkkg..........gk", + "kkkkkkkkkkkkkkkkkkkkkkkkkkkk", + "kkrrkkkkkkkkkkkkkkkkkkkkrrkk", + ], + { + "k": (30, 30, 34), + "g": (56, 56, 62), + "o": (80, 76, 70), # speaker cones + "r": (200, 60, 50), # power lamps + }, +) + +S["van"] = ( + [ + "..........wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww..........", + "........wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww........", + ".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......", + ".......wggggggwwwwwwwwwwwwwwwwwwwwwwwwwwwggggggww.......", + ".......wggggggwwwwwwwwwwwwwwwwwwwwwwwwwwwggggggww.......", + ".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......", + ".......wwwwkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkwwwww.......", + ".......wwwwkNOMEANSNOwwwwwwwwwwwwwwwwwwwwwwkwwwww.......", + ".......wwwwkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkwwwww.......", + ".......wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.......", + "........bb..tttt..bbbbbbbbbbbbbbbbbb..tttt..bb...........", + "........bb.tttttt.bbbbbbbbbbbbbbbbbb.tttttt.bb...........", + "............tttt......................tttt..............", + ], + { + "w": (216, 214, 206), + "g": (110, 140, 160), # windows + "k": (60, 60, 66), # hand-painted band stripe + "N": (220, 60, 50), + "O": (220, 60, 50), + "M": (220, 60, 50), + "E": (220, 60, 50), + "A": (220, 60, 50), + "S": (220, 60, 50), + "b": (70, 70, 76), + "t": (30, 30, 34), + }, +) + +S["dumpster"] = ( + [ + "gggggggggggggggggggggggggggggggggggg", + "ggllllllllllllllllllllllllllllllllgg", + "gg..............................gg..", + "ggllllllllllllllllllllllllllllllgg..", + "gg..............................gg..", + "gggggggggggggggggggggggggggggggggg..", + "..kk..........................kk....", + "..kk..........................kk....", + ], + {"g": (60, 90, 70), "l": (80, 112, 88), "k": (30, 30, 34)}, +) + + +def main(): + os.makedirs(OUT, exist_ok=True) + print("writing sprites:") + for name, (rows, legend) in S.items(): + # People and props render 2x (320-wide rooms); portraits and the + # tiny flyer stay 1:1. + scale = 1 if name.endswith("-face") or name == "flyer" else 2 + write_png(os.path.join(OUT, f"{name}.png"), rows, legend, scale) + + +if __name__ == "__main__": + main() diff --git a/tools/mb_background.py b/tools/mb_background.py new file mode 100755 index 0000000..073a348 --- /dev/null +++ b/tools/mb_background.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Generate a room background on the MODELBEAST farm (flux_local). + +Reads MB_TOKEN from ~/Documents/backnforth/.env (never printed). The engine +quantizes whatever lands in pics/ to 256 colors at load, so the prompt aims +for painterly SCI1 material, not pixel-perfection. + + python3 tools/mb_background.py roskilde "prompt text..." +""" + +import json +import os +import re +import sys +import time +import urllib.request + +HOST = "http://100.89.131.57:8777" + + +def token(): + # MB_TOKEN env var, else the usual .env spots (ultra keeps it in + # backnforth, the m3ultra primary in MODELBEAST/.env as MB_LOCAL_TOKEN). + if os.environ.get("MB_TOKEN"): + return os.environ["MB_TOKEN"] + for env, keys in [ + (os.path.expanduser("~/Documents/backnforth/.env"), ("MB_TOKEN",)), + (os.path.expanduser("~/Documents/MODELBEAST/.env"), ("MB_TOKEN", "MB_LOCAL_TOKEN")), + ]: + if not os.path.exists(env): + continue + with open(env) as f: + for line in f: + for k in keys: + m = re.match(rf"\s*{k}\s*=\s*(\S+)", line) + if m: + return m.group(1).strip().strip('"') + raise SystemExit("no MODELBEAST token found in known .env locations") + + +def api(path, data=None, raw=False): + req = urllib.request.Request( + HOST + path, + data=json.dumps(data).encode() if data is not None else None, + headers={"Authorization": f"Bearer {token()}", "Content-Type": "application/json"}, + method="POST" if data is not None else "GET", + ) + with urllib.request.urlopen(req, timeout=60) as r: + body = r.read() + if raw: + return body + # job logs can carry raw control chars — strip before parsing + text = re.sub(rb"[\x00-\x08\x0b-\x1f]", b"", body).decode("utf-8", "replace") + return json.loads(text) + + +def main(): + name, prompt = sys.argv[1], sys.argv[2] + job = api("/api/jobs", {"operator": "flux_local", "params": {"prompt": prompt, "width": 1024, "height": 608}}) + jid = job.get("id") or job.get("job_id") + print(f"submitted flux_local job {jid}") + for _ in range(120): + time.sleep(3) + j = api(f"/api/jobs/{jid}") + status = j.get("status", "?") + if status in ("done", "error"): + print(f"job {jid}: {status}") + if status == "error": + raise SystemExit(1) + break + else: + raise SystemExit("timed out") + # newest own asset; jobs don't back-link, so match parent_job + assets = api("/api/assets") + items = assets if isinstance(assets, list) else assets.get("assets", assets.get("items", [])) + mine = [a for a in items if str(a.get("parent_job", "")) == str(jid)] + pick = (mine or items)[0] + data = api(f"/api/assets/{pick['id']}/file", raw=True) + out = os.path.join(os.path.dirname(__file__), "..", "pics", f"{name}.png") + os.makedirs(os.path.dirname(out), exist_ok=True) + with open(out, "wb") as f: + f.write(data) + print(f"wrote pics/{name}.png ({len(data)} bytes, asset {pick.get('name', pick['id'])})") + + +if __name__ == "__main__": + main()