Add synchronized Fishnet jobs and laptop UI

This commit is contained in:
Alexander Sellite 2026-08-03 00:43:43 -04:00
parent 46df74361a
commit 26fbd41132
39 changed files with 3257 additions and 188 deletions

View file

@ -25,8 +25,12 @@ const PlayerExperienceType = preload(
"res://progression/player_experience.gd"
)
const WorldTimeServiceType = preload("res://world/world_time_service.gd")
const WorldWeatherServiceType = preload(
"res://world/world_weather_service.gd"
)
const PlayerJobServiceType = preload("res://jobs/player_job_service.gd")
const SAVE_VERSION: int = 6
const SAVE_VERSION: int = 7
const BASIC_ROD_ID: StringName = &"basic_fishing_rod"
const MAX_SAFE_BALANCE: int = 1000000000000
@ -48,6 +52,14 @@ class LoadSnapshot:
var art_unlock_mask: int = 0
var total_experience: int = 0
var world_time_hours: float = WorldTimeServiceType.DEFAULT_START_HOUR
var has_world_weather_state: bool = false
var world_weather: WorldWeatherServiceType.Weather = (
WorldWeatherServiceType.DEFAULT_WEATHER
)
var world_weather_seconds_remaining: float = (
WorldWeatherServiceType.SUNNY_DURATION_RANGE.x
)
var jobs_data: Dictionary = PlayerJobServiceType.default_save_data()
@export_range(0.05, 5.0, 0.05) var autosave_delay: float = 0.5
@ -64,6 +76,8 @@ var _cooler_capacity: PlayerCoolerCapacityType
var _art_unlocks: PlayerArtUnlocksType
var _experience: PlayerExperienceType
var _world_time: WorldTimeServiceType
var _world_weather: WorldWeatherServiceType
var _jobs: PlayerJobServiceType
var _autosave_timer: Timer
var _is_configured: bool = false
var _is_restoring: bool = false
@ -108,6 +122,8 @@ func setup(
art_unlocks: PlayerArtUnlocksType,
experience: PlayerExperienceType,
world_time: WorldTimeServiceType,
world_weather: WorldWeatherServiceType,
jobs: PlayerJobServiceType,
) -> void:
_inventory = inventory
_collection_log = collection_log
@ -121,6 +137,8 @@ func setup(
_art_unlocks = art_unlocks
_experience = experience
_world_time = world_time
_world_weather = world_weather
_jobs = jobs
_is_configured = (
_inventory != null
and _collection_log != null
@ -134,6 +152,8 @@ func setup(
and _art_unlocks != null
and _experience != null
and _world_time != null
and _world_weather != null
and _jobs != null
)
if not _is_configured:
push_error("PlayerSaveManager setup is missing required references.")
@ -172,6 +192,8 @@ func setup(
_on_experience_changed
):
_experience.experience_changed.connect(_on_experience_changed)
if not _jobs.changed.is_connected(_mark_dirty):
_jobs.changed.connect(_mark_dirty)
func load_player_data() -> bool:
@ -258,6 +280,13 @@ func load_player_data() -> bool:
var world_time_restored: bool = (
_world_time.restore_persistent_time_hours(snapshot.world_time_hours)
)
var world_weather_restored: bool = true
if snapshot.has_world_weather_state:
world_weather_restored = _world_weather.restore_persistent_state(
snapshot.world_weather,
snapshot.world_weather_seconds_remaining,
)
var jobs_restored: bool = _jobs.restore_from_save_data(snapshot.jobs_data)
_is_restoring = false
if (
not inventory_restored
@ -270,6 +299,8 @@ func load_player_data() -> bool:
or not art_restored
or not experience_restored
or not world_time_restored
or not world_weather_restored
or not jobs_restored
):
push_error("Validated player save could not be restored.")
return false
@ -505,7 +536,12 @@ func _build_save_dictionary() -> Dictionary:
"experience": _experience.to_save_data(),
"world": {
"time_hours": _world_time.get_persistent_time_hours(),
"weather": int(_world_weather.get_persistent_weather()),
"weather_seconds_remaining": (
_world_weather.get_persistent_seconds_remaining()
),
},
"jobs": _jobs.to_save_data(),
}
@ -517,6 +553,7 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
or typeof(save_data.get("bag")) != TYPE_DICTIONARY
or typeof(save_data.get("hotbar")) != TYPE_DICTIONARY
or typeof(save_data.get("experience")) != TYPE_DICTIONARY
or typeof(save_data.get("jobs")) != TYPE_DICTIONARY
):
return null
var wallet_data: Dictionary = save_data["wallet"]
@ -525,6 +562,7 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
var bag_data: Dictionary = save_data["bag"]
var hotbar_data: Dictionary = save_data["hotbar"]
var experience_data: Dictionary = save_data["experience"]
var jobs_data: Dictionary = save_data["jobs"]
var world_data: Dictionary = {}
if typeof(save_data.get("world")) == TYPE_DICTIONARY:
world_data = save_data["world"]
@ -579,12 +617,40 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
)
if snapshot.total_experience < 0:
return null
if not PlayerJobServiceType.validate_save_data(jobs_data):
return null
snapshot.jobs_data = jobs_data.duplicate(true)
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 has_weather: bool = world_data.has("weather")
var has_weather_seconds: bool = world_data.has(
"weather_seconds_remaining"
)
if has_weather != has_weather_seconds:
return null
if has_weather:
var weather_value: int = _read_integer(
world_data["weather"],
-1,
WorldWeatherServiceType.Weather.FOGGY,
)
var weather_seconds: float = _read_world_weather_seconds(
world_data["weather_seconds_remaining"]
)
if (
not WorldWeatherServiceType.is_valid_weather(weather_value)
or weather_seconds < 0.0
):
return null
snapshot.has_world_weather_state = true
snapshot.world_weather = (
weather_value as WorldWeatherServiceType.Weather
)
snapshot.world_weather_seconds_remaining = weather_seconds
var discovered_values: Array = collection_data["discovered_fish_ids"]
var seen_discoveries: Dictionary[StringName, bool] = {}
for value: Variant in discovered_values:
@ -806,6 +872,8 @@ func _migrate_save(
migrated = _migrate_version_4_to_5(migrated)
5:
migrated = _migrate_version_5_to_6(migrated)
6:
migrated = _migrate_version_6_to_7(migrated)
_:
return {}
if migrated.is_empty():
@ -915,6 +983,15 @@ func _migrate_version_5_to_6(data: Dictionary) -> Dictionary:
return migrated
func _migrate_version_6_to_7(data: Dictionary) -> Dictionary:
var migrated: Dictionary = data.duplicate(true)
migrated["save_version"] = 7
# Historical catch and sale totals cannot be reconstructed honestly from
# current inventory. New cumulative counters begin at feature introduction.
migrated["jobs"] = PlayerJobServiceType.default_save_data()
return migrated
func _mark_dirty() -> void:
if (
_is_restoring
@ -1029,6 +1106,8 @@ func _restore_defaults() -> void:
_world_time.restore_persistent_time_hours(
WorldTimeServiceType.DEFAULT_START_HOUR
)
_world_weather.reset_persistent_state()
_jobs.reset_to_defaults()
_is_restoring = false
_is_dirty = false
@ -1067,6 +1146,19 @@ func _read_world_time_hours(value: Variant) -> float:
return time_hours
func _read_world_weather_seconds(value: Variant) -> float:
if typeof(value) not in [TYPE_FLOAT, TYPE_INT]:
return -1.0
var seconds_remaining: float = float(value)
if (
not is_finite(seconds_remaining)
or seconds_remaining < 0.0
or seconds_remaining > WorldWeatherServiceType.MAX_PERSISTED_SECONDS
):
return -1.0
return seconds_remaining
func _read_upgrade_level(
value: Variant,
maximum_level: int,