From 738381a16b94b408ff37949c809cf8fce68b1add Mon Sep 17 00:00:00 2001 From: Michele Rossi Date: Fri, 15 May 2026 00:47:38 +0200 Subject: [PATCH] Add final progression to tiny sword, fixes and tweeks required --- TODO.md | 283 ------------- core/generator/currency_generator.gd | 11 +- core/prestige/prestige_buff_graph_tile.gd | 6 +- docs/gyms/tiny_sword/PROGRESSION.md | 388 ++++++++++++++++++ .../buffs/fertile_infusion_buff.tres | 17 + .../tiny_sword/buffs/opus_magnum_buff.tres | 17 + .../tiny_sword/buffs/sylvan_cognite_buff.tres | 17 + .../tiny_sword/buffs/ts_buff_catalogue.tres | 5 +- .../buildings/alchemy_tower/alchemy_tower.gd | 26 +- .../alchemy_tower/alchemy_tower.tscn | 8 +- .../buildings/barracks/Barracks.png | Bin 0 -> 9915 bytes .../buildings/barracks/Barracks.png.import | 40 ++ .../tiny_sword/buildings/barracks/barracks.gd | 25 ++ .../buildings/barracks/barracks.gd.uid | 1 + .../buildings/barracks/barracks.tscn | 40 ++ .../barracks/barracks_generator.tres | 18 + .../tiny_sword/buildings/castle/castle.gd | 24 +- .../buildings/devil_idol/Castle.png | Bin 0 -> 17023 bytes .../buildings/devil_idol/Castle.png.import | 40 ++ .../buildings/devil_idol/devil_idol.gd | 135 ++++++ .../buildings/devil_idol/devil_idol.gd.uid | 1 + .../buildings/devil_idol/devil_idol.tscn | 57 +++ docs/gyms/tiny_sword/buildings/farm/farm.gd | 25 ++ .../tiny_sword/buildings/farm/farm.gd.uid | 1 + docs/gyms/tiny_sword/buildings/farm/farm.tscn | 2 + .../tiny_sword/buildings/forestry/forestry.gd | 26 +- .../buildings/monastery/monastery.gd | 29 +- .../currencies/ts_currency_catalogue.tres | 3 +- docs/gyms/tiny_sword/currencies/unit.tres | 10 + .../gold_2M_food_200k_wood_200k_goal.tres | 30 ++ .../goals/gold_50M_food_5M_wood_5M_goal.tres | 30 ++ .../tiny_sword/goals/ts_goal_catalogue.tres | 4 +- .../prestige_buff_barracks_boost.tres | 13 + .../prestige/prestige_buff_catalogue.tres | 14 +- .../prestige/prestige_buff_farm_boost.tres | 1 + .../prestige/prestige_buff_graph_panel.tscn | 27 +- .../prestige/prestige_buff_wood_boost.tres | 1 + .../tiny_sword/prestige/primary_prestige.tres | 4 +- docs/gyms/tiny_sword/tiny_sword.tscn | 10 +- tests/test_ascension.gd | 15 +- tests/test_goals_prestige_reset.gd | 8 +- tests/test_gold_mine_click.gd | 43 +- tests/test_prestige.gd | 11 +- 43 files changed, 1127 insertions(+), 339 deletions(-) delete mode 100644 TODO.md create mode 100644 docs/gyms/tiny_sword/PROGRESSION.md create mode 100644 docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres create mode 100644 docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres create mode 100644 docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres create mode 100644 docs/gyms/tiny_sword/buildings/barracks/Barracks.png create mode 100644 docs/gyms/tiny_sword/buildings/barracks/Barracks.png.import create mode 100644 docs/gyms/tiny_sword/buildings/barracks/barracks.gd create mode 100644 docs/gyms/tiny_sword/buildings/barracks/barracks.gd.uid create mode 100644 docs/gyms/tiny_sword/buildings/barracks/barracks.tscn create mode 100644 docs/gyms/tiny_sword/buildings/barracks/barracks_generator.tres create mode 100644 docs/gyms/tiny_sword/buildings/devil_idol/Castle.png create mode 100644 docs/gyms/tiny_sword/buildings/devil_idol/Castle.png.import create mode 100644 docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd create mode 100644 docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd.uid create mode 100644 docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.tscn create mode 100644 docs/gyms/tiny_sword/buildings/farm/farm.gd create mode 100644 docs/gyms/tiny_sword/buildings/farm/farm.gd.uid create mode 100644 docs/gyms/tiny_sword/currencies/unit.tres create mode 100644 docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres create mode 100644 docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres create mode 100644 docs/gyms/tiny_sword/prestige/prestige_buff_barracks_boost.tres diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 3b1b8c2..0000000 --- a/TODO.md +++ /dev/null @@ -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. diff --git a/core/generator/currency_generator.gd b/core/generator/currency_generator.gd index d65432b..d4c2220 100644 --- a/core/generator/currency_generator.gd +++ b/core/generator/currency_generator.gd @@ -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) @@ -108,7 +111,7 @@ func _ready() -> void: # Research XP is awarded via game_state.add_research_xp() - buff calculation handled statically _ensure_registered() - + ## Updates click cooldown/click grants and runs automatic production cycles. func _process(delta: float) -> void: _remaining_click_cooldown_seconds = maxf(_remaining_click_cooldown_seconds - maxf(delta, 0.0), 0.0) @@ -716,12 +719,14 @@ func _default_owned_state() -> int: ## Area2D callback: pointer entered generator interaction region. func _on_area_2d_mouse_entered() -> void: _mouse_entered = true - info_generator_container.visible = 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 - info_generator_container.visible = false + if info_generator_container: + info_generator_container.visible = false func _on_generated_state_changed(generator_id: StringName, _state: Dictionary) -> void: diff --git a/core/prestige/prestige_buff_graph_tile.gd b/core/prestige/prestige_buff_graph_tile.gd index 63f62f6..877117a 100644 --- a/core/prestige/prestige_buff_graph_tile.gd +++ b/core/prestige/prestige_buff_graph_tile.gd @@ -109,9 +109,9 @@ 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: - _refresh_state() +func _on_prestige_buff_unlocked(_buff_id: StringName) -> void: + # Any buff unlock may change this tile's parent-availability, so always refresh. + _refresh_state() func _on_currency_changed(currency_id: StringName, _new_amount: BigNumber) -> void: diff --git a/docs/gyms/tiny_sword/PROGRESSION.md b/docs/gyms/tiny_sword/PROGRESSION.md new file mode 100644 index 0000000..8f9bc97 --- /dev/null +++ b/docs/gyms/tiny_sword/PROGRESSION.md @@ -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 +``` diff --git a/docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres b/docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres new file mode 100644 index 0000000..14b6b8a --- /dev/null +++ b/docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres @@ -0,0 +1,17 @@ +[gd_resource type="Resource" script_class="GeneratorBuffData" format=3] + +[ext_resource type="Script" path="res://core/generator/generator_buff_data.gd" id="1_buff"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/mana_stone.tres" id="2_cost"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/food.tres" id="3_target"] + +[resource] +script = ExtResource("1_buff") +id = &"fertile_infusion" +target_ids = Array[StringName]([&"farm"]) +text = "Fertile Infusion" +max_level = 5 +effect_increment = 1.0 +cost_currency = ExtResource("2_cost") +base_cost_mantissa = 1.0 +cost_multiplier = 1.5 +resource_target_currency = ExtResource("3_target") diff --git a/docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres b/docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres new file mode 100644 index 0000000..737b47c --- /dev/null +++ b/docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres @@ -0,0 +1,17 @@ +[gd_resource type="Resource" script_class="GeneratorBuffData" format=3] + +[ext_resource type="Script" path="res://core/generator/generator_buff_data.gd" id="1_buff"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/philosoper_stone.tres" id="2_cost"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/gold.tres" id="3_target"] + +[resource] +script = ExtResource("1_buff") +id = &"opus_magnum" +target_ids = Array[StringName]([&"goldmine"]) +text = "Opus Magnum" +max_level = 5 +effect_increment = 2.0 +cost_currency = ExtResource("2_cost") +base_cost_mantissa = 1.0 +cost_multiplier = 2.0 +resource_target_currency = ExtResource("3_target") diff --git a/docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres b/docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres new file mode 100644 index 0000000..aba32fe --- /dev/null +++ b/docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres @@ -0,0 +1,17 @@ +[gd_resource type="Resource" script_class="GeneratorBuffData" format=3] + +[ext_resource type="Script" path="res://core/generator/generator_buff_data.gd" id="1_buff"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/cognite.tres" id="2_cost"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/currencies/wood.tres" id="3_target"] + +[resource] +script = ExtResource("1_buff") +id = &"sylvan_cognite" +target_ids = Array[StringName]([&"forestry"]) +text = "Sylvan Cognite" +max_level = 5 +effect_increment = 1.0 +cost_currency = ExtResource("2_cost") +base_cost_mantissa = 1.0 +cost_multiplier = 1.5 +resource_target_currency = ExtResource("3_target") diff --git a/docs/gyms/tiny_sword/buffs/ts_buff_catalogue.tres b/docs/gyms/tiny_sword/buffs/ts_buff_catalogue.tres index 00ba8fb..1decca3 100644 --- a/docs/gyms/tiny_sword/buffs/ts_buff_catalogue.tres +++ b/docs/gyms/tiny_sword/buffs/ts_buff_catalogue.tres @@ -7,8 +7,11 @@ [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" path="res://docs/gyms/tiny_sword/buffs/fertile_infusion_buff.tres" id="7_fertile"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/buffs/sylvan_cognite_buff.tres" id="8_sylvan"] +[ext_resource type="Resource" path="res://docs/gyms/tiny_sword/buffs/opus_magnum_buff.tres" id="9_opus"] [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")]) metadata/_custom_type_script = "uid://ctc5yjlnvi0ok" diff --git a/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.gd b/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.gd index fddca0d..0833e27 100644 --- a/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.gd +++ b/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.gd @@ -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) diff --git a/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.tscn b/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.tscn index 7f00c41..ebeeae8 100644 --- a/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.tscn +++ b/docs/gyms/tiny_sword/buildings/alchemy_tower/alchemy_tower.tscn @@ -26,10 +26,10 @@ shape = SubResource("RectangleShape2D_8pntr") [node name="AlchemyCurrenciesPanel" parent="." unique_id=731368154 instance=ExtResource("2_8pntr")] 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") diff --git a/docs/gyms/tiny_sword/buildings/barracks/Barracks.png b/docs/gyms/tiny_sword/buildings/barracks/Barracks.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c186de474abc63667a4b75022d60141e580196 GIT binary patch literal 9915 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 literal 0 HcmV?d00001 diff --git a/docs/gyms/tiny_sword/buildings/barracks/Barracks.png.import b/docs/gyms/tiny_sword/buildings/barracks/Barracks.png.import new file mode 100644 index 0000000..d1a42ed --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/barracks/Barracks.png.import @@ -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 diff --git a/docs/gyms/tiny_sword/buildings/barracks/barracks.gd b/docs/gyms/tiny_sword/buildings/barracks/barracks.gd new file mode 100644 index 0000000..cea1ccb --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/barracks/barracks.gd @@ -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 diff --git a/docs/gyms/tiny_sword/buildings/barracks/barracks.gd.uid b/docs/gyms/tiny_sword/buildings/barracks/barracks.gd.uid new file mode 100644 index 0000000..9220697 --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/barracks/barracks.gd.uid @@ -0,0 +1 @@ +uid://xk7d3maod6uc diff --git a/docs/gyms/tiny_sword/buildings/barracks/barracks.tscn b/docs/gyms/tiny_sword/buildings/barracks/barracks.tscn new file mode 100644 index 0000000..5724930 --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/barracks/barracks.tscn @@ -0,0 +1,40 @@ +[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")] +visible = false +offset_left = 138.0 +offset_top = -100.0 +offset_right = 402.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"] diff --git a/docs/gyms/tiny_sword/buildings/barracks/barracks_generator.tres b/docs/gyms/tiny_sword/buildings/barracks/barracks_generator.tres new file mode 100644 index 0000000..48bc9e4 --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/barracks/barracks_generator.tres @@ -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 diff --git a/docs/gyms/tiny_sword/buildings/castle/castle.gd b/docs/gyms/tiny_sword/buildings/castle/castle.gd index f68c4cc..d7d0c09 100644 --- a/docs/gyms/tiny_sword/buildings/castle/castle.gd +++ b/docs/gyms/tiny_sword/buildings/castle/castle.gd @@ -1,20 +1,26 @@ 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. + 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 diff --git a/docs/gyms/tiny_sword/buildings/devil_idol/Castle.png b/docs/gyms/tiny_sword/buildings/devil_idol/Castle.png new file mode 100644 index 0000000000000000000000000000000000000000..b4c2188b6ff6b8cf41f5e467727fbf4d39c93752 GIT binary patch literal 17023 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 literal 0 HcmV?d00001 diff --git a/docs/gyms/tiny_sword/buildings/devil_idol/Castle.png.import b/docs/gyms/tiny_sword/buildings/devil_idol/Castle.png.import new file mode 100644 index 0000000..ef898a6 --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/devil_idol/Castle.png.import @@ -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 diff --git a/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd b/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd new file mode 100644 index 0000000..fefe77b --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd @@ -0,0 +1,135 @@ +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) + + # 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 _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 + if _max_hp.mantissa > 0.0: + progress_pct = (1.0 - (_current_hp.mantissa / _max_hp.mantissa)) * pow(10.0, float(_current_hp.exponent - _max_hp.exponent)) + progress_pct = clampf(progress_pct * 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 diff --git a/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd.uid b/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd.uid new file mode 100644 index 0000000..d42e6ff --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.gd.uid @@ -0,0 +1 @@ +uid://blfof77kjp7x0 diff --git a/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.tscn b/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.tscn new file mode 100644 index 0000000..6be6270 --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/devil_idol/devil_idol.tscn @@ -0,0 +1,57 @@ +[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] +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"] diff --git a/docs/gyms/tiny_sword/buildings/farm/farm.gd b/docs/gyms/tiny_sword/buildings/farm/farm.gd new file mode 100644 index 0000000..7f56942 --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/farm/farm.gd @@ -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 diff --git a/docs/gyms/tiny_sword/buildings/farm/farm.gd.uid b/docs/gyms/tiny_sword/buildings/farm/farm.gd.uid new file mode 100644 index 0000000..a108c80 --- /dev/null +++ b/docs/gyms/tiny_sword/buildings/farm/farm.gd.uid @@ -0,0 +1 @@ +uid://dnsblnjy82goo diff --git a/docs/gyms/tiny_sword/buildings/farm/farm.tscn b/docs/gyms/tiny_sword/buildings/farm/farm.tscn index 8abd88b..537ded9 100644 --- a/docs/gyms/tiny_sword/buildings/farm/farm.tscn +++ b/docs/gyms/tiny_sword/buildings/farm/farm.tscn @@ -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") diff --git a/docs/gyms/tiny_sword/buildings/forestry/forestry.gd b/docs/gyms/tiny_sword/buildings/forestry/forestry.gd index 1e243a3..289db73 100644 --- a/docs/gyms/tiny_sword/buildings/forestry/forestry.gd +++ b/docs/gyms/tiny_sword/buildings/forestry/forestry.gd @@ -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 diff --git a/docs/gyms/tiny_sword/buildings/monastery/monastery.gd b/docs/gyms/tiny_sword/buildings/monastery/monastery.gd index ad93604..63ee425 100644 --- a/docs/gyms/tiny_sword/buildings/monastery/monastery.gd +++ b/docs/gyms/tiny_sword/buildings/monastery/monastery.gd @@ -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: - _research_panel.visible = true + 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 diff --git a/docs/gyms/tiny_sword/currencies/ts_currency_catalogue.tres b/docs/gyms/tiny_sword/currencies/ts_currency_catalogue.tres index ff00aa2..c9ea422 100644 --- a/docs/gyms/tiny_sword/currencies/ts_currency_catalogue.tres +++ b/docs/gyms/tiny_sword/currencies/ts_currency_catalogue.tres @@ -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" diff --git a/docs/gyms/tiny_sword/currencies/unit.tres b/docs/gyms/tiny_sword/currencies/unit.tres new file mode 100644 index 0000000..fe30506 --- /dev/null +++ b/docs/gyms/tiny_sword/currencies/unit.tres @@ -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") diff --git a/docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres b/docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres new file mode 100644 index 0000000..6fbb422 --- /dev/null +++ b/docs/gyms/tiny_sword/goals/gold_2M_food_200k_wood_200k_goal.tres @@ -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")]) diff --git a/docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres b/docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres new file mode 100644 index 0000000..65210d3 --- /dev/null +++ b/docs/gyms/tiny_sword/goals/gold_50M_food_5M_wood_5M_goal.tres @@ -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")]) diff --git a/docs/gyms/tiny_sword/goals/ts_goal_catalogue.tres b/docs/gyms/tiny_sword/goals/ts_goal_catalogue.tres index d5067d8..5934ccb 100644 --- a/docs/gyms/tiny_sword/goals/ts_goal_catalogue.tres +++ b/docs/gyms/tiny_sword/goals/ts_goal_catalogue.tres @@ -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")]) diff --git a/docs/gyms/tiny_sword/prestige/prestige_buff_barracks_boost.tres b/docs/gyms/tiny_sword/prestige/prestige_buff_barracks_boost.tres new file mode 100644 index 0000000..ac21573 --- /dev/null +++ b/docs/gyms/tiny_sword/prestige/prestige_buff_barracks_boost.tres @@ -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 diff --git a/docs/gyms/tiny_sword/prestige/prestige_buff_catalogue.tres b/docs/gyms/tiny_sword/prestige/prestige_buff_catalogue.tres index 33ecc25..bcb946e 100644 --- a/docs/gyms/tiny_sword/prestige/prestige_buff_catalogue.tres +++ b/docs/gyms/tiny_sword/prestige/prestige_buff_catalogue.tres @@ -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")]) diff --git a/docs/gyms/tiny_sword/prestige/prestige_buff_farm_boost.tres b/docs/gyms/tiny_sword/prestige/prestige_buff_farm_boost.tres index d9bd1e2..f6e0232 100644 --- a/docs/gyms/tiny_sword/prestige/prestige_buff_farm_boost.tres +++ b/docs/gyms/tiny_sword/prestige/prestige_buff_farm_boost.tres @@ -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 diff --git a/docs/gyms/tiny_sword/prestige/prestige_buff_graph_panel.tscn b/docs/gyms/tiny_sword/prestige/prestige_buff_graph_panel.tscn index f05637f..433824f 100644 --- a/docs/gyms/tiny_sword/prestige/prestige_buff_graph_panel.tscn +++ b/docs/gyms/tiny_sword/prestige/prestige_buff_graph_panel.tscn @@ -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") diff --git a/docs/gyms/tiny_sword/prestige/prestige_buff_wood_boost.tres b/docs/gyms/tiny_sword/prestige/prestige_buff_wood_boost.tres index 7a07a44..be3f005 100644 --- a/docs/gyms/tiny_sword/prestige/prestige_buff_wood_boost.tres +++ b/docs/gyms/tiny_sword/prestige/prestige_buff_wood_boost.tres @@ -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 diff --git a/docs/gyms/tiny_sword/prestige/primary_prestige.tres b/docs/gyms/tiny_sword/prestige/primary_prestige.tres index 60e81a8..1672959 100644 --- a/docs/gyms/tiny_sword/prestige/primary_prestige.tres +++ b/docs/gyms/tiny_sword/prestige/primary_prestige.tres @@ -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 diff --git a/docs/gyms/tiny_sword/tiny_sword.tscn b/docs/gyms/tiny_sword/tiny_sword.tscn index 91b15d9..29ff855 100644 --- a/docs/gyms/tiny_sword/tiny_sword.tscn +++ b/docs/gyms/tiny_sword/tiny_sword.tscn @@ -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) diff --git a/tests/test_ascension.gd b/tests/test_ascension.gd index cb9ec39..0336a99 100644 --- a/tests/test_ascension.gd +++ b/tests/test_ascension.gd @@ -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") diff --git a/tests/test_goals_prestige_reset.gd b/tests/test_goals_prestige_reset.gd index 75a7d64..17c6e25 100644 --- a/tests/test_goals_prestige_reset.gd +++ b/tests/test_goals_prestige_reset.gd @@ -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()) diff --git a/tests/test_gold_mine_click.gd b/tests/test_gold_mine_click.gd index 8c64b7c..ac1528c 100644 --- a/tests/test_gold_mine_click.gd +++ b/tests/test_gold_mine_click.gd @@ -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( diff --git a/tests/test_prestige.gd b/tests/test_prestige.gd index 0569763..1df11f0 100644 --- a/tests/test_prestige.gd +++ b/tests/test_prestige.gd @@ -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()