Split reliable Protocol V2 player state
This commit is contained in:
@@ -14,6 +14,7 @@ from ban_store import BanStore
|
||||
from client_session import ClientSession
|
||||
from lan_discovery import DISCOVERY_PORT, LanDiscoveryResponder
|
||||
from npc_authority import NpcAuthorityManager, ScopeKey, scope_from_transform
|
||||
from player_state import normalize_player_state_packet
|
||||
from world_state_presets import normalize_fw_console_arg, relay_weather_form_id
|
||||
|
||||
HOST = "0.0.0.0"
|
||||
@@ -222,6 +223,7 @@ class FalloutTogetherServer:
|
||||
self._server_world_state: dict[str, str] = {}
|
||||
self._last_npc_state: dict[str, Any] | None = None
|
||||
self._last_npc_state_by_scope: dict[ScopeKey, dict[str, Any]] = {}
|
||||
self._last_player_state_by_player_id: dict[int, dict[str, Any]] = {}
|
||||
self._npc_authority = NpcAuthorityManager()
|
||||
self._connect_attempts: dict[str, deque[float]] = {}
|
||||
|
||||
@@ -237,6 +239,8 @@ class FalloutTogetherServer:
|
||||
"transformPacketsReceived": 0,
|
||||
"transformPacketsBroadcast": 0,
|
||||
"transformPacketsInterestFiltered": 0,
|
||||
"playerStatePacketsReceived": 0,
|
||||
"playerStatePacketsBroadcast": 0,
|
||||
"movementPacketsRejected": 0,
|
||||
"movementCorrectionsSent": 0,
|
||||
"worldStatePacketsReceived": 0,
|
||||
@@ -443,6 +447,7 @@ class FalloutTogetherServer:
|
||||
self._world_state_host_player_id = None
|
||||
self._last_npc_state = None
|
||||
self._last_npc_state_by_scope.clear()
|
||||
self._last_player_state_by_player_id.clear()
|
||||
self._npc_authority.clear()
|
||||
self._connect_attempts.clear()
|
||||
|
||||
@@ -568,6 +573,7 @@ class FalloutTogetherServer:
|
||||
},
|
||||
)
|
||||
self._send_existing_transforms_to_client(client)
|
||||
self._send_existing_player_states_to_client(client)
|
||||
self._send_existing_npc_state_to_client(client)
|
||||
if became_host:
|
||||
self._broadcast_world_state_host_assignment(client.player_id)
|
||||
@@ -612,6 +618,7 @@ class FalloutTogetherServer:
|
||||
"rate-limit-v1",
|
||||
"movement-correction-v1",
|
||||
"npc-authority-epoch-v1",
|
||||
"player-state-v1",
|
||||
],
|
||||
},
|
||||
)
|
||||
@@ -728,6 +735,9 @@ class FalloutTogetherServer:
|
||||
if normalized is None:
|
||||
self._reject_packet(client, "Malformed transform")
|
||||
return False
|
||||
if client.protocol_version >= PROTOCOL_VERSION:
|
||||
for reliable_field in ("equippedItems", "appearance", "actionEvents", "characterName"):
|
||||
normalized.pop(reliable_field, None)
|
||||
accepted_monotonic = time.monotonic()
|
||||
movement_valid, movement_reason = self._validate_transform_movement(client, normalized, accepted_monotonic)
|
||||
if not movement_valid:
|
||||
@@ -749,6 +759,22 @@ class FalloutTogetherServer:
|
||||
self._send_npc_authority_for_client(client, current_scope)
|
||||
return True
|
||||
|
||||
if packet_type == "playerState":
|
||||
if client.protocol_version < PROTOCOL_VERSION:
|
||||
self._reject_packet(client, "playerState requires Protocol V2", warning=False)
|
||||
return False
|
||||
normalized = normalize_player_state_packet(packet, _normalize_action_events)
|
||||
if normalized is None:
|
||||
self._reject_packet(client, "Malformed playerState")
|
||||
return False
|
||||
normalized["playerId"] = client.player_id
|
||||
normalized["serverTime"] = time.time()
|
||||
with self._lock:
|
||||
self._last_player_state_by_player_id[client.player_id] = dict(normalized)
|
||||
self._stats["playerStatePacketsReceived"] += 1
|
||||
self._broadcast_player_state(client, normalized)
|
||||
return True
|
||||
|
||||
if packet_type == "worldState":
|
||||
if not self._is_world_host(client):
|
||||
self._reject_packet(client, "worldState from non-authority client", warning=False)
|
||||
@@ -1108,6 +1134,27 @@ class FalloutTogetherServer:
|
||||
with self._lock:
|
||||
self._stats["transformPacketsBroadcast"] += successful
|
||||
|
||||
def _send_existing_player_states_to_client(self, new_client: ClientSession) -> None:
|
||||
if new_client.protocol_version < PROTOCOL_VERSION:
|
||||
return
|
||||
with self._lock:
|
||||
snapshots = [
|
||||
dict(packet)
|
||||
for player_id, packet in self._last_player_state_by_player_id.items()
|
||||
if player_id != new_client.player_id
|
||||
]
|
||||
successful = 0
|
||||
for packet in snapshots:
|
||||
packet["serverTime"] = time.time()
|
||||
try:
|
||||
self._send_packet(new_client, packet, broadcast=True)
|
||||
successful += 1
|
||||
except (OSError, ValueError):
|
||||
self._disconnect_client(new_client)
|
||||
break
|
||||
with self._lock:
|
||||
self._stats["playerStatePacketsBroadcast"] += successful
|
||||
|
||||
def _broadcast_transform(self, sender: ClientSession, packet: dict[str, Any]) -> None:
|
||||
with self._lock:
|
||||
recipients = [client for client in self._active_clients_locked() if client.connection != sender.connection]
|
||||
@@ -1129,6 +1176,13 @@ class FalloutTogetherServer:
|
||||
for recipient in failed:
|
||||
self._disconnect_client(recipient)
|
||||
|
||||
def _broadcast_player_state(self, sender: ClientSession, packet: dict[str, Any]) -> None:
|
||||
successful, failed = self._broadcast_to_active(packet, exclude=sender, v2_only=True)
|
||||
with self._lock:
|
||||
self._stats["playerStatePacketsBroadcast"] += successful
|
||||
for client in failed:
|
||||
self._disconnect_client(client)
|
||||
|
||||
def _broadcast_world_state(self, sender: ClientSession, packet: dict[str, Any]) -> None:
|
||||
successful, failed = self._broadcast_to_active(packet, exclude=sender)
|
||||
with self._lock:
|
||||
@@ -1280,6 +1334,8 @@ class FalloutTogetherServer:
|
||||
def _remove_client(self, client: ClientSession) -> bool:
|
||||
with self._lock:
|
||||
removed = self._clients.pop(client.connection, None) is not None
|
||||
if removed:
|
||||
self._last_player_state_by_player_id.pop(client.player_id, None)
|
||||
if removed and client.gameplay_active:
|
||||
self._stats["clientsDisconnected"] += 1
|
||||
return removed
|
||||
|
||||
Reference in New Issue
Block a user