diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts index 4e33c89..c8e0db4 100644 --- a/apps/client/src/scenes/game/GameManager.ts +++ b/apps/client/src/scenes/game/GameManager.ts @@ -7,7 +7,10 @@ import { GameEventFacade } from "./application/GameEventFacade"; import { SceneLifecycleState } from "./application/lifecycle/SceneLifecycleState"; import { GameSessionFacade } from "./application/lifecycle/GameSessionFacade"; -import { CombatLifecycleFacade } from "./application/combat/CombatLifecycleFacade"; +import { + CombatLifecycleFacade, + type CombatLifecycleFacadeOptions, +} from "./application/combat/CombatLifecycleFacade"; import { DisposableRegistry } from "./application/lifecycle/DisposableRegistry"; import { registerGameManagerDisposers } from "./application/lifecycle/registerGameManagerDisposers"; import { type GameSceneFactoryOptions } from "./application/orchestrators/GameSceneOrchestrator"; @@ -39,6 +42,7 @@ sessionFacade?: GameSessionFacade; lifecycleState?: SceneLifecycleState; gameActionSender?: GameActionSender; + playerMoveSender?: MoveSender; moveSender?: MoveSender; sceneFactories?: GameSceneFactoryOptions; }; @@ -60,7 +64,7 @@ private sessionFacade: GameSessionFacade; private gameActionSender: GameActionSender; private runtime: GameSceneRuntime; - private moveSender: MoveSender; + private playerMoveSender: MoveSender; private gameEventFacade: GameEventFacade; private combatFacade: CombatLifecycleFacade; private lifecycleState: SceneLifecycleState; @@ -123,7 +127,10 @@ dependencies.lifecycleState ?? new SceneLifecycleState(); this.gameActionSender = dependencies.gameActionSender ?? new SocketGameActionSender(); - this.moveSender = dependencies.moveSender ?? new SocketPlayerMoveSender(); + this.playerMoveSender = + dependencies.playerMoveSender + ?? dependencies.moveSender + ?? new SocketPlayerMoveSender(); const sceneFactories = dependencies.sceneFactories; this.app = new Application(); this.worldContainer = new Container(); @@ -137,21 +144,9 @@ }, getBombManager: () => this.runtime.getBombManager(), }); - this.combatFacade = new CombatLifecycleFacade({ - players: this.players, - myId: this.myId, - acquireInputLock: this.lockInput.bind(this), - onSendBombHitReport: (bombId) => { - this.gameActionSender.sendBombHitReport(bombId); - }, - onLocalBombHitCountChanged: (count) => { - this.localBombHitCount = count; - this.uiStateSyncService.emitIfChanged(); - }, - onLocalRespawnCompleted: (position) => { - this.moveSender.sendMove(position.x, position.y, { force: true }); - }, - }); + this.combatFacade = new CombatLifecycleFacade( + this.createCombatLifecycleCallbacks(), + ); this.runtime = new GameSceneRuntime({ app: this.app, worldContainer: this.worldContainer, @@ -159,7 +154,7 @@ myId: this.myId, sessionFacade: this.sessionFacade, gameActionSender: this.gameActionSender, - moveSender: this.moveSender, + moveSender: this.playerMoveSender, getElapsedMs: () => this.sessionFacade.getElapsedMs(), onPongReceived: (payload) => { this.clockSyncService.updateFromPong(payload); @@ -256,6 +251,25 @@ return this.uiStateSyncService.subscribe(listener); } + /** 被弾ライフサイクルのコールバック群を組み立てる */ + private createCombatLifecycleCallbacks(): CombatLifecycleFacadeOptions { + return { + players: this.players, + myId: this.myId, + acquireInputLock: this.lockInput.bind(this), + onSendBombHitReport: (bombId) => { + this.gameActionSender.sendBombHitReport(bombId); + }, + onLocalBombHitCountChanged: (count) => { + this.localBombHitCount = count; + this.uiStateSyncService.emitIfChanged(); + }, + onLocalRespawnCompleted: (position) => { + this.playerMoveSender.sendMove(position.x, position.y, { force: true }); + }, + }; + } + /** HUD状態購読を登録し,解除関数を返す */ public subscribeHudState( listener: (state: GameHudState) => void, diff --git a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts index 51057ec..2bead93 100644 --- a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts +++ b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts @@ -12,6 +12,12 @@ import { RespawnManager } from "./RespawnManager"; import type { GamePlayers } from "@client/scenes/game/application/game.types"; +/** リスポーン完了時に通知するプレイヤー座標型 */ +export type RespawnPosition = { + x: number; + y: number; +}; + /** CombatLifecycleFacade の初期化入力 */ export type CombatLifecycleFacadeOptions = { players: GamePlayers; @@ -19,7 +25,7 @@ acquireInputLock: () => () => void; onSendBombHitReport: (bombId: string) => void; onLocalBombHitCountChanged: (count: number) => void; - onLocalRespawnCompleted?: (position: { x: number; y: number }) => void; + onLocalRespawnCompleted?: (position: RespawnPosition) => void; }; type NetworkDamageSource = "bomb" | "hurricane"; @@ -29,7 +35,7 @@ private readonly myId: string; private readonly onSendBombHitReport: (bombId: string) => void; private readonly onLocalBombHitCountChanged: (count: number) => void; - private readonly onLocalRespawnCompleted?: (position: { x: number; y: number }) => void; + private readonly onLocalRespawnCompleted?: (position: RespawnPosition) => void; private readonly bombHitOrchestrator: BombHitOrchestrator; private readonly playerHitPolicy: PlayerHitPolicy; private readonly playerHitEffectOrchestrator: PlayerHitEffectOrchestrator; diff --git a/apps/server/src/network/handlers/game/services/bombSyncService.ts b/apps/server/src/network/handlers/game/services/bombSyncService.ts index 69ef797..d48e79e 100644 --- a/apps/server/src/network/handlers/game/services/bombSyncService.ts +++ b/apps/server/src/network/handlers/game/services/bombSyncService.ts @@ -15,10 +15,9 @@ type AoiWindow, } from "../aoi/aoiVisibility"; import { - getConnectedSocketIdsInRoom, - getRoomPlayers, type RuntimeResolverDeps, } from "../runtime/gameRuntimeResolvers"; +import { forEachRoomViewer } from "./roomViewerSyncContext"; type RoomId = domain.room.Room["roomId"]; type SocketId = string; @@ -106,24 +105,14 @@ ); }, publishBombPlacedToOthersInRoom: (roomId, excludedSocketId, payload) => { - const roomPlayers = getRoomPlayers(deps.runtimeDeps, roomId); - if (roomPlayers.length === 0) { - return; - } - - const roomPlayerById = new Map(roomPlayers.map((player) => [player.id, player])); - const recipientSocketIds = getConnectedSocketIdsInRoom(deps.runtimeDeps, roomId); - - recipientSocketIds.forEach((viewerId) => { + forEachRoomViewer({ + runtimeDeps: deps.runtimeDeps, + roomId, + run: ({ viewerId, viewer }) => { if (viewerId === excludedSocketId && !isBotPlayerId(excludedSocketId)) { return; } - const viewer = roomPlayerById.get(viewerId); - if (!viewer) { - return; - } - deps.updateViewerAoiCellCache(roomId, viewerId, viewer); const aoiWindow = resolveViewerAoiWindow(viewer); if (!isInViewerAoi(payload, aoiWindow)) { @@ -131,6 +120,7 @@ } deps.reliable.emitToSocketById(viewerId, protocol.SocketEvents.BOMB_PLACED, payload); + }, }); }, }; diff --git a/apps/server/src/network/handlers/game/services/hurricaneSyncService.ts b/apps/server/src/network/handlers/game/services/hurricaneSyncService.ts index e745955..4ce432b 100644 --- a/apps/server/src/network/handlers/game/services/hurricaneSyncService.ts +++ b/apps/server/src/network/handlers/game/services/hurricaneSyncService.ts @@ -13,10 +13,9 @@ import type { ReliableEmitters } from "../../CommonHandler"; import { isTargetInAoiWindow, resolveViewerAoiWindow, type AoiWindow } from "../aoi/aoiVisibility"; import { - getConnectedSocketIdsInRoom, - getRoomPlayers, type RuntimeResolverDeps, } from "../runtime/gameRuntimeResolvers"; +import { forEachRoomViewer } from "./roomViewerSyncContext"; type RoomId = domain.room.Room["roomId"]; type SocketId = string; @@ -134,20 +133,10 @@ publishCurrentHurricanesToRoom: (roomId, hurricanes) => { replaceRoomHurricaneSnapshot(roomId, hurricanes); - const roomPlayers = getRoomPlayers(deps.runtimeDeps, roomId); - if (roomPlayers.length === 0) { - return; - } - - const roomPlayerById = new Map(roomPlayers.map((player) => [player.id, player])); - const recipientSocketIds = getConnectedSocketIdsInRoom(deps.runtimeDeps, roomId); - - recipientSocketIds.forEach((viewerId) => { - const viewer = roomPlayerById.get(viewerId); - if (!viewer) { - return; - } - + forEachRoomViewer({ + runtimeDeps: deps.runtimeDeps, + roomId, + run: ({ viewerId, viewer }) => { const visibleHurricanes = collectVisibleHurricanesByViewer( roomId, viewerId, @@ -161,26 +150,18 @@ protocol.SocketEvents.CURRENT_HURRICANES, visibleHurricanes, ); + }, }); }, publishUpdateHurricanesToRoom: (roomId, hurricanes) => { upsertRoomHurricaneSnapshot(roomId, hurricanes); - const roomPlayers = getRoomPlayers(deps.runtimeDeps, roomId); - if (roomPlayers.length === 0) { - return; - } - - const roomPlayerById = new Map(roomPlayers.map((player) => [player.id, player])); - const recipientSocketIds = getConnectedSocketIdsInRoom(deps.runtimeDeps, roomId); const roomSnapshot = hurricaneSnapshotByRoomId.get(roomId); - recipientSocketIds.forEach((viewerId) => { - const viewer = roomPlayerById.get(viewerId); - if (!viewer) { - return; - } - + forEachRoomViewer({ + runtimeDeps: deps.runtimeDeps, + roomId, + run: ({ viewerId, viewer }) => { const nextVisibleHurricanes = collectVisibleHurricanesByViewer( roomId, viewerId, @@ -219,6 +200,7 @@ visibleUpdateHurricanes, ); syncVisibleHurricaneIdsByViewer(roomId, viewerId, nextVisibleHurricanes); + }, }); }, clearRoomSnapshot: (roomId) => { diff --git a/apps/server/src/network/handlers/game/services/playerSyncService.ts b/apps/server/src/network/handlers/game/services/playerSyncService.ts index 8437fae..71b5b4f 100644 --- a/apps/server/src/network/handlers/game/services/playerSyncService.ts +++ b/apps/server/src/network/handlers/game/services/playerSyncService.ts @@ -11,11 +11,10 @@ import { isTargetInAoiWindow, resolveViewerAoiWindow, type AoiWindow } from "../aoi/aoiVisibility"; import { getActiveBombSnapshotsInRoom, - getConnectedSocketIdsInRoom, - getRoomPlayers, type RuntimeResolverDeps, } from "../runtime/gameRuntimeResolvers"; import type { BombSyncService } from "./bombSyncService"; +import { forEachRoomViewer } from "./roomViewerSyncContext"; type RoomId = domain.room.Room["roomId"]; type SocketId = string; @@ -87,22 +86,12 @@ return { publishUpdatePlayersToRoom: (roomId, players) => { - const roomPlayers = getRoomPlayers(deps.runtimeDeps, roomId); const activeBombs = getActiveBombSnapshotsInRoom(deps.runtimeDeps, roomId); - if (roomPlayers.length === 0) { - return; - } - const quantizedPlayers = quantizeUpdatePlayersPayload(players); - const roomPlayerById = new Map(roomPlayers.map((player) => [player.id, player])); - const recipientSocketIds = getConnectedSocketIdsInRoom(deps.runtimeDeps, roomId); - - recipientSocketIds.forEach((viewerId) => { - const viewer = roomPlayerById.get(viewerId); - if (!viewer) { - return; - } - + forEachRoomViewer({ + runtimeDeps: deps.runtimeDeps, + roomId, + run: ({ viewerId, viewer, roomPlayers }) => { deps.bombSyncService.syncVisibleBombsByViewer( roomId, viewerId, @@ -148,6 +137,7 @@ protocol.SocketEvents.UPDATE_PLAYERS, changedPlayers, ); + }, }); }, }; diff --git a/apps/server/src/network/handlers/game/services/roomViewerSyncContext.ts b/apps/server/src/network/handlers/game/services/roomViewerSyncContext.ts new file mode 100644 index 0000000..1c2a7eb --- /dev/null +++ b/apps/server/src/network/handlers/game/services/roomViewerSyncContext.ts @@ -0,0 +1,54 @@ +/** + * roomViewerSyncContext + * ルーム内の受信者走査で使う共通コンテキストを提供する + * SyncService間で重複する受信者解決処理を集約する + */ +import type { domain } from "@repo/shared"; +import { + getConnectedSocketIdsInRoom, + getRoomPlayers, + type RuntimeResolverDeps, +} from "../runtime/gameRuntimeResolvers"; + +type RoomId = domain.room.Room["roomId"]; + +type ViewerId = string; + +/** ルーム受信者走査の実行入力 */ +export type ForEachRoomViewerParams = { + runtimeDeps: RuntimeResolverDeps; + roomId: RoomId; + run: (params: { + viewerId: ViewerId; + viewer: domain.game.player.PlayerData; + roomPlayers: domain.game.player.PlayerData[]; + }) => void; +}; + +/** ルーム内の接続済み受信者を列挙し,各受信者ごとに処理を実行する */ +export const forEachRoomViewer = ({ + runtimeDeps, + roomId, + run, +}: ForEachRoomViewerParams): void => { + const roomPlayers = getRoomPlayers(runtimeDeps, roomId); + if (roomPlayers.length === 0) { + return; + } + + const roomPlayerById = new Map(roomPlayers.map((player) => [player.id, player])); + const recipientSocketIds = getConnectedSocketIdsInRoom(runtimeDeps, roomId); + + recipientSocketIds.forEach((viewerId) => { + const viewer = roomPlayerById.get(viewerId); + if (!viewer) { + return; + } + + run({ + viewerId, + viewer, + roomPlayers, + }); + }); +};