systems rework: buffs, prestige graph, research, modular architecture
Decouples core systems from centralized globals in favor of catalogue-based architecture and data-driven content. Key changes: - Prestige: node-based buff tech tree, graph panel, progress bar - Research: multi-worker system, per-generator research, xp generation - Ascension: meta-currency layer via multi-currency prestige resets - Buffs: refactored as generator-independent via catalogues - UI: currency panel, edge-scrolling camera + zoom, current-goal panel - Alchemy Tower: crafting building with recipe/cost system - Testing: 7 test suites (ascension, prestige, research, goals) - Content: migrated from hardcoded idles/ to gym format (tiny_sword)
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
class_name PrestigeManager
|
||||
extends Node
|
||||
|
||||
signal prestige_state_changed(total_prestige: BigNumber, pending_gain: BigNumber)
|
||||
signal prestige_performed(gain: BigNumber, total_prestige: BigNumber)
|
||||
signal prestige_threshold_changed(next_threshold: BigNumber)
|
||||
|
||||
const SAVE_KEY: String = "prestige_state"
|
||||
const TOTAL_PRESTIGE_KEY: String = "total_prestige_earned"
|
||||
@@ -14,6 +16,7 @@ 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 BASIS_ALL_CURRENCIES: int = 3
|
||||
|
||||
const FORMULA_POWER: int = 0
|
||||
const FORMULA_TRIANGULAR_INVERSE: int = 1
|
||||
@@ -21,7 +24,8 @@ const FORMULA_TRIANGULAR_INVERSE: int = 1
|
||||
const MULTIPLIER_MODE_ADDITIVE: int = 0
|
||||
const MULTIPLIER_MODE_MULTIPLICATIVE_POWER: int = 1
|
||||
|
||||
@export var config: Resource
|
||||
@export var config: PrestigeConfig
|
||||
@export var game_state: LevelGameState
|
||||
|
||||
var total_prestige_earned: BigNumber = BigNumber.from_float(0.0)
|
||||
var current_prestige_unspent: BigNumber = BigNumber.from_float(0.0)
|
||||
@@ -32,13 +36,17 @@ var last_reset_time: int = 0
|
||||
|
||||
func _ready() -> void:
|
||||
if config == null:
|
||||
config = load("res://idles/prestige/primary_prestige.tres") as Resource
|
||||
config = load("res://docs/gyms/tiny_sword/prestige/primary_prestige.tres") as PrestigeConfig
|
||||
|
||||
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 game_state == null:
|
||||
push_warning("PrestigeManager: game_state reference is not set.")
|
||||
return
|
||||
|
||||
var loaded_state: Dictionary = game_state.get_external_save_data(SAVE_KEY)
|
||||
if loaded_state.is_empty():
|
||||
_initialize_run_tracking_from_current_state()
|
||||
else:
|
||||
@@ -46,8 +54,12 @@ func _ready() -> void:
|
||||
_validate_loaded_run_tracking()
|
||||
_save_state_to_game_state()
|
||||
|
||||
GameState.currency_changed.connect(_on_currency_changed)
|
||||
game_state.currency_changed.connect(_on_currency_changed)
|
||||
prestige_state_changed.emit(get_total_prestige(), calculate_pending_gain())
|
||||
prestige_threshold_changed.emit(get_next_prestige_threshold())
|
||||
|
||||
func get_config() -> PrestigeConfig:
|
||||
return config
|
||||
|
||||
func get_source_currency_id() -> StringName:
|
||||
if not _is_config_valid():
|
||||
@@ -55,6 +67,38 @@ func get_source_currency_id() -> StringName:
|
||||
|
||||
return _normalize_currency_id(_get_config_string_name("source_currency_id", &""))
|
||||
|
||||
func get_prestige_currency_id() -> StringName:
|
||||
if not _is_config_valid():
|
||||
return &""
|
||||
|
||||
return _normalize_currency_id(_get_config_string_name("prestige_currency_id", &""))
|
||||
|
||||
func get_tracked_currency_ids() -> Array[StringName]:
|
||||
if not _is_config_valid():
|
||||
return []
|
||||
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
|
||||
if basis_type == BASIS_ALL_CURRENCIES:
|
||||
var include_ids: Array[StringName] = _get_config_array("include_currency_ids", [])
|
||||
if not include_ids.is_empty():
|
||||
return include_ids
|
||||
if game_state and game_state.currency_catalogue:
|
||||
return game_state.currency_catalogue.get_all_ids()
|
||||
return []
|
||||
|
||||
return [get_source_currency_id()]
|
||||
|
||||
func _get_config_array(property_name: String, fallback: Array = []) -> Array:
|
||||
if config == null:
|
||||
return fallback
|
||||
|
||||
var value: Variant = config.get(property_name)
|
||||
if value is Array:
|
||||
return value
|
||||
|
||||
return fallback
|
||||
|
||||
func get_total_prestige() -> BigNumber:
|
||||
return _copy_big_number(total_prestige_earned)
|
||||
|
||||
@@ -66,7 +110,7 @@ func get_total_multiplier() -> float:
|
||||
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)
|
||||
var prestige_value: float = total_prestige_earned.to_float()
|
||||
if prestige_value <= 0.0:
|
||||
return base
|
||||
|
||||
@@ -125,52 +169,119 @@ func perform_prestige() -> bool:
|
||||
if gain.mantissa > 0.0:
|
||||
total_prestige_earned.add_in_place(gain)
|
||||
current_prestige_unspent.add_in_place(gain)
|
||||
|
||||
var prestige_currency_id: StringName = get_prestige_currency_id()
|
||||
if prestige_currency_id != &"" and game_state:
|
||||
game_state.add_currency_by_id(prestige_currency_id, gain)
|
||||
|
||||
prestige_resets_count += 1
|
||||
last_reset_time = Time.get_unix_time_from_system()
|
||||
|
||||
GameState.reset_for_prestige(true, false)
|
||||
if game_state:
|
||||
var preserve_currencies: Array[StringName] = []
|
||||
var prestige_currency_id: StringName = get_prestige_currency_id()
|
||||
if prestige_currency_id != &"":
|
||||
preserve_currencies.append(prestige_currency_id)
|
||||
game_state.reset_for_prestige(true, false, preserve_currencies)
|
||||
_reset_all_buff_levels()
|
||||
_reinitialize_generators_after_prestige_reset()
|
||||
GameState.emit_currency_changed_for_all()
|
||||
if game_state:
|
||||
game_state.emit_currency_changed_for_all()
|
||||
_initialize_run_tracking_from_current_state()
|
||||
_save_state_to_game_state()
|
||||
GameState.save_game()
|
||||
if game_state:
|
||||
game_state.save_game()
|
||||
|
||||
var total: BigNumber = get_total_prestige()
|
||||
var next_threshold: BigNumber = get_next_prestige_threshold()
|
||||
prestige_performed.emit(gain, total)
|
||||
prestige_state_changed.emit(total, calculate_pending_gain())
|
||||
prestige_threshold_changed.emit(next_threshold)
|
||||
return true
|
||||
|
||||
func get_next_prestige_threshold() -> BigNumber:
|
||||
if not _is_config_valid():
|
||||
return BigNumber.from_float(0.0)
|
||||
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
|
||||
match basis_type:
|
||||
BASIS_RUN_TOTAL, BASIS_ALL_CURRENCIES:
|
||||
return _get_config_threshold()
|
||||
_:
|
||||
var current_total: float = total_prestige_earned.to_float()
|
||||
var next_target: float = _calculate_target_for_prestige(current_total + 1.0)
|
||||
return BigNumber.from_float(next_target)
|
||||
|
||||
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):
|
||||
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
|
||||
match basis_type:
|
||||
BASIS_RUN_TOTAL:
|
||||
return "Run Total"
|
||||
BASIS_RUN_MAX:
|
||||
return "Run Max"
|
||||
BASIS_ALL_CURRENCIES:
|
||||
return "Total Currency"
|
||||
_:
|
||||
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)
|
||||
|
||||
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
|
||||
if basis_type == BASIS_ALL_CURRENCIES:
|
||||
if new_amount.is_greater_than(run_peak_source_currency):
|
||||
run_peak_source_currency = _copy_big_number(new_amount)
|
||||
else:
|
||||
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()
|
||||
var next_threshold: BigNumber = get_next_prestige_threshold()
|
||||
prestige_state_changed.emit(get_total_prestige(), calculate_pending_gain())
|
||||
prestige_threshold_changed.emit(next_threshold)
|
||||
|
||||
func _calculate_target_for_prestige(prestige_level: float) -> float:
|
||||
if config == null:
|
||||
return 0.0
|
||||
|
||||
var threshold_value: float = _get_config_threshold().to_float()
|
||||
if threshold_value <= 0.0:
|
||||
return 0.0
|
||||
|
||||
match _get_config_int("formula", FORMULA_POWER):
|
||||
FORMULA_TRIANGULAR_INVERSE:
|
||||
var target_total: float = prestige_level
|
||||
var k: float = target_total * (target_total + 1.0) * 0.5
|
||||
return k * threshold_value
|
||||
_:
|
||||
var scale: float = maxf(_get_config_float("scale", 1.0), 0.0001)
|
||||
var exponent: float = maxf(_get_config_float("exponent", 1.0), 0.0001)
|
||||
var flat_bonus: float = _get_config_float("flat_bonus", 0.0)
|
||||
if scale <= 0.0:
|
||||
scale = 0.0001
|
||||
if exponent <= 0.0:
|
||||
exponent = 0.0001
|
||||
|
||||
var ratio: float = pow((prestige_level - flat_bonus) / scale, 1.0 / exponent)
|
||||
if ratio <= 0.0:
|
||||
return threshold_value
|
||||
return ratio * threshold_value
|
||||
|
||||
func _calculate_raw_gain_float(basis_value: BigNumber) -> float:
|
||||
if config == null:
|
||||
@@ -178,11 +289,11 @@ func _calculate_raw_gain_float(basis_value: BigNumber) -> float:
|
||||
|
||||
match _get_config_int("formula", FORMULA_POWER):
|
||||
FORMULA_TRIANGULAR_INVERSE:
|
||||
var threshold_value: float = _big_number_to_float(_get_config_threshold())
|
||||
var threshold_value: float = _get_config_threshold().to_float()
|
||||
if threshold_value <= 0.0:
|
||||
return 0.0
|
||||
|
||||
var basis_float: float = _big_number_to_float(basis_value)
|
||||
var basis_float: float = basis_value.to_float()
|
||||
if basis_float <= 0.0:
|
||||
return 0.0
|
||||
|
||||
@@ -192,7 +303,7 @@ func _calculate_raw_gain_float(basis_value: BigNumber) -> float:
|
||||
|
||||
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 - total_prestige_earned.to_float()
|
||||
return target_total
|
||||
_:
|
||||
var ratio: float = _big_number_ratio_to_float(basis_value, _get_config_threshold())
|
||||
@@ -201,20 +312,26 @@ func _calculate_raw_gain_float(basis_value: BigNumber) -> float:
|
||||
|
||||
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 - total_prestige_earned.to_float()
|
||||
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 == &"":
|
||||
if game_state == null:
|
||||
return BigNumber.from_float(0.0)
|
||||
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
|
||||
match _get_config_int("basis", BASIS_LIFETIME_TOTAL):
|
||||
match basis_type:
|
||||
BASIS_ALL_CURRENCIES:
|
||||
return _get_total_all_currencies()
|
||||
BASIS_RUN_TOTAL:
|
||||
var lifetime_now: BigNumber = GameState.get_total_currency_acquired_by_id(source_currency_id)
|
||||
var source_currency_id: StringName = get_source_currency_id()
|
||||
if source_currency_id == &"":
|
||||
return BigNumber.from_float(0.0)
|
||||
var lifetime_now: BigNumber = game_state.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)
|
||||
@@ -222,11 +339,37 @@ func _get_basis_value() -> BigNumber:
|
||||
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))
|
||||
var source_currency_id: StringName = get_source_currency_id()
|
||||
if source_currency_id == &"":
|
||||
return BigNumber.from_float(0.0)
|
||||
return _copy_big_number(game_state.get_total_currency_acquired_by_id(source_currency_id))
|
||||
|
||||
func _get_total_all_currencies() -> BigNumber:
|
||||
var total: BigNumber = BigNumber.from_float(0.0)
|
||||
var currency_ids: Array[StringName] = get_tracked_currency_ids()
|
||||
|
||||
for currency_id in currency_ids:
|
||||
var currency_total: BigNumber = game_state.get_total_currency_acquired_by_id(currency_id)
|
||||
total.add_in_place(currency_total)
|
||||
|
||||
return total
|
||||
|
||||
func _initialize_run_tracking_from_current_state() -> void:
|
||||
if config == null:
|
||||
return
|
||||
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
|
||||
if basis_type == BASIS_ALL_CURRENCIES:
|
||||
run_start_total_source_currency = _get_total_all_currencies()
|
||||
run_peak_source_currency = BigNumber.from_float(0.0)
|
||||
var currency_ids: Array[StringName] = get_tracked_currency_ids()
|
||||
for currency_id in currency_ids:
|
||||
var current: BigNumber = game_state.get_currency_amount_by_id(currency_id)
|
||||
if current.is_greater_than(run_peak_source_currency):
|
||||
run_peak_source_currency = _copy_big_number(current)
|
||||
_save_state_to_game_state()
|
||||
return
|
||||
|
||||
var source_currency_id: StringName = get_source_currency_id()
|
||||
if source_currency_id == &"":
|
||||
@@ -234,13 +377,31 @@ func _initialize_run_tracking_from_current_state() -> void:
|
||||
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))
|
||||
if game_state == null:
|
||||
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(game_state.get_total_currency_acquired_by_id(source_currency_id))
|
||||
run_peak_source_currency = _copy_big_number(game_state.get_currency_amount_by_id(source_currency_id))
|
||||
_save_state_to_game_state()
|
||||
|
||||
func _validate_loaded_run_tracking() -> void:
|
||||
if config == null:
|
||||
return
|
||||
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
|
||||
if basis_type == BASIS_ALL_CURRENCIES:
|
||||
var currency_ids: Array[StringName] = get_tracked_currency_ids()
|
||||
var peak: BigNumber = BigNumber.from_float(0.0)
|
||||
for currency_id in currency_ids:
|
||||
var current: BigNumber = game_state.get_currency_amount_by_id(currency_id)
|
||||
if current.is_greater_than(peak):
|
||||
peak = _copy_big_number(current)
|
||||
if peak.is_greater_than(run_peak_source_currency):
|
||||
run_peak_source_currency = _copy_big_number(peak)
|
||||
return
|
||||
|
||||
var source_currency_id: StringName = get_source_currency_id()
|
||||
if source_currency_id == &"":
|
||||
@@ -248,11 +409,14 @@ func _validate_loaded_run_tracking() -> void:
|
||||
run_peak_source_currency = BigNumber.from_float(0.0)
|
||||
return
|
||||
|
||||
var current_currency: BigNumber = GameState.get_currency_amount_by_id(source_currency_id)
|
||||
if game_state == null:
|
||||
return
|
||||
|
||||
var current_currency: BigNumber = game_state.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)
|
||||
var lifetime_now: BigNumber = game_state.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)
|
||||
|
||||
@@ -278,7 +442,18 @@ func _deserialize_state(raw_state: Dictionary) -> void:
|
||||
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())
|
||||
if game_state:
|
||||
game_state.set_external_save_data(SAVE_KEY, _serialize_state())
|
||||
|
||||
func _reset_all_buff_levels() -> void:
|
||||
if game_state == null:
|
||||
return
|
||||
|
||||
for buff in game_state.get_all_buffs():
|
||||
if buff == null:
|
||||
continue
|
||||
game_state.set_buff_level(buff.id, 0)
|
||||
game_state.set_buff_unlocked(buff.id, false)
|
||||
|
||||
func _reinitialize_generators_after_prestige_reset() -> void:
|
||||
var scene_root: Node = get_tree().current_scene
|
||||
@@ -316,14 +491,7 @@ func _big_number_ratio_to_float(a: BigNumber, b: BigNumber) -> float:
|
||||
|
||||
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()
|
||||
@@ -341,33 +509,18 @@ func _to_non_negative_int(value: Variant, fallback: int = 0) -> int:
|
||||
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"))
|
||||
return config.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
|
||||
var basis_type: int = _get_config_int("basis", BASIS_LIFETIME_TOTAL)
|
||||
return basis_type != BASIS_RUN_TOTAL and basis_type != BASIS_ALL_CURRENCIES
|
||||
|
||||
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)
|
||||
return config.round_value(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
|
||||
|
||||
if config != null:
|
||||
return config.get_threshold()
|
||||
return BigNumber.from_float(1.0)
|
||||
|
||||
func _get_config_string_name(property_name: String, fallback: StringName = &"") -> StringName:
|
||||
|
||||
Reference in New Issue
Block a user