Cleanup and planning
This commit is contained in:
449
README.md
449
README.md
@@ -1,261 +1,190 @@
|
|||||||
# Idles - Systems Architecture Report
|
# Idles - Godot 4.6 Idle Game
|
||||||
|
|
||||||
|
A modular idle/incremental game prototype featuring a decoupled buff system, multi-target buffs with wildcard support, and goal-based progression.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the project
|
||||||
|
"$GODOT_BIN" --path .
|
||||||
|
|
||||||
|
# Headless mode (lint/smoke test)
|
||||||
|
"$GODOT_BIN" --headless --path . --quit
|
||||||
|
|
||||||
|
# Check single script syntax
|
||||||
|
"$GODOT_BIN" --headless --check-only --script res://core/big_number.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Core Systems
|
||||||
|
|
||||||
|
| System | Location | Purpose |
|
||||||
|
|--------|----------|---------|
|
||||||
|
| **BigNumber** | `core/big_number.gd` | Handles huge numbers (mantissa + exponent) for idle game math |
|
||||||
|
| **Currency** | `core/currency/`, `core/currency_database.gd` | Catalog system for all in-game currencies |
|
||||||
|
| **GameState** | `core/game_state.gd` | Central state authority (currencies, generators, buffs, persistence) |
|
||||||
|
| **Generators** | `core/generator/` | Production mechanics (auto cycles, clicks, purchases) |
|
||||||
|
| **Buffs** | `core/generator/generator_buff_data.gd` | Global multipliers with multi-target support |
|
||||||
|
| **Goals** | `core/goals/` | Unlock conditions for generators and buffs |
|
||||||
|
| **Prestige** | `core/prestige/` | Reset-with-bonus mechanics |
|
||||||
|
|
||||||
|
### Autoload Singletons
|
||||||
|
|
||||||
|
| Autoload | Script | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `GameState` | `core/game_state.gd` | Central state management, save/load |
|
||||||
|
| `CurrencyDatabase` | `core/currency_database.gd` | Currency catalog discovery |
|
||||||
|
| `PrestigeManager` | `core/prestige/prestige_manager.gd` | Prestige system |
|
||||||
|
|
||||||
|
### Data Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
sandbox/
|
||||||
|
├── currencies/ # Currency resources (gold, food, wood, worker, etc.)
|
||||||
|
├── generators/ # Generator configuration (.tres files)
|
||||||
|
├── buffs/ # Buff definitions with target_ids
|
||||||
|
├── goals/ # Unlock goal definitions
|
||||||
|
└── prestige/ # Prestige configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Architecture (Post-Refactoring)
|
||||||
|
|
||||||
|
### Decoupled Buff System
|
||||||
|
|
||||||
|
Buffs are now **globally registered** and **multi-target capable**:
|
||||||
|
|
||||||
|
- **Global Levels**: Buff levels are shared across all targets
|
||||||
|
- **Multi-Target**: A single buff resource can affect multiple generators
|
||||||
|
- **Wildcards**: `target_ids: ["*"]` applies to all current + future generators
|
||||||
|
- **Auto-Discovery**: Buffs loaded from `res://sandbox/buffs/` at runtime
|
||||||
|
- **Goal-Based Activation**: Buffs unlock automatically when goals are met
|
||||||
|
|
||||||
|
### Key Design Decisions
|
||||||
|
|
||||||
|
| Feature | Implementation |
|
||||||
|
|---------|----------------|
|
||||||
|
| **Buff Levels** | Global (shared across all targets) |
|
||||||
|
| **Buff Registry** | Auto-discovered from `res://sandbox/buffs/` |
|
||||||
|
| **Wildcards** | `["*"]` matches all current AND future generators |
|
||||||
|
| **Default State** | Inactive until goal met |
|
||||||
|
| **Persistence** | Buff definitions serialized to save |
|
||||||
|
| **Prestige** | All buff levels reset to 0 |
|
||||||
|
| **Activation** | Automatic via goals, no manual toggle |
|
||||||
|
|
||||||
|
### API Changes
|
||||||
|
|
||||||
|
**Old API (per-generator state):**
|
||||||
|
```gdscript
|
||||||
|
GameState.register_generator_buff(generator_id, buff_id, level)
|
||||||
|
GameState.get_generator_buff_level(generator_id, buff_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
**New API (global buff state):**
|
||||||
|
```gdscript
|
||||||
|
GameState.register_buff(buff: GeneratorBuffData)
|
||||||
|
GameState.get_buff(buff_id) -> GeneratorBuffData
|
||||||
|
GameState.get_buffs_for_generator(generator_id) -> Array[GeneratorBuffData]
|
||||||
|
GameState.get_effective_multiplier(generator_id, kind) -> float
|
||||||
|
```
|
||||||
|
|
||||||
|
## Save Format
|
||||||
|
|
||||||
|
**Version 2 (current):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"save_format_version": 2,
|
||||||
|
"currencies": { ... },
|
||||||
|
"generator_states": { ... },
|
||||||
|
"buff_definitions": { ... },
|
||||||
|
"buff_levels": { "farm_flux": 5 },
|
||||||
|
"buff_unlocked": { "farm_flux": true },
|
||||||
|
"buff_active": { "farm_flux": true },
|
||||||
|
"last_save_time": 1234567890
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Breaking change from v1 - old saves are incompatible.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Adding New Content
|
||||||
|
|
||||||
|
**New Currency:**
|
||||||
|
1. Create `res://sandbox/currencies/<name>.tres` (Currency resource)
|
||||||
|
2. Set `id`, `display_name`, `icon`
|
||||||
|
3. Auto-discovered at runtime
|
||||||
|
|
||||||
|
**New Generator:**
|
||||||
|
1. Create `res://sandbox/generators/<name>.tres` (CurrencyGeneratorData)
|
||||||
|
2. Configure production stats, costs, purchase currency
|
||||||
|
3. Optionally add `unlock_goal`
|
||||||
|
4. Add `CurrencyGenerator` node to scene
|
||||||
|
|
||||||
|
**New Buff:**
|
||||||
|
1. Create `res://sandbox/buffs/<name>.tres` (GeneratorBuffData)
|
||||||
|
2. Set `target_ids` (e.g., `["farm", "forestry"]` or `["*"]`)
|
||||||
|
3. Configure effect, cost, unlock goal
|
||||||
|
4. Auto-registered at runtime
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run project and test manually
|
||||||
|
"$GODOT_BIN" --path .
|
||||||
|
|
||||||
|
# No automated test framework committed yet
|
||||||
|
# (GUT not included in repository)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known Issues / TODOs
|
||||||
|
|
||||||
|
- [ ] Manual save triggers not implemented (auto-save needed)
|
||||||
|
- [ ] `CurrencyTile` references `GameState.GOLD_CURRENCY_ID` (constant missing)
|
||||||
|
- [ ] `GeneratorPanel` has missing `mouse_entered`/`mouse_exited` handlers
|
||||||
|
- [ ] Some buffs configured locked with no unlock path
|
||||||
|
- [ ] No automated test suite
|
||||||
|
|
||||||
|
## Refactoring Status
|
||||||
|
|
||||||
|
See `REFACTORING.md` for:
|
||||||
|
- Complete refactoring plan (8 phases)
|
||||||
|
- Implementation timeline (15-23 hours estimated)
|
||||||
|
- Testing checklist
|
||||||
|
- Rollback procedures
|
||||||
|
|
||||||
|
### Completed Phases
|
||||||
|
- Phase 1: GeneratorBuffData with target_ids support
|
||||||
|
|
||||||
|
### Pending Phases
|
||||||
|
- Phase 2-8: GameState extension, save format update, data migration
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### BigNumber System
|
||||||
|
- **Representation**: Mantissa (float) + Exponent (int)
|
||||||
|
- **Range**: Handles values up to ~10^308 (float precision limit)
|
||||||
|
- **Operations**: Add, subtract, multiply, divide, compare
|
||||||
|
- **Optimization**: `add_in_place()` for performance-critical loops
|
||||||
|
|
||||||
|
### Production Formulas
|
||||||
|
```
|
||||||
|
Cost for N items: base * ratio^k * ((ratio^n - 1) / (ratio - 1))
|
||||||
|
Max affordable: log((currency*(ratio-1)/(base*ratio^k)) + 1) / log(ratio)
|
||||||
|
Production: base_productivity * owned * run_mult * milestone_mult * purchased_mult
|
||||||
|
```
|
||||||
|
|
||||||
|
### Buff Multiplier Stacking
|
||||||
|
Multiple buffs of the same kind stack **multiplicatively**:
|
||||||
|
```
|
||||||
|
total_multiplier = buff1_effect * buff2_effect * buff3_effect * ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Internal prototype - no external license applied.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Scope Covered
|
*Last updated: Refactoring plan documented*
|
||||||
|
*Godot version: 4.6*
|
||||||
This report covers all tracked files in the repository (62 total at time of analysis), including:
|
|
||||||
|
|
||||||
1. Project configuration.
|
|
||||||
2. Core runtime scripts.
|
|
||||||
3. Gameplay/economy systems.
|
|
||||||
4. Goal/unlock systems.
|
|
||||||
5. UI adapter scripts and scenes.
|
|
||||||
6. Data resources (`.tres`).
|
|
||||||
7. Technical docs and metadata/import files.
|
|
||||||
|
|
||||||
The focus below is on behavior-bearing files and how they interact.
|
|
||||||
|
|
||||||
## Architecture Summary
|
|
||||||
|
|
||||||
1. Global startup and singleton wiring is defined in `project.godot`.
|
|
||||||
2. Two autoload services power the runtime: `CurrencyDatabase` and `GameState`.
|
|
||||||
3. `BigNumber` is the numeric backbone used by currency amounts, costs, goals, production, and persistence.
|
|
||||||
4. Generator behavior is runtime-driven by `CurrencyGenerator`, parameterized by `CurrencyGeneratorData` and `GeneratorBuffData` resources.
|
|
||||||
5. Goal-based progression uses reusable goal primitives (`GoalData`, `GoalRequirementData`) directly on generator data (`CurrencyGeneratorData.unlock_goal`).
|
|
||||||
6. UI scripts are thin adapters listening to `GameState` signals.
|
|
||||||
|
|
||||||
## Core Systems
|
|
||||||
|
|
||||||
### 1) Numeric System (`BigNumber`)
|
|
||||||
|
|
||||||
File: `core/big_number.gd`
|
|
||||||
|
|
||||||
`BigNumber` stores values as mantissa + exponent (scientific notation style), normalizes them, and provides:
|
|
||||||
|
|
||||||
1. Arithmetic operations (`add`, `subtract`, `multiply`, `divide`).
|
|
||||||
2. In-place fast accumulation (`add_in_place`) for frame-loop performance.
|
|
||||||
3. Safe comparisons (`compare_to`, `is_greater_than`, etc.).
|
|
||||||
4. Ratio conversion for progress UI (`get_ratio`).
|
|
||||||
5. Display formatting (`to_string_sci`, `to_string_suffix`).
|
|
||||||
6. JSON-friendly serialization/deserialization.
|
|
||||||
|
|
||||||
This class is used everywhere currency values move.
|
|
||||||
|
|
||||||
### 2) Currency Catalog System
|
|
||||||
|
|
||||||
Files: `core/currency/currency.gd`, `core/currency_database.gd`, `idles/currencies/*.tres`
|
|
||||||
|
|
||||||
`CurrencyDatabase` scans `res://idles/currencies`, loads typed `Currency` resources, normalizes IDs, and resolves names/icons.
|
|
||||||
|
|
||||||
Current data assets:
|
|
||||||
|
|
||||||
1. `idles/currencies/magic.tres` (`id = magic`).
|
|
||||||
2. `idles/currencies/knowledge.tres` (`id = knowledge`).
|
|
||||||
|
|
||||||
This creates a data-driven currency catalog that `GameState`, goals, and UI can query consistently.
|
|
||||||
|
|
||||||
### 3) Global State + Persistence (`GameState`)
|
|
||||||
|
|
||||||
File: `core/game_state.gd`
|
|
||||||
|
|
||||||
`GameState` is the central state authority and signal bus.
|
|
||||||
|
|
||||||
It owns:
|
|
||||||
|
|
||||||
1. Current currency map by currency ID.
|
|
||||||
2. Total acquired currency map by currency ID.
|
|
||||||
3. Generator states (`owned`, `purchased_count`, `unlocked`, `available`).
|
|
||||||
4. Generator buff levels.
|
|
||||||
5. Generator buff unlocked flags.
|
|
||||||
6. `last_save_time`.
|
|
||||||
|
|
||||||
Important signals:
|
|
||||||
|
|
||||||
1. `currency_changed`.
|
|
||||||
2. `generator_state_changed`.
|
|
||||||
3. `generator_buff_level_changed`.
|
|
||||||
4. `generator_buff_unlocked_changed`.
|
|
||||||
|
|
||||||
Persistence:
|
|
||||||
|
|
||||||
1. Save path: `user://idle_save.json`.
|
|
||||||
2. Save payload includes currencies, generator states, buff levels, buff unlocked map, and timestamp.
|
|
||||||
3. Load path includes defensive sanitization and backward-compatible defaulting.
|
|
||||||
|
|
||||||
### 4) Generator Economy Runtime
|
|
||||||
|
|
||||||
Files: `core/generator/currency_generator.gd`, `core/generator/currency_generator_data.gd`, `core/generator/currency_generator.tscn`
|
|
||||||
|
|
||||||
`CurrencyGenerator` implements active gameplay behavior:
|
|
||||||
|
|
||||||
1. Auto production cycles (`_process`, cycle accumulation).
|
|
||||||
2. Click/hover grants with cooldown.
|
|
||||||
3. Buy-one and buy-max generator purchases.
|
|
||||||
4. Buff purchase logic and buff effect application.
|
|
||||||
5. State registration and lookup through `GameState`.
|
|
||||||
|
|
||||||
`CurrencyGeneratorData` provides formulas and tuning knobs:
|
|
||||||
|
|
||||||
1. Exponential cost growth.
|
|
||||||
2. Bulk-buy geometric cost.
|
|
||||||
3. Max-affordable estimate.
|
|
||||||
4. Milestone multipliers.
|
|
||||||
5. Purchased-count multiplier.
|
|
||||||
6. Production/cycle and production/second helpers.
|
|
||||||
7. ROI-like helpers (`payback_seconds`, `income_to_cost_ratio`).
|
|
||||||
8. Optional generator unlock goal (`unlock_goal`).
|
|
||||||
|
|
||||||
### 5) Buff System
|
|
||||||
|
|
||||||
Files: `core/generator/generator_buff_data.gd`, `idles/buffs/*.tres`
|
|
||||||
|
|
||||||
Buff kinds:
|
|
||||||
|
|
||||||
1. Auto production multiplier.
|
|
||||||
2. Manual click multiplier.
|
|
||||||
3. Resource purchase (instant grant on buff buy).
|
|
||||||
|
|
||||||
Each buff resource defines:
|
|
||||||
|
|
||||||
1. Unlock rules (`unlocked` and optional `unlock_goal`).
|
|
||||||
2. Effect scaling (`base_effect`, `effect_increment`).
|
|
||||||
3. Cost scaling (`base_cost`, `cost_multiplier`).
|
|
||||||
4. Optional target currency and resource grant scaling.
|
|
||||||
|
|
||||||
### 6) Goals and Unlock Progression
|
|
||||||
|
|
||||||
Files:
|
|
||||||
|
|
||||||
1. `core/goals/goal_requirement_data.gd`
|
|
||||||
2. `core/goals/goal_data.gd`
|
|
||||||
3. `core/generator/currency_generator_data.gd`
|
|
||||||
4. `core/generator/currency_generator.gd`
|
|
||||||
5. `idles/goals/*.tres`
|
|
||||||
|
|
||||||
Key behavior:
|
|
||||||
|
|
||||||
1. Goal requirements validate currency + target amount.
|
|
||||||
2. Requirement completion checks **total acquired currency**, not current wallet.
|
|
||||||
3. Each `CurrencyGenerator` evaluates its own `data.unlock_goal` on startup and on `currency_changed`.
|
|
||||||
4. If the goal is met, it sets both `unlocked = true` and `available = true`, then emits `goal_achieved(generator_id, goal_id)`.
|
|
||||||
|
|
||||||
### 7) UI Adapter Layer
|
|
||||||
|
|
||||||
Files:
|
|
||||||
|
|
||||||
1. `generator_container.gd` + `generator_container.tscn`.
|
|
||||||
2. `generator_buff_tile.gd` + `generator_buff_tile.tscn`.
|
|
||||||
3. `currency_label.gd` + `currency_tile.tscn`.
|
|
||||||
4. `big_number_progress_bar.gd` + `big_number_progress_bar.tscn`.
|
|
||||||
5. `core/generator-unlock-goals/goals_debug_ui.gd` + `.tscn`.
|
|
||||||
|
|
||||||
These scripts primarily:
|
|
||||||
|
|
||||||
1. Subscribe to `GameState` and generator signals.
|
|
||||||
2. Convert runtime values to UI strings/states.
|
|
||||||
3. Trigger user actions (buy generator, buy buff, debug unlock).
|
|
||||||
|
|
||||||
The architecture is mostly event-driven and avoids polling for state refresh.
|
|
||||||
|
|
||||||
## Scene Composition
|
|
||||||
|
|
||||||
### Active Gameplay Prototype
|
|
||||||
|
|
||||||
File: `docs/museums/generator_museum.tscn`
|
|
||||||
|
|
||||||
It composes:
|
|
||||||
|
|
||||||
1. `MagicGenerator` instance.
|
|
||||||
2. `KnowledgeGenerator` instance (initially hidden and starts locked via data).
|
|
||||||
3. Currency tiles for `magic` and `knowledge`.
|
|
||||||
4. Goals debug UI panel.
|
|
||||||
|
|
||||||
### Legacy/Secondary Museum
|
|
||||||
|
|
||||||
Files: `docs/museums/big_number_museum.tscn`, `docs/museums/big_number_museum.gd`
|
|
||||||
|
|
||||||
This appears to be an older/placeholder scene and not the current gameplay focus.
|
|
||||||
|
|
||||||
## Data-Driven Content Snapshot
|
|
||||||
|
|
||||||
### Generators
|
|
||||||
|
|
||||||
1. `primary_generator.tres`: `Magic Orb`, active from start, includes 3 buffs.
|
|
||||||
2. `secondary_generator.tres`: `Library`, starts locked/unavailable and unlocks via `magic_total_30` goal.
|
|
||||||
|
|
||||||
### Buffs (Magic)
|
|
||||||
|
|
||||||
1. `magic_auto_flux.tres`: auto-production buff, unlocked by default.
|
|
||||||
2. `magic_click_focus.tres`: manual-click buff.
|
|
||||||
3. `magic_supply_cache.tres`: resource-purchase buff.
|
|
||||||
|
|
||||||
### Goals
|
|
||||||
|
|
||||||
1. `magic_total_30.tres`: reach total magic 30.
|
|
||||||
2. `magic_total_1300.tres`: reach total magic 1300.
|
|
||||||
|
|
||||||
## End-to-End Runtime Flow
|
|
||||||
|
|
||||||
1. `project.godot` autoloads `CurrencyDatabase` and `GameState`.
|
|
||||||
2. `CurrencyDatabase` builds currency catalog from `idles/currencies`.
|
|
||||||
3. `GameState` initializes per-currency maps and loads save data.
|
|
||||||
4. Scene nodes initialize and connect to `GameState` and local gameplay signals.
|
|
||||||
5. Generators produce currency automatically and via click/hover paths.
|
|
||||||
6. Currency updates emit `currency_changed` and drive UI refresh + unlock checks.
|
|
||||||
7. Goal unlock evaluator may transition generator state to unlocked/available.
|
|
||||||
8. Generator and buff purchases route all balance/state mutations through `GameState`.
|
|
||||||
|
|
||||||
## Signal Guide (Emitters and Listeners)
|
|
||||||
|
|
||||||
### Custom Gameplay Signals
|
|
||||||
|
|
||||||
| Signal | Declared In | Emitted By | Emitted When | Listeners |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `currency_changed(currency_id, new_amount)` | `core/game_state.gd` | `GameState.add_currency_by_id`, `GameState.spend_currency_by_id` | Any currency balance increases/decreases | `CurrencyGenerator._on_currency_changed`, `GeneratorPanel._on_currency_changed`, `CurrencyTile._on_currency_changed`, `BigNumberProgressBar._on_currency_changed`, `GoalsDebugUI._on_currency_changed` |
|
|
||||||
| `generator_state_changed(generator_id, state)` | `core/game_state.gd` | `GameState.register_generator`, `GameState._set_generator_state` | Generator state created or changed (`owned`, `purchased_count`, `unlocked`, `available`) | `CurrencyGenerator._on_generated_state_changed`, `GeneratorPanel._on_generator_state_changed`, `GoalsDebugUI._on_generator_state_changed` |
|
|
||||||
| `generator_buff_level_changed(generator_id, buff_id, new_level)` | `core/game_state.gd` | `GameState.register_generator_buff`, `GameState.set_generator_buff_level` | Buff level created/sanitized/updated | `GeneratorPanel._on_generator_buff_level_changed` |
|
|
||||||
| `generator_buff_unlocked_changed(generator_id, buff_id, unlocked)` | `core/game_state.gd` | `GameState.register_generator_buff_unlocked`, `GameState.set_generator_buff_unlocked` | Buff unlock state created/sanitized/updated | `GeneratorPanel._on_generator_buff_unlocked_changed` |
|
|
||||||
| `purchase_completed(amount, total_owned, total_purchased, total_cost)` | `core/generator/currency_generator.gd` | `CurrencyGenerator.buy` | Generator purchase succeeds | `GeneratorPanel._on_generator_updated` |
|
|
||||||
| `purchase_failed(requested_amount, required_cost, available_currency)` | `core/generator/currency_generator.gd` | `CurrencyGenerator.buy` | Generator purchase fails for insufficient funds | `GeneratorPanel._on_generator_updated` |
|
|
||||||
| `production_tick(amount, cycle_count, total_owned)` | `core/generator/currency_generator.gd` | `CurrencyGenerator._grant_cycle_income` | One or more automatic production cycles complete | `GeneratorPanel._on_generator_updated` |
|
|
||||||
| `buff_purchased(buff_id, new_level, cost, cost_currency_id)` | `core/generator/currency_generator.gd` | `CurrencyGenerator.buy_buff` | Buff purchase succeeds | `GeneratorPanel._on_generator_updated` |
|
|
||||||
| `buff_purchase_failed(buff_id, required_cost, cost_currency_id)` | `core/generator/currency_generator.gd` | `CurrencyGenerator.buy_buff` | Buff purchase fails for insufficient funds | `GeneratorPanel._on_generator_updated` |
|
|
||||||
| `goal_achieved(generator_id, goal_id)` | `core/generator/currency_generator.gd` | `CurrencyGenerator._evaluate_generator_unlock_goal` | Generator unlock goal transitions that generator to unlocked+available | None currently (available for gameplay/UI hooks) |
|
|
||||||
| `buy_pressed(buff_id)` | `generator_buff_tile.gd` | `GeneratorBuffTile._on_buy_button_pressed` | User presses buff buy button in a buff row | `GeneratorPanel._on_buy_buff_pressed` |
|
|
||||||
|
|
||||||
### Built-In Godot Signals Wired In This Project
|
|
||||||
|
|
||||||
| Source Signal | Connected In | Listener Method | Purpose |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `Area2D.mouse_entered` | `core/generator/currency_generator.tscn` | `CurrencyGenerator._on_area_2d_mouse_entered` | Enter generator hover zone; mark hover true and show generator panel |
|
|
||||||
| `Area2D.mouse_exited` | `core/generator/currency_generator.tscn` | `CurrencyGenerator._on_area_2d_mouse_exited` | Exit generator hover zone; mark hover false and hide generator panel |
|
|
||||||
| `BuyOneButton.pressed` | `generator_container.tscn` | `GeneratorPanel._on_buy_pressed` | Buy one generator |
|
|
||||||
| `BuyMaxButton.pressed` | `generator_container.tscn` | `GeneratorPanel._on_buy_max_pressed` | Buy max affordable generators |
|
|
||||||
| `GeneratorContainer.mouse_entered` | `generator_container.tscn` | `GeneratorPanel._on_mouse_entered` | Intended hover hook on panel container (method currently missing) |
|
|
||||||
| `GeneratorContainer.mouse_exited` | `generator_container.tscn` | `GeneratorPanel._on_mouse_exited` | Intended hover hook on panel container (method currently missing) |
|
|
||||||
| `BuyButton.pressed` | `generator_buff_tile.tscn` | `GeneratorBuffTile._on_buy_button_pressed` | Emit tile-level `buy_pressed` custom signal |
|
|
||||||
| `DebugIncomeButton.pressed` | `currency_tile.tscn` | `CurrencyTile._on_debug_income_button_pressed` | Add debug income to the tile currency |
|
|
||||||
| `Button.pressed` (runtime-created unlock button) | `core/generator-unlock-goals/goals_debug_ui.gd` | `GoalsDebugUI._on_unlock_pressed` (bound with goal id) | Manual debug unlock when a goal is ready |
|
|
||||||
|
|
||||||
### Common Signal Chains
|
|
||||||
|
|
||||||
1. Currency gain/spend chain: generator or debug action mutates `GameState` currency, `GameState` emits `currency_changed`, UI widgets and unlock systems recompute and redraw.
|
|
||||||
2. Generator purchase chain: panel button triggers `CurrencyGenerator.buy`, generator updates `GameState` state, generator emits purchase signal, panel refreshes stats and buttons.
|
|
||||||
3. Buff purchase chain: buff tile emits `buy_pressed`, panel calls `CurrencyGenerator.buy_buff`, generator updates buff level/unlock and optionally grants currency, `GameState` emits buff/currency/state signals, panel and other listeners refresh.
|
|
||||||
4. Goal unlock chain: `currency_changed` triggers generator-local unlock evaluator, evaluator sets generator `unlocked/available`, emits `goal_achieved(generator_id, goal_id)`, `GameState` emits `generator_state_changed`, generator and UI become interactable/visible.
|
|
||||||
|
|
||||||
## Key Findings and Risks
|
|
||||||
|
|
||||||
1. `project.godot` has no explicit `run/main_scene` entry, so startup scene is not pinned in tracked config.
|
|
||||||
2. Save/load is only partially wired: `load_game()` runs at startup, but `save_game()` is not invoked anywhere else in the repository.
|
|
||||||
3. `currency_label.gd` and `big_number_progress_bar.gd` reference `GameState.GOLD_CURRENCY_ID`, but that constant does not exist in `core/game_state.gd`.
|
|
||||||
4. `generator_container.tscn` connects `mouse_entered`/`mouse_exited` to methods that do not exist in `generator_container.gd`.
|
|
||||||
5. Generator unlock goals now assume the target generator exists in-scene and owns its own `unlock_goal`.
|
|
||||||
6. `KnowledgeGenerator` visibility logic may be inconsistent because `currency_generator.gd` sets `visible = true` whenever its generator state changes.
|
|
||||||
7. Two buffs are configured locked with no unlock-goal path, so they remain permanently inaccessible under current logic.
|
|
||||||
8. `core/generator-unlock-goals/TECH_SPEC.md` contains some outdated assumptions relative to current `.tres`-based implementation.
|
|
||||||
|
|
||||||
## Additional Notes
|
|
||||||
|
|
||||||
1. `.uid` files are identity metadata used by Godot and contain no runtime logic.
|
|
||||||
2. `icon.svg` and `icon.svg.import` are standard icon/import metadata.
|
|
||||||
3. Root config files (`.editorconfig`, `.gitattributes`, `.gitignore`) are minimal and conventional.
|
|
||||||
|
|||||||
433
REFACTORING.md
Normal file
433
REFACTORING.md
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
# Refactoring Plan: Decoupled Buff System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Decouple buffs from generators to enable:
|
||||||
|
- Global buff levels shared across all targets
|
||||||
|
- Multi-target buffs (including wildcards for all current + future generators)
|
||||||
|
- Automatic buff activation via goal achievement
|
||||||
|
- Clean separation between buff registry and generator state
|
||||||
|
- Flexible UI patterns (global buff shop, per-generator panels, debug controls)
|
||||||
|
|
||||||
|
## Current State (Problem)
|
||||||
|
- Buffs stored in `CurrencyGeneratorData.buffs` array (tight coupling)
|
||||||
|
- Buff levels tracked per-generator in `GameState.generator_buff_levels[generator_id][buff_id]`
|
||||||
|
- Buff resources have no `target_ids` - don't know which generators they affect
|
||||||
|
- Cannot create global buffs or multi-target buffs
|
||||||
|
- Buff logic scattered across `CurrencyGenerator` and `GameState`
|
||||||
|
|
||||||
|
## Target State (Solution)
|
||||||
|
- Buff registry in `GameState._buff_definitions` (global, auto-discovered from `res://sandbox/buffs/`)
|
||||||
|
- Buff levels stored globally: `GameState._buff_levels[buff_id]`
|
||||||
|
- Buff resources have `target_ids: Array[StringName]` (supports `["*"]` for wildcards)
|
||||||
|
- Generators query `GameState.get_buffs_for_generator(generator_id)` for applicable buffs
|
||||||
|
- Multipliers calculated in `GameState.get_effective_multiplier(generator_id, kind)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
### Phase 1: Enhance GeneratorBuffData (Safe, Non-Breaking)
|
||||||
|
**File**: `core/generator/generator_buff_data.gd`
|
||||||
|
|
||||||
|
**Add:**
|
||||||
|
```gdscript
|
||||||
|
@export var target_ids: Array[StringName] = []
|
||||||
|
|
||||||
|
func targets_generator(generator_id: StringName) -> bool:
|
||||||
|
if target_ids.is_empty():
|
||||||
|
return false
|
||||||
|
if "*" in target_ids:
|
||||||
|
return true
|
||||||
|
return generator_id in target_ids
|
||||||
|
|
||||||
|
func get_target_generators() -> Array[StringName]:
|
||||||
|
return target_ids.duplicate()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timeline**: 1-2 hours
|
||||||
|
**Risk**: Low (additive change, backward compatible)
|
||||||
|
**Testing**: Verify existing buffs work with empty target_ids
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2: Extend GameState with Buff Registry
|
||||||
|
**File**: `core/game_state.gd`
|
||||||
|
|
||||||
|
**Add state variables:**
|
||||||
|
```gdscript
|
||||||
|
var _buff_definitions: Dictionary = {} # buff_id → GeneratorBuffData
|
||||||
|
var _buff_levels: Dictionary = {} # buff_id → int
|
||||||
|
var _buff_unlocked: Dictionary = {} # buff_id → bool
|
||||||
|
var _buff_active: Dictionary = {} # buff_id → bool (auto-set when unlocked)
|
||||||
|
|
||||||
|
signal buff_unlocked_changed(buff_id: StringName, unlocked: bool)
|
||||||
|
signal buff_level_changed(buff_id: StringName, new_level: int)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add registry methods:**
|
||||||
|
```gdscript
|
||||||
|
func register_buff(buff: GeneratorBuffData) -> void
|
||||||
|
func get_buff(buff_id: StringName) -> GeneratorBuffData
|
||||||
|
func get_all_buffs() -> Array[GeneratorBuffData]
|
||||||
|
func get_buff_level(buff_id: StringName) -> int
|
||||||
|
func set_buff_level(buff_id: StringName, level: int) -> void
|
||||||
|
func is_buff_unlocked(buff_id: StringName) -> bool
|
||||||
|
func set_buff_unlocked(buff_id: StringName, unlocked: bool) -> void
|
||||||
|
func is_buff_active(buff_id: StringName) -> bool
|
||||||
|
func get_generators_targeted_by(buff_id: StringName) -> Array[StringName]
|
||||||
|
func get_buffs_for_generator(generator_id: StringName) -> Array[GeneratorBuffData]
|
||||||
|
func get_effective_multiplier(generator_id: StringName, kind: int) -> float
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add goal evaluation helper:**
|
||||||
|
```gdscript
|
||||||
|
func _try_unlock_buff_from_goal(buff: GeneratorBuffData) -> void
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timeline**: 4-6 hours
|
||||||
|
**Risk**: High (core state management)
|
||||||
|
**Testing**: Unit tests for each method, verify goal-based unlocks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3: Update Save/Load System
|
||||||
|
**File**: `core/game_state.gd`
|
||||||
|
|
||||||
|
**New save format (v2):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"save_format_version": 2,
|
||||||
|
"currencies": { ... },
|
||||||
|
"generator_states": { ... },
|
||||||
|
"buff_definitions": { ... },
|
||||||
|
"buff_levels": { ... },
|
||||||
|
"buff_unlocked": { ... },
|
||||||
|
"buff_active": { ... },
|
||||||
|
"last_save_time": 1234567890
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add serialization helpers:**
|
||||||
|
```gdscript
|
||||||
|
func _serialize_buff_definitions() -> Dictionary
|
||||||
|
func _serialize_buff_data(buff: GeneratorBuffData) -> Dictionary
|
||||||
|
func _deserialize_buff_definitions(raw: Variant) -> void
|
||||||
|
```
|
||||||
|
|
||||||
|
**Version check in load_game():**
|
||||||
|
```gdscript
|
||||||
|
if version < 2:
|
||||||
|
push_error("Save file version %d too old. Please start fresh." % version)
|
||||||
|
return
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timeline**: 3-4 hours
|
||||||
|
**Risk**: High (data loss if bugs exist)
|
||||||
|
**Testing**: Save/load cycles, verify buff state persists, test version rejection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 4: Update CurrencyGenerator
|
||||||
|
**File**: `core/generator/currency_generator.gd`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
```gdscript
|
||||||
|
# Replace:
|
||||||
|
func get_buffs() -> Array[GeneratorBuffData] # Now queries GameState
|
||||||
|
|
||||||
|
func _get_multiplier_for_buff_kind(kind: int) -> float: # Delegates to GameState
|
||||||
|
return GameState.get_effective_multiplier(_generator_id, kind)
|
||||||
|
|
||||||
|
# Update _register_buffs() to only evaluate unlock goals
|
||||||
|
func _register_buffs() -> void:
|
||||||
|
for buff in get_buffs():
|
||||||
|
if buff == null:
|
||||||
|
continue
|
||||||
|
var buff_id: StringName = buff.id
|
||||||
|
if not GameState.get_buff(buff_id):
|
||||||
|
GameState.register_buff(buff)
|
||||||
|
_try_unlock_buff_from_goal(buff)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timeline**: 2-3 hours
|
||||||
|
**Risk**: Medium (runtime behavior change)
|
||||||
|
**Testing**: Verify production multipliers, buff purchases, goal unlocks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 5: Update PrestigeManager
|
||||||
|
**File**: `core/prestige/prestige_manager.gd`
|
||||||
|
|
||||||
|
**Add buff reset logic:**
|
||||||
|
```gdscript
|
||||||
|
func perform_prestige() -> bool:
|
||||||
|
# ... existing logic ...
|
||||||
|
GameState.reset_for_prestige(true, false)
|
||||||
|
_reset_all_buff_levels()
|
||||||
|
# ... rest of logic ...
|
||||||
|
|
||||||
|
func _reset_all_buff_levels() -> void:
|
||||||
|
for buff_id in GameState._buff_levels.keys():
|
||||||
|
GameState.set_buff_level(buff_id, 0)
|
||||||
|
GameState.set_buff_unlocked(buff_id, false)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timeline**: 1-2 hours
|
||||||
|
**Risk**: Low
|
||||||
|
**Testing**: Verify buff levels reset after prestige
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 6: Update UI Components
|
||||||
|
**File**: `core/generator/generator_container.gd`
|
||||||
|
|
||||||
|
**Minimal changes needed** - `GeneratorPanel._build_buff_rows()` should work automatically since it uses `_generator.get_buffs()`.
|
||||||
|
|
||||||
|
**Optional: Add Global Buff Panel**
|
||||||
|
Create `res://ui/global_buff_panel.gd` for testing/future UI:
|
||||||
|
```gdscript
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
func _build_buff_list() -> void:
|
||||||
|
for buff in GameState.get_all_buffs():
|
||||||
|
if GameState.is_buff_hidden(buff.id):
|
||||||
|
continue
|
||||||
|
# Create UI row showing buff state
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timeline**: 1-2 hours (optional)
|
||||||
|
**Risk**: Low
|
||||||
|
**Testing**: Verify buff UI updates on level/unlock changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 7: Data Migration (One-Time Manual)
|
||||||
|
**Manual steps:**
|
||||||
|
|
||||||
|
1. **Update `res://sandbox/buffs/*.tres`:**
|
||||||
|
- Open each buff in Godot editor
|
||||||
|
- Add `target_ids` array (e.g., `["farm", "forestry"]` or `["*"]`)
|
||||||
|
|
||||||
|
2. **Update `res://sandbox/generators/*.tres`:**
|
||||||
|
- Remove `buffs` array field
|
||||||
|
- (Optional) Add `expected_buff_ids` for documentation
|
||||||
|
|
||||||
|
3. **Delete old saves:**
|
||||||
|
- `user://idle_save.json` - starts fresh with v2 format
|
||||||
|
|
||||||
|
**Timeline**: 1 hour
|
||||||
|
**Risk**: Low (data files only)
|
||||||
|
**Testing**: Load game, verify all buffs work correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 8: Buff Auto-Discovery System (Bonus Feature)
|
||||||
|
**New file**: `core/buff_database.gd`
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const BUFF_DIRECTORY_PATH: String = "res://sandbox/buffs"
|
||||||
|
|
||||||
|
var _buff_by_id: Dictionary = {}
|
||||||
|
var _initialized: bool = false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_initialize_buff_catalog()
|
||||||
|
|
||||||
|
func _initialize_buff_catalog() -> void:
|
||||||
|
_buff_by_id.clear()
|
||||||
|
var dir: DirAccess = DirAccess.open(BUFF_DIRECTORY_PATH)
|
||||||
|
if dir == null:
|
||||||
|
push_warning("Unable to open buff directory: %s" % BUFF_DIRECTORY_PATH)
|
||||||
|
return
|
||||||
|
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var entry_name: String = dir.get_next()
|
||||||
|
while not entry_name.is_empty():
|
||||||
|
if not dir.current_is_dir() and entry_name.ends_with(".tres"):
|
||||||
|
var path: String = "%s/%s" % [BUFF_DIRECTORY_PATH, entry_name]
|
||||||
|
var buff: GeneratorBuffData = load(path) as GeneratorBuffData
|
||||||
|
if buff != null:
|
||||||
|
GameState.register_buff(buff)
|
||||||
|
entry_name = dir.get_next()
|
||||||
|
dir.list_dir_end()
|
||||||
|
|
||||||
|
func get_all_buffs() -> Array[GeneratorBuffData]:
|
||||||
|
return _buff_by_id.values()
|
||||||
|
|
||||||
|
func get_buff(buff_id: StringName) -> GeneratorBuffData:
|
||||||
|
return _buff_by_id.get(buff_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add `BuffDatabase` to project.godot autoloads**
|
||||||
|
|
||||||
|
**Timeline**: 2-3 hours
|
||||||
|
**Risk**: Low
|
||||||
|
**Testing**: Verify all buffs auto-load at runtime
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Timeline Summary
|
||||||
|
|
||||||
|
| Phase | Duration | Risk |
|
||||||
|
|-------|----------|------|
|
||||||
|
| Phase 1: GeneratorBuffData | 1-2h | Low |
|
||||||
|
| Phase 2: GameState Extension | 4-6h | High |
|
||||||
|
| Phase 3: Save/Load Update | 3-4h | High |
|
||||||
|
| Phase 4: CurrencyGenerator | 2-3h | Medium |
|
||||||
|
| Phase 5: PrestigeManager | 1-2h | Low |
|
||||||
|
| Phase 6: UI Updates | 1-2h | Low |
|
||||||
|
| Phase 7: Data Migration | 1h | Low |
|
||||||
|
| Phase 8: Buff Auto-Discovery | 2-3h | Low |
|
||||||
|
| **Total** | **15-23 hours** | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [ ] Buff with `target_ids: ["farm", "forestry"]` applies to both
|
||||||
|
- [ ] Buff with `target_ids: ["*"]` applies to all current + future generators
|
||||||
|
- [ ] Buying a buff level increases global level (not per-generator)
|
||||||
|
- [ ] Buff unlock goal automatically activates buff when met
|
||||||
|
- [ ] Generator production reflects active buff multipliers
|
||||||
|
- [ ] Save/load preserves buff levels and unlock states
|
||||||
|
- [ ] Prestige resets all buff levels to 0
|
||||||
|
- [ ] Adding new generator auto-includes wildcard buffs
|
||||||
|
- [ ] Multiple buff kinds stack multiplicatively
|
||||||
|
- [ ] Inactive (locked) buffs don't apply multipliers
|
||||||
|
- [ ] Buff definitions auto-load from `res://sandbox/buffs/`
|
||||||
|
- [ ] New buff resources appear in registry without code changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If issues arise:
|
||||||
|
|
||||||
|
1. **Revert code changes** - Use git to restore previous state
|
||||||
|
2. **Delete user://idle_save.json** - Start fresh with v1 format
|
||||||
|
3. **Restore buff/generator .tres files** - Remove target_ids, restore buffs arrays
|
||||||
|
4. **Remove GameState buff methods** - Restore original API
|
||||||
|
|
||||||
|
**Note**: No save migration code - breaking change requires fresh starts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] All generators produce correct amounts with active buffs
|
||||||
|
- [ ] Buff UI can display global buff list or per-generator view
|
||||||
|
- [ ] Save files load correctly and preserve buff state
|
||||||
|
- [ ] Prestige resets buff levels as expected
|
||||||
|
- [ ] New buffs can be added by dropping .tres files in sandbox/buffs/
|
||||||
|
- [ ] Wildcard buffs auto-apply to new generators
|
||||||
|
- [ ] No performance degradation in _process loop
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Enhancements (Post-Implementation)
|
||||||
|
|
||||||
|
1. **Buff synergies**: "If you have X buff, Y buff gets +10%"
|
||||||
|
2. **Buff tiers**: Common/rare/legendary classification
|
||||||
|
3. **Conditional buffs**: "Only active if generator_ownership > 10"
|
||||||
|
4. **Buff stacking rules**: Additive vs multiplicative vs highest-only
|
||||||
|
5. **Prestige-only buffs**: Permanent buffs unlocked via prestige currency
|
||||||
|
6. **Buff UI shop**: Global buff purchase interface
|
||||||
|
7. **Buff preview**: Show "what-if" calculations before purchase
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### Architecture Decisions
|
||||||
|
|
||||||
|
| Decision | Implementation |
|
||||||
|
|----------|----------------|
|
||||||
|
| **Buff Levels** | Global (shared across all targets) |
|
||||||
|
| **Buff Registry** | Auto-discovered from `res://sandbox/buffs/` |
|
||||||
|
| **Wildcards** | `["*"]` matches all current AND future generators |
|
||||||
|
| **Default State** | Inactive/unlocked = false, auto-activates on goal met |
|
||||||
|
| **Persistence** | Buff definitions serialized to save (by id) |
|
||||||
|
| **Prestige** | All buff levels reset to 0 |
|
||||||
|
| **Activation** | Automatic via goals, no manual toggle |
|
||||||
|
|
||||||
|
### Key API Changes
|
||||||
|
|
||||||
|
**Old API (per-generator state):**
|
||||||
|
```gdscript
|
||||||
|
GameState.register_generator_buff(generator_id, buff_id, level)
|
||||||
|
GameState.get_generator_buff_level(generator_id, buff_id)
|
||||||
|
GameState.is_generator_buff_unlocked(generator_id, buff_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
**New API (global buff state):**
|
||||||
|
```gdscript
|
||||||
|
GameState.register_buff(buff: GeneratorBuffData)
|
||||||
|
GameState.get_buff(buff_id) -> GeneratorBuffData
|
||||||
|
GameState.get_buff_level(buff_id) -> int
|
||||||
|
GameState.is_buff_unlocked(buff_id) -> bool
|
||||||
|
GameState.get_buffs_for_generator(generator_id) -> Array[GeneratorBuffData]
|
||||||
|
GameState.get_effective_multiplier(generator_id, kind) -> float
|
||||||
|
```
|
||||||
|
|
||||||
|
### Save Format Migration
|
||||||
|
|
||||||
|
**Version 1 (old - incompatible):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"currencies": { ... },
|
||||||
|
"generator_states": { ... },
|
||||||
|
"generator_buff_levels": { "farm": { "farm_flux": 5 } },
|
||||||
|
"generator_buff_unlocked": { "farm": { "farm_flux": true } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Version 2 (new):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"save_format_version": 2,
|
||||||
|
"currencies": { ... },
|
||||||
|
"generator_states": { ... },
|
||||||
|
"buff_definitions": { "farm_flux": { ... } },
|
||||||
|
"buff_levels": { "farm_flux": 5 },
|
||||||
|
"buff_unlocked": { "farm_flux": true },
|
||||||
|
"buff_active": { "farm_flux": true }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Godot 4.6+ (current project version)
|
||||||
|
- Existing `BigNumber` system for currency math
|
||||||
|
- Existing `GameState` autoload
|
||||||
|
- Existing `GoalData` and `GoalRequirementData` for unlock conditions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risks & Mitigations
|
||||||
|
|
||||||
|
| Risk | Mitigation |
|
||||||
|
|------|------------|
|
||||||
|
| Save format incompatibility | Clear version check, error on old saves |
|
||||||
|
| Performance in _process loop | Cache multiplier calculations, only recalc on buff changes |
|
||||||
|
| Buff definition loading failures | Graceful degradation, warn but continue |
|
||||||
|
| Wildcard expansion at runtime | Explicit registration in generator _ready() |
|
||||||
|
| Multiple buffs of same kind | Multiplicative stacking (document clearly) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Approval Status
|
||||||
|
|
||||||
|
- [ ] Architecture approved
|
||||||
|
- [ ] Implementation phases reviewed
|
||||||
|
- [ ] Timeline accepted
|
||||||
|
- [ ] Risks understood
|
||||||
|
- [ ] Ready to begin Phase 1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: Initial planning document*
|
||||||
|
*Status: Pending implementation approval*
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Generator-Embedded Unlock Goals - Technical Specification
|
|
||||||
|
|
||||||
## Document Status
|
|
||||||
- Version: 2.0
|
|
||||||
- Date: 2026-03-21
|
|
||||||
- Scope: Unlock generators from `CurrencyGeneratorData.unlock_goal` when goal thresholds are reached.
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
Generator unlock progression should be authored where generator behavior lives: `CurrencyGeneratorData`.
|
|
||||||
The previous scene-level target mapping layer increased setup complexity and created mismatch risk between goal targets and scene content.
|
|
||||||
|
|
||||||
## Current Baseline
|
|
||||||
1. Generator state persists in `GameState.generator_states` (`owned`, `purchased_count`, `unlocked`, `available`).
|
|
||||||
2. `CurrencyGenerator` already gates interactions using `is_available_to_player()`.
|
|
||||||
3. Goal primitives remain reusable and data-driven:
|
|
||||||
- `GoalRequirementData`
|
|
||||||
- `GoalData`
|
|
||||||
4. Generator data now owns unlock definition via `unlock_goal: GoalData`.
|
|
||||||
|
|
||||||
## Functional Requirements
|
|
||||||
1. A generator may define one optional unlock goal (`CurrencyGeneratorData.unlock_goal`).
|
|
||||||
2. Goal completion uses logical AND across requirements.
|
|
||||||
3. Requirement checks use total acquired currency (`GameState.get_total_currency_acquired_by_id`).
|
|
||||||
4. Unlock evaluation runs:
|
|
||||||
- once on generator `_ready()`
|
|
||||||
- on every `GameState.currency_changed`
|
|
||||||
5. When a goal is met, runtime sets:
|
|
||||||
- `GameState.set_generator_unlocked(generator_id, true)`
|
|
||||||
- `GameState.set_generator_available(generator_id, true)`
|
|
||||||
6. Unlock transitions are idempotent.
|
|
||||||
7. Unlock completion emits `CurrencyGenerator.goal_achieved(generator_id, goal_id)` for hooks/UI.
|
|
||||||
|
|
||||||
## Runtime Design
|
|
||||||
1. `CurrencyGeneratorData` exposes:
|
|
||||||
- `has_unlock_goal()`
|
|
||||||
- `is_unlock_goal_met()`
|
|
||||||
- `get_unlock_goal_id()`
|
|
||||||
2. `CurrencyGenerator` executes `_evaluate_generator_unlock_goal()`.
|
|
||||||
3. No scene-level unlock controller is required.
|
|
||||||
4. Goals debug UI discovers goals from scene generators instead of external target-mapping resources.
|
|
||||||
|
|
||||||
## Data Authoring Rules
|
|
||||||
1. Configure locked generators with `starts_unlocked = false` and `starts_available = false`.
|
|
||||||
2. Set `unlock_goal` directly on that generator `.tres` resource.
|
|
||||||
3. Keep `GoalData.id` unique and stable.
|
|
||||||
4. Goal data can still be shared across systems (generator unlocks, buff unlocks).
|
|
||||||
|
|
||||||
## Save/Load Behavior
|
|
||||||
1. Unlock persistence remains unchanged because `GameState` stores `unlocked/available`.
|
|
||||||
2. Re-evaluation after load is safe due to idempotent state writes.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
1. Run headless parse: `"$GODOT_BIN" --headless --path . --quit`
|
|
||||||
2. Manual smoke test in `generator_museum`:
|
|
||||||
- verify locked generator starts unavailable
|
|
||||||
- grant required currency
|
|
||||||
- verify generator unlocks automatically
|
|
||||||
- verify state persists after restart
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
[gd_scene format=3 uid="uid://jeoiinukrrsp"]
|
[gd_scene format=3 uid="uid://jeoiinukrrsp"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dtbxopw6ulhl8" path="res://core/generator/currency_generator.gd" id="1_4n4ca"]
|
[ext_resource type="Script" uid="uid://dtbxopw6ulhl8" path="res://core/generator/currency_generator.gd" id="1_4n4ca"]
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_5tmvy"]
|
|
||||||
[ext_resource type="Resource" uid="uid://co0mcc2kvcpo5" path="res://idles/generators/orb.tres" id="3_wx13b"]
|
|
||||||
|
|
||||||
[node name="CurrencyGenerator" type="Node2D" unique_id=967969064]
|
[node name="CurrencyGenerator" type="Node2D" unique_id=967969064]
|
||||||
script = ExtResource("1_4n4ca")
|
script = ExtResource("1_4n4ca")
|
||||||
currency = ExtResource("2_5tmvy")
|
|
||||||
data = ExtResource("3_wx13b")
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ enum BuffKind {
|
|||||||
const HUGE_EXPONENT: int = 1000000
|
const HUGE_EXPONENT: int = 1000000
|
||||||
|
|
||||||
@export var id: StringName = &""
|
@export var id: StringName = &""
|
||||||
|
@export var target_ids: Array[StringName] = []
|
||||||
@export var kind: BuffKind = BuffKind.AUTO_PRODUCTION_MULTIPLIER
|
@export var kind: BuffKind = BuffKind.AUTO_PRODUCTION_MULTIPLIER
|
||||||
@export_multiline var text: String = ""
|
@export_multiline var text: String = ""
|
||||||
@export var icon: Texture2D
|
@export var icon: Texture2D
|
||||||
@@ -35,6 +36,18 @@ const HUGE_EXPONENT: int = 1000000
|
|||||||
@export var resource_purchase_base_exponent: int = 0
|
@export var resource_purchase_base_exponent: int = 0
|
||||||
@export var resource_purchase_increment_multiplier: float = 1.2
|
@export var resource_purchase_increment_multiplier: float = 1.2
|
||||||
|
|
||||||
|
func targets_generator(generator_id: StringName) -> bool:
|
||||||
|
if target_ids.is_empty():
|
||||||
|
return false
|
||||||
|
|
||||||
|
if target_ids.has("*"):
|
||||||
|
return true
|
||||||
|
|
||||||
|
return target_ids.has(generator_id)
|
||||||
|
|
||||||
|
func get_target_generators() -> Array[StringName]:
|
||||||
|
return target_ids.duplicate()
|
||||||
|
|
||||||
func has_unlock_goal() -> bool:
|
func has_unlock_goal() -> bool:
|
||||||
if unlock_goal == null:
|
if unlock_goal == null:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
[gd_scene format=3 uid="uid://cfryecxmcg8hw"]
|
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://jeoiinukrrsp" path="res://core/generator/currency_generator.tscn" id="1_6pne4"]
|
|
||||||
[ext_resource type="Resource" uid="uid://bnhqk8b31mm4e" path="res://idles/currencies/knowledge.tres" id="2_8qilt"]
|
|
||||||
[ext_resource type="Resource" uid="uid://l0pn6mlcer7t" path="res://idles/currencies/spirit.tres" id="2_jlqd0"]
|
|
||||||
[ext_resource type="Resource" uid="uid://04pmc034qupd" path="res://idles/generators/library.tres" id="3_4ly0e"]
|
|
||||||
[ext_resource type="Resource" uid="uid://cythfovqgqlyh" path="res://idles/currencies/wood.tres" id="4_2xpf5"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://btkxru2gdjsgc" path="res://currency_tile.tscn" id="4_6ri4a"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="5_dl3gy"]
|
|
||||||
[ext_resource type="Resource" uid="uid://df5k58yu1g6rf" path="res://idles/generators/forestry.tres" id="5_u3cug"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dirvi76rkoowf" path="res://goals_debug_ui.tscn" id="7_7i63j"]
|
|
||||||
[ext_resource type="PackedScene" path="res://core/prestige/prestige_panel.tscn" id="9_q5vce"]
|
|
||||||
|
|
||||||
[node name="GeneratorGym" type="Node2D" unique_id=1219373683]
|
|
||||||
|
|
||||||
[node name="MagicGenerator" parent="." unique_id=967969064 instance=ExtResource("1_6pne4")]
|
|
||||||
position = Vector2(262, 301)
|
|
||||||
|
|
||||||
[node name="KnowledgeGenerator" parent="." unique_id=2139088546 instance=ExtResource("1_6pne4")]
|
|
||||||
visible = false
|
|
||||||
position = Vector2(262, 626)
|
|
||||||
currency = ExtResource("2_8qilt")
|
|
||||||
data = ExtResource("3_4ly0e")
|
|
||||||
press_buys_generator = false
|
|
||||||
|
|
||||||
[node name="WoodGenerator" parent="." unique_id=29858558 instance=ExtResource("1_6pne4")]
|
|
||||||
visible = false
|
|
||||||
position = Vector2(1046, 626)
|
|
||||||
currency = ExtResource("4_2xpf5")
|
|
||||||
data = ExtResource("5_u3cug")
|
|
||||||
press_buys_generator = false
|
|
||||||
|
|
||||||
[node name="UI" type="Control" parent="." unique_id=452530906]
|
|
||||||
layout_mode = 3
|
|
||||||
anchors_preset = 0
|
|
||||||
offset_right = 40.0
|
|
||||||
offset_bottom = 40.0
|
|
||||||
|
|
||||||
[node name="MagicCurrencyTile" parent="UI" unique_id=1440583137 instance=ExtResource("4_6ri4a")]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_right = 160.0
|
|
||||||
offset_bottom = 31.0
|
|
||||||
currency = ExtResource("5_dl3gy")
|
|
||||||
|
|
||||||
[node name="SpiritCurrencyTile" parent="UI" unique_id=797237056 instance=ExtResource("4_6ri4a")]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_top = 32.0
|
|
||||||
offset_right = 160.0
|
|
||||||
offset_bottom = 63.0
|
|
||||||
currency = ExtResource("2_jlqd0")
|
|
||||||
|
|
||||||
[node name="KnowledgeCurrencyTile" parent="UI" unique_id=1977342362 instance=ExtResource("4_6ri4a")]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_top = 63.0
|
|
||||||
offset_right = 160.0
|
|
||||||
offset_bottom = 94.0
|
|
||||||
currency = ExtResource("2_8qilt")
|
|
||||||
|
|
||||||
[node name="WoodCurrencyTile" parent="UI" unique_id=1210103101 instance=ExtResource("4_6ri4a")]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_top = 94.0
|
|
||||||
offset_right = 160.0
|
|
||||||
offset_bottom = 125.0
|
|
||||||
currency = ExtResource("4_2xpf5")
|
|
||||||
|
|
||||||
[node name="GoalsDebugUI" parent="UI" unique_id=4710697 instance=ExtResource("7_7i63j")]
|
|
||||||
layout_mode = 1
|
|
||||||
offset_left = 1253.0
|
|
||||||
offset_top = 817.0
|
|
||||||
offset_right = 1913.0
|
|
||||||
offset_bottom = 1076.0
|
|
||||||
|
|
||||||
[node name="PrestigePanel" parent="UI" unique_id=401213142 instance=ExtResource("9_q5vce")]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 12.0
|
|
||||||
offset_top = 822.0
|
|
||||||
offset_right = 432.0
|
|
||||||
offset_bottom = 1028.0
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://mvgfe3nc7uwa"]
|
|
||||||
|
|
||||||
[ext_resource type="Resource" uid="uid://bnhqk8b31mm4e" path="res://idles/currencies/knowledge.tres" id="1_1db6v"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_pg1j1"]
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="3_t80he"]
|
|
||||||
[ext_resource type="Resource" uid="uid://bmlhoeasl7xor" path="res://idles/goals/magic_total_13k.tres" id="4_83vra"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("3_t80he")
|
|
||||||
id = &"bigger_forest"
|
|
||||||
text = "Bigger Forest"
|
|
||||||
max_level = 10
|
|
||||||
unlock_goal = ExtResource("4_83vra")
|
|
||||||
effect_increment = 1.0
|
|
||||||
cost_currency = ExtResource("1_1db6v")
|
|
||||||
base_cost_mantissa = 250.0
|
|
||||||
cost_multiplier = 1.7
|
|
||||||
resource_target_currency = ExtResource("2_pg1j1")
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://tu63jy51yigb"]
|
|
||||||
|
|
||||||
[ext_resource type="Resource" uid="uid://bnhqk8b31mm4e" path="res://idles/currencies/knowledge.tres" id="1_78qkq"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_b57xf"]
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="3_nc8nr"]
|
|
||||||
[ext_resource type="Resource" uid="uid://bmlhoeasl7xor" path="res://idles/goals/magic_total_13k.tres" id="4_78qkq"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("3_nc8nr")
|
|
||||||
id = &"library_auto_flux"
|
|
||||||
text = "Library Dynamo"
|
|
||||||
max_level = 10
|
|
||||||
unlock_goal = ExtResource("4_78qkq")
|
|
||||||
effect_increment = 1.0
|
|
||||||
cost_currency = ExtResource("1_78qkq")
|
|
||||||
base_cost_mantissa = 100.0
|
|
||||||
cost_multiplier = 1.7
|
|
||||||
resource_target_currency = ExtResource("2_b57xf")
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://ceugcxmassmpk"]
|
|
||||||
|
|
||||||
[ext_resource type="Resource" uid="uid://bnhqk8b31mm4e" path="res://idles/currencies/knowledge.tres" id="1_lnp8f"]
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_r7ak1"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_h3we5"]
|
|
||||||
[ext_resource type="Resource" uid="uid://qyxct5gbrxwa" path="res://idles/goals/magic_total_30.tres" id="4_000a0"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_r7ak1")
|
|
||||||
id = &"magic_auto_flux"
|
|
||||||
text = "Arcane Dynamo"
|
|
||||||
max_level = 10
|
|
||||||
unlock_goal = ExtResource("4_000a0")
|
|
||||||
effect_increment = 1.0
|
|
||||||
cost_currency = ExtResource("1_lnp8f")
|
|
||||||
base_cost_mantissa = 25.0
|
|
||||||
cost_multiplier = 1.7
|
|
||||||
resource_target_currency = ExtResource("2_h3we5")
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://6i3fcygusuqf"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_1wyaq"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_0j56j"]
|
|
||||||
[ext_resource type="Resource" uid="uid://qyxct5gbrxwa" path="res://idles/goals/magic_total_30.tres" id="3_bsqao"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_1wyaq")
|
|
||||||
id = &"magic_click_focus"
|
|
||||||
kind = 1
|
|
||||||
text = "Apprentice Gloves"
|
|
||||||
max_level = 4
|
|
||||||
unlock_goal = ExtResource("3_bsqao")
|
|
||||||
effect_increment = 1.0
|
|
||||||
cost_currency = ExtResource("2_0j56j")
|
|
||||||
base_cost_mantissa = 2.5
|
|
||||||
base_cost_exponent = 1
|
|
||||||
cost_multiplier = 2.0
|
|
||||||
resource_target_currency = ExtResource("2_0j56j")
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://coi7k1cx4p4hr"]
|
|
||||||
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="1_aawxd"]
|
|
||||||
[ext_resource type="Resource" uid="uid://l0pn6mlcer7t" path="res://idles/currencies/spirit.tres" id="2_0eqxt"]
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="3_0ov1n"]
|
|
||||||
[ext_resource type="Resource" uid="uid://qyxct5gbrxwa" path="res://idles/goals/magic_total_30.tres" id="4_ifsqd"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("3_0ov1n")
|
|
||||||
id = &"summon_spirit"
|
|
||||||
kind = 2
|
|
||||||
text = "Summon Spirit"
|
|
||||||
unlock_goal = ExtResource("4_ifsqd")
|
|
||||||
effect_increment = 1.0
|
|
||||||
cost_currency = ExtResource("1_aawxd")
|
|
||||||
base_cost_mantissa = 200.0
|
|
||||||
cost_multiplier = 1.15
|
|
||||||
resource_target_currency = ExtResource("2_0eqxt")
|
|
||||||
resource_purchase_base_mantissa = 1.0
|
|
||||||
resource_purchase_increment_multiplier = 1.0
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="Currency" format=3 uid="uid://bnhqk8b31mm4e"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dtgqjf3bl7pm8" path="res://core/currency/currency.gd" id="1_72iuq"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bgtt3wu43tajh" path="res://icon.svg" id="2_x2h5x"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_72iuq")
|
|
||||||
id = &"knowledge"
|
|
||||||
display_name = "Knowledge"
|
|
||||||
icon = ExtResource("2_x2h5x")
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="Currency" format=3 uid="uid://brqaojindcxa5"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dtgqjf3bl7pm8" path="res://core/currency/currency.gd" id="1_x4uiu"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bgtt3wu43tajh" path="res://icon.svg" id="2_52ar0"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_x4uiu")
|
|
||||||
id = &"magic"
|
|
||||||
display_name = "Magic"
|
|
||||||
icon = ExtResource("2_52ar0")
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="Currency" format=3 uid="uid://l0pn6mlcer7t"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dtgqjf3bl7pm8" path="res://core/currency/currency.gd" id="1_m14p5"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_m14p5")
|
|
||||||
id = &"spirit"
|
|
||||||
display_name = "Spirit"
|
|
||||||
metadata/_custom_type_script = "uid://dtgqjf3bl7pm8"
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="Currency" format=3 uid="uid://cythfovqgqlyh"]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bgtt3wu43tajh" path="res://icon.svg" id="1_t8m5x"]
|
|
||||||
[ext_resource type="Script" uid="uid://dtgqjf3bl7pm8" path="res://core/currency/currency.gd" id="2_137dc"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("2_137dc")
|
|
||||||
id = &"wood"
|
|
||||||
display_name = "Wood"
|
|
||||||
icon = ExtResource("1_t8m5x")
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3 uid="uid://df5k58yu1g6rf"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_ffn26"]
|
|
||||||
[ext_resource type="Resource" uid="uid://mvgfe3nc7uwa" path="res://idles/buffs/bigger_forest.tres" id="2_rptf4"]
|
|
||||||
[ext_resource type="Resource" uid="uid://l0pn6mlcer7t" path="res://idles/currencies/spirit.tres" id="3_rptf4"]
|
|
||||||
[ext_resource type="Script" uid="uid://b00tqsuhxdy0d" path="res://core/generator/currency_generator_data.gd" id="5_43n1y"]
|
|
||||||
[ext_resource type="Resource" uid="uid://bmlhoeasl7xor" path="res://idles/goals/magic_total_13k.tres" id="6_ffn26"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("5_43n1y")
|
|
||||||
id = &"Wood"
|
|
||||||
name = "Forestry"
|
|
||||||
starts_unlocked = false
|
|
||||||
starts_available = false
|
|
||||||
initial_owned = 1
|
|
||||||
purchase_currency = ExtResource("3_rptf4")
|
|
||||||
unlock_goal = ExtResource("6_ffn26")
|
|
||||||
unlock_goal_behavior = 1
|
|
||||||
initial_cost = 1.0
|
|
||||||
coefficient = 1.0
|
|
||||||
initial_productivity = 20.0
|
|
||||||
buffs = Array[ExtResource("1_ffn26")]([ExtResource("2_rptf4")])
|
|
||||||
metadata/_custom_type_script = "uid://b00tqsuhxdy0d"
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3 uid="uid://04pmc034qupd"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_jemvk"]
|
|
||||||
[ext_resource type="Resource" uid="uid://tu63jy51yigb" path="res://idles/buffs/library_auto_flux.tres" id="2_fcbji"]
|
|
||||||
[ext_resource type="Resource" uid="uid://l0pn6mlcer7t" path="res://idles/currencies/spirit.tres" id="3_fcbji"]
|
|
||||||
[ext_resource type="Script" uid="uid://b00tqsuhxdy0d" path="res://core/generator/currency_generator_data.gd" id="3_xj12v"]
|
|
||||||
[ext_resource type="Resource" uid="uid://c4mkxj4ubhsi0" path="res://idles/goals/magic_total_1300.tres" id="4_1h03m"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("3_xj12v")
|
|
||||||
id = &"Knowledge"
|
|
||||||
name = "Library"
|
|
||||||
starts_unlocked = false
|
|
||||||
starts_available = false
|
|
||||||
initial_owned = 1
|
|
||||||
purchase_currency = ExtResource("3_fcbji")
|
|
||||||
unlock_goal = ExtResource("4_1h03m")
|
|
||||||
unlock_goal_behavior = 1
|
|
||||||
initial_cost = 1.0
|
|
||||||
coefficient = 1.0
|
|
||||||
initial_time = 3.0
|
|
||||||
initial_revenue = 60.0
|
|
||||||
initial_productivity = 20.0
|
|
||||||
buffs = Array[ExtResource("1_jemvk")]([ExtResource("2_fcbji")])
|
|
||||||
metadata/_custom_type_script = "uid://b00tqsuhxdy0d"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3 uid="uid://nfq4erycp7vy"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_s017t"]
|
|
||||||
[ext_resource type="Script" uid="uid://b00tqsuhxdy0d" path="res://core/generator/currency_generator_data.gd" id="2_2rq43"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("2_2rq43")
|
|
||||||
id = &"monastery"
|
|
||||||
name = "Monastery"
|
|
||||||
starts_unlocked = false
|
|
||||||
starts_available = false
|
|
||||||
metadata/_custom_type_script = "uid://b00tqsuhxdy0d"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3 uid="uid://co0mcc2kvcpo5"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://b00tqsuhxdy0d" path="res://core/generator/currency_generator_data.gd" id="1_c6y77"]
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_t6lg0"]
|
|
||||||
[ext_resource type="Resource" uid="uid://ceugcxmassmpk" path="res://idles/buffs/orb_auto_flux.tres" id="2_x505b"]
|
|
||||||
[ext_resource type="Resource" uid="uid://6i3fcygusuqf" path="res://idles/buffs/orb_click_focus.tres" id="3_fsxdm"]
|
|
||||||
[ext_resource type="Resource" uid="uid://coi7k1cx4p4hr" path="res://idles/buffs/orb_summon_spirit.tres" id="4_5v0af"]
|
|
||||||
[ext_resource type="Resource" uid="uid://l0pn6mlcer7t" path="res://idles/currencies/spirit.tres" id="4_jpwus"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_c6y77")
|
|
||||||
id = &"Magic"
|
|
||||||
name = "Magic Orb"
|
|
||||||
purchase_currency = ExtResource("4_jpwus")
|
|
||||||
initial_cost = 1.0
|
|
||||||
coefficient = 1.0
|
|
||||||
initial_productivity = 20.0
|
|
||||||
buffs = Array[ExtResource("1_t6lg0")]([ExtResource("2_x505b"), ExtResource("3_fsxdm"), ExtResource("4_5v0af")])
|
|
||||||
metadata/_custom_type_script = "uid://b00tqsuhxdy0d"
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3 uid="uid://ri1ggb756753"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_tgm58"]
|
|
||||||
[ext_resource type="Script" uid="uid://b00tqsuhxdy0d" path="res://core/generator/currency_generator_data.gd" id="2_1qpvc"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_chq14"]
|
|
||||||
[ext_resource type="Resource" uid="uid://qyxct5gbrxwa" path="res://idles/goals/magic_total_30.tres" id="4_tj1lt"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("2_1qpvc")
|
|
||||||
id = &"spirit_factory"
|
|
||||||
name = "Spirit Factory"
|
|
||||||
starts_unlocked = false
|
|
||||||
starts_available = false
|
|
||||||
purchase_currency = ExtResource("2_chq14")
|
|
||||||
unlock_goal = ExtResource("4_tj1lt")
|
|
||||||
unlock_goal_behavior = 1
|
|
||||||
initial_cost = 200.0
|
|
||||||
metadata/_custom_type_script = "uid://b00tqsuhxdy0d"
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GoalData" format=3 uid="uid://oh1a4tneuons"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="1_pj6se"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_lsxf0"]
|
|
||||||
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="3_hyry2"]
|
|
||||||
[ext_resource type="Resource" uid="uid://cythfovqgqlyh" path="res://idles/currencies/wood.tres" id="3_lsxf0"]
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_tvl3d"]
|
|
||||||
script = ExtResource("1_pj6se")
|
|
||||||
currency = ExtResource("2_lsxf0")
|
|
||||||
amount_mantissa = 3.5
|
|
||||||
amount_exponent = 5
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_hyry2"]
|
|
||||||
script = ExtResource("1_pj6se")
|
|
||||||
currency = ExtResource("3_lsxf0")
|
|
||||||
amount_mantissa = 2.0
|
|
||||||
amount_exponent = 4
|
|
||||||
metadata/_custom_type_script = "uid://r4js5eajolio"
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("3_hyry2")
|
|
||||||
id = &"magic_350k_wood_20k"
|
|
||||||
requirements = Array[ExtResource("1_pj6se")]([SubResource("Resource_tvl3d"), SubResource("Resource_hyry2")])
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GoalData" format=3 uid="uid://c4mkxj4ubhsi0"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="1_b11ou"]
|
|
||||||
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="2_d507t"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="3_tu3fx"]
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_tvl3d"]
|
|
||||||
script = ExtResource("2_d507t")
|
|
||||||
currency = ExtResource("3_tu3fx")
|
|
||||||
amount_mantissa = 1.3
|
|
||||||
amount_exponent = 3
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_b11ou")
|
|
||||||
id = &"magic_total_1300"
|
|
||||||
requirements = Array[ExtResource("2_d507t")]([SubResource("Resource_tvl3d")])
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GoalData" format=3 uid="uid://bmlhoeasl7xor"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="1_185pw"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_ox6xg"]
|
|
||||||
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="3_ef7ki"]
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_tvl3d"]
|
|
||||||
script = ExtResource("1_185pw")
|
|
||||||
currency = ExtResource("2_ox6xg")
|
|
||||||
amount_mantissa = 1.3
|
|
||||||
amount_exponent = 4
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("3_ef7ki")
|
|
||||||
id = &"magic_total_13k"
|
|
||||||
requirements = Array[ExtResource("1_185pw")]([SubResource("Resource_tvl3d")])
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="GoalData" format=3 uid="uid://qyxct5gbrxwa"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="1_rh4uj"]
|
|
||||||
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="2_58n5q"]
|
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="3_qm5e7"]
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_v4v16"]
|
|
||||||
script = ExtResource("2_58n5q")
|
|
||||||
currency = ExtResource("3_qm5e7")
|
|
||||||
amount_mantissa = 30.0
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_rh4uj")
|
|
||||||
id = &"magic_total_30"
|
|
||||||
requirements = Array[ExtResource("2_58n5q")]([SubResource("Resource_v4v16")])
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="PrestigeConfig" format=3]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://core/prestige/prestige_config.gd" id="1_3tg3a"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("1_3tg3a")
|
|
||||||
id = &"ascension"
|
|
||||||
display_name = "Ascension"
|
|
||||||
prestige_currency_id = &"ascension"
|
|
||||||
source_currency_id = &"magic"
|
|
||||||
basis = 0
|
|
||||||
formula = 0
|
|
||||||
threshold_mantissa = 1.0
|
|
||||||
threshold_exponent = 4
|
|
||||||
scale = 1.0
|
|
||||||
exponent = 0.5
|
|
||||||
flat_bonus = 0.0
|
|
||||||
minimum_gain = 0
|
|
||||||
rounding_mode = 0
|
|
||||||
allow_prestige_without_gain = false
|
|
||||||
multiplier_mode = 0
|
|
||||||
base_multiplier = 1.0
|
|
||||||
multiplier_per_prestige = 0.05
|
|
||||||
multiplier_exponent = 1.0
|
|
||||||
metadata/_custom_type_script = "res://core/prestige/prestige_config.gd"
|
|
||||||
Reference in New Issue
Block a user