Some changes
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user