|
|
|
|
@@ -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
|
|
|
|
|
|