register logic

This commit is contained in:
2026-08-29 23:59:56 +02:00
parent ddea3b0893
commit 94649a38fe
83 changed files with 830 additions and 699 deletions
+126 -19
View File
@@ -22,8 +22,8 @@ one-to-one.
### No tests ### No tests
Do not write tests, and do not add test cases to the ones that already exist. Do not write tests and do not add test files. Correctness is checked by running
Correctness is checked by running the game. the game.
### Struct literals are always multi-line ### 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`, `character`, `dialog`, `script`, `world`, `ui`, `theme`, `names`, `boot`,
`content`. `content`.
- `[category].manager.go` is the file that ties a category together — its - `[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 - 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 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/boot.manager.go wiring; New(Opts) builds the game
inc/names.manager.go entity names and world-state keys 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.manager.go unsaved runtime state
inc/world.action.go custom actions and the action pump inc/world.action.go custom actions and the action pump
inc/ui.manager.go HUD layout and widget registration 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/item.*.go inventory
inc/dialog.*.go dialogue trees inc/dialog.*.go dialogue trees
inc/script.*.go named action sequences 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.exit.go the connections between screens
inc/screen.*.go one file per screen 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 ## Naming
One package means one namespace, so an entity's constructor carries its One package means one namespace, so an entity's constructor carries its
category as a prefix: category as a prefix:
```go ```go
screenAlley() backgroundAlley() screenFloor screenBuild registerScreen fillSelectorPins
itemMysteryTape() characterMysteryTape()
dialogDexTalk() scriptTapeInsert()
``` ```
Category-level functions follow the same shape: `registerScreen`, Entities themselves need no name at all — they are anonymous literals inside
`registerBackground`, `registerItem`, and so on. Only `New` and `Opts` are their file's `init()`, and the file name says which one it is.
exported — they are what `main.go` needs, and nothing else leaves the package.
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 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" wiki, the concept-art deck and the beat tables all count screens. "Scene"
@@ -112,14 +221,12 @@ other way round.
## Adding a screen ## Adding a screen
1. `inc/screen.<name>.go` `screen<Name>() screen` 1. Constants in `inc/names.manager.go`: `Screen<Name>`, `Bg<Name>`
2. `inc/background.<name>.go``background<Name>() inkwell.Asset` 2. `inc/screen.<name>.go`an `init()` registering a `Screen`
3. One line in `screenDeck` (`inc/screen.manager.go`) 3. `inc/background.<name>.go` — an `init()` registering a `Background`
4. One line in `registerBackground` (`inc/background.manager.go`) 4. A 640×380 PNG in `assets/bg/`
5. Constants in `inc/names.manager.go`: `Screen<Name>`, `Bg<Name>`
6. A 640×380 PNG in `assets/bg/`
Nothing else moves. Nothing else moves. There is no list to update.
## Commands ## Commands
+68 -19
View File
@@ -48,7 +48,6 @@ go mod edit -dropreplace git.teletypegames.org/engines/inkwell
```bash ```bash
make build # native binary into bin/ make build # native binary into bin/
make test # headless: validates content and HUD layout
make wasm # dist/game.wasm + wasm_exec.js make wasm # dist/game.wasm + wasm_exec.js
make export VERSION=0.1 # zipped HTML/WASM bundle make export VERSION=0.1 # zipped HTML/WASM bundle
make binaries VERSION=0.1 # win-x86, win-x64, linux-x64 zips 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 | | left click | run the selected verb |
| right click | verb coin (Look / Use / Talk / Take) | | 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 | | `F1` | toggle hotspot outlines |
| `SPACE` | during a cutscene: let the tape speak, if it offered | | `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 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 are a reviewing tool on top of that, walking every screen in turn. The walk is
walk is inert during a cutscene, a menu, or a stopped world, so it can never cut inert during a cutscene, a menu, or a stopped world, so it can never cut
across an authored beat. across an authored beat.
Screenshots: `EBITEN_SCREENSHOT_KEY=q go run .`, then press `q` in the window. Screenshots: `EBITEN_SCREENSHOT_KEY=q go run .`, then press `q` in the window.
@@ -154,12 +153,14 @@ licence.
The game is one flat package, `inc`. There are no subdirectories: a file's 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 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 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 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/names.manager.go entity names and world-state keys
inc/theme.manager.go realworld-93 + nokia-punk inc/theme.*.go realworld-93 + nokia-punk
inc/world.*.go unsaved runtime state, custom actions, action pump inc/world.*.go unsaved runtime state, custom actions, action pump
inc/ui.*.go HUD: layout, custom widgets, coloured text inc/ui.*.go HUD: layout, custom widgets, coloured text
inc/<kind>.<name>.go one file per registered entity, by kind inc/<kind>.<name>.go one file per registered entity, by kind
@@ -167,7 +168,7 @@ inc/boot.manager.go wiring
``` ```
Nothing is enforced by the compiler any more, so the layering is a rule the 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. 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 screen.*.go one file per screen: screen.alley.go, … + screen.exit.go
``` ```
Adding a screen means adding `screen.<name>.go` and `background.<name>.go`, and Adding a screen means adding `screen.<name>.go` and `background.<name>.go`.
one line in each category's manager list. Nothing else moves. Nothing else moves.
Since everything shares one namespace, an entity's constructor carries its Every category owns a manager, and they are all the same generic type,
category: `screenAlley()` is the screen, `backgroundAlley()` the painting behind `Manager[T]` — one slice in registration order, one `map[string]int` beside it,
it, `itemMysteryTape()` the prop and `characterMysteryTape()` the voice on 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 **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 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 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`. 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 The graph stays machine-readable: exit hotspots are named `exit:<target>`, so
screen has a way out, and every screen can be walked to from Paul's shop through the connections can be read straight back out of the registered screens.
the exits alone — the arrow keys do not count.
## Engine workarounds ## 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 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 becomes a memory of what you already tried. Needs a thin override over
`DialogBox`. `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. police-tip flag that beat 2 will eventually set.
- **The render has only been inspected once, by the author of this repo.** - **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 Geometry is derived from one font cell and one screen size, but this
take a screenshot (both synthetic keystrokes and screen capture are blocked by environment cannot take a screenshot (both synthetic keystrokes and screen
macOS privacy permissions), so every visual judgement has to come from you. capture are blocked by macOS privacy permissions), so every visual judgement
has to come from you.
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundAlley() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgAlley, Name: BgAlley,
Path: "assets/bg/alley.png", Path: "assets/bg/alley.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundBbsTerminal() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgBBSTerminal, Name: BgBBSTerminal,
Path: "assets/bg/bbs_terminal.png", Path: "assets/bg/bbs_terminal.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundBvkBranch() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgBVKBranch, Name: BgBVKBranch,
Path: "assets/bg/bvk_branch.png", Path: "assets/bg/bvk_branch.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundColumbarium() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgColumbarium, Name: BgColumbarium,
Path: "assets/bg/columbarium.png", Path: "assets/bg/columbarium.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundCuratorShop() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgCuratorShop, Name: BgCuratorShop,
Path: "assets/bg/curator_shop.png", Path: "assets/bg/curator_shop.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundHackerspace() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgHackerspace, Name: BgHackerspace,
Path: "assets/bg/hackerspace.png", Path: "assets/bg/hackerspace.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundHospital() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgHospital, Name: BgHospital,
Path: "assets/bg/hospital.png", Path: "assets/bg/hospital.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundIceCreamShop() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgIceCreamShop, Name: BgIceCreamShop,
Path: "assets/bg/ice_cream_shop.png", Path: "assets/bg/ice_cream_shop.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -30
View File
@@ -4,33 +4,6 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func registerBackground(w *World) { type Background = inkwell.Asset
for _, a := range []inkwell.Asset{
backgroundSelector(), var BackgroundManager = NewManager(func(entity Background) string { return entity.Name })
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)
}
}
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundNoodleHouse() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgNoodleHouse, Name: BgNoodleHouse,
Path: "assets/bg/noodle_house.png", Path: "assets/bg/noodle_house.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundNormanApartment() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgNormanApartment, Name: BgNormanApartment,
Path: "assets/bg/norman_apartment.png", Path: "assets/bg/norman_apartment.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundPaulShop() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgPaulShop, Name: BgPaulShop,
Path: "assets/bg/paul_shop.png", Path: "assets/bg/paul_shop.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundPoliceStation() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgPoliceStation, Name: BgPoliceStation,
Path: "assets/bg/police_station.png", Path: "assets/bg/police_station.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundPublicBBS() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgPublicBBS, Name: BgPublicBBS,
Path: "assets/bg/public_bbs.png", Path: "assets/bg/public_bbs.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundRooftopHideout() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgRooftopHideout, Name: BgRooftopHideout,
Path: "assets/bg/rooftop_hideout.png", Path: "assets/bg/rooftop_hideout.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundSamizdatPress() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgSamizdatPress, Name: BgSamizdatPress,
Path: "assets/bg/samizdat_press.png", Path: "assets/bg/samizdat_press.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundScrapMarket() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgScrapMarket, Name: BgScrapMarket,
Path: "assets/bg/scrap_market.png", Path: "assets/bg/scrap_market.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundSecretClub() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgSecretClub, Name: BgSecretClub,
Path: "assets/bg/secret_club.png", Path: "assets/bg/secret_club.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundSecretLab() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgSecretLab, Name: BgSecretLab,
Path: "assets/bg/secret_lab.png", Path: "assets/bg/secret_lab.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundSelector() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgSelector, Name: BgSelector,
Path: "assets/bg/selector.png", Path: "assets/bg/selector.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundServerFarm() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgServerFarm, Name: BgServerFarm,
Path: "assets/bg/server_farm.png", Path: "assets/bg/server_farm.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundShowroom() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgShowroom, Name: BgShowroom,
Path: "assets/bg/showroom.png", Path: "assets/bg/showroom.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundSmallRestaurant() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgSmallRestaurant, Name: BgSmallRestaurant,
Path: "assets/bg/small_restaurant.png", Path: "assets/bg/small_restaurant.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundStreet() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgStreet, Name: BgStreet,
Path: "assets/bg/street.png", Path: "assets/bg/street.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+3 -3
View File
@@ -4,10 +4,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func backgroundTrinketShop() inkwell.Asset { func init() {
return inkwell.Asset{ BackgroundManager.Register(Background{
Name: BgTrinketShop, Name: BgTrinketShop,
Path: "assets/bg/trinket_shop.png", Path: "assets/bg/trinket_shop.png",
Kind: inkwell.AssetImage, Kind: inkwell.AssetImage,
} })
} }
+7 -7
View File
@@ -18,21 +18,21 @@ func New(o Opts) *inkwell.Game {
g := inkwell.NewGame("Real World", ScreenW, ScreenH) g := inkwell.NewGame("Real World", ScreenW, ScreenH)
g.MaxLogLines = 64 g.MaxLogLines = 64
w := newWorld(g) World.attach(g)
registerTheme(g) registerTheme()
registerContent(w) registerContent()
g.UseTheme(RealWorld) g.UseTheme(RealWorld)
registerUI(w) registerUI()
g.StartAt(start) g.StartAt(start)
g.OnStart(inkwell.Seq(bootOpening(w, start, o.Finale)...)) g.OnStart(inkwell.Seq(bootOpening(start, o.Finale)...))
return g return g
} }
func bootOpening(w *World, start string, finale bool) []inkwell.Action { func bootOpening(start string, finale bool) []inkwell.Action {
acts := []inkwell.Action{ acts := []inkwell.Action{
EnterScene(w, start), EnterScene(start),
inkwell.SetFlag(FlagPoliceTip), inkwell.SetFlag(FlagPoliceTip),
inkwell.Say(Paul, "Back alley. Just like the clerk said."), 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?"), TapeSay(Dex, "A government office tipped you off to remove something from a scene. That doesn't strike you as odd?"),
+3 -7
View File
@@ -1,12 +1,8 @@
package inc package inc
import ( func init() {
inkwell "git.teletypegames.org/engines/inkwell" CharacterManager.Register(Character{
)
func characterDex() inkwell.Character {
return inkwell.Character{
Name: Dex, Name: Dex,
SpeechColor: Amber, SpeechColor: Amber,
} })
} }
+3 -10
View File
@@ -4,13 +4,6 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func registerCharacter(w *World) { type Character = inkwell.Character
for _, c := range []inkwell.Character{
characterPaul(), var CharacterManager = NewManager(func(entity Character) string { return entity.Name })
characterDex(),
characterMysteryTape(),
characterSupportTape(),
} {
w.G.CharacterManager.Register(c)
}
}
+3 -7
View File
@@ -1,12 +1,8 @@
package inc package inc
import ( func init() {
inkwell "git.teletypegames.org/engines/inkwell" CharacterManager.Register(Character{
)
func characterMysteryTape() inkwell.Character {
return inkwell.Character{
Name: TapeMystery, Name: TapeMystery,
SpeechColor: RGB(0xB9A98A), SpeechColor: RGB(0xB9A98A),
} })
} }
+3 -3
View File
@@ -4,8 +4,8 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func characterPaul() inkwell.Character { func init() {
return inkwell.Character{ CharacterManager.Register(Character{
Name: Paul, Name: Paul,
Speed: 96, Speed: 96,
W: 28, W: 28,
@@ -15,5 +15,5 @@ func characterPaul() inkwell.Character {
Y: 232, Y: 232,
}, },
SpeechColor: Ink, SpeechColor: Ink,
} })
} }
+3 -7
View File
@@ -1,12 +1,8 @@
package inc package inc
import ( func init() {
inkwell "git.teletypegames.org/engines/inkwell" CharacterManager.Register(Character{
)
func characterSupportTape() inkwell.Character {
return inkwell.Character{
Name: TapeSupport, Name: TapeSupport,
SpeechColor: RGB(0xE8C86A), SpeechColor: RGB(0xE8C86A),
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func registerContent(w *World) { func registerContent() {
registerBackground(w) registerAll(BackgroundManager, World.G.AssetManager.Register)
registerCharacter(w) registerAll(CharacterManager, World.G.CharacterManager.Register)
registerItem(w) registerAll(ItemManager, World.G.ItemManager.Register)
registerDialog(w) registerAll(DialogManager, World.G.DialogueManager.Register)
registerScript(w) registerAll(ScriptManager, World.G.ScriptManager.Register)
registerScreen(w) registerScreen()
} }
+3 -3
View File
@@ -4,8 +4,8 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func dialogDexTalk() inkwell.Dialogue { func init() {
return inkwell.Dialogue{ DialogManager.Register(Dialog{
Name: DlgDex, Name: DlgDex,
Start: "root", Start: "root",
Nodes: []inkwell.DialogueNode{{ Nodes: []inkwell.DialogueNode{{
@@ -52,5 +52,5 @@ func dialogDexTalk() inkwell.Dialogue {
}, },
}, },
}}, }},
} })
} }
+3 -8
View File
@@ -4,11 +4,6 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func registerDialog(w *World) { type Dialog = inkwell.Dialogue
for _, d := range []inkwell.Dialogue{
dialogDexTalk(), var DialogManager = NewManager(func(entity Dialog) string { return entity.Name })
dialogMysteryTapeSilent(),
} {
w.G.DialogueManager.Register(d)
}
}
+3 -3
View File
@@ -4,8 +4,8 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func dialogMysteryTapeSilent() inkwell.Dialogue { func init() {
return inkwell.Dialogue{ DialogManager.Register(Dialog{
Name: DlgMysteryTape, Name: DlgMysteryTape,
Start: "root", Start: "root",
Nodes: []inkwell.DialogueNode{{ Nodes: []inkwell.DialogueNode{{
@@ -28,5 +28,5 @@ func dialogMysteryTapeSilent() inkwell.Dialogue {
}, },
}, },
}}, }},
} })
} }
+3 -3
View File
@@ -4,8 +4,8 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func itemBlackMarketArmilla() inkwell.Item { func init() {
return inkwell.Item{ ItemManager.Register(Item{
Name: ItemBlackArmilla, Name: ItemBlackArmilla,
Description: "black-market Armilla", Description: "black-market Armilla",
OnUseSelf: inkwell.Seq( OnUseSelf: inkwell.Seq(
@@ -18,5 +18,5 @@ func itemBlackMarketArmilla() inkwell.Item {
TapeSay(Dex, "Nothing. Which is what I'd charge for it."), TapeSay(Dex, "Nothing. Which is what I'd charge for it."),
), ),
}, },
} })
} }
+3 -9
View File
@@ -4,12 +4,6 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func registerItem(w *World) { type Item = inkwell.Item
for _, i := range []inkwell.Item{
itemNoodleLetter(), var ItemManager = NewManager(func(entity Item) string { return entity.Name })
itemBlackMarketArmilla(),
itemMysteryTape(),
} {
w.G.ItemManager.Register(i)
}
}
+3 -3
View File
@@ -4,13 +4,13 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func itemMysteryTape() inkwell.Item { func init() {
return inkwell.Item{ ItemManager.Register(Item{
Name: ItemMysteryTape, Name: ItemMysteryTape,
Description: "unmarked Personal Tape", Description: "unmarked Personal Tape",
OnUseSelf: inkwell.Seq( OnUseSelf: inkwell.Seq(
inkwell.Say(Paul, "Password-locked. Of course."), inkwell.Say(Paul, "Password-locked. Of course."),
TapeSay(Dex, "Put it in the second slot if you care that much. I did warn you."), TapeSay(Dex, "Put it in the second slot if you care that much. I did warn you."),
), ),
} })
} }
+3 -3
View File
@@ -4,13 +4,13 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func itemNoodleLetter() inkwell.Item { func init() {
return inkwell.Item{ ItemManager.Register(Item{
Name: ItemNoodleLetter, Name: ItemNoodleLetter,
Description: "Noodle's letter", Description: "Noodle's letter",
OnUseSelf: inkwell.Seq( OnUseSelf: inkwell.Seq(
inkwell.Say(Paul, "“Come out, Paul. Not a phone conversation.” That's all it says."), 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."), TapeSay(Dex, "And now you're here. And he isn't."),
), ),
} })
} }
+17
View File
@@ -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
)
+43
View File
@@ -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)
}
}
+10 -10
View File
@@ -4,19 +4,19 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func screenAlley() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenAlley, Name: ScreenAlley,
title: "ALLEY — BEHIND NOODLE'S HOUSE", Title: "ALLEY — BEHIND NOODLE'S HOUSE",
background: BgAlley, Background: BgAlley,
exits: []exit{ Exits: []exit{
{ {
to: ScreenNoodleHouse, to: ScreenNoodleHouse,
label: "back out to the street", label: "back out to the street",
side: sideLeft, side: sideLeft,
}, },
}, },
actors: []inkwell.SceneActor{ Actors: []inkwell.SceneActor{
{ {
CharacterName: Paul, CharacterName: Paul,
At: inkwell.Point{ At: inkwell.Point{
@@ -25,9 +25,9 @@ func screenAlley() screen {
}, },
}, },
}, },
onEnter: setVarIfEmpty(VarArmillaStrip, "SRP: 1 tape"), OnEnter: setVarIfEmpty(VarArmillaStrip, "SRP: 1 tape"),
hotspots: []inkwell.Hotspot{hidingPlace(), backDoor(), bin(), fireEscape()}, Hotspots: []inkwell.Hotspot{hidingPlace(), backDoor(), bin(), fireEscape()},
} })
} }
func hidingPlace() inkwell.Hotspot { func hidingPlace() inkwell.Hotspot {
+7 -7
View File
@@ -1,15 +1,15 @@
package inc package inc
func screenBbsTerminal() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenBBSTerminal, Name: ScreenBBSTerminal,
title: "TERMINAL — BBS ACCESS POINT", Title: "TERMINAL — BBS ACCESS POINT",
background: BgBBSTerminal, Background: BgBBSTerminal,
exits: []exit{ Exits: []exit{
{ {
label: "step back from the terminal", label: "step back from the terminal",
side: sideNear, side: sideNear,
}, },
}, },
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenBvkBranch() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenBVKBranch, Name: ScreenBVKBranch,
title: "BVK — SAN FRANCISCO BRANCH", Title: "BVK — SAN FRANCISCO BRANCH",
background: BgBVKBranch, Background: BgBVKBranch,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenColumbarium() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenColumbarium, Name: ScreenColumbarium,
title: "COLUMBARIUM — DEX'S MEMORIAL", Title: "COLUMBARIUM — DEX'S MEMORIAL",
background: BgColumbarium, Background: BgColumbarium,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenCuratorShop() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenCuratorShop, Name: ScreenCuratorShop,
title: "CURATOR SERVICE — THE MASTER'S WORKSHOP", Title: "CURATOR SERVICE — THE MASTER'S WORKSHOP",
background: BgCuratorShop, Background: BgCuratorShop,
onSelector: true, OnSelector: true,
} })
} }
+2 -2
View File
@@ -52,10 +52,10 @@ func exitName(to string) string {
return "exit:" + to 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) var travel inkwell.Action = inkwell.GoTo(e.to)
if e.to == "" { if e.to == "" {
travel = Back(w) travel = Back()
} }
if e.needs != "" { if e.needs != "" {
travel = inkwell.If(inkwell.Flag(e.needs), travel, inkwell.Say(Paul, e.blocked)) travel = inkwell.If(inkwell.Flag(e.needs), travel, inkwell.Say(Paul, e.blocked))
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenHackerspace() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenHackerspace, Name: ScreenHackerspace,
title: "HACKERSPACE", Title: "HACKERSPACE",
background: BgHackerspace, Background: BgHackerspace,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenHospital() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenHospital, Name: ScreenHospital,
title: "HOSPITAL — PSYCHIATRIC WING", Title: "HOSPITAL — PSYCHIATRIC WING",
background: BgHospital, Background: BgHospital,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenIceCreamShop() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenIceCreamShop, Name: ScreenIceCreamShop,
title: "ICE CREAM SHOP — WHERE THE BAR WAS", Title: "ICE CREAM SHOP — WHERE THE BAR WAS",
background: BgIceCreamShop, Background: BgIceCreamShop,
onSelector: true, OnSelector: true,
} })
} }
+30 -57
View File
@@ -4,55 +4,28 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
type screen struct { type Screen struct {
name string Name string
title string Title string
background string Background string
exits []exit Exits []exit
onSelector bool OnSelector bool
hotspots []inkwell.Hotspot Hotspots []inkwell.Hotspot
actors []inkwell.SceneActor Actors []inkwell.SceneActor
walkboxes []inkwell.Polygon Walkboxes []inkwell.Polygon
onEnter inkwell.Action OnEnter inkwell.Action
} }
var screenDeck = []func() screen{ var ScreenManager = NewManager(func(entity Screen) string { return entity.Name })
screenPaulShop,
screenNoodleHouse,
screenAlley,
screenNormanApartment,
screenPoliceStation,
screenHackerspace,
screenIceCreamShop,
screenTrinketShop,
screenSmallRestaurant,
screenSecretClub,
screenBbsTerminal,
screenStreet,
screenHospital,
screenServerFarm,
screenSecretLab,
screenRooftopHideout,
screenPublicBBS,
screenBvkBranch,
screenCuratorShop,
screenScrapMarket,
screenSamizdatPress,
screenShowroom,
screenColumbarium,
}
func registerScreen(w *World) { func registerScreen() {
screens := make([]screen, 0, len(screenDeck)+1) fillSelectorPins()
for _, f := range screenDeck { registerAll(ScreenManager, func(entity Screen) {
screens = append(screens, f()) World.G.SceneManager.Register(screenBuild(entity))
} })
for _, s := range append([]screen{screenSelector(screens)}, screens...) {
w.G.SceneManager.Register(screenBuild(w, s))
}
} }
var screenFloor = []inkwell.Polygon{ var screenFloor = []inkwell.Polygon{
@@ -74,18 +47,18 @@ var screenFloor = []inkwell.Polygon{
), ),
} }
func screenBuild(w *World, s screen) inkwell.Scene { func screenBuild(s Screen) inkwell.Scene {
exits := s.exits exits := s.Exits
if s.onSelector { if s.OnSelector {
exits = append(exits, toSelector()) exits = append(exits, toSelector())
} }
hotspots := make([]inkwell.Hotspot, 0, len(exits)+len(s.hotspots)) hotspots := make([]inkwell.Hotspot, 0, len(exits)+len(s.Hotspots))
hotspots = append(hotspots, s.hotspots...) hotspots = append(hotspots, s.Hotspots...)
for _, e := range exits { for _, e := range exits {
hotspots = append(hotspots, e.hotspot(w)) hotspots = append(hotspots, e.hotspot())
} }
actors := s.actors actors := s.Actors
if actors == nil { if actors == nil {
actors = []inkwell.SceneActor{ actors = []inkwell.SceneActor{
{ {
@@ -97,19 +70,19 @@ func screenBuild(w *World, s screen) inkwell.Scene {
}, },
} }
} }
walkboxes := s.walkboxes walkboxes := s.Walkboxes
if walkboxes == nil { if walkboxes == nil {
walkboxes = screenFloor walkboxes = screenFloor
} }
onEnter := inkwell.Action(EnterScene(w, s.name)) onEnter := inkwell.Action(EnterScene(s.Name))
if s.onEnter != nil { if s.OnEnter != nil {
onEnter = inkwell.Seq(onEnter, s.onEnter) onEnter = inkwell.Seq(onEnter, s.OnEnter)
} }
return inkwell.Scene{ return inkwell.Scene{
Name: s.name, Name: s.Name,
Title: s.title, Title: s.Title,
Background: s.background, Background: s.Background,
Actors: actors, Actors: actors,
Walkboxes: walkboxes, Walkboxes: walkboxes,
Hotspots: hotspots, Hotspots: hotspots,
+8 -8
View File
@@ -1,17 +1,17 @@
package inc package inc
func screenNoodleHouse() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenNoodleHouse, Name: ScreenNoodleHouse,
title: "NOODLE'S HOUSE — SEALED", Title: "NOODLE'S HOUSE — SEALED",
background: BgNoodleHouse, Background: BgNoodleHouse,
onSelector: true, OnSelector: true,
exits: []exit{ Exits: []exit{
{ {
to: ScreenAlley, to: ScreenAlley,
label: "the alley behind the house", label: "the alley behind the house",
side: sideRight, side: sideRight,
}, },
}, },
} })
} }
+8 -8
View File
@@ -1,12 +1,12 @@
package inc package inc
func screenNormanApartment() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenNormanApartment, Name: ScreenNormanApartment,
title: "NORMAN'S APARTMENT", Title: "NORMAN'S APARTMENT",
background: BgNormanApartment, Background: BgNormanApartment,
onSelector: true, OnSelector: true,
exits: []exit{ Exits: []exit{
{ {
to: ScreenRooftopHideout, to: ScreenRooftopHideout,
label: "the stairs up to the roof", label: "the stairs up to the roof",
@@ -18,5 +18,5 @@ func screenNormanApartment() screen {
side: sideRight, side: sideRight,
}, },
}, },
} })
} }
+7 -7
View File
@@ -1,16 +1,16 @@
package inc package inc
func screenPaulShop() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenPaulShop, Name: ScreenPaulShop,
title: "PAUL'S SHOP — JUNK AND GARAGE", Title: "PAUL'S SHOP — JUNK AND GARAGE",
background: BgPaulShop, Background: BgPaulShop,
exits: []exit{ Exits: []exit{
{ {
to: ScreenNoodleHouse, to: ScreenNoodleHouse,
label: "the bus to San Francisco", label: "the bus to San Francisco",
side: sideNear, side: sideNear,
}, },
}, },
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenPoliceStation() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenPoliceStation, Name: ScreenPoliceStation,
title: "SFPD — STATION AND HOLDING", Title: "SFPD — STATION AND HOLDING",
background: BgPoliceStation, Background: BgPoliceStation,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenPublicBBS() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenPublicBBS, Name: ScreenPublicBBS,
title: "PUBLIC BBS TERMINAL", Title: "PUBLIC BBS TERMINAL",
background: BgPublicBBS, Background: BgPublicBBS,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,11 +1,11 @@
package inc package inc
func screenRooftopHideout() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenRooftopHideout, Name: ScreenRooftopHideout,
title: "NORMAN'S ROOFTOP HIDEOUT", Title: "NORMAN'S ROOFTOP HIDEOUT",
background: BgRooftopHideout, Background: BgRooftopHideout,
exits: []exit{ Exits: []exit{
{ {
to: ScreenNormanApartment, to: ScreenNormanApartment,
label: "back down into the flat", label: "back down into the flat",
@@ -17,5 +17,5 @@ func screenRooftopHideout() screen {
side: sideRight, side: sideRight,
}, },
}, },
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenSamizdatPress() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenSamizdatPress, Name: ScreenSamizdatPress,
title: "SAMIZDAT PRINTING HOUSE", Title: "SAMIZDAT PRINTING HOUSE",
background: BgSamizdatPress, Background: BgSamizdatPress,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenScrapMarket() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenScrapMarket, Name: ScreenScrapMarket,
title: "SCRAP MARKET", Title: "SCRAP MARKET",
background: BgScrapMarket, Background: BgScrapMarket,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,11 +1,11 @@
package inc package inc
func screenSecretClub() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenSecretClub, Name: ScreenSecretClub,
title: "SECRET CLUB — THE UNDERWATER SUN", Title: "SECRET CLUB — THE UNDERWATER SUN",
background: BgSecretClub, Background: BgSecretClub,
exits: []exit{ Exits: []exit{
{ {
to: ScreenBBSTerminal, to: ScreenBBSTerminal,
label: "the terminal in the corner", label: "the terminal in the corner",
@@ -17,5 +17,5 @@ func screenSecretClub() screen {
side: sideNear, side: sideNear,
}, },
}, },
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenSecretLab() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenSecretLab, Name: ScreenSecretLab,
title: "SECRET RESEARCH LABORATORY", Title: "SECRET RESEARCH LABORATORY",
background: BgSecretLab, Background: BgSecretLab,
onSelector: true, OnSelector: true,
} })
} }
+21 -13
View File
@@ -6,26 +6,34 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" 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 var exits []exit
for _, s := range rest { for _, entity := range ScreenManager.GetAll() {
if !s.onSelector { if !entity.OnSelector {
continue continue
} }
exits = append(exits, exit{ exits = append(exits, exit{
to: s.name, to: entity.Name,
label: pinLabel(s.title), label: pinLabel(entity.Title),
area: pin(len(exits)), area: pin(len(exits)),
}) })
} }
return screen{ selector.Exits = exits
name: ScreenSelector, ScreenManager.Register(selector)
title: "SAN FRANCISCO",
background: BgSelector,
exits: exits,
actors: []inkwell.SceneActor{},
walkboxes: []inkwell.Polygon{},
}
} }
const ( const (
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenServerFarm() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenServerFarm, Name: ScreenServerFarm,
title: "SERVER FARM", Title: "SERVER FARM",
background: BgServerFarm, Background: BgServerFarm,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenShowroom() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenShowroom, Name: ScreenShowroom,
title: "NEUMATRONIC SHOWROOM", Title: "NEUMATRONIC SHOWROOM",
background: BgShowroom, Background: BgShowroom,
onSelector: true, OnSelector: true,
} })
} }
+7 -7
View File
@@ -1,16 +1,16 @@
package inc package inc
func screenSmallRestaurant() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenSmallRestaurant, Name: ScreenSmallRestaurant,
title: "SMALL RESTAURANT — NEXT DOOR", Title: "SMALL RESTAURANT — NEXT DOOR",
background: BgSmallRestaurant, Background: BgSmallRestaurant,
exits: []exit{ Exits: []exit{
{ {
to: ScreenTrinketShop, to: ScreenTrinketShop,
label: "back to the trinket shop", label: "back to the trinket shop",
side: sideLeft, side: sideLeft,
}, },
}, },
} })
} }
+7 -7
View File
@@ -1,10 +1,10 @@
package inc package inc
func screenStreet() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenStreet, Name: ScreenStreet,
title: "STREET", Title: "STREET",
background: BgStreet, Background: BgStreet,
onSelector: true, OnSelector: true,
} })
} }
+8 -8
View File
@@ -1,12 +1,12 @@
package inc package inc
func screenTrinketShop() screen { func init() {
return screen{ ScreenManager.Register(Screen{
name: ScreenTrinketShop, Name: ScreenTrinketShop,
title: "CHINATOWN — TRINKET SHOP", Title: "CHINATOWN — TRINKET SHOP",
background: BgTrinketShop, Background: BgTrinketShop,
onSelector: true, OnSelector: true,
exits: []exit{ Exits: []exit{
{ {
to: ScreenSmallRestaurant, to: ScreenSmallRestaurant,
label: "the eating place next door", 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.", blocked: "Just a shop, as far as the man behind the counter is concerned.",
}, },
}, },
} })
} }
+4 -4
View File
@@ -4,11 +4,11 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func scriptAwakeningFinale(w *World) inkwell.Script { func init() {
return inkwell.Script{ ScriptManager.Register(Script{
Name: ScriptFinale, Name: ScriptFinale,
Actions: inkwell.Seq( Actions: inkwell.Seq(
SetMode(w, ModeCutscene), SetMode(ModeCutscene),
inkwell.Wait(0.6), inkwell.Wait(0.6),
inkwell.Say(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), inkwell.Wait(0.4),
@@ -19,5 +19,5 @@ func scriptAwakeningFinale(w *World) inkwell.Script {
inkwell.Wait(1.0), inkwell.Wait(1.0),
inkwell.ShowEnd("REAL WORLD — end of game two"), inkwell.ShowEnd("REAL WORLD — end of game two"),
), ),
} })
} }
+3 -8
View File
@@ -4,11 +4,6 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func registerScript(w *World) { type Script = inkwell.Script
for _, s := range []inkwell.Script{
scriptTapeInsert(w), var ScriptManager = NewManager(func(entity Script) string { return entity.Name })
scriptAwakeningFinale(w),
} {
w.G.ScriptManager.Register(s)
}
}
+5 -5
View File
@@ -4,16 +4,16 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
func scriptTapeInsert(w *World) inkwell.Script { func init() {
return inkwell.Script{ ScriptManager.Register(Script{
Name: ScriptTapeInsert, Name: ScriptTapeInsert,
Actions: inkwell.Seq( Actions: inkwell.Seq(
Fn(func(ctx *inkwell.Ctx) { Fn(func(ctx *inkwell.Ctx) {
item := w.TakePending() item := World.TakePending()
if item == "" { if item == "" {
return return
} }
w.SetSlot2(item) World.SetSlot2(item)
ctx.Game.Inventory.Remove(item) ctx.Game.Inventory.Remove(item)
ctx.Game.State.SetVar(VarArmillaStrip, "SLOT2: READING") 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, "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."), TapeSay(Dex, "A stranger's tape in your own Armilla, Paul. I hope you know what you're doing."),
), ),
} })
} }
+6 -93
View File
@@ -6,6 +6,10 @@ import (
inkwell "git.teletypegames.org/engines/inkwell" inkwell "git.teletypegames.org/engines/inkwell"
) )
type Theme = inkwell.Theme
var ThemeManager = NewManager(func(entity Theme) string { return entity.Name })
const ( const (
RealWorld = "realworld-93" RealWorld = "realworld-93"
NokiaPunk = "nokia-punk" NokiaPunk = "nokia-punk"
@@ -29,97 +33,6 @@ func RGBA(hex uint32, a uint8) color.Color {
} }
} }
var ( func registerTheme() {
Ink = RGB(0xDCD5C4) registerAll(ThemeManager, World.G.ThemeManager.Register)
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())
} }
+44
View File
@@ -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),
})
}
+48
View File
@@ -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,
})
}
+8 -9
View File
@@ -7,26 +7,25 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
) )
type Letterbox struct { type letterbox struct {
Name string Name string
W *World
Bar float64 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) { func (l *letterbox) Tick(ctx *inkwell.UICtx) {
if l.W.Mode() != ModeCutscene || !l.W.TapeWaiting() { if World.Mode() != ModeCutscene || !World.TapeWaiting() {
return return
} }
if inpututil.IsKeyJustPressed(ebiten.KeySpace) { if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
l.W.TakeTapeOffer() World.TakeTapeOffer()
} }
} }
func (l *Letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { func (l *letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if l.W.Mode() != ModeCutscene { if World.Mode() != ModeCutscene {
return return
} }
g := ctx.Game 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, 0, w, float32(bar), black, false)
vector.DrawFilledRect(dst, 0, h-float32(bar), 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" msg := "SPACE — " + TapeDisplayName(Dex) + " has something to say"
drawTextC(dst, msg, g.Width-textW(msg)-4, g.Height-int(bar)+4, th.ChatLogResponse) drawTextC(dst, msg, g.Width-textW(msg)-4, g.Height-int(bar)+4, th.ChatLogResponse)
} }
+17 -27
View File
@@ -27,20 +27,17 @@ const (
invGap = 4 invGap = 4
) )
func registerUI(w *World) { func registerUI() {
g := w.G g := World.G
g.UIManager.Register(&UseWithGuard{ g.UIManager.Register(&useWithGuard{
Name: "usewith_guard", Name: "usewith_guard",
W: w,
}) })
g.UIManager.Register(&ActionPump{ g.UIManager.Register(&actionPump{
Name: "pump", Name: "pump",
W: w,
}) })
g.UIManager.Register(&screenNav{ g.UIManager.Register(&screenNav{
Name: "screen_nav", Name: "screen_nav",
W: w,
}) })
g.UIManager.Register(&windowSizer{ g.UIManager.Register(&windowSizer{
Name: "window", Name: "window",
@@ -55,26 +52,24 @@ func registerUI(w *World) {
Height: TopBarH, Height: TopBarH,
TimeVar: VarArmillaStrip, TimeVar: VarArmillaStrip,
} }
w.SetTopBar(top) World.SetTopBar(top)
g.UIManager.Register(gated(w, "topbar_gate", top)) g.UIManager.Register(gated("topbar_gate", top))
g.UIManager.Register(&Letterbox{ g.UIManager.Register(&letterbox{
Name: "letterbox", Name: "letterbox",
W: w,
Bar: 36, Bar: 36,
}) })
g.UIManager.Register(&hudFrame{ g.UIManager.Register(&hudFrame{
Name: "hudframe", Name: "hudframe",
W: w,
}) })
g.UIManager.Register(gated(w, "status_gate", &inkwell.StatusLine{ g.UIManager.Register(gated("status_gate", &inkwell.StatusLine{
Name: "status", Name: "status",
Y: statusY, Y: statusY,
Align: inkwell.AlignLeft, Align: inkwell.AlignLeft,
ScreenWidth: DividerX, ScreenWidth: DividerX,
})) }))
g.UIManager.Register(gated(w, "inventory_gate", &inkwell.InventoryBar{ g.UIManager.Register(gated("inventory_gate", &inkwell.InventoryBar{
Name: "inventory", Name: "inventory",
Origin: inkwell.Point{ Origin: inkwell.Point{
X: Pad + 2, X: Pad + 2,
@@ -85,14 +80,12 @@ func registerUI(w *World) {
SlotSize: invSlot, SlotSize: invSlot,
Gap: invGap, Gap: invGap,
})) }))
g.UIManager.Register(&TapeSlots{ g.UIManager.Register(&tapeSlots{
Name: "tapes", Name: "tapes",
W: w,
Bounds: inkwell.Rect(192, slotsY, 194, slotsH), Bounds: inkwell.Rect(192, slotsY, 194, slotsH),
}) })
g.UIManager.Register(&TapeChannel{ g.UIManager.Register(&tapeChannel{
Name: "channel", Name: "channel",
W: w,
Bounds: inkwell.Rect(DividerX+2, HUDTop+2, ScreenW-DividerX-4, ScreenH-HUDTop-4), 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 { type gate struct {
Name string Name string
W *World
Inner inkwell.Widget 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{ return &gate{
Name: name, Name: name,
W: w,
Inner: inner, 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) GetName() string { return n.Name }
func (n *gate) Tick(ctx *inkwell.UICtx) { func (n *gate) Tick(ctx *inkwell.UICtx) {
if n.W.HUDVisible() { if World.HUDVisible() {
n.Inner.Tick(ctx) n.Inner.Tick(ctx)
} }
} }
func (n *gate) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { func (n *gate) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if n.W.HUDVisible() { if World.HUDVisible() {
n.Inner.Draw(dst, ctx) n.Inner.Draw(dst, ctx)
} }
} }
func (n *gate) BlocksClickAt(p inkwell.Point) bool { func (n *gate) BlocksClickAt(p inkwell.Point) bool {
if !n.W.HUDVisible() { if !World.HUDVisible() {
return false return false
} }
b, ok := n.Inner.(interface{ BlocksClickAt(inkwell.Point) bool }) b, ok := n.Inner.(interface{ BlocksClickAt(inkwell.Point) bool })
@@ -186,14 +177,13 @@ func (n *gate) BlocksClickAt(p inkwell.Point) bool {
type hudFrame struct { type hudFrame struct {
Name string Name string
W *World
} }
func (h *hudFrame) GetName() string { return h.Name } func (h *hudFrame) GetName() string { return h.Name }
func (h *hudFrame) Tick(ctx *inkwell.UICtx) {} func (h *hudFrame) Tick(ctx *inkwell.UICtx) {}
func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !h.W.HUDVisible() { if !World.HUDVisible() {
return return
} }
th := ctx.Game.Theme() 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 { func (h *hudFrame) BlocksClickAt(p inkwell.Point) bool {
return h.W.HUDVisible() && p.Y >= HUDTop return World.HUDVisible() && p.Y >= HUDTop
} }
+3 -4
View File
@@ -8,14 +8,13 @@ import (
type screenNav struct { type screenNav struct {
Name string Name string
W *World
} }
func (n *screenNav) GetName() string { return n.Name } func (n *screenNav) GetName() string { return n.Name }
func (n *screenNav) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} func (n *screenNav) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (n *screenNav) Tick(ctx *inkwell.UICtx) { func (n *screenNav) Tick(ctx *inkwell.UICtx) {
if !n.W.HUDVisible() || n.W.Paused() { if !World.HUDVisible() || World.Paused() {
return return
} }
step := 0 step := 0
@@ -29,7 +28,7 @@ func (n *screenNav) Tick(ctx *inkwell.UICtx) {
return return
} }
if next := n.neighbour(ctx.Game, step); next != "" { 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 "" return ""
} }
for i, name := range deck { for i, name := range deck {
if name == n.W.Scene() { if name == World.Scene() {
return deck[((i+step)%len(deck)+len(deck))%len(deck)] return deck[((i+step)%len(deck)+len(deck))%len(deck)]
} }
} }
+8 -9
View File
@@ -8,22 +8,21 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
) )
type TapeChannel struct { type tapeChannel struct {
Name string Name string
W *World
Bounds inkwell.Rectangle 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 { func (t *tapeChannel) BlocksClickAt(p inkwell.Point) bool {
return t.W.HUDVisible() && t.Bounds.Contains(p) return World.HUDVisible() && t.Bounds.Contains(p)
} }
func (t *TapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) { func (t *tapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !t.W.HUDVisible() { if !World.HUDVisible() {
return return
} }
g := ctx.Game 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() msgs := g.Messages()
for i := len(msgs) - 1; i >= 0; i-- { for i := len(msgs) - 1; i >= 0; i-- {
m := msgs[i] m := msgs[i]
+23 -24
View File
@@ -6,26 +6,25 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
) )
type TapeSlots struct { type tapeSlots struct {
Name string Name string
W *World
Bounds inkwell.Rectangle 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 { func (t *tapeSlots) BlocksClickAt(p inkwell.Point) bool {
return t.W.HUDVisible() && t.Bounds.Contains(p) 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 b := t.Bounds
w := (b.W - Pad) / 2 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) 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) { func (t *tapeSlots) Tick(ctx *inkwell.UICtx) {
if !t.W.HUDVisible() { if !World.HUDVisible() {
return return
} }
g := ctx.Game g := ctx.Game
@@ -36,7 +35,7 @@ func (t *TapeSlots) Tick(ctx *inkwell.UICtx) {
case r1.Contains(mp): case r1.Contains(mp):
g.SetHoverLabel(TapeDisplayName(Dex)) g.SetHoverLabel(TapeDisplayName(Dex))
case r2.Contains(mp): case r2.Contains(mp):
if s := t.W.Slot2(); s != "" { if s := World.Slot2(); s != "" {
g.SetHoverLabel(itemLabel(g, s)) g.SetHoverLabel(itemLabel(g, s))
} else { } else {
g.SetHoverLabel("empty tape slot") g.SetHoverLabel("empty tape slot")
@@ -57,45 +56,45 @@ func (t *TapeSlots) Tick(ctx *inkwell.UICtx) {
if slot2 && sel != "" { if slot2 && sel != "" {
g.Inventory.Select("") g.Inventory.Select("")
if IsTapeItem(sel) { if IsTapeItem(sel) {
t.W.SetPending(sel) World.SetPending(sel)
t.W.Do(inkwell.RunScript(ScriptTapeInsert)) World.Do(inkwell.RunScript(ScriptTapeInsert))
return 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 return
} }
if !slot2 && sel != "" { if !slot2 && sel != "" {
g.Inventory.Select("") 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 return
} }
switch g.SelectedVerb() { switch g.SelectedVerb() {
case "talk": case "talk":
if !slot2 { if !slot2 {
t.W.Do(Paused(t.W, inkwell.RunDialogue(DlgDex))) World.Do(Paused(inkwell.RunDialogue(DlgDex)))
return return
} }
if s := t.W.Slot2(); s != "" { if s := World.Slot2(); s != "" {
t.W.Do(Paused(t.W, inkwell.RunDialogue(TapeDialogue(s)))) World.Do(Paused(inkwell.RunDialogue(TapeDialogue(s))))
return return
} }
t.W.Do(TapeSay(Dex, "Empty. Nobody to talk to.")) World.Do(TapeSay(Dex, "Empty. Nobody to talk to."))
default: default:
if !slot2 { 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 return
} }
if s := t.W.Slot2(); s != "" { if s := World.Slot2(); s != "" {
t.W.Do(TapeSay(Dex, "That is the second slot. Be careful what you let in.")) World.Do(TapeSay(Dex, "That is the second slot. Be careful what you let in."))
return 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) { func (t *tapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !t.W.HUDVisible() { if !World.HUDVisible() {
return return
} }
g := ctx.Game g := ctx.Game
@@ -116,7 +115,7 @@ func (t *TapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
} }
drawSlot(r1, "SLOT 1", TapeDisplayName(Dex), true) 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) drawSlot(r2, "SLOT 2", itemLabel(g, s), true)
} else { } else {
drawSlot(r2, "SLOT 2", "empty", false) drawSlot(r2, "SLOT 2", "empty", false)
+6 -7
View File
@@ -7,17 +7,16 @@ import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
type UseWithGuard struct { type useWithGuard struct {
Name string Name string
W *World
} }
func (u *UseWithGuard) GetName() string { return u.Name } func (u *useWithGuard) GetName() string { return u.Name }
func (u *UseWithGuard) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} 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 g := ctx.Game
if !u.W.HUDVisible() || !g.Input.LeftClicked() { if !World.HUDVisible() || !g.Input.LeftClicked() {
return return
} }
sel := g.Inventory.Selected() sel := g.Inventory.Selected()
@@ -42,7 +41,7 @@ func (u *UseWithGuard) Tick(ctx *inkwell.UICtx) {
g.State.NoteTalked(key) g.State.NoteTalked(key)
n := g.State.Talked(key) n := g.State.Talked(key)
u.W.Do(inkwell.Seq( World.Do(inkwell.Seq(
inkwell.Say(Paul, pick(paulFails, n)), inkwell.Say(Paul, pick(paulFails, n)),
TapeSay(Dex, dexFail(n)), TapeSay(Dex, dexFail(n)),
)) ))
+23 -26
View File
@@ -5,13 +5,13 @@ import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
func (w *World) Do(a inkwell.Action) { func (w *world) Do(a inkwell.Action) {
if a != nil { if a != nil {
w.queue = append(w.queue, a) w.queue = append(w.queue, a)
} }
} }
func (w *World) PumpTick(dt float64) { func (w *world) PumpTick(dt float64) {
if w.top != nil { if w.top != nil {
title := w.sceneTitle() 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) s, ok := w.G.SceneManager.Get(w.scene)
if !ok { if !ok {
return "" return ""
@@ -51,14 +51,13 @@ func (w *World) sceneTitle() string {
return s.Name return s.Name
} }
type ActionPump struct { type actionPump struct {
Name string Name string
W *World
} }
func (p *ActionPump) GetName() string { return p.Name } func (p *actionPump) GetName() string { return p.Name }
func (p *ActionPump) Tick(ctx *inkwell.UICtx) { p.W.PumpTick(ctx.DT) } func (p *actionPump) Tick(ctx *inkwell.UICtx) { World.PumpTick(ctx.DT) }
func (p *ActionPump) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {} func (p *actionPump) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
type fnAction struct{ fn func(*inkwell.Ctx) } 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) }) return Fn(func(ctx *inkwell.Ctx) { ctx.Game.UseTheme(name) })
} }
func SetMode(w *World, m Mode) inkwell.Action { func SetMode(m Mode) inkwell.Action {
return Fn(func(*inkwell.Ctx) { w.SetMode(m) }) 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) { return Fn(func(*inkwell.Ctx) {
if name != w.scene { if name != World.scene {
w.prev = w.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) { return Fn(func(ctx *inkwell.Ctx) {
if w.prev == "" { if World.prev == "" {
return 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 return inkwell.StatusRunning
} }
func TapeOffer(w *World, line inkwell.Action) inkwell.Action { func TapeOffer(line inkwell.Action) inkwell.Action {
return Fn(func(*inkwell.Ctx) { return Fn(func(*inkwell.Ctx) {
w.tapeLine = line World.tapeLine = line
w.tapeWaiting = true World.tapeWaiting = true
}) })
} }
func (w *World) TakeTapeOffer() { func (w *world) TakeTapeOffer() {
if !w.tapeWaiting { if !w.tapeWaiting {
return return
} }
@@ -160,15 +159,13 @@ func (w *World) TakeTapeOffer() {
w.Do(line) w.Do(line)
} }
func Paused(w *World, inner inkwell.Action) inkwell.Action { func Paused(inner inkwell.Action) inkwell.Action {
return &pausedAction{ return &pausedAction{
w: w,
inner: inner, inner: inner,
} }
} }
type pausedAction struct { type pausedAction struct {
w *World
inner inkwell.Action inner inkwell.Action
} }
@@ -188,11 +185,11 @@ type pausedRunner struct {
func (r *pausedRunner) Tick(ctx *inkwell.Ctx) inkwell.Status { func (r *pausedRunner) Tick(ctx *inkwell.Ctx) inkwell.Status {
if !r.started { if !r.started {
r.started = true r.started = true
r.spec.w.paused = true World.paused = true
} }
s := r.inner.Tick(ctx) s := r.inner.Tick(ctx)
if s != inkwell.StatusRunning { if s != inkwell.StatusRunning {
r.spec.w.paused = false World.paused = false
} }
return s return s
} }
+27 -18
View File
@@ -12,7 +12,7 @@ const (
ModeMenu Mode = "menu" ModeMenu Mode = "menu"
) )
type World struct { type world struct {
G *inkwell.Game G *inkwell.Game
mode Mode mode Mode
@@ -29,42 +29,51 @@ type World struct {
tapeLine inkwell.Action tapeLine inkwell.Action
} }
func newWorld(g *inkwell.Game) *World { var World = &world{}
return &World{
G: g, func (w *world) attach(g *inkwell.Game) {
mode: ModePlay, 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 { if v, ok := w.G.State.Var(VarSlot2).(string); ok {
return v return v
} }
return "" 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 item := w.pending
w.pending = "" w.pending = ""
return item return item
} }
func (w *World) TapeWaiting() bool { return w.tapeWaiting } func (w *world) TapeWaiting() bool { return w.tapeWaiting }