238 lines
6.4 KiB
Go
Executable File
238 lines
6.4 KiB
Go
Executable File
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
|
||
}
|
||
}
|