package logic import ( "encoding/json" "math/rand" "wuziqi-server/characters" "wuziqi-server/config" "wuziqi-server/core" "wuziqi-server/items" "github.com/heroiclabs/nakama-common/runtime" ) type GameEngine struct { CharManager *characters.CharacterManager ItemManager *items.ItemManager Dispatcher runtime.MatchDispatcher Logger runtime.Logger Presences map[string]runtime.Presence DisconnectedPlayers map[string]*core.Player MatchID string } func NewGameEngine(logger runtime.Logger, dispatcher runtime.MatchDispatcher, charMgr *characters.CharacterManager, itemMgr *items.ItemManager, presences map[string]runtime.Presence, disconnected map[string]*core.Player, matchID string) *GameEngine { return &GameEngine{ CharManager: charMgr, ItemManager: itemMgr, Dispatcher: dispatcher, Logger: logger, Presences: presences, DisconnectedPlayers: disconnected, MatchID: matchID, } } // BroadcastEvent 实现 items.GameLogic func (e *GameEngine) BroadcastEvent(event core.GameEvent) { core.BroadcastEvent(e.Dispatcher, event) } // SendPrivateEvent 实现 items.GameLogic,仅向特定用户发送事件 func (e *GameEngine) SendPrivateEvent(targetID string, event core.GameEvent) { presence, ok := e.Presences[targetID] if !ok { e.Logger.Warn("SendPrivateEvent: Presence not found for user %s", targetID) return } data, _ := json.Marshal(event) e.Logger.Debug("SendPrivateEvent: UserID=%s, Type=%s, Msg=%s, FoundPresence=%v", targetID, event.Type, event.Message, ok) // 第三个参数限制为仅包含该玩家的 presence 列表 e.Dispatcher.BroadcastMessage(core.OpCodeGameEvent, data, []runtime.Presence{presence}, nil, true) } // GetRandomAliveTarget 实现 items.GameLogic func (e *GameEngine) GetRandomAliveTarget(state *core.GameState, excludeID string) *core.Player { candidates := []*core.Player{} for _, p := range state.Players { if p.UserID != excludeID && p.HP > 0 { candidates = append(candidates, p) } } if len(candidates) == 0 { return nil } return candidates[rand.Intn(len(candidates))] } // CheckGameOver 检查游戏是否结束 func (e *GameEngine) CheckGameOver(state *core.GameState) bool { if !state.GameStarted { return false } alive := []string{} for _, p := range state.Players { if p.HP > 0 { alive = append(alive, p.UserID) } } // 关键修复:计入正在断线重连中的幸存玩家 for _, p := range e.DisconnectedPlayers { if p.HP > 0 { alive = append(alive, p.UserID) } } if len(alive) <= 1 { winnerID := "" if len(alive) == 1 { winnerID = alive[0] } else if len(alive) == 0 { winnerID = state.LastDeadPlayerID } state.WinnerID = winnerID state.GameStarted = false // 使用真实用户ID向后端批量结算游戏(全员) allPlayers := make(map[string]*core.Player) for uid, p := range state.Players { allPlayers[uid] = p } for uid, p := range e.DisconnectedPlayers { if _, exists := allPlayers[uid]; !exists { allPlayers[uid] = p } } if len(allPlayers) > 0 { // 判定排名:赢家第1,其余按存活轮数倒序 players := make([]config.SettlePlayerRecord, 0, len(allPlayers)) for uid, p := range allPlayers { if p.RealUserID <= 0 { continue } win := uid == winnerID rank := 2 if win { rank = 1 } players = append(players, config.SettlePlayerRecord{ UserID: p.RealUserID, Ticket: p.Ticket, Win: win, Rank: rank, Score: p.ChestCount * 10, DamageDealt: p.DamageDealt, DamageTaken: p.DamageTaken, Kills: p.Kills, ChestsCollected: p.ChestCount, RoundsSurvived: state.Round, }) } // 取游戏类型(从任意玩家) gameType := "" for _, p := range allPlayers { if p.GameType != "" { gameType = p.GameType break } } config.SettleGameWithBackend(e.Logger, e.MatchID, gameType, state.Round, players) } else { e.Logger.Error("No players with RealUserID found, cannot settle") } endDataBytes, _ := json.Marshal(map[string]interface{}{ "winnerId": winnerID, "gameState": state.Sanitize(), }) e.Dispatcher.BroadcastMessage(core.OpCodeGameOver, endDataBytes, nil, nil, true) // 注意:BroadcastMessage 期望 []byte。 // 我们应该在内部进行序列化。 // 等等,Nakama 的 BroadcastMessage 接受 []byte 数据。 // 我马上修好它。 return true } return false } // AdvanceTurn 处理回合推进、定时炸弹、毒药等。 func (e *GameEngine) AdvanceTurn(state *core.GameState) { scanCount := 0 for { state.CurrentTurnIndex = (state.CurrentTurnIndex + 1) % len(state.TurnOrder) scanCount++ // 回合计数:当回到第一个玩家时,全场轮次+1 if state.CurrentTurnIndex == 0 { state.Round++ } // 如果每个人都跳过/死亡,防止死循环 if scanCount > len(state.TurnOrder)*2 { break } nextUID := state.TurnOrder[state.CurrentTurnIndex] nextPlayer := state.Players[nextUID] if nextPlayer == nil { e.Logger.Warn("Player %s found in TurnOrder but missing from Players map", nextUID) continue } if nextPlayer.HP <= 0 { continue } // 处理定时炸弹倒计时 if nextPlayer.TimeBombTurns > 0 { nextPlayer.TimeBombTurns-- if nextPlayer.TimeBombTurns == 0 { // 轰!定时炸弹爆炸 // 树懒受到的炸弹伤害减免是在 ApplyDamage 中处理的吗? // 原始代码在这里处理:如果是树懒dmg=1,否则dmg=2 dmg := 2 if nextPlayer.Character == "sloth" { dmg = 1 } e.Logger.Info("Time bomb exploded on player %s! Taking %d damage", nextPlayer.UserID, dmg) // ApplyDamage(isItemEffect=true 因为它来自道具?) // 是的,BombTimer 是一个道具。 e.ApplyDamage(state, nextPlayer, dmg, true) if nextPlayer.HP <= 0 { continue // 死于炸弹,跳过回合 } } } // 处理毒药 if nextPlayer.Poisoned { nextPlayer.PoisonSteps++ if nextPlayer.PoisonSteps%2 == 0 { // 毒药伤害 e.ApplyDamage(state, nextPlayer, 1, true) if nextPlayer.HP <= 0 { continue // 死于毒药,跳过回合 } } } // 处理跳过 if nextPlayer.SkipTurn { nextPlayer.SkipTurn = false e.Logger.Info("Player %s skipped turn", nextPlayer.UserID) continue } // 找到有效玩家 break } }