Add prestige
This commit is contained in:
@@ -14,10 +14,12 @@ signal generator_buff_unlocked_changed(generator_id: StringName, buff_id: String
|
|||||||
# ==========================================
|
# ==========================================
|
||||||
var _current_currency: Dictionary = {}
|
var _current_currency: Dictionary = {}
|
||||||
var _total_currency_acquired: Dictionary = {}
|
var _total_currency_acquired: Dictionary = {}
|
||||||
|
var _all_time_currency_acquired: Dictionary = {}
|
||||||
|
|
||||||
var generator_states: Dictionary = {}
|
var generator_states: Dictionary = {}
|
||||||
var generator_buff_levels: Dictionary = {}
|
var generator_buff_levels: Dictionary = {}
|
||||||
var generator_buff_unlocked: Dictionary = {}
|
var generator_buff_unlocked: Dictionary = {}
|
||||||
|
var _external_save_sections: Dictionary = {}
|
||||||
|
|
||||||
var last_save_time: int = 0 # Unix timestamp
|
var last_save_time: int = 0 # Unix timestamp
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@ const GENERATOR_BUFF_UNLOCKED_KEY: String = "generator_buff_unlocked"
|
|||||||
const CURRENCIES_KEY: String = "currencies"
|
const CURRENCIES_KEY: String = "currencies"
|
||||||
const CURRENT_KEY: String = "current"
|
const CURRENT_KEY: String = "current"
|
||||||
const TOTAL_KEY: String = "total"
|
const TOTAL_KEY: String = "total"
|
||||||
|
const ALL_TIME_KEY: String = "all_time"
|
||||||
const LAST_SAVE_TIME_KEY: String = "last_save_time"
|
const LAST_SAVE_TIME_KEY: String = "last_save_time"
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -100,6 +103,7 @@ func add_currency_by_id(currency_id: StringName, amount: BigNumber) -> void:
|
|||||||
|
|
||||||
if amount.mantissa > 0.0:
|
if amount.mantissa > 0.0:
|
||||||
_get_total_currency_ref(normalized_currency_id).add_in_place(amount)
|
_get_total_currency_ref(normalized_currency_id).add_in_place(amount)
|
||||||
|
_get_all_time_currency_ref(normalized_currency_id).add_in_place(amount)
|
||||||
|
|
||||||
var current: BigNumber = _get_current_currency_ref(normalized_currency_id)
|
var current: BigNumber = _get_current_currency_ref(normalized_currency_id)
|
||||||
current.add_in_place(amount)
|
current.add_in_place(amount)
|
||||||
@@ -130,6 +134,12 @@ func get_total_currency_acquired(currency: Resource) -> BigNumber:
|
|||||||
func get_total_currency_acquired_by_id(currency_id: StringName) -> BigNumber:
|
func get_total_currency_acquired_by_id(currency_id: StringName) -> BigNumber:
|
||||||
return _get_total_currency_ref(currency_id)
|
return _get_total_currency_ref(currency_id)
|
||||||
|
|
||||||
|
func get_all_time_currency_acquired(currency: Resource) -> BigNumber:
|
||||||
|
return get_all_time_currency_acquired_by_id(get_currency_id(currency))
|
||||||
|
|
||||||
|
func get_all_time_currency_acquired_by_id(currency_id: StringName) -> BigNumber:
|
||||||
|
return _get_all_time_currency_ref(currency_id)
|
||||||
|
|
||||||
func register_generator(generator_id: StringName, unlocked: bool = false, available: bool = false, owned: int = 0) -> void:
|
func register_generator(generator_id: StringName, unlocked: bool = false, available: bool = false, owned: int = 0) -> void:
|
||||||
var key: String = String(generator_id)
|
var key: String = String(generator_id)
|
||||||
if key.is_empty():
|
if key.is_empty():
|
||||||
@@ -293,6 +303,35 @@ func get_generator_buff_unlocked(generator_id: StringName) -> Dictionary:
|
|||||||
func get_generator_buff_levels(generator_id: StringName) -> Dictionary:
|
func get_generator_buff_levels(generator_id: StringName) -> Dictionary:
|
||||||
return _get_generator_buff_levels_ref(generator_id).duplicate(true)
|
return _get_generator_buff_levels_ref(generator_id).duplicate(true)
|
||||||
|
|
||||||
|
func set_external_save_data(section_key: StringName, payload: Dictionary) -> void:
|
||||||
|
var key: String = String(section_key).strip_edges()
|
||||||
|
if key.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
_external_save_sections[key] = payload.duplicate(true)
|
||||||
|
|
||||||
|
func get_external_save_data(section_key: StringName) -> Dictionary:
|
||||||
|
var key: String = String(section_key).strip_edges()
|
||||||
|
if key.is_empty():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
var raw_payload: Variant = _external_save_sections.get(key, {})
|
||||||
|
if raw_payload is Dictionary:
|
||||||
|
return raw_payload.duplicate(true)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
func reset_for_prestige(reset_total_currency: bool = false) -> void:
|
||||||
|
for currency_id in _collect_currency_ids_for_save():
|
||||||
|
_set_current_currency(currency_id, BigNumber.from_float(0.0))
|
||||||
|
currency_changed.emit(currency_id, _get_current_currency_ref(currency_id))
|
||||||
|
if reset_total_currency:
|
||||||
|
_set_total_currency(currency_id, BigNumber.from_float(0.0))
|
||||||
|
|
||||||
|
generator_states.clear()
|
||||||
|
generator_buff_levels.clear()
|
||||||
|
generator_buff_unlocked.clear()
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# PERSISTENCE (Save/Load)
|
# PERSISTENCE (Save/Load)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -305,6 +344,17 @@ func save_game() -> void:
|
|||||||
LAST_SAVE_TIME_KEY: Time.get_unix_time_from_system()
|
LAST_SAVE_TIME_KEY: Time.get_unix_time_from_system()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for section_key in _external_save_sections.keys():
|
||||||
|
var key: String = String(section_key)
|
||||||
|
if key.is_empty():
|
||||||
|
continue
|
||||||
|
|
||||||
|
var payload: Variant = _external_save_sections.get(section_key)
|
||||||
|
if not (payload is Dictionary):
|
||||||
|
continue
|
||||||
|
|
||||||
|
save_data[key] = payload
|
||||||
|
|
||||||
var file: FileAccess = FileAccess.open(file_name, FileAccess.WRITE)
|
var file: FileAccess = FileAccess.open(file_name, FileAccess.WRITE)
|
||||||
if file:
|
if file:
|
||||||
file.store_string(JSON.stringify(save_data))
|
file.store_string(JSON.stringify(save_data))
|
||||||
@@ -318,6 +368,7 @@ func load_game() -> void:
|
|||||||
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
||||||
if parsed is Dictionary:
|
if parsed is Dictionary:
|
||||||
var parsed_data: Dictionary = parsed
|
var parsed_data: Dictionary = parsed
|
||||||
|
_external_save_sections.clear()
|
||||||
if parsed_data.has(CURRENCIES_KEY):
|
if parsed_data.has(CURRENCIES_KEY):
|
||||||
_load_currency_balances(parsed_data.get(CURRENCIES_KEY, {}))
|
_load_currency_balances(parsed_data.get(CURRENCIES_KEY, {}))
|
||||||
|
|
||||||
@@ -325,6 +376,16 @@ func load_game() -> void:
|
|||||||
generator_buff_levels = _deserialize_generator_buff_levels(parsed_data.get(GENERATOR_BUFF_LEVELS_KEY, {}))
|
generator_buff_levels = _deserialize_generator_buff_levels(parsed_data.get(GENERATOR_BUFF_LEVELS_KEY, {}))
|
||||||
generator_buff_unlocked = _deserialize_generator_buff_unlocked(parsed_data.get(GENERATOR_BUFF_UNLOCKED_KEY, {}))
|
generator_buff_unlocked = _deserialize_generator_buff_unlocked(parsed_data.get(GENERATOR_BUFF_UNLOCKED_KEY, {}))
|
||||||
last_save_time = parsed_data.get(LAST_SAVE_TIME_KEY, Time.get_unix_time_from_system())
|
last_save_time = parsed_data.get(LAST_SAVE_TIME_KEY, Time.get_unix_time_from_system())
|
||||||
|
for save_key_variant in parsed_data.keys():
|
||||||
|
var save_key: String = String(save_key_variant)
|
||||||
|
if save_key.is_empty():
|
||||||
|
continue
|
||||||
|
if save_key in [CURRENCIES_KEY, GENERATOR_STATES_KEY, GENERATOR_BUFF_LEVELS_KEY, GENERATOR_BUFF_UNLOCKED_KEY, LAST_SAVE_TIME_KEY]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
var section_payload: Variant = parsed_data.get(save_key_variant)
|
||||||
|
if section_payload is Dictionary:
|
||||||
|
_external_save_sections[save_key] = section_payload.duplicate(true)
|
||||||
|
|
||||||
func _load_currency_balances(raw_currencies: Variant) -> void:
|
func _load_currency_balances(raw_currencies: Variant) -> void:
|
||||||
if not (raw_currencies is Dictionary):
|
if not (raw_currencies is Dictionary):
|
||||||
@@ -343,8 +404,10 @@ func _load_currency_balances(raw_currencies: Variant) -> void:
|
|||||||
var entry: Dictionary = raw_entry
|
var entry: Dictionary = raw_entry
|
||||||
var current_amount: BigNumber = BigNumber.deserialize(entry.get(CURRENT_KEY, {}))
|
var current_amount: BigNumber = BigNumber.deserialize(entry.get(CURRENT_KEY, {}))
|
||||||
var total_amount: BigNumber = _deserialize_total_currency(entry.get(TOTAL_KEY, null), current_amount)
|
var total_amount: BigNumber = _deserialize_total_currency(entry.get(TOTAL_KEY, null), current_amount)
|
||||||
|
var all_time_amount: BigNumber = _deserialize_all_time_currency(entry.get(ALL_TIME_KEY, null), total_amount)
|
||||||
_set_current_currency(currency_id, current_amount)
|
_set_current_currency(currency_id, current_amount)
|
||||||
_set_total_currency(currency_id, total_amount)
|
_set_total_currency(currency_id, total_amount)
|
||||||
|
_set_all_time_currency(currency_id, all_time_amount)
|
||||||
|
|
||||||
func _deserialize_total_currency(raw_total: Variant, current_amount: BigNumber) -> BigNumber:
|
func _deserialize_total_currency(raw_total: Variant, current_amount: BigNumber) -> BigNumber:
|
||||||
var minimum_total: BigNumber = _copy_big_number(current_amount)
|
var minimum_total: BigNumber = _copy_big_number(current_amount)
|
||||||
@@ -361,6 +424,21 @@ func _deserialize_total_currency(raw_total: Variant, current_amount: BigNumber)
|
|||||||
return minimum_total
|
return minimum_total
|
||||||
return parsed_total
|
return parsed_total
|
||||||
|
|
||||||
|
func _deserialize_all_time_currency(raw_all_time: Variant, total_amount: BigNumber) -> BigNumber:
|
||||||
|
var minimum_all_time: BigNumber = _copy_big_number(total_amount)
|
||||||
|
if minimum_all_time.mantissa < 0.0:
|
||||||
|
minimum_all_time = BigNumber.from_float(0.0)
|
||||||
|
|
||||||
|
if not (raw_all_time is Dictionary):
|
||||||
|
return minimum_all_time
|
||||||
|
|
||||||
|
var parsed_all_time: BigNumber = BigNumber.deserialize(raw_all_time)
|
||||||
|
if parsed_all_time.mantissa < 0.0:
|
||||||
|
return minimum_all_time
|
||||||
|
if parsed_all_time.is_less_than(minimum_all_time):
|
||||||
|
return minimum_all_time
|
||||||
|
return parsed_all_time
|
||||||
|
|
||||||
func _copy_big_number(value: BigNumber) -> BigNumber:
|
func _copy_big_number(value: BigNumber) -> BigNumber:
|
||||||
return BigNumber.new(value.mantissa, value.exponent)
|
return BigNumber.new(value.mantissa, value.exponent)
|
||||||
|
|
||||||
@@ -375,6 +453,7 @@ func _serialize_currency_balances() -> Dictionary:
|
|||||||
result[key] = {
|
result[key] = {
|
||||||
CURRENT_KEY: _get_current_currency_ref(currency_id).serialize(),
|
CURRENT_KEY: _get_current_currency_ref(currency_id).serialize(),
|
||||||
TOTAL_KEY: _get_total_currency_ref(currency_id).serialize(),
|
TOTAL_KEY: _get_total_currency_ref(currency_id).serialize(),
|
||||||
|
ALL_TIME_KEY: _get_all_time_currency_ref(currency_id).serialize(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -388,6 +467,12 @@ func _collect_currency_ids_for_save() -> Array[StringName]:
|
|||||||
continue
|
continue
|
||||||
ids.append(normalized_currency_id)
|
ids.append(normalized_currency_id)
|
||||||
|
|
||||||
|
for raw_id in _all_time_currency_acquired.keys():
|
||||||
|
var normalized_currency_id: StringName = _normalize_currency_id(StringName(String(raw_id)))
|
||||||
|
if normalized_currency_id == &"" or ids.has(normalized_currency_id):
|
||||||
|
continue
|
||||||
|
ids.append(normalized_currency_id)
|
||||||
|
|
||||||
for known_currency_id in CurrencyDatabase.get_known_currency_ids():
|
for known_currency_id in CurrencyDatabase.get_known_currency_ids():
|
||||||
var normalized_currency_id: StringName = _normalize_currency_id(known_currency_id)
|
var normalized_currency_id: StringName = _normalize_currency_id(known_currency_id)
|
||||||
if normalized_currency_id == &"" or ids.has(normalized_currency_id):
|
if normalized_currency_id == &"" or ids.has(normalized_currency_id):
|
||||||
@@ -402,6 +487,7 @@ func _initialize_currency_maps() -> void:
|
|||||||
continue
|
continue
|
||||||
_set_current_currency(currency_id, BigNumber.from_float(0.0))
|
_set_current_currency(currency_id, BigNumber.from_float(0.0))
|
||||||
_set_total_currency(currency_id, BigNumber.from_float(0.0))
|
_set_total_currency(currency_id, BigNumber.from_float(0.0))
|
||||||
|
_set_all_time_currency(currency_id, BigNumber.from_float(0.0))
|
||||||
|
|
||||||
func _normalize_currency_id(currency_id: StringName) -> StringName:
|
func _normalize_currency_id(currency_id: StringName) -> StringName:
|
||||||
var normalized: String = String(currency_id).to_lower().strip_edges()
|
var normalized: String = String(currency_id).to_lower().strip_edges()
|
||||||
@@ -435,6 +521,19 @@ func _get_total_currency_ref(currency_id: StringName) -> BigNumber:
|
|||||||
_total_currency_acquired[normalized_currency_id] = fallback
|
_total_currency_acquired[normalized_currency_id] = fallback
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
|
func _get_all_time_currency_ref(currency_id: StringName) -> BigNumber:
|
||||||
|
var normalized_currency_id: StringName = _normalize_currency_id(currency_id)
|
||||||
|
if normalized_currency_id == &"":
|
||||||
|
return BigNumber.from_float(0.0)
|
||||||
|
|
||||||
|
var value: Variant = _all_time_currency_acquired.get(normalized_currency_id)
|
||||||
|
if value is BigNumber:
|
||||||
|
return value
|
||||||
|
|
||||||
|
var fallback: BigNumber = BigNumber.from_float(0.0)
|
||||||
|
_all_time_currency_acquired[normalized_currency_id] = fallback
|
||||||
|
return fallback
|
||||||
|
|
||||||
func _set_current_currency(currency_id: StringName, value: BigNumber) -> void:
|
func _set_current_currency(currency_id: StringName, value: BigNumber) -> void:
|
||||||
var normalized_currency_id: StringName = _normalize_currency_id(currency_id)
|
var normalized_currency_id: StringName = _normalize_currency_id(currency_id)
|
||||||
if normalized_currency_id == &"":
|
if normalized_currency_id == &"":
|
||||||
@@ -447,6 +546,12 @@ func _set_total_currency(currency_id: StringName, value: BigNumber) -> void:
|
|||||||
return
|
return
|
||||||
_total_currency_acquired[normalized_currency_id] = _copy_big_number(value)
|
_total_currency_acquired[normalized_currency_id] = _copy_big_number(value)
|
||||||
|
|
||||||
|
func _set_all_time_currency(currency_id: StringName, value: BigNumber) -> void:
|
||||||
|
var normalized_currency_id: StringName = _normalize_currency_id(currency_id)
|
||||||
|
if normalized_currency_id == &"":
|
||||||
|
return
|
||||||
|
_all_time_currency_acquired[normalized_currency_id] = _copy_big_number(value)
|
||||||
|
|
||||||
func _default_generator_state(unlocked: bool, available: bool, owned: int = 0) -> Dictionary:
|
func _default_generator_state(unlocked: bool, available: bool, owned: int = 0) -> Dictionary:
|
||||||
var owned_value: int = _to_non_negative_int(owned, 0)
|
var owned_value: int = _to_non_negative_int(owned, 0)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ var cycle_progress_seconds: float = 0.0
|
|||||||
var _generator_id: StringName = &""
|
var _generator_id: StringName = &""
|
||||||
## Prevents duplicate state registration in GameState.
|
## Prevents duplicate state registration in GameState.
|
||||||
var _is_registered: bool = false
|
var _is_registered: bool = false
|
||||||
|
## Prevents re-entrant registration while GameState emits synchronous callbacks.
|
||||||
|
var _is_registering: bool = false
|
||||||
|
|
||||||
## Resolves and registers this generator state before gameplay updates.
|
## Resolves and registers this generator state before gameplay updates.
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -127,7 +129,7 @@ func _grant_cycle_income(cycle_count: int) -> void:
|
|||||||
if data == null or cycle_count <= 0:
|
if data == null or cycle_count <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
var effective_run_multiplier: float = run_multiplier * get_automatic_production_multiplier()
|
var effective_run_multiplier: float = get_effective_auto_run_multiplier()
|
||||||
var per_cycle: float = data.production_per_cycle(owned, effective_run_multiplier, purchased_count)
|
var per_cycle: float = data.production_per_cycle(owned, effective_run_multiplier, purchased_count)
|
||||||
if per_cycle <= 0.0:
|
if per_cycle <= 0.0:
|
||||||
return
|
return
|
||||||
@@ -308,6 +310,19 @@ func is_buff_maxed(buff_id: StringName) -> bool:
|
|||||||
func get_automatic_production_multiplier() -> float:
|
func get_automatic_production_multiplier() -> float:
|
||||||
return _get_multiplier_for_buff_kind(GeneratorBuffData.BuffKind.AUTO_PRODUCTION_MULTIPLIER)
|
return _get_multiplier_for_buff_kind(GeneratorBuffData.BuffKind.AUTO_PRODUCTION_MULTIPLIER)
|
||||||
|
|
||||||
|
func get_effective_auto_run_multiplier() -> float:
|
||||||
|
return run_multiplier * get_automatic_production_multiplier() * _get_prestige_multiplier()
|
||||||
|
|
||||||
|
func reset_runtime_state_for_prestige() -> void:
|
||||||
|
_is_registered = false
|
||||||
|
_is_registering = false
|
||||||
|
cycle_progress_seconds = 0.0
|
||||||
|
_remaining_click_cooldown_seconds = 0.0
|
||||||
|
_ensure_registered()
|
||||||
|
_evaluate_generator_unlock_goal(false)
|
||||||
|
_evaluate_buff_unlock_goals()
|
||||||
|
visible = is_available_to_player()
|
||||||
|
|
||||||
func get_manual_click_multiplier() -> float:
|
func get_manual_click_multiplier() -> float:
|
||||||
return _get_multiplier_for_buff_kind(GeneratorBuffData.BuffKind.MANUAL_CLICK_MULTIPLIER)
|
return _get_multiplier_for_buff_kind(GeneratorBuffData.BuffKind.MANUAL_CLICK_MULTIPLIER)
|
||||||
|
|
||||||
@@ -369,6 +384,21 @@ func _get_click_cooldown_seconds() -> float:
|
|||||||
func _grants_click_while_hovering() -> bool:
|
func _grants_click_while_hovering() -> bool:
|
||||||
return grants_click_while_hovering
|
return grants_click_while_hovering
|
||||||
|
|
||||||
|
func _get_prestige_multiplier() -> float:
|
||||||
|
var manager: Node = get_node_or_null("/root/PrestigeManager")
|
||||||
|
if manager == null:
|
||||||
|
return 1.0
|
||||||
|
if not manager.has_method("get_total_multiplier"):
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
var raw_multiplier: Variant = manager.call("get_total_multiplier")
|
||||||
|
if raw_multiplier is float:
|
||||||
|
return maxf(raw_multiplier, 0.0)
|
||||||
|
if raw_multiplier is int:
|
||||||
|
return maxf(float(raw_multiplier), 0.0)
|
||||||
|
|
||||||
|
return 1.0
|
||||||
|
|
||||||
## Returns this generator's resolved state id.
|
## Returns this generator's resolved state id.
|
||||||
func get_generator_id() -> StringName:
|
func get_generator_id() -> StringName:
|
||||||
_ensure_registered()
|
_ensure_registered()
|
||||||
@@ -522,7 +552,7 @@ func _resolve_generator_id() -> StringName:
|
|||||||
|
|
||||||
## Ensures this generator has a state entry in GameState.
|
## Ensures this generator has a state entry in GameState.
|
||||||
func _ensure_registered() -> void:
|
func _ensure_registered() -> void:
|
||||||
if _is_registered:
|
if _is_registered or _is_registering:
|
||||||
return
|
return
|
||||||
|
|
||||||
if _generator_id == &"":
|
if _generator_id == &"":
|
||||||
@@ -530,6 +560,7 @@ func _ensure_registered() -> void:
|
|||||||
if _generator_id == &"":
|
if _generator_id == &"":
|
||||||
return
|
return
|
||||||
|
|
||||||
|
_is_registering = true
|
||||||
GameState.register_generator(
|
GameState.register_generator(
|
||||||
_generator_id,
|
_generator_id,
|
||||||
_default_unlocked_state(),
|
_default_unlocked_state(),
|
||||||
@@ -538,6 +569,7 @@ func _ensure_registered() -> void:
|
|||||||
)
|
)
|
||||||
_register_buffs()
|
_register_buffs()
|
||||||
_is_registered = true
|
_is_registered = true
|
||||||
|
_is_registering = false
|
||||||
|
|
||||||
func _register_buffs() -> void:
|
func _register_buffs() -> void:
|
||||||
for buff in get_buffs():
|
for buff in get_buffs():
|
||||||
@@ -632,4 +664,4 @@ func _on_area_2d_mouse_exited() -> void:
|
|||||||
|
|
||||||
func _on_generated_state_changed(generator_id: StringName, _state: Dictionary) -> void:
|
func _on_generated_state_changed(generator_id: StringName, _state: Dictionary) -> void:
|
||||||
if generator_id == _generator_id:
|
if generator_id == _generator_id:
|
||||||
visible = true
|
visible = is_available_to_player()
|
||||||
|
|||||||
179
core/prestige/TECH_SPEC.md
Normal file
179
core/prestige/TECH_SPEC.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Prestige System - Technical Specification
|
||||||
|
|
||||||
|
## Document Status
|
||||||
|
1. Version: 1.0
|
||||||
|
2. Date: 2026-03-21
|
||||||
|
3. Scope: Add a configurable prestige reset loop with editor-authored curve math and permanent run multipliers.
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
The prototype currently has growth, buffs, and unlock goals, but no reset loop that converts late-run progress into long-term power.
|
||||||
|
Without prestige, the economy has no designed "ladder climb" cycle and balancing eventually relies only on direct cost/production tuning.
|
||||||
|
|
||||||
|
## Design Goals
|
||||||
|
1. Support a prestige reset that grants a permanent currency based on player progress.
|
||||||
|
2. Keep prestige math data-driven and editable in the Godot inspector for balancing iteration.
|
||||||
|
3. Support both major prestige archetypes from Kongregate's analysis:
|
||||||
|
- Lifetime or max-stat based curves (diminishing gains when resetting at the same point repeatedly).
|
||||||
|
- Since-reset/run-only curves (same run depth gives similar rewards each reset).
|
||||||
|
4. Integrate with existing systems (`GameState`, `BigNumber`, generator/buff runtime) without hardcoding one formula.
|
||||||
|
5. Persist all prestige state in `user://idle_save.json` alongside existing runtime data.
|
||||||
|
|
||||||
|
## Non-Goals (For First Iteration)
|
||||||
|
1. Multiple prestige currencies.
|
||||||
|
2. Prestige skill tree/shop UI.
|
||||||
|
3. Offline simulation changes beyond applying the resulting permanent multiplier.
|
||||||
|
|
||||||
|
## Kongregate-Informed Curve Model
|
||||||
|
From "The Math of Idle Games, Part III", prestige formulas can be modeled as transformed progress stats with fractional exponents.
|
||||||
|
|
||||||
|
Core variables:
|
||||||
|
1. `delta_p`: prestige currency gained on reset.
|
||||||
|
2. `p`: total prestige currency owned.
|
||||||
|
3. `c_L`: lifetime currency earned.
|
||||||
|
4. `c_M`: max currency reached in a run.
|
||||||
|
5. `c_R`: currency earned since last reset.
|
||||||
|
|
||||||
|
Supported formula families for this system:
|
||||||
|
1. Power curve (generic): `f(x) = scale * (x / threshold)^exponent`.
|
||||||
|
2. Triangular inverse sqrt (Realm Grinder style): solve `x = threshold * p(p+1)/2` for `p`.
|
||||||
|
3. Log-like compression (future option, disabled by default).
|
||||||
|
|
||||||
|
Behavior groups:
|
||||||
|
1. Cumulative-stat formulas (`c_L`, `c_M`) compute target total prestige then grant `target_total - current_total`.
|
||||||
|
2. Run-only formulas (`c_R`) compute direct gain per run.
|
||||||
|
|
||||||
|
## Proposed Runtime Architecture
|
||||||
|
1. Add `PrestigeManager` autoload (`core/prestige/prestige_manager.gd`) as the single authority for prestige state and reset flow.
|
||||||
|
2. Add `PrestigeConfig` resource (`core/prestige/prestige_config.gd`) authored in inspector and loaded from data assets.
|
||||||
|
3. `GameState` remains owner of currencies/generator state; `PrestigeManager` orchestrates reset and applies permanent multiplier.
|
||||||
|
4. Generators read the active prestige multiplier through one accessor (`PrestigeManager.get_total_multiplier()`), then multiply existing `run_multiplier` behavior.
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
### PrestigeConfig (Resource)
|
||||||
|
Editor-authored resource fields (all exported):
|
||||||
|
1. `id: StringName`.
|
||||||
|
2. `display_name: String`.
|
||||||
|
3. `prestige_currency_id: StringName` (logical id, not regular wallet currency).
|
||||||
|
4. `source_currency_id: StringName` (which gameplay currency powers prestige calculation).
|
||||||
|
5. `basis: BasisType` enum:
|
||||||
|
- `LIFETIME_TOTAL`
|
||||||
|
- `RUN_TOTAL`
|
||||||
|
- `RUN_MAX`
|
||||||
|
6. `formula: FormulaType` enum:
|
||||||
|
- `POWER`
|
||||||
|
- `TRIANGULAR_INVERSE`
|
||||||
|
7. `threshold_mantissa: float` and `threshold_exponent: int` (BigNumber-like threshold).
|
||||||
|
8. `scale: float`.
|
||||||
|
9. `exponent: float` (used by `POWER`).
|
||||||
|
10. `flat_bonus: float`.
|
||||||
|
11. `minimum_gain: int`.
|
||||||
|
12. `rounding_mode: RoundingMode` enum (`FLOOR`, `ROUND`, `CEIL`).
|
||||||
|
13. `allow_prestige_without_gain: bool`.
|
||||||
|
14. Multiplier tuning:
|
||||||
|
- `multiplier_mode: MultiplierMode` enum (`ADDITIVE`, `MULTIPLICATIVE_POWER`).
|
||||||
|
- `base_multiplier: float`.
|
||||||
|
- `multiplier_per_prestige: float`.
|
||||||
|
- `multiplier_exponent: float`.
|
||||||
|
|
||||||
|
### Prestige Runtime State
|
||||||
|
Persisted by `PrestigeManager`:
|
||||||
|
1. `total_prestige_earned: BigNumber`.
|
||||||
|
2. `current_prestige_unspent: BigNumber`.
|
||||||
|
3. `prestige_resets_count: int`.
|
||||||
|
4. `run_start_total_source_currency: BigNumber`.
|
||||||
|
5. `run_peak_source_currency: BigNumber`.
|
||||||
|
6. `last_reset_time: int`.
|
||||||
|
|
||||||
|
## Formula Specification
|
||||||
|
|
||||||
|
### 1) Basis Value Extraction
|
||||||
|
Given configured `source_currency_id`:
|
||||||
|
1. `LIFETIME_TOTAL`: `basis_value = GameState.get_total_currency_acquired_by_id(source_currency_id)`.
|
||||||
|
2. `RUN_TOTAL`: `basis_value = lifetime_now - run_start_total_source_currency`.
|
||||||
|
3. `RUN_MAX`: `basis_value = run_peak_source_currency`.
|
||||||
|
|
||||||
|
### 2) Raw Gain
|
||||||
|
1. POWER: `raw = scale * pow(basis_value / threshold, exponent) + flat_bonus`.
|
||||||
|
2. TRIANGULAR_INVERSE:
|
||||||
|
- `k = basis_value / threshold`
|
||||||
|
- `target = (sqrt(1 + 8k) - 1) / 2`
|
||||||
|
- if basis is cumulative (`LIFETIME_TOTAL`, `RUN_MAX` interpreted as cumulative target), `raw = target - total_prestige_earned`
|
||||||
|
- if basis is run-only, `raw = target`
|
||||||
|
|
||||||
|
### 3) Post-Processing
|
||||||
|
1. Clamp negative to zero.
|
||||||
|
2. Apply configured rounding.
|
||||||
|
3. Enforce `minimum_gain` when basis passed threshold and gain > 0.
|
||||||
|
|
||||||
|
### 4) Multiplier Application
|
||||||
|
`get_total_multiplier()` returns one of:
|
||||||
|
1. Additive mode: `base_multiplier + total_prestige_earned * multiplier_per_prestige`.
|
||||||
|
2. Multiplicative power mode: `base_multiplier * pow(1 + multiplier_per_prestige, pow(total_prestige_earned, multiplier_exponent))`.
|
||||||
|
|
||||||
|
Implementation note: compute large-number powers via `log10` transforms against `BigNumber` to avoid float overflow.
|
||||||
|
|
||||||
|
## Reset Flow
|
||||||
|
1. Player presses prestige button.
|
||||||
|
2. `PrestigeManager.calculate_pending_gain()` computes gain from configured curve.
|
||||||
|
3. If gain is zero and `allow_prestige_without_gain` is false, block reset.
|
||||||
|
4. On confirm:
|
||||||
|
- Add gain to `total_prestige_earned` and `current_prestige_unspent`.
|
||||||
|
- Increment `prestige_resets_count`.
|
||||||
|
- Reset runtime progression (`GameState` currencies, generator owned counts, generator availability that should restart locked).
|
||||||
|
- Preserve permanent unlocks only if explicitly marked as persistent in future extensions.
|
||||||
|
- Reinitialize run-tracking values (`run_start_total_source_currency`, `run_peak_source_currency`).
|
||||||
|
- Emit `prestige_performed(gain, total_prestige)` signal.
|
||||||
|
|
||||||
|
## GameState Integration
|
||||||
|
1. Add APIs for reset-safe state initialization (single call from `PrestigeManager` instead of duplicating clear logic).
|
||||||
|
2. Keep total-acquired currency behavior explicit:
|
||||||
|
- Option A (recommended): reset current currencies and generator ownership, keep lifetime totals for `LIFETIME_TOTAL` basis.
|
||||||
|
- Option B: clear totals too and rely on dedicated lifetime accumulator in prestige state.
|
||||||
|
3. Ensure generator/buff registrations are idempotent after reset (`_ensure_registered` paths already support this pattern).
|
||||||
|
|
||||||
|
## Editor Authoring And Balancing Workflow
|
||||||
|
1. Create `idles/prestige/primary_prestige.tres` using `PrestigeConfig`.
|
||||||
|
2. Designers tune only exported fields in inspector (no code edits).
|
||||||
|
3. Provide presets matching Kongregate article examples:
|
||||||
|
- Realm Grinder-like: `basis=RUN_MAX`, `formula=TRIANGULAR_INVERSE`, `threshold=1e12`.
|
||||||
|
- AdCap-like: `basis=LIFETIME_TOTAL`, `formula=POWER`, `scale=150`, `threshold=1e15`, `exponent=0.5`.
|
||||||
|
- Cookie Clicker-like: `basis=LIFETIME_TOTAL`, `formula=POWER`, `threshold=1e12`, `exponent=0.333333`.
|
||||||
|
- Egg Inc-like: `basis=RUN_TOTAL`, `formula=POWER`, `threshold=1e6`, `exponent=0.14`.
|
||||||
|
4. Balance target guidance:
|
||||||
|
- For sqrt-like curves, doubling prestige generally needs roughly 3x-4x deeper runs.
|
||||||
|
- For cube-root curves, doubling prestige tends toward ~8x deeper runs.
|
||||||
|
- For ~1/7 exponent run curves, doubling prestige needs ~128x deeper runs.
|
||||||
|
|
||||||
|
## UI Requirements
|
||||||
|
1. Prestige panel must display:
|
||||||
|
- current prestige total
|
||||||
|
- pending gain (`delta_p`)
|
||||||
|
- resulting multiplier after reset
|
||||||
|
- basis stat currently used (`c_L`, `c_R`, or `c_M` equivalent)
|
||||||
|
2. Reset CTA disabled when pending gain is zero and config disallows zero-gain resets.
|
||||||
|
3. Confirmation dialog explicitly lists what resets and what is kept.
|
||||||
|
|
||||||
|
## Persistence
|
||||||
|
1. Extend save payload with `prestige_state` object.
|
||||||
|
2. Serialize all `BigNumber` values with existing `BigNumber.serialize()` pattern.
|
||||||
|
3. During load, validate dictionaries and apply defaults if fields are missing.
|
||||||
|
4. Maintain backward compatibility with pre-prestige saves by creating default prestige state when absent.
|
||||||
|
|
||||||
|
## Validation Plan
|
||||||
|
1. Headless parse: `"$GODOT_BIN" --headless --path . --quit`.
|
||||||
|
2. Script checks once implemented:
|
||||||
|
- `"$GODOT_BIN" --headless --check-only --script res://core/prestige/prestige_manager.gd`
|
||||||
|
- `"$GODOT_BIN" --headless --check-only --script res://core/prestige/prestige_config.gd`
|
||||||
|
3. Manual smoke cases:
|
||||||
|
- same reset point in cumulative mode yields reduced/zero incremental gain.
|
||||||
|
- same reset point in run-only mode yields roughly same gain.
|
||||||
|
- multiplier updates production immediately after reset.
|
||||||
|
- prestige state survives save/load.
|
||||||
|
|
||||||
|
## Implementation Milestones
|
||||||
|
1. Introduce data classes (`PrestigeConfig`) and one authored `.tres` profile.
|
||||||
|
2. Implement `PrestigeManager` with gain math + persistence.
|
||||||
|
3. Add reset integration entrypoint in `GameState`.
|
||||||
|
4. Wire generator production multiplier to prestige.
|
||||||
|
5. Add initial prestige UI panel and confirmation flow.
|
||||||
77
core/prestige/prestige_config.gd
Normal file
77
core/prestige/prestige_config.gd
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
class_name PrestigeConfig
|
||||||
|
extends Resource
|
||||||
|
|
||||||
|
enum BasisType {
|
||||||
|
LIFETIME_TOTAL,
|
||||||
|
RUN_TOTAL,
|
||||||
|
RUN_MAX,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FormulaType {
|
||||||
|
POWER,
|
||||||
|
TRIANGULAR_INVERSE,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RoundingMode {
|
||||||
|
FLOOR,
|
||||||
|
ROUND,
|
||||||
|
CEIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MultiplierMode {
|
||||||
|
ADDITIVE,
|
||||||
|
MULTIPLICATIVE_POWER,
|
||||||
|
}
|
||||||
|
|
||||||
|
@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"
|
||||||
|
@export var basis: BasisType = BasisType.LIFETIME_TOTAL
|
||||||
|
@export var formula: FormulaType = FormulaType.POWER
|
||||||
|
@export var threshold_mantissa: float = 1.0
|
||||||
|
@export var threshold_exponent: int = 4
|
||||||
|
@export var scale: float = 1.0
|
||||||
|
@export var exponent: float = 0.5
|
||||||
|
@export var flat_bonus: float = 0.0
|
||||||
|
@export var minimum_gain: int = 0
|
||||||
|
@export var rounding_mode: RoundingMode = RoundingMode.FLOOR
|
||||||
|
@export var allow_prestige_without_gain: bool = false
|
||||||
|
@export var multiplier_mode: MultiplierMode = MultiplierMode.ADDITIVE
|
||||||
|
@export var base_multiplier: float = 1.0
|
||||||
|
@export var multiplier_per_prestige: float = 0.05
|
||||||
|
@export var multiplier_exponent: float = 1.0
|
||||||
|
|
||||||
|
func get_threshold() -> BigNumber:
|
||||||
|
return BigNumber.new(threshold_mantissa, threshold_exponent)
|
||||||
|
|
||||||
|
func is_valid() -> bool:
|
||||||
|
if String(id).strip_edges().is_empty():
|
||||||
|
return false
|
||||||
|
if String(source_currency_id).strip_edges().is_empty():
|
||||||
|
return false
|
||||||
|
if threshold_mantissa <= 0.0:
|
||||||
|
return false
|
||||||
|
if scale < 0.0:
|
||||||
|
return false
|
||||||
|
if formula == FormulaType.POWER and exponent <= 0.0:
|
||||||
|
return false
|
||||||
|
if base_multiplier < 0.0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
func is_cumulative_basis() -> bool:
|
||||||
|
return basis != BasisType.RUN_TOTAL
|
||||||
|
|
||||||
|
func round_value(value: float) -> float:
|
||||||
|
if not is_finite(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
match rounding_mode:
|
||||||
|
RoundingMode.CEIL:
|
||||||
|
return ceilf(value)
|
||||||
|
RoundingMode.ROUND:
|
||||||
|
return roundf(value)
|
||||||
|
_:
|
||||||
|
return floorf(value)
|
||||||
1
core/prestige/prestige_config.gd.uid
Normal file
1
core/prestige/prestige_config.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://w34t58rpkoui
|
||||||
416
core/prestige/prestige_manager.gd
Normal file
416
core/prestige/prestige_manager.gd
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
signal prestige_state_changed(total_prestige: BigNumber, pending_gain: BigNumber)
|
||||||
|
signal prestige_performed(gain: BigNumber, total_prestige: BigNumber)
|
||||||
|
|
||||||
|
const SAVE_KEY: String = "prestige_state"
|
||||||
|
const TOTAL_PRESTIGE_KEY: String = "total_prestige_earned"
|
||||||
|
const CURRENT_PRESTIGE_UNSPENT_KEY: String = "current_prestige_unspent"
|
||||||
|
const PRESTIGE_RESETS_COUNT_KEY: String = "prestige_resets_count"
|
||||||
|
const RUN_START_TOTAL_SOURCE_KEY: String = "run_start_total_source_currency"
|
||||||
|
const RUN_PEAK_SOURCE_KEY: String = "run_peak_source_currency"
|
||||||
|
const LAST_RESET_TIME_KEY: String = "last_reset_time"
|
||||||
|
|
||||||
|
const BASIS_LIFETIME_TOTAL: int = 0
|
||||||
|
const BASIS_RUN_TOTAL: int = 1
|
||||||
|
const BASIS_RUN_MAX: int = 2
|
||||||
|
|
||||||
|
const FORMULA_POWER: int = 0
|
||||||
|
const FORMULA_TRIANGULAR_INVERSE: int = 1
|
||||||
|
|
||||||
|
const MULTIPLIER_MODE_ADDITIVE: int = 0
|
||||||
|
const MULTIPLIER_MODE_MULTIPLICATIVE_POWER: int = 1
|
||||||
|
|
||||||
|
@export var config: Resource
|
||||||
|
|
||||||
|
var total_prestige_earned: BigNumber = BigNumber.from_float(0.0)
|
||||||
|
var current_prestige_unspent: BigNumber = BigNumber.from_float(0.0)
|
||||||
|
var prestige_resets_count: int = 0
|
||||||
|
var run_start_total_source_currency: BigNumber = BigNumber.from_float(0.0)
|
||||||
|
var run_peak_source_currency: BigNumber = BigNumber.from_float(0.0)
|
||||||
|
var last_reset_time: int = 0
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if config == null:
|
||||||
|
config = load("res://idles/prestige/primary_prestige.tres") as Resource
|
||||||
|
|
||||||
|
if not _is_config_valid():
|
||||||
|
push_warning("PrestigeManager has invalid or missing config; prestige is disabled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
var loaded_state: Dictionary = GameState.get_external_save_data(SAVE_KEY)
|
||||||
|
if loaded_state.is_empty():
|
||||||
|
_initialize_run_tracking_from_current_state()
|
||||||
|
else:
|
||||||
|
_deserialize_state(loaded_state)
|
||||||
|
_validate_loaded_run_tracking()
|
||||||
|
_save_state_to_game_state()
|
||||||
|
|
||||||
|
GameState.currency_changed.connect(_on_currency_changed)
|
||||||
|
prestige_state_changed.emit(get_total_prestige(), calculate_pending_gain())
|
||||||
|
|
||||||
|
func get_source_currency_id() -> StringName:
|
||||||
|
if not _is_config_valid():
|
||||||
|
return &""
|
||||||
|
|
||||||
|
return _normalize_currency_id(_get_config_string_name("source_currency_id", &""))
|
||||||
|
|
||||||
|
func get_total_prestige() -> BigNumber:
|
||||||
|
return _copy_big_number(total_prestige_earned)
|
||||||
|
|
||||||
|
func get_current_prestige_unspent() -> BigNumber:
|
||||||
|
return _copy_big_number(current_prestige_unspent)
|
||||||
|
|
||||||
|
func get_total_multiplier() -> float:
|
||||||
|
if not _is_config_valid():
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
var base: float = maxf(_get_config_float("base_multiplier", 1.0), 0.0)
|
||||||
|
var prestige_value: float = _big_number_to_float(total_prestige_earned)
|
||||||
|
if prestige_value <= 0.0:
|
||||||
|
return base
|
||||||
|
|
||||||
|
match _get_config_int("multiplier_mode", MULTIPLIER_MODE_ADDITIVE):
|
||||||
|
MULTIPLIER_MODE_MULTIPLICATIVE_POWER:
|
||||||
|
var growth_base: float = 1.0 + maxf(_get_config_float("multiplier_per_prestige", 0.0), 0.0)
|
||||||
|
if growth_base <= 0.0:
|
||||||
|
return base
|
||||||
|
|
||||||
|
var effective_power: float = pow(prestige_value, maxf(_get_config_float("multiplier_exponent", 1.0), 0.0))
|
||||||
|
var mult: float = base * pow(growth_base, effective_power)
|
||||||
|
if not is_finite(mult):
|
||||||
|
return INF
|
||||||
|
return maxf(mult, 0.0)
|
||||||
|
_:
|
||||||
|
return maxf(base + prestige_value * maxf(_get_config_float("multiplier_per_prestige", 0.0), 0.0), 0.0)
|
||||||
|
|
||||||
|
func calculate_pending_gain() -> BigNumber:
|
||||||
|
if not _is_config_valid():
|
||||||
|
return BigNumber.from_float(0.0)
|
||||||
|
|
||||||
|
var basis_value: BigNumber = _get_basis_value()
|
||||||
|
if basis_value.mantissa <= 0.0:
|
||||||
|
return BigNumber.from_float(0.0)
|
||||||
|
|
||||||
|
var raw_gain: float = _calculate_raw_gain_float(basis_value)
|
||||||
|
if not is_finite(raw_gain):
|
||||||
|
raw_gain = 0.0
|
||||||
|
|
||||||
|
raw_gain = maxf(raw_gain, 0.0)
|
||||||
|
var rounded_gain: float = _round_gain(raw_gain)
|
||||||
|
if not is_finite(rounded_gain):
|
||||||
|
rounded_gain = 0.0
|
||||||
|
|
||||||
|
rounded_gain = maxf(rounded_gain, 0.0)
|
||||||
|
if rounded_gain > 0.0:
|
||||||
|
rounded_gain = maxf(rounded_gain, float(maxi(_get_config_int("minimum_gain", 0), 0)))
|
||||||
|
|
||||||
|
return BigNumber.from_float(rounded_gain)
|
||||||
|
|
||||||
|
func can_prestige() -> bool:
|
||||||
|
if not _is_config_valid():
|
||||||
|
return false
|
||||||
|
|
||||||
|
var pending: BigNumber = calculate_pending_gain()
|
||||||
|
if pending.mantissa > 0.0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
return _get_config_bool("allow_prestige_without_gain", false)
|
||||||
|
|
||||||
|
func perform_prestige() -> bool:
|
||||||
|
if not can_prestige():
|
||||||
|
return false
|
||||||
|
|
||||||
|
var gain: BigNumber = calculate_pending_gain()
|
||||||
|
if gain.mantissa > 0.0:
|
||||||
|
total_prestige_earned.add_in_place(gain)
|
||||||
|
current_prestige_unspent.add_in_place(gain)
|
||||||
|
|
||||||
|
prestige_resets_count += 1
|
||||||
|
last_reset_time = Time.get_unix_time_from_system()
|
||||||
|
|
||||||
|
GameState.reset_for_prestige(true)
|
||||||
|
_reinitialize_generators_after_prestige_reset()
|
||||||
|
_initialize_run_tracking_from_current_state()
|
||||||
|
_save_state_to_game_state()
|
||||||
|
GameState.save_game()
|
||||||
|
|
||||||
|
var total: BigNumber = get_total_prestige()
|
||||||
|
prestige_performed.emit(gain, total)
|
||||||
|
prestige_state_changed.emit(total, calculate_pending_gain())
|
||||||
|
return true
|
||||||
|
|
||||||
|
func get_basis_value_for_display() -> BigNumber:
|
||||||
|
return _get_basis_value()
|
||||||
|
|
||||||
|
func get_basis_label() -> String:
|
||||||
|
if not _is_config_valid():
|
||||||
|
return "-"
|
||||||
|
|
||||||
|
match _get_config_int("basis", BASIS_LIFETIME_TOTAL):
|
||||||
|
BASIS_RUN_TOTAL:
|
||||||
|
return "Run Total"
|
||||||
|
BASIS_RUN_MAX:
|
||||||
|
return "Run Max"
|
||||||
|
_:
|
||||||
|
return "Lifetime Total"
|
||||||
|
|
||||||
|
func _on_currency_changed(changed_currency_id: StringName, new_amount: BigNumber) -> void:
|
||||||
|
if config == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var source_currency_id: StringName = get_source_currency_id()
|
||||||
|
if source_currency_id == &"":
|
||||||
|
return
|
||||||
|
if changed_currency_id != source_currency_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
if new_amount.is_greater_than(run_peak_source_currency):
|
||||||
|
run_peak_source_currency = _copy_big_number(new_amount)
|
||||||
|
|
||||||
|
_save_state_to_game_state()
|
||||||
|
prestige_state_changed.emit(get_total_prestige(), calculate_pending_gain())
|
||||||
|
|
||||||
|
func _calculate_raw_gain_float(basis_value: BigNumber) -> float:
|
||||||
|
if config == null:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
match _get_config_int("formula", FORMULA_POWER):
|
||||||
|
FORMULA_TRIANGULAR_INVERSE:
|
||||||
|
var threshold_value: float = _big_number_to_float(_get_config_threshold())
|
||||||
|
if threshold_value <= 0.0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
var basis_float: float = _big_number_to_float(basis_value)
|
||||||
|
if basis_float <= 0.0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
var k: float = basis_float / threshold_value
|
||||||
|
if k <= 0.0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
var target_total: float = (sqrt(1.0 + 8.0 * k) - 1.0) * 0.5
|
||||||
|
if _is_cumulative_basis():
|
||||||
|
return target_total - _big_number_to_float(total_prestige_earned)
|
||||||
|
return target_total
|
||||||
|
_:
|
||||||
|
var ratio: float = _big_number_ratio_to_float(basis_value, _get_config_threshold())
|
||||||
|
if ratio <= 0.0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
var value: float = maxf(_get_config_float("scale", 0.0), 0.0) * pow(ratio, maxf(_get_config_float("exponent", 0.0001), 0.0001)) + _get_config_float("flat_bonus", 0.0)
|
||||||
|
if _is_cumulative_basis():
|
||||||
|
return value - _big_number_to_float(total_prestige_earned)
|
||||||
|
return value
|
||||||
|
|
||||||
|
func _get_basis_value() -> BigNumber:
|
||||||
|
if config == null:
|
||||||
|
return BigNumber.from_float(0.0)
|
||||||
|
|
||||||
|
var source_currency_id: StringName = get_source_currency_id()
|
||||||
|
if source_currency_id == &"":
|
||||||
|
return BigNumber.from_float(0.0)
|
||||||
|
|
||||||
|
match _get_config_int("basis", BASIS_LIFETIME_TOTAL):
|
||||||
|
BASIS_RUN_TOTAL:
|
||||||
|
var lifetime_now: BigNumber = GameState.get_total_currency_acquired_by_id(source_currency_id)
|
||||||
|
var run_total: BigNumber = lifetime_now.subtract(run_start_total_source_currency)
|
||||||
|
if run_total.mantissa < 0.0:
|
||||||
|
return BigNumber.from_float(0.0)
|
||||||
|
return run_total
|
||||||
|
BASIS_RUN_MAX:
|
||||||
|
return _copy_big_number(run_peak_source_currency)
|
||||||
|
_:
|
||||||
|
return _copy_big_number(GameState.get_total_currency_acquired_by_id(source_currency_id))
|
||||||
|
|
||||||
|
func _initialize_run_tracking_from_current_state() -> void:
|
||||||
|
if config == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var source_currency_id: StringName = get_source_currency_id()
|
||||||
|
if source_currency_id == &"":
|
||||||
|
run_start_total_source_currency = BigNumber.from_float(0.0)
|
||||||
|
run_peak_source_currency = BigNumber.from_float(0.0)
|
||||||
|
return
|
||||||
|
|
||||||
|
run_start_total_source_currency = _copy_big_number(GameState.get_total_currency_acquired_by_id(source_currency_id))
|
||||||
|
run_peak_source_currency = _copy_big_number(GameState.get_currency_amount_by_id(source_currency_id))
|
||||||
|
_save_state_to_game_state()
|
||||||
|
|
||||||
|
func _validate_loaded_run_tracking() -> void:
|
||||||
|
if config == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var source_currency_id: StringName = get_source_currency_id()
|
||||||
|
if source_currency_id == &"":
|
||||||
|
run_start_total_source_currency = BigNumber.from_float(0.0)
|
||||||
|
run_peak_source_currency = BigNumber.from_float(0.0)
|
||||||
|
return
|
||||||
|
|
||||||
|
var current_currency: BigNumber = GameState.get_currency_amount_by_id(source_currency_id)
|
||||||
|
if current_currency.is_greater_than(run_peak_source_currency):
|
||||||
|
run_peak_source_currency = _copy_big_number(current_currency)
|
||||||
|
|
||||||
|
var lifetime_now: BigNumber = GameState.get_total_currency_acquired_by_id(source_currency_id)
|
||||||
|
if run_start_total_source_currency.is_greater_than(lifetime_now):
|
||||||
|
run_start_total_source_currency = _copy_big_number(lifetime_now)
|
||||||
|
|
||||||
|
func _serialize_state() -> Dictionary:
|
||||||
|
return {
|
||||||
|
TOTAL_PRESTIGE_KEY: total_prestige_earned.serialize(),
|
||||||
|
CURRENT_PRESTIGE_UNSPENT_KEY: current_prestige_unspent.serialize(),
|
||||||
|
PRESTIGE_RESETS_COUNT_KEY: maxi(prestige_resets_count, 0),
|
||||||
|
RUN_START_TOTAL_SOURCE_KEY: run_start_total_source_currency.serialize(),
|
||||||
|
RUN_PEAK_SOURCE_KEY: run_peak_source_currency.serialize(),
|
||||||
|
LAST_RESET_TIME_KEY: maxi(last_reset_time, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
func _deserialize_state(raw_state: Dictionary) -> void:
|
||||||
|
total_prestige_earned = _deserialize_big_number(raw_state.get(TOTAL_PRESTIGE_KEY, {}))
|
||||||
|
current_prestige_unspent = _deserialize_big_number(raw_state.get(CURRENT_PRESTIGE_UNSPENT_KEY, {}))
|
||||||
|
prestige_resets_count = _to_non_negative_int(raw_state.get(PRESTIGE_RESETS_COUNT_KEY, 0), 0)
|
||||||
|
run_start_total_source_currency = _deserialize_big_number(raw_state.get(RUN_START_TOTAL_SOURCE_KEY, {}))
|
||||||
|
run_peak_source_currency = _deserialize_big_number(raw_state.get(RUN_PEAK_SOURCE_KEY, {}))
|
||||||
|
last_reset_time = _to_non_negative_int(raw_state.get(LAST_RESET_TIME_KEY, 0), 0)
|
||||||
|
|
||||||
|
if current_prestige_unspent.is_greater_than(total_prestige_earned):
|
||||||
|
current_prestige_unspent = _copy_big_number(total_prestige_earned)
|
||||||
|
|
||||||
|
func _save_state_to_game_state() -> void:
|
||||||
|
GameState.set_external_save_data(SAVE_KEY, _serialize_state())
|
||||||
|
|
||||||
|
func _reinitialize_generators_after_prestige_reset() -> void:
|
||||||
|
var scene_root: Node = get_tree().current_scene
|
||||||
|
if scene_root == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var generator_nodes: Array[Node] = scene_root.find_children("*", "CurrencyGenerator", true, false)
|
||||||
|
for generator_node in generator_nodes:
|
||||||
|
if generator_node == null:
|
||||||
|
continue
|
||||||
|
if not generator_node.has_method("reset_runtime_state_for_prestige"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
generator_node.call("reset_runtime_state_for_prestige")
|
||||||
|
|
||||||
|
func _deserialize_big_number(raw_value: Variant) -> BigNumber:
|
||||||
|
if raw_value is Dictionary:
|
||||||
|
return BigNumber.deserialize(raw_value)
|
||||||
|
return BigNumber.from_float(0.0)
|
||||||
|
|
||||||
|
func _copy_big_number(value: BigNumber) -> BigNumber:
|
||||||
|
return BigNumber.new(value.mantissa, value.exponent)
|
||||||
|
|
||||||
|
func _big_number_ratio_to_float(a: BigNumber, b: BigNumber) -> float:
|
||||||
|
if b.mantissa <= 0.0:
|
||||||
|
return 0.0
|
||||||
|
if a.mantissa <= 0.0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
var exponent_delta: int = a.exponent - b.exponent
|
||||||
|
if exponent_delta > 308:
|
||||||
|
return INF
|
||||||
|
if exponent_delta < -308:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
return (a.mantissa / b.mantissa) * pow(10.0, float(exponent_delta))
|
||||||
|
|
||||||
|
func _big_number_to_float(value: BigNumber) -> float:
|
||||||
|
if value.mantissa <= 0.0:
|
||||||
|
return 0.0
|
||||||
|
if value.exponent > 308:
|
||||||
|
return INF
|
||||||
|
if value.exponent < -308:
|
||||||
|
return 0.0
|
||||||
|
return value.mantissa * pow(10.0, float(value.exponent))
|
||||||
|
|
||||||
|
func _normalize_currency_id(currency_id: StringName) -> StringName:
|
||||||
|
var normalized: String = String(currency_id).to_lower().strip_edges()
|
||||||
|
if normalized.is_empty():
|
||||||
|
return &""
|
||||||
|
return StringName(normalized)
|
||||||
|
|
||||||
|
func _to_non_negative_int(value: Variant, fallback: int = 0) -> int:
|
||||||
|
if value is int:
|
||||||
|
return maxi(value, 0)
|
||||||
|
if value is float:
|
||||||
|
return maxi(int(value), 0)
|
||||||
|
return maxi(fallback, 0)
|
||||||
|
|
||||||
|
func _is_config_valid() -> bool:
|
||||||
|
if config == null:
|
||||||
|
return false
|
||||||
|
if not config.has_method("is_valid"):
|
||||||
|
return false
|
||||||
|
|
||||||
|
return bool(config.call("is_valid"))
|
||||||
|
|
||||||
|
func _is_cumulative_basis() -> bool:
|
||||||
|
if config != null and config.has_method("is_cumulative_basis"):
|
||||||
|
return bool(config.call("is_cumulative_basis"))
|
||||||
|
|
||||||
|
return _get_config_int("basis", BASIS_LIFETIME_TOTAL) != BASIS_RUN_TOTAL
|
||||||
|
|
||||||
|
func _round_gain(value: float) -> float:
|
||||||
|
if config != null and config.has_method("round_value"):
|
||||||
|
var rounded: Variant = config.call("round_value", value)
|
||||||
|
if rounded is float:
|
||||||
|
return rounded
|
||||||
|
if rounded is int:
|
||||||
|
return float(rounded)
|
||||||
|
|
||||||
|
return floorf(value)
|
||||||
|
|
||||||
|
func _get_config_threshold() -> BigNumber:
|
||||||
|
if config != null and config.has_method("get_threshold"):
|
||||||
|
var threshold: Variant = config.call("get_threshold")
|
||||||
|
if threshold is BigNumber:
|
||||||
|
return threshold
|
||||||
|
|
||||||
|
return BigNumber.from_float(1.0)
|
||||||
|
|
||||||
|
func _get_config_string_name(property_name: String, fallback: StringName = &"") -> StringName:
|
||||||
|
if config == null:
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
var value: Variant = config.get(property_name)
|
||||||
|
if value is StringName:
|
||||||
|
return value
|
||||||
|
if value is String:
|
||||||
|
return StringName(String(value))
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
func _get_config_int(property_name: String, fallback: int = 0) -> int:
|
||||||
|
if config == null:
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
var value: Variant = config.get(property_name)
|
||||||
|
if value is int:
|
||||||
|
return value
|
||||||
|
if value is float:
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
func _get_config_float(property_name: String, fallback: float = 0.0) -> float:
|
||||||
|
if config == null:
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
var value: Variant = config.get(property_name)
|
||||||
|
if value is float:
|
||||||
|
return value
|
||||||
|
if value is int:
|
||||||
|
return float(value)
|
||||||
|
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
func _get_config_bool(property_name: String, fallback: bool = false) -> bool:
|
||||||
|
if config == null:
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
var value: Variant = config.get(property_name)
|
||||||
|
if value is bool:
|
||||||
|
return value
|
||||||
|
|
||||||
|
return fallback
|
||||||
1
core/prestige/prestige_manager.gd.uid
Normal file
1
core/prestige/prestige_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://srkiu4qe8s2m
|
||||||
90
core/prestige/prestige_panel.gd
Normal file
90
core/prestige/prestige_panel.gd
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
class_name PrestigePanel
|
||||||
|
extends PanelContainer
|
||||||
|
|
||||||
|
@onready var _total_value: Label = $MarginContainer/VBoxContainer/StatsGrid/TotalValue
|
||||||
|
@onready var _pending_value: Label = $MarginContainer/VBoxContainer/StatsGrid/PendingValue
|
||||||
|
@onready var _multiplier_value: Label = $MarginContainer/VBoxContainer/StatsGrid/MultiplierValue
|
||||||
|
@onready var _basis_label: Label = $MarginContainer/VBoxContainer/StatsGrid/BasisValue
|
||||||
|
@onready var _reset_button: Button = $MarginContainer/VBoxContainer/Buttons/ResetButton
|
||||||
|
@onready var _save_button: Button = $MarginContainer/VBoxContainer/Buttons/SaveButton
|
||||||
|
@onready var _confirm_dialog: ConfirmationDialog = $ConfirmDialog
|
||||||
|
|
||||||
|
var _is_waiting_for_confirm: bool = false
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var manager: Node = _get_prestige_manager()
|
||||||
|
if manager != null and manager.has_signal("prestige_state_changed"):
|
||||||
|
manager.prestige_state_changed.connect(_on_prestige_state_changed)
|
||||||
|
if manager != null and manager.has_signal("prestige_performed"):
|
||||||
|
manager.prestige_performed.connect(_on_prestige_performed)
|
||||||
|
if not GameState.currency_changed.is_connected(_on_currency_changed):
|
||||||
|
GameState.currency_changed.connect(_on_currency_changed)
|
||||||
|
|
||||||
|
_refresh_ui()
|
||||||
|
|
||||||
|
func _on_reset_button_pressed() -> void:
|
||||||
|
var manager: Node = _get_prestige_manager()
|
||||||
|
if manager == null or not manager.has_method("can_prestige"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not bool(manager.call("can_prestige")):
|
||||||
|
return
|
||||||
|
|
||||||
|
var pending: BigNumber = manager.call("calculate_pending_gain")
|
||||||
|
var source_currency_id: StringName = manager.call("get_source_currency_id")
|
||||||
|
var source_currency_name: String = GameState.get_currency_name(source_currency_id)
|
||||||
|
var confirm_message: String = "Reset this run for +%s prestige?\n\nThis resets currencies, owned generators, and buff levels. Lifetime totals and prestige are kept." % pending.to_string_suffix(2)
|
||||||
|
if not source_currency_name.is_empty():
|
||||||
|
confirm_message = "%s\n\nBased on %s progression." % [confirm_message, source_currency_name]
|
||||||
|
|
||||||
|
_confirm_dialog.dialog_text = confirm_message
|
||||||
|
_is_waiting_for_confirm = true
|
||||||
|
_confirm_dialog.popup_centered_ratio(0.4)
|
||||||
|
|
||||||
|
func _on_save_button_pressed() -> void:
|
||||||
|
GameState.save_game()
|
||||||
|
|
||||||
|
func _on_prestige_state_changed(_total: BigNumber, _pending: BigNumber) -> void:
|
||||||
|
_refresh_ui()
|
||||||
|
|
||||||
|
func _on_prestige_performed(_gain: BigNumber, _total: BigNumber) -> void:
|
||||||
|
_refresh_ui()
|
||||||
|
|
||||||
|
func _on_currency_changed(_currency_id: StringName, _new_amount: BigNumber) -> void:
|
||||||
|
_refresh_ui()
|
||||||
|
|
||||||
|
func _on_confirm_dialog_confirmed() -> void:
|
||||||
|
if not _is_waiting_for_confirm:
|
||||||
|
return
|
||||||
|
|
||||||
|
_is_waiting_for_confirm = false
|
||||||
|
var manager: Node = _get_prestige_manager()
|
||||||
|
if manager == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
manager.call("perform_prestige")
|
||||||
|
|
||||||
|
func _on_confirm_dialog_canceled() -> void:
|
||||||
|
_is_waiting_for_confirm = false
|
||||||
|
|
||||||
|
func _refresh_ui() -> void:
|
||||||
|
var manager: Node = _get_prestige_manager()
|
||||||
|
if manager == null:
|
||||||
|
visible = false
|
||||||
|
return
|
||||||
|
|
||||||
|
visible = true
|
||||||
|
var total: BigNumber = manager.call("get_total_prestige")
|
||||||
|
var pending: BigNumber = manager.call("calculate_pending_gain")
|
||||||
|
var multiplier: float = float(manager.call("get_total_multiplier"))
|
||||||
|
var basis_text: String = String(manager.call("get_basis_label"))
|
||||||
|
var basis_value: BigNumber = manager.call("get_basis_value_for_display")
|
||||||
|
|
||||||
|
_total_value.text = total.to_string_suffix(2)
|
||||||
|
_pending_value.text = pending.to_string_suffix(2)
|
||||||
|
_multiplier_value.text = "x%.2f" % multiplier
|
||||||
|
_basis_label.text = "%s (%s)" % [basis_text, basis_value.to_string_suffix(2)]
|
||||||
|
_reset_button.disabled = not bool(manager.call("can_prestige"))
|
||||||
|
|
||||||
|
func _get_prestige_manager() -> Node:
|
||||||
|
return get_node_or_null("/root/PrestigeManager")
|
||||||
1
core/prestige/prestige_panel.gd.uid
Normal file
1
core/prestige/prestige_panel.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dmsmmgtbrul1t
|
||||||
81
core/prestige/prestige_panel.tscn
Normal file
81
core/prestige/prestige_panel.tscn
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://core/prestige/prestige_panel.gd" id="1_7fdwq"]
|
||||||
|
|
||||||
|
[node name="PrestigePanel" type="PanelContainer"]
|
||||||
|
offset_right = 420.0
|
||||||
|
offset_bottom = 206.0
|
||||||
|
script = ExtResource("1_7fdwq")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
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="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Prestige"
|
||||||
|
|
||||||
|
[node name="StatsGrid" type="GridContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
columns = 2
|
||||||
|
|
||||||
|
[node name="TotalTitle" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Total"
|
||||||
|
|
||||||
|
[node name="TotalValue" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "0"
|
||||||
|
|
||||||
|
[node name="PendingTitle" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Pending"
|
||||||
|
|
||||||
|
[node name="PendingValue" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "0"
|
||||||
|
|
||||||
|
[node name="MultiplierTitle" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Multiplier"
|
||||||
|
|
||||||
|
[node name="MultiplierValue" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "x1.00"
|
||||||
|
|
||||||
|
[node name="BasisTitle" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Basis"
|
||||||
|
|
||||||
|
[node name="BasisValue" type="Label" parent="MarginContainer/VBoxContainer/StatsGrid"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "-"
|
||||||
|
|
||||||
|
[node name="Buttons" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="ResetButton" type="Button" parent="MarginContainer/VBoxContainer/Buttons"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Prestige Reset"
|
||||||
|
|
||||||
|
[node name="SaveButton" type="Button" parent="MarginContainer/VBoxContainer/Buttons"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Save"
|
||||||
|
|
||||||
|
[node name="ConfirmDialog" type="ConfirmationDialog" parent="."]
|
||||||
|
title = "Prestige Reset"
|
||||||
|
ok_button_text = "Prestige"
|
||||||
|
dialog_text = "Reset this run?"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="MarginContainer/VBoxContainer/Buttons/ResetButton" to="." method="_on_reset_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MarginContainer/VBoxContainer/Buttons/SaveButton" to="." method="_on_save_button_pressed"]
|
||||||
|
[connection signal="canceled" from="ConfirmDialog" to="." method="_on_confirm_dialog_canceled"]
|
||||||
|
[connection signal="confirmed" from="ConfirmDialog" to="." method="_on_confirm_dialog_confirmed"]
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="5_dl3gy"]
|
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="5_dl3gy"]
|
||||||
[ext_resource type="Resource" uid="uid://df5k58yu1g6rf" path="res://idles/generators/forestry.tres" id="5_u3cug"]
|
[ext_resource type="Resource" uid="uid://df5k58yu1g6rf" path="res://idles/generators/forestry.tres" id="5_u3cug"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dirvi76rkoowf" path="res://goals_debug_ui.tscn" id="7_7i63j"]
|
[ext_resource type="PackedScene" uid="uid://dirvi76rkoowf" path="res://goals_debug_ui.tscn" id="7_7i63j"]
|
||||||
|
[ext_resource type="PackedScene" path="res://core/prestige/prestige_panel.tscn" id="9_q5vce"]
|
||||||
|
|
||||||
[node name="GeneratorGym" type="Node2D" unique_id=1219373683]
|
[node name="GeneratorGym" type="Node2D" unique_id=1219373683]
|
||||||
|
|
||||||
@@ -60,3 +61,10 @@ offset_left = 1253.0
|
|||||||
offset_top = 817.0
|
offset_top = 817.0
|
||||||
offset_right = 1913.0
|
offset_right = 1913.0
|
||||||
offset_bottom = 1076.0
|
offset_bottom = 1076.0
|
||||||
|
|
||||||
|
[node name="PrestigePanel" parent="UI" unique_id=401213142 instance=ExtResource("9_q5vce")]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 12.0
|
||||||
|
offset_top = 822.0
|
||||||
|
offset_right = 432.0
|
||||||
|
offset_bottom = 1028.0
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func _refresh_generator_ui() -> void:
|
|||||||
if _generator.data != null and can_interact:
|
if _generator.data != null and can_interact:
|
||||||
production_per_second = _generator.data.production_per_second(
|
production_per_second = _generator.data.production_per_second(
|
||||||
_generator.owned,
|
_generator.owned,
|
||||||
_generator.run_multiplier * _generator.get_automatic_production_multiplier(),
|
_generator.get_effective_auto_run_multiplier(),
|
||||||
_generator.purchased_count
|
_generator.purchased_count
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,22 @@
|
|||||||
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="1_pj6se"]
|
[ext_resource type="Script" uid="uid://r4js5eajolio" path="res://core/goals/goal_requirement_data.gd" id="1_pj6se"]
|
||||||
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_lsxf0"]
|
[ext_resource type="Resource" uid="uid://brqaojindcxa5" path="res://idles/currencies/magic.tres" id="2_lsxf0"]
|
||||||
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="3_hyry2"]
|
[ext_resource type="Script" uid="uid://dx4kmh33rrkpy" path="res://core/goals/goal_data.gd" id="3_hyry2"]
|
||||||
|
[ext_resource type="Resource" uid="uid://cythfovqgqlyh" path="res://idles/currencies/wood.tres" id="3_lsxf0"]
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_tvl3d"]
|
[sub_resource type="Resource" id="Resource_tvl3d"]
|
||||||
script = ExtResource("1_pj6se")
|
script = ExtResource("1_pj6se")
|
||||||
currency = ExtResource("2_lsxf0")
|
currency = ExtResource("2_lsxf0")
|
||||||
amount_mantissa = 1.3
|
amount_mantissa = 3.5
|
||||||
|
amount_exponent = 5
|
||||||
|
|
||||||
|
[sub_resource type="Resource" id="Resource_hyry2"]
|
||||||
|
script = ExtResource("1_pj6se")
|
||||||
|
currency = ExtResource("3_lsxf0")
|
||||||
|
amount_mantissa = 2.0
|
||||||
amount_exponent = 4
|
amount_exponent = 4
|
||||||
|
metadata/_custom_type_script = "uid://r4js5eajolio"
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("3_hyry2")
|
script = ExtResource("3_hyry2")
|
||||||
id = &"magic_total_13k"
|
id = &"magic_350k_wood_20k"
|
||||||
requirements = Array[ExtResource("1_pj6se")]([SubResource("Resource_tvl3d")])
|
requirements = Array[ExtResource("1_pj6se")]([SubResource("Resource_tvl3d"), SubResource("Resource_hyry2")])
|
||||||
|
|||||||
25
idles/prestige/primary_prestige.tres
Normal file
25
idles/prestige/primary_prestige.tres
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[gd_resource type="Resource" script_class="PrestigeConfig" format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://core/prestige/prestige_config.gd" id="1_3tg3a"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_3tg3a")
|
||||||
|
id = &"ascension"
|
||||||
|
display_name = "Ascension"
|
||||||
|
prestige_currency_id = &"ascension"
|
||||||
|
source_currency_id = &"magic"
|
||||||
|
basis = 0
|
||||||
|
formula = 0
|
||||||
|
threshold_mantissa = 1.0
|
||||||
|
threshold_exponent = 4
|
||||||
|
scale = 1.0
|
||||||
|
exponent = 0.5
|
||||||
|
flat_bonus = 0.0
|
||||||
|
minimum_gain = 0
|
||||||
|
rounding_mode = 0
|
||||||
|
allow_prestige_without_gain = false
|
||||||
|
multiplier_mode = 0
|
||||||
|
base_multiplier = 1.0
|
||||||
|
multiplier_per_prestige = 0.05
|
||||||
|
multiplier_exponent = 1.0
|
||||||
|
metadata/_custom_type_script = "res://core/prestige/prestige_config.gd"
|
||||||
@@ -19,6 +19,7 @@ config/icon="res://icon.svg"
|
|||||||
|
|
||||||
CurrencyDatabase="*res://core/currency_database.gd"
|
CurrencyDatabase="*res://core/currency_database.gd"
|
||||||
GameState="*uid://d2j7tvlgxr2jp"
|
GameState="*uid://d2j7tvlgxr2jp"
|
||||||
|
PrestigeManager="*res://core/prestige/prestige_manager.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user