feat: expand progression and multiplayer systems
Add named save slots, progression import/export, and a unified play flow. Add live friend requests, presence, invitations, and relationship controls without durable discovery-server social storage. Advance the network protocol with isolated channels, movement reconciliation, late-join recovery, fishing replication, and animation synchronization. Preserve per-species catch totals, refine generated-world startup and water recovery, and complete the related input and interface improvements.
This commit is contained in:
parent
1db1a5b754
commit
3b84bfe3a0
97 changed files with 7869 additions and 982 deletions
|
|
@ -124,6 +124,7 @@ static func migrate_active_to(
|
|||
for relative: String in [
|
||||
"player/player_save.nfsave",
|
||||
"player/player_save.json",
|
||||
"player/save_slots.json",
|
||||
"player/network_profile.json",
|
||||
"player/player_appearance.json",
|
||||
"social/saved_servers.json",
|
||||
|
|
@ -142,6 +143,16 @@ static func migrate_active_to(
|
|||
):
|
||||
_remove_tree(staging)
|
||||
return {"ok": false, "message": "Migration validation failed for %s." % relative}
|
||||
var source_slots: String = data_root.root_path.path_join("player/saves")
|
||||
if (
|
||||
DirAccess.dir_exists_absolute(source_slots)
|
||||
and not _copy_progression_slots(
|
||||
source_slots,
|
||||
staging.path_join("player/saves"),
|
||||
)
|
||||
):
|
||||
_remove_tree(staging)
|
||||
return {"ok": false, "message": "Migration validation failed for save slots."}
|
||||
if DirAccess.dir_exists_absolute(normalized):
|
||||
DirAccess.remove_absolute(normalized)
|
||||
if DirAccess.rename_absolute(staging, normalized) != OK:
|
||||
|
|
@ -257,7 +268,7 @@ static func _copy_verified_owned_file(
|
|||
source: String,
|
||||
destination: String,
|
||||
) -> Dictionary:
|
||||
if source.get_file() != "player_save.nfsave":
|
||||
if source.get_extension().to_lower() != "nfsave":
|
||||
return _copy_verified_json(source, destination)
|
||||
var decoded: Dictionary = ProgressionSaveCodec.read_local_save(source)
|
||||
if not bool(decoded.get("ok", false)):
|
||||
|
|
@ -281,12 +292,42 @@ static func _valid_owned_data(filename: String, data: Dictionary) -> bool:
|
|||
if filename == "player_save.json":
|
||||
var version: int = int(data.get("save_version", -1))
|
||||
return version >= 1 and version <= PlayerSaveManager.SAVE_VERSION
|
||||
if filename == "save_slots.json":
|
||||
return (
|
||||
int(data.get("format_version", -1)) == PlayerSaveSlotCatalog.FORMAT_VERSION
|
||||
and typeof(data.get("active_slot_id")) == TYPE_STRING
|
||||
and typeof(data.get("slots")) == TYPE_ARRAY
|
||||
)
|
||||
return (
|
||||
filename not in LEGACY_FILES
|
||||
or int(data.get("format_version", -1)) == 1
|
||||
)
|
||||
|
||||
|
||||
static func _copy_progression_slots(source: String, destination: String) -> bool:
|
||||
if DirAccess.make_dir_recursive_absolute(destination) != OK:
|
||||
return false
|
||||
var access := DirAccess.open(source)
|
||||
if access == null:
|
||||
return false
|
||||
access.list_dir_begin()
|
||||
var filename: String = access.get_next()
|
||||
while not filename.is_empty():
|
||||
if (
|
||||
not access.current_is_dir()
|
||||
and filename.get_extension().to_lower() == "nfsave"
|
||||
and not bool(_copy_verified_owned_file(
|
||||
source.path_join(filename),
|
||||
destination.path_join(filename),
|
||||
).get("ok", false))
|
||||
):
|
||||
access.list_dir_end()
|
||||
return false
|
||||
filename = access.get_next()
|
||||
access.list_dir_end()
|
||||
return true
|
||||
|
||||
|
||||
static func _copy_bytes(source: String, destination: String) -> bool:
|
||||
return _write_bytes(destination, PortableFileGuard.read_bytes(source))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue