Add catch experience and persistent world time

This commit is contained in:
Alexander Sellite 2026-08-02 19:01:16 -04:00
parent 35d00e8035
commit 291cc4a713
23 changed files with 1027 additions and 27 deletions

View file

@ -21,8 +21,12 @@ const PlayerCoolerCapacityType = preload(
const PlayerArtUnlocksType = preload(
"res://progression/player_art_unlocks.gd"
)
const PlayerExperienceType = preload(
"res://progression/player_experience.gd"
)
const WorldTimeServiceType = preload("res://world/world_time_service.gd")
const SAVE_VERSION: int = 5
const SAVE_VERSION: int = 6
const BASIC_ROD_ID: StringName = &"basic_fishing_rod"
const MAX_SAFE_BALANCE: int = 1000000000000
@ -42,6 +46,8 @@ class LoadSnapshot:
var barrier_power_level: int = 0
var cooler_capacity_level: int = 0
var art_unlock_mask: int = 0
var total_experience: int = 0
var world_time_hours: float = WorldTimeServiceType.DEFAULT_START_HOUR
@export_range(0.05, 5.0, 0.05) var autosave_delay: float = 0.5
@ -56,6 +62,8 @@ var _item_catalog: ItemCatalogType
var _fishing_upgrades: PlayerFishingUpgradesType
var _cooler_capacity: PlayerCoolerCapacityType
var _art_unlocks: PlayerArtUnlocksType
var _experience: PlayerExperienceType
var _world_time: WorldTimeServiceType
var _autosave_timer: Timer
var _is_configured: bool = false
var _is_restoring: bool = false
@ -98,6 +106,8 @@ func setup(
fishing_upgrades: PlayerFishingUpgradesType,
cooler_capacity: PlayerCoolerCapacityType,
art_unlocks: PlayerArtUnlocksType,
experience: PlayerExperienceType,
world_time: WorldTimeServiceType,
) -> void:
_inventory = inventory
_collection_log = collection_log
@ -109,6 +119,8 @@ func setup(
_fishing_upgrades = fishing_upgrades
_cooler_capacity = cooler_capacity
_art_unlocks = art_unlocks
_experience = experience
_world_time = world_time
_is_configured = (
_inventory != null
and _collection_log != null
@ -120,6 +132,8 @@ func setup(
and _fishing_upgrades != null
and _cooler_capacity != null
and _art_unlocks != null
and _experience != null
and _world_time != null
)
if not _is_configured:
push_error("PlayerSaveManager setup is missing required references.")
@ -154,6 +168,10 @@ func setup(
)
if not _art_unlocks.unlocks_changed.is_connected(_on_art_unlocks_changed):
_art_unlocks.unlocks_changed.connect(_on_art_unlocks_changed)
if not _experience.experience_changed.is_connected(
_on_experience_changed
):
_experience.experience_changed.connect(_on_experience_changed)
func load_player_data() -> bool:
@ -234,6 +252,12 @@ func load_player_data() -> bool:
var art_restored: bool = _art_unlocks.restore_mask(
snapshot.art_unlock_mask
)
var experience_restored: bool = _experience.restore_total_experience(
snapshot.total_experience
)
var world_time_restored: bool = (
_world_time.restore_persistent_time_hours(snapshot.world_time_hours)
)
_is_restoring = false
if (
not inventory_restored
@ -244,6 +268,8 @@ func load_player_data() -> bool:
or not upgrades_restored
or not cooler_restored
or not art_restored
or not experience_restored
or not world_time_restored
):
push_error("Validated player save could not be restored.")
return false
@ -337,6 +363,17 @@ func save_if_dirty() -> bool:
return not _is_dirty or save_now()
func save_world_time_checkpoint() -> bool:
if (
not _is_configured
or _automatic_saving_blocked
or not _autosave_enabled
):
return false
_is_dirty = true
return save_now()
func is_dirty() -> bool:
return _is_dirty
@ -465,6 +502,10 @@ func _build_save_dictionary() -> Dictionary:
"upgrades": _fishing_upgrades.to_save_data(),
"cooler": _cooler_capacity.to_save_data(),
"art": _art_unlocks.to_save_data(),
"experience": _experience.to_save_data(),
"world": {
"time_hours": _world_time.get_persistent_time_hours(),
},
}
@ -475,6 +516,7 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
or typeof(save_data.get("inventory")) != TYPE_DICTIONARY
or typeof(save_data.get("bag")) != TYPE_DICTIONARY
or typeof(save_data.get("hotbar")) != TYPE_DICTIONARY
or typeof(save_data.get("experience")) != TYPE_DICTIONARY
):
return null
var wallet_data: Dictionary = save_data["wallet"]
@ -482,6 +524,10 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
var inventory_data: Dictionary = save_data["inventory"]
var bag_data: Dictionary = save_data["bag"]
var hotbar_data: Dictionary = save_data["hotbar"]
var experience_data: Dictionary = save_data["experience"]
var world_data: Dictionary = {}
if typeof(save_data.get("world")) == TYPE_DICTIONARY:
world_data = save_data["world"]
var upgrades_data: Dictionary = {}
var cooler_data: Dictionary = {}
var art_data: Dictionary = {}
@ -507,6 +553,7 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
or typeof(bag_data.get("items")) != TYPE_ARRAY
or typeof(hotbar_data.get("slots")) != TYPE_ARRAY
or not hotbar_data.has("selected_slot")
or not experience_data.has("total_experience")
):
return null
@ -525,6 +572,19 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
var snapshot := LoadSnapshot.new()
snapshot.wallet_balance = balance
snapshot.total_experience = _read_integer(
experience_data["total_experience"],
-1,
PlayerExperienceType.MAX_TOTAL_EXPERIENCE,
)
if snapshot.total_experience < 0:
return null
if world_data.has("time_hours"):
snapshot.world_time_hours = _read_world_time_hours(
world_data["time_hours"]
)
if snapshot.world_time_hours < 0.0:
return null
var discovered_values: Array = collection_data["discovered_fish_ids"]
var seen_discoveries: Dictionary[StringName, bool] = {}
for value: Variant in discovered_values:
@ -744,6 +804,8 @@ func _migrate_save(
migrated = _migrate_version_3_to_4(migrated)
4:
migrated = _migrate_version_4_to_5(migrated)
5:
migrated = _migrate_version_5_to_6(migrated)
_:
return {}
if migrated.is_empty():
@ -843,6 +905,16 @@ func _migrate_version_4_to_5(data: Dictionary) -> Dictionary:
return migrated
func _migrate_version_5_to_6(data: Dictionary) -> Dictionary:
var migrated: Dictionary = data.duplicate(true)
migrated["save_version"] = 6
migrated["experience"] = {"total_experience": 0}
migrated["world"] = {
"time_hours": WorldTimeServiceType.DEFAULT_START_HOUR,
}
return migrated
func _mark_dirty() -> void:
if (
_is_restoring
@ -888,6 +960,10 @@ func _on_art_unlocks_changed(_unlock_mask: int) -> void:
_mark_dirty()
func _on_experience_changed(_total_experience: int, _level: int) -> void:
_mark_dirty()
func _on_autosave_timeout() -> void:
if _is_dirty:
save_now()
@ -949,6 +1025,10 @@ func _restore_defaults() -> void:
_fishing_upgrades.reset_to_defaults()
_cooler_capacity.reset_to_defaults()
_art_unlocks.reset_to_defaults()
_experience.reset_to_defaults()
_world_time.restore_persistent_time_hours(
WorldTimeServiceType.DEFAULT_START_HOUR
)
_is_restoring = false
_is_dirty = false
@ -974,6 +1054,19 @@ func _read_integer(
return invalid_value
func _read_world_time_hours(value: Variant) -> float:
if typeof(value) not in [TYPE_FLOAT, TYPE_INT]:
return -1.0
var time_hours: float = float(value)
if (
not is_finite(time_hours)
or time_hours < 0.0
or time_hours >= WorldTimeServiceType.HOURS_PER_DAY
):
return -1.0
return time_hours
func _read_upgrade_level(
value: Variant,
maximum_level: int,