Decouple everything from globals
This commit is contained in:
325
PLANNING.md
Normal file
325
PLANNING.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# Per-Level GameState Architecture Refactoring Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Convert the current autoload-based global GameState system to a per-level instance-based architecture where each level scene has its own `LevelGameState` node with references to catalogues (resources) and its own save file.
|
||||
|
||||
## Current Architecture (To Be Replaced)
|
||||
|
||||
```
|
||||
project.godot autoloads:
|
||||
├─ BuffDatabase (autoload)
|
||||
├─ CurrencyDatabase (autoload)
|
||||
├─ GameState (autoload)
|
||||
└─ PrestigeManager (autoload)
|
||||
|
||||
tiny_sword.tscn:
|
||||
└─ Uses GameState autoload directly from any node
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
- Level scenes implicitly depend on global autoloads
|
||||
- Cannot have multiple levels with independent state
|
||||
- Catalogues auto-discover from filesystem (tightly coupled)
|
||||
- Save file path is hardcoded globally
|
||||
|
||||
## Target Architecture
|
||||
|
||||
```
|
||||
tiny_sword.tscn (root node)
|
||||
├─ LevelGameState (Node)
|
||||
│ ├─ @export var currency_catalogue: CurrencyCatalogue (Resource)
|
||||
│ ├─ @export var buff_catalogue: BuffCatalogue (Resource)
|
||||
│ ├─ @export var goal_catalogue: GoalCatalogue (Resource)
|
||||
│ ├─ @export var save_file_path: String = "user://tiny_sword_save.json"
|
||||
│ └─ PrestigeManager (Node child)
|
||||
│ └─ @export var game_state: LevelGameState
|
||||
├─ World (Node2D)
|
||||
│ ├─ GoldMine (CurrencyGenerator)
|
||||
│ │ └─ @onready var game_state = find_parent("LevelGameState")
|
||||
│ └─ ...other generators
|
||||
└─ UI (Control)
|
||||
├─ CurrencyTile (each: @export var game_state: LevelGameState)
|
||||
└─ ...
|
||||
```
|
||||
|
||||
## Catalogue Resources
|
||||
|
||||
All catalogues are `Resource` subclasses with explicit arrays populated in the editor:
|
||||
|
||||
### CurrencyCatalogue
|
||||
```gdscript
|
||||
class_name CurrencyCatalogue
|
||||
extends Resource
|
||||
|
||||
@export var currencies: Array[Currency] = []
|
||||
|
||||
func get_currency_by_id(id: StringName) -> Currency
|
||||
func get_all_ids() -> Array[StringName]
|
||||
```
|
||||
|
||||
**Location:** `res://docs/gyms/tiny_sword/currencies/tiny_sword_catalogue.tres`
|
||||
|
||||
### BuffCatalogue
|
||||
```gdscript
|
||||
class_name BuffCatalogue
|
||||
extends Resource
|
||||
|
||||
@export var buffs: Array[GeneratorBuffData] = []
|
||||
|
||||
func get_buff_by_id(id: StringName) -> GeneratorBuffData
|
||||
func get_all_ids() -> Array[StringName]
|
||||
func get_buffs_for_generator(gen_id: StringName) -> Array[GeneratorBuffData]
|
||||
```
|
||||
|
||||
**Location:** `res://docs/gyms/tiny_sword/buffs/tiny_sword_buffs.tres`
|
||||
|
||||
### GoalCatalogue
|
||||
```gdscript
|
||||
class_name GoalCatalogue
|
||||
extends Resource
|
||||
|
||||
@export var goals: Array[GoalData] = []
|
||||
|
||||
func get_goal_by_id(id: StringName) -> GoalData
|
||||
func get_all_ids() -> Array[StringName]
|
||||
```
|
||||
|
||||
**Location:** `res://docs/gyms/tiny_sword/goals/tiny_sword_goals.tres`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Create Catalogue Classes
|
||||
|
||||
**Files to create:**
|
||||
|
||||
1. `core/currency_catalogue.gd`
|
||||
2. `core/buff_catalogue.gd`
|
||||
3. `core/goal_catalogue.gd`
|
||||
|
||||
**Purpose:** Resource classes that hold arrays of game data and provide lookup methods.
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Create LevelGameState
|
||||
|
||||
**File to create:** `core/level_game_state.gd`
|
||||
|
||||
**Key changes from current `game_state.gd`:**
|
||||
|
||||
| Current | New |
|
||||
|---------|-----|
|
||||
| `extends Node` (autoload) | `extends Node` (instance) |
|
||||
| `CurrencyDatabase.get_known_currency_ids()` | `currency_catalogue.get_all_ids()` |
|
||||
| `BuffDatabase` calls | `buff_catalogue` calls |
|
||||
| Static `file_name` | `@export var save_file_path: String` |
|
||||
| Filesystem goal discovery | `goal_catalogue.goals` array |
|
||||
| Buff auto-registration from BuffDatabase | Buff registration from `buff_catalogue` |
|
||||
|
||||
**New properties:**
|
||||
```gdscript
|
||||
@export var currency_catalogue: CurrencyCatalogue
|
||||
@export var buff_catalogue: BuffCatalogue
|
||||
@export var goal_catalogue: GoalCatalogue
|
||||
@export var save_file_path: String = "user://level_save.json"
|
||||
```
|
||||
|
||||
**Methods to port:**
|
||||
- All currency methods (`add_currency`, `spend_currency`, `get_currency_amount_by_id`, etc.)
|
||||
- All generator methods (`register_generator`, `get_generator_state`, etc.)
|
||||
- All buff methods (`register_buff`, `get_buff_level`, `get_effective_multiplier`, etc.)
|
||||
- All goal methods (`register_goal`, `is_goal_completed`, `evaluate_all_goals`, etc.)
|
||||
- Save/load (`save_game()`, `load_game()`) with configurable path
|
||||
- Prestige methods (`reset_for_prestige`, `emit_currency_changed_for_all`)
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Convert PrestigeManager
|
||||
|
||||
**File to modify:** `core/prestige/prestige_manager.gd`
|
||||
|
||||
**Changes:**
|
||||
- Remove autoload registration from `project.godot`
|
||||
- Add `@export var game_state: LevelGameState`
|
||||
- Change `/root/PrestigeManager` lookup to use direct reference
|
||||
- Make it a child node of `LevelGameState` in scenes
|
||||
|
||||
**Key fix in `_get_prestige_multiplier()`:**
|
||||
```gdscript
|
||||
# Old: get_node_or_null("/root/PrestigeManager")
|
||||
# New: Direct access through injected reference
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Update CurrencyGenerator
|
||||
|
||||
**File to modify:** `core/generator/currency_generator.gd`
|
||||
|
||||
**Changes:**
|
||||
- Replace all `GameState.` calls with `game_state.` calls
|
||||
- Add `@onready var game_state: LevelGameState = find_parent("LevelGameState")`
|
||||
- Update signal connections: `game_state.currency_changed.connect(...)`
|
||||
- Update `_get_prestige_multiplier()` to use injected reference
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Update CurrencyTile
|
||||
|
||||
**File to modify:** `currency_label.gd`
|
||||
|
||||
**Changes:**
|
||||
```gdscript
|
||||
@export var game_state: LevelGameState # ← NEW
|
||||
|
||||
func _ready() -> void:
|
||||
if game_state == null:
|
||||
push_error("CurrencyTile '%s' missing game_state reference" % name)
|
||||
return
|
||||
|
||||
_currency_id = game_state.get_currency_id(currency)
|
||||
# ... rest of initialization
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 6: Update project.godot
|
||||
|
||||
**Remove autoloads:**
|
||||
```ini
|
||||
# REMOVE these lines:
|
||||
# BuffDatabase="*res://core/buff_database.gd"
|
||||
# CurrencyDatabase="*res://core/currency_database.gd"
|
||||
# GameState="*uid://d2j7tvlgxr2jp"
|
||||
# PrestigeManager="*res://core/prestige/prestige_manager.gd"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 7: Update tiny_sword.tscn
|
||||
|
||||
**Add LevelGameState node:**
|
||||
```ini
|
||||
[node name="LevelGameState" type="Node" parent="."]
|
||||
currency_catalogue = ExtResource("currency_catalogue_tres")
|
||||
buff_catalogue = ExtResource("buff_catalogue_tres")
|
||||
goal_catalogue = ExtResource("goal_catalogue_tres")
|
||||
save_file_path = "user://tiny_sword_save.json"
|
||||
```
|
||||
|
||||
**Add PrestigeManager as child:**
|
||||
```ini
|
||||
[node name="PrestigeManager" type="Node" parent="LevelGameState"]
|
||||
game_state = NodePath("..") # Reference to LevelGameState
|
||||
```
|
||||
|
||||
**Wire up children:**
|
||||
- Each `CurrencyTile`: `game_state = LevelGameState`
|
||||
- Each `CurrencyGenerator`: auto-discovers via `find_parent()`
|
||||
|
||||
---
|
||||
|
||||
### Step 8: Create Catalogue Resources
|
||||
|
||||
**Create `.tres` files via Godot editor:**
|
||||
|
||||
1. `res://docs/gyms/tiny_sword/currencies/tiny_sword_catalogue.tres`
|
||||
- `currencies = [gold, worker, food, wood]`
|
||||
|
||||
2. `res://docs/gyms/tiny_sword/buffs/tiny_sword_buffs.tres`
|
||||
- `buffs = [all relevant buffs for this level]`
|
||||
|
||||
3. `res://docs/gyms/tiny_sword/goals/tiny_sword_goals.tres`
|
||||
- `goals = [all relevant goals for this level]`
|
||||
|
||||
---
|
||||
|
||||
### Step 9: Clean Up
|
||||
|
||||
**Files to delete:**
|
||||
- `core/currency_database.gd` (replaced by `CurrencyCatalogue`)
|
||||
- `core/buff_database.gd` (replaced by `BuffCatalogue`)
|
||||
- `core/game_state.gd` (replaced by `level_game_state.gd`)
|
||||
|
||||
---
|
||||
|
||||
## File Summary
|
||||
|
||||
### Files to Create (4)
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `core/currency_catalogue.gd` | Resource with `Array[Currency]` + lookup methods |
|
||||
| `core/buff_catalogue.gd` | Resource with `Array[GeneratorBuffData]` + lookup methods |
|
||||
| `core/goal_catalogue.gd` | Resource with `Array[GoalData]` + lookup methods |
|
||||
| `core/level_game_state.gd` | Complete GameState implementation without autoload dependencies |
|
||||
|
||||
### Files to Modify (4)
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `core/prestige/prestige_manager.gd` | Remove autoload, add `@export var game_state` |
|
||||
| `core/generator/currency_generator.gd` | Add `find_parent()` lookup, replace `GameState.` |
|
||||
| `currency_label.gd` | Add `@export var game_state: LevelGameState` |
|
||||
| `project.godot` | Remove 4 autoloads |
|
||||
|
||||
### Files to Delete (3)
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `core/currency_database.gd` | Replaced by `CurrencyCatalogue` |
|
||||
| `core/buff_database.gd` | Replaced by `BuffCatalogue` |
|
||||
| `core/game_state.gd` | Replaced by `level_game_state.gd` |
|
||||
|
||||
### Scene Updates
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `tiny_sword.tscn` | Add `LevelGameState` node, add `PrestigeManager` child, wire catalogues |
|
||||
| Create `.tres` resources | `tiny_sword_catalogue.tres`, `tiny_sword_buffs.tres`, `tiny_sword_goals.tres` |
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Decoupled levels**: Each level can exist independently without global dependencies
|
||||
2. **Multiple levels**: Can support multiple levels with independent state (future feature)
|
||||
3. **Explicit dependencies**: All dependencies are visible in the inspector
|
||||
4. **Resource-based catalogues**: Content is defined as editor-authorable resources
|
||||
5. **Per-level saves**: Each level has its own save file path
|
||||
6. **Better testing**: Easier to test individual levels in isolation
|
||||
|
||||
---
|
||||
|
||||
## Risks and Considerations
|
||||
|
||||
1. **Signal connections**: UI components must be manually wired to `LevelGameState` instance
|
||||
2. **PrestigeManager access**: Must ensure proper reference injection
|
||||
3. **Catalogue population**: Catalogue resources must be created and populated in editor
|
||||
4. **Migration**: Existing save files may need migration logic
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. Create catalogue classes (3 files)
|
||||
2. Create level_game_state.gd (the core refactor)
|
||||
3. Update prestige_manager.gd (remove autoload dependency)
|
||||
4. Update currency_generator.gd (add game_state injection)
|
||||
5. Update currency_label.gd (add game_state export)
|
||||
6. Update project.godot (remove autoloads)
|
||||
7. Create catalogue resources (.tres files via editor)
|
||||
8. Update tiny_sword.tscn (wire everything together)
|
||||
9. Delete old files (cleanup)
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
- [ ] Step 1: Create catalogue classes
|
||||
- [ ] Step 2: Create level_game_state.gd
|
||||
- [ ] Step 3: Update prestige_manager.gd
|
||||
- [ ] Step 4: Update currency_generator.gd
|
||||
- [ ] Step 5: Update currency_label.gd
|
||||
- [ ] Step 6: Update project.godot
|
||||
- [ ] Step 7: Create catalogue resources (manual editor work)
|
||||
- [ ] Step 8: Update tiny_sword.tscn (manual editor work)
|
||||
- [ ] Step 9: Delete old files
|
||||
Reference in New Issue
Block a user