Files
bl/logic/service/fight/action.go
昔念 ce1a2a3588
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
feat(xmlres): 使用rawFlexibleString替换字符串类型以支持灵活解析

- 将EffectArg结构体中的SideEffectArg字段类型从string改为rawFlexibleString
- 将Move结构体中的Name字段类型从string改为rawFlexibleString,并更新反序列化逻辑
- 统一配置文件解析方式,移除磁盘回退机制并简化readConfigContent函数
- 移除不再使用的导入包和变量

fix(fight): 修复战斗系统中的空技能和无效数据问题

- 在collectAttackValues函数中过滤掉SkillID为0的攻击值
- 添加检查避免发送空的攻击信息到客户端
- 移除输入模块中未使用的捕捉逻辑

refactor(middleware): 重构中间件配置并添加CDK权限控制

- 简化middleware.go文件结构
- 为CDK相关接口添加适当的权限中间件
- 优化服务器代理配置

feat(player): 移除宠物捕捉状态字段

- 从ReadyFightPetInfo结构体中移除IsCapture字段
- 简化宠物准备信息的数据结构
```
2026-04-13 11:34:28 +08:00

419 lines
10 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/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/player/model"
)
// Compare 比较两个战斗动作的执行优先级
func (f *FightC) Compare(a, b action.BattleActionI) (action.BattleActionI, action.BattleActionI) {
if a == nil {
return b, a
}
if b == nil {
return a, b
}
// 动作本身的优先级比较
p1 := b.Priority() - a.Priority()
if p1 > 0 { // 对手优先级更高
return b, a
} else if p1 < 0 {
return a, b
}
if speedA, speedB := f.actionSpeed(a), f.actionSpeed(b); speedA.Cmp(speedB) != 0 {
if speedA.Cmp(speedB) > 0 {
return a, b
}
return b, a
}
if a.GetActorIndex() != b.GetActorIndex() {
if a.GetActorIndex() < b.GetActorIndex() {
return a, b
}
return b, a
}
return a, b // 速度相同时,发起方优先
}
func (f *FightC) openActionWindow() {
f.actionMu.Lock()
f.acceptActions = true
f.pendingActions = f.pendingActions[:0]
f.pendingHead = 0
f.actionRound.Store(uint32(f.Round))
f.actionMu.Unlock()
}
func (f *FightC) closeActionWindow() {
f.actionMu.Lock()
f.acceptActions = false
f.pendingActions = f.pendingActions[:0]
f.pendingHead = 0
f.actionRound.Store(0)
f.actionMu.Unlock()
}
func (f *FightC) submitAction(act action.BattleActionI) {
if act == nil || f.closefight {
return
}
round := f.actionRound.Load()
if round == 0 {
return
}
act.SetRound(round)
f.actionMu.Lock()
if !f.acceptActions || act.GetRound() != f.actionRound.Load() {
f.actionMu.Unlock()
return
}
f.compactPendingActionsLocked()
replaceIndex := -1
for i := f.pendingHead; i < len(f.pendingActions); i++ {
pending := f.pendingActions[i]
if pending == nil || actionSlotKeyFromAction(pending) != actionSlotKeyFromAction(act) {
continue
}
replaceIndex = i
break
}
if replaceIndex >= 0 {
if f.LegacyGroupProtocol {
f.actionMu.Unlock()
return
}
f.pendingActions[replaceIndex] = act
} else {
f.pendingActions = append(f.pendingActions, act)
}
notify := f.actionNotify
f.actionMu.Unlock()
if notify == nil {
return
}
select {
case notify <- struct{}{}:
default:
}
}
func (f *FightC) nextAction() action.BattleActionI {
f.actionMu.Lock()
if f.pendingHead >= len(f.pendingActions) {
f.pendingActions = f.pendingActions[:0]
f.pendingHead = 0
f.actionMu.Unlock()
return nil
}
act := f.pendingActions[f.pendingHead]
f.pendingActions[f.pendingHead] = nil
f.pendingHead++
hasMore := f.pendingHead < len(f.pendingActions)
if !hasMore {
f.pendingActions = f.pendingActions[:0]
f.pendingHead = 0
} else {
f.compactPendingActionsLocked()
}
notify := f.actionNotify
f.actionMu.Unlock()
if hasMore && notify != nil {
select {
case notify <- struct{}{}:
default:
}
}
return act
}
func (f *FightC) compactPendingActionsLocked() {
if f.pendingHead == 0 {
return
}
if f.pendingHead < len(f.pendingActions)/2 && len(f.pendingActions) < cap(f.pendingActions) {
return
}
remaining := len(f.pendingActions) - f.pendingHead
copy(f.pendingActions, f.pendingActions[f.pendingHead:])
for i := remaining; i < len(f.pendingActions); i++ {
f.pendingActions[i] = nil
}
f.pendingActions = f.pendingActions[:remaining]
f.pendingHead = 0
}
// 玩家逃跑/无响应/掉线
func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
if f.closefight {
return
}
if f.Info.Status != info.BattleMode.FIGHT_WITH_NPC && res == model.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
}
f.FightOverInfo.Reason = f.Reason
f.FightOverInfo.WinnerId = f.WinnerId
f.closefight = true
close(f.quit)
})
}
// 切换精灵 主动和被驱逐
func (f *FightC) ChangePet(c common.PlayerI, id uint32) {
f.ChangePetAt(c, id, 0)
}
func (f *FightC) ChangePetAt(c common.PlayerI, id uint32, actorIndex int) {
if f.closefight {
return
}
self := f.getInputByUserID(c.GetInfo().UserID, actorIndex, false)
if self == nil {
return
}
ii, _ := self.GetPet(id)
if ii == nil {
//无法切换不允许切换的精灵
return
}
//todo 待实现无法切精灵的情况
ret := &action.ActiveSwitchAction{
BaseAction: action.NewBaseAction(c.GetInfo().UserID),
Cid: id,
}
ret.ActorIndex = actorIndex
f.submitAction(ret)
}
// 玩家使用技能
func (f *FightC) UseSkill(c common.PlayerI, id uint32) {
f.UseSkillAt(c, id, 0, 0)
}
func (f *FightC) UseSkillAt(c common.PlayerI, id uint32, actorIndex, targetIndex int) {
if f.closefight {
return
}
ret := &action.SelectSkillAction{
BaseAction: action.NewBaseAction(c.GetInfo().UserID),
}
ret.ActorIndex = actorIndex
self := f.getInputByUserID(c.GetInfo().UserID, actorIndex, false)
if self == nil {
return
}
currentPet := self.PrimaryCurPet()
if currentPet == nil {
return
}
if currentPet.Info.Hp <= 0 {
return
}
// t, ok := f.GetInputByPlayer(c, false).CurPet[0].Skills[id]
// if ok {
// ret.SkillEntity = t
// }
for _, v := range currentPet.Skills {
if v.XML.ID == int(id) {
ret.SkillEntity = v
break
}
}
ret.TargetIndex = normalizeSkillTargetIndex(actorIndex, targetIndex, ret.SkillEntity)
f.submitAction(ret)
}
func normalizeSkillTargetIndex(actorIndex, targetIndex int, skill *info.SkillEntity) int {
if skill == nil {
return targetIndex
}
// 约定:非负目标位表示敌方;负值 -(index+1) 表示同侧(自己/队友)
if _, targetIsOpposite := DecodeTargetIndex(targetIndex); !targetIsOpposite {
return targetIndex
}
// GBTL.AtkType: 0=所有人 1=仅己方 2=仅对方 3=仅自己默认2
switch skill.XML.AtkType {
case 1, 3:
// 旧协议未传目标时,己方类技能默认作用自己;新协议可通过负编码显式指定队友。
return EncodeTargetIndex(actorIndex, false)
default:
return targetIndex
}
}
// 玩家使用技能
func (f *FightC) Capture(c common.PlayerI, id uint32) {
if f.closefight {
return
}
f.submitAction(&action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: id})
}
func (f *FightC) c(c common.PlayerI, cacthid, itemid uint32) {
f.UseItemAt(c, cacthid, itemid, 0, 0)
}
func (f *FightC) UseItem(c common.PlayerI, cacthid, itemid uint32) {
f.UseItemAt(c, cacthid, itemid, 0, 0)
}
func (f *FightC) UseItemAt(c common.PlayerI, cacthid, itemid uint32, actorIndex, targetIndex int) {
if f.closefight {
return
}
if f.Info.Mode == info.BattleMode.PET_MELEE {
go f.UseSkill(c, 0)
return
}
actionInfo := &action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: itemid, CacthTime: cacthid}
actionInfo.ActorIndex = actorIndex
actionInfo.TargetIndex = targetIndex
f.submitAction(actionInfo)
}
// ReadyFight 处理玩家战斗准备逻辑,当满足条件时启动战斗循环
func (f *FightC) ReadyFight(c common.PlayerI) {
if f.LegacyGroupProtocol {
input := f.GetInputByPlayer(c, false)
if input == nil {
return
}
input.Finished = true
if f.checkBothPlayersReady(c) {
f.startBattle(f.FightStartOutboundInfo)
}
return
}
f.BroadcastPlayers(func(p common.PlayerI) {
p.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 {
startInfo := info.FightStartOutboundInfo{}
ourInfos := f.collectFightPetInfos(f.Our)
oppInfos := f.collectFightPetInfos(f.Opp)
startInfo.Info1 = append(startInfo.Info1, ourInfos...)
startInfo.Info2 = append(startInfo.Info2, oppInfos...)
startInfo.Info1Len = uint32(len(startInfo.Info1))
startInfo.Info2Len = uint32(len(startInfo.Info2))
return startInfo
}
func (f *FightC) collectFightPetInfos(inputs []*input.Input) []info.FightPetInfo {
infos := make([]info.FightPetInfo, 0, len(inputs))
for actorIndex, fighter := range inputs {
if fighter == nil || fighter.Player == nil {
continue
}
currentPet := fighter.PrimaryCurPet()
if currentPet == nil {
continue
}
fightInfo := info.FightPetInfo{
UserID: fighter.Player.GetInfo().UserID,
ActorIndex: uint32(actorIndex),
ControllerUserID: currentPet.ControllerUserID,
ID: currentPet.Info.ID,
Name: currentPet.Info.Name,
CatchTime: currentPet.Info.CatchTime,
Hp: currentPet.Info.Hp,
MaxHp: currentPet.Info.MaxHp,
Level: currentPet.Info.Level,
Catchable: fightPetCatchableFlag(fighter.CanCapture),
}
if fighter.AttackValue != nil {
fightInfo.Prop = fighter.AttackValue.Prop
}
infos = append(infos, fightInfo)
}
return infos
}
func fightPetCatchableFlag(catchRate int) uint32 {
if catchRate > 0 {
return 1
}
return 0
}
// 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.BroadcastPlayers(func(p common.PlayerI) {
if f.LegacyGroupProtocol {
f.sendLegacyGroupStart(p)
return
}
f.sendFightPacket(p, fightPacketStart, &startInfo)
})
})
}