修复多人模式成绩问题
This commit is contained in:
@@ -666,7 +666,13 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
if old != MultiplayerUserState.PLAYING:
|
if old != MultiplayerUserState.PLAYING:
|
||||||
raise InvokeException(f"Cannot change state from {old} to {new}")
|
raise InvokeException(f"Cannot change state from {old} to {new}")
|
||||||
case MultiplayerUserState.RESULTS:
|
case MultiplayerUserState.RESULTS:
|
||||||
raise InvokeException(f"Cannot change state from {old} to {new}")
|
# Allow server-managed transitions to RESULTS state
|
||||||
|
# This includes spectators who need to see results
|
||||||
|
if old not in (
|
||||||
|
MultiplayerUserState.FINISHED_PLAY,
|
||||||
|
MultiplayerUserState.SPECTATING, # Allow spectators to see results
|
||||||
|
):
|
||||||
|
raise InvokeException(f"Cannot change state from {old} to {new}")
|
||||||
case MultiplayerUserState.SPECTATING:
|
case MultiplayerUserState.SPECTATING:
|
||||||
# Enhanced spectator validation - allow transitions from more states
|
# Enhanced spectator validation - allow transitions from more states
|
||||||
# This matches official osu-server-spectator behavior
|
# This matches official osu-server-spectator behavior
|
||||||
@@ -766,6 +772,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
f"[MultiplayerHub] Spectator {user.user_id} joining during active gameplay"
|
f"[MultiplayerHub] Spectator {user.user_id} joining during active gameplay"
|
||||||
)
|
)
|
||||||
await self.call_noblock(client, "LoadRequested")
|
await self.call_noblock(client, "LoadRequested")
|
||||||
|
|
||||||
|
# Also sync the spectator with current game state
|
||||||
|
await self._send_current_gameplay_state_to_spectator(client, room)
|
||||||
|
|
||||||
async def _send_current_gameplay_state_to_spectator(
|
async def _send_current_gameplay_state_to_spectator(
|
||||||
self, client: Client, room: ServerMultiplayerRoom
|
self, client: Client, room: ServerMultiplayerRoom
|
||||||
@@ -780,13 +789,22 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
|
|
||||||
# Send current user states for all players
|
# Send current user states for all players
|
||||||
for room_user in room.room.users:
|
for room_user in room.room.users:
|
||||||
if room_user.state.is_playing:
|
if room_user.state.is_playing or room_user.state == MultiplayerUserState.RESULTS:
|
||||||
await self.call_noblock(
|
await self.call_noblock(
|
||||||
client,
|
client,
|
||||||
"UserStateChanged",
|
"UserStateChanged",
|
||||||
room_user.user_id,
|
room_user.user_id,
|
||||||
room_user.state,
|
room_user.state,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If the room is in OPEN state but we have users in RESULTS state,
|
||||||
|
# this means the game just finished and we should send ResultsReady
|
||||||
|
if (room.room.state == MultiplayerRoomState.OPEN and
|
||||||
|
any(u.state == MultiplayerUserState.RESULTS for u in room.room.users)):
|
||||||
|
logger.debug(
|
||||||
|
f"[MultiplayerHub] Sending ResultsReady to new spectator {client.user_id}"
|
||||||
|
)
|
||||||
|
await self.call_noblock(client, "ResultsReady")
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[MultiplayerHub] Sent current gameplay state to spectator {client.user_id}"
|
f"[MultiplayerHub] Sent current gameplay state to spectator {client.user_id}"
|
||||||
@@ -829,6 +847,15 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
room_user.state,
|
room_user.state,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Critical fix: If room is OPEN but has users in RESULTS state,
|
||||||
|
# send ResultsReady to new joiners (including spectators)
|
||||||
|
if (room.room.state == MultiplayerRoomState.OPEN and
|
||||||
|
any(u.state == MultiplayerUserState.RESULTS for u in room.room.users)):
|
||||||
|
logger.info(
|
||||||
|
f"[MultiplayerHub] Sending ResultsReady to newly joined user {client.user_id}"
|
||||||
|
)
|
||||||
|
await self.call_noblock(client, "ResultsReady")
|
||||||
|
|
||||||
# Critical addition: Send current playing users to SpectatorHub for cross-hub sync
|
# Critical addition: Send current playing users to SpectatorHub for cross-hub sync
|
||||||
# This ensures spectators can watch multiplayer players properly
|
# This ensures spectators can watch multiplayer players properly
|
||||||
await self._sync_with_spectator_hub(client, room)
|
await self._sync_with_spectator_hub(client, room)
|
||||||
@@ -852,8 +879,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
# Import here to avoid circular imports
|
# Import here to avoid circular imports
|
||||||
from app.signalr.hub import SpectatorHubs
|
from app.signalr.hub import SpectatorHubs
|
||||||
|
|
||||||
# For each playing user in the room, check if they have SpectatorHub state
|
# For each user in the room, check their state and sync appropriately
|
||||||
# and notify the new client about their playing status
|
|
||||||
for room_user in room.room.users:
|
for room_user in room.room.users:
|
||||||
if room_user.state.is_playing:
|
if room_user.state.is_playing:
|
||||||
spectator_state = SpectatorHubs.state.get(room_user.user_id)
|
spectator_state = SpectatorHubs.state.get(room_user.user_id)
|
||||||
@@ -869,6 +895,35 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
f"[MultiplayerHub] Synced spectator state for user {room_user.user_id} "
|
f"[MultiplayerHub] Synced spectator state for user {room_user.user_id} "
|
||||||
f"to new client {client.user_id}"
|
f"to new client {client.user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Critical addition: Notify SpectatorHub about users in RESULTS state
|
||||||
|
elif room_user.state == MultiplayerUserState.RESULTS:
|
||||||
|
# Create a synthetic finished state for cross-hub spectating
|
||||||
|
try:
|
||||||
|
from app.models.spectator_hub import SpectatedUserState, SpectatorState
|
||||||
|
|
||||||
|
finished_state = SpectatorState(
|
||||||
|
beatmap_id=room.queue.current_item.beatmap_id,
|
||||||
|
ruleset_id=room_user.ruleset_id or 0,
|
||||||
|
mods=room_user.mods,
|
||||||
|
state=SpectatedUserState.Passed, # Assume passed for results
|
||||||
|
maximum_statistics={},
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.call_noblock(
|
||||||
|
client,
|
||||||
|
"UserFinishedPlaying",
|
||||||
|
room_user.user_id,
|
||||||
|
finished_state,
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"[MultiplayerHub] Sent synthetic finished state for user {room_user.user_id} "
|
||||||
|
f"to client {client.user_id}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(
|
||||||
|
f"[MultiplayerHub] Failed to create synthetic finished state: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[MultiplayerHub] Failed to sync with SpectatorHub: {e}")
|
logger.debug(f"[MultiplayerHub] Failed to sync with SpectatorHub: {e}")
|
||||||
@@ -913,6 +968,8 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
u.state != MultiplayerUserState.PLAYING for u in room.room.users
|
u.state != MultiplayerUserState.PLAYING for u in room.room.users
|
||||||
):
|
):
|
||||||
any_user_finished_playing = False
|
any_user_finished_playing = False
|
||||||
|
|
||||||
|
# Handle finished players first
|
||||||
for u in filter(
|
for u in filter(
|
||||||
lambda u: u.state == MultiplayerUserState.FINISHED_PLAY,
|
lambda u: u.state == MultiplayerUserState.FINISHED_PLAY,
|
||||||
room.room.users,
|
room.room.users,
|
||||||
@@ -921,11 +978,32 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
await self.change_user_state(
|
await self.change_user_state(
|
||||||
room, u, MultiplayerUserState.RESULTS
|
room, u, MultiplayerUserState.RESULTS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Critical fix: Handle spectators who should also see results
|
||||||
|
# Move spectators to RESULTS state so they can see the results screen
|
||||||
|
for u in filter(
|
||||||
|
lambda u: u.state == MultiplayerUserState.SPECTATING,
|
||||||
|
room.room.users,
|
||||||
|
):
|
||||||
|
logger.debug(
|
||||||
|
f"[MultiplayerHub] Moving spectator {u.user_id} to RESULTS state"
|
||||||
|
)
|
||||||
|
await self.change_user_state(
|
||||||
|
room, u, MultiplayerUserState.RESULTS
|
||||||
|
)
|
||||||
|
|
||||||
await self.change_room_state(room, MultiplayerRoomState.OPEN)
|
await self.change_room_state(room, MultiplayerRoomState.OPEN)
|
||||||
|
|
||||||
|
# Send ResultsReady to all room members
|
||||||
await self.broadcast_group_call(
|
await self.broadcast_group_call(
|
||||||
self.group_id(room.room.room_id),
|
self.group_id(room.room.room_id),
|
||||||
"ResultsReady",
|
"ResultsReady",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Critical addition: Notify SpectatorHub about finished games
|
||||||
|
# This ensures cross-hub spectating works properly
|
||||||
|
await self._notify_spectator_hub_game_ended(room)
|
||||||
|
|
||||||
if any_user_finished_playing:
|
if any_user_finished_playing:
|
||||||
await self.event_logger.game_completed(
|
await self.event_logger.game_completed(
|
||||||
room.room.room_id,
|
room.room.room_id,
|
||||||
@@ -1428,3 +1506,43 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
await room.stop_all_countdowns(MatchStartCountdown)
|
await room.stop_all_countdowns(MatchStartCountdown)
|
||||||
|
|
||||||
|
async def _notify_spectator_hub_game_ended(self, room: ServerMultiplayerRoom):
|
||||||
|
"""
|
||||||
|
Notify SpectatorHub about ended multiplayer game.
|
||||||
|
This ensures cross-hub spectating works properly when games end.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from app.signalr.hub import SpectatorHubs
|
||||||
|
from app.models.spectator_hub import SpectatedUserState, SpectatorState
|
||||||
|
|
||||||
|
# For each user who finished the game, notify SpectatorHub
|
||||||
|
for room_user in room.room.users:
|
||||||
|
if room_user.state == MultiplayerUserState.RESULTS:
|
||||||
|
# Create a synthetic finished state
|
||||||
|
finished_state = SpectatorState(
|
||||||
|
beatmap_id=room.queue.current_item.beatmap_id,
|
||||||
|
ruleset_id=room_user.ruleset_id or 0,
|
||||||
|
mods=room_user.mods,
|
||||||
|
state=SpectatedUserState.Passed, # Assume passed for results
|
||||||
|
maximum_statistics={},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Notify all SpectatorHub watchers that this user finished
|
||||||
|
await SpectatorHubs.broadcast_group_call(
|
||||||
|
SpectatorHubs.group_id(room_user.user_id),
|
||||||
|
"UserFinishedPlaying",
|
||||||
|
room_user.user_id,
|
||||||
|
finished_state,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"[MultiplayerHub] Notified SpectatorHub that user {room_user.user_id} finished game"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(
|
||||||
|
f"[MultiplayerHub] Failed to notify SpectatorHub about game end: {e}"
|
||||||
|
)
|
||||||
|
# This is not critical, so we don't raise the exception
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ class SpectatorHub(Hub[StoreClientState]):
|
|||||||
# Check all active multiplayer rooms for playing users
|
# Check all active multiplayer rooms for playing users
|
||||||
for room_id, server_room in MultiplayerHubs.rooms.items():
|
for room_id, server_room in MultiplayerHubs.rooms.items():
|
||||||
for room_user in server_room.room.users:
|
for room_user in server_room.room.users:
|
||||||
# If user is playing in multiplayer but we don't have their spectator state
|
# Send state for users who are playing or in results
|
||||||
if (
|
if (
|
||||||
room_user.state.is_playing
|
room_user.state.is_playing
|
||||||
and room_user.user_id not in self.state
|
and room_user.user_id not in self.state
|
||||||
@@ -266,6 +266,35 @@ class SpectatorHub(Hub[StoreClientState]):
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
f"[SpectatorHub] Failed to create synthetic state: {e}"
|
f"[SpectatorHub] Failed to create synthetic state: {e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Critical addition: Notify about finished players in multiplayer games
|
||||||
|
elif (
|
||||||
|
room_user.state == MultiplayerUserState.RESULTS
|
||||||
|
and room_user.user_id not in self.state
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
# Create a synthetic finished state
|
||||||
|
finished_state = SpectatorState(
|
||||||
|
beatmap_id=server_room.queue.current_item.beatmap_id,
|
||||||
|
ruleset_id=room_user.ruleset_id or 0,
|
||||||
|
mods=room_user.mods,
|
||||||
|
state=SpectatedUserState.Passed, # Assume passed for results
|
||||||
|
maximum_statistics={},
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.call_noblock(
|
||||||
|
client,
|
||||||
|
"UserFinishedPlaying",
|
||||||
|
room_user.user_id,
|
||||||
|
finished_state,
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"[SpectatorHub] Sent synthetic finished state for user {room_user.user_id}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(
|
||||||
|
f"[SpectatorHub] Failed to create synthetic finished state: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"[SpectatorHub] Failed to sync with MultiplayerHub: {e}")
|
logger.debug(f"[SpectatorHub] Failed to sync with MultiplayerHub: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user