fix buff unlock

This commit is contained in:
2026-03-31 01:45:58 +02:00
parent f1e08c848e
commit eaa8f18a4a
6 changed files with 292 additions and 31 deletions

View File

@@ -10,6 +10,8 @@ signal generator_buff_level_changed(generator_id: StringName, buff_id: StringNam
signal generator_buff_unlocked_changed(generator_id: StringName, buff_id: StringName, unlocked: bool)
signal buff_level_changed(buff_id: StringName, new_level: int)
signal buff_unlocked_changed(buff_id: StringName, unlocked: bool)
signal goal_completed(goal_id: StringName)
signal goal_progress_changed(goal_id: StringName, progress: float)
# ==========================================
# STATE VARIABLES
@@ -28,6 +30,9 @@ var _buff_levels: Dictionary = {}
var _buff_unlocked: Dictionary = {}
var _buff_active: Dictionary = {}
var _goal_definitions: Dictionary = {}
var _goal_completed: Dictionary = {}
var last_save_time: int = 0 # Unix timestamp
# ==========================================
@@ -35,7 +40,7 @@ var last_save_time: int = 0 # Unix timestamp
# ==========================================
static var file_name: String = "user://idle_save.json"
const SAVE_FORMAT_VERSION_KEY: String = "save_format_version"
const CURRENT_SAVE_FORMAT_VERSION: int = 2
const CURRENT_SAVE_FORMAT_VERSION: int = 3
const GENERATOR_OWNED_KEY: String = "owned"
const GENERATOR_PURCHASED_COUNT_KEY: String = "purchased_count"
const GENERATOR_UNLOCKED_KEY: String = "unlocked"
@@ -47,6 +52,8 @@ const BUFF_DEFINITIONS_KEY: String = "buff_definitions"
const BUFF_LEVELS_KEY: String = "buff_levels"
const BUFF_UNLOCKED_KEY: String = "buff_unlocked"
const BUFF_ACTIVE_KEY: String = "buff_active"
const GOALS_KEY: String = "goals"
const GOAL_COMPLETED_KEY: String = "completed"
const CURRENCIES_KEY: String = "currencies"
const CURRENT_KEY: String = "current"
const TOTAL_KEY: String = "total"
@@ -55,6 +62,7 @@ const LAST_SAVE_TIME_KEY: String = "last_save_time"
func _ready() -> void:
_initialize_currency_maps()
_initialize_goals()
load_game()
# ==========================================
@@ -404,6 +412,12 @@ func get_effective_multiplier(generator_id: StringName, kind: int) -> float:
return multiplier
func _try_unlock_all_buffs_from_goals() -> void:
for buff in get_all_buffs():
if buff == null:
continue
_try_unlock_buff_from_goal(buff)
func _try_unlock_buff_from_goal(buff: GeneratorBuffData) -> void:
if buff == null:
return
@@ -411,7 +425,8 @@ func _try_unlock_buff_from_goal(buff: GeneratorBuffData) -> void:
return
if is_buff_unlocked(buff.id):
return
if buff.is_unlock_goal_met():
var goal_id: StringName = buff.unlock_goal.id
if not goal_id.is_empty() and is_goal_completed(goal_id):
set_buff_unlocked(buff.id, true)
func set_external_save_data(section_key: StringName, payload: Dictionary) -> void:
@@ -442,6 +457,7 @@ func reset_for_prestige(reset_total_currency: bool = false, emit_currency_signal
generator_states.clear()
generator_buff_levels.clear()
generator_buff_unlocked.clear()
_goal_completed.clear()
if not emit_currency_signals:
return
@@ -464,6 +480,7 @@ func save_game() -> void:
BUFF_LEVELS_KEY: _serialize_buff_levels(),
BUFF_UNLOCKED_KEY: _serialize_buff_unlocked(),
BUFF_ACTIVE_KEY: _serialize_buff_active(),
GOALS_KEY: _serialize_goals(),
LAST_SAVE_TIME_KEY: Time.get_unix_time_from_system()
}
@@ -497,18 +514,22 @@ func load_game() -> void:
push_error("Save file version %d too old. Please start fresh." % version)
return
if version < 3:
_migrate_goals_from_version_2(parsed_data)
_external_save_sections.clear()
if parsed_data.has(CURRENCIES_KEY):
_load_currency_balances(parsed_data.get(CURRENCIES_KEY, {}))
generator_states = _deserialize_generator_states(parsed_data.get(GENERATOR_STATES_KEY, {}))
_deserialize_buff_states(parsed_data)
_deserialize_goals(parsed_data.get(GOALS_KEY, {}))
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 [SAVE_FORMAT_VERSION_KEY, CURRENCIES_KEY, GENERATOR_STATES_KEY, BUFF_LEVELS_KEY, BUFF_UNLOCKED_KEY, BUFF_ACTIVE_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, LAST_SAVE_TIME_KEY]:
continue
var section_payload: Variant = parsed_data.get(save_key_variant)
@@ -977,3 +998,186 @@ func _deserialize_buff_states(parsed_data: Dictionary) -> void:
var key: String = String(key_variant)
if not key.is_empty():
_buff_active[key] = true
# ==========================================
# GOALS
# ==========================================
func _initialize_goals() -> void:
_goal_definitions.clear()
_goal_completed.clear()
var goal_paths: PackedStringArray = _discover_goal_paths()
for goal_path in goal_paths:
var goal_resource: Resource = load(goal_path)
if goal_resource == null:
push_warning("Failed to load goal resource at '%s'." % goal_path)
continue
if not (goal_resource is GoalData):
continue
var goal: GoalData = goal_resource
if not goal.has_id():
push_warning("Skipping goal with missing id: %s" % goal_path)
continue
if _goal_definitions.has(goal.id):
push_warning("Duplicate goal id '%s' found at %s. Keeping first occurrence." % [String(goal.id), goal_path])
continue
_goal_definitions[goal.id] = goal
_goal_completed[goal.id] = false
func _discover_goal_paths() -> PackedStringArray:
var result: PackedStringArray = []
const GOAL_DIRECTORY_PATH: String = "res://sandbox/goals"
const GOAL_RESOURCE_EXTENSIONS: PackedStringArray = ["tres", "res"]
var directory: DirAccess = DirAccess.open(GOAL_DIRECTORY_PATH)
if directory == null:
push_warning("Unable to open goals directory: %s" % GOAL_DIRECTORY_PATH)
return result
directory.list_dir_begin()
var entry_name: String = directory.get_next()
while not entry_name.is_empty():
if directory.current_is_dir() or entry_name.begins_with("."):
entry_name = directory.get_next()
continue
var extension: String = entry_name.get_extension().to_lower()
if GOAL_RESOURCE_EXTENSIONS.has(extension):
result.append("%s/%s" % [GOAL_DIRECTORY_PATH, entry_name])
entry_name = directory.get_next()
directory.list_dir_end()
result.sort()
return result
func register_goal(goal: GoalData) -> void:
if goal == null:
return
var goal_id: StringName = goal.id
if goal_id == &"":
push_warning("Attempted to register goal with invalid id")
return
if _goal_definitions.has(goal_id):
return
_goal_definitions[goal_id] = goal
_goal_completed[goal_id] = false
func get_goal(goal_id: StringName) -> GoalData:
return _goal_definitions.get(goal_id, null)
func get_all_goals() -> Array[GoalData]:
var result: Array[GoalData] = []
for goal in _goal_definitions.values():
if goal is GoalData:
result.append(goal)
return result
func is_goal_completed(goal_id: StringName) -> bool:
return _goal_completed.get(goal_id, false)
func get_goal_progress(goal_id: StringName) -> float:
var goal: GoalData = get_goal(goal_id)
if goal == null:
return 0.0
return goal.get_progress()
func evaluate_all_goals() -> void:
for goal_id in _goal_completed.keys():
if not _goal_completed[goal_id]:
_try_complete_goal(goal_id)
_try_unlock_all_buffs_from_goals()
func _try_complete_goal(goal_id: StringName) -> void:
var goal: GoalData = _goal_definitions.get(goal_id, null)
if goal == null:
return
if _goal_completed[goal_id]:
return
if goal.is_met():
_goal_completed[goal_id] = true
goal_completed.emit(goal_id)
func _serialize_goals() -> Dictionary:
var result: Dictionary = {}
var completed_goals: Array[StringName] = []
for goal_id in _goal_completed.keys():
if _goal_completed[goal_id]:
completed_goals.append(goal_id)
result[GOAL_COMPLETED_KEY] = completed_goals
return result
func _deserialize_goals(raw_value: Variant) -> void:
_goal_completed.clear()
if not (raw_value is Dictionary):
return
var raw_completed: Variant = raw_value.get(GOAL_COMPLETED_KEY, [])
if raw_completed is Array:
for goal_id_variant in raw_completed:
var goal_id: String = String(goal_id_variant)
if not goal_id.is_empty():
_goal_completed[goal_id] = true
for goal_id in _goal_definitions.keys():
if not _goal_completed.has(goal_id):
_goal_completed[goal_id] = false
func _migrate_goals_from_version_2(parsed_data: Dictionary) -> void:
_goal_completed.clear()
var generator_states_data: Dictionary = parsed_data.get(GENERATOR_STATES_KEY, {})
for gen_id_variant in generator_states_data.keys():
var gen_id: String = String(gen_id_variant)
if gen_id.is_empty():
continue
var state: Dictionary = generator_states_data.get(gen_id_variant, {})
if not state.get(GENERATOR_UNLOCKED_KEY, false):
continue
var generator_data: CurrencyGeneratorData = _load_generator_data_from_id(gen_id)
if generator_data == null or not generator_data.has_unlock_goal():
continue
var goal_id: StringName = generator_data.get_unlock_goal_id()
if not goal_id.is_empty():
_goal_completed[goal_id] = true
for goal_id in _goal_definitions.keys():
if not _goal_completed.has(goal_id):
_goal_completed[goal_id] = false
func _load_generator_data_from_id(generator_id: String) -> CurrencyGeneratorData:
const GENERATORS_DIRECTORY_PATH: String = "res://sandbox/generators"
const GENERATOR_RESOURCE_EXTENSIONS: PackedStringArray = ["tres", "res"]
var directory: DirAccess = DirAccess.open(GENERATORS_DIRECTORY_PATH)
if directory == null:
return null
directory.list_dir_begin()
var entry_name: String = directory.get_next()
while not entry_name.is_empty():
if directory.current_is_dir() or entry_name.begins_with("."):
entry_name = directory.get_next()
continue
var extension: String = entry_name.get_extension().to_lower()
if GENERATOR_RESOURCE_EXTENSIONS.has(extension):
var generator_path: String = "%s/%s" % [GENERATORS_DIRECTORY_PATH, entry_name]
var generator_resource: Resource = load(generator_path)
if generator_resource is CurrencyGeneratorData:
var gen_data: CurrencyGeneratorData = generator_resource
if String(gen_data.id) == generator_id:
directory.list_dir_end()
return gen_data
entry_name = directory.get_next()
directory.list_dir_end()
return null