diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts index a468fe4..410f186 100644 --- a/apps/client/src/scenes/game/GameManager.ts +++ b/apps/client/src/scenes/game/GameManager.ts @@ -110,8 +110,8 @@ players: this.players, myId: this.myId, acquireInputLock: this.lockInput.bind(this), - onSendBombHitReport: (bombId, targetPlayerId) => { - gameActionSender.sendBombHitReport(bombId, targetPlayerId); + onSendBombHitReport: (bombId) => { + gameActionSender.sendBombHitReport(bombId); }, }); this.runtime = new GameSceneRuntime({ diff --git a/apps/client/src/scenes/game/application/BombHitContextProvider.ts b/apps/client/src/scenes/game/application/BombHitContextProvider.ts deleted file mode 100644 index 691c13b..0000000 --- a/apps/client/src/scenes/game/application/BombHitContextProvider.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * BombHitContextProvider - * 爆弾当たり判定で利用するローカルプレイヤー情報を供給する - * プレイヤー管理構造から最小の判定DTOへ変換して返す - */ -import { config } from "@client/config"; -import type { domain } from "@repo/shared"; - -type TeamCollisionCircle = domain.game.bombHit.TeamCollisionCircle; -import type { GamePlayers } from "./game.types"; - -/** 被弾判定と報告に利用するプレイヤー円情報 */ -export type ReportablePlayerCircle = TeamCollisionCircle & { - playerId: string; -}; - -/** 判定用コンテキスト供給クラスの初期化入力 */ -type BombHitContextProviderOptions = { - players: GamePlayers; - myId: string; -}; - -/** 爆弾当たり判定に必要なローカルプレイヤー情報を返す */ -export class BombHitContextProvider { - private players: GamePlayers; - private myId: string; - - constructor({ players, myId }: BombHitContextProviderOptions) { - this.players = players; - this.myId = myId; - } - - /** 自プレイヤーの被弾判定用円情報を取得する(不在ならnull) */ - public getLocalReportableCircle(): ReportablePlayerCircle | null { - const me = this.players[this.myId]; - if (!me) return null; - - const position = me.getPosition(); - const snapshot = me.getSnapshot(); - - return { - playerId: this.myId, - x: position.x, - y: position.y, - radius: config.GAME_CONFIG.PLAYER_RADIUS, - teamId: snapshot.teamId, - }; - } -} diff --git a/apps/client/src/scenes/game/application/BombHitOrchestrator.ts b/apps/client/src/scenes/game/application/BombHitOrchestrator.ts index f0484ea..021f26f 100644 --- a/apps/client/src/scenes/game/application/BombHitOrchestrator.ts +++ b/apps/client/src/scenes/game/application/BombHitOrchestrator.ts @@ -1,17 +1,19 @@ /** * BombHitOrchestrator - * 爆弾爆発イベントとローカルプレイヤー情報を橋渡しして当たり判定を実行する + * 爆弾爆発イベントと自プレイヤー情報から当たり判定を実行する * 自プレイヤーのみを判定対象とし,Bot被弾はサーバー側で処理する */ +import { config } from "@client/config"; import { domain } from "@repo/shared"; const { checkBombHit } = domain.game.bombHit; import type { BombExplodedPayload } from "@client/scenes/game/entities/bomb/BombManager"; -import { BombHitContextProvider } from "./BombHitContextProvider"; +import type { GamePlayers } from "./game.types"; /** 当たり判定オーケストレーターの初期化入力 */ type BombHitOrchestratorOptions = { - contextProvider: BombHitContextProvider; + players: GamePlayers; + myId: string; }; /** 爆弾爆発イベントの判定結果を表す型 */ @@ -22,11 +24,13 @@ /** 爆弾当たり判定の実行順序を制御する */ export class BombHitOrchestrator { - private contextProvider: BombHitContextProvider; + private readonly players: GamePlayers; + private readonly myId: string; private handledBombIds = new Set(); - constructor({ contextProvider }: BombHitOrchestratorOptions) { - this.contextProvider = contextProvider; + constructor({ players, myId }: BombHitOrchestratorOptions) { + this.players = players; + this.myId = myId; } /** 爆弾爆発イベントを受けて自プレイヤーの当たり判定を実行し結果を返す */ @@ -36,8 +40,8 @@ } this.handledBombIds.add(payload.bombId); - const localPlayer = this.contextProvider.getLocalReportableCircle(); - if (!localPlayer) { + const localCircle = this.getLocalPlayerCircle(); + if (!localCircle) { return { status: "missing-local-player", hitPlayerId: null }; } @@ -48,18 +52,34 @@ radius: payload.radius, teamId: payload.teamId, }, - player: localPlayer, + player: localCircle, }); if (!result.isHit) { return { status: "no-hit", hitPlayerId: null }; } - return { status: "hit", hitPlayerId: localPlayer.playerId }; + return { status: "hit", hitPlayerId: this.myId }; } /** 判定済み状態を初期化する */ public clear(): void { this.handledBombIds.clear(); } + + /** 自プレイヤーの判定用円情報を取得する */ + private getLocalPlayerCircle() { + const me = this.players[this.myId]; + if (!me) return null; + + const position = me.getPosition(); + const snapshot = me.getSnapshot(); + + return { + x: position.x, + y: position.y, + radius: config.GAME_CONFIG.PLAYER_RADIUS, + teamId: snapshot.teamId, + }; + } } diff --git a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts index c2fe46e..1e925bb 100644 --- a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts +++ b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts @@ -6,19 +6,17 @@ import { config } from "@client/config"; import type { PlayerDeadPayload } from "@repo/shared"; import type { BombExplodedPayload } from "@client/scenes/game/entities/bomb/BombManager"; -import { BombHitContextProvider } from "@client/scenes/game/application/BombHitContextProvider"; import { BombHitOrchestrator } from "@client/scenes/game/application/BombHitOrchestrator"; import { PlayerDeathPolicy } from "@client/scenes/game/application/PlayerDeathPolicy"; import { PlayerHitEffectOrchestrator } from "@client/scenes/game/application/PlayerHitEffectOrchestrator"; import type { GamePlayers } from "@client/scenes/game/application/game.types"; -import { HitReportPolicy } from "./HitReportPolicy"; /** CombatLifecycleFacade の初期化入力 */ export type CombatLifecycleFacadeOptions = { players: GamePlayers; myId: string; acquireInputLock: () => () => void; - onSendBombHitReport: (bombId: string, targetPlayerId: string) => void; + onSendBombHitReport: (bombId: string) => void; }; /** 被弾関連ライフサイクルの制御を担当する */ @@ -26,12 +24,10 @@ private readonly myId: string; private readonly onSendBombHitReport: ( bombId: string, - targetPlayerId: string, ) => void; private readonly bombHitOrchestrator: BombHitOrchestrator; private readonly playerDeathPolicy: PlayerDeathPolicy; private readonly playerHitEffectOrchestrator: PlayerHitEffectOrchestrator; - private readonly hitReportPolicy = new HitReportPolicy(); constructor({ players, @@ -42,10 +38,8 @@ this.myId = myId; this.onSendBombHitReport = onSendBombHitReport; this.bombHitOrchestrator = new BombHitOrchestrator({ - contextProvider: new BombHitContextProvider({ - players, - myId, - }), + players, + myId, }); this.playerDeathPolicy = new PlayerDeathPolicy({ myId, @@ -66,10 +60,7 @@ this.playerDeathPolicy.applyLocalHitStun(); this.playerHitEffectOrchestrator.handleLocalBombHit(this.myId); - - if (this.hitReportPolicy.shouldSendReport(result.status, payload.bombId, result.hitPlayerId)) { - this.onSendBombHitReport(payload.bombId, result.hitPlayerId); - } + this.onSendBombHitReport(payload.bombId); } /** ネットワーク被弾通知を適用する */ @@ -82,6 +73,5 @@ public dispose(): void { this.bombHitOrchestrator.clear(); this.playerDeathPolicy.dispose(); - this.hitReportPolicy.clear(); } } \ No newline at end of file diff --git a/apps/client/src/scenes/game/application/combat/HitReportPolicy.ts b/apps/client/src/scenes/game/application/combat/HitReportPolicy.ts deleted file mode 100644 index e65cd87..0000000 --- a/apps/client/src/scenes/game/application/combat/HitReportPolicy.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * HitReportPolicy - * 爆弾被弾報告の送信可否を判定するポリシー - * 同一爆弾IDの重複送信を抑止する - */ - -/** 被弾判定結果の最小表現型 */ -export type HitEvaluationResult = "duplicate" | "missing-local-player" | "no-hit" | "hit"; - -/** 爆弾被弾報告の送信可否を管理する */ -export class HitReportPolicy { - private readonly reportedBombHitKeys = new Set(); - - /** 被弾報告を送信すべき場合に true を返す */ - public shouldSendReport( - result: HitEvaluationResult | undefined, - bombId: string, - targetPlayerId: string, - ): boolean { - if (result !== "hit") { - return false; - } - - const reportKey = `${bombId}:${targetPlayerId}`; - if (this.reportedBombHitKeys.has(reportKey)) { - return false; - } - - this.reportedBombHitKeys.add(reportKey); - return true; - } - - /** 判定済みIDをすべて破棄する */ - public clear(): void { - this.reportedBombHitKeys.clear(); - } -} \ No newline at end of file diff --git a/apps/client/src/scenes/game/application/network/GameActionSender.ts b/apps/client/src/scenes/game/application/network/GameActionSender.ts index 358ec60..8c22eb2 100644 --- a/apps/client/src/scenes/game/application/network/GameActionSender.ts +++ b/apps/client/src/scenes/game/application/network/GameActionSender.ts @@ -10,7 +10,7 @@ export type GameActionSender = { readyForGame: () => void; sendPlaceBomb: (payload: PlaceBombPayload) => void; - sendBombHitReport: (bombId: string, targetPlayerId: string) => void; + sendBombHitReport: (bombId: string) => void; }; /** ソケット経由でゲーム中送信アクションを実行する実装 */ @@ -26,7 +26,7 @@ } /** 被弾報告をサーバーへ送信する */ - public sendBombHitReport(bombId: string, targetPlayerId: string): void { - socketManager.game.sendBombHitReport({ bombId, targetPlayerId }); + public sendBombHitReport(bombId: string): void { + socketManager.game.sendBombHitReport({ bombId }); } } \ No newline at end of file diff --git a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts index bf0d735..95e1944 100644 --- a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts +++ b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts @@ -74,8 +74,8 @@ ): void; } -/** 爆弾ユースケースが利用する送信出力ポート */ -export interface BombOutputPort { +/** 爆弾設置ユースケースが利用する送信出力ポート */ +export interface BombPlacementOutputPort { publishBombPlacedToOthersInRoom( roomId: domain.room.Room["roomId"], ownerSocketId: string, @@ -85,6 +85,10 @@ socketId: string, payload: BombPlacedAckPayload, ): void; +} + +/** プレイヤー死亡通知の送信出力ポート */ +export interface PlayerDeadOutputPort { publishPlayerDeadToOthersInRoom( roomId: domain.room.Room["roomId"], deadPlayerId: string, @@ -93,10 +97,7 @@ } /** 爆弾設置ユースケースが利用する出力ポート */ -export type PlaceBombOutputPort = Pick< - BombOutputPort, - "publishBombPlacedToOthersInRoom" | "publishBombPlacedAckToSocket" ->; +export type PlaceBombOutputPort = BombPlacementOutputPort; /** start-game 系フローで利用する送信出力ポート */ export type StartGameOutputPort = Pick< @@ -107,12 +108,8 @@ | "publishGameResultToRoom" | "publishGameStartToRoom" > & - Pick< - BombOutputPort, - | "publishBombPlacedToOthersInRoom" - | "publishBombPlacedAckToSocket" - | "publishPlayerDeadToOthersInRoom" - >; + BombPlacementOutputPort & + PlayerDeadOutputPort; /** 爆弾設置ユースケースが利用する爆弾状態入力ポート */ export interface BombPlacementPort { @@ -150,7 +147,4 @@ }; /** 被弾報告ユースケースが利用する出力ポート */ -export type BombHitOutputPort = Pick< - BombOutputPort, - "publishPlayerDeadToOthersInRoom" ->; +export type BombHitOutputPort = PlayerDeadOutputPort; diff --git a/apps/server/src/domains/game/application/useCases/reportBombHitValidation.ts b/apps/server/src/domains/game/application/useCases/reportBombHitValidation.ts index 6437ea4..7dfd691 100644 --- a/apps/server/src/domains/game/application/useCases/reportBombHitValidation.ts +++ b/apps/server/src/domains/game/application/useCases/reportBombHitValidation.ts @@ -12,7 +12,6 @@ const dedupeKey = createBombHitReportDedupeKey( input.socketId, input.payload.bombId, - input.payload.targetPlayerId, ); return validation.shouldBroadcastBombHitReport(dedupeKey, input.nowMs); }; diff --git a/apps/server/src/domains/game/entities/bomb/bombHitReport.ts b/apps/server/src/domains/game/entities/bomb/bombHitReport.ts index 24478f8..5ca7406 100644 --- a/apps/server/src/domains/game/entities/bomb/bombHitReport.ts +++ b/apps/server/src/domains/game/entities/bomb/bombHitReport.ts @@ -7,11 +7,6 @@ export const createBombHitReportDedupeKey = ( reporterSocketId: string, bombId: string, - targetPlayerId?: string, ): string => { - if (targetPlayerId) { - return `${bombId}:${targetPlayerId}`; - } - return `${reporterSocketId}:${bombId}`; }; diff --git a/apps/server/src/network/handlers/game/createGameOutputAdapter.ts b/apps/server/src/network/handlers/game/createGameOutputAdapter.ts index 530bbe1..5ac2cf9 100644 --- a/apps/server/src/network/handlers/game/createGameOutputAdapter.ts +++ b/apps/server/src/network/handlers/game/createGameOutputAdapter.ts @@ -18,7 +18,8 @@ UpdatePlayersPayload, } from "@repo/shared"; import type { - BombOutputPort, + BombPlacementOutputPort, + PlayerDeadOutputPort, GameOutputPort, } from "@server/domains/game/application/ports/gameUseCasePorts"; import { sanitizeUpdatePlayersPayload } from "@server/network/adapters/gamePayloadSanitizers"; @@ -32,7 +33,8 @@ GameOutputPort, "publishPlayerRemovedToRoom" > & - BombOutputPort; + BombPlacementOutputPort & + PlayerDeadOutputPort; /** ゲーム切断時の出力アダプターのインターフェース */ export type GameDisconnectOutputAdapter = Pick< diff --git a/apps/server/src/network/validation/socketPayloadValidators.ts b/apps/server/src/network/validation/socketPayloadValidators.ts index 981940b..b999b69 100644 --- a/apps/server/src/network/validation/socketPayloadValidators.ts +++ b/apps/server/src/network/validation/socketPayloadValidators.ts @@ -51,12 +51,6 @@ } const candidate = value as Record; - const targetPlayerId = candidate.targetPlayerId; - - if (targetPlayerId !== undefined && !isNonEmptyString(targetPlayerId)) { - return false; - } - return isNonEmptyString(candidate.bombId); }; diff --git a/packages/shared/src/protocol/payloads/gamePayloads.ts b/packages/shared/src/protocol/payloads/gamePayloads.ts index d70c94b..7090983 100644 --- a/packages/shared/src/protocol/payloads/gamePayloads.ts +++ b/packages/shared/src/protocol/payloads/gamePayloads.ts @@ -106,7 +106,6 @@ /** bomb-hit-report イベントで送受信する被弾報告 */ export type BombHitReportPayload = { bombId: string; - targetPlayerId?: string; }; /** player-dead イベントで送受信する死亡プレイヤー情報 */