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
|
|
@ -32,7 +32,10 @@ func _validate_latency_smoothing() -> void:
|
|||
await process_frame
|
||||
avatar.set_process(false)
|
||||
avatar.set_physics_process(false)
|
||||
_validate_compact_input_encoding()
|
||||
_validate_compact_snapshot_encoding()
|
||||
_validate_compact_animation_encoding()
|
||||
_validate_animation_action_ordering(avatar)
|
||||
_validate_transit_estimation()
|
||||
_validate_remote_snapshot_smoothing(avatar)
|
||||
_validate_reliable_jump_intent(avatar)
|
||||
|
|
@ -41,6 +44,26 @@ func _validate_latency_smoothing() -> void:
|
|||
await process_frame
|
||||
|
||||
|
||||
func _validate_compact_input_encoding() -> void:
|
||||
var input: Dictionary = _movement_input(
|
||||
12,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
&"strike",
|
||||
4,
|
||||
true,
|
||||
)
|
||||
var encoded: Array = NetworkSession._encode_movement_input(input)
|
||||
assert(encoded.size() == NetworkSession.MOVEMENT_INPUT_FIELD_COUNT)
|
||||
assert(var_to_bytes(encoded).size() < var_to_bytes(input).size())
|
||||
assert(NetworkSession._decode_movement_input(encoded) == input)
|
||||
assert(NetworkSession._decode_movement_input(encoded.slice(0, 3)).is_empty())
|
||||
var unknown_flags: Array = encoded.duplicate()
|
||||
unknown_flags[3] = 1 << 12
|
||||
assert(NetworkSession._decode_movement_input(unknown_flags).is_empty())
|
||||
|
||||
|
||||
func _validate_compact_snapshot_encoding() -> void:
|
||||
var moving_snapshot: Dictionary = _network_snapshot(
|
||||
Vector3.ZERO,
|
||||
|
|
@ -54,31 +77,133 @@ func _validate_compact_snapshot_encoding() -> void:
|
|||
NetworkSession._encode_movement_snapshot(moving_snapshot)
|
||||
)
|
||||
assert(var_to_bytes(encoded_snapshots).size() < 1200)
|
||||
var expected_snapshot: Dictionary = moving_snapshot.duplicate(true)
|
||||
expected_snapshot.erase("animation_state")
|
||||
assert(
|
||||
NetworkSession._decode_movement_snapshot(encoded_snapshots[0])
|
||||
== expected_snapshot
|
||||
)
|
||||
assert(
|
||||
NetworkSession._decode_movement_snapshot(
|
||||
encoded_snapshots[0]
|
||||
) == moving_snapshot
|
||||
encoded_snapshots[0].slice(0, 4)
|
||||
).is_empty()
|
||||
)
|
||||
var paused_snapshot: Dictionary = moving_snapshot.duplicate(true)
|
||||
paused_snapshot["animation_state"] = (
|
||||
var invalid_snapshot_flags: Array = encoded_snapshots[0].duplicate()
|
||||
invalid_snapshot_flags[5] = 1 << 12
|
||||
assert(
|
||||
NetworkSession._decode_movement_snapshot(
|
||||
invalid_snapshot_flags
|
||||
).is_empty()
|
||||
)
|
||||
|
||||
|
||||
func _validate_compact_animation_encoding() -> void:
|
||||
var paused_state := NetworkPlayerAnimationProtocol.make_state(
|
||||
NetworkPlayerAnimationProtocol.LOCOMOTION_IDLE,
|
||||
true,
|
||||
&"strike",
|
||||
7,
|
||||
0.5,
|
||||
true,
|
||||
)
|
||||
var encoded: Array = NetworkSession._encode_movement_animation(
|
||||
2,
|
||||
paused_state,
|
||||
)
|
||||
assert(encoded.size() == NetworkSession.MOVEMENT_ANIMATION_FIELD_COUNT)
|
||||
var decoded: Dictionary = NetworkSession._decode_movement_animation(encoded)
|
||||
assert(int(decoded.get("peer_id", 0)) == 2)
|
||||
assert(Dictionary(decoded.get("state", {})) == paused_state)
|
||||
var advanced_state: Dictionary = paused_state.duplicate(true)
|
||||
advanced_state["action"]["elapsed"] = 0.75
|
||||
assert(
|
||||
NetworkSession._movement_animation_signature(advanced_state)
|
||||
== NetworkSession._movement_animation_signature(paused_state)
|
||||
)
|
||||
var action: Dictionary = paused_state["action"]
|
||||
var encoded_action: Array = (
|
||||
NetworkSession._encode_movement_animation_action(action, true)
|
||||
)
|
||||
assert(
|
||||
encoded_action.size()
|
||||
== NetworkSession.MOVEMENT_ANIMATION_ACTION_FIELD_COUNT
|
||||
)
|
||||
var decoded_action: Dictionary = (
|
||||
NetworkSession._decode_movement_animation_action(encoded_action)
|
||||
)
|
||||
assert(Dictionary(decoded_action.get("action", {})) == action)
|
||||
assert(bool(decoded_action.get("sitting", false)))
|
||||
assert(
|
||||
NetworkSession._decode_movement_animation_action(
|
||||
encoded_action.slice(0, 2)
|
||||
).is_empty()
|
||||
)
|
||||
|
||||
|
||||
func _validate_animation_action_ordering(avatar: Player) -> void:
|
||||
var draw := NetworkPlayerAnimationProtocol.make_action_state(
|
||||
&"draw", 5, 0.2
|
||||
)
|
||||
avatar.apply_authoritative_network_animation_action(draw)
|
||||
assert(StringName(str(avatar.get("_animation_action_id"))) == &"draw")
|
||||
var cleared := NetworkPlayerAnimationProtocol.make_action_state(
|
||||
&"", 6, 0.0
|
||||
)
|
||||
avatar.apply_authoritative_network_animation_action(cleared)
|
||||
assert(StringName(str(avatar.get("_animation_action_id"))).is_empty())
|
||||
avatar.apply_authoritative_network_animation_action(draw)
|
||||
assert(StringName(str(avatar.get("_animation_action_id"))).is_empty())
|
||||
avatar.apply_authoritative_network_animation_action(
|
||||
NetworkPlayerAnimationProtocol.make_action_state(&"strike", 6, 0.0)
|
||||
)
|
||||
assert(StringName(str(avatar.get("_animation_action_id"))).is_empty())
|
||||
var strike := NetworkPlayerAnimationProtocol.make_action_state(
|
||||
&"strike", 7, 0.2
|
||||
)
|
||||
avatar.apply_authoritative_network_animation_action(strike)
|
||||
avatar.apply_authoritative_network_animation_action(
|
||||
NetworkPlayerAnimationProtocol.make_action_state(
|
||||
&"strike", 8, 0.4, true
|
||||
)
|
||||
)
|
||||
avatar.apply_authoritative_network_animation_action(strike)
|
||||
assert(bool(avatar.get("_animation_action_paused")))
|
||||
avatar.apply_authoritative_network_animation_action(
|
||||
NetworkPlayerAnimationProtocol.make_action_state(&"", 9, 0.0)
|
||||
)
|
||||
|
||||
avatar.configure_network_remote(false)
|
||||
avatar.apply_network_animation_state(
|
||||
NetworkPlayerAnimationProtocol.make_state(
|
||||
NetworkPlayerAnimationProtocol.LOCOMOTION_IDLE,
|
||||
true,
|
||||
&"strike",
|
||||
7,
|
||||
0.5,
|
||||
9,
|
||||
0.4,
|
||||
true,
|
||||
)
|
||||
)
|
||||
var paused_encoded: Array = NetworkSession._encode_movement_snapshot(
|
||||
paused_snapshot
|
||||
avatar.apply_network_animation_state(
|
||||
NetworkPlayerAnimationProtocol.make_state(
|
||||
NetworkPlayerAnimationProtocol.LOCOMOTION_WALKING,
|
||||
true,
|
||||
&"draw",
|
||||
8,
|
||||
0.1,
|
||||
)
|
||||
)
|
||||
assert(paused_encoded.size() == NetworkSession.MOVEMENT_SNAPSHOT_FIELD_COUNT)
|
||||
var paused_decoded: Dictionary = (
|
||||
NetworkSession._decode_movement_snapshot(paused_encoded)
|
||||
assert(
|
||||
StringName(str(avatar.get("_network_target_animation_action_id")))
|
||||
== &"strike"
|
||||
)
|
||||
avatar.apply_network_animation_state(
|
||||
NetworkPlayerAnimationProtocol.make_state(
|
||||
NetworkPlayerAnimationProtocol.LOCOMOTION_IDLE,
|
||||
true,
|
||||
&"",
|
||||
10,
|
||||
)
|
||||
)
|
||||
assert(not paused_decoded.is_empty())
|
||||
assert(bool(paused_decoded["animation_state"]["action"]["paused"]))
|
||||
|
||||
|
||||
func _validate_transit_estimation() -> void:
|
||||
|
|
@ -158,14 +283,16 @@ func _validate_remote_snapshot_smoothing(avatar: Player) -> void:
|
|||
|
||||
avatar.set_local_control(true)
|
||||
avatar.global_position = Vector3(0.8, 0.0, 0.0)
|
||||
var replay_input: Dictionary = _movement_input(2, false)
|
||||
replay_input["axis"] = [1.0, 0.0]
|
||||
var pending_inputs: Array[Dictionary] = [replay_input]
|
||||
avatar.apply_local_prediction_correction(
|
||||
moving_snapshot,
|
||||
10,
|
||||
pending_inputs,
|
||||
1.0 / 30.0,
|
||||
0.1,
|
||||
)
|
||||
assert(avatar.global_position.x < 0.8)
|
||||
assert(avatar.global_position.x > 0.7)
|
||||
assert(avatar.global_position.x > 0.0)
|
||||
|
||||
|
||||
func _validate_reliable_jump_intent(avatar: Player) -> void:
|
||||
|
|
@ -178,9 +305,8 @@ func _validate_reliable_jump_intent(avatar: Player) -> void:
|
|||
assert(int(avatar.get("_local_network_jump_intent_sequence")) == 20)
|
||||
avatar.apply_local_prediction_correction(
|
||||
_network_snapshot(avatar.global_position, Vector3.ZERO, 20),
|
||||
21,
|
||||
[],
|
||||
1.0 / 30.0,
|
||||
0.0,
|
||||
)
|
||||
assert(not bool(avatar.capture_network_input(22)["jump"]))
|
||||
|
||||
|
|
@ -209,12 +335,24 @@ func _validate_reliable_jump_intent(avatar: Player) -> void:
|
|||
|
||||
func _validate_stale_input_expiry(avatar: Player) -> void:
|
||||
avatar.configure_network_remote(true)
|
||||
avatar.apply_authoritative_network_input(_movement_input(40, true))
|
||||
var high_latency_timeout := (
|
||||
Player.resolve_network_input_stale_timeout_seconds(400)
|
||||
)
|
||||
assert(high_latency_timeout > Player.NETWORK_INPUT_STALE_TIMEOUT_SECONDS)
|
||||
avatar.apply_authoritative_network_input(
|
||||
_movement_input(40, true),
|
||||
high_latency_timeout,
|
||||
)
|
||||
assert((avatar.get("_network_axis") as Vector2).length_squared() > 0.0)
|
||||
avatar.call(
|
||||
"_update_network_input_freshness",
|
||||
Player.NETWORK_INPUT_STALE_TIMEOUT_SECONDS + 0.01,
|
||||
)
|
||||
assert(not bool(avatar.get("_network_input_stale")))
|
||||
avatar.call(
|
||||
"_update_network_input_freshness",
|
||||
high_latency_timeout,
|
||||
)
|
||||
assert((avatar.get("_network_axis") as Vector2) == Vector2.ZERO)
|
||||
assert(not bool(avatar.get("_network_sprint")))
|
||||
assert(bool(avatar.get("_network_input_stale")))
|
||||
|
|
@ -234,6 +372,7 @@ func _network_snapshot(
|
|||
"position": [position.x, position.y, position.z],
|
||||
"velocity": [velocity.x, velocity.y, velocity.z],
|
||||
"visual_yaw": 0.0,
|
||||
"grounded": true,
|
||||
"animation_state": NetworkPlayerAnimationProtocol.make_state(
|
||||
NetworkPlayerAnimationProtocol.LOCOMOTION_RUNNING,
|
||||
true,
|
||||
|
|
@ -247,8 +386,15 @@ func _run_host() -> void:
|
|||
var main: Node = await _create_initialized_main()
|
||||
var session := main.get_node("%NetworkSession") as NetworkSession
|
||||
var save_manager := main.get("_save_manager") as PlayerSaveManager
|
||||
assert(session.set_host_world(
|
||||
WorldLayout.STARTER_ISLAND,
|
||||
NetworkProtocol.DEFAULT_WORLD_SEED,
|
||||
))
|
||||
assert(session.start_private_host(TEST_PORT))
|
||||
assert(save_manager.initialize_new_game())
|
||||
assert(save_manager.initialize_new_game(
|
||||
NetworkProtocol.DEFAULT_WORLD_SEED,
|
||||
WorldLayout.STARTER_ISLAND,
|
||||
))
|
||||
main.call("_enter_gameplay")
|
||||
for _frame: int in 4:
|
||||
await physics_frame
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue