Some changes

This commit is contained in:
2026-06-11 19:59:31 -05:00
parent 2f2ea060b1
commit eec429147b
150 changed files with 16615 additions and 262 deletions
+69
View File
@@ -0,0 +1,69 @@
# Architecture
## Singletons (Autoloads)
Never instantiate these — access only via the global handle.
| Handle | Role |
|---|---|
| `SCENE` | Current scene state; `SCENE.setScene(SceneSingleton.SceneType.X)` to switch |
| `TRANSITION` | Fade in/out; `TRANSITION.fade(FadeType, duration, color)` |
| `BATTLE` | Battle state and fighter map |
| `PARTY` | Party members (`PARTY.getFullParty()`) and `PARTY.BACKPACK` inventory |
| `OVERWORLD` | Map switching with threaded loading |
| `COOKING` | Cooking mini-game lifecycle |
| `SAVE` | Persistence (stub) |
| `QUEST` | Quest management (stub) |
| `CUTSCENE` | Cutscene global (stub) |
| `DialogueManager` | godot_dialogue_manager v3.10.4 — parses and steps through `.dialogue` files |
| `UI` | Root UI accessor — `UI.TEXTBOX`, `UI.DEBUG_MENU` |
## Scene Graph
```
RootScene (Node3D)
└─ overworld / battle / cooking / initial ← one shown at a time
RootUI (Control, always visible)
└─ VNTextbox, DebugMenu
```
`RootScene` listens to `SCENE.sceneChanged` and shows/hides the correct sub-tree.
## Cutscene / Event Queue
`Cutscene` is the universal sequencing engine. It holds an `Array[Dictionary]` queue; each entry has a `"function": Callable` plus arbitrary data keys.
**Return codes from a callable:**
- `Cutscene.CUTSCENE_CONTINUE` — advance to next item
- `Cutscene.CUTSCENE_END` — stop the cutscene
- An integer index — jump to that position
**Insertion positions:**
- `Cutscene.CUTSCENE_ADD_END` — append (default)
- `Cutscene.CUTSCENE_ADD_NEXT` — insert immediately after current
**Callable pattern** — every action class exposes a pair:
```gdscript
# The actual callable (static, takes params:Dictionary, returns int)
static func myCallable(params:Dictionary) -> int:
...
return Cutscene.CUTSCENE_CONTINUE
# Factory that builds the dictionary for addCallable()
static func getMyCallable(arg) -> Dictionary:
return { "function": myCallable, "myArg": arg }
```
## Data Registry Pattern
Static registries (Item, Recipe) follow this pattern:
1. `enum Id { NULL, ... }` — typed identifier
2. `static var DATA:Array = []` — indexed by Id value
3. `static func define(params) -> Dictionary` — called at class load to populate `DATA`
4. `static var FOO = define({...})` — registers the entry as a static var
5. `static func get*(id) -> *` — typed accessors
## `_init(params:Dictionary)` Pattern
Non-Node data classes (`BattleFighter`, `BattleDecision`, `ItemStack`, etc.) use a single `params` dictionary constructor with `.get('key', default)` for optional fields.
+27
View File
@@ -0,0 +1,27 @@
# Code Style
## Naming
| Kind | Convention | Examples |
|---|---|---|
| Variables & functions | camelCase | `fighterMap`, `startBattle()`, `getFullParty()` |
| Classes, enums, enum values | PascalCase | `BattleFighter`, `FighterTeam`, `ALLY` |
| Constants & static data | SCREAMING_SNAKE_CASE | `CUTSCENE_CONTINUE`, `ITEM_DATA`, `PARTY_JOHN` |
| Private/internal helpers | leading underscore | `_onConversationInteract()`, `_applyGravity()` |
No trailing underscores. Leading underscore = "don't call this externally."
## Formatting
- **2-space indent** — not 4, not tabs
- **Type annotations everywhere**: `var health:int`, `func damage(amount:int, crit:bool) -> void:`
- **No space** between name and type: `var foo:int` not `var foo : int`
- Blank lines between logical sections; comment headers label groups: `# Health`, `# Signals`
## General Rules
- `assert()` for invariants and preconditions — prefer over silent failures
- `params:Dictionary` for multi-argument constructors/callables; access with `.get('key', default)` or `.has('key')` guards
- `match` for enum dispatch; `if/elif` chains for non-exhaustive checks
- `continue` / early `return` to flatten nesting instead of deep else-branches
- Avoid long inline lambdas — extract named static functions when logic is non-trivial
+89
View File
@@ -0,0 +1,89 @@
# Dialogue System
All authored text — NPC conversations, item pickup messages, battle narration — is written in `.dialogue` files and played back via `DialogueManager` (godot_dialogue_manager v3.10.4).
## Plugin
- Autoload: `DialogueManager` (registered in `project.godot`)
- Plugin must be enabled in Godot → Project Settings → Plugins → Dialogue Manager
- `.dialogue` files are imported as `DialogueResource` once the plugin is active
- Dialogue files live in `dialogue/` organised by category (`npc/`, `item/`, `battle/`)
## DialogueAction — the Cutscene bridge
[cutscene/dialogue/DialogueAction.gd](../../cutscene/dialogue/DialogueAction.gd) is the glue between the `Cutscene` queue and `DialogueManager`. It runs a `.dialogue` file through `VNTextbox` line-by-line and returns `CUTSCENE_CONTINUE` when the last line is dismissed.
```gdscript
# Add a dialogue step to any Cutscene
cutscene.addCallable(DialogueAction.getDialogueCallable(
load("res://dialogue/npc/test.dialogue"),
"start", # title to begin from
[entity] # extra_game_states: objects/dicts accessible in the .dialogue file
))
```
Movement is blocked automatically while dialogue runs because `VNTextbox` is open (`EntityMovement._canMove()` checks `UI.TEXTBOX.isClosed`).
## Writing .dialogue files
```
~ title_name # entry point / jump target
Speaker: Line of dialogue.
Another line with no speaker.
~ another_section
Speaker: Variables resolve inline: {{some_property}}.
=> END # end this dialogue
```
**Key syntax:**
- `~ title` — section anchor; use as the `title` argument to `DialogueAction`
- `=> title` — jump to another section; `=> END` ends the dialogue
- `{{variable}}` — resolves a property from any autoload or `extra_game_states` object
- `do AUTOLOAD.method()` — call a method on any autoload (e.g. `do PARTY.BACKPACK.addStack(stack)`)
- `set AUTOLOAD.property = value` — set a property
- `- Option text` — response branch (indented lines handle each branch)
- `[if condition]` — conditional line or response
**Mutations fire automatically** before the next dialogue line is returned — you do not handle them manually in GDScript.
## Passing runtime data to dialogue
Use `extra_game_states` to expose GDScript objects to `{{variable}}` tokens:
```gdscript
class ItemDialogueState:
var item_name:String
var quantity:int
DialogueAction.getDialogueCallable(resource, 'start', [ItemDialogueState.new("Potato", 1)])
```
Then in the `.dialogue` file:
```
Obtained {{item_name}} x{{quantity}}.
```
## NPC entities
Set these two exports on an Entity node whose `interactType = CONVERSATION`:
| Export | Purpose |
|---|---|
| `dialogueResource:DialogueResource` | The `.dialogue` file to run |
| `dialogueTitle:String` | Title (section) to start from (default `"start"`) |
After first enabling the plugin and reimporting, assign `dialogueResource` directly in the Inspector. Until then, assign it in code (see [TestMap.gd](../../overworld/map/TestMap.gd) for the pattern).
## Dialogue files
| File | Used by |
|---|---|
| `dialogue/npc/test.dialogue` | TestMap NPC (NotPlayer) |
| `dialogue/item/pickup.dialogue` | `ItemAction` — item pickup text with `{{item_name}}` and `{{quantity}}` |
| `dialogue/battle/narration.dialogue` | `BattleCutsceneAction` — move announcements, victory/defeat |
## Response branching (current limitation)
`DialogueAction` auto-selects the **first allowed response** when a line has multiple options. There is no in-game response UI yet. To add one, replace the auto-select block in `DialogueAction.dialogueCallable` with a call to your response menu and `await` its selection signal.
+15
View File
@@ -0,0 +1,15 @@
# Stubs & Incomplete Systems
These exist in the codebase but have no real implementation yet. Don't assume they work.
| File / Symbol | Status |
|---|---|
| `Save.gd` | No actual serialization — all persistence is a stub |
| `Quest.gd` | Empty singleton |
| `CutsceneSingleton.gd` | Minimal stub |
| `BattleFighter.getAIDecision()` | Always returns `null` |
| `BattleDecision.execute()` | Stub |
| `BattleItem.perform()` | Stub |
| `CookingScene.tscn` | Placeholder UI only |
| Response branching in `DialogueAction` | Auto-selects first allowed response; no response UI exists yet |
| `Pause.gd` | Logic commented out |
+26
View File
@@ -0,0 +1,26 @@
# System Conventions
## Battle
- Fighters live in `BATTLE.fighterMap: Dictionary[BattlePosition, BattleFighter]`
- `BattleFighter` is pure data (no Node); `BattleFighterScene` is the 3D visual
- Battle flow is driven by `BattleCutsceneAction.playerDecisionCallable` which loops via `CUTSCENE_ADD_END`
- New moves → add static preset in [battle/fighter/BattleMove.gd](../../battle/fighter/BattleMove.gd), add `perform()` logic in [battle/action/BattleMove.gd](../../battle/action/BattleMove.gd)
## Entities (Overworld)
- All interactable world objects extend `Entity` (CharacterBody3D)
- Interaction type set via `@export var interactType:InteractType`
- Interaction routing lives in `EntityInteractableArea.onInteract()` — add new `InteractType` values there
- NPC conversation entities: set `interactType = CONVERSATION`, assign `dialogueResource` (a `.dialogue` file) and `dialogueTitle` (section to start from, default `"start"`)
## Items
- Register new items in [item/Item.gd](../../item/Item.gd) — add to `Id` enum and call `itemDefine()`
- Item ID order in the enum **must match** insertion order into `ITEM_DATA`
## UI
- `UI.TEXTBOX.setTextAndWait(text)` — show dialogue and await player dismiss (use `await`)
- Movement is blocked automatically when `UI.TEXTBOX` is visible (`EntityMovement._canMove()` checks this)
- Menus extend `ClosableMenu` for open/close/toggle + `closed`/`opened` signals