More work on states, added bot

This commit is contained in:
Vladislav Khorev committed 2026-10-29 22:37:30 +06:00
1 parent 66e93d46f4
commit 5e9a663812
25 files changed
+1595 -740

No files matched your search

+85 -31
View File
@@ -12,7 +12,7 @@ The architecture has four layers:
1. `GameState` is the aggregate root for persistent, authoritative state.
2. `InternalGameServer` owns `GameState`, advances the local simulation, handles commands, and produces immutable snapshots.
3. Runtime objects such as `Location`, `Character`, and `InteractiveObject` hold rendering resources and non-owning bindings to server-owned state. `Location` also translates local input, scripts, and collision results into semantic server commands.
3. Runtime objects hold rendering resources. `Character` and `Location` read immutable server snapshots by stable ID, while `InteractiveObject` still uses a transitional non-owning binding to server-owned state. `Location` also translates local input, scripts, and collision results into semantic server commands.
4. Client-facing systems such as `MenuManager` read immutable snapshots and submit typed commands through `IGameServer`.
```text
@@ -47,6 +47,8 @@ IGameServer ---- immutable GameSnapshot ----> UI and other clients
- Inventory, money, player-health HUD values, quests, tutorial progress, chat data, taxi state, and script-global values.
Live `Location` pointers and rendering objects are no longer stored in `GameState`.
Embedded state models such as `Inventory` and `QuestJournal` contain only value
data and deterministic operations; they do not retain runtime callbacks.
### Campaign-level players
@@ -54,6 +56,11 @@ Player `CharacterState` objects are stored in `GameState::players`. The current
The player state is no longer owned by a `Character` or duplicated as a separately authoritative state for each loaded location. This permits additional player IDs to be introduced later without changing the aggregate ownership model.
Every player, including the one controlled by the current client, remains in this
same authoritative collection. `IGameServer::localPlayerId()` identifies which
entry is local to a particular client connection; it is not a separate state
object.
`LocationGameState` does not retain a player pointer or back-reference to `GameState`.
Its update boundary receives `GameState::players` by reference, allowing character
lookup and simulation to treat every campaign player by stable ID.
@@ -70,7 +77,12 @@ Each location record owns:
- Serialized dialogue runtime state.
- Serialized Lua runtime globals.
State objects use `shared_ptr` internally to keep their addresses stable while local runtime objects are bound to them. These pointers never appear in public snapshots.
World-mode flags (`darklands`, `night`, and `dawn`) exist only at the aggregate
`GameState` level. Location rendering and scripts read those values from the
top-level snapshot rather than maintaining per-location mirrors.
State objects use `shared_ptr` internally to keep addresses stable for the
remaining local runtime bindings. These pointers never appear in public snapshots.
### Stable identity
@@ -121,6 +133,7 @@ Snapshots have a monotonically increasing revision. `InternalGameServer` caches
- Chat history and unread state.
- Taxi state.
- Inventory additions and removals.
- Quest status, objective completion/visibility, and active-objective changes.
- Script globals.
- Character transforms, attack targets, navigation destinations, stopping,
facing, replanning, health, damage, death reset, attack combos, and animation
@@ -131,6 +144,14 @@ Snapshots have a monotonically increasing revision. `InternalGameServer` caches
A future network implementation can serialize equivalent requests instead of mutating local state.
### Presentation events
`IGameServer::drainEvents()` returns value-only events produced by successful
authoritative mutations. Inventory add/remove and quest unlock/complete/fail/
objective-complete events contain stable IDs only. `Game` drains these events and
performs local tutorial, toast, and audio presentation through `MenuManager`.
Loading and persistence restoration clear pending events and remain silent.
## Internal Server
`InternalGameServer` is the current `IGameServer` implementation. It:
@@ -143,10 +164,10 @@ A future network implementation can serialize equivalent requests instead of mut
- Builds and caches immutable snapshots.
- Delegates save/load serialization to the aggregate state model.
`Game` remains a privileged host during this migration. It can access private
internal-server operations for campaign loading, location construction, callback
wiring, and persistence restoration. These host operations are not exposed by
`IGameServer`.
`Game` remains a privileged lifecycle host, but it no longer holds or receives a
mutable `GameState` reference. It uses private, focused internal-server operations
for campaign loading and location construction. Those operations publish revisions
themselves and are not exposed by `IGameServer`.
### Animation and combat
@@ -180,16 +201,25 @@ collision handling, and presentation-event processing.
## Runtime and Visual Object Bindings
`Location`, `Character`, and `InteractiveObject` no longer own their authoritative state objects.
Runtime visual objects do not own their authoritative state objects.
- `Location` keeps a private non-owning reference to its `LocationState` and a stable location ID.
- `Character` keeps a private non-owning **const** reference named `characterState`, plus stable entity and location IDs.
- `Location` keeps only its `IGameServer` reference and stable location ID. It resolves its immutable `LocationState` view from `IGameServer::snapshot()`.
- `Character` keeps only its `IGameServer` reference plus stable entity and location IDs. It resolves the player-first character view from `IGameServer::snapshot()`.
- `InteractiveObject` keeps a private non-owning reference to its `InteractiveObjectState`; its configured object name is its stable ID.
The bound state objects are owned by `GameState` or `LocationGameState`. The internal
server advances them directly before runtime presentation. Rendering reads the bound
state; runtime input, scripts, and collision handling submit semantic commands that
invoke `CharacterState` operations against the authoritative `LocationState`.
`Character::stateSnapshot()` and `Location::stateSnapshot()` return aliasing
`shared_ptr` views. Each pointer addresses a state copy inside a `GameSnapshot`,
while shared ownership keeps that whole immutable snapshot alive. Update, input,
editor, and draw operations capture one such pointer and use it consistently
throughout the operation. A missing or unloaded entity produces an empty pointer
and presentation or input work is skipped.
The authoritative state objects are owned by `GameState` or `LocationGameState`.
The internal server advances them before publishing a new snapshot revision.
Runtime input, scripts, and collision handling submit semantic commands that invoke
authoritative character and location operations. Location commands cover camera
orientation, pending interaction targets, navigation switching and area changes,
editor obstacles, and taxi reset. No whole-location commit command is exposed.
Navigation target selection, replanning, stopping, facing, death reset, health
assignment, and attack-combo clock changes are implemented by `CharacterState`.
@@ -203,12 +233,19 @@ Arrival behavior is represented solely by the serialized Lua function name in
`CharacterState`; `Location` resolves and invokes that name through `ScriptEngine`
when it consumes the arrival event.
`Location` owns presentation-only runtime characters for all non-local entries in
`GameSnapshot::players`. It reconciles them by stable ID as snapshots change,
reuses the local-player assets and animation set, and includes them in all render,
shadow, lighting, animation, damage, and name-tag passes. The local entry is
excluded using `IGameServer::localPlayerId()` because its visual is already owned
by `Game`.
`GameObjectLoader` now:
1. Loads static creation data from location configuration.
2. Creates or reuses server-owned state objects.
3. Applies saved runtime fields over the configuration defaults.
4. Constructs visual objects bound to those state objects.
4. Constructs visual objects using stable IDs and state-backed creation data.
Configuration remains the source of visual assets, animation paths, meshes, textures, and fixed tuning data.
@@ -225,11 +262,14 @@ The normal frame flow is:
back as semantic destination commands, whose server handler calls
`CharacterState::setTarget()` with the authoritative `LocationState`; arrival
function names are resolved through `ScriptEngine`.
6. Runtime characters apply the authoritative animation frame to loaded models and
6. The server invalidates its cached snapshot and advances the revision so runtime
presentation can observe the completed simulation tick.
7. Runtime characters read one immutable snapshot per operation, apply the
authoritative animation frame to loaded models, and
play audio, particles, and presentation callbacks.
7. Auxiliary dialogue, Lua, trigger, and teleport state is captured into `LocationGameState`.
8. The server increments its revision and invalidates the cached snapshot.
9. Rendering reads the bound local state; UI and external-style consumers read an immutable snapshot.
8. Auxiliary dialogue, Lua, trigger, and teleport state is captured into `LocationGameState`.
9. The server publishes those auxiliary changes in a later revision.
10. Rendering, UI, and external-style consumers read immutable snapshots.
Commands that update bound character or object state do not reload the whole location. This avoids replaying dialogue-node effects or Lua callbacks.
@@ -244,7 +284,7 @@ When a campaign starts:
- Existing campaign state and runtime locations are cleared.
- Quest definitions are loaded.
- A campaign-level player state is created under the `"player"` ID.
- The visual player is created and bound to that state.
- The visual player is created with its stable ID and resolves that state through snapshots.
### Location loading
@@ -254,6 +294,8 @@ When a location is loaded:
- Static configuration is loaded.
- NPC and interactive-object visuals are created against state owned by the location record.
- The shared campaign player is attached.
- Runtime visuals are created for other player IDs currently present in the
snapshot and are subsequently spawned or removed as snapshots change.
- Dialogue, Lua, navigation, trigger, teleport, and presentation callbacks are initialized.
- The runtime `Location` is registered privately with `InternalGameServer`.
@@ -278,8 +320,8 @@ unsupported versions are rejected rather than migrated.
- Pending interaction targets and character references use stable IDs.
- Immutable content such as meshes, initial taxi configuration, and object
definitions is reconstructed from campaign configuration.
- Configuration is loaded first and mutable saved state is overlaid in place, so
runtime render bindings keep stable addresses.
- Configuration is loaded first and mutable saved state is overlaid in place.
Snapshot-backed runtime objects then resolve the resulting state by stable ID.
Old user save slots are intentionally unsupported. Bundled start-state snapshots
must be regenerated with `Game::saveInitialState()` whenever the schema changes.
@@ -298,11 +340,14 @@ It now:
### Scripts
Lua remains an in-process single-player runtime. Character bindings submit semantic
commands through `Location`; the server invokes `CharacterState` operations on the
centrally owned state. NPC, interactive-object, trigger-zone, and teleport-zone
bindings address entities by configured ID. Object, inventory, quest, and
global-state bindings remain trusted local integrations.
Lua remains an in-process single-player runtime. Character, inventory, quest, and
campaign-global bindings submit semantic commands through `Location`; the server
invokes operations on centrally owned state. Snapshot queries provide global
values, inventory counts, item presence, and synchronous quest validation for the
existing Lua API. NPC,
interactive-object, trigger-zone, and teleport-zone bindings address entities by
configured ID. Other object and global-state bindings remain trusted local
integrations.
Each location script registers initialization, enter, and exit callbacks.
Initialization runs only when the location has no retained state; loading the Lua
@@ -313,13 +358,18 @@ The scalar Lua runtime state for each location is stored under `LocationGameStat
### Dialogue and cutscenes
Dialogue runtime state is captured in each `LocationGameState`. Dialogue and cutscene rendering remain local presentation systems.
Dialogue runtime state is captured in each `LocationGameState`. Dialogue global
flags use snapshot getter and command setter callbacks rather than retaining a
mutable map pointer. Dialogue and cutscene rendering remain local presentation
systems.
Dialogues and cutscenes are outside the planned multiplayer gameplay mode, but their existing single-player save and callback behavior is retained.
### Quests, inventory, and tutorial
These systems remain part of `GameState` for single-player compatibility. UI access now crosses the server interface. They can remain empty or disabled in multiplayer mode as planned.
These callback-free state systems remain part of `GameState` for single-player
compatibility. UI access, mutations, and presentation notifications cross the
server interface. They can remain empty or disabled in multiplayer mode as planned.
## Multiplayer Readiness
@@ -372,7 +422,11 @@ cmake --build proj-windows/build --config Debug --target ShadowOverBishkekDemo -
location-local NPC ID).
- Some top-level values remain mirrors for compatibility, notably player health used by the HUD and scripts.
- Dialogue and Lua are synchronized as auxiliary location state rather than fully networked subsystems.
- `Game` currently uses privileged internal-server APIs for local content construction. A remote client implementation will need a client-side runtime-location factory driven by configuration and snapshots.
- `Game` uses focused privileged internal-server APIs for local content
construction, but cannot access the aggregate or its state maps directly. The
private location-construction path still supplies `LocationGameState` to the
local object loader. A remote client implementation will need a client-side
runtime-location factory driven by configuration and snapshots.
## Main Files
@@ -383,8 +437,8 @@ cmake --build proj-windows/build --config Debug --target ShadowOverBishkekDemo -
- `src/Game.h` / `src/Game.cpp` — application host, campaign lifecycle, and runtime wiring.
- `src/Location.h` / `src/Location.cpp` — loaded location runtime, command routing,
interactions, collisions, scripts, and presentation-event consumption.
- `src/Character.h` / `src/Character.cpp` — passive character presentation bound to
a const authoritative `CharacterState`.
- `src/Character.h` / `src/Character.cpp` — passive character presentation resolving
immutable `CharacterState` snapshots by stable ID.
- `src/CharacterState.h` / `src/CharacterState.cpp` — deterministic character
simulation, navigation operations, combat state, and logical animation clock.
- `src/items/InteractiveObject.h` / `.cpp` — visual object runtime bound to central object state.