diff --git a/README.md b/README.md index 1ea1787..6efaf53 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ go run . -screen selector # the location selector: the map the city hangs off go run . -screen hospital # any screen in the deck, e.g. to look at the art ``` -Screen names are the constants in `internal/names` (`selector`, `paul_shop`, +Screen names are the constants in `names.manager.go` (`selector`, `paul_shop`, `noodle_house`, `alley`, `norman_apartment`, `police_station`, `hackerspace`, `ice_cream_shop`, `trinket_shop`, `small_restaurant`, `secret_club`, `bbs_terminal`, `street`, `hospital`, `server_farm`, `secret_lab`, @@ -151,18 +151,24 @@ licence. ## Structure +The game is one flat package, `inc`. There are no subdirectories: a file's +name says where it belongs, in the form `[category].[name].go`. The category is +always singular, and `[category].manager.go` is the file that ties that category +together — its `register…`, its shared types, its list. + ``` main.go flags + inkwell.Run -internal/names/ entity names and world-state keys -internal/theme/ realworld-93 + nokia-punk -internal/world/ unsaved runtime state, custom actions, action pump -internal/ui/ HUD: layout, custom widgets, coloured text -internal/content/ one file per registered entity, by kind -internal/boot/ wiring +inc/names.manager.go entity names and world-state keys +inc/theme.manager.go realworld-93 + nokia-punk +inc/world.*.go unsaved runtime state, custom actions, action pump +inc/ui.*.go HUD: layout, custom widgets, coloured text +inc/..go one file per registered entity, by kind +inc/boot.manager.go wiring ``` -`world` deliberately knows nothing about the HUD or the content — both build on -it, so the reverse would be a cycle: +Nothing is enforced by the compiler any more, so the layering is a rule the +code keeps by hand: the world knows nothing about the HUD or the content, and +both build on it, never the other way round. ``` names ← theme ← world ← ui @@ -170,26 +176,30 @@ names ← theme ← world ← ui content ──┴── boot ← main ``` -Content is one registered entity per file, grouped by kind: +Content is one registered entity per file, and the category prefix groups them +the way directories used to: ``` -internal/content/ - background/ one file per screen: paul_shop.go, noodle_house.go, … - character/ paul.go dex.go mystery_tape.go support_tape.go - item/ noodle_letter.go black_market_armilla.go mystery_tape.go - dialog/ dex_talk.go mystery_tape_silent.go - script/ tape_insert.go awakening_finale.go - screen/ one file per screen: alley.go, trinket_shop.go, … + exit.go +background.*.go one file per screen: background.paul_shop.go, … +character.*.go character.paul.go character.dex.go character.mystery_tape.go +item.*.go item.noodle_letter.go item.black_market_armilla.go … +dialog.*.go dialog.dex_talk.go dialog.mystery_tape_silent.go +script.*.go script.tape_insert.go script.awakening_finale.go +screen.*.go one file per screen: screen.alley.go, … + screen.exit.go ``` -Adding a screen means adding `screen/.go` and `background/.go`, and -one line in each package's list. Nothing else moves. +Adding a screen means adding `screen..go` and `background..go`, and +one line in each category's manager list. Nothing else moves. + +Since everything shares one namespace, an entity's constructor carries its +category: `screenAlley()` is the screen, `backgroundAlley()` the painting behind +it, `itemMysteryTape()` the prop and `characterMysteryTape()` the voice on it. **On the word "screen".** inkwell's entity is called a `Scene`, and that is the -type every file in `screen/` returns — but the wiki, the concept-art deck and -the beat tables all count *screens*, so the package, the files and the flag say +type every `screen.*.go` file returns — but the wiki, the concept-art deck and +the beat tables all count *screens*, so the category, the files and the flag say screen. "Scene" survives only where the code is talking to the engine -(`world.EnterScene`, `SceneManager`). +(`EnterScene`, `SceneManager`). Art lives in `assets/bg/` and is embedded into the binary (`main.go`), because js/wasm has no OS filesystem and `make binaries` packages the executable alone. @@ -234,13 +244,13 @@ for a small engine change. 1. **`drawText` discards colour.** In `asset.text.go` the colour argument is `_ = c` and rendering goes through `ebitenutil.DebugPrintAt`, which only draws white. Every text colour in `Theme` is therefore inert. *Workaround:* - `ui/text.go` renders onto a scratch image and blits it tinted with + `ui.text.go` renders onto a scratch image and blits it tinted with `ColorScale`. The custom widgets colour correctly; the built-ins (`StatusLine`, `DialogBox`, `TopBar`, `InventoryBar`) are still white. *Fix:* move `drawText` to `text/v2` — no call site would change. 2. **`queueAction` is unexported**, so a domain widget cannot start an action. - *Workaround:* the pump in `world/action.go` drives its own `Runner` through + *Workaround:* the pump in `world.action.go` drives its own `Runner` through the exported `inkwell.Ctx`. *Caveat:* it runs alongside the engine's script runner, not instead of it. @@ -251,7 +261,7 @@ for a small engine change. character instead, escalating on repeats. 4. **No exported `CurrentScene()`**, but the pump needs `Ctx.Scene`. - *Workaround:* `world.EnterScene` is the first step of every scene's + *Workaround:* `EnterScene` is the first step of every scene's `OnEnter`; the domain tracks it. 5. **Widgets have no `Visible` field and the `Manager` cannot unregister**, so diff --git a/internal/content/background/alley.go b/inc/background.alley.go similarity index 75% rename from internal/content/background/alley.go rename to inc/background.alley.go index b537387..137f0bf 100644 --- a/internal/content/background/alley.go +++ b/inc/background.alley.go @@ -1,18 +1,16 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // alley — part of the catalogue's "Noodle háza & a sikátor" row — not in the deck. // // Not painted yet: the file is missing on purpose, and inkwell draws its // placeholder instead — which is exactly the greybox convention. -func alley() inkwell.Asset { +func backgroundAlley() inkwell.Asset { return inkwell.Asset{ - Name: names.BgAlley, + Name: BgAlley, Path: "assets/bg/alley.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/bbs_terminal.go b/inc/background.bbs_terminal.go similarity index 56% rename from internal/content/background/bbs_terminal.go rename to inc/background.bbs_terminal.go index af43011..507fcb3 100644 --- a/internal/content/background/bbs_terminal.go +++ b/inc/background.bbs_terminal.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // bbsTerminal — deck 11. -func bbsTerminal() inkwell.Asset { +func backgroundBbsTerminal() inkwell.Asset { return inkwell.Asset{ - Name: names.BgBBSTerminal, + Name: BgBBSTerminal, Path: "assets/bg/bbs_terminal.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/bvk_branch.go b/inc/background.bvk_branch.go similarity index 56% rename from internal/content/background/bvk_branch.go rename to inc/background.bvk_branch.go index 4b85542..4e45269 100644 --- a/internal/content/background/bvk_branch.go +++ b/inc/background.bvk_branch.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // bvkBranch — deck 18. -func bvkBranch() inkwell.Asset { +func backgroundBvkBranch() inkwell.Asset { return inkwell.Asset{ - Name: names.BgBVKBranch, + Name: BgBVKBranch, Path: "assets/bg/bvk_branch.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/columbarium.go b/inc/background.columbarium.go similarity index 56% rename from internal/content/background/columbarium.go rename to inc/background.columbarium.go index 26b92fe..f43a1b6 100644 --- a/internal/content/background/columbarium.go +++ b/inc/background.columbarium.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // columbarium — deck 23. -func columbarium() inkwell.Asset { +func backgroundColumbarium() inkwell.Asset { return inkwell.Asset{ - Name: names.BgColumbarium, + Name: BgColumbarium, Path: "assets/bg/columbarium.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/curator_shop.go b/inc/background.curator_shop.go similarity index 56% rename from internal/content/background/curator_shop.go rename to inc/background.curator_shop.go index df9945e..e77d8e4 100644 --- a/internal/content/background/curator_shop.go +++ b/inc/background.curator_shop.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // curatorShop — deck 19. -func curatorShop() inkwell.Asset { +func backgroundCuratorShop() inkwell.Asset { return inkwell.Asset{ - Name: names.BgCuratorShop, + Name: BgCuratorShop, Path: "assets/bg/curator_shop.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/hackerspace.go b/inc/background.hackerspace.go similarity index 56% rename from internal/content/background/hackerspace.go rename to inc/background.hackerspace.go index 4330887..216a061 100644 --- a/internal/content/background/hackerspace.go +++ b/inc/background.hackerspace.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // hackerspace — deck 06. -func hackerspace() inkwell.Asset { +func backgroundHackerspace() inkwell.Asset { return inkwell.Asset{ - Name: names.BgHackerspace, + Name: BgHackerspace, Path: "assets/bg/hackerspace.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/hospital.go b/inc/background.hospital.go similarity index 56% rename from internal/content/background/hospital.go rename to inc/background.hospital.go index 33d4090..0631751 100644 --- a/internal/content/background/hospital.go +++ b/inc/background.hospital.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // hospital — deck 13. -func hospital() inkwell.Asset { +func backgroundHospital() inkwell.Asset { return inkwell.Asset{ - Name: names.BgHospital, + Name: BgHospital, Path: "assets/bg/hospital.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/ice_cream_shop.go b/inc/background.ice_cream_shop.go similarity index 56% rename from internal/content/background/ice_cream_shop.go rename to inc/background.ice_cream_shop.go index 552ccf3..62ad27a 100644 --- a/internal/content/background/ice_cream_shop.go +++ b/inc/background.ice_cream_shop.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // iceCreamShop — deck 07. -func iceCreamShop() inkwell.Asset { +func backgroundIceCreamShop() inkwell.Asset { return inkwell.Asset{ - Name: names.BgIceCreamShop, + Name: BgIceCreamShop, Path: "assets/bg/ice_cream_shop.png", Kind: inkwell.AssetImage, } diff --git a/inc/background.manager.go b/inc/background.manager.go new file mode 100644 index 0000000..aa1d454 --- /dev/null +++ b/inc/background.manager.go @@ -0,0 +1,47 @@ +package inc + +// Backgrounds — one image asset per screen, one file each. +// +// A background is the whole of a screen's art: inkwell scales it over the +// entire window, so every file here points at a 640×380 PNG under assets/bg/, +// the size the concept-art deck is drawn at. Two of them are not painted yet +// and fall back to inkwell's placeholder. +// +// The order below is the deck's, which is the wiki's location-catalogue order +// (projects/realworld/entities#helyszinek). + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// registerBackground adds every background to the game. +func registerBackground(w *World) { + for _, a := range []inkwell.Asset{ + backgroundSelector(), + backgroundPaulShop(), + backgroundNoodleHouse(), + backgroundNormanApartment(), + backgroundPoliceStation(), + backgroundAlley(), + backgroundHackerspace(), + backgroundIceCreamShop(), + backgroundTrinketShop(), + backgroundSmallRestaurant(), + backgroundSecretClub(), + backgroundBbsTerminal(), + backgroundStreet(), + backgroundHospital(), + backgroundServerFarm(), + backgroundSecretLab(), + backgroundRooftopHideout(), + backgroundPublicBBS(), + backgroundBvkBranch(), + backgroundCuratorShop(), + backgroundScrapMarket(), + backgroundSamizdatPress(), + backgroundShowroom(), + backgroundColumbarium(), + } { + w.G.AssetManager.Register(a) + } +} diff --git a/internal/content/background/noodle_house.go b/inc/background.noodle_house.go similarity index 56% rename from internal/content/background/noodle_house.go rename to inc/background.noodle_house.go index 3bedf11..c72bacd 100644 --- a/internal/content/background/noodle_house.go +++ b/inc/background.noodle_house.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // noodleHouse — deck 02. -func noodleHouse() inkwell.Asset { +func backgroundNoodleHouse() inkwell.Asset { return inkwell.Asset{ - Name: names.BgNoodleHouse, + Name: BgNoodleHouse, Path: "assets/bg/noodle_house.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/norman_apartment.go b/inc/background.norman_apartment.go similarity index 56% rename from internal/content/background/norman_apartment.go rename to inc/background.norman_apartment.go index f920259..5083482 100644 --- a/internal/content/background/norman_apartment.go +++ b/inc/background.norman_apartment.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // normanApartment — deck 03. -func normanApartment() inkwell.Asset { +func backgroundNormanApartment() inkwell.Asset { return inkwell.Asset{ - Name: names.BgNormanApartment, + Name: BgNormanApartment, Path: "assets/bg/norman_apartment.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/paul_shop.go b/inc/background.paul_shop.go similarity index 57% rename from internal/content/background/paul_shop.go rename to inc/background.paul_shop.go index 4a89fe6..2e18d15 100644 --- a/internal/content/background/paul_shop.go +++ b/inc/background.paul_shop.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // paulShop — deck 01. -func paulShop() inkwell.Asset { +func backgroundPaulShop() inkwell.Asset { return inkwell.Asset{ - Name: names.BgPaulShop, + Name: BgPaulShop, Path: "assets/bg/paul_shop.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/police_station.go b/inc/background.police_station.go similarity index 56% rename from internal/content/background/police_station.go rename to inc/background.police_station.go index 7254ddd..8cbf219 100644 --- a/internal/content/background/police_station.go +++ b/inc/background.police_station.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // policeStation — deck 04. -func policeStation() inkwell.Asset { +func backgroundPoliceStation() inkwell.Asset { return inkwell.Asset{ - Name: names.BgPoliceStation, + Name: BgPoliceStation, Path: "assets/bg/police_station.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/public_bbs.go b/inc/background.public_bbs.go similarity index 56% rename from internal/content/background/public_bbs.go rename to inc/background.public_bbs.go index fa9ae7e..4bc3c5e 100644 --- a/internal/content/background/public_bbs.go +++ b/inc/background.public_bbs.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // publicBbs — deck 17. -func publicBbs() inkwell.Asset { +func backgroundPublicBBS() inkwell.Asset { return inkwell.Asset{ - Name: names.BgPublicBBS, + Name: BgPublicBBS, Path: "assets/bg/public_bbs.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/rooftop_hideout.go b/inc/background.rooftop_hideout.go similarity index 56% rename from internal/content/background/rooftop_hideout.go rename to inc/background.rooftop_hideout.go index 33655da..31e2f67 100644 --- a/internal/content/background/rooftop_hideout.go +++ b/inc/background.rooftop_hideout.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // rooftopHideout — deck 16. -func rooftopHideout() inkwell.Asset { +func backgroundRooftopHideout() inkwell.Asset { return inkwell.Asset{ - Name: names.BgRooftopHideout, + Name: BgRooftopHideout, Path: "assets/bg/rooftop_hideout.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/samizdat_press.go b/inc/background.samizdat_press.go similarity index 56% rename from internal/content/background/samizdat_press.go rename to inc/background.samizdat_press.go index 7760886..79f0946 100644 --- a/internal/content/background/samizdat_press.go +++ b/inc/background.samizdat_press.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // samizdatPress — deck 21. -func samizdatPress() inkwell.Asset { +func backgroundSamizdatPress() inkwell.Asset { return inkwell.Asset{ - Name: names.BgSamizdatPress, + Name: BgSamizdatPress, Path: "assets/bg/samizdat_press.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/scrap_market.go b/inc/background.scrap_market.go similarity index 56% rename from internal/content/background/scrap_market.go rename to inc/background.scrap_market.go index 8614c73..7fa5092 100644 --- a/internal/content/background/scrap_market.go +++ b/inc/background.scrap_market.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // scrapMarket — deck 20. -func scrapMarket() inkwell.Asset { +func backgroundScrapMarket() inkwell.Asset { return inkwell.Asset{ - Name: names.BgScrapMarket, + Name: BgScrapMarket, Path: "assets/bg/scrap_market.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/secret_club.go b/inc/background.secret_club.go similarity index 56% rename from internal/content/background/secret_club.go rename to inc/background.secret_club.go index 865f6ef..de951e4 100644 --- a/internal/content/background/secret_club.go +++ b/inc/background.secret_club.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // secretClub — deck 10. -func secretClub() inkwell.Asset { +func backgroundSecretClub() inkwell.Asset { return inkwell.Asset{ - Name: names.BgSecretClub, + Name: BgSecretClub, Path: "assets/bg/secret_club.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/secret_lab.go b/inc/background.secret_lab.go similarity index 56% rename from internal/content/background/secret_lab.go rename to inc/background.secret_lab.go index f4dc041..f67cb70 100644 --- a/internal/content/background/secret_lab.go +++ b/inc/background.secret_lab.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // secretLab — deck 15. -func secretLab() inkwell.Asset { +func backgroundSecretLab() inkwell.Asset { return inkwell.Asset{ - Name: names.BgSecretLab, + Name: BgSecretLab, Path: "assets/bg/secret_lab.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/selector.go b/inc/background.selector.go similarity index 73% rename from internal/content/background/selector.go rename to inc/background.selector.go index 46b1d11..c77cc51 100644 --- a/internal/content/background/selector.go +++ b/inc/background.selector.go @@ -1,18 +1,16 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // selector — the wiki's Helyszínválasztó — no deck number, and not drawn. // // Not painted yet: the file is missing on purpose, and inkwell draws its // placeholder instead — which is exactly the greybox convention. -func selector() inkwell.Asset { +func backgroundSelector() inkwell.Asset { return inkwell.Asset{ - Name: names.BgSelector, + Name: BgSelector, Path: "assets/bg/selector.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/server_farm.go b/inc/background.server_farm.go similarity index 56% rename from internal/content/background/server_farm.go rename to inc/background.server_farm.go index 113e3b3..e319d6f 100644 --- a/internal/content/background/server_farm.go +++ b/inc/background.server_farm.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // serverFarm — deck 14. -func serverFarm() inkwell.Asset { +func backgroundServerFarm() inkwell.Asset { return inkwell.Asset{ - Name: names.BgServerFarm, + Name: BgServerFarm, Path: "assets/bg/server_farm.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/showroom.go b/inc/background.showroom.go similarity index 56% rename from internal/content/background/showroom.go rename to inc/background.showroom.go index bf997b0..de7a241 100644 --- a/internal/content/background/showroom.go +++ b/inc/background.showroom.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // showroom — deck 22. -func showroom() inkwell.Asset { +func backgroundShowroom() inkwell.Asset { return inkwell.Asset{ - Name: names.BgShowroom, + Name: BgShowroom, Path: "assets/bg/showroom.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/small_restaurant.go b/inc/background.small_restaurant.go similarity index 56% rename from internal/content/background/small_restaurant.go rename to inc/background.small_restaurant.go index 2f7969e..b9925d8 100644 --- a/internal/content/background/small_restaurant.go +++ b/inc/background.small_restaurant.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // smallRestaurant — deck 09. -func smallRestaurant() inkwell.Asset { +func backgroundSmallRestaurant() inkwell.Asset { return inkwell.Asset{ - Name: names.BgSmallRestaurant, + Name: BgSmallRestaurant, Path: "assets/bg/small_restaurant.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/street.go b/inc/background.street.go similarity index 57% rename from internal/content/background/street.go rename to inc/background.street.go index eb4c45f..7c8b751 100644 --- a/internal/content/background/street.go +++ b/inc/background.street.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // street — deck 12. -func street() inkwell.Asset { +func backgroundStreet() inkwell.Asset { return inkwell.Asset{ - Name: names.BgStreet, + Name: BgStreet, Path: "assets/bg/street.png", Kind: inkwell.AssetImage, } diff --git a/internal/content/background/trinket_shop.go b/inc/background.trinket_shop.go similarity index 56% rename from internal/content/background/trinket_shop.go rename to inc/background.trinket_shop.go index 6a05786..cc2ccf9 100644 --- a/internal/content/background/trinket_shop.go +++ b/inc/background.trinket_shop.go @@ -1,15 +1,13 @@ -package background +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // trinketShop — deck 08. -func trinketShop() inkwell.Asset { +func backgroundTrinketShop() inkwell.Asset { return inkwell.Asset{ - Name: names.BgTrinketShop, + Name: BgTrinketShop, Path: "assets/bg/trinket_shop.png", Kind: inkwell.AssetImage, } diff --git a/inc/boot.manager.go b/inc/boot.manager.go new file mode 100644 index 0000000..3cc5156 --- /dev/null +++ b/inc/boot.manager.go @@ -0,0 +1,59 @@ +// Package inc is the whole game, in one flat package. +// +// Every file is named [category].[name].go: one category per concern, one +// entity per file. A category's .manager.go holds what ties that +// category together — its Register, its shared types — and the files beside it +// hold one screen, one background, one item each. +// +// This file is the boot category: it wires the layers together, and it is the +// only place where the world, the content, the theme and the HUD meet. main.go +// knows nothing else. +package inc + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// Opts are the run parameters. +type Opts struct { + Screen string // starting screen; empty means the alley + Finale bool // start with the finale, to try the Nokia-punk theme switch +} + +// New builds a ready-to-run game. +func New(o Opts) *inkwell.Game { + start := o.Screen + if start == "" { + start = ScreenAlley + } + + g := inkwell.NewGame("Real World", ScreenW, ScreenH) + g.MaxLogLines = 64 + + w := newWorld(g) + registerTheme(g) + registerContent(w) + + g.UseTheme(RealWorld) + registerUI(w) + + g.StartAt(start) + g.OnStart(inkwell.Seq(bootOpening(w, start, o.Finale)...)) + return g +} + +func bootOpening(w *World, start string, finale bool) []inkwell.Action { + acts := []inkwell.Action{ + EnterScene(w, start), + // Beat 2 (the police station) will set the tip flag. Until that beat + // is written we set it here so the alley slice is playable end to end — + // the conditional branch itself is real code, not bypassed. + inkwell.SetFlag(FlagPoliceTip), + inkwell.Say(Paul, "Back alley. Just like the clerk said."), + TapeSay(Dex, "A government office tipped you off to remove something from a scene. That doesn't strike you as odd?"), + } + if finale { + acts = append(acts, inkwell.RunScript(ScriptFinale)) + } + return acts +} diff --git a/internal/boot/boot_test.go b/inc/boot.manager_test.go similarity index 79% rename from internal/boot/boot_test.go rename to inc/boot.manager_test.go index ac0b814..d836c44 100644 --- a/internal/boot/boot_test.go +++ b/inc/boot.manager_test.go @@ -1,4 +1,4 @@ -package boot_test +package inc import ( "image/png" @@ -8,15 +8,11 @@ import ( "testing" inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/boot" - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/ui" ) -func build(t *testing.T) *inkwell.Game { +func newTestGame(t *testing.T) *inkwell.Game { t.Helper() - g := boot.New(boot.Opts{}) + g := New(Opts{}) if err := g.Validate(); err != nil { t.Fatalf("Validate: %v", err) } @@ -27,38 +23,38 @@ func build(t *testing.T) *inkwell.Game { // a dialogue, script or item name would surface at runtime as a silent no-op // (RunDialogue and RunScript fail quietly), so check them here. func TestReferencesResolve(t *testing.T) { - g := build(t) + g := newTestGame(t) - for _, n := range []string{names.DlgDex, names.DlgMysteryTape} { + for _, n := range []string{DlgDex, DlgMysteryTape} { if !g.DialogueManager.Has(n) { t.Errorf("missing dialogue: %q", n) } } - for _, n := range []string{names.ScriptTapeInsert, names.ScriptFinale} { + for _, n := range []string{ScriptTapeInsert, ScriptFinale} { if !g.ScriptManager.Has(n) { t.Errorf("missing script: %q", n) } } - for _, n := range []string{names.ItemNoodleLetter, names.ItemMysteryTape, names.ItemBlackArmilla} { + for _, n := range []string{ItemNoodleLetter, ItemMysteryTape, ItemBlackArmilla} { if !g.ItemManager.Has(n) { t.Errorf("missing item: %q", n) } } - for n := range names.Tapes { + for n := range Tapes { if !g.CharacterManager.Has(n) { t.Errorf("tape character not registered: %q", n) } } // Every loadable tape needs an item, a character and a dialogue. - for item, char := range names.TapeItems { + for item, char := range TapeItems { if !g.ItemManager.Has(item) { t.Errorf("tape item not registered: %q", item) } if !g.CharacterManager.Has(char) { t.Errorf("tape character not registered: %q", char) } - if !g.DialogueManager.Has(names.TapeDialogue(item)) { - t.Errorf("tape %q has no dialogue: %q", item, names.TapeDialogue(item)) + if !g.DialogueManager.Has(TapeDialogue(item)) { + t.Errorf("tape %q has no dialogue: %q", item, TapeDialogue(item)) } } } @@ -67,10 +63,10 @@ func TestReferencesResolve(t *testing.T) { // the world's. Without that, "if something is amber, a tape said it" is not // readable. func TestTapeVoicesAreDistinct(t *testing.T) { - g := build(t) - paul, _ := g.CharacterManager.Get(names.Paul) + g := newTestGame(t) + paul, _ := g.CharacterManager.Get(Paul) seen := map[[4]uint32]string{} - for n := range names.Tapes { + for n := range Tapes { c, _ := g.CharacterManager.Get(n) if c.SpeechColor == nil { t.Errorf("%s: no SpeechColor", n) @@ -91,29 +87,29 @@ func TestTapeVoicesAreDistinct(t *testing.T) { // HUD geometry: the two channels must not overlap, and both must stay inside // the HUD strip. func TestHUDGeometry(t *testing.T) { - g := build(t) + g := newTestGame(t) ch, ok := g.UIManager.Get("channel") if !ok { t.Fatal("no 'channel' widget") } - tc := ch.(*ui.TapeChannel) - if tc.Bounds.X < ui.DividerX { - t.Errorf("tape channel crosses into the world side: X=%v < %v", tc.Bounds.X, ui.DividerX) + tc := ch.(*TapeChannel) + if tc.Bounds.X < DividerX { + t.Errorf("tape channel crosses into the world side: X=%v < %v", tc.Bounds.X, DividerX) } - if tc.Bounds.X+tc.Bounds.W > ui.ScreenW { + if tc.Bounds.X+tc.Bounds.W > ScreenW { t.Errorf("tape channel runs off screen: %v", tc.Bounds.X+tc.Bounds.W) } - if tc.Bounds.Y < ui.HUDTop { - t.Errorf("tape channel reaches into the scene: Y=%v < %v", tc.Bounds.Y, ui.HUDTop) + if tc.Bounds.Y < HUDTop { + t.Errorf("tape channel reaches into the scene: Y=%v < %v", tc.Bounds.Y, HUDTop) } sl, ok := g.UIManager.Get("tapes") if !ok { t.Fatal("no 'tapes' widget") } - if ts := sl.(*ui.TapeSlots); ts.Bounds.X+ts.Bounds.W > ui.DividerX { - t.Errorf("tape slots cross the divider: %v > %v", ts.Bounds.X+ts.Bounds.W, ui.DividerX) + if ts := sl.(*TapeSlots); ts.Bounds.X+ts.Bounds.W > DividerX { + t.Errorf("tape slots cross the divider: %v > %v", ts.Bounds.X+ts.Bounds.W, DividerX) } // The dialogue box deliberately covers only the left region, so a tape can @@ -122,16 +118,16 @@ func TestHUDGeometry(t *testing.T) { if !ok { t.Fatal("no 'dialog' widget") } - if box := db.(*inkwell.DialogBox); box.Bounds.X+box.Bounds.W > ui.DividerX { + if box := db.(*inkwell.DialogBox); box.Bounds.X+box.Bounds.W > DividerX { t.Errorf("dialogue box would cover the tape channel: %v > %v", - box.Bounds.X+box.Bounds.W, ui.DividerX) + box.Bounds.X+box.Bounds.W, DividerX) } } // The guard must be registered FIRST so it ticks LAST among the widgets — // otherwise it would swallow HUD clicks. func TestWidgetOrder(t *testing.T) { - g := build(t) + g := newTestGame(t) n := g.UIManager.Names() if len(n) == 0 || n[0] != "usewith_guard" { t.Errorf("usewith_guard is not registered first: %v", n) @@ -152,12 +148,12 @@ func TestWidgetOrder(t *testing.T) { // painting that is not ScreenW×ScreenH is silently squashed to fit. That is why // the screen is 640×380 in the first place; this keeps the two in step. func TestSceneBackgroundsResolve(t *testing.T) { - g := build(t) + g := newTestGame(t) // Not drawn yet: these render as placeholders on purpose. The alley is // missing from the concept-art deck; the selector is the wiki's menu // screen, which the deck does not cover at all. - undrawn := map[string]bool{names.BgAlley: true, names.BgSelector: true} + undrawn := map[string]bool{BgAlley: true, BgSelector: true} for _, n := range g.SceneManager.Names() { s := g.SceneManager.MustGet(n) @@ -169,7 +165,7 @@ func TestSceneBackgroundsResolve(t *testing.T) { if undrawn[a.Name] { continue } - f, err := os.Open(filepath.Join("..", "..", a.Path)) + f, err := os.Open(filepath.Join("..", a.Path)) if err != nil { t.Errorf("scene %q: background file missing: %v", n, err) continue @@ -180,16 +176,16 @@ func TestSceneBackgroundsResolve(t *testing.T) { t.Errorf("scene %q: unreadable background: %v", n, err) continue } - if cfg.Width != ui.ScreenW || cfg.Height != ui.ScreenH { + if cfg.Width != ScreenW || cfg.Height != ScreenH { t.Errorf("scene %q: background is %d×%d, want %d×%d — it will be stretched", - n, cfg.Width, cfg.Height, ui.ScreenW, ui.ScreenH) + n, cfg.Width, cfg.Height, ScreenW, ScreenH) } } } // exits reads the connection graph back out of the registered screens. The // exit hotspots are named "exit:", which is what keeps the graph -// machine-readable — see content/screen/exit.go. +// machine-readable — see screen.exit.go. func exits(g *inkwell.Game, screen string) []string { var out []string for _, h := range g.SceneManager.MustGet(screen).Hotspots { @@ -203,7 +199,7 @@ func exits(g *inkwell.Game, screen string) []string { // An exit that names a screen nobody registered is a dead end the player walks // into: the engine logs "changeScene: unknown" and nothing happens on screen. func TestExitsResolve(t *testing.T) { - g := build(t) + g := newTestGame(t) for _, n := range g.SceneManager.Names() { for _, to := range exits(g, n) { if !g.SceneManager.Has(to) { @@ -220,10 +216,10 @@ func TestExitsResolve(t *testing.T) { // The walk starts at Paul's shop, where the letter arrives, and mostly runs // through the selector: that is what the wiki's location graph is centred on. func TestEveryScreenIsReachable(t *testing.T) { - g := build(t) + g := newTestGame(t) - seen := map[string]bool{names.ScreenPaulShop: true} - queue := []string{names.ScreenPaulShop} + seen := map[string]bool{ScreenPaulShop: true} + queue := []string{ScreenPaulShop} for len(queue) > 0 { at := queue[0] queue = queue[1:] @@ -237,7 +233,7 @@ func TestEveryScreenIsReachable(t *testing.T) { for _, n := range g.SceneManager.Names() { if !seen[n] { - t.Errorf("screen %q cannot be reached from %q by its exits", n, names.ScreenPaulShop) + t.Errorf("screen %q cannot be reached from %q by its exits", n, ScreenPaulShop) } } } @@ -245,7 +241,7 @@ func TestEveryScreenIsReachable(t *testing.T) { // A screen with no way out at all is a trap: the player walks in and the only // way on is the debug walk. func TestEveryScreenHasAWayOut(t *testing.T) { - g := build(t) + g := newTestGame(t) for _, n := range g.SceneManager.Names() { var ways int for _, h := range g.SceneManager.MustGet(n).Hotspots { diff --git a/internal/content/character/dex.go b/inc/character.dex.go similarity index 52% rename from internal/content/character/dex.go rename to inc/character.dex.go index ed29ffd..1da5be9 100644 --- a/internal/content/character/dex.go +++ b/inc/character.dex.go @@ -1,17 +1,14 @@ -package character +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/theme" ) // dex is the personality imprint of a dead friend, permanently in slot 1 of // Paul's Armilla. Constant commentary, hint system, dialogue partner. -func dex() inkwell.Character { +func characterDex() inkwell.Character { return inkwell.Character{ - Name: names.Dex, - SpeechColor: theme.Amber, + Name: Dex, + SpeechColor: Amber, } } diff --git a/inc/character.manager.go b/inc/character.manager.go new file mode 100644 index 0000000..dd31b57 --- /dev/null +++ b/inc/character.manager.go @@ -0,0 +1,23 @@ +package inc + +// Characters — the game's cast, one file each. +// +// Tapes are characters too, not props — the wiki is explicit about it. They +// have no body in the scene, so they carry no W/H and never appear in +// Scene.Actors; their voice lives in the tape channel (see TapeSay). + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// registerCharacter adds every character to the game. +func registerCharacter(w *World) { + for _, c := range []inkwell.Character{ + characterPaul(), + characterDex(), + characterMysteryTape(), + characterSupportTape(), + } { + w.G.CharacterManager.Register(c) + } +} diff --git a/internal/content/character/mystery_tape.go b/inc/character.mystery_tape.go similarity index 60% rename from internal/content/character/mystery_tape.go rename to inc/character.mystery_tape.go index cdcf1b5..fc3a472 100644 --- a/internal/content/character/mystery_tape.go +++ b/inc/character.mystery_tape.go @@ -1,10 +1,7 @@ -package character +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/theme" ) // mysteryTape is the tape found in the alley — in truth Norman's Personal @@ -12,9 +9,9 @@ import ( // // Its voice is a colder, greyer amber than Dex's. That pays off in reverse: // it never once spoke in the same voice as the friend. -func mysteryTape() inkwell.Character { +func characterMysteryTape() inkwell.Character { return inkwell.Character{ - Name: names.TapeMystery, - SpeechColor: theme.RGB(0xB9A98A), + Name: TapeMystery, + SpeechColor: RGB(0xB9A98A), } } diff --git a/internal/content/character/paul.go b/inc/character.paul.go similarity index 66% rename from internal/content/character/paul.go rename to inc/character.paul.go index cb06b75..4ec7d8b 100644 --- a/internal/content/character/paul.go +++ b/inc/character.paul.go @@ -1,23 +1,20 @@ -package character +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/theme" ) // paul is the player character: a junk dealer and street survivor, technically // competent, living on the edge of the city. -func paul() inkwell.Character { +func characterPaul() inkwell.Character { return inkwell.Character{ - Name: names.Paul, + Name: Paul, Speed: 96, W: 28, H: 68, Start: inkwell.Point{X: 120, Y: 232}, // Bone white: the world and Paul. If something is amber, a tape said // it — see the colour rule in README. - SpeechColor: theme.Ink, + SpeechColor: Ink, } } diff --git a/inc/character.support_tape.go b/inc/character.support_tape.go new file mode 100644 index 0000000..730eb43 --- /dev/null +++ b/inc/character.support_tape.go @@ -0,0 +1,14 @@ +package inc + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// supportTape answers everything with basic information, insufferably. Dex +// hates it. Acquired in Chinatown (beat 5) — not reachable yet. +func characterSupportTape() inkwell.Character { + return inkwell.Character{ + Name: TapeSupport, + SpeechColor: RGB(0xE8C86A), + } +} diff --git a/inc/content.manager.go b/inc/content.manager.go new file mode 100644 index 0000000..e1b3335 --- /dev/null +++ b/inc/content.manager.go @@ -0,0 +1,19 @@ +package inc + +// Content — the composition root for everything the game declares: +// assets, characters, items, dialogues, scripts and scenes. +// +// Each registered entity lives in its own file, named after its category: +// adding a screen means adding screen..go and background..go, plus +// one line in each category's manager list — nothing else moves. + +// registerContent adds every entity to the game. Order matters only in that +// screens reference backgrounds and characters, which Game.Validate cross-checks. +func registerContent(w *World) { + registerBackground(w) + registerCharacter(w) + registerItem(w) + registerDialog(w) + registerScript(w) + registerScreen(w) +} diff --git a/internal/content/dialog/dex_talk.go b/inc/dialog.dex_talk.go similarity index 52% rename from internal/content/dialog/dex_talk.go rename to inc/dialog.dex_talk.go index d6d9f9f..c67c381 100644 --- a/internal/content/dialog/dex_talk.go +++ b/inc/dialog.dex_talk.go @@ -1,41 +1,39 @@ -package dialog +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // dexTalk is the standing conversation with Dex. Its branches are gated on // story flags, so the same entry point stays useful as the game progresses. -func dexTalk() inkwell.Dialogue { +func dialogDexTalk() inkwell.Dialogue { return inkwell.Dialogue{ - Name: names.DlgDex, + Name: DlgDex, Start: "root", Nodes: []inkwell.DialogueNode{{ Name: "root", - Lines: []inkwell.DialogueLine{{Speaker: names.Dex, Text: "Talk."}}, + Lines: []inkwell.DialogueLine{{Speaker: Dex, Text: "Talk."}}, Choices: []inkwell.DialogueChoice{ { Text: "What am I doing here, Dex?", Actions: []inkwell.Action{ - inkwell.Say(names.Dex, "You got a letter from a man you hadn't seen in twenty years. And you came. That says more about you than it does about him."), + inkwell.Say(Dex, "You got a letter from a man you hadn't seen in twenty years. And you came. That says more about you than it does about him."), inkwell.GotoNode("root"), }, }, { Text: "What do you know about Noodle?", - Show: inkwell.Not(inkwell.Flag(names.FlagHasTape)), + Show: inkwell.Not(inkwell.Flag(FlagHasTape)), Actions: []inkwell.Action{ - inkwell.Say(names.Dex, "He traded cracked Armillas. That isn't charity, Paul, that's a business. The kind they take you in for."), + inkwell.Say(Dex, "He traded cracked Armillas. That isn't charity, Paul, that's a business. The kind they take you in for."), inkwell.GotoNode("root"), }, }, { Text: "What do you make of this tape?", - Show: inkwell.Flag(names.FlagHasTape), + Show: inkwell.Flag(FlagHasTape), Actions: []inkwell.Action{ - inkwell.Say(names.Dex, "I make of it that you found a password-locked tape in a gap between two bins, on a tip from a government office. Count how many times that has ended well."), + inkwell.Say(Dex, "I make of it that you found a password-locked tape in a gap between two bins, on a tip from a government office. Count how many times that has ended well."), inkwell.GotoNode("root"), }, }, @@ -43,7 +41,7 @@ func dexTalk() inkwell.Dialogue { Text: "I miss you.", Once: true, Actions: []inkwell.Action{ - inkwell.Say(names.Dex, "I'm four kilobytes of a dead man. Don't do this to yourself."), + inkwell.Say(Dex, "I'm four kilobytes of a dead man. Don't do this to yourself."), inkwell.GotoNode("root"), }, }, diff --git a/internal/content/dialog/dialog.go b/inc/dialog.manager.go similarity index 69% rename from internal/content/dialog/dialog.go rename to inc/dialog.manager.go index 3c6a7e7..e8404e9 100644 --- a/internal/content/dialog/dialog.go +++ b/inc/dialog.manager.go @@ -1,4 +1,6 @@ -// Package dialog registers the game's dialogue trees. +package inc + +// Dialogues — the game's dialogue trees, one file each. // // Talking to a tape carries the same weight as talking to a human NPC — the // wiki states this explicitly. Tapes are dialogue partners, not menus. @@ -7,19 +9,16 @@ // deck wanted it struck through but still visible, so the list becomes a // memory of what you already tried. That is still open — see README, "Known // gaps". -package dialog import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/world" ) -// Register adds every dialogue to the game. -func Register(w *world.World) { +// registerDialog adds every dialogue to the game. +func registerDialog(w *World) { for _, d := range []inkwell.Dialogue{ - dexTalk(), - mysteryTapeSilent(), + dialogDexTalk(), + dialogMysteryTapeSilent(), } { w.G.DialogueManager.Register(d) } diff --git a/internal/content/dialog/mystery_tape_silent.go b/inc/dialog.mystery_tape_silent.go similarity index 64% rename from internal/content/dialog/mystery_tape_silent.go rename to inc/dialog.mystery_tape_silent.go index 02de135..8882a17 100644 --- a/internal/content/dialog/mystery_tape_silent.go +++ b/inc/dialog.mystery_tape_silent.go @@ -1,26 +1,24 @@ -package dialog +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // mysteryTapeSilent covers the stretch before the club trigger (beat 6), while // the tape is still mute. Talking to it is really Dex talking about it — the // tape itself says nothing. -func mysteryTapeSilent() inkwell.Dialogue { +func dialogMysteryTapeSilent() inkwell.Dialogue { return inkwell.Dialogue{ - Name: names.DlgMysteryTape, + Name: DlgMysteryTape, Start: "root", Nodes: []inkwell.DialogueNode{{ Name: "root", - Lines: []inkwell.DialogueLine{{Speaker: names.Dex, Text: "Nothing. Warm, spinning, and silent."}}, + Lines: []inkwell.DialogueLine{{Speaker: Dex, Text: "Nothing. Warm, spinning, and silent."}}, Choices: []inkwell.DialogueChoice{ { Text: "Are you sure it works?", Actions: []inkwell.Action{ - inkwell.Say(names.Dex, "It works. It just isn't talking to you. That is not the same as silence."), + inkwell.Say(Dex, "It works. It just isn't talking to you. That is not the same as silence."), inkwell.GotoNode("root"), }, }, diff --git a/inc/item.black_market_armilla.go b/inc/item.black_market_armilla.go new file mode 100644 index 0000000..8684764 --- /dev/null +++ b/inc/item.black_market_armilla.go @@ -0,0 +1,26 @@ +package inc + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// blackMarketArmilla is flavour, not a quest trigger: the gap between +// legitimised technology and street reality. +func itemBlackMarketArmilla() inkwell.Item { + return inkwell.Item{ + Name: ItemBlackArmilla, + Description: "black-market Armilla", + OnUseSelf: inkwell.Seq( + inkwell.Say(Paul, "Jailbroken. Pushes ads, but it was cheap."), + TapeSay(Dex, "Two slots, and both of them lie about the temperature. Don't trade me in for it."), + ), + OnUseWith: map[string]inkwell.Action{ + // An AUTHORED failure. Nothing is consumed, the character reacts, + // the tape remarks. Never a generic error line. + ItemNoodleLetter: inkwell.Seq( + inkwell.Say(Paul, "A letter and a bracelet. Brilliant."), + TapeSay(Dex, "Nothing. Which is what I'd charge for it."), + ), + }, + } +} diff --git a/internal/content/item/item.go b/inc/item.manager.go similarity index 53% rename from internal/content/item/item.go rename to inc/item.manager.go index f45c792..bd9cbad 100644 --- a/internal/content/item/item.go +++ b/inc/item.manager.go @@ -1,21 +1,20 @@ -// Package item registers the game's inventory items. +package inc + +// Items — the game's inventory, one file each. // // Personal Tapes are inventory items and quest triggers at the same time: they // go into slot 2 of Paul's Armilla, and loading one activates its effect. -package item import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/world" ) -// Register adds every item to the game. -func Register(w *world.World) { +// registerItem adds every item to the game. +func registerItem(w *World) { for _, i := range []inkwell.Item{ - noodleLetter(), - blackMarketArmilla(), - mysteryTape(), + itemNoodleLetter(), + itemBlackMarketArmilla(), + itemMysteryTape(), } { w.G.ItemManager.Register(i) } diff --git a/inc/item.mystery_tape.go b/inc/item.mystery_tape.go new file mode 100644 index 0000000..7a69e8f --- /dev/null +++ b/inc/item.mystery_tape.go @@ -0,0 +1,18 @@ +package inc + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// mysteryTape is the engine of the investigation. Password-locked; it only +// starts speaking after the club trigger (beat 6). +func itemMysteryTape() inkwell.Item { + return inkwell.Item{ + Name: ItemMysteryTape, + Description: "unmarked Personal Tape", + OnUseSelf: inkwell.Seq( + inkwell.Say(Paul, "Password-locked. Of course."), + TapeSay(Dex, "Put it in the second slot if you care that much. I did warn you."), + ), + } +} diff --git a/inc/item.noodle_letter.go b/inc/item.noodle_letter.go new file mode 100644 index 0000000..84e6695 --- /dev/null +++ b/inc/item.noodle_letter.go @@ -0,0 +1,17 @@ +package inc + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// noodleLetter starts the story: it is what brings Paul to the city. +func itemNoodleLetter() inkwell.Item { + return inkwell.Item{ + Name: ItemNoodleLetter, + Description: "Noodle's letter", + OnUseSelf: inkwell.Seq( + inkwell.Say(Paul, "“Come out, Paul. Not a phone conversation.” That's all it says."), + TapeSay(Dex, "And now you're here. And he isn't."), + ), + } +} diff --git a/internal/names/names.go b/inc/names.manager.go similarity index 96% rename from internal/names/names.go rename to inc/names.manager.go index 736b849..ff1c850 100644 --- a/internal/names/names.go +++ b/inc/names.manager.go @@ -1,4 +1,6 @@ -// Package names is the single source of entity names and world-state keys. +package inc + +// Names — the single source of entity names and world-state keys. // // The names follow the wiki entity catalogue // (services/wiki-pages/pages/projects/realworld/entities) so that content and @@ -7,9 +9,8 @@ // tape_behelyezes, sikator); the constants below are their English // equivalents — the mapping is one-to-one, in catalogue order. // -// This package imports nothing from the project, so every layer can depend on -// it without creating a cycle. -package names +// These constants are the vocabulary every other category speaks in; nothing +// here depends on anything else in the game. // Characters. const ( diff --git a/inc/screen.alley.go b/inc/screen.alley.go new file mode 100644 index 0000000..e77afba --- /dev/null +++ b/inc/screen.alley.go @@ -0,0 +1,104 @@ +package inc + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// alley is the alley behind Noodle's house — beat 3, and the vertical slice the +// whole HUD is measured against: hotspots, a gated pickup, the two-slot +// Armilla, tape dialogue and an authored failure. +// +// The wiki keeps it in the same catalogue row as the house ("Noodle háza & a +// sikátor") and its graph has one arrow in, NoodleUtcafront → Sikátor, so the +// only way out is back to the street. The alley's own locked back door is the +// New Game+ hook and leads to a room the deck has not painted. +func screenAlley() screen { + return screen{ + name: ScreenAlley, + title: "ALLEY — BEHIND NOODLE'S HOUSE", + background: BgAlley, + exits: []exit{ + // On the left: the back door hotspot below owns the other end. + {to: ScreenNoodleHouse, label: "back out to the street", side: sideLeft}, + }, + actors: []inkwell.SceneActor{ + {CharacterName: Paul, At: inkwell.Point{X: 120, Y: 232}}, + }, + onEnter: setVarIfEmpty(VarArmillaStrip, "SRP: 1 tape"), + hotspots: []inkwell.Hotspot{hidingPlace(), backDoor(), bin(), fireEscape()}, + } +} + +// hidingPlace holds the mystery tape. It only opens once Paul has the tip from +// the police station (beat 2) — without it he does not know what to look for. +func hidingPlace() inkwell.Hotspot { + return inkwell.Hotspot{ + Name: "hiding_place", + Label: "gap at the foot of the wall", + Area: inkwell.Rect(416, 150, 68, 52), + OnLook: inkwell.If(inkwell.Flag(FlagHasTape), + inkwell.Say(Paul, "Empty. Whatever was in there is on me now."), + inkwell.Say(Paul, "Loose brick. There's a gap behind it."), + ), + OnUse: inkwell.If(inkwell.Flag(FlagPoliceTip), + inkwell.If(inkwell.Flag(FlagHasTape), + inkwell.Say(Paul, "There's nothing else in there."), + inkwell.Seq( + inkwell.Say(Paul, "“Behind the brick.” All right then."), + inkwell.Give(ItemMysteryTape), + inkwell.SetFlag(FlagHasTape), + inkwell.Say(Paul, "A Personal Tape. No label, no seal."), + TapeSay(Dex, "Password-locked. And somebody very much did not want it found."), + ), + ), + inkwell.Say(Paul, "One loose brick. I'm not taking the alley apart over it."), + ), + OnTake: inkwell.Say(Paul, "I'm not carrying a wall."), + } +} + +// backDoor is the New Game+ hook: the code can be entered on the first visit +// too, if the player already knows it. +func backDoor() inkwell.Hotspot { + return inkwell.Hotspot{ + Name: "back_door", + Label: "locked back door", + Area: inkwell.Rect(72, 106, 80, 124), + OnLook: inkwell.Say(Paul, "Keypad. Four digits, worn keys."), + OnUse: inkwell.Seq( + inkwell.Say(Paul, "Locked. And I'm not guessing four digits."), + TapeSay(Dex, "The wear is heaviest on the two and the seven. That isn't a code. It's fewer options."), + ), + OnTalk: inkwell.Say(Paul, "To the door? I'm not there yet."), + OnUseWith: map[string]inkwell.Action{ + // An AUTHORED failure — not the engine's hardcoded flash. + ItemBlackArmilla: inkwell.Seq( + inkwell.Say(Paul, "A black-market bracelet doesn't open a keypad."), + TapeSay(Dex, "But you do get an advert out of it."), + ), + }, + } +} + +func bin() inkwell.Hotspot { + return inkwell.Hotspot{ + Name: "bin", + Label: "toppled bin", + Area: inkwell.Rect(240, 170, 88, 60), + OnLook: inkwell.Say(Paul, "Somebody's been through it. Thoroughly."), + OnUse: inkwell.Seq( + inkwell.Say(Paul, "Already turned out. I'm not going to be the second one."), + TapeSay(Dex, "The police don't go through bins. So who did?"), + ), + } +} + +func fireEscape() inkwell.Hotspot { + return inkwell.Hotspot{ + Name: "fire_escape", + Label: "fire escape", + Area: inkwell.Rect(528, 44, 88, 160), + OnLook: inkwell.Say(Paul, "Runs all the way to the roof. Bottom rung is two metres over my head."), + OnUse: inkwell.Say(Paul, "Can't reach it. I'd need something to stand on."), + } +} diff --git a/internal/content/screen/bbs_terminal.go b/inc/screen.bbs_terminal.go similarity index 75% rename from internal/content/screen/bbs_terminal.go rename to inc/screen.bbs_terminal.go index a21272c..8b0f7c5 100644 --- a/internal/content/screen/bbs_terminal.go +++ b/inc/screen.bbs_terminal.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // bbsTerminal is the screen you get when Paul sits down at a terminal: the // catalogue calls it a "Tárgy/portál", a physical object in the world that @@ -9,11 +7,11 @@ import "git.teletypegames.org/games/realworld/internal/names" // It is the same screen wherever the terminal stands — the club's ECHO box, // Norman's machine at the flat, the old one on the roof — which is why its way // out is "back", and not a place. -func bbsTerminal() screen { +func screenBbsTerminal() screen { return screen{ - name: names.ScreenBBSTerminal, + name: ScreenBBSTerminal, title: "TERMINAL — BBS ACCESS POINT", - background: names.BgBBSTerminal, + background: BgBBSTerminal, exits: []exit{ {label: "step back from the terminal", side: sideNear}, }, diff --git a/internal/content/screen/bvk_branch.go b/inc/screen.bvk_branch.go similarity index 67% rename from internal/content/screen/bvk_branch.go rename to inc/screen.bvk_branch.go index b13b84f..8165a4c 100644 --- a/internal/content/screen/bvk_branch.go +++ b/inc/screen.bvk_branch.go @@ -1,16 +1,14 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // bvkBranch is a 💡 suggestion: the local certification office, imprimatur // stamps and licence checks and numbered waiting rooms — everything a man with // a jailbroken two-slot Armilla should stay away from // (wiki: entities#potenciális-további-helyszínek). -func bvkBranch() screen { +func screenBvkBranch() screen { return screen{ - name: names.ScreenBVKBranch, + name: ScreenBVKBranch, title: "BVK — SAN FRANCISCO BRANCH", - background: names.BgBVKBranch, + background: BgBVKBranch, onSelector: true, } } diff --git a/internal/content/screen/columbarium.go b/inc/screen.columbarium.go similarity index 65% rename from internal/content/screen/columbarium.go rename to inc/screen.columbarium.go index 66d6639..6d0631f 100644 --- a/internal/content/screen/columbarium.go +++ b/inc/screen.columbarium.go @@ -1,16 +1,14 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // columbarium is a 💡 suggestion: where Dex is buried. A quiet place, away // from the noise of the terminals — the loss-and-letting-go thread, which is // the same thread as Norman and The AI // (wiki: entities#potenciális-további-helyszínek). -func columbarium() screen { +func screenColumbarium() screen { return screen{ - name: names.ScreenColumbarium, + name: ScreenColumbarium, title: "COLUMBARIUM — DEX'S MEMORIAL", - background: names.BgColumbarium, + background: BgColumbarium, onSelector: true, } } diff --git a/internal/content/screen/curator_shop.go b/inc/screen.curator_shop.go similarity index 66% rename from internal/content/screen/curator_shop.go rename to inc/screen.curator_shop.go index ed089ab..37d9beb 100644 --- a/internal/content/screen/curator_shop.go +++ b/inc/screen.curator_shop.go @@ -1,15 +1,13 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // curatorShop is a 💡 suggestion: the last workshop of the declining curators' // guild — quartz tuning, tape repair, craft handed down for generations. The // man who built Paul's second slot (wiki: entities#potenciális-további-helyszínek). -func curatorShop() screen { +func screenCuratorShop() screen { return screen{ - name: names.ScreenCuratorShop, + name: ScreenCuratorShop, title: "CURATOR SERVICE — THE MASTER'S WORKSHOP", - background: names.BgCuratorShop, + background: BgCuratorShop, onSelector: true, } } diff --git a/internal/content/screen/exit.go b/inc/screen.exit.go similarity index 87% rename from internal/content/screen/exit.go rename to inc/screen.exit.go index 6810648..6f9df7d 100644 --- a/internal/content/screen/exit.go +++ b/inc/screen.exit.go @@ -1,4 +1,4 @@ -package screen +package inc // Exits — the connections between screens. // @@ -25,9 +25,6 @@ package screen import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" ) type side int @@ -73,7 +70,7 @@ type exit struct { // toSelector is the way back out to the rest of the city, on every screen the // wiki's Helyszínválasztó lists. func toSelector() exit { - return exit{to: names.ScreenSelector, label: "the rest of the city", side: sideNear} + return exit{to: ScreenSelector, label: "the rest of the city", side: sideNear} } // exitName is how a connection stays machine-readable: the graph can be read @@ -85,13 +82,13 @@ func exitName(to string) string { return "exit:" + to } -func (e exit) hotspot(w *world.World) inkwell.Hotspot { +func (e exit) hotspot(w *World) inkwell.Hotspot { var travel inkwell.Action = inkwell.GoTo(e.to) if e.to == "" { - travel = world.Back(w) + travel = Back(w) } if e.needs != "" { - travel = inkwell.If(inkwell.Flag(e.needs), travel, inkwell.Say(names.Paul, e.blocked)) + travel = inkwell.If(inkwell.Flag(e.needs), travel, inkwell.Say(Paul, e.blocked)) } area := e.area if area == nil { @@ -102,8 +99,8 @@ func (e exit) hotspot(w *world.World) inkwell.Hotspot { Label: e.label, Area: area, Cursor: inkwell.CursorExit, - OnLook: inkwell.Say(names.Paul, "That way: "+e.label+"."), + OnLook: inkwell.Say(Paul, "That way: "+e.label+"."), OnUse: travel, - OnTake: inkwell.Say(names.Paul, "It's a way out, not a thing."), + OnTake: inkwell.Say(Paul, "It's a way out, not a thing."), } } diff --git a/internal/content/screen/hackerspace.go b/inc/screen.hackerspace.go similarity index 69% rename from internal/content/screen/hackerspace.go rename to inc/screen.hackerspace.go index 9541d0a..1d9791a 100644 --- a/internal/content/screen/hackerspace.go +++ b/inc/screen.hackerspace.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // hackerspace: the community workshop at the edge of the Mission, printers and // stripped servers and paranoid amateur cryptographers. Where the server-farm @@ -8,11 +6,11 @@ import "git.teletypegames.org/games/realworld/internal/names" // // The wiki's graph gives it no physical neighbour, so it hangs off the // selector like every other main location. -func hackerspace() screen { +func screenHackerspace() screen { return screen{ - name: names.ScreenHackerspace, + name: ScreenHackerspace, title: "HACKERSPACE", - background: names.BgHackerspace, + background: BgHackerspace, onSelector: true, } } diff --git a/internal/content/screen/hospital.go b/inc/screen.hospital.go similarity index 58% rename from internal/content/screen/hospital.go rename to inc/screen.hospital.go index 45de302..ed6980e 100644 --- a/internal/content/screen/hospital.go +++ b/inc/screen.hospital.go @@ -1,14 +1,12 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // hospital is the clinical dead end: Dr. Maren looked for a medical reason for // Norman's state and did not find one (wiki: entities, timeline 21). -func hospital() screen { +func screenHospital() screen { return screen{ - name: names.ScreenHospital, + name: ScreenHospital, title: "HOSPITAL — PSYCHIATRIC WING", - background: names.BgHospital, + background: BgHospital, onSelector: true, } } diff --git a/internal/content/screen/ice_cream_shop.go b/inc/screen.ice_cream_shop.go similarity index 73% rename from internal/content/screen/ice_cream_shop.go rename to inc/screen.ice_cream_shop.go index 4169517..df87219 100644 --- a/internal/content/screen/ice_cream_shop.go +++ b/inc/screen.ice_cream_shop.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // iceCreamShop stands where Paul's old bar used to be, and they insist there // never was a bar. The BVK-registered terminal in the corner is the one the @@ -9,11 +7,11 @@ import "git.teletypegames.org/games/realworld/internal/names" // The search he eventually allows is what names the club — a lead, not a door: // the way there is the trinket shop's address, so this screen has no exit of // its own. -func iceCreamShop() screen { +func screenIceCreamShop() screen { return screen{ - name: names.ScreenIceCreamShop, + name: ScreenIceCreamShop, title: "ICE CREAM SHOP — WHERE THE BAR WAS", - background: names.BgIceCreamShop, + background: BgIceCreamShop, onSelector: true, } } diff --git a/internal/content/screen/screen.go b/inc/screen.manager.go similarity index 63% rename from internal/content/screen/screen.go rename to inc/screen.manager.go index edbbbb8..8d79e3c 100644 --- a/internal/content/screen/screen.go +++ b/inc/screen.manager.go @@ -1,4 +1,6 @@ -// Package screen registers the game's screens — one file per screen. +package inc + +// Screens — one file per screen. // // "Screen" is this project's word: the wiki's catalogue, the concept-art deck // and the beat tables all count screens. inkwell's word for the same thing is @@ -7,16 +9,12 @@ // engine. // // A file here holds everything that is true of one screen: its picture, the -// name on the top bar, where you can get to from it (exit.go), and — once its +// name on the top bar, where you can get to from it (screen.exit.go), and — once its // beat is written — its hotspots, NPCs and dialogue. Only the alley has that // last part so far; the rest are greybox. -package screen import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" ) // screen is what each file in this package fills in. @@ -25,7 +23,7 @@ type screen struct { title string background string - // exits: where you can get to from here. See exit.go. + // exits: where you can get to from here. See screen.exit.go. exits []exit // onSelector marks a main location — one of those the wiki's // Helyszínválasztó lists (story#helyszín-gráf-vázlat). It gets a way back @@ -40,68 +38,68 @@ type screen struct { onEnter inkwell.Action } -// deck is every screen in the game, in the order of the wiki's location +// screenDeck is every screen in the game, in the order of the wiki's location // catalogue (entities#helyszinek) — which the concept-art deck follows number // for number. Registration order is this order, and it is also the order the -// arrow keys walk (ui/screen_nav.go). +// arrow keys walk (ui.screen_nav.go). // // Deck 05, Norman's workplace, is in the catalogue but has not been drawn, so // it has no file yet. The alley has no deck number: the catalogue keeps it in // the same row as Noodle's house, and that is where it sits here too. -var deck = []func() screen{ - paulShop, // 01 - noodleHouse, // 02 - alley, // 02, same catalogue row - normanApartment, // 03 - policeStation, // 04 - hackerspace, // 06 - iceCreamShop, // 07 - trinketShop, // 08 - smallRestaurant, // 09 - secretClub, // 10 - bbsTerminal, // 11 - street, // 12 - hospital, // 13 - serverFarm, // 14 - secretLab, // 15 - rooftopHideout, // 16 - publicBBS, // 17 - bvkBranch, // 18 - curatorShop, // 19 - scrapMarket, // 20 - samizdatPress, // 21 - showroom, // 22 - columbarium, // 23 +var screenDeck = []func() screen{ + screenPaulShop, // 01 + screenNoodleHouse, // 02 + screenAlley, // 02, same catalogue row + screenNormanApartment, // 03 + screenPoliceStation, // 04 + screenHackerspace, // 06 + screenIceCreamShop, // 07 + screenTrinketShop, // 08 + screenSmallRestaurant, // 09 + screenSecretClub, // 10 + screenBbsTerminal, // 11 + screenStreet, // 12 + screenHospital, // 13 + screenServerFarm, // 14 + screenSecretLab, // 15 + screenRooftopHideout, // 16 + screenPublicBBS, // 17 + screenBvkBranch, // 18 + screenCuratorShop, // 19 + screenScrapMarket, // 20 + screenSamizdatPress, // 21 + screenShowroom, // 22 + screenColumbarium, // 23 } -// Register adds every screen to the game, the selector first: it is the centre -// of the wiki's location graph, and it is built from the deck rather than +// registerScreen adds every screen to the game, the selector first: it is the centre +// of the wiki's location graph, and it is built from the screenDeck rather than // listed in it. -func Register(w *world.World) { - screens := make([]screen, 0, len(deck)+1) - for _, f := range deck { +func registerScreen(w *World) { + screens := make([]screen, 0, len(screenDeck)+1) + for _, f := range screenDeck { screens = append(screens, f()) } - for _, s := range append([]screen{selector(screens)}, screens...) { - w.G.SceneManager.Register(build(w, s)) + for _, s := range append([]screen{screenSelector(screens)}, screens...) { + w.G.SceneManager.Register(screenBuild(w, s)) } } // The default floor: the bottom of the scene region, above the HUD divider. // A screen with a painted floor of its own overrides it. -var floor = []inkwell.Polygon{ +var screenFloor = []inkwell.Polygon{ inkwell.Poly( inkwell.Point{X: 16, Y: 196}, inkwell.Point{X: 624, Y: 196}, inkwell.Point{X: 624, Y: 258}, inkwell.Point{X: 16, Y: 258}, ), } -// build turns a screen into the engine's scene. +// screenBuild turns a screen into the engine's scene. // // The authored hotspots go in front of the exits, because the engine takes the // first one whose area contains the click (HotspotAt): a painted thing beats // the edge strip an exit sits on, wherever the two overlap. -func build(w *world.World, s screen) inkwell.Scene { +func screenBuild(w *World, s screen) inkwell.Scene { exits := s.exits if s.onSelector { exits = append(exits, toSelector()) @@ -115,14 +113,14 @@ func build(w *world.World, s screen) inkwell.Scene { actors := s.actors if actors == nil { actors = []inkwell.SceneActor{ - {CharacterName: names.Paul, At: inkwell.Point{X: 320, Y: 232}}, + {CharacterName: Paul, At: inkwell.Point{X: 320, Y: 232}}, } } walkboxes := s.walkboxes if walkboxes == nil { - walkboxes = floor + walkboxes = screenFloor } - onEnter := inkwell.Action(world.EnterScene(w, s.name)) + onEnter := inkwell.Action(EnterScene(w, s.name)) if s.onEnter != nil { onEnter = inkwell.Seq(onEnter, s.onEnter) } @@ -141,7 +139,7 @@ func build(w *world.World, s screen) inkwell.Scene { // setVarIfEmpty only writes when the variable is still unset, so re-entering a // screen does not clobber what the game has set in the meantime. func setVarIfEmpty(name string, v any) inkwell.Action { - return world.Fn(func(ctx *inkwell.Ctx) { + return Fn(func(ctx *inkwell.Ctx) { if ctx.Game.State.Var(name) == nil { ctx.Game.State.SetVar(name, v) } diff --git a/internal/content/screen/noodle_house.go b/inc/screen.noodle_house.go similarity index 63% rename from internal/content/screen/noodle_house.go rename to inc/screen.noodle_house.go index b8257cc..8bce985 100644 --- a/internal/content/screen/noodle_house.go +++ b/inc/screen.noodle_house.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // noodleHouse is the street front outside Noodle's place. By the time Paul // gets there the police have sealed the house and taken Noodle in, and a @@ -8,14 +6,14 @@ import "git.teletypegames.org/games/realworld/internal/names" // // The wiki's graph runs NoodleUtcafront → Sikátor: the alley behind the house // is reached from here, and from nowhere else. -func noodleHouse() screen { +func screenNoodleHouse() screen { return screen{ - name: names.ScreenNoodleHouse, + name: ScreenNoodleHouse, title: "NOODLE'S HOUSE — SEALED", - background: names.BgNoodleHouse, + background: BgNoodleHouse, onSelector: true, exits: []exit{ - {to: names.ScreenAlley, label: "the alley behind the house", side: sideRight}, + {to: ScreenAlley, label: "the alley behind the house", side: sideRight}, }, } } diff --git a/internal/content/screen/norman_apartment.go b/inc/screen.norman_apartment.go similarity index 59% rename from internal/content/screen/norman_apartment.go rename to inc/screen.norman_apartment.go index 0810096..45f8622 100644 --- a/internal/content/screen/norman_apartment.go +++ b/inc/screen.norman_apartment.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // normanApartment is the flat Norman was taken from: his Armilla left behind // on the side, his Personal Tape missing from it, his terminal full of the @@ -9,15 +7,15 @@ import "git.teletypegames.org/games/realworld/internal/names" // Two ways on from here. His terminal is one of the BBS access points the // catalogue lists ("a titkos klub ECHO-terminálja; később Norman lakása / // tetőterasz"), and the roof hideout is up his own stairs. -func normanApartment() screen { +func screenNormanApartment() screen { return screen{ - name: names.ScreenNormanApartment, + name: ScreenNormanApartment, title: "NORMAN'S APARTMENT", - background: names.BgNormanApartment, + background: BgNormanApartment, onSelector: true, exits: []exit{ - {to: names.ScreenRooftopHideout, label: "the stairs up to the roof", side: sideBack}, - {to: names.ScreenBBSTerminal, label: "Norman's terminal", side: sideRight}, + {to: ScreenRooftopHideout, label: "the stairs up to the roof", side: sideBack}, + {to: ScreenBBSTerminal, label: "Norman's terminal", side: sideRight}, }, } } diff --git a/internal/content/screen/paul_shop.go b/inc/screen.paul_shop.go similarity index 69% rename from internal/content/screen/paul_shop.go rename to inc/screen.paul_shop.go index dbfe5ae..ecb1c16 100644 --- a/internal/content/screen/paul_shop.go +++ b/inc/screen.paul_shop.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // paulShop is where the game starts: the junk shop and garage Paul lives // behind (wiki: entities#helyszinek). It is the only screen outside San @@ -10,13 +8,13 @@ import "git.teletypegames.org/games/realworld/internal/names" // The bus is a screen the deck does not have, so the exit is the journey // itself: one way, with no coming back to the shop mid-story. That is also why // the shop is not on the selector — it is not one of the city's locations. -func paulShop() screen { +func screenPaulShop() screen { return screen{ - name: names.ScreenPaulShop, + name: ScreenPaulShop, title: "PAUL'S SHOP — JUNK AND GARAGE", - background: names.BgPaulShop, + background: BgPaulShop, exits: []exit{ - {to: names.ScreenNoodleHouse, label: "the bus to San Francisco", side: sideNear}, + {to: ScreenNoodleHouse, label: "the bus to San Francisco", side: sideNear}, }, } } diff --git a/internal/content/screen/police_station.go b/inc/screen.police_station.go similarity index 74% rename from internal/content/screen/police_station.go rename to inc/screen.police_station.go index e4eee64..898293e 100644 --- a/internal/content/screen/police_station.go +++ b/inc/screen.police_station.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // policeStation is where Noodle is held for dealing in cracked Armillas, and // where a clerk quietly tips Paul off about the hiding place @@ -10,11 +8,11 @@ import "git.teletypegames.org/games/realworld/internal/names" // but the deck paints it as one screen, so the reception desk, the waiting // room and the interview window are hotspots inside this one, not screens of // their own. -func policeStation() screen { +func screenPoliceStation() screen { return screen{ - name: names.ScreenPoliceStation, + name: ScreenPoliceStation, title: "SFPD — STATION AND HOLDING", - background: names.BgPoliceStation, + background: BgPoliceStation, onSelector: true, } } diff --git a/internal/content/screen/public_bbs.go b/inc/screen.public_bbs.go similarity index 69% rename from internal/content/screen/public_bbs.go rename to inc/screen.public_bbs.go index 6c69196..cc050f8 100644 --- a/internal/content/screen/public_bbs.go +++ b/inc/screen.public_bbs.go @@ -1,16 +1,14 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // publicBBS is one of the wiki's 💡 suggestions: the canon has BBS terminals // standing in cafés, post offices and bus stops — live infrastructure, not // relics. An alternative way to read the boards, in a place where somebody can // read over your shoulder (wiki: entities#potenciális-további-helyszínek). -func publicBBS() screen { +func screenPublicBBS() screen { return screen{ - name: names.ScreenPublicBBS, + name: ScreenPublicBBS, title: "PUBLIC BBS TERMINAL", - background: names.BgPublicBBS, + background: BgPublicBBS, onSelector: true, } } diff --git a/internal/content/screen/rooftop_hideout.go b/inc/screen.rooftop_hideout.go similarity index 54% rename from internal/content/screen/rooftop_hideout.go rename to inc/screen.rooftop_hideout.go index 8c4f529..8a52781 100644 --- a/internal/content/screen/rooftop_hideout.go +++ b/inc/screen.rooftop_hideout.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // rooftopHideout is Norman's hiding place: an abandoned roof terrace with his // personal things, his notes and an old BBS terminal — the emotional high point @@ -8,14 +6,14 @@ import "git.teletypegames.org/games/realworld/internal/names" // // It is up the stairs from his flat, and it is one of the places the catalogue // puts a terminal. -func rooftopHideout() screen { +func screenRooftopHideout() screen { return screen{ - name: names.ScreenRooftopHideout, + name: ScreenRooftopHideout, title: "NORMAN'S ROOFTOP HIDEOUT", - background: names.BgRooftopHideout, + background: BgRooftopHideout, exits: []exit{ - {to: names.ScreenNormanApartment, label: "back down into the flat", side: sideNear}, - {to: names.ScreenBBSTerminal, label: "the old terminal", side: sideRight}, + {to: ScreenNormanApartment, label: "back down into the flat", side: sideNear}, + {to: ScreenBBSTerminal, label: "the old terminal", side: sideRight}, }, } } diff --git a/internal/content/screen/samizdat_press.go b/inc/screen.samizdat_press.go similarity index 67% rename from internal/content/screen/samizdat_press.go rename to inc/screen.samizdat_press.go index 0e3dddd..ccb09fc 100644 --- a/internal/content/screen/samizdat_press.go +++ b/inc/screen.samizdat_press.go @@ -1,16 +1,14 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // samizdatPress is a 💡 suggestion: the Resistance's physical node — parody // BVK databases, underground publications, a P2P node humming in the cellar. // The samizdat intellectuals are the first to write down that the world is // slipping (wiki: entities#potenciális-további-helyszínek). -func samizdatPress() screen { +func screenSamizdatPress() screen { return screen{ - name: names.ScreenSamizdatPress, + name: ScreenSamizdatPress, title: "SAMIZDAT PRINTING HOUSE", - background: names.BgSamizdatPress, + background: BgSamizdatPress, onSelector: true, } } diff --git a/internal/content/screen/scrap_market.go b/inc/screen.scrap_market.go similarity index 63% rename from internal/content/screen/scrap_market.go rename to inc/screen.scrap_market.go index bc4a7c1..ceb5629 100644 --- a/internal/content/screen/scrap_market.go +++ b/inc/screen.scrap_market.go @@ -1,15 +1,13 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // scrapMarket is a 💡 suggestion: the black market in condemned ACPs and bare, // unstamped tape. Canon says the leak of scrap is not an accident — "something // wants to live on" (wiki: entities#potenciális-további-helyszínek). -func scrapMarket() screen { +func screenScrapMarket() screen { return screen{ - name: names.ScreenScrapMarket, + name: ScreenScrapMarket, title: "SCRAP MARKET", - background: names.BgScrapMarket, + background: BgScrapMarket, onSelector: true, } } diff --git a/internal/content/screen/secret_club.go b/inc/screen.secret_club.go similarity index 59% rename from internal/content/screen/secret_club.go rename to inc/screen.secret_club.go index b2ae010..781cd47 100644 --- a/internal/content/screen/secret_club.go +++ b/inc/screen.secret_club.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // secretClub is the empty room behind the shop where the "underwater sun" // company gave their talk, with a secretary who knows about the BVK's plans for @@ -10,14 +8,14 @@ import "git.teletypegames.org/games/realworld/internal/names" // The terminal in here is the one that matters: not under BVK control, ECHO // running under the standard LECTOR surface. Reading it is where the mystery // tape finally speaks. -func secretClub() screen { +func screenSecretClub() screen { return screen{ - name: names.ScreenSecretClub, + name: ScreenSecretClub, title: "SECRET CLUB — THE UNDERWATER SUN", - background: names.BgSecretClub, + background: BgSecretClub, exits: []exit{ - {to: names.ScreenBBSTerminal, label: "the terminal in the corner", side: sideBack}, - {to: names.ScreenTrinketShop, label: "back out through the shop", side: sideNear}, + {to: ScreenBBSTerminal, label: "the terminal in the corner", side: sideBack}, + {to: ScreenTrinketShop, label: "back out through the shop", side: sideNear}, }, } } diff --git a/internal/content/screen/secret_lab.go b/inc/screen.secret_lab.go similarity index 77% rename from internal/content/screen/secret_lab.go rename to inc/screen.secret_lab.go index 4250ec4..5545226 100644 --- a/internal/content/screen/secret_lab.go +++ b/inc/screen.secret_lab.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // secretLab is the lab the BVK's secret section runs, where Norman lies wired // into the simulator: the two-thirds point where Paul finds him, and the @@ -10,11 +8,11 @@ import "git.teletypegames.org/games/realworld/internal/names" // the story puts the whole thing inside the Golden Gate Bridge. The deck paints // it as one screen, so the door at the foot of the bridge, the observation room // and the glass wall are hotspots in here. -func secretLab() screen { +func screenSecretLab() screen { return screen{ - name: names.ScreenSecretLab, + name: ScreenSecretLab, title: "SECRET RESEARCH LABORATORY", - background: names.BgSecretLab, + background: BgSecretLab, onSelector: true, } } diff --git a/internal/content/screen/selector.go b/inc/screen.selector.go similarity index 86% rename from internal/content/screen/selector.go rename to inc/screen.selector.go index a4c5111..4c43fb4 100644 --- a/internal/content/screen/selector.go +++ b/inc/screen.selector.go @@ -1,11 +1,9 @@ -package screen +package inc import ( "strings" inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // selector is the wiki's Helyszínválasztó: "nem valódi helyszín, hanem a @@ -17,9 +15,9 @@ import ( // It is not in the concept-art deck and has no painting, so it renders as an // inkwell placeholder with one labelled pin per destination, laid out on a // grid. Whatever the map ends up looking like, the connections it carries are -// the ones the screens declare, not a list kept here: selector() is built from +// the ones the screens declare, not a list kept here: screenSelector() is built from // whichever screens marked themselves onSelector. -func selector(rest []screen) screen { +func screenSelector(rest []screen) screen { var exits []exit for _, s := range rest { if !s.onSelector { @@ -32,9 +30,9 @@ func selector(rest []screen) screen { }) } return screen{ - name: names.ScreenSelector, + name: ScreenSelector, title: "SAN FRANCISCO", - background: names.BgSelector, + background: BgSelector, exits: exits, // Nobody stands on a map. actors: []inkwell.SceneActor{}, diff --git a/internal/content/screen/server_farm.go b/inc/screen.server_farm.go similarity index 62% rename from internal/content/screen/server_farm.go rename to inc/screen.server_farm.go index ba148e3..9e659b8 100644 --- a/internal/content/screen/server_farm.go +++ b/inc/screen.server_farm.go @@ -1,15 +1,13 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // serverFarm is the cooled, humming corridors where The AI's physical // infrastructure can be seen for what it is. Getting in needs the access tape // from the hackerspace or from Noodle (wiki: entities, timeline 24). -func serverFarm() screen { +func screenServerFarm() screen { return screen{ - name: names.ScreenServerFarm, + name: ScreenServerFarm, title: "SERVER FARM", - background: names.BgServerFarm, + background: BgServerFarm, onSelector: true, } } diff --git a/internal/content/screen/showroom.go b/inc/screen.showroom.go similarity index 66% rename from internal/content/screen/showroom.go rename to inc/screen.showroom.go index 38db216..4485585 100644 --- a/internal/content/screen/showroom.go +++ b/inc/screen.showroom.go @@ -1,16 +1,14 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // showroom is a 💡 suggestion: the legitimate, glittering side — Armilla // cabinets, licensed operators, matte-black Nokia-punk under showroom lights. // The contrast with Paul's edge of the world // (wiki: entities#potenciális-további-helyszínek). -func showroom() screen { +func screenShowroom() screen { return screen{ - name: names.ScreenShowroom, + name: ScreenShowroom, title: "NEUMATRONIC SHOWROOM", - background: names.BgShowroom, + background: BgShowroom, onSelector: true, } } diff --git a/internal/content/screen/small_restaurant.go b/inc/screen.small_restaurant.go similarity index 65% rename from internal/content/screen/small_restaurant.go rename to inc/screen.small_restaurant.go index 48fcb6f..0c2901c 100644 --- a/internal/content/screen/small_restaurant.go +++ b/inc/screen.small_restaurant.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // smallRestaurant is the little Chinese place beside the trinket shop, and the // lit mirror in its toilet is the puzzle: it flickers, and something shows @@ -9,13 +7,13 @@ import "git.teletypegames.org/games/realworld/internal/names" // The wiki's graph runs Kajálda → étterem rész → WC; the deck paints all three // as one screen, so the mirror is a hotspot here. The only way out is back to // the shop — this is an inner room, not a location on the selector. -func smallRestaurant() screen { +func screenSmallRestaurant() screen { return screen{ - name: names.ScreenSmallRestaurant, + name: ScreenSmallRestaurant, title: "SMALL RESTAURANT — NEXT DOOR", - background: names.BgSmallRestaurant, + background: BgSmallRestaurant, exits: []exit{ - {to: names.ScreenTrinketShop, label: "back to the trinket shop", side: sideLeft}, + {to: ScreenTrinketShop, label: "back to the trinket shop", side: sideLeft}, }, } } diff --git a/internal/content/screen/street.go b/inc/screen.street.go similarity index 73% rename from internal/content/screen/street.go rename to inc/screen.street.go index ec963ad..1d3f838 100644 --- a/internal/content/screen/street.go +++ b/inc/screen.street.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // street is the city itself: pavements, a square, a metro entrance, and the // environmental decay that tracks The AI's spread — glitching systems, odd @@ -9,11 +7,11 @@ import "git.teletypegames.org/games/realworld/internal/names" // The wiki's own street front is the one with the hotdog stand and Sumphor on // it (story#norman-lakása); that is a beat for this screen, not a screen of its // own. -func street() screen { +func screenStreet() screen { return screen{ - name: names.ScreenStreet, + name: ScreenStreet, title: "STREET", - background: names.BgStreet, + background: BgStreet, onSelector: true, } } diff --git a/internal/content/screen/trinket_shop.go b/inc/screen.trinket_shop.go similarity index 60% rename from internal/content/screen/trinket_shop.go rename to inc/screen.trinket_shop.go index 3e1f79f..25846a3 100644 --- a/internal/content/screen/trinket_shop.go +++ b/inc/screen.trinket_shop.go @@ -1,6 +1,4 @@ -package screen - -import "git.teletypegames.org/games/realworld/internal/names" +package inc // trinketShop is the Chinatown shop at the club's address, whose owner sells // Paul junk he does not need and is, in fact, the club's doorman @@ -9,17 +7,17 @@ import "git.teletypegames.org/games/realworld/internal/names" // Both of the wiki's arrows out of it are here: Bolt → Kajálda, the eating // place next door, and Bolt →|beengedés| Klub, which stays shut until the // shopkeeper has been shown the underwater sun. -func trinketShop() screen { +func screenTrinketShop() screen { return screen{ - name: names.ScreenTrinketShop, + name: ScreenTrinketShop, title: "CHINATOWN — TRINKET SHOP", - background: names.BgTrinketShop, + background: BgTrinketShop, onSelector: true, exits: []exit{ - {to: names.ScreenSmallRestaurant, label: "the eating place next door", side: sideRight}, + {to: ScreenSmallRestaurant, label: "the eating place next door", side: sideRight}, { - to: names.ScreenSecretClub, label: "the way in at the back", side: sideBack, - needs: names.FlagClubEntry, + to: ScreenSecretClub, label: "the way in at the back", side: sideBack, + needs: FlagClubEntry, blocked: "Just a shop, as far as the man behind the counter is concerned.", }, }, diff --git a/internal/content/script/awakening_finale.go b/inc/script.awakening_finale.go similarity index 57% rename from internal/content/script/awakening_finale.go rename to inc/script.awakening_finale.go index fe423e5..7e012b0 100644 --- a/internal/content/script/awakening_finale.go +++ b/inc/script.awakening_finale.go @@ -1,11 +1,7 @@ -package script +package inc import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/theme" - "git.teletypegames.org/games/realworld/internal/world" ) // awakeningFinale is the end of game two. The point of it, mechanically, is @@ -14,18 +10,18 @@ import ( // Run it early and often during development (`go run . -finale`) — it is the // single most important visual beat in the game, so you want to be looking at // it long before the surrounding scene exists. -func awakeningFinale(w *world.World) inkwell.Script { +func scriptAwakeningFinale(w *World) inkwell.Script { return inkwell.Script{ - Name: names.ScriptFinale, + Name: ScriptFinale, Actions: inkwell.Seq( - world.SetMode(w, world.ModeCutscene), + SetMode(w, ModeCutscene), inkwell.Wait(0.6), - inkwell.Say(names.Paul, "I'm putting it in. That's all this is."), + inkwell.Say(Paul, "I'm putting it in. That's all this is."), inkwell.Wait(0.4), - world.UseTheme(theme.NokiaPunk), + UseTheme(NokiaPunk), inkwell.Say("norman", "No — this isn't what you were supposed to do!"), inkwell.Wait(0.6), - world.TapeSay(names.TapeMystery, "Thank you, Paul. You did exactly what I asked, the whole way through."), + TapeSay(TapeMystery, "Thank you, Paul. You did exactly what I asked, the whole way through."), inkwell.Wait(1.0), inkwell.ShowEnd("REAL WORLD — end of game two"), ), diff --git a/inc/script.manager.go b/inc/script.manager.go new file mode 100644 index 0000000..605c23a --- /dev/null +++ b/inc/script.manager.go @@ -0,0 +1,18 @@ +package inc + +// Scripts — the game's named, reusable action sequences: +// cutscenes and recurring logic, built from the inkwell action set. + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// registerScript adds every script to the game. +func registerScript(w *World) { + for _, s := range []inkwell.Script{ + scriptTapeInsert(w), + scriptAwakeningFinale(w), + } { + w.G.ScriptManager.Register(s) + } +} diff --git a/inc/script.tape_insert.go b/inc/script.tape_insert.go new file mode 100644 index 0000000..264488b --- /dev/null +++ b/inc/script.tape_insert.go @@ -0,0 +1,28 @@ +package inc + +import ( + inkwell "git.teletypegames.org/engines/inkwell" +) + +// tapeInsert runs whenever a tape goes into slot 2: it activates the tape and +// makes Dex comment. The wiki calls the comment mandatory — Dex remarks on +// every newly loaded tape, without exception. +func scriptTapeInsert(w *World) inkwell.Script { + return inkwell.Script{ + Name: ScriptTapeInsert, + Actions: inkwell.Seq( + Fn(func(ctx *inkwell.Ctx) { + item := w.TakePending() + if item == "" { + return + } + w.SetSlot2(item) + ctx.Game.Inventory.Remove(item) + ctx.Game.State.SetVar(VarArmillaStrip, "SLOT2: READING") + }), + inkwell.Say(Paul, "Right. Let's see who you are."), + TapeSay(Dex, "Clicked in. Spinning. And now: nothing."), + TapeSay(Dex, "A stranger's tape in your own Armilla, Paul. I hope you know what you're doing."), + ), + } +} diff --git a/internal/theme/theme.go b/inc/theme.manager.go similarity index 94% rename from internal/theme/theme.go rename to inc/theme.manager.go index e9c0404..fde0336 100644 --- a/internal/theme/theme.go +++ b/inc/theme.manager.go @@ -1,13 +1,14 @@ -// Package theme holds the game's two colour themes and its colour tokens. +package inc + +// Themes — the game's two colour themes and its colour tokens. // // In canon the machines of this world are matte black with a green or amber // character display, and in the Real World finale "the Nokia-punk texture // breaks in" — with inkwell's runtime theme switching that beat is a single -// line (see world.UseTheme and script.awakeningFinale). +// line (see UseTheme and scriptAwakeningFinale). // // The colour rule: tapes speak in amber (green in Nokia-punk), the world and // Paul speak in bone white. If something is amber, a tape said it. -package theme import ( "image/color" @@ -147,8 +148,8 @@ func nokiaPunkTheme() inkwell.Theme { } } -// Register adds both themes to the game. -func Register(g *inkwell.Game) { +// registerTheme adds both themes to the game. +func registerTheme(g *inkwell.Game) { g.ThemeManager.Register(realWorldTheme()) g.ThemeManager.Register(nokiaPunkTheme()) } diff --git a/internal/ui/letterbox.go b/inc/ui.letterbox.go similarity index 76% rename from internal/ui/letterbox.go rename to inc/ui.letterbox.go index 9cfb118..8474d65 100644 --- a/internal/ui/letterbox.go +++ b/inc/ui.letterbox.go @@ -1,4 +1,4 @@ -package ui +package inc // Letterbox — the cutscene frame. // @@ -12,22 +12,18 @@ import ( "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/vector" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/theme" - "git.teletypegames.org/games/realworld/internal/world" ) type Letterbox struct { Name string - W *world.World + W *World Bar float64 // height of the black bar, top and bottom } func (l *Letterbox) GetName() string { return l.Name } func (l *Letterbox) Tick(ctx *inkwell.UICtx) { - if l.W.Mode() != world.ModeCutscene || !l.W.TapeWaiting() { + if l.W.Mode() != ModeCutscene || !l.W.TapeWaiting() { return } // inkwell's Input only covers the mouse; read the key directly. @@ -37,7 +33,7 @@ func (l *Letterbox) Tick(ctx *inkwell.UICtx) { } func (l *Letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { - if l.W.Mode() != world.ModeCutscene { + if l.W.Mode() != ModeCutscene { return } g := ctx.Game @@ -47,12 +43,12 @@ func (l *Letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { bar = 18 } w, h := float32(g.Width), float32(g.Height) - black := theme.RGB(0x000000) + black := RGB(0x000000) vector.DrawFilledRect(dst, 0, 0, w, float32(bar), black, false) vector.DrawFilledRect(dst, 0, h-float32(bar), w, float32(bar), black, false) if l.W.TapeWaiting() { - msg := "SPACE — " + names.TapeDisplayName(names.Dex) + " has something to say" + msg := "SPACE — " + TapeDisplayName(Dex) + " has something to say" drawTextC(dst, msg, g.Width-textW(msg)-4, g.Height-int(bar)+4, th.ChatLogResponse) } } diff --git a/internal/ui/ui.go b/inc/ui.manager.go similarity index 95% rename from internal/ui/ui.go rename to inc/ui.manager.go index 7f9b173..ec2399e 100644 --- a/internal/ui/ui.go +++ b/inc/ui.manager.go @@ -1,4 +1,4 @@ -package ui +package inc // HUD layout. // @@ -37,9 +37,6 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/vector" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" ) // Screen and layout, in internal (unscaled) pixels. @@ -75,16 +72,16 @@ const ( invGap = 4 ) -// Register assembles the whole HUD in the conventional Z-order: the debug +// registerUI assembles the whole HUD in the conventional Z-order: the debug // overlay at the back, the cursor in front. -func Register(w *world.World) { +func registerUI(w *World) { g := w.G // The guard goes FIRST so it ticks LAST among the widgets — widgets tick // in reverse registration order, so every HUD widget gets the click before // it does, and it only ever catches clicks that land on the scene. g.UIManager.Register(&UseWithGuard{Name: "usewith_guard", W: w}) - g.UIManager.Register(&world.ActionPump{Name: "pump", W: w}) + g.UIManager.Register(&ActionPump{Name: "pump", W: w}) g.UIManager.Register(&screenNav{Name: "screen_nav", W: w}) g.UIManager.Register(&windowSizer{Name: "window", Scale: 2}) g.UIManager.Register(&inkwell.HotspotDebug{Name: "hotspot_debug"}) @@ -95,7 +92,7 @@ func Register(w *world.World) { // The right section prints State.Var(TimeVar). We deliberately reuse // it for the Armilla's single-line phosphor strip: in canon the device // display is one line of text, not a phone screen. - TimeVar: names.VarArmillaStrip, + TimeVar: VarArmillaStrip, } w.SetTopBar(top) g.UIManager.Register(gated(w, "topbar_gate", top)) @@ -176,11 +173,11 @@ func (s *windowSizer) Tick(ctx *inkwell.UICtx) { // BlocksClickAt is forwarded, otherwise the verb coin would open over the HUD. type gate struct { Name string - W *world.World + W *World Inner inkwell.Widget } -func gated(w *world.World, name string, inner inkwell.Widget) inkwell.Widget { +func gated(w *World, name string, inner inkwell.Widget) inkwell.Widget { return &gate{Name: name, W: w, Inner: inner} } @@ -212,7 +209,7 @@ func (n *gate) BlocksClickAt(p inkwell.Point) bool { // two channels always have a visible, continuous separator between them. type hudFrame struct { Name string - W *world.World + W *World } func (h *hudFrame) GetName() string { return h.Name } diff --git a/internal/ui/screen_nav.go b/inc/ui.screen_nav.go similarity index 87% rename from internal/ui/screen_nav.go rename to inc/ui.screen_nav.go index 81db856..d9a26ad 100644 --- a/internal/ui/screen_nav.go +++ b/inc/ui.screen_nav.go @@ -1,9 +1,9 @@ -package ui +package inc // screenNav — walking the deck with the arrow keys. // // The screens are connected — each one records where you can get to from it -// (content/screen/exit.go) — but walking the map is the player's job, and a +// (screen.exit.go) — but walking the map is the player's job, and a // screen whose beat is not written yet has nothing in it to walk towards. LEFT // and RIGHT step to the previous and next screen so the deck can be reviewed as // a deck: in concept-art order, wrapping at both ends. It is a tool for looking @@ -17,13 +17,11 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" - - "git.teletypegames.org/games/realworld/internal/world" ) type screenNav struct { Name string - W *world.World + W *World } func (n *screenNav) GetName() string { return n.Name } @@ -49,7 +47,7 @@ func (n *screenNav) Tick(ctx *inkwell.UICtx) { } // neighbour returns the screen step places from the current one, wrapping. -// Registration order is the deck order — see content/screen/screen.go. +// Registration order is the deck order — see screen.manager.go. func (n *screenNav) neighbour(g *inkwell.Game, step int) string { deck := g.SceneManager.Names() if len(deck) < 2 { diff --git a/internal/ui/screen_nav_test.go b/inc/ui.screen_nav_test.go similarity index 83% rename from internal/ui/screen_nav_test.go rename to inc/ui.screen_nav_test.go index 8d6bb59..cd847ed 100644 --- a/internal/ui/screen_nav_test.go +++ b/inc/ui.screen_nav_test.go @@ -1,11 +1,9 @@ -package ui +package inc import ( "testing" inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/world" ) // The arrow keys walk the deck in registration order and wrap at both ends — @@ -16,9 +14,9 @@ func TestScreenNavWrapsBothWays(t *testing.T) { for _, n := range []string{"a", "b", "c"} { g.SceneManager.Register(inkwell.Scene{Name: n, Background: "bg"}) } - w := world.New(g) + w := newWorld(g) nav := &screenNav{Name: "screen_nav", W: w} - at := func(name string) { world.EnterScene(w, name).Start().Tick(&inkwell.Ctx{Game: g}) } + at := func(name string) { EnterScene(w, name).Start().Tick(&inkwell.Ctx{Game: g}) } for _, tc := range []struct { from string diff --git a/internal/ui/tape_channel.go b/inc/ui.tape_channel.go similarity index 89% rename from internal/ui/tape_channel.go rename to inc/ui.tape_channel.go index f76fff2..c58b54f 100644 --- a/internal/ui/tape_channel.go +++ b/inc/ui.tape_channel.go @@ -1,4 +1,4 @@ -package ui +package inc // TapeChannel — the tape channel down the right-hand side. // @@ -19,14 +19,11 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/vector" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" ) type TapeChannel struct { Name string - W *world.World + W *World Bounds inkwell.Rectangle } @@ -76,7 +73,7 @@ func (t *TapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { // joke book readable. col, txt = th.ChatLogPrompt, m.Text case inkwell.LogResponse: - if !names.IsTape(m.Speaker) { + if !IsTape(m.Speaker) { continue // Paul and the NPCs speak in the scene, not here } col, txt = speechColor(g, m.Speaker, th.ChatLogResponse), m.Text @@ -107,11 +104,11 @@ func (t *TapeChannel) lastSpeaker(g *inkwell.Game, th inkwell.Theme) (string, co msgs := g.Messages() for i := len(msgs) - 1; i >= 0; i-- { m := msgs[i] - if m.Kind == inkwell.LogResponse && names.IsTape(m.Speaker) { - return names.TapeDisplayName(m.Speaker), speechColor(g, m.Speaker, th.ChatLogResponse) + if m.Kind == inkwell.LogResponse && IsTape(m.Speaker) { + return TapeDisplayName(m.Speaker), speechColor(g, m.Speaker, th.ChatLogResponse) } } - return names.TapeDisplayName(names.Dex), speechColor(g, names.Dex, th.ChatLogResponse) + return TapeDisplayName(Dex), speechColor(g, Dex, th.ChatLogResponse) } // speechColor is the character's voice colour, falling back to the theme's. diff --git a/internal/ui/tape_slots.go b/inc/ui.tape_slots.go similarity index 78% rename from internal/ui/tape_slots.go rename to inc/ui.tape_slots.go index 09e822f..de268d6 100644 --- a/internal/ui/tape_slots.go +++ b/inc/ui.tape_slots.go @@ -1,4 +1,4 @@ -package ui +package inc // TapeSlots — the two slots of Paul's Armilla. // @@ -19,14 +19,11 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/vector" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" ) type TapeSlots struct { Name string - W *world.World + W *World Bounds inkwell.Rectangle } @@ -54,7 +51,7 @@ func (t *TapeSlots) Tick(ctx *inkwell.UICtx) { // Hover label, so the StatusLine spells out what is about to happen. switch { case r1.Contains(mp): - g.SetHoverLabel(names.TapeDisplayName(names.Dex)) + g.SetHoverLabel(TapeDisplayName(Dex)) case r2.Contains(mp): if s := t.W.Slot2(); s != "" { g.SetHoverLabel(itemLabel(g, s)) @@ -77,41 +74,41 @@ func (t *TapeSlots) Tick(ctx *inkwell.UICtx) { // Loading a tape into slot 2. if slot2 && sel != "" { g.Inventory.Select("") - if names.IsTapeItem(sel) { + if IsTapeItem(sel) { t.W.SetPending(sel) - t.W.Do(inkwell.RunScript(names.ScriptTapeInsert)) + t.W.Do(inkwell.RunScript(ScriptTapeInsert)) return } - t.W.Do(world.TapeSay(names.Dex, "That isn't a tape, Paul. That's an object. There is a difference.")) + t.W.Do(TapeSay(Dex, "That isn't a tape, Paul. That's an object. There is a difference.")) return } if !slot2 && sel != "" { g.Inventory.Select("") - t.W.Do(world.TapeSay(names.Dex, "The first slot is mine. I'm not moving.")) + t.W.Do(TapeSay(Dex, "The first slot is mine. I'm not moving.")) return } switch g.SelectedVerb() { case "talk": if !slot2 { - t.W.Do(world.Paused(t.W, inkwell.RunDialogue(names.DlgDex))) + t.W.Do(Paused(t.W, inkwell.RunDialogue(DlgDex))) return } if s := t.W.Slot2(); s != "" { - t.W.Do(world.Paused(t.W, inkwell.RunDialogue(names.TapeDialogue(s)))) + t.W.Do(Paused(t.W, inkwell.RunDialogue(TapeDialogue(s)))) return } - t.W.Do(world.TapeSay(names.Dex, "Empty. Nobody to talk to.")) + t.W.Do(TapeSay(Dex, "Empty. Nobody to talk to.")) default: // look, take, use all fall back to a description if !slot2 { - t.W.Do(world.TapeSay(names.Dex, "Me. Four kilobytes of a dead man. Be grateful.")) + t.W.Do(TapeSay(Dex, "Me. Four kilobytes of a dead man. Be grateful.")) return } if s := t.W.Slot2(); s != "" { - t.W.Do(world.TapeSay(names.Dex, "That is the second slot. Be careful what you let in.")) + t.W.Do(TapeSay(Dex, "That is the second slot. Be careful what you let in.")) return } - t.W.Do(world.TapeSay(names.Dex, "The second slot is empty. That's the rarer state.")) + t.W.Do(TapeSay(Dex, "The second slot is empty. That's the rarer state.")) } } @@ -138,7 +135,7 @@ func (t *TapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { drawTextC(dst, clip(body, inner), x, int(r.Y)+Pad/2+LineH, col) } - drawSlot(r1, "SLOT 1", names.TapeDisplayName(names.Dex), true) + drawSlot(r1, "SLOT 1", TapeDisplayName(Dex), true) if s := t.W.Slot2(); s != "" { drawSlot(r2, "SLOT 2", itemLabel(g, s), true) } else { diff --git a/internal/ui/text.go b/inc/ui.text.go similarity index 95% rename from internal/ui/text.go rename to inc/ui.text.go index 7eb1879..91f10f3 100644 --- a/internal/ui/text.go +++ b/inc/ui.text.go @@ -1,8 +1,6 @@ -// Package ui is the game's HUD: the layout, the custom widgets and coloured -// text rendering. -package ui +package inc -// Coloured text rendering. +// The HUD's coloured text rendering. // // inkwell's drawText (asset.text.go) discards its colour argument (`_ = c`) and // always renders white through the ebitenutil debug font. The two-channel diff --git a/internal/ui/text_test.go b/inc/ui.text_test.go similarity index 99% rename from internal/ui/text_test.go rename to inc/ui.text_test.go index 0fa2b5f..e79d654 100644 --- a/internal/ui/text_test.go +++ b/inc/ui.text_test.go @@ -1,4 +1,4 @@ -package ui +package inc import "testing" diff --git a/internal/ui/usewith_guard.go b/inc/ui.usewith_guard.go similarity index 93% rename from internal/ui/usewith_guard.go rename to inc/ui.usewith_guard.go index e5510ff..df6e558 100644 --- a/internal/ui/usewith_guard.go +++ b/inc/ui.usewith_guard.go @@ -1,4 +1,4 @@ -package ui +package inc // UseWithGuard — enforcing authored failure. // @@ -22,14 +22,11 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" "github.com/hajimehoshi/ebiten/v2" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" ) type UseWithGuard struct { Name string - W *world.World + W *World } func (u *UseWithGuard) GetName() string { return u.Name } @@ -64,8 +61,8 @@ func (u *UseWithGuard) Tick(ctx *inkwell.UICtx) { n := g.State.Talked(key) u.W.Do(inkwell.Seq( - inkwell.Say(names.Paul, pick(paulFails, n)), - world.TapeSay(names.Dex, dexFail(n)), + inkwell.Say(Paul, pick(paulFails, n)), + TapeSay(Dex, dexFail(n)), )) } diff --git a/internal/world/action.go b/inc/world.action.go similarity index 99% rename from internal/world/action.go rename to inc/world.action.go index 3e6ae30..2dd5f7a 100644 --- a/internal/world/action.go +++ b/inc/world.action.go @@ -1,4 +1,4 @@ -package world +package inc // The game's own actions, and the domain action pump. // diff --git a/internal/world/world.go b/inc/world.manager.go similarity index 87% rename from internal/world/world.go rename to inc/world.manager.go index df8b719..ce45b94 100644 --- a/internal/world/world.go +++ b/inc/world.manager.go @@ -1,15 +1,14 @@ -// Package world is the domain aggregate: a thin layer around the inkwell Game +package inc + +// World — the domain aggregate: a thin layer around the inkwell Game // holding the runtime state that must NOT be saved, plus the game's own // actions and the action pump. // -// This package knows nothing about the HUD or the content, so ui and content -// can both build on it without creating an import cycle. -package world +// The world knows nothing about the HUD or the content: the ui and content +// categories both build on it, never the other way round. import ( inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" ) // Mode is the HUD mode. @@ -48,7 +47,7 @@ type World struct { } // New wraps an existing inkwell game. -func New(g *inkwell.Game) *World { +func newWorld(g *inkwell.Game) *World { return &World{G: g, mode: ModePlay} } @@ -78,14 +77,14 @@ func (w *World) SetTopBar(t *inkwell.TopBar) { w.top = t } // Unlike Mode, this is REAL game state and lives in State.Var so that it is // saved. func (w *World) Slot2() string { - if v, ok := w.G.State.Var(names.VarSlot2).(string); ok { + if v, ok := w.G.State.Var(VarSlot2).(string); ok { return v } return "" } // SetSlot2 sets the contents of the second tape slot. -func (w *World) SetSlot2(item string) { w.G.State.SetVar(names.VarSlot2, item) } +func (w *World) SetSlot2(item string) { w.G.State.SetVar(VarSlot2, item) } // SetPending marks which tape is going into slot 2. The tape-insert script // performs the actual load; the widget only marks. diff --git a/internal/world/world_test.go b/inc/world.manager_test.go similarity index 60% rename from internal/world/world_test.go rename to inc/world.manager_test.go index c8d4180..e03cb47 100644 --- a/internal/world/world_test.go +++ b/inc/world.manager_test.go @@ -1,42 +1,39 @@ -package world_test +package inc import ( "testing" inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" ) // The mode must not leak into saved state: inkwell saves State.Vars but drops // the in-flight script, so a save taken mid-cutscene would otherwise reload // into a letterboxed, HUD-less game with nothing running. func TestModeIsNotSavedState(t *testing.T) { - w := world.New(inkwell.NewGame("t", 320, 200)) + w := newWorld(inkwell.NewGame("t", 320, 200)) - w.SetMode(world.ModeCutscene) - for _, k := range []string{"ui.mode", "mode"} { + w.SetMode(ModeCutscene) + for _, k := range []string{"mode", "mode"} { if v := w.G.State.Var(k); v != nil { t.Errorf("mode leaked into State.Vars (%q = %v)", k, v) } } // Slot 2, by contrast, is real game state and must be saved. - w.SetSlot2(names.ItemMysteryTape) - if w.G.State.Var(names.VarSlot2) != names.ItemMysteryTape { + w.SetSlot2(ItemMysteryTape) + if w.G.State.Var(VarSlot2) != ItemMysteryTape { t.Error("slot 2 is not in State.Vars") } - if got := w.Slot2(); got != names.ItemMysteryTape { + if got := w.Slot2(); got != ItemMysteryTape { t.Errorf("Slot2() = %q", got) } } // The widget marks a tape as pending; the script consumes it — once. func TestPendingIsConsumedOnce(t *testing.T) { - w := world.New(inkwell.NewGame("t", 320, 200)) - w.SetPending(names.ItemMysteryTape) - if got := w.TakePending(); got != names.ItemMysteryTape { + w := newWorld(inkwell.NewGame("t", 320, 200)) + w.SetPending(ItemMysteryTape) + if got := w.TakePending(); got != ItemMysteryTape { t.Fatalf("TakePending() = %q", got) } if got := w.TakePending(); got != "" { diff --git a/internal/boot/boot.go b/internal/boot/boot.go deleted file mode 100644 index d7f2d99..0000000 --- a/internal/boot/boot.go +++ /dev/null @@ -1,57 +0,0 @@ -// Package boot wires the layers together: this is the only place where the -// world, the content, the theme and the HUD meet. main.go knows nothing else. -package boot - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/content" - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/theme" - "git.teletypegames.org/games/realworld/internal/ui" - "git.teletypegames.org/games/realworld/internal/world" -) - -// Opts are the run parameters. -type Opts struct { - Screen string // starting screen; empty means the alley - Finale bool // start with the finale, to try the Nokia-punk theme switch -} - -// New builds a ready-to-run game. -func New(o Opts) *inkwell.Game { - screen := o.Screen - if screen == "" { - screen = names.ScreenAlley - } - - g := inkwell.NewGame("Real World", ui.ScreenW, ui.ScreenH) - g.MaxLogLines = 64 - - w := world.New(g) - theme.Register(g) - content.Register(w) - - g.UseTheme(theme.RealWorld) - ui.Register(w) - - g.StartAt(screen) - g.OnStart(inkwell.Seq(opening(w, screen, o.Finale)...)) - return g -} - -func opening(w *world.World, screen string, finale bool) []inkwell.Action { - acts := []inkwell.Action{ - world.EnterScene(w, screen), - // Beat 2 (the police station) will set the tip flag. Until that beat - // is written we set it here so the alley slice is playable end to end — - // the conditional branch itself is real code, not bypassed. - inkwell.SetFlag(names.FlagPoliceTip), - inkwell.Say(names.Paul, "Back alley. Just like the clerk said."), - world.TapeSay(names.Dex, "A government office tipped you off to remove something from a scene. That doesn't strike you as odd?"), - } - if finale { - acts = append(acts, inkwell.RunScript(names.ScriptFinale)) - } - return acts -} diff --git a/internal/content/background/background.go b/internal/content/background/background.go deleted file mode 100644 index 82e20fe..0000000 --- a/internal/content/background/background.go +++ /dev/null @@ -1,48 +0,0 @@ -// Package background registers one image asset per screen — one file each. -// -// A background is the whole of a screen's art: inkwell scales it over the -// entire window, so every file here points at a 640×380 PNG under assets/bg/, -// the size the concept-art deck is drawn at. Two of them are not painted yet -// and fall back to inkwell's placeholder. -// -// The order below is the deck's, which is the wiki's location-catalogue order -// (projects/realworld/entities#helyszinek). -package background - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/world" -) - -// Register adds every background to the game. -func Register(w *world.World) { - for _, a := range []inkwell.Asset{ - selector(), - paulShop(), - noodleHouse(), - normanApartment(), - policeStation(), - alley(), - hackerspace(), - iceCreamShop(), - trinketShop(), - smallRestaurant(), - secretClub(), - bbsTerminal(), - street(), - hospital(), - serverFarm(), - secretLab(), - rooftopHideout(), - publicBbs(), - bvkBranch(), - curatorShop(), - scrapMarket(), - samizdatPress(), - showroom(), - columbarium(), - } { - w.G.AssetManager.Register(a) - } -} diff --git a/internal/content/character/character.go b/internal/content/character/character.go deleted file mode 100644 index ec3ebf1..0000000 --- a/internal/content/character/character.go +++ /dev/null @@ -1,24 +0,0 @@ -// Package character registers the game's characters. -// -// Tapes are characters too, not props — the wiki is explicit about it. They -// have no body in the scene, so they carry no W/H and never appear in -// Scene.Actors; their voice lives in the tape channel (see world.TapeSay). -package character - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/world" -) - -// Register adds every character to the game. -func Register(w *world.World) { - for _, c := range []inkwell.Character{ - paul(), - dex(), - mysteryTape(), - supportTape(), - } { - w.G.CharacterManager.Register(c) - } -} diff --git a/internal/content/character/support_tape.go b/internal/content/character/support_tape.go deleted file mode 100644 index 56ec722..0000000 --- a/internal/content/character/support_tape.go +++ /dev/null @@ -1,17 +0,0 @@ -package character - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/theme" -) - -// supportTape answers everything with basic information, insufferably. Dex -// hates it. Acquired in Chinatown (beat 5) — not reachable yet. -func supportTape() inkwell.Character { - return inkwell.Character{ - Name: names.TapeSupport, - SpeechColor: theme.RGB(0xE8C86A), - } -} diff --git a/internal/content/content.go b/internal/content/content.go deleted file mode 100644 index a4a9d20..0000000 --- a/internal/content/content.go +++ /dev/null @@ -1,28 +0,0 @@ -// Package content is the composition root for everything the game declares: -// assets, characters, items, dialogues, scripts and scenes. -// -// Each registered entity lives in its own file, under a subpackage named after -// its kind. Adding a screen means adding screen/.go, background/.go -// and one line in each package's list — nothing else moves. -package content - -import ( - "git.teletypegames.org/games/realworld/internal/content/background" - "git.teletypegames.org/games/realworld/internal/content/character" - "git.teletypegames.org/games/realworld/internal/content/dialog" - "git.teletypegames.org/games/realworld/internal/content/item" - "git.teletypegames.org/games/realworld/internal/content/screen" - "git.teletypegames.org/games/realworld/internal/content/script" - "git.teletypegames.org/games/realworld/internal/world" -) - -// Register adds every entity to the game. Order matters only in that screens -// reference backgrounds and characters, which Game.Validate cross-checks. -func Register(w *world.World) { - background.Register(w) - character.Register(w) - item.Register(w) - dialog.Register(w) - script.Register(w) - screen.Register(w) -} diff --git a/internal/content/item/black_market_armilla.go b/internal/content/item/black_market_armilla.go deleted file mode 100644 index e1721c8..0000000 --- a/internal/content/item/black_market_armilla.go +++ /dev/null @@ -1,29 +0,0 @@ -package item - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" -) - -// blackMarketArmilla is flavour, not a quest trigger: the gap between -// legitimised technology and street reality. -func blackMarketArmilla() inkwell.Item { - return inkwell.Item{ - Name: names.ItemBlackArmilla, - Description: "black-market Armilla", - OnUseSelf: inkwell.Seq( - inkwell.Say(names.Paul, "Jailbroken. Pushes ads, but it was cheap."), - world.TapeSay(names.Dex, "Two slots, and both of them lie about the temperature. Don't trade me in for it."), - ), - OnUseWith: map[string]inkwell.Action{ - // An AUTHORED failure. Nothing is consumed, the character reacts, - // the tape remarks. Never a generic error line. - names.ItemNoodleLetter: inkwell.Seq( - inkwell.Say(names.Paul, "A letter and a bracelet. Brilliant."), - world.TapeSay(names.Dex, "Nothing. Which is what I'd charge for it."), - ), - }, - } -} diff --git a/internal/content/item/mystery_tape.go b/internal/content/item/mystery_tape.go deleted file mode 100644 index a86b87d..0000000 --- a/internal/content/item/mystery_tape.go +++ /dev/null @@ -1,21 +0,0 @@ -package item - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" -) - -// mysteryTape is the engine of the investigation. Password-locked; it only -// starts speaking after the club trigger (beat 6). -func mysteryTape() inkwell.Item { - return inkwell.Item{ - Name: names.ItemMysteryTape, - Description: "unmarked Personal Tape", - OnUseSelf: inkwell.Seq( - inkwell.Say(names.Paul, "Password-locked. Of course."), - world.TapeSay(names.Dex, "Put it in the second slot if you care that much. I did warn you."), - ), - } -} diff --git a/internal/content/item/noodle_letter.go b/internal/content/item/noodle_letter.go deleted file mode 100644 index dfbe99e..0000000 --- a/internal/content/item/noodle_letter.go +++ /dev/null @@ -1,20 +0,0 @@ -package item - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" -) - -// noodleLetter starts the story: it is what brings Paul to the city. -func noodleLetter() inkwell.Item { - return inkwell.Item{ - Name: names.ItemNoodleLetter, - Description: "Noodle's letter", - OnUseSelf: inkwell.Seq( - inkwell.Say(names.Paul, "“Come out, Paul. Not a phone conversation.” That's all it says."), - world.TapeSay(names.Dex, "And now you're here. And he isn't."), - ), - } -} diff --git a/internal/content/screen/alley.go b/internal/content/screen/alley.go deleted file mode 100644 index f8c0e0d..0000000 --- a/internal/content/screen/alley.go +++ /dev/null @@ -1,107 +0,0 @@ -package screen - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" -) - -// alley is the alley behind Noodle's house — beat 3, and the vertical slice the -// whole HUD is measured against: hotspots, a gated pickup, the two-slot -// Armilla, tape dialogue and an authored failure. -// -// The wiki keeps it in the same catalogue row as the house ("Noodle háza & a -// sikátor") and its graph has one arrow in, NoodleUtcafront → Sikátor, so the -// only way out is back to the street. The alley's own locked back door is the -// New Game+ hook and leads to a room the deck has not painted. -func alley() screen { - return screen{ - name: names.ScreenAlley, - title: "ALLEY — BEHIND NOODLE'S HOUSE", - background: names.BgAlley, - exits: []exit{ - // On the left: the back door hotspot below owns the other end. - {to: names.ScreenNoodleHouse, label: "back out to the street", side: sideLeft}, - }, - actors: []inkwell.SceneActor{ - {CharacterName: names.Paul, At: inkwell.Point{X: 120, Y: 232}}, - }, - onEnter: setVarIfEmpty(names.VarArmillaStrip, "SRP: 1 tape"), - hotspots: []inkwell.Hotspot{hidingPlace(), backDoor(), bin(), fireEscape()}, - } -} - -// hidingPlace holds the mystery tape. It only opens once Paul has the tip from -// the police station (beat 2) — without it he does not know what to look for. -func hidingPlace() inkwell.Hotspot { - return inkwell.Hotspot{ - Name: "hiding_place", - Label: "gap at the foot of the wall", - Area: inkwell.Rect(416, 150, 68, 52), - OnLook: inkwell.If(inkwell.Flag(names.FlagHasTape), - inkwell.Say(names.Paul, "Empty. Whatever was in there is on me now."), - inkwell.Say(names.Paul, "Loose brick. There's a gap behind it."), - ), - OnUse: inkwell.If(inkwell.Flag(names.FlagPoliceTip), - inkwell.If(inkwell.Flag(names.FlagHasTape), - inkwell.Say(names.Paul, "There's nothing else in there."), - inkwell.Seq( - inkwell.Say(names.Paul, "“Behind the brick.” All right then."), - inkwell.Give(names.ItemMysteryTape), - inkwell.SetFlag(names.FlagHasTape), - inkwell.Say(names.Paul, "A Personal Tape. No label, no seal."), - world.TapeSay(names.Dex, "Password-locked. And somebody very much did not want it found."), - ), - ), - inkwell.Say(names.Paul, "One loose brick. I'm not taking the alley apart over it."), - ), - OnTake: inkwell.Say(names.Paul, "I'm not carrying a wall."), - } -} - -// backDoor is the New Game+ hook: the code can be entered on the first visit -// too, if the player already knows it. -func backDoor() inkwell.Hotspot { - return inkwell.Hotspot{ - Name: "back_door", - Label: "locked back door", - Area: inkwell.Rect(72, 106, 80, 124), - OnLook: inkwell.Say(names.Paul, "Keypad. Four digits, worn keys."), - OnUse: inkwell.Seq( - inkwell.Say(names.Paul, "Locked. And I'm not guessing four digits."), - world.TapeSay(names.Dex, "The wear is heaviest on the two and the seven. That isn't a code. It's fewer options."), - ), - OnTalk: inkwell.Say(names.Paul, "To the door? I'm not there yet."), - OnUseWith: map[string]inkwell.Action{ - // An AUTHORED failure — not the engine's hardcoded flash. - names.ItemBlackArmilla: inkwell.Seq( - inkwell.Say(names.Paul, "A black-market bracelet doesn't open a keypad."), - world.TapeSay(names.Dex, "But you do get an advert out of it."), - ), - }, - } -} - -func bin() inkwell.Hotspot { - return inkwell.Hotspot{ - Name: "bin", - Label: "toppled bin", - Area: inkwell.Rect(240, 170, 88, 60), - OnLook: inkwell.Say(names.Paul, "Somebody's been through it. Thoroughly."), - OnUse: inkwell.Seq( - inkwell.Say(names.Paul, "Already turned out. I'm not going to be the second one."), - world.TapeSay(names.Dex, "The police don't go through bins. So who did?"), - ), - } -} - -func fireEscape() inkwell.Hotspot { - return inkwell.Hotspot{ - Name: "fire_escape", - Label: "fire escape", - Area: inkwell.Rect(528, 44, 88, 160), - OnLook: inkwell.Say(names.Paul, "Runs all the way to the roof. Bottom rung is two metres over my head."), - OnUse: inkwell.Say(names.Paul, "Can't reach it. I'd need something to stand on."), - } -} diff --git a/internal/content/script/script.go b/internal/content/script/script.go deleted file mode 100644 index fc12950..0000000 --- a/internal/content/script/script.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package script registers the game's named, reusable action sequences — -// cutscenes and recurring logic, built from the inkwell action set. -package script - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/world" -) - -// Register adds every script to the game. -func Register(w *world.World) { - for _, s := range []inkwell.Script{ - tapeInsert(w), - awakeningFinale(w), - } { - w.G.ScriptManager.Register(s) - } -} diff --git a/internal/content/script/tape_insert.go b/internal/content/script/tape_insert.go deleted file mode 100644 index eaba0ed..0000000 --- a/internal/content/script/tape_insert.go +++ /dev/null @@ -1,31 +0,0 @@ -package script - -import ( - inkwell "git.teletypegames.org/engines/inkwell" - - "git.teletypegames.org/games/realworld/internal/names" - "git.teletypegames.org/games/realworld/internal/world" -) - -// tapeInsert runs whenever a tape goes into slot 2: it activates the tape and -// makes Dex comment. The wiki calls the comment mandatory — Dex remarks on -// every newly loaded tape, without exception. -func tapeInsert(w *world.World) inkwell.Script { - return inkwell.Script{ - Name: names.ScriptTapeInsert, - Actions: inkwell.Seq( - world.Fn(func(ctx *inkwell.Ctx) { - item := w.TakePending() - if item == "" { - return - } - w.SetSlot2(item) - ctx.Game.Inventory.Remove(item) - ctx.Game.State.SetVar(names.VarArmillaStrip, "SLOT2: READING") - }), - inkwell.Say(names.Paul, "Right. Let's see who you are."), - world.TapeSay(names.Dex, "Clicked in. Spinning. And now: nothing."), - world.TapeSay(names.Dex, "A stranger's tape in your own Armilla, Paul. I hope you know what you're doing."), - ), - } -} diff --git a/main.go b/main.go index a694533..da2bbc5 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,7 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" - "git.teletypegames.org/games/realworld/internal/boot" + "git.teletypegames.org/games/realworld/inc" ) // The art ships inside the binary. js/wasm has no OS filesystem, so without @@ -21,13 +21,13 @@ var assets embed.FS func init() { inkwell.AssetFS = assets } func main() { - var o boot.Opts + var o inc.Opts flag.StringVar(&o.Screen, "screen", "", "starting screen (empty: the alley)") flag.BoolVar(&o.Finale, "finale", false, "start with the finale, to try the Nokia-punk theme switch") flag.Parse() - if err := inkwell.Run(boot.New(o)); err != nil { + if err := inkwell.Run(inc.New(o)); err != nil { log.Fatal(err) } }