From cd23125739db8bdb8c214457a33e720cd2955451 Mon Sep 17 00:00:00 2001 From: Michele Rossi Date: Sat, 4 Apr 2026 16:04:51 +0200 Subject: [PATCH] Add docs --- core/README.md | 117 ++++++++++++ core/currency/README.md | 84 +++++++++ core/game_state.gd | 31 +++- core/generator/README.md | 204 +++++++++++++++++++++ core/generator/currency_generator.gd | 22 ++- core/generator/generator_container.gd | 7 + core/goals/README.md | 167 ++++++++++++++++++ core/prestige/README.md | 244 ++++++++++++++++++++++++++ project.godot | 1 + 9 files changed, 870 insertions(+), 7 deletions(-) create mode 100644 core/README.md create mode 100644 core/currency/README.md create mode 100644 core/generator/README.md create mode 100644 core/goals/README.md create mode 100644 core/prestige/README.md diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..144c40a --- /dev/null +++ b/core/README.md @@ -0,0 +1,117 @@ +# Core Module Documentation + +## Overview + +The `core/` directory contains the fundamental systems for the idle game. It provides: + +- **Numeric Foundation**: `BigNumber` for handling huge idle game values +- **Game State Management**: `GameState` autoload for all persistent state +- **Currency System**: Database and tracking for multiple currency types +- **Generator System**: Automated resource production with scaling costs +- **Buff System**: Upgrades that modify generator behavior +- **Goal System**: Achievement tracking and unlock triggers +- **Prestige System**: Rebirth mechanics with permanent multipliers + +## Architecture + +``` +core/ +├── big_number.gd # Core numeric API +├── game_state.gd # Main state management autoload +├── buff_database.gd # Buff registration system +├── currency_database.gd # Currency registration system +├── currency/ # Currency data structures +├── generator/ # Generator logic and data +├── goals/ # Goal/achievement system +└── prestige/ # Prestige/rebirth system +``` + +## Data Flow + +1. **Game Start**: `GameState._ready()` initializes currencies, loads save +2. **Player Action**: Generator bought → `GameState` updated → signals emitted +3. **UI Reaction**: Listeners respond to signals → update display +4. **Goal Check**: Currency changed → goals evaluated → buffs unlocked +5. **Save**: `GameState.save_game()` → JSON to `user://idle_save.json` + +## Save Format + +**Version**: 3 + +```json +{ + "save_format_version": 3, + "currencies": { + "": { + "current": {"m": float, "e": int}, + "total": {"m": float, "e": int}, + "all_time": {"m": float, "e": int} + } + }, + "generator_states": { + "": { + "owned": int, + "purchased_count": int, + "unlocked": bool, + "available": bool + } + }, + "buff_levels": { + "": int + }, + "buff_unlocked": { + "": true + }, + "buff_active": { + "": true + }, + "goals": { + "completed": ["", ...] + }, + "last_save_time": unix_timestamp +} +``` + +## Key Signals + +| Signal | Emitted When | +|--------|--------------| +| `currency_changed(currency_id, new_amount)` | Any currency balance changes | +| `generator_state_changed(generator_id, state)` | Generator owned/unlocked changes | +| `generator_buff_level_changed(gen_id, buff_id, level)` | Buff level increased | +| `buff_unlocked_changed(buff_id, unlocked)` | Buff unlock state changed | +| `goal_completed(goal_id)` | Goal criteria met | + +## BigNumber Serialization + +All large numbers use scientific notation internally: +```gdscript +# Value = mantissa * 10^exponent +var num = BigNumber.new(1.5, 24) # 1.5e24 +var dict = num.serialize() # {"m": 1.5, "e": 24} +var restored = BigNumber.deserialize(dict) +``` + +## Extending the Core + +### Adding a New Currency +1. Create `res://sandbox/currencies/.tres` extending `Currency` +2. Set `id`, `display_name`, and `icon` +3. Auto-discovered by `CurrencyDatabase` + +### Adding a New Generator +1. Create `res://sandbox/generators/.tres` extending `CurrencyGeneratorData` +2. Configure costs, production, and unlock goals +3. Instance `CurrencyGenerator` scene with this data + +### Adding a New Buff +1. Create `res://sandbox/buffs/.tres` extending `GeneratorBuffData` +2. Set `target_ids`, `kind`, `effect_increment`, and `cost` +3. Auto-discovered by `BuffDatabase` + +## See Also + +- `currency/` - Currency data structures +- `generator/` - Generator production system +- `goals/` - Achievement system +- `prestige/` - Rebirth mechanics diff --git a/core/currency/README.md b/core/currency/README.md new file mode 100644 index 0000000..a215267 --- /dev/null +++ b/core/currency/README.md @@ -0,0 +1,84 @@ +# Currency Module Documentation + +## Overview + +The `currency/` subfolder contains the data structure for defining in-game currencies. + +## Files + +### `currency.gd` + +Defines the `Currency` class - a Resource for currency metadata. + +## Currency Class + +```gdscript +class_name Currency +extends Resource + +@export var id: StringName = &"" # Unique identifier (e.g., &"gold") +@export var display_name: String = "" # UI-friendly name +@export var icon: Texture2D # Display icon +``` + +## Usage + +### Defining a Currency + +Create a `.tres` resource file with: +- **id**: Lowercase unique key (used internally) +- **display_name**: What players see ("Gold", "Gems") +- **icon**: Texture for UI display + +Example `res://sandbox/currencies/gold.tres`: +``` +[gd_resource type="Resource" script_class="Currency"] +[resource] +id = &"gold" +display_name = "Gold" +icon = ExtResource("uid://...") +``` + +### Accessing Currencies + +```gdscript +# Get all known currencies +var currencies: Array[Currency] = CurrencyDatabase.get_known_currencies() + +# Get currency by ID +var gold: Currency = CurrencyDatabase.get_currency_resource(&"gold") + +# Check if currency exists +var is_valid: bool = CurrencyDatabase.is_known_currency_id(&"gems") + +# Get display name +var name: String = CurrencyDatabase.get_currency_name(&"gold") +``` + +## Integration with GameState + +Currencies are tracked in three ways: +1. **current**: Current balance (resets on prestige) +2. **total**: Total acquired this run (resets on prestige) +3. **all_time**: Lifetime total (never resets) + +```gdscript +# Add currency +GameState.add_currency(gold_resource, BigNumber.new(100.0, 0)) + +# Get balance +var balance: BigNumber = GameState.get_currency_amount_by_id(&"gold") + +# Spend currency (returns false if insufficient) +var success: bool = GameState.spend_currency_by_id(&"gold", BigNumber.new(50.0, 0)) +``` + +## Auto-Discovery + +`CurrencyDatabase` automatically scans `res://sandbox/currencies/` for `.tres` and `.res` files on startup. + +## See Also + +- `core/game_state.gd` - Currency state management +- `core/big_number.gd` - Large number handling +- `generator/currency_generator_data.gd` - Generator purchase currency diff --git a/core/game_state.gd b/core/game_state.gd index 8a4501f..27100a0 100644 --- a/core/game_state.gd +++ b/core/game_state.gd @@ -721,7 +721,10 @@ func _get_generator_state(generator_id: StringName) -> Dictionary: var existing_raw: Variant = generator_states.get(key, _default_generator_state(false, false, 0)) if existing_raw is Dictionary: var state: Dictionary = existing_raw - var sanitized: Dictionary = _sanitize_generator_state(state, false, false, 0) + var current_unlocked: bool = state.get(GENERATOR_UNLOCKED_KEY, false) + var current_available: bool = state.get(GENERATOR_AVAILABLE_KEY, false) + var current_owned: int = state.get(GENERATOR_OWNED_KEY, 0) + var sanitized: Dictionary = _sanitize_generator_state(state, current_unlocked, current_available, current_owned) generator_states[key] = sanitized return sanitized @@ -734,7 +737,10 @@ func _set_generator_state(generator_id: StringName, state: Dictionary) -> void: if key.is_empty(): return - var sanitized: Dictionary = _sanitize_generator_state(state, false, false, 0) + var current_unlocked: bool = state.get(GENERATOR_UNLOCKED_KEY, false) + var current_available: bool = state.get(GENERATOR_AVAILABLE_KEY, false) + var current_owned: int = state.get(GENERATOR_OWNED_KEY, 0) + var sanitized: Dictionary = _sanitize_generator_state(state, current_unlocked, current_available, current_owned) generator_states[key] = sanitized generator_state_changed.emit(StringName(key), sanitized.duplicate(true)) @@ -1098,9 +1104,30 @@ func _try_complete_goal(goal_id: StringName) -> void: if _goal_completed[goal_id]: return if goal.is_met(): + if _is_goal_manual_unlock(goal_id): + return _goal_completed[goal_id] = true goal_completed.emit(goal_id) +func _is_goal_manual_unlock(goal_id: StringName) -> bool: + var goal: GoalData = _goal_definitions.get(goal_id, null) + if goal == null or not goal.has_id(): + return false + + var scene_root: Node = get_tree().current_scene + if scene_root == null: + return false + + var gen_nodes: Array[Node] = scene_root.find_children("*", "CurrencyGenerator", true, false) + for node in gen_nodes: + if node.has_method("get_generator_id") and node.has_method("data"): + var data: Variant = node.get("data") + if data != null and data.has_method("has_unlock_goal") and data.has_method("unlocks_automatically_from_goal"): + if data.has_unlock_goal() and data.get_unlock_goal_id() == goal_id: + return not data.unlocks_automatically_from_goal() + + return false + func _serialize_goals() -> Dictionary: var result: Dictionary = {} var completed_goals: Array[StringName] = [] diff --git a/core/generator/README.md b/core/generator/README.md new file mode 100644 index 0000000..cec85f6 --- /dev/null +++ b/core/generator/README.md @@ -0,0 +1,204 @@ +# Generator Module Documentation + +## Overview + +The `generator/` subfolder contains the core idle game mechanics: automated resource production with scaling costs, upgrades (buffs), and manual interaction. + +## Files + +| File | Purpose | +|------|---------| +| `currency_generator.gd` | Runtime generator logic (Node2D) | +| `currency_generator_data.gd` | Balancing data resource | +| `generator_buff_data.gd` | Buff/upgrade definitions | +| `generator_container.gd` | UI panel for generator stats | + +## Architecture + +``` +CurrencyGenerator (Node2D) +├── Handles production cycles +├── Manages purchases +├── Processes clicks/hover +└── GeneratorPanel (UI) + ├── Shows stats/costs + └── Buff upgrade buttons +``` + +## CurrencyGeneratorData + +Resource class for generator balancing data. + +### Key Properties + +```gdscript +@export var id: StringName = &"" # Unique identifier +@export var name: String = "" # Display name +@export var starts_unlocked: bool = true # Visible at game start +@export var purchase_currency: Currency # What to spend +@export var unlock_goal: GoalData # Goal to unlock this + +# Cost formula: cost = base * coefficient^owned +@export var initial_cost: float = 10.0 # Base cost +@export var coefficient: float = 1.15 # Growth factor + +# Production +@export var initial_time: float = 1.0 # Seconds per cycle +@export var initial_productivity: float = 1.0 # Resources per cycle + +# Milestones +@export var milestone_step: int = 25 # Every N units +@export var milestone_multiplier: float = 2.0 # x multiplier + +# Manual click +@export var click_mantissa: float = 1.0 +@export var click_exponent: int = 0 +@export var click_cooldown_seconds: float = 0.2 +``` + +### Cost Formula + +**Bulk Buy Cost** (geometric series): +``` +total_cost = base * r^owned * ((r^n - 1) / (r - 1)) +``` +Where: +- `base` = initial_cost +- `r` = coefficient +- `owned` = current count +- `n` = amount to buy + +**Max Buy** (inverse formula): +``` +max = floor(log((currency * (r-1) / (base * r^owned)) + 1) / log(r)) +``` + +### Production Formula + +```gdscript +production_per_cycle = initial_productivity * owned * run_multiplier * milestone_mult * purchased_mult +production_per_second = production_per_cycle / initial_time +``` + +## CurrencyGenerator + +Runtime node instance attached to generator scenes. + +### Signals + +```gdscript +signal purchase_completed(amount, total_owned, total_purchased, total_cost) +signal production_tick(amount, cycle_count, total_owned) +signal buff_purchased(buff_id, new_level, cost, cost_currency_id) +signal goal_achieved(generator_id, goal_id) +``` + +### Key Methods + +```gdscript +# Purchase +func buy(amount: int = 1) -> bool +func buy_max() -> int +func get_cost_for_amount(amount: int) -> BigNumber +func can_buy(amount: int) -> bool + +# Production +func grant_currency(amount: BigNumber) +func get_effective_auto_run_multiplier() -> float + +# Buffs +func buy_buff(buff_id: StringName) -> bool +func get_buff_level(buff_id: StringName) -> int +func is_buff_unlocked(buff_id: StringName) -> bool +``` + +### Production Cycle + +1. Accumulate `cycle_progress_seconds` in `_process()` +2. When `>= initial_time`, grant production +3. Emit `production_tick` signal +4. Multiply by: + - Owned count + - Run multiplier (export) + - Buff multipliers (auto_production) + - Milestone bonuses + - Purchase boost + +## GeneratorBuffData + +Defines upgradeable modifiers for generators. + +### Buff Kinds + +```gdscript +enum BuffKind { + AUTO_PRODUCTION_MULTIPLIER, # x output per cycle + MANUAL_CLICK_MULTIPLIER, # x click reward + RESOURCE_PURCHASE # Instant currency grant +} +``` + +### Key Properties + +```gdscript +@export var id: StringName = &"" +@export var target_ids: Array[StringName] = [] # Generators affected +@export var kind: BuffKind +@export var max_level: int = -1 # -1 = unlimited +@export var base_effect: float = 1.0 # Starting multiplier +@export var effect_increment: float = 0.1 # +per level + +@export var base_cost_mantissa: float = 10.0 +@export var base_cost_exponent: int = 0 +@export var cost_multiplier: float = 1.5 # Cost growth + +# Effect: base_effect + (effect_increment * level) +func get_effect_multiplier(level: int) -> float +``` + +### Cost Formula + +```gdscript +cost_for_level = base_cost * cost_multiplier^level +``` + +### Resource Purchase Buff + +Special buff type that grants instant currency: +```gdscript +resource_purchase_amount = base_amount * (resource_purchase_increment_multiplier^level) +``` + +## GeneratorPanel + +UI component showing generator information. + +### Displays +- Generator name and owned count +- Next purchase cost +- Production per second +- Buff upgrade buttons with costs + +### Signals Connected +- `purchase_completed` - Refresh UI +- `production_tick` - Update production display +- `generator_buff_level_changed` - Refresh buff rows + +## Prestige Integration + +Generators automatically apply prestige multipliers: +```gdscript +effective_multiplier = run_multiplier * buff_multiplier * prestige_multiplier +``` + +On prestige: +1. `reset_runtime_state_for_prestige()` called +2. `cycle_progress_seconds` reset +3. Buff levels reset to 0 +4. Unlocks re-evaluated from goals + +## See Also + +- `core/game_state.gd` - Generator state storage +- `goals/goal_data.gd` - Unlock conditions +- `prestige/prestige_manager.gd` - Multiplier application diff --git a/core/generator/currency_generator.gd b/core/generator/currency_generator.gd index 6f8ac60..b5f2073 100644 --- a/core/generator/currency_generator.gd +++ b/core/generator/currency_generator.gd @@ -96,12 +96,15 @@ func _ready() -> void: assert(data != null, "Data cannot be null") _generator_id = _resolve_generator_id() - #_ensure_registered() cycle_progress_seconds = 0.0 GameState.currency_changed.connect(_on_currency_changed) GameState.generator_state_changed.connect(_on_generated_state_changed) - _evaluate_generator_unlock_goal(false) + + # Register with default state first, then evaluate unlock goal + _ensure_registered() + if data != null and data.unlocks_automatically_from_goal(): + _evaluate_generator_unlock_goal(false) ## Updates click cooldown/click grants and runs automatic production cycles. func _process(delta: float) -> void: @@ -319,7 +322,8 @@ func reset_runtime_state_for_prestige() -> void: cycle_progress_seconds = 0.0 _remaining_click_cooldown_seconds = 0.0 _ensure_registered() - _evaluate_generator_unlock_goal(false) + if data != null and data.unlocks_automatically_from_goal(): + _evaluate_generator_unlock_goal(false) #_evaluate_buff_unlock_goals() visible = is_available_to_player() @@ -579,7 +583,8 @@ func _try_unlock_buff_from_goal(buff: GeneratorBuffData) -> void: func _on_currency_changed(_changed_currency_id: StringName, _new_amount: BigNumber) -> void: GameState.evaluate_all_goals() - _evaluate_generator_unlock_goal(false) + if data != null and data.unlocks_automatically_from_goal(): + _evaluate_generator_unlock_goal(false) func try_unlock_from_goal() -> bool: return _evaluate_generator_unlock_goal(true) @@ -595,12 +600,17 @@ func _evaluate_generator_unlock_goal(allow_manual_unlock: bool) -> bool: return false var goal_id: StringName = data.get_unlock_goal_id() - if goal_id.is_empty() or not GameState.is_goal_completed(goal_id): + if goal_id.is_empty(): + return false + + var goal_completed: bool = GameState.is_goal_completed(goal_id) + if not goal_completed: return false GameState.set_generator_unlocked(_generator_id, true) GameState.set_generator_available(_generator_id, true) goal_achieved.emit(_generator_id, goal_id) + _on_generated_state_changed(_generator_id, GameState._get_generator_state(_generator_id)) return true ## Default unlocked state loaded from generator data. @@ -634,3 +644,5 @@ func _on_area_2d_mouse_exited() -> void: func _on_generated_state_changed(generator_id: StringName, _state: Dictionary) -> void: if generator_id == _generator_id: visible = is_available_to_player() + if info_generator_container != null: + info_generator_container._refresh_generator_ui() diff --git a/core/generator/generator_container.gd b/core/generator/generator_container.gd index f2e1d4d..d3e6969 100644 --- a/core/generator/generator_container.gd +++ b/core/generator/generator_container.gd @@ -147,7 +147,14 @@ func _refresh_buff_rows(can_interact: bool) -> void: row.tile.set_runtime(level, effect_text, cost_text, can_buy, false) func _refresh_generator_ui() -> void: + if _buy_button == null or _buy_max_button == null: + return + + if _generator == null: + return + var can_interact: bool = _generator.is_available_to_player() + _buy_button.disabled = not can_interact _buy_max_button.disabled = not can_interact diff --git a/core/goals/README.md b/core/goals/README.md new file mode 100644 index 0000000..22b1e3a --- /dev/null +++ b/core/goals/README.md @@ -0,0 +1,167 @@ +# Goals Module Documentation + +## Overview + +The `goals/` subfolder contains the achievement system. Goals track player progress and can unlock generators or buffs when conditions are met. + +## Files + +| File | Purpose | +|------|---------| +| `goal_data.gd` | Goal definition resource | +| `goal_requirement_data.gd` | Individual requirement criteria | + +## GoalData + +Resource class defining an achievement. + +### Properties + +```gdscript +class_name GoalData +extends Resource + +@export var id: StringName = &"" # Unique identifier +@export var requirements: Array[GoalRequirementData] # Conditions to complete +``` + +### Methods + +```gdscript +func is_valid() -> bool # Has id and valid requirements +func is_met() -> bool # All requirements satisfied +func get_progress() -> float # 0.0 to 1.0 based on closest requirement +func get_first_requirement_summary() -> String # "100 Gold" format +``` + +## GoalRequirementData + +Single condition within a goal. + +### Properties + +```gdscript +class_name GoalRequirementData +extends Resource + +@export var currency: Currency # Currency to track +@export var amount_mantissa: float = 0.0 # Required amount (scientific notation) +@export var amount_exponent: int = 0 +``` + +### Methods + +```gdscript +func get_amount() -> BigNumber # Required amount +func is_met() -> bool # Current >= required +func get_progress() -> float # Log-scaled 0.0-1.0 +func get_summary_text() -> String # "100 Gold" format +``` + +### Progress Calculation + +Uses **logarithmic scaling** for better visual feedback on huge numbers: + +```gdscript +# Convert to log space +current_log = log(current_mantissa) / log(10) + current_exponent +required_log = log(required_mantissa) / log(10) + required_exponent + +# Progress ratio +progress = clampf(current_log / required_log, 0.0, 1.0) +``` + +This means reaching 50% progress on a 1e100 goal requires ~1e50 currency. + +## Goal Evaluation + +### Automatic Checking + +`GameState` evaluates goals whenever currency changes: + +```gdscript +func _on_currency_changed(currency_id, new_amount): + GameState.evaluate_all_goals() +``` + +### Completion Flow + +1. Currency updated → signal emitted +2. `GameState.evaluate_all_goals()` called +3. Each uncompleted goal checked via `is_met()` +4. If met → `goal_completed` signal emitted +5. Buffs with unlock goals checked → unlocked if goal complete + +### Manual Unlock vs Automatic + +Generators can be configured to: +- **Automatic**: Unlock immediately when goal completes +- **Manual**: Goal completion enables a button player must click + +## Usage Example + +### Defining a Goal + +Create `res://sandbox/goals/first_million.tres`: + +``` +[gd_resource type="Resource" script_class="GoalData"] +[resource] +id = &"first_million" +requirements = [ + preload("res://sandbox/goals/first_million_req.tres") +] +``` + +### Defining a Requirement + +Create `res://sandbox/goals/first_million_req.tres`: + +``` +[gd_resource type="Resource" script_class="GoalRequirementData"] +[resource] +currency = preload("res://sandbox/currencies/gold.tres") +amount_mantissa = 1.0 +amount_exponent = 6 # 1,000,000 +``` + +### Connecting to Generator + +In `CurrencyGeneratorData`: +```gdscript +@export var unlock_goal: GoalData # Assign goal resource +@export var unlock_goal_behavior: UnlockGoalBehavior = AUTOMATIC +``` + +## Signals + +```gdscript +signal goal_completed(goal_id: StringName) +signal goal_progress_changed(goal_id: StringName, progress: float) +``` + +## Integration Points + +### With GameState +- Goals auto-discovered from `res://sandbox/goals/` +- Completion state saved to JSON +- Buff unlock goals checked automatically + +### With Buffs +```gdscript +# In GeneratorBuffData +@export var unlock_goal: GoalData + +# When goal completes, buff auto-unlocks: +GameState._try_unlock_buff_from_goal(buff) +``` + +### With Prestige +- Goal completion state persists through prestige +- Reset via `GameState.reset_for_prestige()` + +## See Also + +- `core/game_state.gd` - Goal state management +- `generator/currency_generator_data.gd` - Generator unlock goals +- `generator/generator_buff_data.gd` - Buff unlock goals diff --git a/core/prestige/README.md b/core/prestige/README.md new file mode 100644 index 0000000..4e4b919 --- /dev/null +++ b/core/prestige/README.md @@ -0,0 +1,244 @@ +# Prestige Module Documentation + +## Overview + +The `prestige/` subfolder implements the rebirth/reset system. Players can reset progress in exchange for a permanent multiplier that accelerates future runs. + +## Files + +| File | Purpose | +|------|---------| +| `prestige_config.gd` | Configuration resource for prestige rules | +| `prestige_manager.gd` | Core logic and state management | +| `prestige_panel.gd` | UI panel for prestige interaction | +| `TECH_SPEC.md` | Detailed technical specification | + +## Architecture + +``` +PrestigeManager (autoload node) +├── Tracks prestige currency +├── Calculates pending gain +├── Applies multipliers to generators +└── PrestigePanel (UI) + ├── Shows current/pending prestige + └── Trigger reset button +``` + +## PrestigeConfig + +Resource defining prestige rules. + +### Enums + +```gdscript +enum BasisType { + LIFETIME_TOTAL, # All-time currency acquired + RUN_TOTAL, # Currency this run + RUN_MAX # Peak currency this run +} + +enum FormulaType { + POWER, # gain = scale * (ratio^exponent) + TRIANGULAR_INVERSE # gain based on triangular number inverse +} + +enum RoundingMode { + FLOOR, ROUND, CEIL +} + +enum MultiplierMode { + ADDITIVE, # +X per prestige + MULTIPLICATIVE_POWER # x^(multiplier_per_prestige) +} +``` + +### Key Properties + +```gdscript +@export var id: StringName = &"primary" +@export var display_name: String = "Ascension" +@export var prestige_currency_id: StringName = &"prestige" +@export var source_currency_id: StringName = &"magic" # What to measure +@export var basis: BasisType = LIFETIME_TOTAL +@export var formula: FormulaType = POWER + +# Threshold: minimum source currency needed +@export var threshold_mantissa: float = 1.0 +@export var threshold_exponent: int = 4 # 1e4 = 10,000 + +# Formula parameters +@export var scale: float = 1.0 +@export var exponent: float = 0.5 # Square root + +# Multiplier growth +@export var multiplier_per_prestige: float = 0.05 # +5% per prestige +@export var multiplier_mode: MultiplierMode = ADDITIVE +``` + +## PrestigeManager + +Autoload node managing prestige state. + +### Signals + +```gdscript +signal prestige_state_changed(total_prestige, pending_gain) +signal prestige_performed(gain, total_prestige) +``` + +### State Variables + +```gdscript +var total_prestige_earned: BigNumber # Lifetime prestige +var current_prestige_unspent: BigNumber # Available to spend +var prestige_resets_count: int # How many resets +var run_start_total_source_currency: BigNumber # Track run start +var run_peak_source_currency: BigNumber # Peak this run +``` + +### Key Methods + +```gdscript +# Query state +func get_total_prestige() -> BigNumber +func calculate_pending_gain() -> BigNumber +func can_prestige() -> bool +func get_total_multiplier() -> float + +# Perform prestige +func perform_prestige() -> bool +``` + +### Gain Calculation + +**Power Formula** (default): +```gdscript +ratio = basis_value / threshold +raw_gain = scale * (ratio^exponent) + flat_bonus + +# Cumulative basis subtracts already earned +if basis == LIFETIME_TOTAL: + gain = raw_gain - total_prestige_earned +``` + +**Triangular Inverse Formula**: +```gdscript +# Based on inverse triangular number: n = (sqrt(1 + 8k) - 1) / 2 +k = basis_value / threshold +target_total = (sqrt(1 + 8k) - 1) * 0.5 + +if basis == LIFETIME_TOTAL: + gain = target_total - total_prestige_earned +``` + +### Multiplier Application + +**Additive Mode**: +```gdscript +multiplier = base_multiplier + (total_prestige * multiplier_per_prestige) +# e.g., 1.0 + (5 * 0.05) = 1.25 (25% bonus) +``` + +**Multiplicative Power Mode**: +```gdscript +growth_base = 1.0 + multiplier_per_prestige +multiplier = base_multiplier * (growth_base ^ total_prestige) +# e.g., 1.0 * (1.05 ^ 5) = 1.276 (27.6% bonus) +``` + +### Prestige Reset Flow + +1. Player clicks "Prestige" button +2. `perform_prestige()` called +3. Gain calculated and added to total +4. `GameState.reset_for_prestige()` called: + - Current currencies reset to 0 + - Generator states cleared + - Buff levels reset to 0 + - Goal completion cleared + - Lifetime totals preserved +5. Generators reinitialized +6. Save triggered + +## PrestigePanel + +UI component for prestige interaction. + +### Displays + +```gdscript +_total_value.text = "123.45" # Total prestige earned +_pending_value.text = "56.78" # Gain available now +_multiplier_value.text = "x2.34" # Current multiplier +_basis_label.text = "Lifetime Total (1.50e6 Magic)" +``` + +### Buttons + +- **Reset**: Triggers `perform_prestige()` after confirmation +- **Save**: Manually saves game state + +## Integration with Generators + +Generators apply prestige multipliers automatically: + +```gdscript +func get_effective_auto_run_multiplier() -> float: + return run_multiplier * buff_multiplier * _get_prestige_multiplier() + +func _get_prestige_multiplier() -> float: + var manager = get_node_or_null("/root/PrestigeManager") + if manager: + return manager.get_total_multiplier() + return 1.0 +``` + +## Save Integration + +Prestige state stored in `GameState` external save: + +```json +{ + "prestige": { + "total_prestige_earned": {"m": 10.0, "e": 0}, + "current_prestige_unspent": {"m": 5.0, "e": 0}, + "prestige_resets_count": 3, + "run_start_total_source_currency": {"m": 1000.0, "e": 0}, + "run_peak_source_currency": {"m": 5000.0, "e": 0}, + "last_reset_time": 1234567890 + } +} +``` + +## Configuration Example + +For a typical idle game: + +```gdscript +# Primary prestige currency +id = &"primary" +display_name = "Ascension" +source_currency_id = &"magic" + +# Based on lifetime total of Magic currency +basis = LIFETIME_TOTAL + +# Need 1e6 Magic to get first prestige +threshold_mantissa = 1.0 +threshold_exponent = 6 + +# Square root scaling (diminishing returns) +formula = POWER +exponent = 0.5 + +# +10% multiplier per prestige point +multiplier_mode = ADDITIVE +multiplier_per_prestige = 0.10 +``` + +## See Also + +- `core/game_state.gd` - State persistence +- `generator/currency_generator.gd` - Multiplier application +- `core/big_number.gd` - Large number math diff --git a/project.godot b/project.godot index 4c72ef5..bcbb188 100644 --- a/project.godot +++ b/project.godot @@ -12,6 +12,7 @@ config_version=5 config/name="Idles" config/version="0.0.1" +run/main_scene="uid://dadeowsvaywpp" config/features=PackedStringArray("4.6", "Forward Plus") config/icon="res://icon.svg"