diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index ce01adc..128278b 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -94,6 +94,11 @@ return this.lifecycleService.issueServerBombId(); } + /** 指定プレイヤーがBotなら被弾硬直を適用する */ + applyBotHitStun(playerId: string, nowMs: number): boolean { + return this.lifecycleService.applyBotHitStun(playerId, nowMs); + } + dispose(): void { this.lifecycleService.dispose(); } diff --git a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts index 73819f0..a5bd40d 100644 --- a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts +++ b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts @@ -123,6 +123,11 @@ shouldBroadcastBombHitReport(dedupeKey: string, nowMs: number): boolean; } +/** 被弾報告ユースケースが利用するBot被弾反映入力ポート */ +export interface BotHitReactionPort { + applyBotHitStun(playerId: string, nowMs: number): boolean; +} + /** 爆弾設置ユースケースの入力値 */ export type PlaceBombInput = { socketId: string; diff --git a/apps/server/src/domains/game/application/services/GameRoomSession.ts b/apps/server/src/domains/game/application/services/GameRoomSession.ts index abbd935..6b52829 100644 --- a/apps/server/src/domains/game/application/services/GameRoomSession.ts +++ b/apps/server/src/domains/game/application/services/GameRoomSession.ts @@ -165,6 +165,15 @@ return this.bombStateStore.issueServerBombId(); } + /** 指定プレイヤーがBotなら被弾硬直を適用する */ + public applyBotHitStun(playerId: string, nowMs: number): boolean { + if (!this.gameLoop) { + return false; + } + + return this.gameLoop.applyBotHitStun(playerId, nowMs); + } + public dispose(): void { if (this.startDelayTimer) { clearTimeout(this.startDelayTimer); diff --git a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts index e5d05ec..10fcf2e 100644 --- a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts +++ b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts @@ -61,6 +61,16 @@ return session.issueServerBombId(); } + /** 指定プレイヤーがBotなら被弾硬直を適用する */ + public applyBotHitStun(playerId: string, nowMs: number): boolean { + const session = this.sessionRef.current; + if (!session) { + return false; + } + + return session.applyBotHitStun(playerId, nowMs); + } + public startRoomSession( playerIds: string[], playerNamesById: Record, diff --git a/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts b/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts index 5bbe2e4..9fb2a40 100644 --- a/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts @@ -3,6 +3,7 @@ * 被弾報告を受け取り,死亡通知の配信処理へ橋渡しする */ import type { + BotHitReactionPort, BombHitOutputPort, BombHitReportValidationPort, ReportBombHitInput, @@ -12,6 +13,7 @@ type ReportBombHitUseCaseParams = { roomId: string; validation: BombHitReportValidationPort; + botHitReaction: BotHitReactionPort; input: ReportBombHitInput; output: BombHitOutputPort; }; @@ -31,6 +33,7 @@ export const reportBombHitUseCase = ({ roomId, validation, + botHitReaction, input, output, }: ReportBombHitUseCaseParams): void => { @@ -38,5 +41,10 @@ return; } + const targetPlayerId = input.payload.targetPlayerId; + if (targetPlayerId && botHitReaction.applyBotHitStun(targetPlayerId, input.nowMs)) { + return; + } + publishPlayerDeadFromBombHit(roomId, input, output); }; diff --git a/apps/server/src/domains/game/loop/GameLoop.ts b/apps/server/src/domains/game/loop/GameLoop.ts index 9bde2a1..633959d 100644 --- a/apps/server/src/domains/game/loop/GameLoop.ts +++ b/apps/server/src/domains/game/loop/GameLoop.ts @@ -143,6 +143,17 @@ }); } + /** 指定プレイヤーがBotなら被弾硬直を適用する */ + public applyBotHitStun(playerId: string, nowMs: number): boolean { + const player = this.players.get(playerId); + if (!player || !isBotPlayerId(player.id)) { + return false; + } + + this.botTurnOrchestrator.applyHitStun(player.id, nowMs); + return true; + } + private buildTickData(): domain.game.TickData { const activePlayerIds = new Set(); const playerUpdates = this.collectChangedPlayerUpdates(activePlayerIds); diff --git a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts index 3cbf6d3..8826220 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -4,6 +4,7 @@ */ import { domain } from "@repo/shared"; import type { + BotHitReactionPort, BombHitReportValidationPort, BombPlacementPort, DisconnectPlayerPort, @@ -19,6 +20,7 @@ & MovePlayerPort & BombPlacementPort & BombHitReportValidationPort + & BotHitReactionPort & DisconnectPlayerPort; /** ルーム参加処理の実行結果 */ diff --git a/apps/server/src/network/handlers/game/gameEventOrchestrators.ts b/apps/server/src/network/handlers/game/gameEventOrchestrators.ts index 874cdf2..cffe09a 100644 --- a/apps/server/src/network/handlers/game/gameEventOrchestrators.ts +++ b/apps/server/src/network/handlers/game/gameEventOrchestrators.ts @@ -143,6 +143,7 @@ reportBombHitUseCase({ roomId, validation: gameManager, + botHitReaction: gameManager, input: { socketId: deps.socketId, payload, diff --git a/apps/server/src/network/validation/socketPayloadValidators.ts b/apps/server/src/network/validation/socketPayloadValidators.ts index 39941ea..3fbb775 100644 --- a/apps/server/src/network/validation/socketPayloadValidators.ts +++ b/apps/server/src/network/validation/socketPayloadValidators.ts @@ -51,6 +51,12 @@ } 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 6e1d6b2..22c2b64 100644 --- a/packages/shared/src/protocol/payloads/gamePayloads.ts +++ b/packages/shared/src/protocol/payloads/gamePayloads.ts @@ -99,6 +99,7 @@ /** bomb-hit-report イベントで送受信する被弾報告 */ export type BombHitReportPayload = { bombId: string; + targetPlayerId?: string; }; /** player-dead イベントで送受信する死亡プレイヤー情報 */