Add prestige graph
This commit is contained in:
@@ -27,6 +27,8 @@ signal research_level_up(research_id: StringName, old_level: int, new_level: int
|
||||
signal worker_count_changed(currency_id: StringName, new_count: int)
|
||||
## Emitted when research workers change.
|
||||
signal research_workers_changed(new_count: int)
|
||||
## Emitted when an prestige buff node is unlocked.
|
||||
signal prestige_buff_unlocked(buff_id: StringName)
|
||||
|
||||
# ==============================================================================
|
||||
# EXPORTED REFERENCES
|
||||
@@ -39,6 +41,8 @@ signal research_workers_changed(new_count: int)
|
||||
@export var goal_catalogue: GoalCatalogue
|
||||
## Catalog of all defined research tracks in the game.
|
||||
@export var research_catalogue: ResearchCatalogue
|
||||
## Catalog of all prestige buff nodes in the graph.
|
||||
@export var prestige_buff_catalogue: PrestigeBuffCatalogue
|
||||
## File path for save/load persistence. Defaults to user directory.
|
||||
@export var save_file_path: String = "user://level_save.json"
|
||||
|
||||
@@ -78,6 +82,8 @@ var research_xp: Dictionary = {}
|
||||
var research_levels: Dictionary = {}
|
||||
## Workers assigned to research (total across all tracks).
|
||||
var research_workers: int = 0
|
||||
## Ascension buff unlock state: maps node_id (StringName) → bool (true if unlocked). Persists across prestige.
|
||||
var _prestige_buff_unlocked: Dictionary = {}
|
||||
|
||||
## Unix timestamp of last save.
|
||||
var last_save_time: int = 0
|
||||
@@ -88,7 +94,7 @@ var last_save_time: int = 0
|
||||
## JSON key for save format version.
|
||||
const SAVE_FORMAT_VERSION_KEY: String = "save_format_version"
|
||||
## Current save format version (7 includes research workers tracking).
|
||||
const CURRENT_SAVE_FORMAT_VERSION: int = 7
|
||||
const CURRENT_SAVE_FORMAT_VERSION: int = 8
|
||||
## JSON key for generator owned count.
|
||||
const GENERATOR_OWNED_KEY: String = "owned"
|
||||
## JSON key for generator purchased count.
|
||||
@@ -128,6 +134,8 @@ const RESEARCH_XP_KEY: String = "research_xp"
|
||||
const RESEARCH_LEVELS_KEY: String = "research_levels"
|
||||
## JSON key for research workers section.
|
||||
const RESEARCH_WORKERS_KEY: String = "research_workers"
|
||||
## JSON key for prestige buff unlocked array.
|
||||
const PRESTIGE_BUFF_UNLOCKED_KEY: String = "prestige_buff_unlocked"
|
||||
|
||||
## Reference to the PrestigeManager autoload node (set in _ready).
|
||||
var prestige_manager: PrestigeManager
|
||||
@@ -139,6 +147,7 @@ func _ready() -> void:
|
||||
|
||||
_initialize_currency_maps()
|
||||
_initialize_catalogues()
|
||||
_initialize_prestige_buffs()
|
||||
_load_catalogue_goals()
|
||||
load_game()
|
||||
_try_unlock_all_buffs_from_goals()
|
||||
@@ -184,7 +193,7 @@ func get_currency_name(currency_id: StringName) -> String:
|
||||
|
||||
## Returns the icon texture for a currency ID, or null if not found.
|
||||
func get_currency_icon(currency_id: StringName) -> Texture2D:
|
||||
var currency: Resource = get_currency_resource(currency_id)
|
||||
var currency: Currency = get_currency_resource(currency_id)
|
||||
if currency == null:
|
||||
return null
|
||||
var raw_icon: Variant = currency.icon
|
||||
@@ -627,6 +636,7 @@ func save_game() -> void:
|
||||
RESEARCH_XP_KEY: _serialize_research_xp(),
|
||||
RESEARCH_LEVELS_KEY: research_levels.duplicate(),
|
||||
RESEARCH_WORKERS_KEY: research_workers,
|
||||
PRESTIGE_BUFF_UNLOCKED_KEY: _serialize_prestige_buff_unlocked(),
|
||||
LAST_SAVE_TIME_KEY: Time.get_unix_time_from_system()
|
||||
}
|
||||
|
||||
@@ -682,11 +692,13 @@ func load_game() -> void:
|
||||
research_workers = int(parsed_data.get(RESEARCH_WORKERS_KEY, 0))
|
||||
|
||||
last_save_time = parsed_data.get(LAST_SAVE_TIME_KEY, Time.get_unix_time_from_system())
|
||||
if parsed_data.has(PRESTIGE_BUFF_UNLOCKED_KEY):
|
||||
_deserialize_prestige_buff_unlocked(parsed_data.get(PRESTIGE_BUFF_UNLOCKED_KEY))
|
||||
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 [SAVE_FORMAT_VERSION_KEY, CURRENCIES_KEY, GENERATOR_STATES_KEY, BUFF_LEVELS_KEY, BUFF_UNLOCKED_KEY, BUFF_ACTIVE_KEY, GOALS_KEY, RESEARCH_XP_KEY, RESEARCH_LEVELS_KEY, RESEARCH_WORKERS_KEY, LAST_SAVE_TIME_KEY]:
|
||||
if save_key in [SAVE_FORMAT_VERSION_KEY, CURRENCIES_KEY, GENERATOR_STATES_KEY, BUFF_LEVELS_KEY, BUFF_UNLOCKED_KEY, BUFF_ACTIVE_KEY, GOALS_KEY, RESEARCH_XP_KEY, RESEARCH_LEVELS_KEY, RESEARCH_WORKERS_KEY, PRESTIGE_BUFF_UNLOCKED_KEY, LAST_SAVE_TIME_KEY]:
|
||||
continue
|
||||
|
||||
var section_payload: Variant = parsed_data.get(save_key_variant)
|
||||
@@ -819,6 +831,16 @@ func _initialize_catalogues() -> void:
|
||||
continue
|
||||
register_goal(goal)
|
||||
|
||||
## Internal helper: Initializes prestige buff unlock state from the catalogue, filling missing node IDs as false.
|
||||
func _initialize_prestige_buffs() -> void:
|
||||
if prestige_buff_catalogue == null:
|
||||
return
|
||||
for node in prestige_buff_catalogue.nodes:
|
||||
if node == null or node.id == &"":
|
||||
continue
|
||||
if not _prestige_buff_unlocked.has(node.id):
|
||||
_prestige_buff_unlocked[node.id] = false
|
||||
|
||||
## Internal helper: Loads goal definitions from the catalogue.
|
||||
func _load_catalogue_goals() -> void:
|
||||
if goal_catalogue:
|
||||
@@ -1297,6 +1319,99 @@ func _try_unlock_all_buffs_from_goals() -> void:
|
||||
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# PRESTIGE BUFF API
|
||||
# ==============================================================================
|
||||
|
||||
## Returns the ascension currency ID used for purchasing buff nodes.
|
||||
func get_prestige_currency_id() -> StringName:
|
||||
return &"ascension"
|
||||
|
||||
## Attempts to purchase and unlock an prestige buff node. Returns true on success.
|
||||
func purchase_prestige_buff(buff_id: StringName) -> bool:
|
||||
if not can_purchase_prestige_buff(buff_id):
|
||||
return false
|
||||
|
||||
var node: PrestigeBuffNode = _get_prestige_buff_node(buff_id)
|
||||
if node == null:
|
||||
return false
|
||||
|
||||
var cost: BigNumber = node.get_cost()
|
||||
var currency_id: StringName = get_prestige_currency_id()
|
||||
if not spend_currency_by_id(currency_id, cost):
|
||||
return false
|
||||
|
||||
_prestige_buff_unlocked[buff_id] = true
|
||||
prestige_buff_unlocked.emit(buff_id)
|
||||
save_game()
|
||||
return true
|
||||
|
||||
## Checks whether an prestige buff node can be purchased (all parents unlocked, not already unlocked, enough currency).
|
||||
func can_purchase_prestige_buff(buff_id: StringName) -> bool:
|
||||
var node: PrestigeBuffNode = _get_prestige_buff_node(buff_id)
|
||||
if node == null:
|
||||
return false
|
||||
|
||||
if is_prestige_buff_unlocked(buff_id):
|
||||
return false
|
||||
|
||||
if not node.all_parents_unlocked(_prestige_buff_unlocked):
|
||||
return false
|
||||
|
||||
var cost: BigNumber = node.get_cost()
|
||||
var currency_id: StringName = get_prestige_currency_id()
|
||||
var balance: BigNumber = get_currency_amount_by_id(currency_id)
|
||||
return balance.is_greater_than(cost) or balance.is_equal_to(cost)
|
||||
|
||||
## Returns true if the given prestige buff node is unlocked.
|
||||
func is_prestige_buff_unlocked(buff_id: StringName) -> bool:
|
||||
return bool(_prestige_buff_unlocked.get(buff_id, false))
|
||||
|
||||
## Returns the fixed BigNumber cost for an prestige buff node.
|
||||
func get_prestige_buff_cost(buff_id: StringName) -> BigNumber:
|
||||
var node: PrestigeBuffNode = _get_prestige_buff_node(buff_id)
|
||||
if node == null:
|
||||
return BigNumber.from_float(0.0)
|
||||
return node.get_cost()
|
||||
|
||||
## Computes the combined multiplier from all unlocked prestige buff nodes matching the given effect type.
|
||||
## [param target_id] filters to nodes targeting this ID (or global nodes when empty).
|
||||
func get_prestige_buff_multiplier(effect_type: int, target_id: StringName = &"") -> float:
|
||||
var multiplier: float = 1.0
|
||||
for node_id in _prestige_buff_unlocked.keys():
|
||||
if not bool(_prestige_buff_unlocked.get(node_id, false)):
|
||||
continue
|
||||
var node: PrestigeBuffNode = _get_prestige_buff_node(node_id)
|
||||
if node == null:
|
||||
continue
|
||||
if node.effect_type != effect_type:
|
||||
continue
|
||||
if target_id != &"" and node.target_id != &"" and node.target_id != target_id:
|
||||
continue
|
||||
multiplier *= node.effect_value
|
||||
return multiplier
|
||||
|
||||
## Returns all prestige buff nodes whose prerequisites are met but are not yet unlocked.
|
||||
func get_available_prestige_buffs() -> Array[PrestigeBuffNode]:
|
||||
if prestige_buff_catalogue == null:
|
||||
return []
|
||||
|
||||
var available: Array[PrestigeBuffNode] = []
|
||||
for node in prestige_buff_catalogue.nodes:
|
||||
if node == null or node.id == &"":
|
||||
continue
|
||||
if is_prestige_buff_unlocked(node.id):
|
||||
continue
|
||||
if node.all_parents_unlocked(_prestige_buff_unlocked):
|
||||
available.append(node)
|
||||
return available
|
||||
|
||||
## Returns the PrestigeBuffNode resource for a given ID, or null if not found.
|
||||
func _get_prestige_buff_node(buff_id: StringName) -> PrestigeBuffNode:
|
||||
if prestige_buff_catalogue == null:
|
||||
return null
|
||||
return prestige_buff_catalogue.get_node_by_id(buff_id)
|
||||
|
||||
# ==============================================================================
|
||||
# PERSISTENCE HELPERS
|
||||
# ==============================================================================
|
||||
@@ -1394,3 +1509,26 @@ func _deserialize_research_xp(raw: Variant) -> Dictionary:
|
||||
if serialized is Dictionary:
|
||||
result[research_id] = BigNumber.deserialize(serialized)
|
||||
return result
|
||||
|
||||
## Serializes prestige buff unlock state as an array of unlocked node IDs.
|
||||
func _serialize_prestige_buff_unlocked() -> Array:
|
||||
var unlocked: Array = []
|
||||
for node_id in _prestige_buff_unlocked.keys():
|
||||
if _prestige_buff_unlocked.get(node_id, false):
|
||||
unlocked.append(node_id)
|
||||
return unlocked
|
||||
|
||||
## Deserializes prestige buff unlock state from an array of unlocked node ID strings, then fills missing catalogue entries.
|
||||
func _deserialize_prestige_buff_unlocked(raw: Variant) -> void:
|
||||
_prestige_buff_unlocked.clear()
|
||||
if raw is Array:
|
||||
for element in raw:
|
||||
var node_id: StringName = StringName(String(element))
|
||||
if node_id != &"":
|
||||
_prestige_buff_unlocked[node_id] = true
|
||||
_initialize_prestige_buffs()
|
||||
|
||||
# Notify tiles that were waiting for state to load
|
||||
for node_id in _prestige_buff_unlocked.keys():
|
||||
if _prestige_buff_unlocked.get(node_id, false):
|
||||
prestige_buff_unlocked.emit(node_id)
|
||||
|
||||
Reference in New Issue
Block a user