Compare commits
5 Commits
main
...
scene_mana
| Author | SHA1 | Date | |
|---|---|---|---|
| 51d042261f | |||
| 4fd23d9416 | |||
| 41466af1ca | |||
| 738381a16b | |||
| 66666defcb |
58
AGENTS.md
58
AGENTS.md
@@ -5,23 +5,61 @@ Godot 4.6 idle-game; code is GDScript + `.tscn` scenes.
|
||||
Use `GODOT_BIN` for your local editor binary (e.g. `godot`, `godot4`, or full path).
|
||||
Run project: `"$GODOT_BIN" --path .`
|
||||
Headless smoke/lint parse: `"$GODOT_BIN" --headless --path . --quit`
|
||||
Script syntax check (single script): `"$GODOT_BIN" --headless --check-only --script res://big_number.gd`
|
||||
Tests: no test framework is currently committed (`tests/` and `addons/gut` are absent).
|
||||
Single-test command: N/A right now; if GUT is added, use `"$GODOT_BIN" --headless --path . -s res://addons/gut/gut_cmdln.gd -gtest=res://tests/<file>.gd`.
|
||||
Script syntax check (single script): `./scripts/check_syntax.sh <path>` (e.g. `./scripts/check_syntax.sh res://core/level_game_state.gd`)
|
||||
Run single test: `./scripts/run_test.sh res://tests/<file>.gd`
|
||||
Run all tests: `./scripts/run_all.sh`
|
||||
Tests are custom headless scripts in `tests/` using `LevelGameState` simulation (no GUT).
|
||||
|
||||
## Architecture And Structure
|
||||
`project.godot` defines autoload singletons: `GameState` (runtime currency state + save/load) and `CurrencyGeneratorDatabase` (loads `generator_data.json`).
|
||||
`big_number.gd` (`class_name BigNumber`) is the internal numeric API for huge-value math, comparisons, formatting, and serialization.
|
||||
`big_number_museum.tscn` + `big_number_museum.gd` is the current main playable scene/prototype UI.
|
||||
`currency_generator.gd`, `currency_label.gd`, and `big_number_progress_bar.gd` are UI/gameplay adapters that react to `GameState` signals.
|
||||
Persistence is local JSON only: `user://idle_save.json`; there is no external DB/server/backend.
|
||||
|
||||
### No Autoloads
|
||||
There are **no autoload singletons**. All state lives in scene nodes. The project was fully decoupled from globals.
|
||||
|
||||
### Main Scene
|
||||
`run/main_scene` points to `docs/gyms/tiny_sword/tiny_sword.tscn` (uid `dadeowsvaywpp`). This is the canonical playable scene. `docs/museums/big_number_museum.tscn` is a secondary prototype/testing scene.
|
||||
|
||||
### Core Systems (`core/`)
|
||||
- **`core/big_number.gd`** — `class_name BigNumber`: numeric API for huge-value math, comparisons, formatting, and serialization (`{m: float, e: int}` scientific notation).
|
||||
- **`core/level_game_state.gd`** — `class_name LevelGameState` (extends `Node`): the central runtime state node. Owns all currency balances, generator states, buff levels, goal completion, research XP, prestige buff unlocks, and save/load. Assigned catalogue resources via `@export`:
|
||||
- `currency_catalogue` → `CurrencyCatalogue` (defines all currencies)
|
||||
- `buff_catalogue` → `BuffCatalogue` (generator buffs)
|
||||
- `goal_catalogue` → `GoalCatalogue` (achievement goals)
|
||||
- `research_catalogue` → `ResearchCatalogue` (research tracks)
|
||||
- `prestige_buff_catalogue` → `PrestigeBuffCatalogue` (permanent prestige upgrades)
|
||||
- **`core/edge_scroll_camera.gd`** — camera with edge-scrolling and zoom.
|
||||
|
||||
### Subsystems (`core/<name>/`)
|
||||
Each subsystem has its own directory with a `README.md`:
|
||||
- **`core/currency/`** — `Currency` resource, `CurrencyCatalogue` resource, `currency_panel.gd` UI.
|
||||
- **`core/generator/`** — `CurrencyGenerator` (production node), `CurrencyGeneratorData` (balancing resource), `GeneratorBuffData` (upgrades), `ResearchBuffCalculator` (static utility), `generator_container.gd` UI.
|
||||
- **`core/goals/`** — `GoalData` (achievement resource), `GoalRequirementData` (individual criteria), `current_goal_panel.gd` UI.
|
||||
- **`core/prestige/`** — `PrestigeManager` (child node of `LevelGameState` managing resets + multipliers), `PrestigeConfig`, `PrestigeBuffNode` (DAG graph node), `PrestigeBuffCatalogue`, UI panels (`prestige_panel`, `prestige_progress_bar`, `prestige_buff_graph_panel` with tiles).
|
||||
- **`core/research/`** — `ResearchData` resource (XP thresholds, production multipliers per level), `ResearchCatalogue`, `research_panel.gd` + `research_row.gd` UI.
|
||||
|
||||
### Content Is Data-Driven (`docs/gyms/`)
|
||||
All gameplay content (currencies, generators/buildings, buffs, goals, research, prestige config) is defined as `.tres` resource files under `docs/gyms/tiny_sword/` and composed in `tiny_sword.tscn`. No hardcoded JSON data files.
|
||||
|
||||
### Root-Level UI Helpers
|
||||
- `currency_label.gd`, `currency_tile.tscn` — reusable currency display components.
|
||||
- `big_number_progress_bar.gd` / `.tscn` — progress bar for BigNumber values.
|
||||
- `generator_buff_tile.gd` / `.tscn` — individual buff upgrade tile.
|
||||
- `goals_debug_ui.gd` / `.tscn` — debug overlay for goal state.
|
||||
|
||||
### Persistence
|
||||
Local JSON only: `LevelGameState` saves to the path set in its `save_file_path` export (e.g. `user://tiny_sword_save.json`). No external DB/server/backend. Save format version is currently 8.
|
||||
|
||||
### Testing
|
||||
Custom headless test scripts in `tests/` that instantiate `LevelGameState` directly (no scene tree needed). Helper scripts in `scripts/`:
|
||||
- `check_syntax.sh` — single-file Godot syntax check.
|
||||
- `run_test.sh` — run a single test script headless.
|
||||
- `run_all.sh` — run all tests.
|
||||
|
||||
## Code Style And Conventions
|
||||
Prefer typed GDScript (`var x: Type`, `func f() -> void`) and keep shared models/components as `class_name` scripts.
|
||||
Naming: `snake_case` for vars/functions/signals, `PascalCase` for classes, `UPPER_SNAKE_CASE` for constants.
|
||||
Follow existing Godot style: tabs/consistent indentation, early returns, and short functions grouped by purpose.
|
||||
When reading files/JSON, always guard `FileAccess.open(...)` and validate parsed types before indexing.
|
||||
Use signals (already in `GameState`) for UI updates instead of polling or cross-node direct mutation.
|
||||
Use signals (emitted by `LevelGameState`) for UI updates instead of polling or cross-node direct mutation.
|
||||
No Cursor/Claude/Windsurf/Cline/Goose/Copilot rule files were found in this repository, so this file is the canonical agent guidance.
|
||||
Avoid trivial one-line wrapper/helper functions that only forward or repack data. Inline the logic at the real call site unless the wrapper adds meaningful abstraction or is reused enough to justify it.
|
||||
Resource type safety: Use the specific resource. Avoid as much as possible explicit `Resource` usage.
|
||||
Resource type safety: Use the specific resource type. Avoid the bare `Resource` type. Prefer `Currency`, `CurrencyGeneratorData`, `GeneratorBuffData`, `GoalData`, `ResearchData`, `PrestigeBuffNode`, etc.
|
||||
|
||||
283
TODO.md
283
TODO.md
@@ -1,283 +0,0 @@
|
||||
# TODO
|
||||
|
||||
## Ascension Buff Graph — Needs Review Before Implementation
|
||||
|
||||
### Goal
|
||||
|
||||
Add a permanent buff graph system:
|
||||
- **Separate** from existing `GeneratorBuffData` (which resets on prestige).
|
||||
- **Permanent** — prestige does NOT reset ascension buff state.
|
||||
- **Unlocked** by spending ascension currency (earned during prestige).
|
||||
- **Graph/DAG-structured** — nodes have prerequisites (parent_ids) that must be met before a node can be purchased.
|
||||
- **One-shot unlocks** — each node is either locked or unlocked. No repeatable levels.
|
||||
|
||||
### Data Structure
|
||||
|
||||
#### `AscensionBuffNode` (Resource)
|
||||
Single node in the graph.
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `id` | `StringName` | Unique node ID |
|
||||
| `display_name` | `String` | UI label |
|
||||
| `description` | `String` (multiline) | Tooltip |
|
||||
| `icon` | `Texture2D` | Visual |
|
||||
| `parent_ids` | `Array[StringName]` | Prerequisites (DAG — multiple parents allowed) |
|
||||
| `effect_type` | `enum EffectType` | What this buff does |
|
||||
| `effect_value` | `float` | Effect magnitude |
|
||||
| `target_id` | `StringName` | Target generator/currency for type-specific effects (empty = global) |
|
||||
| `cost_mantissa` | `float` | Ascension currency cost |
|
||||
| `cost_exponent` | `int` | |
|
||||
| `tier` | `int` | Row in graph UI (visual grouping) |
|
||||
| `x_position` | `float` | Column in graph UI |
|
||||
|
||||
**Effect types** (extensible enum):
|
||||
|
||||
```
|
||||
GLOBAL_PRODUCTION_MULTIPLIER → multiply all generator output
|
||||
GENERATOR_PRODUCTION_MULTIPLIER → multiply one generator's output
|
||||
CURRENCY_PER_RUN → start each prestige with this currency
|
||||
COST_REDUCTION → reduce all generator purchase costs
|
||||
PRESTIGE_GAIN_MULTIPLIER → earn more ascension currency per prestige
|
||||
GENERATOR_COUNT_MULTIPLIER → scaling based on owned generator count
|
||||
RESEARCH_XP_MULTIPLIER → faster research leveling
|
||||
```
|
||||
|
||||
#### `AscensionBuffCatalogue` (Resource)
|
||||
Flat list of nodes. The graph emerges from `parent_ids`. Follows existing catalogue pattern (`BuffCatalogue`, `GoalCatalogue`, etc.).
|
||||
|
||||
| Field | Type |
|
||||
|-------|------|
|
||||
| `nodes` | `Array[AscensionBuffNode]` |
|
||||
|
||||
#### State in `LevelGameState`
|
||||
|
||||
```gdscript
|
||||
@export var ascension_buff_catalogue: AscensionBuffCatalogue
|
||||
var _ascension_buff_unlocked: Dictionary = {} # {StringName: bool} — node_id → unlocked
|
||||
```
|
||||
|
||||
- Never cleared by `reset_for_prestige()` (unlike `_buff_levels` for generator buffs, which IS cleared).
|
||||
- Normalized/sanitized on load (like generator states).
|
||||
|
||||
### Save Format
|
||||
|
||||
- Bump `CURRENT_SAVE_FORMAT_VERSION` from 7 → 8.
|
||||
- New key: `"ascension_buff_unlocked"` → array of unlocked node_id strings: `["strong_start", "efficiency"]`.
|
||||
|
||||
### API on LevelGameState
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `purchase_ascension_buff(buff_id) → bool` | Spend ascension currency, unlock node, emit signal, save |
|
||||
| `can_purchase_ascension_buff(buff_id) → bool` | All parent_ids unlocked? Not already unlocked? Enough currency? |
|
||||
| `is_ascension_buff_unlocked(buff_id) → bool` | Check if node is unlocked |
|
||||
| `get_ascension_buff_cost(buff_id) → BigNumber` | Fixed cost for the node |
|
||||
| `get_ascension_buff_multiplier(effect_type, target_id) → float` | Walk graph, compute combined effect from unlocked nodes |
|
||||
| `get_available_ascension_buffs() → Array[AscensionBuffNode]` | Nodes whose prerequisites are all met |
|
||||
|
||||
### Signals
|
||||
|
||||
```gdscript
|
||||
signal ascension_buff_unlocked(buff_id: StringName)
|
||||
```
|
||||
|
||||
Ascension currency balance changes are covered by the existing `currency_changed` signal — no separate signal needed.
|
||||
|
||||
### Prestige Integration
|
||||
|
||||
No changes needed to `PrestigeManager` — `_reset_all_buff_levels()` only touches `_buff_levels` (generator buffs), not `_ascension_buff_unlocked`. Ascension currency earning already emits `currency_changed` via `add_currency_by_id()`. The new dictionary naturally survives the reset.
|
||||
|
||||
### Purchase Flow
|
||||
|
||||
```
|
||||
Player clicks unlock node
|
||||
→ can_purchase_ascension_buff(node_id)
|
||||
→ All parent_ids unlocked?
|
||||
→ Not already unlocked?
|
||||
→ Enough ascension currency?
|
||||
→ YES: spend_currency_by_id(ascension_currency, cost)
|
||||
→ _ascension_buff_unlocked[node_id] = true
|
||||
→ emit ascension_buff_unlocked
|
||||
→ save_game()
|
||||
```
|
||||
|
||||
### Example Graph Content (Illustrative)
|
||||
|
||||
```
|
||||
Tier 0 (roots):
|
||||
strong_start → +10 gold/run cost: 1 parents: []
|
||||
efficiency → +10% global prod cost: 1 parents: []
|
||||
thrifty → -5% gen costs cost: 2 parents: []
|
||||
|
||||
Tier 1:
|
||||
better_start → +100 gold/run cost: 3 parents: [strong_start]
|
||||
enhanced_efficiency → +25% global prod cost: 3 parents: [efficiency]
|
||||
double_dip → +50% gold from forestry cost: 5 parents: [strong_start, efficiency]
|
||||
|
||||
Tier 2:
|
||||
power_surge → +100% global prod cost: 10 parents: [better_start, enhanced_efficiency]
|
||||
```
|
||||
|
||||
### Files to Create/Modify
|
||||
|
||||
| File | Action | Purpose |
|
||||
|------|--------|---------|
|
||||
| `core/ascension/ascension_buff_node.gd` | Create | Node resource |
|
||||
| `core/ascension/ascension_buff_catalogue.gd` | Create | Catalogue resource |
|
||||
| `core/ascension/ascension_buff_graph_panel.gd` | Create | Separate-screen UI panel |
|
||||
| `core/level_game_state.gd` | Modify | Add catalogue ref, `_ascension_buff_unlocked`, purchase/query methods, save/load, signal |
|
||||
| `docs/gyms/tiny_sword/ascension/*.tres` | Create | `.tres` resources for actual graph content |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Step 1: `core/ascension/ascension_buff_node.gd`
|
||||
|
||||
Create new `Resource` class.
|
||||
|
||||
- `class_name AscensionBuffNode extends Resource`
|
||||
- `enum EffectType` with all seven types from the data structure table
|
||||
- `@export` fields: `id`, `display_name`, `description` (multiline), `icon`, `parent_ids`, `effect_type`, `effect_value`, `target_id`, `cost_mantissa`, `cost_exponent`, `tier`, `x_position`
|
||||
- Helper methods:
|
||||
- `get_cost() → BigNumber` — returns `BigNumber.new(cost_mantissa, cost_exponent)`
|
||||
- `has_parents() → bool` — returns `not parent_ids.is_empty()`
|
||||
- `all_parents_unlocked(unlocked: Dictionary) → bool` — checks every id in `parent_ids` is in the dict as `true`
|
||||
- `is_valid() → bool` — id not empty, cost_mantissa > 0
|
||||
|
||||
### Step 2: `core/ascension/ascension_buff_catalogue.gd`
|
||||
|
||||
Create new `Resource` class. Follows `BuffCatalogue` pattern exactly.
|
||||
|
||||
- `class_name AscensionBuffCatalogue extends Resource`
|
||||
- `@export var nodes: Array[AscensionBuffNode] = []`
|
||||
- `get_node_by_id(id: StringName) → AscensionBuffNode` — linear search
|
||||
- `get_all_ids() → Array[StringName]` — collect non-empty ids
|
||||
- `get_root_nodes() → Array[AscensionBuffNode]` — nodes with empty `parent_ids`
|
||||
- `get_children_of(parent_id: StringName) → Array[AscensionBuffNode]` — nodes that list this id in `parent_ids`
|
||||
|
||||
### Step 3: Modify `core/level_game_state.gd`
|
||||
|
||||
**3a. Add export, state, signal, constants**
|
||||
|
||||
- Add `@export var ascension_buff_catalogue: AscensionBuffCatalogue` in the export section
|
||||
- Add `var _ascension_buff_unlocked: Dictionary = {}` in state variables
|
||||
- Add signal: `signal ascension_buff_unlocked(buff_id: StringName)` in signals section
|
||||
- Add constant: `const ASCENSION_BUFF_UNLOCKED_KEY: String = "ascension_buff_unlocked"` in save/load constants
|
||||
- Bump `CURRENT_SAVE_FORMAT_VERSION` from 7 to 8
|
||||
|
||||
**3b. Add initialization in `_ready()`**
|
||||
|
||||
After `_initialize_catalogues()`, call a new `_initialize_ascension_buffs()` method that:
|
||||
- Iterates `ascension_buff_catalogue.nodes` (if catalogue is set)
|
||||
- For each node id not already in `_ascension_buff_unlocked`, sets it to `false`
|
||||
|
||||
**3c. Add save serialization**
|
||||
|
||||
In `save_game()`, add:
|
||||
```gdscript
|
||||
ASCENSION_BUFF_UNLOCKED_KEY: _serialize_ascension_buff_unlocked(),
|
||||
```
|
||||
|
||||
New private method `_serialize_ascension_buff_unlocked() → Array`:
|
||||
- Returns an array of node id strings where `_ascension_buff_unlocked[id] == true`
|
||||
|
||||
**3d. Add load deserialization**
|
||||
|
||||
In `load_game()`, under the save format version check, add:
|
||||
```gdscript
|
||||
if parsed_data.has(ASCENSION_BUFF_UNLOCKED_KEY):
|
||||
_deserialize_ascension_buff_unlocked(parsed_data.get(ASCENSION_BUFF_UNLOCKED_KEY))
|
||||
```
|
||||
|
||||
New private method `_deserialize_ascension_buff_unlocked(raw: Variant) → void`:
|
||||
- If raw is Array, for each string element set `_ascension_buff_unlocked[StringName(s)] = true`
|
||||
- Then call `_initialize_ascension_buffs()` to fill in any missing node ids as `false`
|
||||
|
||||
**3e. Add API methods**
|
||||
|
||||
In a new `#region ascension_buff` section:
|
||||
|
||||
- `purchase_ascension_buff(buff_id: StringName) → bool`
|
||||
- Guard: if not `can_purchase_ascension_buff(buff_id)`, return false
|
||||
- Get node from catalogue, get cost via `BigNumber` from node's mantissa/exponent
|
||||
- Call `spend_currency_by_id(ascension_currency_id, cost)` — use `get_ascension_currency_id()` helper
|
||||
- Set `_ascension_buff_unlocked[buff_id] = true`
|
||||
- Emit `ascension_buff_unlocked.emit(buff_id)`
|
||||
- `save_game()`
|
||||
- Return true
|
||||
|
||||
- `can_purchase_ascension_buff(buff_id: StringName) → bool`
|
||||
- Node must exist in catalogue
|
||||
- Not already unlocked (`_ascension_buff_unlocked.get(buff_id, false) == false`)
|
||||
- All parent_ids unlocked (use node's helper or iterate)
|
||||
- Enough ascension currency (check via `get_currency_amount_by_id(ascension_currency_id)`)
|
||||
|
||||
- `is_ascension_buff_unlocked(buff_id: StringName) → bool`
|
||||
- `return _ascension_buff_unlocked.get(buff_id, false)`
|
||||
|
||||
- `get_ascension_buff_cost(buff_id: StringName) → BigNumber`
|
||||
- Get node, return `BigNumber.new(node.cost_mantissa, node.cost_exponent)`
|
||||
|
||||
- `get_ascension_buff_multiplier(effect_type: int, target_id: StringName = &"") → float`
|
||||
- Iterate all unlocked nodes matching the effect type
|
||||
- Multiply their `effect_value` together
|
||||
- If `target_id` is non-empty, filter nodes whose `target_id` matches or is empty (global) — TBD based on type semantics
|
||||
- Return the product (starting from 1.0)
|
||||
|
||||
- `get_available_ascension_buffs() → Array[AscensionBuffNode]`
|
||||
- Iterate all catalogue nodes not yet unlocked where all parent_ids are unlocked
|
||||
|
||||
- `get_ascension_currency_id() → StringName` (private helper or public)
|
||||
- Look up the ascension currency ID from PrestigeManager's config, or use a well-known ID like `&"ascension"`. Prefer using the catalogue to determine it. For now, return `&"ascension"` and validate it exists in the currency catalogue.
|
||||
|
||||
**3f. Ensure prestige does NOT reset ascension buffs**
|
||||
|
||||
Verify `reset_for_prestige()` does not touch `_ascension_buff_unlocked`. It currently clears `_buff_levels` (generator buffs), `generator_states`, `research_xp`, `research_levels`, goals, etc. No change needed — `_ascension_buff_unlocked` is a separate dictionary that is not listed in the reset logic.
|
||||
|
||||
### Step 4: Create `core/ascension/ascension_buff_graph_panel.gd`
|
||||
|
||||
Create a new `PanelContainer`-based UI. This is a separate screen — it can be toggled visible/hidden (e.g., via a button in the main UI).
|
||||
|
||||
- `class_name AscensionBuffGraphPanel extends PanelContainer`
|
||||
- `@onready` references to:
|
||||
- Title label
|
||||
- `ScrollContainer`/`VBoxContainer` for tier rows
|
||||
- Ascension currency balance label
|
||||
- `_game_state: LevelGameState` — found via `find_parent("LevelGameState")`
|
||||
- In `_ready()`:
|
||||
- Connect `game_state.ascension_buff_unlocked` → refresh
|
||||
- Connect `game_state.currency_changed` → refresh ascension currency label
|
||||
- Call `_build_graph()`
|
||||
- `_build_graph()`:
|
||||
- Clear existing rows
|
||||
- Group all catalogue nodes by `tier`, sort tiers ascending
|
||||
- For each tier, create an `HBoxContainer` row
|
||||
- For each node in the tier (sorted by `x_position`), instantiate a tile scene
|
||||
- **Tile scene** (`ascension_buff_graph_tile.tscn` + `.gd`):
|
||||
- A small `Button`/`Panel` showing:
|
||||
- Node icon
|
||||
- Node display_name
|
||||
- Effect description (e.g., "+10% global production")
|
||||
- Cost in ascension currency
|
||||
- Visual state: locked (greyed out), available (highlighted, purchasable), unlocked (green check)
|
||||
- On press: call `_game_state.purchase_ascension_buff(node_id)`
|
||||
- Connect to `ascension_buff_unlocked` and `currency_changed` to re-evaluate state
|
||||
- Ascension currency label at the top showing current balance
|
||||
|
||||
### Step 5: Create content `.tres` files (deferred)
|
||||
|
||||
Content is not defined yet per resolved question 4. When ready:
|
||||
- Create `docs/gyms/tiny_sword/ascension/` directory
|
||||
- Create `ascension_buff_catalogue.tres` resource with nodes
|
||||
- Wire it into the `LevelGameState` node in `tiny_sword.tscn` via editor
|
||||
|
||||
---
|
||||
|
||||
### Resolved Questions
|
||||
|
||||
1. **Effect stacking**: Multiply — consistent with the existing generator buff multiplier pattern.
|
||||
2. **UI placement**: A separate screen.
|
||||
3. **Ascension currency spending**: Spend through `LevelGameState.spend_currency_by_id()` — ascension currency is already in the currency catalogue.
|
||||
4. **Content**: None at the moment — define later.
|
||||
@@ -101,6 +101,9 @@ func _ready() -> void:
|
||||
_generator_id = _resolve_generator_id()
|
||||
cycle_progress_seconds = 0.0
|
||||
|
||||
game_state.ready.connect(_on_game_state_ready)
|
||||
|
||||
func _on_game_state_ready() -> void:
|
||||
if game_state:
|
||||
game_state.currency_changed.connect(_on_currency_changed)
|
||||
game_state.generator_state_changed.connect(_on_generated_state_changed)
|
||||
@@ -716,11 +719,13 @@ func _default_owned_state() -> int:
|
||||
## Area2D callback: pointer entered generator interaction region.
|
||||
func _on_area_2d_mouse_entered() -> void:
|
||||
_mouse_entered = true
|
||||
if info_generator_container:
|
||||
info_generator_container.visible = true
|
||||
|
||||
## Area2D callback: pointer exited generator interaction region.
|
||||
func _on_area_2d_mouse_exited() -> void:
|
||||
_mouse_entered = false
|
||||
if info_generator_container:
|
||||
info_generator_container.visible = false
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ func _on_generator_updated(_a = null, _b = null, _c = null, _d = null) -> void:
|
||||
|
||||
func _on_generator_state_changed(generator_id: StringName, _state: Dictionary) -> void:
|
||||
if generator_id == _generator_id:
|
||||
visible = true
|
||||
_refresh_generator_ui()
|
||||
|
||||
func _on_buy_buff_pressed(buff_id: StringName) -> void:
|
||||
|
||||
@@ -601,6 +601,7 @@ func reset_for_prestige(reset_total_currency: bool = false, emit_currency_signal
|
||||
var research_ids: Array = research_xp.keys()
|
||||
research_xp.clear()
|
||||
research_levels.clear()
|
||||
research_workers = 0
|
||||
|
||||
for research_id in research_ids:
|
||||
research_xp_changed.emit(research_id, BigNumber.from_float(0.0))
|
||||
|
||||
@@ -109,8 +109,8 @@ func _on_button_pressed() -> void:
|
||||
_game_state.purchase_prestige_buff(prestige_buff.id)
|
||||
|
||||
|
||||
func _on_prestige_buff_unlocked(buff_id: StringName) -> void:
|
||||
if prestige_buff and buff_id == prestige_buff.id:
|
||||
func _on_prestige_buff_unlocked(_buff_id: StringName) -> void:
|
||||
# Any buff unlock may change this tile's parent-availability, so always refresh.
|
||||
_refresh_state()
|
||||
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ func perform_prestige() -> bool:
|
||||
game_state.reset_for_prestige(true, false, preserve_currencies)
|
||||
_reset_all_buff_levels()
|
||||
_reinitialize_generators_after_prestige_reset()
|
||||
_reset_alchemy_workers_after_prestige()
|
||||
if game_state:
|
||||
game_state.emit_currency_changed_for_all()
|
||||
_initialize_run_tracking_from_current_state()
|
||||
@@ -469,6 +470,16 @@ func _reinitialize_generators_after_prestige_reset() -> void:
|
||||
|
||||
generator_node.call("reset_runtime_state_for_prestige")
|
||||
|
||||
func _reset_alchemy_workers_after_prestige() -> void:
|
||||
var scene_root: Node = get_tree().current_scene
|
||||
if scene_root == null:
|
||||
return
|
||||
|
||||
var alchemy_panels: Array[Node] = scene_root.find_children("*", "AlchemyCurrenciesPanel", true, false)
|
||||
for panel in alchemy_panels:
|
||||
if panel != null and panel.has_method("reset_workers"):
|
||||
panel.reset_workers()
|
||||
|
||||
func _deserialize_big_number(raw_value: Variant) -> BigNumber:
|
||||
if raw_value is Dictionary:
|
||||
return BigNumber.deserialize(raw_value)
|
||||
|
||||
@@ -73,7 +73,6 @@ func _refresh_ui() -> void:
|
||||
visible = false
|
||||
return
|
||||
|
||||
visible = true
|
||||
var total: BigNumber = manager.get_total_prestige()
|
||||
var pending: BigNumber = manager.calculate_pending_gain()
|
||||
var multiplier: float = float(manager.get_total_multiplier())
|
||||
|
||||
388
docs/gyms/tiny_sword/PROGRESSION.md
Normal file
388
docs/gyms/tiny_sword/PROGRESSION.md
Normal file
@@ -0,0 +1,388 @@
|
||||
# Tiny Sword — Progression Plan
|
||||
|
||||
Full progression design for a playable idle game, from first click to victory.
|
||||
|
||||
## Overview
|
||||
|
||||
The game has 8 phases. Each phase is gated behind a goal. Completing the goal unlocks new buildings, buffs, or systems that accelerate progress toward the next phase.
|
||||
|
||||
The Devil Idol is visible from the start as a long-term goal — destroying it is the one-time win condition. Prestige loops make it asymptotically easier.
|
||||
|
||||
---
|
||||
|
||||
## Phase Table
|
||||
|
||||
| Phase | Goal | Threshold | Unlocks |
|
||||
|---|---|---|---|
|
||||
| 1 | *(none)* | Start | Gold Mine (click only, no auto-production) |
|
||||
| 2 | `gold_total_30` | 30 total gold | Gold Clicker, Gold Dynamo, Summon Worker buffs |
|
||||
| 3 | `gold_total_1300` | 1,300 total gold | Farm building (food economy begins) |
|
||||
| 4 | `gold_total_13k` | 13,000 total gold | Forestry building (wood economy), Farm Dynamo, Forest Dynamo |
|
||||
| 5 | `gold_350k_wood_20k` | 350K gold + 20K wood | **Prestige** (Castle) + **Research** (Monastery) |
|
||||
| 6 | `gold_2M_food_200k_wood_200k` | 2M gold + 200K food + 200K wood | **Alchemy Tower** |
|
||||
| 7 | *(post-alchemy)* | — | Alchemy buffs (spend mana stone / cognite / philosopher stone) |
|
||||
| 8 | `gold_50M_food_5M_wood_5M` | 50M gold + 5M food + 5M wood | **Barracks** + **Devil Idol fight** |
|
||||
|
||||
> **Note**: Phase 7 has no dedicated goal — it unlocks implicitly when the Alchemy Tower is active and the player can craft alchemy resources. The Phase 8 goal only needs to be reachable after Alchemy buffs have been applied.
|
||||
|
||||
---
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 1 — Start
|
||||
|
||||
**State**: Gold Mine is visible and unlocked, 0 owned, 0 workers.
|
||||
**Action**: Player clicks the Gold Mine to earn gold manually (1 gold / click, 0.2s cooldown).
|
||||
**Exit**: Accumulate 30 total gold → Phase 2.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2 — First Automation
|
||||
|
||||
**Goal**: `gold_total_30` (30 total gold)
|
||||
|
||||
**Unlocks**:
|
||||
| Buff | Kind | Cost | Effect | Max Level |
|
||||
|---|---|---|---|---|
|
||||
| Gold Clicker | MANUAL_CLICK_MULTIPLIER | Gold | ×(1 + 1×level) click multiplier | 4 |
|
||||
| Gold Dynamo | AUTO_PRODUCTION_MULTIPLIER | Food | ×(1 + 1×level) auto prod | 10 |
|
||||
| Summon Worker | RESOURCE_PURCHASE | Gold | Grants 1 worker | ∞ |
|
||||
|
||||
**Design note**: Gold Dynamo costs food but Farm isn't unlocked until Phase 3. This is intentional — the buff is visible but unusable until food flows. It creates anticipation.
|
||||
|
||||
**Exit**: Buy Summon Worker (200 gold) → get 1 worker → buy first Gold Mine (1 worker) → auto-production begins at 20 gold/s. Accumulate 1,300 total gold → Phase 3.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3 — Food Economy
|
||||
|
||||
**Goal**: `gold_total_1300` (1,300 total gold)
|
||||
|
||||
**Unlocks**:
|
||||
- **Farm** building — starts with 1 owned, produces 20 food/s, purchasable with workers
|
||||
|
||||
**New loop**: Gold → workers → more Gold Mines + more Farms. Food accumulates. Gold Dynamo (unlocked in Phase 2) now becomes purchasable with food, multiplying gold mine output.
|
||||
|
||||
**Exit**: Accumulate 13,000 total gold → Phase 4.
|
||||
|
||||
---
|
||||
|
||||
### Phase 4 — Wood Economy
|
||||
|
||||
**Goal**: `gold_total_13k` (13,000 total gold)
|
||||
|
||||
**Unlocks**:
|
||||
- **Forestry** building — starts with 1 owned, produces 20 wood/s, purchasable with workers
|
||||
- **Farm Dynamo** buff — AUTO_PRODUCTION_MULTIPLIER for Farm, costs food, max 10
|
||||
- **Forest Dynamo** buff — AUTO_PRODUCTION_MULTIPLIER for Forestry, costs food, max 10
|
||||
|
||||
**New loop**: Wood flows alongside gold and food. Three parallel production lines scale independently.
|
||||
|
||||
**Exit**: Accumulate 350,000 gold + 20,000 wood → Phase 5.
|
||||
|
||||
---
|
||||
|
||||
### Phase 5 — Prestige + Research
|
||||
|
||||
**Goal**: `gold_350k_wood_20k` (350,000 total gold + 20,000 total wood)
|
||||
|
||||
**Unlocks**:
|
||||
- **Prestige panel** at Castle becomes visible and interactive
|
||||
- **Research panel** at Monastery becomes visible and interactive
|
||||
|
||||
#### Prestige System
|
||||
|
||||
| Config Property | Value |
|
||||
|---|---|
|
||||
| Source basis | RUN_TOTAL (gold earned this run) |
|
||||
| Formula | POWER, exponent 0.5, scale 1.0 |
|
||||
| Threshold | 50,000 gold (per prestige point) |
|
||||
| Rounding | FLOOR |
|
||||
|
||||
**First prestige estimate**: At 350K gold → `floor(sqrt(350000/50000))` = `floor(sqrt(7))` = `floor(2.64)` = **2 ascension points**.
|
||||
**Second run**: With prestige buffs applied and ~500K gold → `floor(sqrt(10))` = **3 ascension points**.
|
||||
|
||||
`total_prestige_earned` persists across resets — it's a strictly increasing counter that never resets. It feeds the prestige multiplier (`base_multiplier + total_prestige_earned × multiplier_per_prestige`).
|
||||
|
||||
**Prestige Buff Graph** (purchasable with ascension points):
|
||||
| Node | Cost | Prerequisites | Effect |
|
||||
|---|---|---|---|
|
||||
| Golden Touch | 1 ascension | *(none)* | Gold Mine ×2 production |
|
||||
| Fertile Grounds | 2 ascension | Golden Touch | Farm ×2 production |
|
||||
| Wood Touch | 3 ascension | Fertile Grounds | Forestry ×2 production |
|
||||
| Martial Prowess | 5 ascension | Wood Touch | Barracks ×2 unit production *(Phase 8)* |
|
||||
|
||||
#### Research System
|
||||
|
||||
Three research tracks at the Monastery:
|
||||
|
||||
| Track | Linked Generator | XP Source |
|
||||
|---|---|---|
|
||||
| Gold Mine Research | Gold Mine | Gold production |
|
||||
| Farm Research | Farm | Food production |
|
||||
| Forestry Research | Forestry | Wood production |
|
||||
|
||||
One track active at a time. XP scales with workers assigned to research (`research_workers`). Default: 0.01 XP per currency unit, 100 XP for level 1, ×1.5 growth per level, +0.1 multiplier per level.
|
||||
|
||||
**Exit**: Accumulate 2M gold + 200K food + 200K wood → Phase 6.
|
||||
|
||||
---
|
||||
|
||||
### Phase 6 — Alchemy Tower
|
||||
|
||||
**Goal**: `gold_2M_food_200k_wood_200k` (2,000,000 total gold + 200,000 food + 200,000 wood)
|
||||
|
||||
**Unlocks**:
|
||||
- **Alchemy Tower** building — passively produces Magic Gold
|
||||
|
||||
#### Alchemy Tower Mechanics
|
||||
|
||||
- Cycles of base 5 seconds, producing 1 Magic Gold per cycle
|
||||
- Cycle time grows ×1.2 per completed cycle
|
||||
- Workers can be assigned (1 worker each, up to 10) for +10% speed each
|
||||
- Workers assigned here are separate from research workers (spent from worker currency)
|
||||
|
||||
#### Crafting Recipes
|
||||
|
||||
| Recipe | Input | Output |
|
||||
|---|---|---|
|
||||
| Mana Stone | 10 Magic Gold | 1 Mana Stone |
|
||||
| Cognite | 25 Magic Gold | 1 Cognite |
|
||||
| Philosopher Stone | 10 Mana Stone + 10 Cognite | 1 Philosopher Stone |
|
||||
|
||||
**Exit**: Build up alchemy resources → Phase 7 (implicit).
|
||||
|
||||
---
|
||||
|
||||
### Phase 7 — Alchemy Buffs
|
||||
|
||||
**Prerequisite**: Alchemy Tower active, player can craft resources.
|
||||
|
||||
Three new buffs, purchasable with alchemy resources:
|
||||
|
||||
| Buff | Cost Currency | Target | Effect | Max Level |
|
||||
|---|---|---|---|---|
|
||||
| Fertile Infusion | Mana Stone | Farm | ×2 production per level | 5 |
|
||||
| Sylvan Cognite | Cognite | Forestry | ×2 production per level | 5 |
|
||||
| Opus Magnum | Philosopher Stone | Gold Mine | ×3 production per level | 5 |
|
||||
|
||||
Added to the buff catalogue. Available via the existing buff purchase UI in each generator's panel.
|
||||
|
||||
**Exit**: Accumulate 50M gold + 5M food + 5M wood → Phase 8.
|
||||
|
||||
---
|
||||
|
||||
### Phase 8 — Devil Idol (Finale)
|
||||
|
||||
**Goal**: `gold_50M_food_5M_wood_5M` (50,000,000 total gold + 5,000,000 food + 5,000,000 wood)
|
||||
|
||||
**Unlocks**:
|
||||
- **Barracks** building — produces Military Units (pure damage currency)
|
||||
- **Devil Idol** becomes targetable
|
||||
|
||||
#### Barracks
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Produces | `unit` currency |
|
||||
| Purchase cost | Workers |
|
||||
| Unlock goal | Phase 8 goal |
|
||||
| Research track | Shares with ? or none (TBD) |
|
||||
| Prestige buff | Martial Prowess (×2 unit production) |
|
||||
|
||||
#### Devil Idol
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Visible from | Start (always visible in world) |
|
||||
| Targetable from | Phase 8 |
|
||||
| HP | `1e12` (1,000,000,000,000) — defined in an exported resource |
|
||||
| Damage mechanic | Click idol to spend 1 unit → deal 1 damage. Or "Send Army" button for bulk spend. |
|
||||
| Win condition | HP ≤ 0 → victory signal (TBD: victory screen, credits, etc.) |
|
||||
|
||||
#### Asymptotic Loop
|
||||
|
||||
Without prestige: defeating the idol would require 10^12 units, which with baseline production is essentially infinite.
|
||||
|
||||
With prestige: each loop applies Martial Prowess (+×2 per level) to Barracks production. A player with 5 ascension points in Martial Prowess produces ×32 units per cycle. As they earn more ascension points across runs, unit production scales multiplicatively, asymptotically reducing the "time to kill."
|
||||
|
||||
The idol HP never resets — it's persistent across prestige. Each prestige loop chips away at it.
|
||||
|
||||
---
|
||||
|
||||
## Currency Catalogue (Complete)
|
||||
|
||||
| Currency | ID | Produced By | Used For |
|
||||
|---|---|---|---|
|
||||
| Gold | `gold` | Gold Mine | Buff purchases, worker generation |
|
||||
| Worker | `worker` | Summon Worker buff | Purchasing generators, research assignment, alchemy workers |
|
||||
| Food | `food` | Farm | Buff purchases |
|
||||
| Wood | `wood` | Forestry | Goal requirements |
|
||||
| Ascension | `ascension` | Prestige resets | Prestige buff graph |
|
||||
| Magic Gold | `magic_gold` | Alchemy Tower | Crafting mana stones + cognite |
|
||||
| Mana Stone | `mana_stone` | Craft (10 magic gold) | Crafting philosopher stones, Fertile Infusion buff |
|
||||
| Cognite | `cognite` | Craft (25 magic gold) | Crafting philosopher stones, Sylvan Cognite buff |
|
||||
| Philosopher Stone | `philosoper_stone` | Craft (10 mana + 10 cognite) | Opus Magnum buff |
|
||||
| Unit | `unit` | Barracks | Devil Idol damage |
|
||||
|
||||
---
|
||||
|
||||
## Buff Catalogue (Complete)
|
||||
|
||||
| Buff | ID | Kind | Cost | Unlock Goal | Max Lvl | Target |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Gold Clicker | `gold_click_focus` | CLICK_MULT | Gold | `gold_total_30` | 4 | Gold Mine |
|
||||
| Gold Dynamo | `gold_auto_flux` | AUTO_MULT | Food | `gold_total_30` | 10 | Gold Mine |
|
||||
| Summon Worker | `summon_worker` | RESOURCE | Gold | `gold_total_30` | ∞ | Worker |
|
||||
| Farm Dynamo | `farm_auto_flux` | AUTO_MULT | Food | `gold_total_13k` | 10 | Farm |
|
||||
| Forest Dynamo | `forestry_auto_flux` | AUTO_MULT | Food | `gold_total_13k` | 10 | Forestry |
|
||||
| Fertile Infusion | *(new)* | AUTO_MULT | Mana Stone | *(alchemy active)* | 5 | Farm |
|
||||
| Sylvan Cognite | *(new)* | AUTO_MULT | Cognite | *(alchemy active)* | 5 | Forestry |
|
||||
| Opus Magnum | *(new)* | AUTO_MULT | Philo Stone | *(alchemy active)* | 5 | Gold Mine |
|
||||
|
||||
---
|
||||
|
||||
## Goal Catalogue (Complete)
|
||||
|
||||
| Goal ID | Requirements | Unlock Behavior | Phase |
|
||||
|---|---|---|---|
|
||||
| `gold_total_30` | 30 gold | AUTOMATIC | 2 |
|
||||
| `gold_total_1300` | 1,300 gold | MANUAL | 3 |
|
||||
| `gold_total_13k` | 13,000 gold | MANUAL | 4 |
|
||||
| `gold_350k_wood_20k` | 350,000 gold + 20,000 wood | MANUAL | 5 |
|
||||
| `gold_2M_food_200k_wood_200k` *(new)* | 2M gold + 200K food + 200K wood | MANUAL | 6 |
|
||||
| `gold_50M_food_5M_wood_5M` *(new)* | 50M gold + 5M food + 5M wood | MANUAL | 8 |
|
||||
|
||||
---
|
||||
|
||||
## Prestige Buff Graph (Complete)
|
||||
|
||||
```
|
||||
Golden Touch (1pt)
|
||||
│
|
||||
Fertile Grounds (2pt)
|
||||
│
|
||||
Wood Touch (3pt)
|
||||
│
|
||||
Martial Prowess (5pt)
|
||||
```
|
||||
|
||||
| Node | Cost | Prerequisites | Effect | Target |
|
||||
|---|---|---|---|---|
|
||||
| Golden Touch | 1 | none | ×2 production | Gold Mine |
|
||||
| Fertile Grounds | 2 | Golden Touch | ×2 production | Farm |
|
||||
| Wood Touch | 3 | Fertile Grounds | ×2 production | Forestry |
|
||||
| Martial Prowess | 5 | Wood Touch | ×2 production | Barracks |
|
||||
|
||||
---
|
||||
|
||||
## Buildings (Complete)
|
||||
|
||||
| Building | Scene | Generates | Cost Currency | Unlock Goal |
|
||||
|---|---|---|---|---|
|
||||
| Gold Mine | `gold_mine.tscn` | Gold | Worker | *(none)* |
|
||||
| Farm | `farm.tscn` | Food | Worker | `gold_total_1300` |
|
||||
| Forestry | `forestry.tscn` | Wood | Worker | `gold_total_13k` |
|
||||
| Monastery | `monastery.tscn` | *(none — Research UI)* | — | `gold_350k_wood_20k` (panel visible) |
|
||||
| Castle | `castle.tscn` | *(none — Prestige UI)* | — | `gold_350k_wood_20k` (panel visible) |
|
||||
| Alchemy Tower | `alchemy_tower.tscn` | Magic Gold | — | `gold_2M_food_200k_wood_200k` *(new)* |
|
||||
| Barracks | `barracks.tscn` *(new)* | Unit | Worker | `gold_50M_food_5M_wood_5M` *(new)* |
|
||||
| Devil Idol | `devil_idol.tscn` *(new)* | *(none — damage sink)* | — | `gold_50M_food_5M_wood_5M` (targetable) |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1–2 (already exists)
|
||||
- [x] Gold Mine building + generator
|
||||
- [x] Gold Clicker, Gold Dynamo, Summon Worker buffs
|
||||
- [x] `gold_total_30` goal
|
||||
|
||||
### Phase 3–4 (already exists)
|
||||
- [x] Farm building + generator
|
||||
- [x] `gold_total_1300` goal
|
||||
- [x] Forestry building + generator
|
||||
- [x] Farm Dynamo, Forest Dynamo buffs
|
||||
- [x] `gold_total_13k` goal
|
||||
|
||||
### Phase 5 — Prestige + Research Visibility
|
||||
- [ ] **`castle.gd`**: Uncomment `_prestige_panel.visible = true`, gate behind `gold_350k_wood_20k` goal completion
|
||||
- [ ] **`primary_prestige.tres`**: Tune basis → RUN_TOTAL, source → `"gold"`, threshold → 50,000, exponent → 0.5
|
||||
- [ ] **`monastery.gd`**: Gate `_research_panel.visible` behind `gold_350k_wood_20k` goal completion
|
||||
- [ ] **`prestige_buff_catalogue.tres`**: Add Wood Touch + Martial Prowess nodes
|
||||
- [x] `gold_350k_wood_20k` goal (already exists)
|
||||
|
||||
### Phase 6 — Alchemy Tower Gating
|
||||
- [ ] **New goal `.tres`**: `gold_2M_food_200k_wood_200k`
|
||||
- [ ] **`ts_goal_catalogue.tres`**: Register new goal
|
||||
- [ ] **`alchemy_tower_data.tres`** or **`alchemy_tower.tscn`**: Add `unlock_goal` field
|
||||
- [ ] Wire unlock to Alchemy Tower visibility
|
||||
|
||||
### Phase 7 — Alchemy Buffs
|
||||
- [ ] **New buff `.tres`**: Fertile Infusion (mana stone → farm, ×2, max 5)
|
||||
- [ ] **New buff `.tres`**: Sylvan Cognite (cognite → forestry, ×2, max 5)
|
||||
- [ ] **New buff `.tres`**: Opus Magnum (philosopher stone → gold mine, ×3, max 5)
|
||||
- [ ] **`ts_buff_catalogue.tres`**: Register the 3 new buffs
|
||||
|
||||
### Phase 8 — Barracks + Devil Idol
|
||||
- [ ] **New goal `.tres`**: `gold_50M_food_5M_wood_5M`
|
||||
- [ ] **`ts_goal_catalogue.tres`**: Register new goal
|
||||
- [ ] **New currency `.tres`**: Unit (`unit`)
|
||||
- [ ] **`ts_currency_catalogue.tres`**: Register unit currency
|
||||
- [ ] **New generator data `.tres`**: `barracks_generator.tres`
|
||||
- [ ] **New scene**: `barracks.tscn` (NG2D, sprite, Area2D, CurrencyGenerator, GeneratorContainer)
|
||||
- [ ] **New scene**: `devil_idol.tscn` (NG2D, sprite, Area2D, click handler, HP display, win logic)
|
||||
- [ ] **New prestige buff `.tres`**: Martial Prowess (×2 barracks production, cost 5 ascension, parent: Wood Touch)
|
||||
- [ ] **`prestige_buff_catalogue.tres`**: Register Martial Prowess
|
||||
- [ ] **`tiny_sword.tscn`**: Place Barracks + Devil Idol in world
|
||||
- [ ] Victory condition handling (TBD: signal, screen, save state)
|
||||
|
||||
### Research (Phase 5+, already functional)
|
||||
- [x] ResearchData for Gold Mine, Farm, Forestry
|
||||
- [x] ResearchPanel at Monastery
|
||||
- [x] ResearchRow UI
|
||||
- [x] XP generation in CurrencyGenerator
|
||||
- [ ] *(optional)*: Add Barracks research track
|
||||
- [ ] *(optional)*: Tune per-track values (XP required, multiplier growth)
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
```
|
||||
docs/gyms/tiny_sword/
|
||||
├── PROGRESSION.md ← this file
|
||||
├── tiny_sword.tscn ← main scene (add Barracks + Devil Idol)
|
||||
│
|
||||
├── currencies/
|
||||
│ ├── ts_currency_catalogue.tres ← register "unit" currency
|
||||
│ └── unit.tres ← NEW: unit currency definition
|
||||
│
|
||||
├── buffs/
|
||||
│ ├── ts_buff_catalogue.tres ← register alchemy buffs
|
||||
│ ├── fertile_infusion_buff.tres ← NEW
|
||||
│ ├── sylvan_cognite_buff.tres ← NEW
|
||||
│ └── opus_magnum_buff.tres ← NEW
|
||||
│
|
||||
├── goals/
|
||||
│ ├── ts_goal_catalogue.tres ← register new phase 6 + 8 goals
|
||||
│ ├── gold_2M_food_200k_wood_200k.tres ← NEW
|
||||
│ └── gold_50M_food_5M_wood_5M.tres ← NEW
|
||||
│
|
||||
├── prestige/
|
||||
│ ├── primary_prestige.tres ← tune: RUN_TOTAL, gold, 50K threshold
|
||||
│ ├── prestige_buff_catalogue.tres ← register Wood Touch + Martial Prowess
|
||||
│ ├── prestige_buff_wood_boost.tres ← existing (may need cost/parent update)
|
||||
│ └── prestige_buff_barracks_boost.tres ← NEW
|
||||
│
|
||||
├── buildings/
|
||||
│ ├── barracks/ ← NEW
|
||||
│ │ ├── barracks.tscn
|
||||
│ │ └── barracks_generator.tres
|
||||
│ ├── devil_idol/ ← NEW
|
||||
│ │ └── devil_idol.tscn
|
||||
│ └── ... (existing buildings)
|
||||
│
|
||||
└── research/
|
||||
└── ts_research_catalogue.tres ← (optional) add barracks research
|
||||
```
|
||||
17
docs/gyms/tiny_sword/buffs/barracks_food_boost_buff.tres
Normal file
17
docs/gyms/tiny_sword/buffs/barracks_food_boost_buff.tres
Normal file
@@ -0,0 +1,17 @@
|
||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_buff"]
|
||||
[ext_resource type="Resource" uid="uid://bxg2au0ijp242" path="res://docs/gyms/tiny_sword/currencies/food.tres" id="2_cost"]
|
||||
[ext_resource type="Resource" uid="uid://bn4gm4sss85d0" path="res://docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres" id="3_goal"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_buff")
|
||||
id = &"barracks_food_boost"
|
||||
target_ids = Array[StringName]([&"barracks"])
|
||||
text = "Supply Lines"
|
||||
unlock_goal = ExtResource("3_goal")
|
||||
max_level = 50
|
||||
effect_increment = 0.5
|
||||
cost_currency = ExtResource("2_cost")
|
||||
base_cost_mantissa = 50000.0
|
||||
cost_multiplier = 1.15
|
||||
17
docs/gyms/tiny_sword/buffs/barracks_gold_boost_buff.tres
Normal file
17
docs/gyms/tiny_sword/buffs/barracks_gold_boost_buff.tres
Normal file
@@ -0,0 +1,17 @@
|
||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_buff"]
|
||||
[ext_resource type="Resource" uid="uid://w4u1hddplb4e" path="res://docs/gyms/tiny_sword/currencies/gold.tres" id="2_cost"]
|
||||
[ext_resource type="Resource" uid="uid://bn4gm4sss85d0" path="res://docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres" id="3_goal"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_buff")
|
||||
id = &"barracks_gold_boost"
|
||||
target_ids = Array[StringName]([&"barracks"])
|
||||
text = "War Chest"
|
||||
unlock_goal = ExtResource("3_goal")
|
||||
max_level = 50
|
||||
effect_increment = 0.5
|
||||
cost_currency = ExtResource("2_cost")
|
||||
base_cost_mantissa = 100000.0
|
||||
cost_multiplier = 1.15
|
||||
17
docs/gyms/tiny_sword/buffs/barracks_wood_boost_buff.tres
Normal file
17
docs/gyms/tiny_sword/buffs/barracks_wood_boost_buff.tres
Normal file
@@ -0,0 +1,17 @@
|
||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_buff"]
|
||||
[ext_resource type="Resource" uid="uid://bgsk8h4w80h45" path="res://docs/gyms/tiny_sword/currencies/wood.tres" id="2_cost"]
|
||||
[ext_resource type="Resource" uid="uid://bn4gm4sss85d0" path="res://docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres" id="3_goal"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_buff")
|
||||
id = &"barracks_wood_boost"
|
||||
target_ids = Array[StringName]([&"barracks"])
|
||||
text = "Siege Works"
|
||||
unlock_goal = ExtResource("3_goal")
|
||||
max_level = 50
|
||||
effect_increment = 0.5
|
||||
cost_currency = ExtResource("2_cost")
|
||||
base_cost_mantissa = 50000.0
|
||||
cost_multiplier = 1.15
|
||||
18
docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres
Normal file
18
docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres
Normal file
@@ -0,0 +1,18 @@
|
||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://c0ed8k5nlm22p"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_buff"]
|
||||
[ext_resource type="Resource" uid="uid://brctmnpmhjas6" path="res://docs/gyms/tiny_sword/currencies/mana_stone.tres" id="2_cost"]
|
||||
[ext_resource type="Resource" uid="uid://bxg2au0ijp242" path="res://docs/gyms/tiny_sword/currencies/food.tres" id="3_target"]
|
||||
[ext_resource type="Resource" uid="uid://bex2hsenbr54e" path="res://docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres" id="4_goal"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_buff")
|
||||
id = &"fertile_infusion"
|
||||
target_ids = Array[StringName]([&"farm"])
|
||||
text = "Fertile Infusion"
|
||||
max_level = 5
|
||||
unlock_goal = ExtResource("4_goal")
|
||||
effect_increment = 1.0
|
||||
cost_currency = ExtResource("2_cost")
|
||||
base_cost_mantissa = 1.0
|
||||
resource_target_currency = ExtResource("3_target")
|
||||
19
docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres
Normal file
19
docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres
Normal file
@@ -0,0 +1,19 @@
|
||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://xdr3fma7h2b6"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_buff"]
|
||||
[ext_resource type="Resource" uid="uid://co4fiqgluwit0" path="res://docs/gyms/tiny_sword/currencies/philosoper_stone.tres" id="2_cost"]
|
||||
[ext_resource type="Resource" uid="uid://w4u1hddplb4e" path="res://docs/gyms/tiny_sword/currencies/gold.tres" id="3_target"]
|
||||
[ext_resource type="Resource" uid="uid://bex2hsenbr54e" path="res://docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres" id="4_goal"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_buff")
|
||||
id = &"opus_magnum"
|
||||
target_ids = Array[StringName]([&"goldmine"])
|
||||
text = "Opus Magnum"
|
||||
max_level = 5
|
||||
unlock_goal = ExtResource("4_goal")
|
||||
effect_increment = 2.0
|
||||
cost_currency = ExtResource("2_cost")
|
||||
base_cost_mantissa = 1.0
|
||||
cost_multiplier = 2.0
|
||||
resource_target_currency = ExtResource("3_target")
|
||||
18
docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres
Normal file
18
docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres
Normal file
@@ -0,0 +1,18 @@
|
||||
[gd_resource type="Resource" script_class="GeneratorBuffData" format=3 uid="uid://bkqosgm72sb8w"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dnhocfsuvmeyb" path="res://core/generator/generator_buff_data.gd" id="1_buff"]
|
||||
[ext_resource type="Resource" uid="uid://t6du7gm2ywbi" path="res://docs/gyms/tiny_sword/currencies/cognite.tres" id="2_cost"]
|
||||
[ext_resource type="Resource" uid="uid://bgsk8h4w80h45" path="res://docs/gyms/tiny_sword/currencies/wood.tres" id="3_target"]
|
||||
[ext_resource type="Resource" uid="uid://bex2hsenbr54e" path="res://docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres" id="4_goal"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_buff")
|
||||
id = &"sylvan_cognite"
|
||||
target_ids = Array[StringName]([&"forestry"])
|
||||
text = "Sylvan Cognite"
|
||||
max_level = 5
|
||||
unlock_goal = ExtResource("4_goal")
|
||||
effect_increment = 1.0
|
||||
cost_currency = ExtResource("2_cost")
|
||||
base_cost_mantissa = 1.0
|
||||
resource_target_currency = ExtResource("3_target")
|
||||
@@ -7,8 +7,14 @@
|
||||
[ext_resource type="Resource" uid="uid://bjc6qmvr7pe12" path="res://docs/gyms/tiny_sword/buffs/spawn_worker_buff.tres" id="4_hnq3m"]
|
||||
[ext_resource type="Resource" uid="uid://cg7os1rfknw05" path="res://docs/gyms/tiny_sword/buffs/farm_auto_flux_buff.tres" id="5_mii30"]
|
||||
[ext_resource type="Resource" uid="uid://kamgujbqqhg3" path="res://docs/gyms/tiny_sword/buffs/forestry_auto_flux_buff.tres" id="6_k5yi4"]
|
||||
[ext_resource type="Resource" uid="uid://c0ed8k5nlm22p" path="res://docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres" id="7_fertile"]
|
||||
[ext_resource type="Resource" uid="uid://bkqosgm72sb8w" path="res://docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres" id="8_sylvan"]
|
||||
[ext_resource type="Resource" uid="uid://xdr3fma7h2b6" path="res://docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres" id="9_opus"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/buffs/barracks_gold_boost_buff.tres" id="10_wc"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/buffs/barracks_food_boost_buff.tres" id="11_sl"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/buffs/barracks_wood_boost_buff.tres" id="12_sw"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_iuxc1")
|
||||
buffs = Array[ExtResource("1_up80l")]([ExtResource("2_dtvfl"), ExtResource("3_0bb41"), ExtResource("4_hnq3m"), ExtResource("5_mii30"), ExtResource("6_k5yi4")])
|
||||
buffs = Array[ExtResource("1_up80l")]([ExtResource("2_dtvfl"), ExtResource("3_0bb41"), ExtResource("4_hnq3m"), ExtResource("5_mii30"), ExtResource("6_k5yi4"), ExtResource("7_fertile"), ExtResource("8_sylvan"), ExtResource("9_opus"), ExtResource("10_wc"), ExtResource("11_sl"), ExtResource("12_sw")])
|
||||
metadata/_custom_type_script = "uid://ctc5yjlnvi0ok"
|
||||
|
||||
@@ -98,7 +98,7 @@ func _update_worker_display() -> void:
|
||||
var worker_currency: Currency = _get_worker_currency()
|
||||
var can_assign: bool = false
|
||||
|
||||
if worker_currency != null and _alchemy_worker_count < 10:
|
||||
if worker_currency != null and _alchemy_worker_count < _alchemy_tower.data.max_workers:
|
||||
var worker_balance: BigNumber = _game_state.get_currency_amount(worker_currency)
|
||||
can_assign = worker_balance.mantissa >= 1.0
|
||||
|
||||
@@ -159,3 +159,9 @@ func _on_assign_button_pressed() -> void:
|
||||
|
||||
func _on_craft_button_pressed(recipe: Variant) -> void:
|
||||
craft_requested.emit(recipe)
|
||||
|
||||
## Called by PrestigeManager after prestige to reset assigned alchemy workers.
|
||||
func reset_workers() -> void:
|
||||
_alchemy_worker_count = 0
|
||||
_update_worker_display()
|
||||
alchemy_workers_changed.emit(0)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class_name AlchemyTower
|
||||
extends Node2D
|
||||
|
||||
const UNLOCK_GOAL_ID: StringName = &"gold_2M_food_200k_wood_200k"
|
||||
|
||||
## Emitted when production progress changes (0.0 to 1.0)
|
||||
signal production_progress_updated(progress: float)
|
||||
## Emitted when magic gold balance changes
|
||||
@@ -16,6 +18,8 @@ signal production_completed(amount: BigNumber)
|
||||
## Reference to game state
|
||||
@onready var game_state: LevelGameState = find_parent("LevelGameState")
|
||||
@onready var _alchemy_currencies_panel: AlchemyCurrenciesPanel = $AlchemyCurrenciesPanel
|
||||
@onready var _sprite: Sprite2D = $Sprite2D
|
||||
@onready var _area: Area2D = $Area2D
|
||||
|
||||
## Current production state
|
||||
var production_time_elapsed: float = 0.0
|
||||
@@ -36,17 +40,23 @@ func _ready() -> void:
|
||||
# Initialize production time
|
||||
current_production_time = data.base_production_time_seconds
|
||||
|
||||
# Register this tower as an available generator
|
||||
game_state.set_generator_available(get_generator_id(), true)
|
||||
# Hide tower until unlock goal is completed
|
||||
_sprite.visible = false
|
||||
_area.monitoring = false
|
||||
|
||||
# Connect to magic gold balance changes
|
||||
game_state.currency_changed.connect(_on_currency_changed)
|
||||
game_state.goal_completed.connect(_on_goal_completed)
|
||||
|
||||
# Connect panel signals if available
|
||||
if alchemy_panel:
|
||||
alchemy_panel.craft_requested.connect(_on_craft_requested)
|
||||
alchemy_panel.alchemy_workers_changed.connect(_on_alchemy_workers_changed)
|
||||
|
||||
# If goal was already completed in a previous session, unlock immediately
|
||||
if game_state.is_goal_completed(UNLOCK_GOAL_ID):
|
||||
_unlock_tower()
|
||||
|
||||
# Initial progress update
|
||||
production_progress_updated.emit(0.0)
|
||||
|
||||
@@ -200,3 +210,15 @@ func _on_area_2d_mouse_entered() -> void:
|
||||
|
||||
func _on_area_2d_mouse_exited() -> void:
|
||||
_alchemy_currencies_panel.visible = false
|
||||
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
if goal_id == UNLOCK_GOAL_ID:
|
||||
_unlock_tower()
|
||||
|
||||
|
||||
func _unlock_tower() -> void:
|
||||
_sprite.visible = true
|
||||
_area.monitoring = true
|
||||
game_state.set_generator_available(get_generator_id(), true)
|
||||
game_state.register_generator(get_generator_id(), true, true, 1)
|
||||
|
||||
@@ -25,11 +25,12 @@ position = Vector2(0.5, 11)
|
||||
shape = SubResource("RectangleShape2D_8pntr")
|
||||
|
||||
[node name="AlchemyCurrenciesPanel" parent="." unique_id=731368154 instance=ExtResource("2_8pntr")]
|
||||
z_index = 1
|
||||
visible = false
|
||||
offset_left = 65.0
|
||||
offset_top = -75.0
|
||||
offset_right = 65.0
|
||||
offset_bottom = -75.0
|
||||
offset_left = 44.0
|
||||
offset_top = -81.0
|
||||
offset_right = 444.0
|
||||
offset_bottom = 219.0
|
||||
currency = ExtResource("7_b8p5n")
|
||||
craft_recipes = ExtResource("5_recipes")
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_data")
|
||||
max_workers = 0
|
||||
|
||||
BIN
docs/gyms/tiny_sword/buildings/barracks/Barracks.png
Normal file
BIN
docs/gyms/tiny_sword/buildings/barracks/Barracks.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
40
docs/gyms/tiny_sword/buildings/barracks/Barracks.png.import
Normal file
40
docs/gyms/tiny_sword/buildings/barracks/Barracks.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cx11iodvbcfb1"
|
||||
path="res://.godot/imported/Barracks.png-6c10bd8efd919b8eb03e40d568e18cf1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://docs/gyms/tiny_sword/buildings/barracks/Barracks.png"
|
||||
dest_files=["res://.godot/imported/Barracks.png-6c10bd8efd919b8eb03e40d568e18cf1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
25
docs/gyms/tiny_sword/buildings/barracks/barracks.gd
Normal file
25
docs/gyms/tiny_sword/buildings/barracks/barracks.gd
Normal file
@@ -0,0 +1,25 @@
|
||||
extends Node2D
|
||||
|
||||
const UNLOCK_GOAL_ID: StringName = &"gold_50M_food_5M_wood_5M"
|
||||
|
||||
@onready var _sprite: Sprite2D = $Sprite2D
|
||||
@onready var _area: Area2D = $Area2D
|
||||
@onready var _game_state: LevelGameState = find_parent("LevelGameState")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_sprite.visible = false
|
||||
_area.monitoring = false
|
||||
_game_state.goal_completed.connect(_on_goal_completed)
|
||||
if _game_state.is_goal_completed(UNLOCK_GOAL_ID):
|
||||
_unlock()
|
||||
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
if goal_id == UNLOCK_GOAL_ID:
|
||||
_unlock()
|
||||
|
||||
|
||||
func _unlock() -> void:
|
||||
_sprite.visible = true
|
||||
_area.monitoring = true
|
||||
1
docs/gyms/tiny_sword/buildings/barracks/barracks.gd.uid
Normal file
1
docs/gyms/tiny_sword/buildings/barracks/barracks.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://xk7d3maod6uc
|
||||
41
docs/gyms/tiny_sword/buildings/barracks/barracks.tscn
Normal file
41
docs/gyms/tiny_sword/buildings/barracks/barracks.tscn
Normal file
@@ -0,0 +1,41 @@
|
||||
[gd_scene format=3 uid="uid://cyvijgwj6l8vg"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://jeoiinukrrsp" path="res://core/generator/currency_generator.tscn" id="1_gen"]
|
||||
[ext_resource type="Script" uid="uid://xk7d3maod6uc" path="res://docs/gyms/tiny_sword/buildings/barracks/barracks.gd" id="1_script"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/unit.tres" id="2_unit"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/buildings/barracks/barracks_generator.tres" id="3_data"]
|
||||
[ext_resource type="Texture2D" uid="uid://cx11iodvbcfb1" path="res://docs/gyms/tiny_sword/buildings/barracks/Barracks.png" id="4_ud3ae"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckos7f22pnmyh" path="res://core/generator/generator_container.tscn" id="5_panel"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_barracks"]
|
||||
size = Vector2(160, 175)
|
||||
|
||||
[node name="Barracks" type="Node2D" unique_id=1061432510]
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="CurrencyGenerator" parent="." unique_id=427948318 node_paths=PackedStringArray("info_generator_container") instance=ExtResource("1_gen")]
|
||||
currency = ExtResource("2_unit")
|
||||
data = ExtResource("3_data")
|
||||
press_buys_generator = false
|
||||
info_generator_container = NodePath("../GeneratorContainer")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=1503773416]
|
||||
texture = ExtResource("4_ud3ae")
|
||||
|
||||
[node name="Area2D" type="Area2D" parent="." unique_id=1808028142]
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D" unique_id=614030143]
|
||||
position = Vector2(1, 19.5)
|
||||
shape = SubResource("RectangleShape2D_barracks")
|
||||
|
||||
[node name="GeneratorContainer" parent="." unique_id=1905754561 node_paths=PackedStringArray("_generator") instance=ExtResource("5_panel")]
|
||||
z_index = 1
|
||||
visible = false
|
||||
offset_left = 70.0
|
||||
offset_top = -100.0
|
||||
offset_right = 334.0
|
||||
offset_bottom = 110.0
|
||||
_generator = NodePath("../CurrencyGenerator")
|
||||
|
||||
[connection signal="mouse_entered" from="Area2D" to="CurrencyGenerator" method="_on_area_2d_mouse_entered"]
|
||||
[connection signal="mouse_exited" from="Area2D" to="CurrencyGenerator" method="_on_area_2d_mouse_exited"]
|
||||
@@ -0,0 +1,18 @@
|
||||
[gd_resource type="Resource" script_class="CurrencyGeneratorData" format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://core/generator/currency_generator_data.gd" id="1_data"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/worker.tres" id="2_worker"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres" id="3_goal"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_data")
|
||||
id = &"barracks"
|
||||
name = "Barracks"
|
||||
starts_unlocked = false
|
||||
starts_available = false
|
||||
initial_owned = 1
|
||||
purchase_currency = ExtResource("2_worker")
|
||||
unlock_goal = ExtResource("3_goal")
|
||||
initial_cost = 1.0
|
||||
coefficient = 1.0
|
||||
initial_productivity = 1.0
|
||||
@@ -1,20 +1,27 @@
|
||||
extends Node2D
|
||||
|
||||
const PRESTIGE_GOAL_ID: StringName = &"gold_350k_wood_20k"
|
||||
|
||||
@onready var _prestige_panel: PrestigePanel = $PrestigePanel
|
||||
@onready var game_state: LevelGameState = find_parent("LevelGameState")
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
_prestige_panel.visible = false
|
||||
if game_state:
|
||||
game_state.goal_completed.connect(_on_goal_completed)
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
|
||||
func _on_area_2d_mouse_entered() -> void:
|
||||
## TODO: remove this comment to enable prestige panel
|
||||
#_prestige_panel.visible = true
|
||||
pass
|
||||
if game_state and game_state.is_goal_completed(PRESTIGE_GOAL_ID):
|
||||
_prestige_panel.visible = true
|
||||
|
||||
|
||||
func _on_area_2d_mouse_exited() -> void:
|
||||
_prestige_panel.visible = false
|
||||
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
if goal_id == PRESTIGE_GOAL_ID:
|
||||
# Prestige is now unlocked — panel will show on next hover.
|
||||
pass
|
||||
|
||||
@@ -23,16 +23,17 @@ position = Vector2(1, 22)
|
||||
shape = SubResource("RectangleShape2D_tgvch")
|
||||
|
||||
[node name="PrestigePanel" parent="." unique_id=245519778 instance=ExtResource("3_l7gct")]
|
||||
offset_left = 161.0
|
||||
offset_top = -232.0
|
||||
offset_right = 581.0
|
||||
offset_bottom = -3.0
|
||||
z_index = 1
|
||||
offset_left = 142.0
|
||||
offset_right = 562.0
|
||||
offset_bottom = 229.0
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="." unique_id=549346899]
|
||||
offset_left = 160.0
|
||||
offset_top = 5.0
|
||||
offset_right = 575.0
|
||||
offset_bottom = 201.0
|
||||
z_index = 1
|
||||
offset_left = 142.0
|
||||
offset_top = -201.0
|
||||
offset_right = 557.0
|
||||
offset_bottom = -5.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
|
||||
BIN
docs/gyms/tiny_sword/buildings/devil_idol/Castle.png
Normal file
BIN
docs/gyms/tiny_sword/buildings/devil_idol/Castle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
40
docs/gyms/tiny_sword/buildings/devil_idol/Castle.png.import
Normal file
40
docs/gyms/tiny_sword/buildings/devil_idol/Castle.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://rpedoq4102to"
|
||||
path="res://.godot/imported/Castle.png-abe84e074a7668619903091d591ffce7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://docs/gyms/tiny_sword/buildings/devil_idol/Castle.png"
|
||||
dest_files=["res://.godot/imported/Castle.png-abe84e074a7668619903091d591ffce7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
142
docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd
Normal file
142
docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd
Normal file
@@ -0,0 +1,142 @@
|
||||
class_name DevilIdol
|
||||
extends Node2D
|
||||
|
||||
const UNLOCK_GOAL_ID: StringName = &"gold_50M_food_5M_wood_5M"
|
||||
const SAVE_KEY: String = "devil_idol_state"
|
||||
const HP_KEY: String = "current_hp"
|
||||
|
||||
## Emitted when the idol takes damage.
|
||||
signal idol_damaged(current_hp: BigNumber, max_hp: BigNumber)
|
||||
## Emitted when the idol is destroyed (one-time win condition).
|
||||
signal idol_destroyed()
|
||||
|
||||
## Maximum HP mantissa.
|
||||
@export var max_hp_mantissa: float = 1.0
|
||||
## Maximum HP exponent (default 1e12 = 1 trillion).
|
||||
@export var max_hp_exponent: int = 12
|
||||
|
||||
@onready var game_state: LevelGameState = find_parent("LevelGameState")
|
||||
@onready var _sprite: Sprite2D = $Sprite2D
|
||||
@onready var _area: Area2D = $Area2D
|
||||
@onready var _panel: PanelContainer = $IdolPanel
|
||||
@onready var _hp_value: Label = $IdolPanel/MarginContainer/VBoxContainer/HPValue
|
||||
@onready var _damage_button: Button = $IdolPanel/MarginContainer/VBoxContainer/DamageButton
|
||||
@onready var _victory_label: Label = $IdolPanel/MarginContainer/VBoxContainer/VictoryLabel
|
||||
|
||||
var _max_hp: BigNumber
|
||||
var _current_hp: BigNumber
|
||||
var _is_targetable: bool = false
|
||||
var _is_destroyed: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
assert(game_state != null, "DevilIdol: LevelGameState parent not found")
|
||||
|
||||
_max_hp = BigNumber.new(max_hp_mantissa, max_hp_exponent)
|
||||
_current_hp = BigNumber.new(max_hp_mantissa, max_hp_exponent)
|
||||
|
||||
# Load persisted HP from save data
|
||||
_load_state()
|
||||
|
||||
# Panel hidden by default; shown on hover if unlocked
|
||||
_panel.visible = false
|
||||
_victory_label.visible = false
|
||||
_damage_button.pressed.connect(_on_damage_button_pressed)
|
||||
|
||||
# Connect to goal completion for unlocking
|
||||
game_state.goal_completed.connect(_on_goal_completed)
|
||||
# Refresh button state when unit balance changes
|
||||
game_state.currency_changed.connect(_on_currency_changed)
|
||||
|
||||
# If goal was already completed in a previous session, unlock immediately
|
||||
if game_state.is_goal_completed(UNLOCK_GOAL_ID):
|
||||
_unlock_idol()
|
||||
|
||||
_update_ui()
|
||||
|
||||
|
||||
func _on_area_2d_mouse_entered() -> void:
|
||||
if _is_targetable and not _is_destroyed:
|
||||
_panel.visible = true
|
||||
|
||||
|
||||
func _on_area_2d_mouse_exited() -> void:
|
||||
_panel.visible = false
|
||||
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
if goal_id == UNLOCK_GOAL_ID:
|
||||
_unlock_idol()
|
||||
|
||||
|
||||
func _on_currency_changed(currency_id: StringName, _new_amount: BigNumber) -> void:
|
||||
if currency_id == &"unit":
|
||||
_update_ui()
|
||||
|
||||
func _unlock_idol() -> void:
|
||||
_is_targetable = true
|
||||
|
||||
|
||||
func _on_damage_button_pressed() -> void:
|
||||
if _is_destroyed:
|
||||
return
|
||||
|
||||
var unit_balance: BigNumber = game_state.get_currency_amount_by_id(&"unit")
|
||||
if unit_balance.mantissa <= 0.0:
|
||||
return
|
||||
|
||||
# Spend all available units as damage
|
||||
var damage: BigNumber = BigNumber.new(unit_balance.mantissa, unit_balance.exponent)
|
||||
if game_state.spend_currency_by_id(&"unit", damage):
|
||||
_current_hp = _current_hp.subtract(damage)
|
||||
if _current_hp.mantissa <= 0.0:
|
||||
_current_hp = BigNumber.new(0.0, 0)
|
||||
_is_destroyed = true
|
||||
idol_destroyed.emit()
|
||||
_victory_label.visible = true
|
||||
_panel.visible = true
|
||||
|
||||
idol_damaged.emit(_current_hp, _max_hp)
|
||||
_save_state()
|
||||
_update_ui()
|
||||
|
||||
|
||||
func _update_ui() -> void:
|
||||
if _is_destroyed:
|
||||
_hp_value.text = "0 / %s — DESTROYED" % _max_hp.to_string_suffix(2)
|
||||
_damage_button.disabled = true
|
||||
_damage_button.text = "VICTORY!"
|
||||
return
|
||||
|
||||
var progress_pct: float = 0.0
|
||||
var max_float: float = _max_hp.to_float()
|
||||
if max_float > 0.0:
|
||||
var current_float: float = _current_hp.to_float()
|
||||
progress_pct = clampf((1.0 - current_float / max_float) * 100.0, 0.0, 100.0)
|
||||
|
||||
_hp_value.text = "%s / %s (%.1f%%)" % [_current_hp.to_string_suffix(2), _max_hp.to_string_suffix(2), progress_pct]
|
||||
|
||||
var unit_balance: BigNumber = game_state.get_currency_amount_by_id(&"unit")
|
||||
_damage_button.text = "Send Army (%s units)" % unit_balance.to_string_suffix(2)
|
||||
_damage_button.disabled = unit_balance.mantissa <= 0.0
|
||||
|
||||
|
||||
func _save_state() -> void:
|
||||
if game_state == null:
|
||||
return
|
||||
var payload: Dictionary = {HP_KEY: _current_hp.serialize()}
|
||||
game_state.set_external_save_data(SAVE_KEY, payload)
|
||||
|
||||
|
||||
func _load_state() -> void:
|
||||
if game_state == null:
|
||||
return
|
||||
var saved: Dictionary = game_state.get_external_save_data(SAVE_KEY)
|
||||
if saved.has(HP_KEY):
|
||||
var raw_hp: Variant = saved.get(HP_KEY)
|
||||
if raw_hp is Dictionary:
|
||||
_current_hp = BigNumber.deserialize(raw_hp)
|
||||
if _current_hp.mantissa <= 0.0:
|
||||
_current_hp = BigNumber.new(0.0, 0)
|
||||
_is_destroyed = true
|
||||
_victory_label.visible = true
|
||||
@@ -0,0 +1 @@
|
||||
uid://blfof77kjp7x0
|
||||
58
docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.tscn
Normal file
58
docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.tscn
Normal file
@@ -0,0 +1,58 @@
|
||||
[gd_scene format=3 uid="uid://d1cvlnd5vwjol"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://blfof77kjp7x0" path="res://docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd" id="1_script"]
|
||||
[ext_resource type="Texture2D" uid="uid://rpedoq4102to" path="res://docs/gyms/tiny_sword/buildings/devil_idol/Castle.png" id="2_g7pcm"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_idol"]
|
||||
size = Vector2(312, 198)
|
||||
|
||||
[node name="DevilIdol" type="Node2D" unique_id=797117647]
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=661703335]
|
||||
texture = ExtResource("2_g7pcm")
|
||||
|
||||
[node name="Area2D" type="Area2D" parent="." unique_id=1812195052]
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D" unique_id=1492517397]
|
||||
position = Vector2(1, 22)
|
||||
shape = SubResource("RectangleShape2D_idol")
|
||||
|
||||
[node name="IdolPanel" type="PanelContainer" parent="." unique_id=906038530]
|
||||
z_index = 1
|
||||
offset_left = -200.0
|
||||
offset_top = -120.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = -10.0
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="IdolPanel" unique_id=1952977282]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 10
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 10
|
||||
theme_override_constants/margin_bottom = 10
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="IdolPanel/MarginContainer" unique_id=1214756176]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=1268978638]
|
||||
layout_mode = 2
|
||||
text = "Devil Idol"
|
||||
|
||||
[node name="HPValue" type="Label" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=529436307]
|
||||
layout_mode = 2
|
||||
text = "??? / ???"
|
||||
|
||||
[node name="DamageButton" type="Button" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=1565608485]
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Send Army"
|
||||
|
||||
[node name="VictoryLabel" type="Label" parent="IdolPanel/MarginContainer/VBoxContainer" unique_id=169309796]
|
||||
layout_mode = 2
|
||||
text = "THE DEVIL IDOL HAS BEEN DESTROYED!"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[connection signal="mouse_entered" from="Area2D" to="." method="_on_area_2d_mouse_entered"]
|
||||
[connection signal="mouse_exited" from="Area2D" to="." method="_on_area_2d_mouse_exited"]
|
||||
25
docs/gyms/tiny_sword/buildings/farm/farm.gd
Normal file
25
docs/gyms/tiny_sword/buildings/farm/farm.gd
Normal file
@@ -0,0 +1,25 @@
|
||||
extends Node2D
|
||||
|
||||
const UNLOCK_GOAL_ID: StringName = &"gold_total_1300"
|
||||
|
||||
@onready var _sprite: Sprite2D = $Sprite2D
|
||||
@onready var _area: Area2D = $Area2D
|
||||
@onready var _game_state: LevelGameState = find_parent("LevelGameState")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_sprite.visible = false
|
||||
_area.monitoring = false
|
||||
_game_state.goal_completed.connect(_on_goal_completed)
|
||||
if _game_state.is_goal_completed(UNLOCK_GOAL_ID):
|
||||
_unlock()
|
||||
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
if goal_id == UNLOCK_GOAL_ID:
|
||||
_unlock()
|
||||
|
||||
|
||||
func _unlock() -> void:
|
||||
_sprite.visible = true
|
||||
_area.monitoring = true
|
||||
1
docs/gyms/tiny_sword/buildings/farm/farm.gd.uid
Normal file
1
docs/gyms/tiny_sword/buildings/farm/farm.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dnsblnjy82goo
|
||||
@@ -1,6 +1,7 @@
|
||||
[gd_scene format=3 uid="uid://djedqovgngrx5"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://jeoiinukrrsp" path="res://core/generator/currency_generator.tscn" id="1_rvsna"]
|
||||
[ext_resource type="Script" path="res://docs/gyms/tiny_sword/buildings/farm/farm.gd" id="1_script"]
|
||||
[ext_resource type="Resource" uid="uid://bxg2au0ijp242" path="res://docs/gyms/tiny_sword/currencies/food.tres" id="2_djlc5"]
|
||||
[ext_resource type="Resource" uid="uid://d08h51y0pnsnf" path="res://docs/gyms/tiny_sword/buildings/farm/farm_generator.tres" id="3_82rmq"]
|
||||
[ext_resource type="Texture2D" uid="uid://dfdh8r03sj7qv" path="res://docs/gyms/tiny_sword/buildings/farm/House1.png" id="4_djlc5"]
|
||||
@@ -10,6 +11,7 @@
|
||||
size = Vector2(106, 157)
|
||||
|
||||
[node name="Farm" type="Node2D" unique_id=283335851]
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="CurrencyGenerator" parent="." unique_id=967969064 node_paths=PackedStringArray("info_generator_container") instance=ExtResource("1_rvsna")]
|
||||
currency = ExtResource("2_djlc5")
|
||||
@@ -27,6 +29,7 @@ position = Vector2(0, -2.5)
|
||||
shape = SubResource("RectangleShape2D_82rmq")
|
||||
|
||||
[node name="GeneratorContainer" parent="." unique_id=1451609580 node_paths=PackedStringArray("_generator") instance=ExtResource("5_82rmq")]
|
||||
z_index = 1
|
||||
visible = false
|
||||
offset_left = 138.0
|
||||
offset_top = -100.0
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
extends Node2D
|
||||
|
||||
@onready var _generator_container: GeneratorPanel = $GeneratorContainer
|
||||
const UNLOCK_GOAL_ID: StringName = &"gold_total_13k"
|
||||
|
||||
@onready var _generator_container: GeneratorPanel = $GeneratorContainer
|
||||
@onready var _sprite: Sprite2D = $Sprite2D
|
||||
@onready var _area: Area2D = $Area2D
|
||||
@onready var _game_state: LevelGameState = find_parent("LevelGameState")
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
_generator_container.visible = false
|
||||
_sprite.visible = false
|
||||
_area.monitoring = false
|
||||
_game_state.goal_completed.connect(_on_goal_completed)
|
||||
if _game_state.is_goal_completed(UNLOCK_GOAL_ID):
|
||||
_unlock()
|
||||
|
||||
|
||||
func _on_area_2d_mouse_entered() -> void:
|
||||
_generator_container.visible = true
|
||||
|
||||
|
||||
func _on_area_2d_mouse_exited() -> void:
|
||||
_generator_container.visible = false
|
||||
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
if goal_id == UNLOCK_GOAL_ID:
|
||||
_unlock()
|
||||
|
||||
|
||||
func _unlock() -> void:
|
||||
_sprite.visible = true
|
||||
_area.monitoring = true
|
||||
|
||||
@@ -29,6 +29,7 @@ position = Vector2(0.5, -2.5)
|
||||
shape = SubResource("RectangleShape2D_v4aq3")
|
||||
|
||||
[node name="GeneratorContainer" parent="." unique_id=1451609580 node_paths=PackedStringArray("_generator") instance=ExtResource("5_rh3m6")]
|
||||
z_index = 1
|
||||
visible = false
|
||||
offset_left = 128.0
|
||||
offset_top = -100.0
|
||||
|
||||
@@ -26,6 +26,7 @@ position = Vector2(-1, -11)
|
||||
shape = SubResource("RectangleShape2D_sbi6i")
|
||||
|
||||
[node name="GeneratorContainer" parent="." unique_id=1451609580 node_paths=PackedStringArray("_generator") instance=ExtResource("2_lnbxc")]
|
||||
z_index = 1
|
||||
visible = false
|
||||
offset_left = 127.0
|
||||
offset_top = -103.0
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
extends Node2D
|
||||
|
||||
@onready var _research_panel: ResearchPanel = $ResearchPanel
|
||||
const RESEARCH_GOAL_ID: StringName = &"gold_350k_wood_20k"
|
||||
|
||||
@onready var _research_panel: ResearchPanel = $ResearchPanel
|
||||
@onready var _sprite: Sprite2D = $Sprite2D
|
||||
@onready var _area: Area2D = $Area2D
|
||||
@onready var game_state: LevelGameState = find_parent("LevelGameState")
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
_research_panel.visible = false
|
||||
_sprite.visible = false
|
||||
_area.monitoring = false
|
||||
if game_state:
|
||||
game_state.goal_completed.connect(_on_goal_completed)
|
||||
if game_state.is_goal_completed(RESEARCH_GOAL_ID):
|
||||
_unlock()
|
||||
|
||||
|
||||
func _on_area_2d_mouse_entered() -> void:
|
||||
if game_state and game_state.is_goal_completed(RESEARCH_GOAL_ID):
|
||||
_research_panel.visible = true
|
||||
|
||||
|
||||
func _on_area_2d_mouse_exited() -> void:
|
||||
_research_panel.visible = false
|
||||
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
if goal_id == RESEARCH_GOAL_ID:
|
||||
_unlock()
|
||||
|
||||
|
||||
func _unlock() -> void:
|
||||
_sprite.visible = true
|
||||
_area.monitoring = true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://cj0sxoceqqyrm" path="res://docs/gyms/tiny_sword/buildings/monastery/monastery.gd" id="1_e17qd"]
|
||||
[ext_resource type="Texture2D" uid="uid://dlmjd2berkb6v" path="res://docs/gyms/tiny_sword/buildings/monastery/Monastery.png" id="1_yyw6r"]
|
||||
[ext_resource type="PackedScene" uid="uid://dd8roif0mqirl" path="res://core/research/research_panel.tscn" id="3_c6gi6"]
|
||||
[ext_resource type="PackedScene" uid="uid://6d101h70mcx" path="res://core/research/research_panel.tscn" id="3_c6gi6"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_yyw6r"]
|
||||
size = Vector2(160, 254)
|
||||
@@ -20,11 +20,12 @@ position = Vector2(1, 16)
|
||||
shape = SubResource("RectangleShape2D_yyw6r")
|
||||
|
||||
[node name="ResearchPanel" parent="." unique_id=1105274967 instance=ExtResource("3_c6gi6")]
|
||||
z_index = 1
|
||||
visible = false
|
||||
offset_left = 78.0
|
||||
offset_top = -98.0
|
||||
offset_right = 478.0
|
||||
offset_bottom = 128.0
|
||||
offset_left = 59.0
|
||||
offset_top = -87.0
|
||||
offset_right = 459.0
|
||||
offset_bottom = 139.0
|
||||
|
||||
[connection signal="mouse_entered" from="Area2D" to="." method="_on_area_2d_mouse_entered"]
|
||||
[connection signal="mouse_exited" from="Area2D" to="." method="_on_area_2d_mouse_exited"]
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
[ext_resource type="Resource" uid="uid://brctmnpmhjas6" path="res://docs/gyms/tiny_sword/currencies/mana_stone.tres" id="8_p3urk"]
|
||||
[ext_resource type="Resource" uid="uid://t6du7gm2ywbi" path="res://docs/gyms/tiny_sword/currencies/cognite.tres" id="9_ejnoj"]
|
||||
[ext_resource type="Resource" uid="uid://co4fiqgluwit0" path="res://docs/gyms/tiny_sword/currencies/philosoper_stone.tres" id="10_ejnoj"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/unit.tres" id="11_unit"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_hmju3")
|
||||
currencies = Array[ExtResource("1_501l6")]([ExtResource("2_ucsji"), ExtResource("3_7hx6u"), ExtResource("4_c77gh"), ExtResource("5_gyp05"), ExtResource("6_ascension"), ExtResource("7_vu05v"), ExtResource("8_p3urk"), ExtResource("9_ejnoj"), ExtResource("10_ejnoj")])
|
||||
currencies = Array[ExtResource("1_501l6")]([ExtResource("2_ucsji"), ExtResource("3_7hx6u"), ExtResource("4_c77gh"), ExtResource("5_gyp05"), ExtResource("6_ascension"), ExtResource("7_vu05v"), ExtResource("8_p3urk"), ExtResource("9_ejnoj"), ExtResource("10_ejnoj"), ExtResource("11_unit")])
|
||||
metadata/_custom_type_script = "uid://621tus0uvbrd"
|
||||
|
||||
10
docs/gyms/tiny_sword/currencies/unit.tres
Normal file
10
docs/gyms/tiny_sword/currencies/unit.tres
Normal file
@@ -0,0 +1,10 @@
|
||||
[gd_resource type="Resource" script_class="Currency" format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://core/currency/currency.gd" id="1_script"]
|
||||
[ext_resource type="Texture2D" path="res://sandbox/tiny_swords/UI Elements/UI Elements/Icons/Icon_05.png" id="2_icon"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
id = &"unit"
|
||||
display_name = "Unit"
|
||||
icon = ExtResource("2_icon")
|
||||
@@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="GoalData" format=3 uid="uid://bex2hsenbr54e"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="1_req"]
|
||||
[ext_resource type="Resource" uid="uid://w4u1hddplb4e" path="res://docs/gyms/tiny_sword/currencies/gold.tres" id="2_gold"]
|
||||
[ext_resource type="Resource" uid="uid://bxg2au0ijp242" path="res://docs/gyms/tiny_sword/currencies/food.tres" id="3_food"]
|
||||
[ext_resource type="Resource" uid="uid://bgsk8h4w80h45" path="res://docs/gyms/tiny_sword/currencies/wood.tres" id="4_wood"]
|
||||
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="5_goal"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_gold"]
|
||||
script = ExtResource("1_req")
|
||||
currency = ExtResource("2_gold")
|
||||
amount_mantissa = 2.0
|
||||
amount_exponent = 6
|
||||
|
||||
[sub_resource type="Resource" id="Resource_food"]
|
||||
script = ExtResource("1_req")
|
||||
currency = ExtResource("3_food")
|
||||
amount_mantissa = 2.0
|
||||
amount_exponent = 5
|
||||
|
||||
[sub_resource type="Resource" id="Resource_wood"]
|
||||
script = ExtResource("1_req")
|
||||
currency = ExtResource("4_wood")
|
||||
amount_mantissa = 2.0
|
||||
amount_exponent = 5
|
||||
|
||||
[resource]
|
||||
script = ExtResource("5_goal")
|
||||
id = &"gold_2M_food_200k_wood_200k"
|
||||
requirements = Array[ExtResource("1_req")]([SubResource("Resource_gold"), SubResource("Resource_food"), SubResource("Resource_wood")])
|
||||
@@ -0,0 +1,30 @@
|
||||
[gd_resource type="Resource" script_class="GoalData" format=3 uid="uid://bn4gm4sss85d0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="1_req"]
|
||||
[ext_resource type="Resource" uid="uid://w4u1hddplb4e" path="res://docs/gyms/tiny_sword/currencies/gold.tres" id="2_gold"]
|
||||
[ext_resource type="Resource" uid="uid://bxg2au0ijp242" path="res://docs/gyms/tiny_sword/currencies/food.tres" id="3_food"]
|
||||
[ext_resource type="Resource" uid="uid://bgsk8h4w80h45" path="res://docs/gyms/tiny_sword/currencies/wood.tres" id="4_wood"]
|
||||
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="5_goal"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_gold"]
|
||||
script = ExtResource("1_req")
|
||||
currency = ExtResource("2_gold")
|
||||
amount_mantissa = 5.0
|
||||
amount_exponent = 7
|
||||
|
||||
[sub_resource type="Resource" id="Resource_food"]
|
||||
script = ExtResource("1_req")
|
||||
currency = ExtResource("3_food")
|
||||
amount_mantissa = 5.0
|
||||
amount_exponent = 6
|
||||
|
||||
[sub_resource type="Resource" id="Resource_wood"]
|
||||
script = ExtResource("1_req")
|
||||
currency = ExtResource("4_wood")
|
||||
amount_mantissa = 5.0
|
||||
amount_exponent = 6
|
||||
|
||||
[resource]
|
||||
script = ExtResource("5_goal")
|
||||
id = &"gold_50M_food_5M_wood_5M"
|
||||
requirements = Array[ExtResource("1_req")]([SubResource("Resource_gold"), SubResource("Resource_food"), SubResource("Resource_wood")])
|
||||
@@ -6,7 +6,9 @@
|
||||
[ext_resource type="Resource" uid="uid://cts0407h130d6" path="res://docs/gyms/tiny_sword/goals/gold_total_1300_goal.tres" id="3_wb626"]
|
||||
[ext_resource type="Resource" uid="uid://bo463s6jt0ep7" path="res://docs/gyms/tiny_sword/goals/gold_total_13k_goal.tres" id="4_3oaoj"]
|
||||
[ext_resource type="Resource" uid="uid://b6gb6vk0kgxnw" path="res://docs/gyms/tiny_sword/goals/gold_350k_wood_20k_goal.tres" id="5_or88d"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres" id="6_alchemy"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres" id="7_barracks"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_84pbn")
|
||||
goals = Array[ExtResource("1_6p6ue")]([ExtResource("2_xvtf2"), ExtResource("3_wb626"), ExtResource("4_3oaoj"), ExtResource("5_or88d")])
|
||||
goals = Array[ExtResource("1_6p6ue")]([ExtResource("2_xvtf2"), ExtResource("3_wb626"), ExtResource("4_3oaoj"), ExtResource("5_or88d"), ExtResource("6_alchemy"), ExtResource("7_barracks")])
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="PrestigeBuffNode" format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://core/prestige/prestige_buff_node.gd" id="1_script"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
id = &"barracks_boost"
|
||||
display_name = "Martial Prowess"
|
||||
description = "Permanently increases barracks unit production."
|
||||
parent_ids = Array[StringName]([&"wood_boost"])
|
||||
effect_value = 2.0
|
||||
target_id = &"barracks"
|
||||
cost_mantissa = 5.0
|
||||
@@ -1,10 +1,12 @@
|
||||
[gd_resource type="Resource" script_class="PrestigeBuffCatalogue" format=3]
|
||||
[gd_resource type="Resource" script_class="PrestigeBuffCatalogue" format=3 uid="uid://dpt3j1c2ndgkj"]
|
||||
|
||||
[ext_resource type="Script" path="res://core/prestige/prestige_buff_node.gd" id="1_node"]
|
||||
[ext_resource type="Script" path="res://core/prestige/prestige_buff_catalogue.gd" id="2_cat"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_gold_boost.tres" id="3_gold"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_farm_boost.tres" id="4_farm"]
|
||||
[ext_resource type="Script" uid="uid://mjyig8xlgki0" path="res://core/prestige/prestige_buff_node.gd" id="1_node"]
|
||||
[ext_resource type="Script" uid="uid://bjcvnyh2bc6l5" path="res://core/prestige/prestige_buff_catalogue.gd" id="2_cat"]
|
||||
[ext_resource type="Resource" uid="uid://vl3ot4t876pc" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_gold_boost.tres" id="3_gold"]
|
||||
[ext_resource type="Resource" uid="uid://cvogxo0msis2w" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_farm_boost.tres" id="4_farm"]
|
||||
[ext_resource type="Resource" uid="uid://bif136o485oqh" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_wood_boost.tres" id="5_wood"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_barracks_boost.tres" id="6_barracks"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("2_cat")
|
||||
nodes = Array[ExtResource("1_node")]([ExtResource("3_gold"), ExtResource("4_farm")])
|
||||
nodes = Array[ExtResource("1_node")]([ExtResource("3_gold"), ExtResource("4_farm"), ExtResource("5_wood"), ExtResource("6_barracks")])
|
||||
|
||||
@@ -10,4 +10,5 @@ description = "Permanently increases farm food production."
|
||||
parent_ids = Array[StringName]([&"gold_boost"])
|
||||
effect_value = 2.0
|
||||
target_id = &"farm"
|
||||
cost_mantissa = 2.0
|
||||
x_position = 1.0
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
[ext_resource type="PackedScene" uid="uid://bqk8rwia7lpbg" path="res://core/prestige/prestige_buff_graph_tile.tscn" id="3_tile"]
|
||||
[ext_resource type="Resource" uid="uid://vl3ot4t876pc" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_gold_boost.tres" id="4_gold"]
|
||||
[ext_resource type="Resource" uid="uid://cvogxo0msis2w" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_farm_boost.tres" id="5_farm"]
|
||||
[ext_resource type="Resource" uid="uid://bif136o485oqh" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_wood_boost.tres" id="6_pdyju"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_barracks_boost.tres" id="7_x62av"]
|
||||
|
||||
[node name="PrestigeBuffGraphPanel" type="PanelContainer" unique_id=947835716]
|
||||
custom_minimum_size = Vector2(600, 400)
|
||||
offset_right = 857.0
|
||||
offset_bottom = 632.0
|
||||
script = ExtResource("1_panel")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="." unique_id=829451203]
|
||||
@@ -56,14 +60,31 @@ script = ExtResource("2_overlay")
|
||||
|
||||
[node name="GoldBoostTile" parent="MarginContainer/VBoxContainer/TilesArea" unique_id=756192834 instance=ExtResource("3_tile")]
|
||||
layout_mode = 0
|
||||
offset_top = 27.0
|
||||
offset_right = 220.0
|
||||
offset_bottom = 100.0
|
||||
offset_bottom = 127.0
|
||||
prestige_buff = ExtResource("4_gold")
|
||||
|
||||
[node name="FarmBoostTile" parent="MarginContainer/VBoxContainer/TilesArea" unique_id=619283475 instance=ExtResource("3_tile")]
|
||||
layout_mode = 0
|
||||
offset_left = 3.0
|
||||
offset_top = 121.0
|
||||
offset_top = 198.0
|
||||
offset_right = 223.0
|
||||
offset_bottom = 221.0
|
||||
offset_bottom = 298.0
|
||||
prestige_buff = ExtResource("5_farm")
|
||||
|
||||
[node name="FarmBoostTile2" parent="MarginContainer/VBoxContainer/TilesArea" unique_id=582396224 instance=ExtResource("3_tile")]
|
||||
layout_mode = 0
|
||||
offset_left = 317.0
|
||||
offset_top = 198.0
|
||||
offset_right = 537.0
|
||||
offset_bottom = 298.0
|
||||
prestige_buff = ExtResource("6_pdyju")
|
||||
|
||||
[node name="FarmBoostTile3" parent="MarginContainer/VBoxContainer/TilesArea" unique_id=818264061 instance=ExtResource("3_tile")]
|
||||
layout_mode = 0
|
||||
offset_left = 317.0
|
||||
offset_top = 382.0
|
||||
offset_right = 537.0
|
||||
offset_bottom = 482.0
|
||||
prestige_buff = ExtResource("7_x62av")
|
||||
|
||||
@@ -10,3 +10,4 @@ description = "Permanently increases forestry production."
|
||||
parent_ids = Array[StringName]([&"farm_boost"])
|
||||
effect_value = 2.0
|
||||
target_id = &"forestry"
|
||||
cost_mantissa = 3.0
|
||||
|
||||
@@ -6,4 +6,6 @@
|
||||
script = ExtResource("1_3tg3a")
|
||||
id = &"ascension"
|
||||
prestige_currency_id = &"ascension"
|
||||
basis = 3
|
||||
source_currency_id = &"gold"
|
||||
basis = 1
|
||||
threshold_mantissa = 5.0
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[ext_resource type="Resource" uid="uid://cgt1mjir1v4br" path="res://docs/gyms/tiny_sword/goals/ts_goal_catalogue.tres" id="4_x77df"]
|
||||
[ext_resource type="Resource" uid="uid://umi37hotcq8m" path="res://docs/gyms/tiny_sword/research/ts_research_catalogue.tres" id="5_v0pty"]
|
||||
[ext_resource type="Script" uid="uid://srkiu4qe8s2m" path="res://core/prestige/prestige_manager.gd" id="5_x77df"]
|
||||
[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_catalogue.tres" id="6_asc"]
|
||||
[ext_resource type="Resource" uid="uid://dpt3j1c2ndgkj" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_catalogue.tres" id="6_asc"]
|
||||
[ext_resource type="Resource" uid="uid://dwmfvmusfskk6" path="res://docs/gyms/tiny_sword/prestige/primary_prestige.tres" id="6_xnhlc"]
|
||||
[ext_resource type="PackedScene" path="res://docs/gyms/tiny_sword/currency_panel.tscn" id="9_cpnl"]
|
||||
[ext_resource type="PackedScene" uid="uid://djedqovgngrx5" path="res://docs/gyms/tiny_sword/buildings/farm/farm.tscn" id="10_1lv5i"]
|
||||
@@ -17,6 +17,8 @@
|
||||
[ext_resource type="PackedScene" uid="uid://bf8lqbexvnx6e" path="res://docs/gyms/tiny_sword/buildings/monastery/monastery.tscn" id="13_no27p"]
|
||||
[ext_resource type="PackedScene" uid="uid://rejxvjwybkll" path="res://sandbox/tiny_swords/Terrain/Resources/Wood/Trees/tree_1.tscn" id="14_0cs5o"]
|
||||
[ext_resource type="PackedScene" uid="uid://bp5ng4vu4ot4a" path="res://docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.tscn" id="14_hum8s"]
|
||||
[ext_resource type="PackedScene" uid="uid://cyvijgwj6l8vg" path="res://docs/gyms/tiny_sword/buildings/barracks/barracks.tscn" id="15_barracks"]
|
||||
[ext_resource type="PackedScene" uid="uid://d1cvlnd5vwjol" path="res://docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.tscn" id="16_idol"]
|
||||
[ext_resource type="Script" uid="uid://b6systw05frh0" path="res://core/edge_scroll_camera.gd" id="17_x77df"]
|
||||
[ext_resource type="PackedScene" uid="uid://dprbgokqitaav" path="res://docs/gyms/tiny_sword/prestige/prestige_buff_graph_panel.tscn" id="18_pnl"]
|
||||
|
||||
@@ -114,6 +116,12 @@ position = Vector2(105, 920)
|
||||
[node name="AlchemyTower" parent="LevelGameState/World" unique_id=51852160 instance=ExtResource("14_hum8s")]
|
||||
position = Vector2(1239, 775)
|
||||
|
||||
[node name="Barracks" parent="LevelGameState/World" unique_id=765432109 instance=ExtResource("15_barracks")]
|
||||
position = Vector2(1597, 900)
|
||||
|
||||
[node name="DevilIdol" parent="LevelGameState/World" unique_id=876543210 instance=ExtResource("16_idol")]
|
||||
position = Vector2(873, 1289)
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="." unique_id=1494908331]
|
||||
position = Vector2(5, 0)
|
||||
offset = Vector2(960, 540)
|
||||
|
||||
@@ -6,7 +6,7 @@ var _game_root: Node = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
await run()
|
||||
pass # run() is called by the test runner; avoid double-execution.
|
||||
|
||||
|
||||
func run() -> void:
|
||||
@@ -118,10 +118,17 @@ func _test_purchase_flow() -> void:
|
||||
|
||||
var scene: PackedScene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
||||
_game_root = scene.instantiate()
|
||||
|
||||
# Use a unique save path to avoid contamination from previous test runs.
|
||||
var save_path: String = "user://test_ascension_%d.json" % randi()
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(save_path))
|
||||
var gs: LevelGameState = _game_root.get_node("LevelGameState")
|
||||
gs.save_file_path = save_path
|
||||
|
||||
add_child(_game_root)
|
||||
await _wait()
|
||||
|
||||
var gs: LevelGameState = _game_root.find_child("LevelGameState")
|
||||
gs = _game_root.find_child("LevelGameState")
|
||||
if gs == null:
|
||||
print("[ERROR] LevelGameState not found")
|
||||
return
|
||||
@@ -142,9 +149,9 @@ func _test_purchase_flow() -> void:
|
||||
TestUtils.assert_false(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost starts locked")
|
||||
TestUtils.assert_false(gs.is_prestige_buff_unlocked(&"farm_boost"), "farm_boost starts locked")
|
||||
|
||||
# Check can_purchase (both are root nodes with no parents, should be purchaseable)
|
||||
# gold_boost is a root node (no parents), farm_boost requires gold_boost as parent.
|
||||
TestUtils.assert_true(gs.can_purchase_prestige_buff(&"gold_boost"), "gold_boost is purchasable")
|
||||
TestUtils.assert_true(gs.can_purchase_prestige_buff(&"farm_boost"), "farm_boost is purchasable")
|
||||
TestUtils.assert_false(gs.can_purchase_prestige_buff(&"farm_boost"), "farm_boost is NOT purchasable (parent gold_boost not yet unlocked)")
|
||||
|
||||
# Check cost
|
||||
var cost: BigNumber = gs.get_prestige_buff_cost(&"gold_boost")
|
||||
|
||||
@@ -6,7 +6,6 @@ var _game_root: Node = null
|
||||
func _ready() -> void:
|
||||
if not Engine.is_editor_hint():
|
||||
test_goals_prestige_reset()
|
||||
get_tree().quit()
|
||||
|
||||
func test_goals_prestige_reset() -> void:
|
||||
print("\n=== TEST: Goals Prestige Reset ===\n")
|
||||
@@ -15,6 +14,13 @@ func test_goals_prestige_reset() -> void:
|
||||
# Load the tiny_sword scene directly
|
||||
var scene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
||||
_game_root = scene.instantiate()
|
||||
|
||||
# Use a unique save path to avoid contaminating the real game's save.
|
||||
var save_path: String = "user://test_goals_prestige_%d.json" % randi()
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(save_path))
|
||||
var gs: LevelGameState = _game_root.get_node("LevelGameState")
|
||||
gs.save_file_path = save_path
|
||||
|
||||
add_child(_game_root)
|
||||
|
||||
print("[TARGET] children_after_load: %d" % get_child_count())
|
||||
|
||||
@@ -5,8 +5,7 @@ var failed: int = 0
|
||||
var _game_root: Node = null
|
||||
|
||||
func _ready():
|
||||
await run()
|
||||
# Don't quit here - let test_runner handle it
|
||||
pass # run() is called by the test runner; avoid double-execution.
|
||||
|
||||
func run():
|
||||
print("\n=== TEST: Gold Mine Click ===\n")
|
||||
@@ -15,34 +14,62 @@ func run():
|
||||
# Load tiny_sword scene
|
||||
var scene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
||||
_game_root = scene.instantiate()
|
||||
|
||||
# Use a unique save path to avoid contamination from previous test runs.
|
||||
var save_path: String = "user://test_gold_mine_%d.json" % randi()
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(save_path))
|
||||
var gs: LevelGameState = _game_root.get_node("LevelGameState")
|
||||
gs.save_file_path = save_path
|
||||
|
||||
add_child(_game_root)
|
||||
|
||||
await _wait()
|
||||
|
||||
var game_state: LevelGameState = _game_root.find_child("LevelGameState")
|
||||
var gold_mine = _game_root.find_child("GoldMine") as CurrencyGenerator
|
||||
var gold_mine_node: Node = _game_root.find_child("GoldMine")
|
||||
|
||||
if game_state == null:
|
||||
print("[ERROR] LevelGameState not found")
|
||||
_print_summary()
|
||||
return
|
||||
|
||||
if gold_mine == null:
|
||||
if gold_mine_node == null:
|
||||
print("[ERROR] GoldMine node not found")
|
||||
_print_summary()
|
||||
return
|
||||
|
||||
# Record initial gold
|
||||
var initial_gold: BigNumber = game_state.get_currency_amount_by_id(&"gold")
|
||||
var gold_mine: CurrencyGenerator = gold_mine_node.find_child("CurrencyGenerator") as CurrencyGenerator
|
||||
if gold_mine == null:
|
||||
print("[ERROR] CurrencyGenerator not found under GoldMine")
|
||||
_print_summary()
|
||||
return
|
||||
|
||||
# Verify game_state is reachable and generator is available
|
||||
TestUtils.assert_not_null(gold_mine.game_state, "game_state is not null")
|
||||
TestUtils.assert_true(gold_mine.is_available_to_player(), "Gold Mine is available to player")
|
||||
|
||||
# Record initial gold (copy to avoid BigNumber reference sharing)
|
||||
var initial_gold: BigNumber = BigNumber.new(
|
||||
game_state.get_currency_amount_by_id(&"gold").mantissa,
|
||||
game_state.get_currency_amount_by_id(&"gold").exponent
|
||||
)
|
||||
print("[TARGET] initial_gold: %s" % initial_gold.to_string_suffix(2))
|
||||
|
||||
# Temporarily set press_buys_generator to false so _on_pressed grants click currency
|
||||
# (Gold Mine defaults to true which would try to buy with workers instead)
|
||||
var original_press_buys: bool = gold_mine.press_buys_generator
|
||||
gold_mine.press_buys_generator = false
|
||||
|
||||
# Simulate clicking the generator
|
||||
gold_mine._on_pressed()
|
||||
|
||||
await _wait()
|
||||
|
||||
# Validate gold increased
|
||||
var final_gold: BigNumber = game_state.get_currency_amount_by_id(&"gold")
|
||||
# Validate gold increased (copy to avoid BigNumber reference sharing)
|
||||
var final_gold: BigNumber = BigNumber.new(
|
||||
game_state.get_currency_amount_by_id(&"gold").mantissa,
|
||||
game_state.get_currency_amount_by_id(&"gold").exponent
|
||||
)
|
||||
print("[TARGET] final_gold: %s" % final_gold.to_string_suffix(2))
|
||||
|
||||
TestUtils.assert_greater_than(
|
||||
|
||||
@@ -5,9 +5,7 @@ var failed: int = 0
|
||||
var _game_root: Node = null
|
||||
|
||||
func _ready():
|
||||
await run()
|
||||
# Let test_runner handle exit
|
||||
pass
|
||||
pass # run() is called by the test runner; avoid double-execution.
|
||||
|
||||
func run():
|
||||
print("\n=== TEST: Prestige Mechanics ===\n")
|
||||
@@ -15,6 +13,13 @@ func run():
|
||||
|
||||
var scene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
||||
_game_root = scene.instantiate()
|
||||
|
||||
# Use a unique save path to avoid contaminating the real game's save.
|
||||
var save_path: String = "user://test_prestige_%d.json" % randi()
|
||||
DirAccess.remove_absolute(ProjectSettings.globalize_path(save_path))
|
||||
var gs: LevelGameState = _game_root.get_node("LevelGameState")
|
||||
gs.save_file_path = save_path
|
||||
|
||||
add_child(_game_root)
|
||||
|
||||
await _wait()
|
||||
|
||||
Reference in New Issue
Block a user