fix buff unlock
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -320,7 +320,7 @@ func reset_runtime_state_for_prestige() -> void:
|
||||
_remaining_click_cooldown_seconds = 0.0
|
||||
_ensure_registered()
|
||||
_evaluate_generator_unlock_goal(false)
|
||||
_evaluate_buff_unlock_goals()
|
||||
#_evaluate_buff_unlock_goals()
|
||||
visible = is_available_to_player()
|
||||
|
||||
func get_manual_click_multiplier() -> float:
|
||||
@@ -574,16 +574,12 @@ func _register_buffs() -> void:
|
||||
|
||||
_try_unlock_buff_from_goal(buff)
|
||||
|
||||
func _evaluate_buff_unlock_goals() -> void:
|
||||
for buff in get_buffs():
|
||||
GameState._try_unlock_buff_from_goal(buff)
|
||||
|
||||
func _try_unlock_buff_from_goal(buff: GeneratorBuffData) -> void:
|
||||
GameState._try_unlock_buff_from_goal(buff)
|
||||
|
||||
func _on_currency_changed(_changed_currency_id: StringName, _new_amount: BigNumber) -> void:
|
||||
GameState.evaluate_all_goals()
|
||||
_evaluate_generator_unlock_goal(false)
|
||||
_evaluate_buff_unlock_goals()
|
||||
|
||||
func try_unlock_from_goal() -> bool:
|
||||
return _evaluate_generator_unlock_goal(true)
|
||||
@@ -597,12 +593,14 @@ func _evaluate_generator_unlock_goal(allow_manual_unlock: bool) -> bool:
|
||||
return false
|
||||
if not data.unlocks_automatically_from_goal() and not allow_manual_unlock:
|
||||
return false
|
||||
if not data.is_unlock_goal_met():
|
||||
|
||||
var goal_id: StringName = data.get_unlock_goal_id()
|
||||
if goal_id.is_empty() or not GameState.is_goal_completed(goal_id):
|
||||
return false
|
||||
|
||||
GameState.set_generator_unlocked(_generator_id, true)
|
||||
GameState.set_generator_available(_generator_id, true)
|
||||
goal_achieved.emit(_generator_id, data.get_unlock_goal_id())
|
||||
goal_achieved.emit(_generator_id, goal_id)
|
||||
return true
|
||||
|
||||
## Default unlocked state loaded from generator data.
|
||||
|
||||
@@ -88,7 +88,11 @@ func is_unlock_goal_met() -> bool:
|
||||
if unlock_goal == null:
|
||||
return false
|
||||
|
||||
return bool(unlock_goal.is_met())
|
||||
var goal_id: StringName = unlock_goal.id
|
||||
if goal_id.is_empty():
|
||||
return false
|
||||
|
||||
return GameState.is_goal_completed(goal_id)
|
||||
|
||||
func can_purchase_next_level(current_level: int) -> bool:
|
||||
if max_level < 0:
|
||||
|
||||
@@ -41,3 +41,19 @@ func get_first_requirement_summary() -> String:
|
||||
if valid_requirements.is_empty():
|
||||
return ""
|
||||
return String(valid_requirements[0].get_summary_text())
|
||||
|
||||
func get_progress() -> float:
|
||||
var valid_requirements: Array[GoalRequirementData] = get_valid_requirements()
|
||||
if valid_requirements.is_empty():
|
||||
return 0.0
|
||||
|
||||
var min_progress: float = 1.0
|
||||
for requirement in valid_requirements:
|
||||
if requirement == null or not requirement.is_valid():
|
||||
continue
|
||||
|
||||
var requirement_progress: float = requirement.get_progress()
|
||||
if requirement_progress < min_progress:
|
||||
min_progress = requirement_progress
|
||||
|
||||
return min_progress
|
||||
|
||||
@@ -40,3 +40,28 @@ func get_summary_text() -> String:
|
||||
|
||||
var currency_name: String = GameState.get_currency_name(get_currency_id())
|
||||
return "%s %s" % [get_amount().to_string_suffix(2), currency_name]
|
||||
|
||||
func get_progress() -> float:
|
||||
if not is_valid():
|
||||
return 0.0
|
||||
|
||||
var current_amount: BigNumber = GameState.get_total_currency_acquired_by_id(get_currency_id())
|
||||
var required_amount: BigNumber = get_amount()
|
||||
|
||||
if required_amount.mantissa <= 0.0:
|
||||
return 1.0
|
||||
|
||||
if current_amount.is_greater_than(required_amount) or current_amount.is_equal_to(required_amount):
|
||||
return 1.0
|
||||
|
||||
if current_amount.mantissa <= 0.0:
|
||||
return 0.0
|
||||
|
||||
var current_log: float = log(maxf(current_amount.mantissa, 0.0001)) / log(10) + float(current_amount.exponent)
|
||||
var required_log: float = log(maxf(required_amount.mantissa, 0.0001)) / log(10) + float(required_amount.exponent)
|
||||
|
||||
if required_log <= 0.0:
|
||||
return 1.0
|
||||
|
||||
var progress: float = current_log / required_log
|
||||
return clampf(progress, 0.0, 1.0)
|
||||
|
||||
@@ -38,12 +38,16 @@ func _ready() -> void:
|
||||
_build_goal_rows()
|
||||
GameState.currency_changed.connect(_on_currency_changed)
|
||||
GameState.generator_state_changed.connect(_on_generator_state_changed)
|
||||
GameState.goal_completed.connect(_on_goal_completed)
|
||||
_refresh_ui()
|
||||
_refresh_ui.call_deferred()
|
||||
|
||||
func _on_currency_changed(_currency_id: StringName, _new_amount: BigNumber) -> void:
|
||||
_refresh_ui()
|
||||
|
||||
func _on_goal_completed(goal_id: StringName) -> void:
|
||||
_refresh_ui()
|
||||
|
||||
func _on_generator_state_changed(generator_id: StringName, _state: Dictionary) -> void:
|
||||
var generator_key: String = String(generator_id)
|
||||
if not generator_key.is_empty():
|
||||
@@ -58,7 +62,7 @@ func _load_goals() -> void:
|
||||
var scene_root: Node = get_tree().current_scene
|
||||
var search_root: Node = scene_root if scene_root != null else self
|
||||
var generator_nodes: Array[Node] = search_root.find_children("*", "CurrencyGenerator", true, false)
|
||||
var seen_goal_ids: Dictionary = {}
|
||||
|
||||
for generator_node in generator_nodes:
|
||||
var generator: CurrencyGenerator = generator_node as CurrencyGenerator
|
||||
if generator == null:
|
||||
@@ -69,23 +73,21 @@ func _load_goals() -> void:
|
||||
continue
|
||||
_generators_by_id[String(generator_id)] = generator
|
||||
|
||||
if generator.data == null:
|
||||
continue
|
||||
if not generator.data.has_unlock_goal():
|
||||
var seen_goal_ids: Dictionary = {}
|
||||
for goal in GameState.get_all_goals():
|
||||
if goal == null or not goal.has_id():
|
||||
continue
|
||||
|
||||
var resolved_goal: GoalData = generator.data.unlock_goal
|
||||
if resolved_goal == null:
|
||||
continue
|
||||
if not bool(resolved_goal.is_valid()):
|
||||
continue
|
||||
var goal_key: String = String(resolved_goal.get("id"))
|
||||
var goal_key: String = String(goal.id)
|
||||
if seen_goal_ids.has(goal_key):
|
||||
push_warning("Goals debug UI: duplicate goal id '%s' skipped" % goal_key)
|
||||
continue
|
||||
|
||||
var target_generator_id: StringName = _find_generator_for_goal(goal)
|
||||
if target_generator_id.is_empty():
|
||||
continue
|
||||
|
||||
seen_goal_ids[goal_key] = true
|
||||
_loaded_goals.append(GoalDefinition.new(generator_id, resolved_goal))
|
||||
_loaded_goals.append(GoalDefinition.new(target_generator_id, goal))
|
||||
|
||||
func _build_goal_rows() -> void:
|
||||
_rows_by_goal_id.clear()
|
||||
@@ -188,10 +190,9 @@ func _is_goal_met(goal: GoalDefinition) -> bool:
|
||||
return bool(goal.goal.is_met())
|
||||
|
||||
func _is_goal_completed(goal: GoalDefinition) -> bool:
|
||||
return (
|
||||
GameState.is_generator_unlocked(goal.target_generator_id)
|
||||
and GameState.is_generator_available(goal.target_generator_id)
|
||||
)
|
||||
if goal.goal == null or goal.goal.id.is_empty():
|
||||
return false
|
||||
return GameState.is_goal_completed(goal.goal.id)
|
||||
|
||||
func _on_unlock_pressed(goal_id: StringName) -> void:
|
||||
var row: GoalRow = _rows_by_goal_id.get(String(goal_id))
|
||||
@@ -276,6 +277,16 @@ func _get_generator_for_goal(goal: GoalDefinition) -> CurrencyGenerator:
|
||||
|
||||
return _generators_by_id.get(key) as CurrencyGenerator
|
||||
|
||||
func _find_generator_for_goal(goal: GoalData) -> StringName:
|
||||
for generator_id in _generators_by_id.keys():
|
||||
var generator: CurrencyGenerator = _generators_by_id[generator_id]
|
||||
if generator == null or generator.data == null:
|
||||
continue
|
||||
if generator.data.has_unlock_goal():
|
||||
if String(generator.data.get_unlock_goal_id()) == String(goal.id):
|
||||
return StringName(generator_id)
|
||||
return &""
|
||||
|
||||
func _is_manual_goal_generator(generator: CurrencyGenerator) -> bool:
|
||||
if generator == null:
|
||||
return false
|
||||
@@ -311,6 +322,9 @@ func _get_requirements_text(goal: GoalDefinition) -> String:
|
||||
required_amount.to_string_suffix(2)
|
||||
]
|
||||
)
|
||||
|
||||
if parts.is_empty():
|
||||
return "No requirements"
|
||||
return " | ".join(parts)
|
||||
|
||||
func _currency_label(currency_id: StringName) -> String:
|
||||
|
||||
Reference in New Issue
Block a user