Declare the managers in boot.go, hand them to the engine whole
ci/woodpecker/push/ebitengine Pipeline was successful
ci/woodpecker/push/ebitengine Pipeline was successful
boot.manager.go becomes boot.go and carries all seven managers as one block, so what the game holds reads in one place instead of a line hidden in each category file. The category files keep their entity alias. The hand-off stops copying. Our managers are the same *inkwell.Manager[T] the Game holds, so New assigns them onto it and engine and game share one registry per category. content.manager.go is gone with the copy loop. Themes stay additive — NewGame seeds four presets we would otherwise throw away — and registerScene becomes prepareScene, which now has to run before the hand-off: with no copy pass left, the scene defaults are written back into SceneManager with Set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,25 +49,25 @@ All game code lives in one flat package, `inc`. There are no subdirectories: a
|
|||||||
file's name carries the structure, in the form `[category].[name].go`.
|
file's name carries the structure, in the form `[category].[name].go`.
|
||||||
|
|
||||||
- The category is always **singular**: `item`, `scene`, `background`,
|
- The category is always **singular**: `item`, `scene`, `background`,
|
||||||
`character`, `dialog`, `script`, `world`, `ui`, `theme`, `names`, `boot`,
|
`character`, `dialog`, `script`, `world`, `ui`, `theme`, `names`.
|
||||||
`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
|
||||||
manager, its entity type, whatever the category shares.
|
entity type and whatever else the category shares.
|
||||||
- Every other file in a category holds exactly one entity: one scene, one
|
- Every other file in a category holds exactly one entity: one scene, one
|
||||||
background, one item. The file registers it itself, in an `init()`.
|
background, one item. The file registers it itself, in an `init()`.
|
||||||
|
- `boot.go` is the exception that has no category: it declares every manager
|
||||||
|
and builds the game.
|
||||||
|
|
||||||
```
|
```
|
||||||
main.go flags + inkwell.Run
|
main.go flags + inkwell.Run
|
||||||
inc/boot.manager.go wiring; New(Opts) builds the game
|
inc/boot.go the managers; 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 the theme manager and the colour helpers
|
inc/theme.manager.go the Theme alias and the colour helpers
|
||||||
inc/theme.realworld.go realworld-93
|
inc/theme.realworld.go realworld-93
|
||||||
inc/theme.nokia_punk.go nokia-punk
|
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
|
||||||
inc/ui.*.go custom widgets, coloured text
|
inc/ui.*.go custom widgets, coloured text
|
||||||
inc/content.manager.go composition root; calls every register…
|
|
||||||
inc/background.*.go one image asset per scene
|
inc/background.*.go one image asset per scene
|
||||||
inc/character.*.go the cast, tapes included
|
inc/character.*.go the cast, tapes included
|
||||||
inc/item.*.go inventory
|
inc/item.*.go inventory
|
||||||
@@ -84,18 +84,30 @@ Every category that owns a collection of entities has a manager, and they are
|
|||||||
all the engine's own `inkwell.Manager[T]` — the same registry type the `*Game`
|
all the engine's own `inkwell.Manager[T]` — the same registry type the `*Game`
|
||||||
hangs its content off. The game defines no registry of its own.
|
hangs its content off. The game defines no registry of its own.
|
||||||
|
|
||||||
The entity types are aliases of engine structs, so they already carry
|
All seven are declared together, in `boot.go`, so the list of what the game
|
||||||
`GetName()` and satisfy `inkwell.Named`; the manager needs nothing else:
|
holds is one block rather than a line hidden in each category file:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var (
|
||||||
|
BackgroundManager = inkwell.NewManager[Background]()
|
||||||
|
CharacterManager = inkwell.NewManager[Character]()
|
||||||
|
DialogManager = inkwell.NewManager[Dialog]()
|
||||||
|
ItemManager = inkwell.NewManager[Item]()
|
||||||
|
SceneManager = inkwell.NewManager[Scene]()
|
||||||
|
ScriptManager = inkwell.NewManager[Script]()
|
||||||
|
ThemeManager = inkwell.NewManager[Theme]()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The entity types stay in their own category files, one alias each, and that is
|
||||||
|
all a manager file holds now:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Character = inkwell.Character
|
type Character = inkwell.Character
|
||||||
|
|
||||||
var CharacterManager = inkwell.NewManager[Character]()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
That is the whole of a category's manager file — an alias and one line. There
|
Aliases of engine structs already carry `GetName()` and satisfy
|
||||||
are seven managers: `BackgroundManager`, `CharacterManager`, `DialogManager`,
|
`inkwell.Named`, which is the whole of what a manager asks of them.
|
||||||
`ItemManager`, `ScriptManager`, `SceneManager`, `ThemeManager`.
|
|
||||||
|
|
||||||
The methods the game uses are `Register`, `Set`, `Get`, `All` and `Each`.
|
The methods the game uses are `Register`, `Set`, `Get`, `All` and `Each`.
|
||||||
`Register` panics on a duplicate name — a second registration is a
|
`Register` panics on a duplicate name — a second registration is a
|
||||||
@@ -126,8 +138,9 @@ func init() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Adding an entity is adding a file. Deleting one is deleting a file. Package-level
|
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
|
variables are initialised before any `init()` runs, whatever file each sits in,
|
||||||
time the first file registers into one.
|
so the managers in `boot.go` exist by the time the first entity file registers
|
||||||
|
into one.
|
||||||
|
|
||||||
`init()` order is file-name order, so **registration order is alphabetical**.
|
`init()` order is file-name order, so **registration order is alphabetical**.
|
||||||
Nothing may depend on it — including the order the arrow keys walk the scenes,
|
Nothing may depend on it — including the order the arrow keys walk the scenes,
|
||||||
@@ -135,23 +148,30 @@ which is simply the order the files sit in.
|
|||||||
|
|
||||||
### Handing a category to the engine
|
### Handing a category to the engine
|
||||||
|
|
||||||
Once the game exists, `Each` walks a manager and gives every entity to the
|
Nothing is copied. `inkwell.NewGame` builds its own empty managers, and `New`
|
||||||
engine's own manager. `registerContent` is the whole of it:
|
hands ours over in their place — the types are identical, so the engine and the
|
||||||
|
game end up sharing one registry per category rather than two in step:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func registerContent() {
|
g.AssetManager = BackgroundManager
|
||||||
BackgroundManager.Each(World.G.AssetManager.Register)
|
g.CharacterManager = CharacterManager
|
||||||
CharacterManager.Each(World.G.CharacterManager.Register)
|
g.DialogueManager = DialogManager
|
||||||
ItemManager.Each(World.G.ItemManager.Register)
|
g.ItemManager = ItemManager
|
||||||
DialogManager.Each(World.G.DialogueManager.Register)
|
g.SceneManager = SceneManager
|
||||||
ScriptManager.Each(World.G.ScriptManager.Register)
|
g.ScriptManager = ScriptManager
|
||||||
registerScene()
|
ThemeManager.Each(g.ThemeManager.Register)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`registerScene` is the one that needs its own function: it fills in the defaults
|
Themes are the odd one out and stay a copy: `NewGame` puts four preset themes
|
||||||
a scene may leave out (Paul's starting position, the floor walkbox), and it
|
into its `ThemeManager`, and replacing it would throw them away. Ours are added
|
||||||
derives the selector's pins by reading the exit graph backwards.
|
to that set instead.
|
||||||
|
|
||||||
|
Two consequences of not copying. `prepareScene` has to run **before** the
|
||||||
|
hand-off, because there is no longer a copy pass to fill in the defaults a
|
||||||
|
scene leaves out — it writes them back into `SceneManager` with `Set`, and
|
||||||
|
derives the selector's pins by reading the exit graph backwards. And a manager
|
||||||
|
swapped in this way must be in place before `inkwell.Run`, which is where the
|
||||||
|
engine wires up the parts that hold a registry directly.
|
||||||
|
|
||||||
## The world
|
## The world
|
||||||
|
|
||||||
@@ -171,7 +191,7 @@ One package means one namespace, so an entity's constructor carries its
|
|||||||
category as a prefix:
|
category as a prefix:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sceneFloor sceneDefaults registerScene fillSelectorPins
|
sceneFloor sceneDefaults prepareScene fillSelectorPins
|
||||||
```
|
```
|
||||||
|
|
||||||
Entities themselves need no name at all — they are anonymous literals inside
|
Entities themselves need no name at all — they are anonymous literals inside
|
||||||
@@ -183,8 +203,8 @@ constants in `names.manager.go`, the colour tokens, and the action constructors
|
|||||||
(`TapeSay`, `Paused`, `SetMode`, `EnterScene`, `Back`, `Fn`).
|
(`TapeSay`, `Paused`, `SetMode`, `EnterScene`, `Back`, `Fn`).
|
||||||
|
|
||||||
Everything else is machinery and stays unexported: the HUD widgets
|
Everything else is machinery and stays unexported: the HUD widgets
|
||||||
(`tapeSlots`, `letterbox`, `hudFrame`, …), the `register…` functions, the
|
(`tapeSlots`, `letterbox`, `hudFrame`, …), `registerUI`, the runners,
|
||||||
runners, `sceneDefaults`, `fillSelectorPins`.
|
`prepareScene`, `sceneDefaults`, `fillSelectorPins`.
|
||||||
|
|
||||||
The word is **scene**, the engine's own. The wiki and the concept-art deck count
|
The word is **scene**, the engine's own. The wiki and the concept-art deck count
|
||||||
*screens*, and this code used to as well, but everything a screen had that a
|
*screens*, and this code used to as well, but everything a screen had that a
|
||||||
|
|||||||
@@ -153,16 +153,17 @@ 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 manager, its shared types, its hand-off to the engine.
|
together — its entity type and whatever else the category shares. `boot.go` is
|
||||||
|
the one file with no category: it declares every manager and builds the game.
|
||||||
|
|
||||||
```
|
```
|
||||||
main.go flags + inkwell.Run
|
main.go flags + inkwell.Run
|
||||||
|
inc/boot.go the managers, and New(Opts)
|
||||||
inc/names.manager.go entity names and world-state keys
|
inc/names.manager.go entity names and world-state keys
|
||||||
inc/theme.*.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
|
||||||
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
|
||||||
@@ -194,13 +195,12 @@ Every category owns a manager, and they are all the engine's own
|
|||||||
`inkwell.Manager[T]` — the same registry the `*Game` hangs its content off, kept
|
`inkwell.Manager[T]` — the same registry the `*Game` hangs its content off, kept
|
||||||
in registration order and addressed by name. There is no second registry type
|
in registration order and addressed by name. There is no second registry type
|
||||||
here: the entity types are aliases of engine structs, so they already satisfy
|
here: the entity types are aliases of engine structs, so they already satisfy
|
||||||
`inkwell.Named` and need no help reading their own name. A category's manager
|
`inkwell.Named` and need no help reading their own name. The seven of them are
|
||||||
file is an alias and one line:
|
declared as one block in `boot.go`, and a category's manager file is left
|
||||||
|
holding the alias:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Character = inkwell.Character
|
type Character = inkwell.Character
|
||||||
|
|
||||||
var CharacterManager = inkwell.NewManager[Character]()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
An entity file is a literal that hands itself over in an `init()`:
|
An entity file is a literal that hands itself over in an `init()`:
|
||||||
@@ -223,9 +223,18 @@ step with the files by hand, and the deck is a thing to look at, not a thing to
|
|||||||
play through.
|
play through.
|
||||||
|
|
||||||
The game's own catalogue is readable without going through the engine:
|
The game's own catalogue is readable without going through the engine:
|
||||||
`SceneManager.Get("alley")` answers before a single scene has been handed over.
|
`SceneManager.Get("alley")` answers long before there is a `*Game` to ask. And
|
||||||
`registerContent` is where the hand-off happens, one `Each` call per category —
|
when the game is built, nothing is copied into it — `New` assigns our managers
|
||||||
the game's manager iterated straight into the engine manager's `Register`.
|
onto the `*Game` in place of the empty ones `NewGame` made, so engine and game
|
||||||
|
share one registry per category instead of keeping two of them in step. Themes
|
||||||
|
are the exception: `NewGame` seeds its theme manager with four presets, so ours
|
||||||
|
are added to that set rather than replacing it.
|
||||||
|
|
||||||
|
The price of not copying is that the defaults a scene may leave out — Paul's
|
||||||
|
starting position, the floor walkbox — have to be written back into
|
||||||
|
`SceneManager` before the hand-off rather than filled in on the way past.
|
||||||
|
`prepareScene` does that with `Set`, and derives the selector's pins in the same
|
||||||
|
pass by reading the exit graph backwards.
|
||||||
|
|
||||||
There is one world, and it is a package-level singleton: `World`. Nothing takes
|
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
|
a `*world` parameter and no widget holds a back-reference, which is what lets a
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module git.teletypegames.org/games/realworld
|
|||||||
go 1.26.3
|
go 1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830075028-08f15a7e3d11
|
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830083613-39af65f3c98f
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.9.9
|
github.com/hajimehoshi/ebiten/v2 v2.9.9
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830075028-08f15a7e3d11 h1:DPN74Xy0xcyelfh5xLuv6XBj6wrdzKVME1a1HyACsj4=
|
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830083613-39af65f3c98f h1:Qw/sUeJeCBEFvdiqwGUn919hsmoTILJyKSPlkKvNgh0=
|
||||||
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830075028-08f15a7e3d11/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs=
|
git.teletypegames.org/engines/inkwell v0.1.1-0.20260830083613-39af65f3c98f/go.mod h1:/v6QtismTE8+e7kobbVR5B/CpTSrd4j2DYwJ8DvINhs=
|
||||||
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 h1:+kz5iTT3L7uU+VhlMfTb8hHcxLO3TlaELlX8wa4XjA0=
|
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 h1:+kz5iTT3L7uU+VhlMfTb8hHcxLO3TlaELlX8wa4XjA0=
|
||||||
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1/go.mod h1:lKJoeixeJwnFmYsBny4vvCJGVFc3aYDalhuDsfZzWHI=
|
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1/go.mod h1:lKJoeixeJwnFmYsBny4vvCJGVFc3aYDalhuDsfZzWHI=
|
||||||
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
||||||
|
|||||||
@@ -5,5 +5,3 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Background = inkwell.Asset
|
type Background = inkwell.Asset
|
||||||
|
|
||||||
var BackgroundManager = inkwell.NewManager[Background]()
|
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import (
|
|||||||
inkwell "git.teletypegames.org/engines/inkwell"
|
inkwell "git.teletypegames.org/engines/inkwell"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
BackgroundManager = inkwell.NewManager[Background]()
|
||||||
|
CharacterManager = inkwell.NewManager[Character]()
|
||||||
|
DialogManager = inkwell.NewManager[Dialog]()
|
||||||
|
ItemManager = inkwell.NewManager[Item]()
|
||||||
|
SceneManager = inkwell.NewManager[Scene]()
|
||||||
|
ScriptManager = inkwell.NewManager[Script]()
|
||||||
|
ThemeManager = inkwell.NewManager[Theme]()
|
||||||
|
)
|
||||||
|
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
Scene string
|
Scene string
|
||||||
Finale bool
|
Finale bool
|
||||||
@@ -15,6 +25,8 @@ func New(o Opts) *inkwell.Game {
|
|||||||
start = SceneAlley
|
start = SceneAlley
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareScene()
|
||||||
|
|
||||||
g := inkwell.NewGame("Real World", ScreenW, ScreenH)
|
g := inkwell.NewGame("Real World", ScreenW, ScreenH)
|
||||||
g.MaxLogLines = 64
|
g.MaxLogLines = 64
|
||||||
g.SceneRect = inkwell.Rect(0, TopBarH, ScreenW, HUDTop-TopBarH)
|
g.SceneRect = inkwell.Rect(0, TopBarH, ScreenW, HUDTop-TopBarH)
|
||||||
@@ -25,10 +37,15 @@ func New(o Opts) *inkwell.Game {
|
|||||||
return inkwell.Say(Paul, "It's a way out, not a thing.")
|
return inkwell.Say(Paul, "It's a way out, not a thing.")
|
||||||
}
|
}
|
||||||
|
|
||||||
World.attach(g)
|
g.AssetManager = BackgroundManager
|
||||||
registerTheme()
|
g.CharacterManager = CharacterManager
|
||||||
registerContent()
|
g.DialogueManager = DialogManager
|
||||||
|
g.ItemManager = ItemManager
|
||||||
|
g.SceneManager = SceneManager
|
||||||
|
g.ScriptManager = ScriptManager
|
||||||
|
ThemeManager.Each(g.ThemeManager.Register)
|
||||||
|
|
||||||
|
World.attach(g)
|
||||||
g.UseTheme(RealWorld)
|
g.UseTheme(RealWorld)
|
||||||
registerUI()
|
registerUI()
|
||||||
|
|
||||||
@@ -5,5 +5,3 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Character = inkwell.Character
|
type Character = inkwell.Character
|
||||||
|
|
||||||
var CharacterManager = inkwell.NewManager[Character]()
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package inc
|
|
||||||
|
|
||||||
func registerContent() {
|
|
||||||
BackgroundManager.Each(World.G.AssetManager.Register)
|
|
||||||
CharacterManager.Each(World.G.CharacterManager.Register)
|
|
||||||
ItemManager.Each(World.G.ItemManager.Register)
|
|
||||||
DialogManager.Each(World.G.DialogueManager.Register)
|
|
||||||
ScriptManager.Each(World.G.ScriptManager.Register)
|
|
||||||
registerScene()
|
|
||||||
}
|
|
||||||
@@ -5,5 +5,3 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Dialog = inkwell.Dialogue
|
type Dialog = inkwell.Dialogue
|
||||||
|
|
||||||
var DialogManager = inkwell.NewManager[Dialog]()
|
|
||||||
|
|||||||
@@ -5,5 +5,3 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Item = inkwell.Item
|
type Item = inkwell.Item
|
||||||
|
|
||||||
var ItemManager = inkwell.NewManager[Item]()
|
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import (
|
|||||||
|
|
||||||
type Scene = inkwell.Scene
|
type Scene = inkwell.Scene
|
||||||
|
|
||||||
var SceneManager = inkwell.NewManager[Scene]()
|
func prepareScene() {
|
||||||
|
|
||||||
func registerScene() {
|
|
||||||
fillSelectorPins()
|
fillSelectorPins()
|
||||||
SceneManager.Each(func(entity Scene) {
|
for _, entity := range SceneManager.All() {
|
||||||
World.G.SceneManager.Register(sceneDefaults(entity))
|
SceneManager.Set(sceneDefaults(entity))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sceneFloor = []inkwell.Polygon{
|
var sceneFloor = []inkwell.Polygon{
|
||||||
|
|||||||
@@ -5,5 +5,3 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Script = inkwell.Script
|
type Script = inkwell.Script
|
||||||
|
|
||||||
var ScriptManager = inkwell.NewManager[Script]()
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
|
|
||||||
type Theme = inkwell.Theme
|
type Theme = inkwell.Theme
|
||||||
|
|
||||||
var ThemeManager = inkwell.NewManager[Theme]()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RealWorld = "realworld-93"
|
RealWorld = "realworld-93"
|
||||||
NokiaPunk = "nokia-punk"
|
NokiaPunk = "nokia-punk"
|
||||||
@@ -32,7 +30,3 @@ func RGBA(hex uint32, a uint8) color.Color {
|
|||||||
A: a,
|
A: a,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerTheme() {
|
|
||||||
ThemeManager.Each(World.G.ThemeManager.Register)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user