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