Files
bl/logic/service/fight/action.go
xinian 78a68148ce
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
chore: update fight logic and effect implementations
2026-04-05 02:25:44 +08:00

363 lines
8.9 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.actionRound.Store(uint32(f.Round))
f.actionMu.Unlock()
}
func (f *FightC) closeActionWindow() {
f.actionMu.Lock()
f.acceptActions = false
f.pendingActions = f.pendingActions[: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
}
replaceIndex := -1
for i, pending := range f.pendingActions {
if pending == nil || actionSlotKeyFromAction(pending) != actionSlotKeyFromAction(act) {
continue
}
replaceIndex = i
break
}
if replaceIndex >= 0 {
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 len(f.pendingActions) == 0 {
f.actionMu.Unlock()
return nil
}
act := f.pendingActions[0]
copy(f.pendingActions, f.pendingActions[1:])
f.pendingActions = f.pendingActions[:len(f.pendingActions)-1]
hasMore := len(f.pendingActions) > 0
notify := f.actionNotify
f.actionMu.Unlock()
if hasMore && notify != nil {
select {
case notify <- struct{}{}:
default:
}
}
return act
}
// 玩家逃跑/无响应/掉线
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
}
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) {
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 {
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: uint32(fighter.CanCapture),
}
if fighter.AttackValue != nil {
fightInfo.Prop = fighter.AttackValue.Prop
}
infos = append(infos, fightInfo)
}
return infos
}
// 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)
})
})
}