From 94649a38fe2ac1d2f5b9c0152c31ab2230319fe8 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Sat, 29 Aug 2026 23:59:56 +0200 Subject: [PATCH] register logic --- CLAUDE.md | 145 +++++++++++++++++++++++++---- README.md | 99 +++++++++++++++----- inc/background.alley.go | 6 +- inc/background.bbs_terminal.go | 6 +- inc/background.bvk_branch.go | 6 +- inc/background.columbarium.go | 6 +- inc/background.curator_shop.go | 6 +- inc/background.hackerspace.go | 6 +- inc/background.hospital.go | 6 +- inc/background.ice_cream_shop.go | 6 +- inc/background.manager.go | 33 +------ inc/background.noodle_house.go | 6 +- inc/background.norman_apartment.go | 6 +- inc/background.paul_shop.go | 6 +- inc/background.police_station.go | 6 +- inc/background.public_bbs.go | 6 +- inc/background.rooftop_hideout.go | 6 +- inc/background.samizdat_press.go | 6 +- inc/background.scrap_market.go | 6 +- inc/background.secret_club.go | 6 +- inc/background.secret_lab.go | 6 +- inc/background.selector.go | 6 +- inc/background.server_farm.go | 6 +- inc/background.showroom.go | 6 +- inc/background.small_restaurant.go | 6 +- inc/background.street.go | 6 +- inc/background.trinket_shop.go | 6 +- inc/boot.manager.go | 14 +-- inc/character.dex.go | 10 +- inc/character.manager.go | 13 +-- inc/character.mystery_tape.go | 10 +- inc/character.paul.go | 6 +- inc/character.support_tape.go | 10 +- inc/content.manager.go | 14 +-- inc/dialog.dex_talk.go | 6 +- inc/dialog.manager.go | 11 +-- inc/dialog.mystery_tape_silent.go | 6 +- inc/item.black_market_armilla.go | 6 +- inc/item.manager.go | 12 +-- inc/item.mystery_tape.go | 6 +- inc/item.noodle_letter.go | 6 +- inc/manager.interface.go | 17 ++++ inc/manager.manager.go | 43 +++++++++ inc/screen.alley.go | 20 ++-- inc/screen.bbs_terminal.go | 14 +-- inc/screen.bvk_branch.go | 14 +-- inc/screen.columbarium.go | 14 +-- inc/screen.curator_shop.go | 14 +-- inc/screen.exit.go | 4 +- inc/screen.hackerspace.go | 14 +-- inc/screen.hospital.go | 14 +-- inc/screen.ice_cream_shop.go | 14 +-- inc/screen.manager.go | 87 ++++++----------- inc/screen.noodle_house.go | 16 ++-- inc/screen.norman_apartment.go | 16 ++-- inc/screen.paul_shop.go | 14 +-- inc/screen.police_station.go | 14 +-- inc/screen.public_bbs.go | 14 +-- inc/screen.rooftop_hideout.go | 14 +-- inc/screen.samizdat_press.go | 14 +-- inc/screen.scrap_market.go | 14 +-- inc/screen.secret_club.go | 14 +-- inc/screen.secret_lab.go | 14 +-- inc/screen.selector.go | 34 ++++--- inc/screen.server_farm.go | 14 +-- inc/screen.showroom.go | 14 +-- inc/screen.small_restaurant.go | 14 +-- inc/screen.street.go | 14 +-- inc/screen.trinket_shop.go | 16 ++-- inc/script.awakening_finale.go | 8 +- inc/script.manager.go | 11 +-- inc/script.tape_insert.go | 10 +- inc/theme.manager.go | 99 ++------------------ inc/theme.nokia_punk.go | 44 +++++++++ inc/theme.realworld.go | 48 ++++++++++ inc/ui.letterbox.go | 17 ++-- inc/ui.manager.go | 44 ++++----- inc/ui.screen_nav.go | 7 +- inc/ui.tape_channel.go | 17 ++-- inc/ui.tape_slots.go | 47 +++++----- inc/ui.usewith_guard.go | 13 ++- inc/world.action.go | 49 +++++----- inc/world.manager.go | 45 +++++---- 83 files changed, 830 insertions(+), 699 deletions(-) create mode 100644 inc/manager.interface.go create mode 100644 inc/manager.manager.go create mode 100644 inc/theme.nokia_punk.go create mode 100644 inc/theme.realworld.go diff --git a/CLAUDE.md b/CLAUDE.md index 9c2e4f3..2b2ba71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,8 +22,8 @@ one-to-one. ### No tests -Do not write tests, and do not add test cases to the ones that already exist. -Correctness is checked by running the game. +Do not write tests and do not add test files. Correctness is checked by running +the game. ### Struct literals are always multi-line @@ -52,15 +52,19 @@ file's name carries the structure, in the form `[category].[name].go`. `character`, `dialog`, `script`, `world`, `ui`, `theme`, `names`, `boot`, `content`. - `[category].manager.go` is the file that ties a category together — its - `register…` function, its shared types, its list of entities. + manager, its entity type, whatever the category shares. - Every other file in a category holds exactly one entity: one screen, one - background, one item. + background, one item. The file registers it itself, in an `init()`. ``` main.go flags + inkwell.Run +inc/manager.manager.go Manager[T]: the generic registry every category uses +inc/manager.interface.go ManagerInterface and the compile-time assertions inc/boot.manager.go wiring; New(Opts) builds the game inc/names.manager.go entity names and world-state keys -inc/theme.manager.go realworld-93 + nokia-punk +inc/theme.manager.go the theme manager and the colour helpers +inc/theme.realworld.go realworld-93 +inc/theme.nokia_punk.go nokia-punk inc/world.manager.go unsaved runtime state inc/world.action.go custom actions and the action pump inc/ui.manager.go HUD layout and widget registration @@ -71,25 +75,130 @@ inc/character.*.go the cast, tapes included inc/item.*.go inventory inc/dialog.*.go dialogue trees inc/script.*.go named action sequences -inc/screen.manager.go the deck, the scene builder +inc/screen.manager.go the Screen type, the deck, the scene builder inc/screen.exit.go the connections between screens inc/screen.*.go one file per screen ``` +`manager` is the one category with no entities of its own: it holds the registry +every other category is built from. + +## Managers + +Every category that owns a collection of entities has a manager, and they are +all the same generic type — `Manager[T]` in `manager.manager.go`. It keeps one +slice in registration order and one `map[string]int` beside it, so `GetByName` +is a map lookup, not a scan. + +Since the entity types are aliases of engine structs, no method can be attached +to them; the manager is told how to read a name instead, which is all it needs: + +```go +type Character = inkwell.Character + +var CharacterManager = NewManager(func(entity Character) string { return entity.Name }) +``` + +That is the whole of a category's manager file — an alias and one line. There +are seven managers: `BackgroundManager`, `CharacterManager`, `DialogManager`, +`ItemManager`, `ScriptManager`, `ScreenManager`, `ThemeManager`. + +`manager.interface.go` holds the contract they all keep, and asserts each one +against it. A new manager goes on that list. + +```go +type ManagerInterface[T any] interface { + Register(entity T) + GetByName(name string) (T, bool) + GetAll() []T +} +``` + +`Register` replaces by name and keeps the entity's position, so registering +twice is an update, never a duplicate. `GetAll` returns the slice itself, in +registration order. + +`Screen` is the one entity type that is not an alias: a screen carries exits and +an `OnSelector` flag that inkwell's `Scene` knows nothing about. + +### Entities register themselves + +An entity file is a literal and nothing else. No constructor function, no list +somewhere else to keep in step — the file hands itself to its manager in an +`init()`: + +```go +package inc + +func init() { + BackgroundManager.Register(Background{ + Name: BgServerFarm, + Path: "assets/bg/server_farm.png", + Kind: inkwell.AssetImage, + }) +} +``` + +Adding an entity is adding a file. Deleting one is deleting a file. Package-level +variables are initialised before any `init()` runs, so the managers exist by the +time the first file registers into one. + +`init()` order is file-name order, so **registration order is alphabetical**. +Nothing may depend on it — including the order the arrow keys walk the screens, +which is simply the order the files sit in. + +### Handing a category to the engine + +Once the game exists, `registerAll` walks a manager and gives every entity to +the engine's own manager. `registerContent` is the whole of it: + +```go +func registerContent() { + registerAll(BackgroundManager, World.G.AssetManager.Register) + registerAll(CharacterManager, World.G.CharacterManager.Register) + registerAll(ItemManager, World.G.ItemManager.Register) + registerAll(DialogManager, World.G.DialogueManager.Register) + registerAll(ScriptManager, World.G.ScriptManager.Register) + registerScreen() +} +``` + +`registerScreen` is the one that needs its own function, because a screen has to +be turned into an `inkwell.Scene` first, and because the selector's pins are +derived from whichever screens marked themselves `OnSelector`. + +## The world + +There is exactly one game, so there is exactly one world: `World`, a +package-level singleton in `world.manager.go`. Nothing takes a `*world` +parameter and no widget holds a back-reference — `World.Do(…)`, +`World.HUDVisible()`, `World.Slot2()` are reachable from anywhere in the +package. `New` calls `World.attach(g)`, which binds the engine and resets the +runtime state, so building the game twice is clean. + +This is what lets an entity file be a literal: the tape-insert script closes +over `World`, not over a parameter it would have had to be handed. + ## Naming One package means one namespace, so an entity's constructor carries its category as a prefix: ```go -screenAlley() backgroundAlley() -itemMysteryTape() characterMysteryTape() -dialogDexTalk() scriptTapeInsert() +screenFloor screenBuild registerScreen fillSelectorPins ``` -Category-level functions follow the same shape: `registerScreen`, -`registerBackground`, `registerItem`, and so on. Only `New` and `Opts` are -exported — they are what `main.go` needs, and nothing else leaves the package. +Entities themselves need no name at all — they are anonymous literals inside +their file's `init()`, and the file name says which one it is. + +Exported names are the authoring vocabulary — what a content file spells out: +`New` and `Opts`, `World`, the managers and the entity types they hold, the +constants in `names.manager.go`, the colour tokens, and the action constructors +(`TapeSay`, `Paused`, `SetMode`, `EnterScene`, `Back`, `Fn`). + +Everything else is machinery and stays unexported: the HUD widgets +(`tapeSlots`, `letterbox`, `hudFrame`, …), the `register…` functions, the +runners, `screenBuild`, `exit`. The engine's word for a screen is `Scene`. Ours is **screen**, because the wiki, the concept-art deck and the beat tables all count screens. "Scene" @@ -112,14 +221,12 @@ other way round. ## Adding a screen -1. `inc/screen..go` — `screen() screen` -2. `inc/background..go` — `background() inkwell.Asset` -3. One line in `screenDeck` (`inc/screen.manager.go`) -4. One line in `registerBackground` (`inc/background.manager.go`) -5. Constants in `inc/names.manager.go`: `Screen`, `Bg` -6. A 640×380 PNG in `assets/bg/` +1. Constants in `inc/names.manager.go`: `Screen`, `Bg` +2. `inc/screen..go` — an `init()` registering a `Screen` +3. `inc/background..go` — an `init()` registering a `Background` +4. A 640×380 PNG in `assets/bg/` -Nothing else moves. +Nothing else moves. There is no list to update. ## Commands diff --git a/README.md b/README.md index 6efaf53..f7d510c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ go mod edit -dropreplace git.teletypegames.org/engines/inkwell ```bash make build # native binary into bin/ -make test # headless: validates content and HUD layout make wasm # dist/game.wasm + wasm_exec.js make export VERSION=0.1 # zipped HTML/WASM bundle make binaries VERSION=0.1 # win-x86, win-x64, linux-x64 zips @@ -63,13 +62,13 @@ Release metadata lives in `metadata.json`. |---|---| | left click | run the selected verb | | right click | verb coin (Look / Use / Talk / Take) | -| `←` `→` | previous / next screen, in concept-art order, wrapping | +| `←` `→` | previous / next screen, in file order, wrapping | | `F1` | toggle hotspot outlines | | `SPACE` | during a cutscene: let the tape speak, if it offered | The screens are connected to each other (see **The map** below); the arrow keys -are a reviewing tool on top of that, walking the deck in concept-art order. The -walk is inert during a cutscene, a menu, or a stopped world, so it can never cut +are a reviewing tool on top of that, walking every screen in turn. The walk is +inert during a cutscene, a menu, or a stopped world, so it can never cut across an authored beat. Screenshots: `EBITEN_SCREENSHOT_KEY=q go run .`, then press `q` in the window. @@ -154,20 +153,22 @@ licence. 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. +together — its manager, its shared types, its hand-off to the engine. ``` -main.go flags + inkwell.Run -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 +main.go flags + inkwell.Run +inc/manager.manager.go Manager[T], the generic registry +inc/manager.interface.go ManagerInterface, the contract every manager keeps +inc/names.manager.go entity names and world-state keys +inc/theme.*.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 ``` 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 +code keeps by hand: the world holds no opinion about the HUD or the content, and both build on it, never the other way round. ``` @@ -188,12 +189,60 @@ 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 category's manager list. Nothing else moves. +Adding a screen means adding `screen..go` and `background..go`. +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. +Every category owns a manager, and they are all the same generic type, +`Manager[T]` — one slice in registration order, one `map[string]int` beside it, +so a lookup by name is a map hit rather than a scan. The entity types are +aliases of engine structs and cannot carry methods, so the manager is handed a +function that reads the name instead. A category's manager file is an alias and +one line: + +```go +type Character = inkwell.Character + +var CharacterManager = NewManager(func(entity Character) string { return entity.Name }) +``` + +They all keep the same contract, asserted in `manager.interface.go`: + +```go +type ManagerInterface[T any] interface { + Register(entity T) + GetByName(name string) (T, bool) + GetAll() []T +} +``` + +An entity file is a literal that hands itself over in an `init()`: + +```go +func init() { + BackgroundManager.Register(Background{ + Name: BgServerFarm, + Path: "assets/bg/server_farm.png", + Kind: inkwell.AssetImage, + }) +} +``` + +So adding an entity is adding a file, and there is no second list to keep in +step. The price is that registration order is file-name order: the arrow keys +walk the screens alphabetically rather than in the concept-art deck's order. +That was a deliberate trade — the deck order was a list that had to be kept in +step with the files by hand, and the deck is a thing to look at, not a thing to +play through. + +The game's own catalogue is readable without going through the engine: +`ScreenManager.GetByName("alley")` answers before a single scene has been handed +over. `registerContent` is where the hand-off happens, one `registerAll` call +per category. + +There is one world, and it is a package-level singleton: `World`. Nothing takes +a `*world` parameter and no widget holds a back-reference, which is what lets a +script file be a literal — the tape-insert script closes over `World` rather +than over a parameter someone would have had to thread to it. **On the word "screen".** inkwell's entity is called a `Scene`, and that is the type every `screen.*.go` file returns — but the wiki, the concept-art deck and @@ -231,9 +280,8 @@ convention every 1990s point & click used, and one field to re-aim later. A screen reached from several rooms has no fixed way out: the BBS terminal is the same terminal from the club, the flat or the roof, so its exit is `back`. -Three tests keep the map honest: every exit names a registered screen, every -screen has a way out, and every screen can be walked to from Paul's shop through -the exits alone — the arrow keys do not count. +The graph stays machine-readable: exit hotspots are named `exit:`, so +the connections can be read straight back out of the registered screens. ## Engine workarounds @@ -297,9 +345,10 @@ Also: the inkwell README gives the module path as hides them; the deck wanted them struck through but visible, so the list becomes a memory of what you already tried. Needs a thin override over `DialogBox`. -- **Only beat 3 exists.** The alley is a vertical slice; `boot.opening` sets the +- **Only beat 3 exists.** The alley is a vertical slice; `bootOpening` sets the police-tip flag that beat 2 will eventually set. - **The render has only been inspected once, by the author of this repo.** - Geometry is covered by tests and a layout dump, but this environment cannot - take a screenshot (both synthetic keystrokes and screen capture are blocked by - macOS privacy permissions), so every visual judgement has to come from you. + Geometry is derived from one font cell and one screen size, but this + environment cannot take a screenshot (both synthetic keystrokes and screen + capture are blocked by macOS privacy permissions), so every visual judgement + has to come from you. diff --git a/inc/background.alley.go b/inc/background.alley.go index 7cfac2a..3e41030 100644 --- a/inc/background.alley.go +++ b/inc/background.alley.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundAlley() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgAlley, Path: "assets/bg/alley.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.bbs_terminal.go b/inc/background.bbs_terminal.go index 8de2a22..554c26c 100644 --- a/inc/background.bbs_terminal.go +++ b/inc/background.bbs_terminal.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundBbsTerminal() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgBBSTerminal, Path: "assets/bg/bbs_terminal.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.bvk_branch.go b/inc/background.bvk_branch.go index 60b857f..34dc762 100644 --- a/inc/background.bvk_branch.go +++ b/inc/background.bvk_branch.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundBvkBranch() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgBVKBranch, Path: "assets/bg/bvk_branch.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.columbarium.go b/inc/background.columbarium.go index abdaf9f..6ae9602 100644 --- a/inc/background.columbarium.go +++ b/inc/background.columbarium.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundColumbarium() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgColumbarium, Path: "assets/bg/columbarium.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.curator_shop.go b/inc/background.curator_shop.go index 853c7d3..89a79a0 100644 --- a/inc/background.curator_shop.go +++ b/inc/background.curator_shop.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundCuratorShop() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgCuratorShop, Path: "assets/bg/curator_shop.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.hackerspace.go b/inc/background.hackerspace.go index 0a0c67d..314e26d 100644 --- a/inc/background.hackerspace.go +++ b/inc/background.hackerspace.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundHackerspace() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgHackerspace, Path: "assets/bg/hackerspace.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.hospital.go b/inc/background.hospital.go index 7089809..ffb5f34 100644 --- a/inc/background.hospital.go +++ b/inc/background.hospital.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundHospital() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgHospital, Path: "assets/bg/hospital.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.ice_cream_shop.go b/inc/background.ice_cream_shop.go index 00a8d76..78229e8 100644 --- a/inc/background.ice_cream_shop.go +++ b/inc/background.ice_cream_shop.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundIceCreamShop() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgIceCreamShop, Path: "assets/bg/ice_cream_shop.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.manager.go b/inc/background.manager.go index 7b9deb5..0a9ef88 100644 --- a/inc/background.manager.go +++ b/inc/background.manager.go @@ -4,33 +4,6 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -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) - } -} +type Background = inkwell.Asset + +var BackgroundManager = NewManager(func(entity Background) string { return entity.Name }) diff --git a/inc/background.noodle_house.go b/inc/background.noodle_house.go index f893a04..741c02b 100644 --- a/inc/background.noodle_house.go +++ b/inc/background.noodle_house.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundNoodleHouse() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgNoodleHouse, Path: "assets/bg/noodle_house.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.norman_apartment.go b/inc/background.norman_apartment.go index 29b494b..0a53152 100644 --- a/inc/background.norman_apartment.go +++ b/inc/background.norman_apartment.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundNormanApartment() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgNormanApartment, Path: "assets/bg/norman_apartment.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.paul_shop.go b/inc/background.paul_shop.go index 2355799..9dc2a5e 100644 --- a/inc/background.paul_shop.go +++ b/inc/background.paul_shop.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundPaulShop() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgPaulShop, Path: "assets/bg/paul_shop.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.police_station.go b/inc/background.police_station.go index 98b855b..345718c 100644 --- a/inc/background.police_station.go +++ b/inc/background.police_station.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundPoliceStation() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgPoliceStation, Path: "assets/bg/police_station.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.public_bbs.go b/inc/background.public_bbs.go index 2669421..77fd301 100644 --- a/inc/background.public_bbs.go +++ b/inc/background.public_bbs.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundPublicBBS() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgPublicBBS, Path: "assets/bg/public_bbs.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.rooftop_hideout.go b/inc/background.rooftop_hideout.go index c0f21b9..2fd6e3c 100644 --- a/inc/background.rooftop_hideout.go +++ b/inc/background.rooftop_hideout.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundRooftopHideout() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgRooftopHideout, Path: "assets/bg/rooftop_hideout.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.samizdat_press.go b/inc/background.samizdat_press.go index 78f274d..ec0848e 100644 --- a/inc/background.samizdat_press.go +++ b/inc/background.samizdat_press.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundSamizdatPress() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgSamizdatPress, Path: "assets/bg/samizdat_press.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.scrap_market.go b/inc/background.scrap_market.go index 8a21792..c1d1d8f 100644 --- a/inc/background.scrap_market.go +++ b/inc/background.scrap_market.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundScrapMarket() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgScrapMarket, Path: "assets/bg/scrap_market.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.secret_club.go b/inc/background.secret_club.go index 71a990d..3562278 100644 --- a/inc/background.secret_club.go +++ b/inc/background.secret_club.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundSecretClub() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgSecretClub, Path: "assets/bg/secret_club.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.secret_lab.go b/inc/background.secret_lab.go index 769df52..4df46c0 100644 --- a/inc/background.secret_lab.go +++ b/inc/background.secret_lab.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundSecretLab() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgSecretLab, Path: "assets/bg/secret_lab.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.selector.go b/inc/background.selector.go index 6bc220f..89f749d 100644 --- a/inc/background.selector.go +++ b/inc/background.selector.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundSelector() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgSelector, Path: "assets/bg/selector.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.server_farm.go b/inc/background.server_farm.go index 352f927..7429316 100644 --- a/inc/background.server_farm.go +++ b/inc/background.server_farm.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundServerFarm() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgServerFarm, Path: "assets/bg/server_farm.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.showroom.go b/inc/background.showroom.go index fa349d6..3e6df5a 100644 --- a/inc/background.showroom.go +++ b/inc/background.showroom.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundShowroom() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgShowroom, Path: "assets/bg/showroom.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.small_restaurant.go b/inc/background.small_restaurant.go index fb5937f..d55a428 100644 --- a/inc/background.small_restaurant.go +++ b/inc/background.small_restaurant.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundSmallRestaurant() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgSmallRestaurant, Path: "assets/bg/small_restaurant.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.street.go b/inc/background.street.go index e59659c..df179f1 100644 --- a/inc/background.street.go +++ b/inc/background.street.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundStreet() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgStreet, Path: "assets/bg/street.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/background.trinket_shop.go b/inc/background.trinket_shop.go index 5f2fea5..5accce1 100644 --- a/inc/background.trinket_shop.go +++ b/inc/background.trinket_shop.go @@ -4,10 +4,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func backgroundTrinketShop() inkwell.Asset { - return inkwell.Asset{ +func init() { + BackgroundManager.Register(Background{ Name: BgTrinketShop, Path: "assets/bg/trinket_shop.png", Kind: inkwell.AssetImage, - } + }) } diff --git a/inc/boot.manager.go b/inc/boot.manager.go index d5178dd..8329d80 100644 --- a/inc/boot.manager.go +++ b/inc/boot.manager.go @@ -18,21 +18,21 @@ func New(o Opts) *inkwell.Game { g := inkwell.NewGame("Real World", ScreenW, ScreenH) g.MaxLogLines = 64 - w := newWorld(g) - registerTheme(g) - registerContent(w) + World.attach(g) + registerTheme() + registerContent() g.UseTheme(RealWorld) - registerUI(w) + registerUI() g.StartAt(start) - g.OnStart(inkwell.Seq(bootOpening(w, start, o.Finale)...)) + g.OnStart(inkwell.Seq(bootOpening(start, o.Finale)...)) return g } -func bootOpening(w *World, start string, finale bool) []inkwell.Action { +func bootOpening(start string, finale bool) []inkwell.Action { acts := []inkwell.Action{ - EnterScene(w, start), + EnterScene(start), 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?"), diff --git a/inc/character.dex.go b/inc/character.dex.go index fd5abab..a28a15a 100644 --- a/inc/character.dex.go +++ b/inc/character.dex.go @@ -1,12 +1,8 @@ package inc -import ( - inkwell "git.teletypegames.org/engines/inkwell" -) - -func characterDex() inkwell.Character { - return inkwell.Character{ +func init() { + CharacterManager.Register(Character{ Name: Dex, SpeechColor: Amber, - } + }) } diff --git a/inc/character.manager.go b/inc/character.manager.go index 8992f11..4f8a38d 100644 --- a/inc/character.manager.go +++ b/inc/character.manager.go @@ -4,13 +4,6 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func registerCharacter(w *World) { - for _, c := range []inkwell.Character{ - characterPaul(), - characterDex(), - characterMysteryTape(), - characterSupportTape(), - } { - w.G.CharacterManager.Register(c) - } -} +type Character = inkwell.Character + +var CharacterManager = NewManager(func(entity Character) string { return entity.Name }) diff --git a/inc/character.mystery_tape.go b/inc/character.mystery_tape.go index de1b5ab..b2f9978 100644 --- a/inc/character.mystery_tape.go +++ b/inc/character.mystery_tape.go @@ -1,12 +1,8 @@ package inc -import ( - inkwell "git.teletypegames.org/engines/inkwell" -) - -func characterMysteryTape() inkwell.Character { - return inkwell.Character{ +func init() { + CharacterManager.Register(Character{ Name: TapeMystery, SpeechColor: RGB(0xB9A98A), - } + }) } diff --git a/inc/character.paul.go b/inc/character.paul.go index 5c53b95..7aa281b 100644 --- a/inc/character.paul.go +++ b/inc/character.paul.go @@ -4,8 +4,8 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func characterPaul() inkwell.Character { - return inkwell.Character{ +func init() { + CharacterManager.Register(Character{ Name: Paul, Speed: 96, W: 28, @@ -15,5 +15,5 @@ func characterPaul() inkwell.Character { Y: 232, }, SpeechColor: Ink, - } + }) } diff --git a/inc/character.support_tape.go b/inc/character.support_tape.go index 74bb607..6d135ff 100644 --- a/inc/character.support_tape.go +++ b/inc/character.support_tape.go @@ -1,12 +1,8 @@ package inc -import ( - inkwell "git.teletypegames.org/engines/inkwell" -) - -func characterSupportTape() inkwell.Character { - return inkwell.Character{ +func init() { + CharacterManager.Register(Character{ Name: TapeSupport, SpeechColor: RGB(0xE8C86A), - } + }) } diff --git a/inc/content.manager.go b/inc/content.manager.go index 1761a64..a560985 100644 --- a/inc/content.manager.go +++ b/inc/content.manager.go @@ -1,10 +1,10 @@ package inc -func registerContent(w *World) { - registerBackground(w) - registerCharacter(w) - registerItem(w) - registerDialog(w) - registerScript(w) - registerScreen(w) +func registerContent() { + registerAll(BackgroundManager, World.G.AssetManager.Register) + registerAll(CharacterManager, World.G.CharacterManager.Register) + registerAll(ItemManager, World.G.ItemManager.Register) + registerAll(DialogManager, World.G.DialogueManager.Register) + registerAll(ScriptManager, World.G.ScriptManager.Register) + registerScreen() } diff --git a/inc/dialog.dex_talk.go b/inc/dialog.dex_talk.go index 50a2f34..27b66a2 100644 --- a/inc/dialog.dex_talk.go +++ b/inc/dialog.dex_talk.go @@ -4,8 +4,8 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func dialogDexTalk() inkwell.Dialogue { - return inkwell.Dialogue{ +func init() { + DialogManager.Register(Dialog{ Name: DlgDex, Start: "root", Nodes: []inkwell.DialogueNode{{ @@ -52,5 +52,5 @@ func dialogDexTalk() inkwell.Dialogue { }, }, }}, - } + }) } diff --git a/inc/dialog.manager.go b/inc/dialog.manager.go index 5b2a3c6..9d3236c 100644 --- a/inc/dialog.manager.go +++ b/inc/dialog.manager.go @@ -4,11 +4,6 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func registerDialog(w *World) { - for _, d := range []inkwell.Dialogue{ - dialogDexTalk(), - dialogMysteryTapeSilent(), - } { - w.G.DialogueManager.Register(d) - } -} +type Dialog = inkwell.Dialogue + +var DialogManager = NewManager(func(entity Dialog) string { return entity.Name }) diff --git a/inc/dialog.mystery_tape_silent.go b/inc/dialog.mystery_tape_silent.go index cb86ea1..ae19a27 100644 --- a/inc/dialog.mystery_tape_silent.go +++ b/inc/dialog.mystery_tape_silent.go @@ -4,8 +4,8 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func dialogMysteryTapeSilent() inkwell.Dialogue { - return inkwell.Dialogue{ +func init() { + DialogManager.Register(Dialog{ Name: DlgMysteryTape, Start: "root", Nodes: []inkwell.DialogueNode{{ @@ -28,5 +28,5 @@ func dialogMysteryTapeSilent() inkwell.Dialogue { }, }, }}, - } + }) } diff --git a/inc/item.black_market_armilla.go b/inc/item.black_market_armilla.go index 325f28d..afc9b98 100644 --- a/inc/item.black_market_armilla.go +++ b/inc/item.black_market_armilla.go @@ -4,8 +4,8 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func itemBlackMarketArmilla() inkwell.Item { - return inkwell.Item{ +func init() { + ItemManager.Register(Item{ Name: ItemBlackArmilla, Description: "black-market Armilla", OnUseSelf: inkwell.Seq( @@ -18,5 +18,5 @@ func itemBlackMarketArmilla() inkwell.Item { TapeSay(Dex, "Nothing. Which is what I'd charge for it."), ), }, - } + }) } diff --git a/inc/item.manager.go b/inc/item.manager.go index 2260861..7cf1497 100644 --- a/inc/item.manager.go +++ b/inc/item.manager.go @@ -4,12 +4,6 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func registerItem(w *World) { - for _, i := range []inkwell.Item{ - itemNoodleLetter(), - itemBlackMarketArmilla(), - itemMysteryTape(), - } { - w.G.ItemManager.Register(i) - } -} +type Item = inkwell.Item + +var ItemManager = NewManager(func(entity Item) string { return entity.Name }) diff --git a/inc/item.mystery_tape.go b/inc/item.mystery_tape.go index bd08f7f..1c0bb7d 100644 --- a/inc/item.mystery_tape.go +++ b/inc/item.mystery_tape.go @@ -4,13 +4,13 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func itemMysteryTape() inkwell.Item { - return inkwell.Item{ +func init() { + ItemManager.Register(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 index 5f50dca..307ef5f 100644 --- a/inc/item.noodle_letter.go +++ b/inc/item.noodle_letter.go @@ -4,13 +4,13 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func itemNoodleLetter() inkwell.Item { - return inkwell.Item{ +func init() { + ItemManager.Register(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/inc/manager.interface.go b/inc/manager.interface.go new file mode 100644 index 0000000..6edaabd --- /dev/null +++ b/inc/manager.interface.go @@ -0,0 +1,17 @@ +package inc + +type ManagerInterface[T any] interface { + Register(entity T) + GetByName(name string) (T, bool) + GetAll() []T +} + +var ( + _ ManagerInterface[Background] = BackgroundManager + _ ManagerInterface[Character] = CharacterManager + _ ManagerInterface[Dialog] = DialogManager + _ ManagerInterface[Item] = ItemManager + _ ManagerInterface[Script] = ScriptManager + _ ManagerInterface[Screen] = ScreenManager + _ ManagerInterface[Theme] = ThemeManager +) diff --git a/inc/manager.manager.go b/inc/manager.manager.go new file mode 100644 index 0000000..c16f128 --- /dev/null +++ b/inc/manager.manager.go @@ -0,0 +1,43 @@ +package inc + +type Manager[T any] struct { + nameOf func(T) string + entities []T + index map[string]int +} + +func NewManager[T any](nameOf func(T) string) *Manager[T] { + return &Manager[T]{ + nameOf: nameOf, + index: map[string]int{}, + } +} + +func (m *Manager[T]) Register(entity T) { + name := m.nameOf(entity) + if i, ok := m.index[name]; ok { + m.entities[i] = entity + return + } + m.index[name] = len(m.entities) + m.entities = append(m.entities, entity) +} + +func (m *Manager[T]) GetByName(name string) (T, bool) { + i, ok := m.index[name] + if !ok { + var missing T + return missing, false + } + return m.entities[i], true +} + +func (m *Manager[T]) GetAll() []T { + return m.entities +} + +func registerAll[T any](m *Manager[T], register func(T)) { + for _, entity := range m.GetAll() { + register(entity) + } +} diff --git a/inc/screen.alley.go b/inc/screen.alley.go index 9f24cb9..56db3d1 100644 --- a/inc/screen.alley.go +++ b/inc/screen.alley.go @@ -4,19 +4,19 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func screenAlley() screen { - return screen{ - name: ScreenAlley, - title: "ALLEY — BEHIND NOODLE'S HOUSE", - background: BgAlley, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenAlley, + Title: "ALLEY — BEHIND NOODLE'S HOUSE", + Background: BgAlley, + Exits: []exit{ { to: ScreenNoodleHouse, label: "back out to the street", side: sideLeft, }, }, - actors: []inkwell.SceneActor{ + Actors: []inkwell.SceneActor{ { CharacterName: Paul, At: inkwell.Point{ @@ -25,9 +25,9 @@ func screenAlley() screen { }, }, }, - onEnter: setVarIfEmpty(VarArmillaStrip, "SRP: 1 tape"), - hotspots: []inkwell.Hotspot{hidingPlace(), backDoor(), bin(), fireEscape()}, - } + OnEnter: setVarIfEmpty(VarArmillaStrip, "SRP: 1 tape"), + Hotspots: []inkwell.Hotspot{hidingPlace(), backDoor(), bin(), fireEscape()}, + }) } func hidingPlace() inkwell.Hotspot { diff --git a/inc/screen.bbs_terminal.go b/inc/screen.bbs_terminal.go index 2ccc775..fd8073f 100644 --- a/inc/screen.bbs_terminal.go +++ b/inc/screen.bbs_terminal.go @@ -1,15 +1,15 @@ package inc -func screenBbsTerminal() screen { - return screen{ - name: ScreenBBSTerminal, - title: "TERMINAL — BBS ACCESS POINT", - background: BgBBSTerminal, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenBBSTerminal, + Title: "TERMINAL — BBS ACCESS POINT", + Background: BgBBSTerminal, + Exits: []exit{ { label: "step back from the terminal", side: sideNear, }, }, - } + }) } diff --git a/inc/screen.bvk_branch.go b/inc/screen.bvk_branch.go index 8a8ead2..bb80f2f 100644 --- a/inc/screen.bvk_branch.go +++ b/inc/screen.bvk_branch.go @@ -1,10 +1,10 @@ package inc -func screenBvkBranch() screen { - return screen{ - name: ScreenBVKBranch, - title: "BVK — SAN FRANCISCO BRANCH", - background: BgBVKBranch, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenBVKBranch, + Title: "BVK — SAN FRANCISCO BRANCH", + Background: BgBVKBranch, + OnSelector: true, + }) } diff --git a/inc/screen.columbarium.go b/inc/screen.columbarium.go index 7c5b0bb..62155ea 100644 --- a/inc/screen.columbarium.go +++ b/inc/screen.columbarium.go @@ -1,10 +1,10 @@ package inc -func screenColumbarium() screen { - return screen{ - name: ScreenColumbarium, - title: "COLUMBARIUM — DEX'S MEMORIAL", - background: BgColumbarium, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenColumbarium, + Title: "COLUMBARIUM — DEX'S MEMORIAL", + Background: BgColumbarium, + OnSelector: true, + }) } diff --git a/inc/screen.curator_shop.go b/inc/screen.curator_shop.go index ab62c00..e227dc9 100644 --- a/inc/screen.curator_shop.go +++ b/inc/screen.curator_shop.go @@ -1,10 +1,10 @@ package inc -func screenCuratorShop() screen { - return screen{ - name: ScreenCuratorShop, - title: "CURATOR SERVICE — THE MASTER'S WORKSHOP", - background: BgCuratorShop, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenCuratorShop, + Title: "CURATOR SERVICE — THE MASTER'S WORKSHOP", + Background: BgCuratorShop, + OnSelector: true, + }) } diff --git a/inc/screen.exit.go b/inc/screen.exit.go index 65fd260..059b39b 100644 --- a/inc/screen.exit.go +++ b/inc/screen.exit.go @@ -52,10 +52,10 @@ func exitName(to string) string { return "exit:" + to } -func (e exit) hotspot(w *World) inkwell.Hotspot { +func (e exit) hotspot() inkwell.Hotspot { var travel inkwell.Action = inkwell.GoTo(e.to) if e.to == "" { - travel = Back(w) + travel = Back() } if e.needs != "" { travel = inkwell.If(inkwell.Flag(e.needs), travel, inkwell.Say(Paul, e.blocked)) diff --git a/inc/screen.hackerspace.go b/inc/screen.hackerspace.go index 0631d4c..d8d4f00 100644 --- a/inc/screen.hackerspace.go +++ b/inc/screen.hackerspace.go @@ -1,10 +1,10 @@ package inc -func screenHackerspace() screen { - return screen{ - name: ScreenHackerspace, - title: "HACKERSPACE", - background: BgHackerspace, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenHackerspace, + Title: "HACKERSPACE", + Background: BgHackerspace, + OnSelector: true, + }) } diff --git a/inc/screen.hospital.go b/inc/screen.hospital.go index b0d6fc2..cbdeea1 100644 --- a/inc/screen.hospital.go +++ b/inc/screen.hospital.go @@ -1,10 +1,10 @@ package inc -func screenHospital() screen { - return screen{ - name: ScreenHospital, - title: "HOSPITAL — PSYCHIATRIC WING", - background: BgHospital, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenHospital, + Title: "HOSPITAL — PSYCHIATRIC WING", + Background: BgHospital, + OnSelector: true, + }) } diff --git a/inc/screen.ice_cream_shop.go b/inc/screen.ice_cream_shop.go index 18b7731..2c9d4dc 100644 --- a/inc/screen.ice_cream_shop.go +++ b/inc/screen.ice_cream_shop.go @@ -1,10 +1,10 @@ package inc -func screenIceCreamShop() screen { - return screen{ - name: ScreenIceCreamShop, - title: "ICE CREAM SHOP — WHERE THE BAR WAS", - background: BgIceCreamShop, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenIceCreamShop, + Title: "ICE CREAM SHOP — WHERE THE BAR WAS", + Background: BgIceCreamShop, + OnSelector: true, + }) } diff --git a/inc/screen.manager.go b/inc/screen.manager.go index f21a2d3..869ef8b 100644 --- a/inc/screen.manager.go +++ b/inc/screen.manager.go @@ -4,55 +4,28 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -type screen struct { - name string - title string - background string +type Screen struct { + Name string + Title string + Background string - exits []exit + Exits []exit - onSelector bool + OnSelector bool - hotspots []inkwell.Hotspot - actors []inkwell.SceneActor - walkboxes []inkwell.Polygon - onEnter inkwell.Action + Hotspots []inkwell.Hotspot + Actors []inkwell.SceneActor + Walkboxes []inkwell.Polygon + OnEnter inkwell.Action } -var screenDeck = []func() screen{ - screenPaulShop, - screenNoodleHouse, - screenAlley, - screenNormanApartment, - screenPoliceStation, - screenHackerspace, - screenIceCreamShop, - screenTrinketShop, - screenSmallRestaurant, - screenSecretClub, - screenBbsTerminal, - screenStreet, - screenHospital, - screenServerFarm, - screenSecretLab, - screenRooftopHideout, - screenPublicBBS, - screenBvkBranch, - screenCuratorShop, - screenScrapMarket, - screenSamizdatPress, - screenShowroom, - screenColumbarium, -} +var ScreenManager = NewManager(func(entity Screen) string { return entity.Name }) -func registerScreen(w *World) { - screens := make([]screen, 0, len(screenDeck)+1) - for _, f := range screenDeck { - screens = append(screens, f()) - } - for _, s := range append([]screen{screenSelector(screens)}, screens...) { - w.G.SceneManager.Register(screenBuild(w, s)) - } +func registerScreen() { + fillSelectorPins() + registerAll(ScreenManager, func(entity Screen) { + World.G.SceneManager.Register(screenBuild(entity)) + }) } var screenFloor = []inkwell.Polygon{ @@ -74,18 +47,18 @@ var screenFloor = []inkwell.Polygon{ ), } -func screenBuild(w *World, s screen) inkwell.Scene { - exits := s.exits - if s.onSelector { +func screenBuild(s Screen) inkwell.Scene { + exits := s.Exits + if s.OnSelector { exits = append(exits, toSelector()) } - hotspots := make([]inkwell.Hotspot, 0, len(exits)+len(s.hotspots)) - hotspots = append(hotspots, s.hotspots...) + hotspots := make([]inkwell.Hotspot, 0, len(exits)+len(s.Hotspots)) + hotspots = append(hotspots, s.Hotspots...) for _, e := range exits { - hotspots = append(hotspots, e.hotspot(w)) + hotspots = append(hotspots, e.hotspot()) } - actors := s.actors + actors := s.Actors if actors == nil { actors = []inkwell.SceneActor{ { @@ -97,19 +70,19 @@ func screenBuild(w *World, s screen) inkwell.Scene { }, } } - walkboxes := s.walkboxes + walkboxes := s.Walkboxes if walkboxes == nil { walkboxes = screenFloor } - onEnter := inkwell.Action(EnterScene(w, s.name)) - if s.onEnter != nil { - onEnter = inkwell.Seq(onEnter, s.onEnter) + onEnter := inkwell.Action(EnterScene(s.Name)) + if s.OnEnter != nil { + onEnter = inkwell.Seq(onEnter, s.OnEnter) } return inkwell.Scene{ - Name: s.name, - Title: s.title, - Background: s.background, + Name: s.Name, + Title: s.Title, + Background: s.Background, Actors: actors, Walkboxes: walkboxes, Hotspots: hotspots, diff --git a/inc/screen.noodle_house.go b/inc/screen.noodle_house.go index 0aed5b3..84910d1 100644 --- a/inc/screen.noodle_house.go +++ b/inc/screen.noodle_house.go @@ -1,17 +1,17 @@ package inc -func screenNoodleHouse() screen { - return screen{ - name: ScreenNoodleHouse, - title: "NOODLE'S HOUSE — SEALED", - background: BgNoodleHouse, - onSelector: true, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenNoodleHouse, + Title: "NOODLE'S HOUSE — SEALED", + Background: BgNoodleHouse, + OnSelector: true, + Exits: []exit{ { to: ScreenAlley, label: "the alley behind the house", side: sideRight, }, }, - } + }) } diff --git a/inc/screen.norman_apartment.go b/inc/screen.norman_apartment.go index c92b56c..d52c1c9 100644 --- a/inc/screen.norman_apartment.go +++ b/inc/screen.norman_apartment.go @@ -1,12 +1,12 @@ package inc -func screenNormanApartment() screen { - return screen{ - name: ScreenNormanApartment, - title: "NORMAN'S APARTMENT", - background: BgNormanApartment, - onSelector: true, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenNormanApartment, + Title: "NORMAN'S APARTMENT", + Background: BgNormanApartment, + OnSelector: true, + Exits: []exit{ { to: ScreenRooftopHideout, label: "the stairs up to the roof", @@ -18,5 +18,5 @@ func screenNormanApartment() screen { side: sideRight, }, }, - } + }) } diff --git a/inc/screen.paul_shop.go b/inc/screen.paul_shop.go index 6ad2c89..ec06f44 100644 --- a/inc/screen.paul_shop.go +++ b/inc/screen.paul_shop.go @@ -1,16 +1,16 @@ package inc -func screenPaulShop() screen { - return screen{ - name: ScreenPaulShop, - title: "PAUL'S SHOP — JUNK AND GARAGE", - background: BgPaulShop, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenPaulShop, + Title: "PAUL'S SHOP — JUNK AND GARAGE", + Background: BgPaulShop, + Exits: []exit{ { to: ScreenNoodleHouse, label: "the bus to San Francisco", side: sideNear, }, }, - } + }) } diff --git a/inc/screen.police_station.go b/inc/screen.police_station.go index 6bfca63..06dbdd7 100644 --- a/inc/screen.police_station.go +++ b/inc/screen.police_station.go @@ -1,10 +1,10 @@ package inc -func screenPoliceStation() screen { - return screen{ - name: ScreenPoliceStation, - title: "SFPD — STATION AND HOLDING", - background: BgPoliceStation, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenPoliceStation, + Title: "SFPD — STATION AND HOLDING", + Background: BgPoliceStation, + OnSelector: true, + }) } diff --git a/inc/screen.public_bbs.go b/inc/screen.public_bbs.go index a509c43..e91c673 100644 --- a/inc/screen.public_bbs.go +++ b/inc/screen.public_bbs.go @@ -1,10 +1,10 @@ package inc -func screenPublicBBS() screen { - return screen{ - name: ScreenPublicBBS, - title: "PUBLIC BBS TERMINAL", - background: BgPublicBBS, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenPublicBBS, + Title: "PUBLIC BBS TERMINAL", + Background: BgPublicBBS, + OnSelector: true, + }) } diff --git a/inc/screen.rooftop_hideout.go b/inc/screen.rooftop_hideout.go index 8cce037..6cccb37 100644 --- a/inc/screen.rooftop_hideout.go +++ b/inc/screen.rooftop_hideout.go @@ -1,11 +1,11 @@ package inc -func screenRooftopHideout() screen { - return screen{ - name: ScreenRooftopHideout, - title: "NORMAN'S ROOFTOP HIDEOUT", - background: BgRooftopHideout, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenRooftopHideout, + Title: "NORMAN'S ROOFTOP HIDEOUT", + Background: BgRooftopHideout, + Exits: []exit{ { to: ScreenNormanApartment, label: "back down into the flat", @@ -17,5 +17,5 @@ func screenRooftopHideout() screen { side: sideRight, }, }, - } + }) } diff --git a/inc/screen.samizdat_press.go b/inc/screen.samizdat_press.go index cde9adf..6f9a715 100644 --- a/inc/screen.samizdat_press.go +++ b/inc/screen.samizdat_press.go @@ -1,10 +1,10 @@ package inc -func screenSamizdatPress() screen { - return screen{ - name: ScreenSamizdatPress, - title: "SAMIZDAT PRINTING HOUSE", - background: BgSamizdatPress, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenSamizdatPress, + Title: "SAMIZDAT PRINTING HOUSE", + Background: BgSamizdatPress, + OnSelector: true, + }) } diff --git a/inc/screen.scrap_market.go b/inc/screen.scrap_market.go index 521a2d2..60f82d3 100644 --- a/inc/screen.scrap_market.go +++ b/inc/screen.scrap_market.go @@ -1,10 +1,10 @@ package inc -func screenScrapMarket() screen { - return screen{ - name: ScreenScrapMarket, - title: "SCRAP MARKET", - background: BgScrapMarket, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenScrapMarket, + Title: "SCRAP MARKET", + Background: BgScrapMarket, + OnSelector: true, + }) } diff --git a/inc/screen.secret_club.go b/inc/screen.secret_club.go index e92f510..e4ec784 100644 --- a/inc/screen.secret_club.go +++ b/inc/screen.secret_club.go @@ -1,11 +1,11 @@ package inc -func screenSecretClub() screen { - return screen{ - name: ScreenSecretClub, - title: "SECRET CLUB — THE UNDERWATER SUN", - background: BgSecretClub, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenSecretClub, + Title: "SECRET CLUB — THE UNDERWATER SUN", + Background: BgSecretClub, + Exits: []exit{ { to: ScreenBBSTerminal, label: "the terminal in the corner", @@ -17,5 +17,5 @@ func screenSecretClub() screen { side: sideNear, }, }, - } + }) } diff --git a/inc/screen.secret_lab.go b/inc/screen.secret_lab.go index cb9f371..98679a3 100644 --- a/inc/screen.secret_lab.go +++ b/inc/screen.secret_lab.go @@ -1,10 +1,10 @@ package inc -func screenSecretLab() screen { - return screen{ - name: ScreenSecretLab, - title: "SECRET RESEARCH LABORATORY", - background: BgSecretLab, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenSecretLab, + Title: "SECRET RESEARCH LABORATORY", + Background: BgSecretLab, + OnSelector: true, + }) } diff --git a/inc/screen.selector.go b/inc/screen.selector.go index ace3a86..bcfce9d 100644 --- a/inc/screen.selector.go +++ b/inc/screen.selector.go @@ -6,26 +6,34 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func screenSelector(rest []screen) screen { +func init() { + ScreenManager.Register(Screen{ + Name: ScreenSelector, + Title: "SAN FRANCISCO", + Background: BgSelector, + Actors: []inkwell.SceneActor{}, + Walkboxes: []inkwell.Polygon{}, + }) +} + +func fillSelectorPins() { + selector, ok := ScreenManager.GetByName(ScreenSelector) + if !ok { + return + } var exits []exit - for _, s := range rest { - if !s.onSelector { + for _, entity := range ScreenManager.GetAll() { + if !entity.OnSelector { continue } exits = append(exits, exit{ - to: s.name, - label: pinLabel(s.title), + to: entity.Name, + label: pinLabel(entity.Title), area: pin(len(exits)), }) } - return screen{ - name: ScreenSelector, - title: "SAN FRANCISCO", - background: BgSelector, - exits: exits, - actors: []inkwell.SceneActor{}, - walkboxes: []inkwell.Polygon{}, - } + selector.Exits = exits + ScreenManager.Register(selector) } const ( diff --git a/inc/screen.server_farm.go b/inc/screen.server_farm.go index 29f8a44..d1cac94 100644 --- a/inc/screen.server_farm.go +++ b/inc/screen.server_farm.go @@ -1,10 +1,10 @@ package inc -func screenServerFarm() screen { - return screen{ - name: ScreenServerFarm, - title: "SERVER FARM", - background: BgServerFarm, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenServerFarm, + Title: "SERVER FARM", + Background: BgServerFarm, + OnSelector: true, + }) } diff --git a/inc/screen.showroom.go b/inc/screen.showroom.go index 7d05ada..a9f0ded 100644 --- a/inc/screen.showroom.go +++ b/inc/screen.showroom.go @@ -1,10 +1,10 @@ package inc -func screenShowroom() screen { - return screen{ - name: ScreenShowroom, - title: "NEUMATRONIC SHOWROOM", - background: BgShowroom, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenShowroom, + Title: "NEUMATRONIC SHOWROOM", + Background: BgShowroom, + OnSelector: true, + }) } diff --git a/inc/screen.small_restaurant.go b/inc/screen.small_restaurant.go index 44a2a93..8b4d0b7 100644 --- a/inc/screen.small_restaurant.go +++ b/inc/screen.small_restaurant.go @@ -1,16 +1,16 @@ package inc -func screenSmallRestaurant() screen { - return screen{ - name: ScreenSmallRestaurant, - title: "SMALL RESTAURANT — NEXT DOOR", - background: BgSmallRestaurant, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenSmallRestaurant, + Title: "SMALL RESTAURANT — NEXT DOOR", + Background: BgSmallRestaurant, + Exits: []exit{ { to: ScreenTrinketShop, label: "back to the trinket shop", side: sideLeft, }, }, - } + }) } diff --git a/inc/screen.street.go b/inc/screen.street.go index bf4cb7d..58f4b02 100644 --- a/inc/screen.street.go +++ b/inc/screen.street.go @@ -1,10 +1,10 @@ package inc -func screenStreet() screen { - return screen{ - name: ScreenStreet, - title: "STREET", - background: BgStreet, - onSelector: true, - } +func init() { + ScreenManager.Register(Screen{ + Name: ScreenStreet, + Title: "STREET", + Background: BgStreet, + OnSelector: true, + }) } diff --git a/inc/screen.trinket_shop.go b/inc/screen.trinket_shop.go index fc325ff..f05126a 100644 --- a/inc/screen.trinket_shop.go +++ b/inc/screen.trinket_shop.go @@ -1,12 +1,12 @@ package inc -func screenTrinketShop() screen { - return screen{ - name: ScreenTrinketShop, - title: "CHINATOWN — TRINKET SHOP", - background: BgTrinketShop, - onSelector: true, - exits: []exit{ +func init() { + ScreenManager.Register(Screen{ + Name: ScreenTrinketShop, + Title: "CHINATOWN — TRINKET SHOP", + Background: BgTrinketShop, + OnSelector: true, + Exits: []exit{ { to: ScreenSmallRestaurant, label: "the eating place next door", @@ -20,5 +20,5 @@ func screenTrinketShop() screen { blocked: "Just a shop, as far as the man behind the counter is concerned.", }, }, - } + }) } diff --git a/inc/script.awakening_finale.go b/inc/script.awakening_finale.go index cfc9f75..442d344 100644 --- a/inc/script.awakening_finale.go +++ b/inc/script.awakening_finale.go @@ -4,11 +4,11 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func scriptAwakeningFinale(w *World) inkwell.Script { - return inkwell.Script{ +func init() { + ScriptManager.Register(Script{ Name: ScriptFinale, Actions: inkwell.Seq( - SetMode(w, ModeCutscene), + SetMode(ModeCutscene), inkwell.Wait(0.6), inkwell.Say(Paul, "I'm putting it in. That's all this is."), inkwell.Wait(0.4), @@ -19,5 +19,5 @@ func scriptAwakeningFinale(w *World) inkwell.Script { inkwell.Wait(1.0), inkwell.ShowEnd("REAL WORLD — end of game two"), ), - } + }) } diff --git a/inc/script.manager.go b/inc/script.manager.go index c96a85a..6e9ed36 100644 --- a/inc/script.manager.go +++ b/inc/script.manager.go @@ -4,11 +4,6 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func registerScript(w *World) { - for _, s := range []inkwell.Script{ - scriptTapeInsert(w), - scriptAwakeningFinale(w), - } { - w.G.ScriptManager.Register(s) - } -} +type Script = inkwell.Script + +var ScriptManager = NewManager(func(entity Script) string { return entity.Name }) diff --git a/inc/script.tape_insert.go b/inc/script.tape_insert.go index a5453f1..0dbbb40 100644 --- a/inc/script.tape_insert.go +++ b/inc/script.tape_insert.go @@ -4,16 +4,16 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) -func scriptTapeInsert(w *World) inkwell.Script { - return inkwell.Script{ +func init() { + ScriptManager.Register(Script{ Name: ScriptTapeInsert, Actions: inkwell.Seq( Fn(func(ctx *inkwell.Ctx) { - item := w.TakePending() + item := World.TakePending() if item == "" { return } - w.SetSlot2(item) + World.SetSlot2(item) ctx.Game.Inventory.Remove(item) ctx.Game.State.SetVar(VarArmillaStrip, "SLOT2: READING") }), @@ -21,5 +21,5 @@ func scriptTapeInsert(w *World) inkwell.Script { 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/inc/theme.manager.go b/inc/theme.manager.go index b5bcc3c..768712b 100644 --- a/inc/theme.manager.go +++ b/inc/theme.manager.go @@ -6,6 +6,10 @@ import ( inkwell "git.teletypegames.org/engines/inkwell" ) +type Theme = inkwell.Theme + +var ThemeManager = NewManager(func(entity Theme) string { return entity.Name }) + const ( RealWorld = "realworld-93" NokiaPunk = "nokia-punk" @@ -29,97 +33,6 @@ func RGBA(hex uint32, a uint8) color.Color { } } -var ( - Ink = RGB(0xDCD5C4) - InkDim = RGB(0x8A806B) - Amber = RGB(0xE0A33E) - AmberLo = RGB(0xA8701A) - black = RGB(0x14120E) - panel = RGB(0x0D0B08) - sceneBG = RGB(0x241F18) -) - -func realWorldTheme() inkwell.Theme { - return inkwell.Theme{ - Name: RealWorld, - PanelBG: black, - StatusText: Ink, - FlashText: Amber, - VerbButtonBG: RGB(0x1E1A14), - VerbButtonSelectedBG: AmberLo, - VerbButtonText: Ink, - InventorySlotBG: RGB(0x1E1A14), - InventorySlotSelectedBG: AmberLo, - SpeechBubbleBG: RGBA(0x14120E, 0xDC), - SpeechDefaultText: Ink, - DialogBG: RGBA(0x0D0B08, 0xF0), - DialogBorder: AmberLo, - DialogChoiceBG: RGB(0x1E1A14), - DialogChoiceHover: Amber, - DialogSpeaker: Amber, - DialogText: Ink, - EndCardBG: RGBA(0x000000, 0xFA), - EndCardText: Ink, - CursorColor: Amber, - HotspotOutline: RGB(0x7A6A44), - SceneBackdrop: sceneBG, - TopBarBG: panel, - TopBarText: InkDim, - TopBarAccent: Amber, - ChatLogBG: panel, - ChatLogPrompt: InkDim, - ChatLogResponse: Amber, - ChatLogSystem: RGB(0x6B6353), - CharacterPanelBG: panel, - CharacterPanelBorder: AmberLo, - CharacterPanelTitle: Amber, - } -} - -var ( - Green = RGB(0x3BE86B) - GreenLo = RGB(0x1F7A39) - npBlack = RGB(0x030603) -) - -func nokiaPunkTheme() inkwell.Theme { - return inkwell.Theme{ - Name: NokiaPunk, - PanelBG: npBlack, - StatusText: Green, - FlashText: RGB(0xD8F06A), - VerbButtonBG: RGB(0x08140A), - VerbButtonSelectedBG: GreenLo, - VerbButtonText: Green, - InventorySlotBG: RGB(0x08140A), - InventorySlotSelectedBG: GreenLo, - SpeechBubbleBG: RGBA(0x030603, 0xDC), - SpeechDefaultText: Green, - DialogBG: RGBA(0x030603, 0xF0), - DialogBorder: Green, - DialogChoiceBG: RGB(0x08140A), - DialogChoiceHover: RGB(0xB4FFC8), - DialogSpeaker: RGB(0xB4FFC8), - DialogText: Green, - EndCardBG: RGBA(0x000000, 0xFA), - EndCardText: Green, - CursorColor: Green, - HotspotOutline: GreenLo, - SceneBackdrop: npBlack, - TopBarBG: RGB(0x061006), - TopBarText: GreenLo, - TopBarAccent: Green, - ChatLogBG: RGB(0x040A04), - ChatLogPrompt: GreenLo, - ChatLogResponse: Green, - ChatLogSystem: RGB(0x156030), - CharacterPanelBG: RGB(0x040A04), - CharacterPanelBorder: Green, - CharacterPanelTitle: RGB(0xB4FFC8), - } -} - -func registerTheme(g *inkwell.Game) { - g.ThemeManager.Register(realWorldTheme()) - g.ThemeManager.Register(nokiaPunkTheme()) +func registerTheme() { + registerAll(ThemeManager, World.G.ThemeManager.Register) } diff --git a/inc/theme.nokia_punk.go b/inc/theme.nokia_punk.go new file mode 100644 index 0000000..e909cc4 --- /dev/null +++ b/inc/theme.nokia_punk.go @@ -0,0 +1,44 @@ +package inc + +var ( + Green = RGB(0x3BE86B) + GreenLo = RGB(0x1F7A39) + npBlack = RGB(0x030603) +) + +func init() { + ThemeManager.Register(Theme{ + Name: NokiaPunk, + PanelBG: npBlack, + StatusText: Green, + FlashText: RGB(0xD8F06A), + VerbButtonBG: RGB(0x08140A), + VerbButtonSelectedBG: GreenLo, + VerbButtonText: Green, + InventorySlotBG: RGB(0x08140A), + InventorySlotSelectedBG: GreenLo, + SpeechBubbleBG: RGBA(0x030603, 0xDC), + SpeechDefaultText: Green, + DialogBG: RGBA(0x030603, 0xF0), + DialogBorder: Green, + DialogChoiceBG: RGB(0x08140A), + DialogChoiceHover: RGB(0xB4FFC8), + DialogSpeaker: RGB(0xB4FFC8), + DialogText: Green, + EndCardBG: RGBA(0x000000, 0xFA), + EndCardText: Green, + CursorColor: Green, + HotspotOutline: GreenLo, + SceneBackdrop: npBlack, + TopBarBG: RGB(0x061006), + TopBarText: GreenLo, + TopBarAccent: Green, + ChatLogBG: RGB(0x040A04), + ChatLogPrompt: GreenLo, + ChatLogResponse: Green, + ChatLogSystem: RGB(0x156030), + CharacterPanelBG: RGB(0x040A04), + CharacterPanelBorder: Green, + CharacterPanelTitle: RGB(0xB4FFC8), + }) +} diff --git a/inc/theme.realworld.go b/inc/theme.realworld.go new file mode 100644 index 0000000..e646dea --- /dev/null +++ b/inc/theme.realworld.go @@ -0,0 +1,48 @@ +package inc + +var ( + Ink = RGB(0xDCD5C4) + InkDim = RGB(0x8A806B) + Amber = RGB(0xE0A33E) + AmberLo = RGB(0xA8701A) + black = RGB(0x14120E) + panel = RGB(0x0D0B08) + sceneBG = RGB(0x241F18) +) + +func init() { + ThemeManager.Register(Theme{ + Name: RealWorld, + PanelBG: black, + StatusText: Ink, + FlashText: Amber, + VerbButtonBG: RGB(0x1E1A14), + VerbButtonSelectedBG: AmberLo, + VerbButtonText: Ink, + InventorySlotBG: RGB(0x1E1A14), + InventorySlotSelectedBG: AmberLo, + SpeechBubbleBG: RGBA(0x14120E, 0xDC), + SpeechDefaultText: Ink, + DialogBG: RGBA(0x0D0B08, 0xF0), + DialogBorder: AmberLo, + DialogChoiceBG: RGB(0x1E1A14), + DialogChoiceHover: Amber, + DialogSpeaker: Amber, + DialogText: Ink, + EndCardBG: RGBA(0x000000, 0xFA), + EndCardText: Ink, + CursorColor: Amber, + HotspotOutline: RGB(0x7A6A44), + SceneBackdrop: sceneBG, + TopBarBG: panel, + TopBarText: InkDim, + TopBarAccent: Amber, + ChatLogBG: panel, + ChatLogPrompt: InkDim, + ChatLogResponse: Amber, + ChatLogSystem: RGB(0x6B6353), + CharacterPanelBG: panel, + CharacterPanelBorder: AmberLo, + CharacterPanelTitle: Amber, + }) +} diff --git a/inc/ui.letterbox.go b/inc/ui.letterbox.go index 82bb645..3dc4ba0 100644 --- a/inc/ui.letterbox.go +++ b/inc/ui.letterbox.go @@ -7,26 +7,25 @@ import ( "github.com/hajimehoshi/ebiten/v2/vector" ) -type Letterbox struct { +type letterbox struct { Name string - W *World Bar float64 } -func (l *Letterbox) GetName() string { return l.Name } +func (l *letterbox) GetName() string { return l.Name } -func (l *Letterbox) Tick(ctx *inkwell.UICtx) { - if l.W.Mode() != ModeCutscene || !l.W.TapeWaiting() { +func (l *letterbox) Tick(ctx *inkwell.UICtx) { + if World.Mode() != ModeCutscene || !World.TapeWaiting() { return } if inpututil.IsKeyJustPressed(ebiten.KeySpace) { - l.W.TakeTapeOffer() + World.TakeTapeOffer() } } -func (l *Letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { - if l.W.Mode() != ModeCutscene { +func (l *letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { + if World.Mode() != ModeCutscene { return } g := ctx.Game @@ -40,7 +39,7 @@ func (l *Letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { 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() { + if World.TapeWaiting() { 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/inc/ui.manager.go b/inc/ui.manager.go index dc1ff3f..4a166d7 100644 --- a/inc/ui.manager.go +++ b/inc/ui.manager.go @@ -27,20 +27,17 @@ const ( invGap = 4 ) -func registerUI(w *World) { - g := w.G +func registerUI() { + g := World.G - g.UIManager.Register(&UseWithGuard{ + g.UIManager.Register(&useWithGuard{ Name: "usewith_guard", - W: w, }) - g.UIManager.Register(&ActionPump{ + g.UIManager.Register(&actionPump{ Name: "pump", - W: w, }) g.UIManager.Register(&screenNav{ Name: "screen_nav", - W: w, }) g.UIManager.Register(&windowSizer{ Name: "window", @@ -55,26 +52,24 @@ func registerUI(w *World) { Height: TopBarH, TimeVar: VarArmillaStrip, } - w.SetTopBar(top) - g.UIManager.Register(gated(w, "topbar_gate", top)) + World.SetTopBar(top) + g.UIManager.Register(gated("topbar_gate", top)) - g.UIManager.Register(&Letterbox{ + g.UIManager.Register(&letterbox{ Name: "letterbox", - W: w, Bar: 36, }) g.UIManager.Register(&hudFrame{ Name: "hudframe", - W: w, }) - g.UIManager.Register(gated(w, "status_gate", &inkwell.StatusLine{ + g.UIManager.Register(gated("status_gate", &inkwell.StatusLine{ Name: "status", Y: statusY, Align: inkwell.AlignLeft, ScreenWidth: DividerX, })) - g.UIManager.Register(gated(w, "inventory_gate", &inkwell.InventoryBar{ + g.UIManager.Register(gated("inventory_gate", &inkwell.InventoryBar{ Name: "inventory", Origin: inkwell.Point{ X: Pad + 2, @@ -85,14 +80,12 @@ func registerUI(w *World) { SlotSize: invSlot, Gap: invGap, })) - g.UIManager.Register(&TapeSlots{ + g.UIManager.Register(&tapeSlots{ Name: "tapes", - W: w, Bounds: inkwell.Rect(192, slotsY, 194, slotsH), }) - g.UIManager.Register(&TapeChannel{ + g.UIManager.Register(&tapeChannel{ Name: "channel", - W: w, Bounds: inkwell.Rect(DividerX+2, HUDTop+2, ScreenW-DividerX-4, ScreenH-HUDTop-4), }) @@ -150,14 +143,12 @@ func (s *windowSizer) Tick(ctx *inkwell.UICtx) { type gate struct { Name string - W *World Inner inkwell.Widget } -func gated(w *World, name string, inner inkwell.Widget) inkwell.Widget { +func gated(name string, inner inkwell.Widget) inkwell.Widget { return &gate{ Name: name, - W: w, Inner: inner, } } @@ -165,19 +156,19 @@ func gated(w *World, name string, inner inkwell.Widget) inkwell.Widget { func (n *gate) GetName() string { return n.Name } func (n *gate) Tick(ctx *inkwell.UICtx) { - if n.W.HUDVisible() { + if World.HUDVisible() { n.Inner.Tick(ctx) } } func (n *gate) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { - if n.W.HUDVisible() { + if World.HUDVisible() { n.Inner.Draw(dst, ctx) } } func (n *gate) BlocksClickAt(p inkwell.Point) bool { - if !n.W.HUDVisible() { + if !World.HUDVisible() { return false } b, ok := n.Inner.(interface{ BlocksClickAt(inkwell.Point) bool }) @@ -186,14 +177,13 @@ func (n *gate) BlocksClickAt(p inkwell.Point) bool { type hudFrame struct { Name string - W *World } func (h *hudFrame) GetName() string { return h.Name } func (h *hudFrame) Tick(ctx *inkwell.UICtx) {} func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { - if !h.W.HUDVisible() { + if !World.HUDVisible() { return } th := ctx.Game.Theme() @@ -205,5 +195,5 @@ func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { } func (h *hudFrame) BlocksClickAt(p inkwell.Point) bool { - return h.W.HUDVisible() && p.Y >= HUDTop + return World.HUDVisible() && p.Y >= HUDTop } diff --git a/inc/ui.screen_nav.go b/inc/ui.screen_nav.go index 59d6542..df19f9e 100644 --- a/inc/ui.screen_nav.go +++ b/inc/ui.screen_nav.go @@ -8,14 +8,13 @@ import ( type screenNav struct { Name string - W *World } func (n *screenNav) GetName() string { return n.Name } func (n *screenNav) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} func (n *screenNav) Tick(ctx *inkwell.UICtx) { - if !n.W.HUDVisible() || n.W.Paused() { + if !World.HUDVisible() || World.Paused() { return } step := 0 @@ -29,7 +28,7 @@ func (n *screenNav) Tick(ctx *inkwell.UICtx) { return } if next := n.neighbour(ctx.Game, step); next != "" { - n.W.Do(inkwell.GoTo(next)) + World.Do(inkwell.GoTo(next)) } } @@ -39,7 +38,7 @@ func (n *screenNav) neighbour(g *inkwell.Game, step int) string { return "" } for i, name := range deck { - if name == n.W.Scene() { + if name == World.Scene() { return deck[((i+step)%len(deck)+len(deck))%len(deck)] } } diff --git a/inc/ui.tape_channel.go b/inc/ui.tape_channel.go index eccefaa..6804b52 100644 --- a/inc/ui.tape_channel.go +++ b/inc/ui.tape_channel.go @@ -8,22 +8,21 @@ import ( "github.com/hajimehoshi/ebiten/v2/vector" ) -type TapeChannel struct { +type tapeChannel struct { Name string - W *World Bounds inkwell.Rectangle } -func (t *TapeChannel) GetName() string { return t.Name } +func (t *tapeChannel) GetName() string { return t.Name } -func (t *TapeChannel) Tick(ctx *inkwell.UICtx) {} +func (t *tapeChannel) Tick(ctx *inkwell.UICtx) {} -func (t *TapeChannel) BlocksClickAt(p inkwell.Point) bool { - return t.W.HUDVisible() && t.Bounds.Contains(p) +func (t *tapeChannel) BlocksClickAt(p inkwell.Point) bool { + return World.HUDVisible() && t.Bounds.Contains(p) } -func (t *TapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { - if !t.W.HUDVisible() { +func (t *tapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { + if !World.HUDVisible() { return } g := ctx.Game @@ -84,7 +83,7 @@ func (t *TapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { } } -func (t *TapeChannel) lastSpeaker(g *inkwell.Game, th inkwell.Theme) (string, color.Color) { +func (t *tapeChannel) lastSpeaker(g *inkwell.Game, th inkwell.Theme) (string, color.Color) { msgs := g.Messages() for i := len(msgs) - 1; i >= 0; i-- { m := msgs[i] diff --git a/inc/ui.tape_slots.go b/inc/ui.tape_slots.go index 3ac51b5..a7f0fce 100644 --- a/inc/ui.tape_slots.go +++ b/inc/ui.tape_slots.go @@ -6,26 +6,25 @@ import ( "github.com/hajimehoshi/ebiten/v2/vector" ) -type TapeSlots struct { +type tapeSlots struct { Name string - W *World Bounds inkwell.Rectangle } -func (t *TapeSlots) GetName() string { return t.Name } +func (t *tapeSlots) GetName() string { return t.Name } -func (t *TapeSlots) BlocksClickAt(p inkwell.Point) bool { - return t.W.HUDVisible() && t.Bounds.Contains(p) +func (t *tapeSlots) BlocksClickAt(p inkwell.Point) bool { + return World.HUDVisible() && t.Bounds.Contains(p) } -func (t *TapeSlots) slotRects() (inkwell.Rectangle, inkwell.Rectangle) { +func (t *tapeSlots) slotRects() (inkwell.Rectangle, inkwell.Rectangle) { b := t.Bounds w := (b.W - Pad) / 2 return inkwell.Rect(b.X, b.Y, w, b.H), inkwell.Rect(b.X+w+Pad, b.Y, w, b.H) } -func (t *TapeSlots) Tick(ctx *inkwell.UICtx) { - if !t.W.HUDVisible() { +func (t *tapeSlots) Tick(ctx *inkwell.UICtx) { + if !World.HUDVisible() { return } g := ctx.Game @@ -36,7 +35,7 @@ func (t *TapeSlots) Tick(ctx *inkwell.UICtx) { case r1.Contains(mp): g.SetHoverLabel(TapeDisplayName(Dex)) case r2.Contains(mp): - if s := t.W.Slot2(); s != "" { + if s := World.Slot2(); s != "" { g.SetHoverLabel(itemLabel(g, s)) } else { g.SetHoverLabel("empty tape slot") @@ -57,45 +56,45 @@ func (t *TapeSlots) Tick(ctx *inkwell.UICtx) { if slot2 && sel != "" { g.Inventory.Select("") if IsTapeItem(sel) { - t.W.SetPending(sel) - t.W.Do(inkwell.RunScript(ScriptTapeInsert)) + World.SetPending(sel) + World.Do(inkwell.RunScript(ScriptTapeInsert)) return } - t.W.Do(TapeSay(Dex, "That isn't a tape, Paul. That's an object. There is a difference.")) + World.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(TapeSay(Dex, "The first slot is mine. I'm not moving.")) + World.Do(TapeSay(Dex, "The first slot is mine. I'm not moving.")) return } switch g.SelectedVerb() { case "talk": if !slot2 { - t.W.Do(Paused(t.W, inkwell.RunDialogue(DlgDex))) + World.Do(Paused(inkwell.RunDialogue(DlgDex))) return } - if s := t.W.Slot2(); s != "" { - t.W.Do(Paused(t.W, inkwell.RunDialogue(TapeDialogue(s)))) + if s := World.Slot2(); s != "" { + World.Do(Paused(inkwell.RunDialogue(TapeDialogue(s)))) return } - t.W.Do(TapeSay(Dex, "Empty. Nobody to talk to.")) + World.Do(TapeSay(Dex, "Empty. Nobody to talk to.")) default: if !slot2 { - t.W.Do(TapeSay(Dex, "Me. Four kilobytes of a dead man. Be grateful.")) + World.Do(TapeSay(Dex, "Me. Four kilobytes of a dead man. Be grateful.")) return } - if s := t.W.Slot2(); s != "" { - t.W.Do(TapeSay(Dex, "That is the second slot. Be careful what you let in.")) + if s := World.Slot2(); s != "" { + World.Do(TapeSay(Dex, "That is the second slot. Be careful what you let in.")) return } - t.W.Do(TapeSay(Dex, "The second slot is empty. That's the rarer state.")) + World.Do(TapeSay(Dex, "The second slot is empty. That's the rarer state.")) } } -func (t *TapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { - if !t.W.HUDVisible() { +func (t *tapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { + if !World.HUDVisible() { return } g := ctx.Game @@ -116,7 +115,7 @@ func (t *TapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { } drawSlot(r1, "SLOT 1", TapeDisplayName(Dex), true) - if s := t.W.Slot2(); s != "" { + if s := World.Slot2(); s != "" { drawSlot(r2, "SLOT 2", itemLabel(g, s), true) } else { drawSlot(r2, "SLOT 2", "empty", false) diff --git a/inc/ui.usewith_guard.go b/inc/ui.usewith_guard.go index c2b0bff..2db90e9 100644 --- a/inc/ui.usewith_guard.go +++ b/inc/ui.usewith_guard.go @@ -7,17 +7,16 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -type UseWithGuard struct { +type useWithGuard struct { Name string - W *World } -func (u *UseWithGuard) GetName() string { return u.Name } -func (u *UseWithGuard) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} +func (u *useWithGuard) GetName() string { return u.Name } +func (u *useWithGuard) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} -func (u *UseWithGuard) Tick(ctx *inkwell.UICtx) { +func (u *useWithGuard) Tick(ctx *inkwell.UICtx) { g := ctx.Game - if !u.W.HUDVisible() || !g.Input.LeftClicked() { + if !World.HUDVisible() || !g.Input.LeftClicked() { return } sel := g.Inventory.Selected() @@ -42,7 +41,7 @@ func (u *UseWithGuard) Tick(ctx *inkwell.UICtx) { g.State.NoteTalked(key) n := g.State.Talked(key) - u.W.Do(inkwell.Seq( + World.Do(inkwell.Seq( inkwell.Say(Paul, pick(paulFails, n)), TapeSay(Dex, dexFail(n)), )) diff --git a/inc/world.action.go b/inc/world.action.go index 13c4441..422c9e7 100644 --- a/inc/world.action.go +++ b/inc/world.action.go @@ -5,13 +5,13 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -func (w *World) Do(a inkwell.Action) { +func (w *world) Do(a inkwell.Action) { if a != nil { w.queue = append(w.queue, a) } } -func (w *World) PumpTick(dt float64) { +func (w *world) PumpTick(dt float64) { if w.top != nil { title := w.sceneTitle() @@ -40,7 +40,7 @@ func (w *World) PumpTick(dt float64) { } } -func (w *World) sceneTitle() string { +func (w *world) sceneTitle() string { s, ok := w.G.SceneManager.Get(w.scene) if !ok { return "" @@ -51,14 +51,13 @@ func (w *World) sceneTitle() string { return s.Name } -type ActionPump struct { +type actionPump struct { Name string - W *World } -func (p *ActionPump) GetName() string { return p.Name } -func (p *ActionPump) Tick(ctx *inkwell.UICtx) { p.W.PumpTick(ctx.DT) } -func (p *ActionPump) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} +func (p *actionPump) GetName() string { return p.Name } +func (p *actionPump) Tick(ctx *inkwell.UICtx) { World.PumpTick(ctx.DT) } +func (p *actionPump) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} type fnAction struct{ fn func(*inkwell.Ctx) } @@ -85,25 +84,25 @@ func UseTheme(name string) inkwell.Action { return Fn(func(ctx *inkwell.Ctx) { ctx.Game.UseTheme(name) }) } -func SetMode(w *World, m Mode) inkwell.Action { - return Fn(func(*inkwell.Ctx) { w.SetMode(m) }) +func SetMode(m Mode) inkwell.Action { + return Fn(func(*inkwell.Ctx) { World.SetMode(m) }) } -func EnterScene(w *World, name string) inkwell.Action { +func EnterScene(name string) inkwell.Action { return Fn(func(*inkwell.Ctx) { - if name != w.scene { - w.prev = w.scene + if name != World.scene { + World.prev = World.scene } - w.scene = name + World.scene = name }) } -func Back(w *World) inkwell.Action { +func Back() inkwell.Action { return Fn(func(ctx *inkwell.Ctx) { - if w.prev == "" { + if World.prev == "" { return } - inkwell.GoTo(w.prev).Start().Tick(ctx) + inkwell.GoTo(World.prev).Start().Tick(ctx) }) } @@ -143,14 +142,14 @@ func (r *tapeSayRunner) Tick(ctx *inkwell.Ctx) inkwell.Status { return inkwell.StatusRunning } -func TapeOffer(w *World, line inkwell.Action) inkwell.Action { +func TapeOffer(line inkwell.Action) inkwell.Action { return Fn(func(*inkwell.Ctx) { - w.tapeLine = line - w.tapeWaiting = true + World.tapeLine = line + World.tapeWaiting = true }) } -func (w *World) TakeTapeOffer() { +func (w *world) TakeTapeOffer() { if !w.tapeWaiting { return } @@ -160,15 +159,13 @@ func (w *World) TakeTapeOffer() { w.Do(line) } -func Paused(w *World, inner inkwell.Action) inkwell.Action { +func Paused(inner inkwell.Action) inkwell.Action { return &pausedAction{ - w: w, inner: inner, } } type pausedAction struct { - w *World inner inkwell.Action } @@ -188,11 +185,11 @@ type pausedRunner struct { func (r *pausedRunner) Tick(ctx *inkwell.Ctx) inkwell.Status { if !r.started { r.started = true - r.spec.w.paused = true + World.paused = true } s := r.inner.Tick(ctx) if s != inkwell.StatusRunning { - r.spec.w.paused = false + World.paused = false } return s } diff --git a/inc/world.manager.go b/inc/world.manager.go index 03166f3..151c52d 100644 --- a/inc/world.manager.go +++ b/inc/world.manager.go @@ -12,7 +12,7 @@ const ( ModeMenu Mode = "menu" ) -type World struct { +type world struct { G *inkwell.Game mode Mode @@ -29,42 +29,51 @@ type World struct { tapeLine inkwell.Action } -func newWorld(g *inkwell.Game) *World { - return &World{ - G: g, - mode: ModePlay, - } +var World = &world{} + +func (w *world) attach(g *inkwell.Game) { + w.G = g + w.mode = ModePlay + w.scene = "" + w.prev = "" + w.paused = false + w.pending = "" + w.queue = nil + w.running = nil + w.top = nil + w.tapeWaiting = false + w.tapeLine = nil } -func (w *World) Mode() Mode { return w.mode } +func (w *world) Mode() Mode { return w.mode } -func (w *World) SetMode(m Mode) { w.mode = m } +func (w *world) SetMode(m Mode) { w.mode = m } -func (w *World) HUDVisible() bool { return w.mode == ModePlay } +func (w *world) HUDVisible() bool { return w.mode == ModePlay } -func (w *World) Scene() string { return w.scene } +func (w *world) Scene() string { return w.scene } -func (w *World) Previous() string { return w.prev } +func (w *world) Previous() string { return w.prev } -func (w *World) Paused() bool { return w.paused } +func (w *world) Paused() bool { return w.paused } -func (w *World) SetTopBar(t *inkwell.TopBar) { w.top = t } +func (w *world) SetTopBar(t *inkwell.TopBar) { w.top = t } -func (w *World) Slot2() string { +func (w *world) Slot2() string { if v, ok := w.G.State.Var(VarSlot2).(string); ok { return v } return "" } -func (w *World) SetSlot2(item string) { w.G.State.SetVar(VarSlot2, item) } +func (w *world) SetSlot2(item string) { w.G.State.SetVar(VarSlot2, item) } -func (w *World) SetPending(item string) { w.pending = item } +func (w *world) SetPending(item string) { w.pending = item } -func (w *World) TakePending() string { +func (w *world) TakePending() string { item := w.pending w.pending = "" return item } -func (w *World) TapeWaiting() bool { return w.tapeWaiting } +func (w *world) TapeWaiting() bool { return w.tapeWaiting }