game/server/logic/engine.go

201 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
func NewGameEngine(logger runtime.Logger, dispatcher runtime.MatchDispatcher, charMgr *characters.CharacterManager, itemMgr *items.ItemManager, presences map[string]runtime.Presence, disconnected map[string]*core.Player) *GameEngine {
return &GameEngine{
CharManager: charMgr,
ItemManager: itemMgr,
Dispatcher: dispatcher,
Logger: logger,
Presences: presences,
DisconnectedPlayers: disconnected,
}
}
// 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向后端结算游戏
if winnerID != "" && winnerID != "draw" {
winnerPlayer, ok := state.Players[winnerID]
if !ok {
// 如果在 Players 里没找到,尝试在断线列表中找(可能获胜者刚好断线)
winnerPlayer = e.DisconnectedPlayers[winnerID]
}
if winnerPlayer != nil && winnerPlayer.RealUserID > 0 {
// 分数参数暂时传宝箱数,后端奖励由配置决定
config.SettleGameWithBackend(e.Logger, winnerPlayer.RealUserID, winnerPlayer.Ticket, "", true, winnerPlayer.ChestCount)
} else {
e.Logger.Error("Winner player %s has no RealUserID, cannot settle", winnerID)
}
}
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)
// ApplyDamageisItemEffect=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
}
}