Files
bl/logic/service/fight/action.go
昔念 4fff047c4c
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
feat(item): 调整玄彩道具使用数量限制

玄彩道具检查逻辑从 items <= 0 修改为 items < 100,
确保玩家拥有至少100个道具才能使用。

fix(fight): 修复战斗操作通道阻塞问题

添加10秒超时机制到战斗操作通道发送逻辑中,
避免通道满载时的
2026-02-23 10:21:58 +08:00

225 lines
6.1 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 fight
import (
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"context"
"time"
"github.com/jinzhu/copier"
)
// Compare 比较两个1v1战斗动作的执行优先级核心逻辑
func (*FightC) Compare(a, b action.BattleActionI) (action.BattleActionI, action.BattleActionI) {
// 动作本身的优先级比较
p1 := b.Priority() - a.Priority()
if p1 > 0 { // 对手优先级更高
return b, a
} else if p1 < 0 {
return a, b
}
return a, b // 速度相同时,发起方优先
}
// 玩家逃跑/无响应/掉线
func (f *FightC) Over(c common.PlayerI, res info.EnumBattleOverReason) {
if f.closefight {
return
}
if f.Info.Status != info.BattleMode.FIGHT_WITH_NPC && res == info.BattleOverReason.PlayerEscape {
return
}
// case *action.EscapeAction:
// f.FightOverInfo.WinnerId = b2.GetPlayerID() //对方胜利
// f.FightOverInfo.Reason = a.Reason
// f.closefight = true
// ret := &action.EscapeAction{
// BaseAction: action.NewBaseAction(c.GetInfo().UserID),
// Reason: res,
// }
f.overl.Do(func() {
f.Reason = res
if f.GetInputByPlayer(c, true) != nil {
f.WinnerId = f.GetInputByPlayer(c, true).UserID
}
close(f.quit)
})
}
// 切换精灵 主动和被驱逐
func (f *FightC) ChangePet(c common.PlayerI, id uint32) {
if f.closefight {
return
}
ii, _ := f.GetInputByPlayer(c, false).GetPet(id)
if ii == nil {
//无法切换不允许切换的精灵
return
}
//todo 待实现无法切精灵的情况
ret := &action.ActiveSwitchAction{
BaseAction: action.NewBaseAction(c.GetInfo().UserID),
Cid: id,
}
select {
case f.actionChan <- ret:
// 发送成功,可选记录日志
// log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id)
case <-time.After(time.Second * 10):
// 通道满时的降级处理
cool.Logger.Printf(context.Background(), "actionChan is full, failed to send skill, userID: %d, skillID: %d",
c.GetInfo().UserID, id)
}
}
// 玩家使用技能
func (f *FightC) UseSkill(c common.PlayerI, id uint32) {
if f.closefight {
return
}
ret := &action.SelectSkillAction{
BaseAction: action.NewBaseAction(c.GetInfo().UserID),
}
if f.GetInputByPlayer(c, false).CurrentPet == nil {
return
}
if f.GetInputByPlayer(c, false).CurrentPet.Info.Hp <= 0 {
return
}
// t, ok := f.GetInputByPlayer(c, false).CurrentPet.Skills[id]
// if ok {
// ret.SkillEntity = t
// }
for _, v := range f.GetInputByPlayer(c, false).CurrentPet.Skills {
if v.ID == int(id) {
ret.SkillEntity = v
break
}
}
// 非阻塞发送避免goroutine永久阻塞
select {
case f.actionChan <- ret:
// 发送成功,可选记录日志
// log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id)
case <-time.After(time.Second * 10):
// 通道满时的降级处理
cool.Logger.Printf(context.Background(), "actionChan is full, failed to send skill, userID: %d, skillID: %d",
c.GetInfo().UserID, id)
}
}
// 玩家使用技能
func (f *FightC) Capture(c common.PlayerI, id uint32) {
if f.closefight {
return
}
select {
case f.actionChan <- &action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: id}:
// 发送成功,可选记录日志
// log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id)
case <-time.After(time.Second * 10):
// 通道满时的降级处理
cool.Logger.Printf(context.Background(), "actionChan is full, failed to send Capture, userID: %d ",
c.GetInfo().UserID)
}
}
func (f *FightC) UseItem(c common.PlayerI, cacthid, itemid uint32) {
if f.closefight {
return
}
select {
case f.actionChan <- &action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: itemid, CacthTime: cacthid}:
// 发送成功,可选记录日志
// log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id)
case <-time.After(time.Second * 10):
// 通道满时的降级处理
cool.Logger.Printf(context.Background(), "actionChan is full, failed to send UseItem, userID: %d, skillID: %d",
c.GetInfo().UserID, cacthid)
}
}
// ReadyFight 处理玩家战斗准备逻辑,当满足条件时启动战斗循环
func (f *FightC) ReadyFight(c common.PlayerI) {
f.Broadcast(func(ff *input.Input) {
ff.Player.SendPackCmd(2404, &info.S2C_2404{UserID: c.GetInfo().UserID})
})
// 2. 标记当前玩家已准备完成
input := f.GetInputByPlayer(c, false)
input.Finished = true
if f.checkBothPlayersReady(c) {
f.startBattle(f.FightStartOutboundInfo)
}
}
// buildFightStartInfo 构建战斗开始时需要发送给双方的信息
func (f *FightC) buildFightStartInfo() info.FightStartOutboundInfo {
var startInfo info.FightStartOutboundInfo
// 复制双方初始宠物信息(取列表第一个宠物)
if len(f.ReadyInfo.OurPetList) > 0 {
_ = copier.Copy(&startInfo.Info1, &f.ReadyInfo.OurPetList[0])
startInfo.Info1.UserID = f.ReadyInfo.OurInfo.UserID
}
if len(f.ReadyInfo.OpponentPetList) > 0 {
_ = copier.Copy(&startInfo.Info2, &f.ReadyInfo.OpponentPetList[0])
startInfo.Info2.UserID = f.ReadyInfo.OpponentInfo.UserID
}
return startInfo
}
// checkBothPlayersReady 检查PVP战斗中双方是否都已准备完成
// 参数c为当前准备的玩家返回true表示双方均准备完成
func (f *FightC) checkBothPlayersReady(currentPlayer common.PlayerI) bool {
// 这里的第二个参数true含义需结合业务确认推测为"检查对手"),建议用常量替代
opponentInput := f.GetInputByPlayer(currentPlayer, true)
return opponentInput.Finished
}
// startBattle 启动战斗核心逻辑:提交战斗循环任务并通知双方
func (f *FightC) startBattle(startInfo info.FightStartOutboundInfo) {
f.startl.Do(func() {
// 提交战斗循环到战斗池(处理战斗池容量问题)
// if err := Fightpool.Invoke(f); err != nil {
// log.Panic(context.Background(), "战斗循环提交失败", "error", err)
// }
go f.battleLoop()
f.Broadcast(func(ff *input.Input) {
// 通知双方玩家准备完成,即将开始战斗
ff.Player.SendPackCmd(2504, &startInfo)
})
})
}