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字段 - 简化宠物准备信息的数据结构 ```
419 lines
10 KiB
Go
419 lines
10 KiB
Go
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)
|
||
})
|
||
})
|
||
}
|