Files
bl/logic/service/fight/loop.go
昔念 96b5dbb425 feat(fight): 重构属性同步与反转效果逻辑
统一处理效果45、51、55、56的属性同步与反转逻辑,优化代码结构并提高可维护性。新增通用效果结构体 `EffectPropSyncReverse` 和操作类型定义,集中管理不同属性操作行为。

fix(controller): 修复玩家离开地图逻辑错误

修正 `FRESH_LEAVE_FIGHT_LEVEL` 接口中 defer 调用为进入地图,并发送角色信息包给客户端以确保状态一致。

feat(effect): 新增天敌机制核心逻辑占位

在 `NewSel14` 效果中添加 `Turn_Start` 方法,实现若遇到天敌则害怕多回合的核心逻辑框架。

chore(config): 更新Boss配置怪物ID及血量

调整Boss ID为2的怪物配置,替换原有Monster ID并设置血量为10,用于测试或平衡调整。

refactor(fight): 优化战斗循环和精灵切换逻辑

整理战斗主循环中的血量赋值语句格式,调整精灵切换时变量顺序以避免潜在问题,并修复死亡标记逻辑。

refactor(node): 恢复BoolisFalse方法实现

取消注释 `BoolisFalse` 方法内容,恢复其正常功能以便其他模块正确判断布尔条件。

style(logic): 格式化代码空行和缩进

清理多余空行,对齐导入语句与其他代码块格式,增强整体代码可读性。

debug(effect): 增加烧伤伤害调试打印

在持续伤害效果中加入println语句,输出实际造成的真实伤害数值便于排查问题。
2025-12-18 23:57:17 +08:00

396 lines
9.7 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/common/data/xmlres"
"blazing/common/utils"
"blazing/cool"
"context"
"blazing/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/player"
"fmt"
"time"
"github.com/alpacahq/alpacadecimal"
"github.com/gogf/gf/v2/util/gconv"
)
func (f *FightC) battleLoop() {
f.actionChan = make(chan action.BattleActionI, 10)
//fmt.Println("战斗开始精灵", f.Our.Player.GetInfo().PetList[0].CatchTime)
ourID := f.Our.Player.GetInfo().UserID
oppID := f.Opp.Player.GetInfo().UserID
//fmt.Println("开始收集玩家动作", waitr)
for !f.closefight {
f.Round++
//fmt.Printf("—— 第 %d 回合开始 ——\n", f.Round)
// AI自动技能
actions := f.collectPlayerActions(ourID, oppID)
if f.closefight {
break
}
println("进入回合")
f.resolveRound(actions[ourID], actions[oppID])
}
f.Broadcast(func(ff *input.Input) {
//todo 将血量和技能pp传回enterturn
ff.Exec(func(tt input.Effect) bool {
tt.OnBattleEnd()
tt.Alive(false) //将所有属性变化失效掉
return true
})
if f.Info.Mode != info.BattleMode.PET_MELEE { //不是乱斗,传回血量
for i := 0; i < len(ff.AllPet); i++ {
for j := 0; j < len(ff.Player.GetInfo().PetList); j++ {
if ff.Player.GetInfo().PetList[j].CatchTime == ff.AllPet[i].Info.CatchTime {
if ff.UserID == f.WinnerId {
if ff.CurrentPet.Info.CatchTime == ff.Player.GetInfo().PetList[j].CatchTime {
f.Winpet = &ff.Player.GetInfo().PetList[j]
}
}
ff.Player.GetInfo().PetList[j].Hp = utils.Max(ff.Player.GetInfo().PetList[j].MaxHp, ff.AllPet[i].Info.Hp)
ff.Player.GetInfo().PetList[j].SkillList = ff.AllPet[i].Info.SkillList
}
}
}
}
})
if f.Reason == info.BattleOverReason.Cacthok {
f.WinnerId = f.ownerID
f.Our.Player.(*player.Player).Service.Pet.PetAdd(&f.Opp.Player.GetInfo().PetList[0])
f.Our.Player.SendPackCmd(2409, &info.CatchMonsterOutboundInfo{
CatchTime: uint32(f.Opp.Player.GetInfo().PetList[0].CatchTime),
PetId: uint32(f.Opp.CurrentPet.ID),
})
//f.Reason = 0 //清空
}
//f.Reason = info.BattleOverReason.PlayerCaptureSuccess
//f.WinnerId = 0 //捕捉成功不算胜利
if f.callback != nil {
f.callback(&f.FightOverInfo) //先执行回调,再执行返回信息,在回调内修改战斗判断
}
//大乱斗,给个延迟
//<-time.After(1000)
f.Broadcast(func(ff *input.Input) {
ff.Player.SendPackCmd(2506, &f.FightOverInfo)
ff.Player.QuitFight()
//待退出玩家战斗状态
})
// close(f.actionChan)
fmt.Println(f.ownerID, "战斗循环结束")
close(f.over)
}
// 收集玩家动作(含超时判定)
func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.BattleActionI {
actions := make(map[uint32]action.BattleActionI)
waitr := time.Duration(f.waittime)*time.Millisecond*10 + 60*time.Second
timeout := time.After(waitr)
for len(actions) < 2 {
select {
case <-f.quit:
f.closefight = true
return actions
case paction, ok := <-f.actionChan:
if !ok {
f.closefight = true
return actions
}
if paction == nil {
continue
}
pid := paction.GetPlayerID()
if pid != ourID && pid != oppID {
continue
}
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC && pid != 0 {
f.Opp.GetAction()
println("开始执行NPC动作")
}
selfinput := f.GetInputByAction(paction, false)
if ret, ok := paction.(*action.ActiveSwitchAction); ok {
//正常结束可以切换,以及死切后还能再切一次
if selfinput.CanChange == 0 {
if selfinput.CurrentPet.Info.Hp > 0 { //非死亡切换
selfinput.CanChange = 1
} else {
selfinput.CanChange = 2
}
oldpet := selfinput.CurrentPet
InitAttackValue := *selfinput.AttackValue
selfinput.CurrentPet, ret.Reason = selfinput.GetPet(ret.Cid)
selfinput.Player.SendPackCmd(2407, &ret.Reason)
f.Switch[selfinput.UserID] = ret
selfinput.InitAttackValue() //切换精灵消除能力提升
//这时候精灵已经切换过了,可以直接给新精灵加效果
f.Broadcast(func(ff *input.Input) {
ff.Exec(func(t input.Effect) bool {
t.Switch(selfinput, InitAttackValue, oldpet)
return true
})
})
if selfinput.CanChange == 2 {
selfinput.CanChange = 0
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC && pid == 0 {
f.Switch = make(map[uint32]*action.ActiveSwitchAction)
f.Our.Player.SendPackCmd(2407, &ret.Reason)
// f.Opp.GetAction()
println("开始执行NPC死切动作")
}
continue
}
}
} else {
if selfinput.CurrentPet.Info.Hp <= 0 { //0血执行非切换动作
//todo 记录异常操作
continue
}
if selfinput.CanChange == 1 { //非被动死亡情况下,不能执行额外动作,0允许切2是死亡可以额外动作
continue
}
// if selfinput.CanChange == 2 {
// selfinput.CanChange--
// }
}
actions[pid] = paction
//fmt.Println("玩家执行动作:", pid, paction.Priority())
case <-timeout:
f.handleTimeout(ourID, oppID, actions)
return actions
}
}
return actions
}
// 超时处理逻辑
func (f *FightC) handleTimeout(ourID, oppID uint32, actions map[uint32]action.BattleActionI) {
for _, pid := range []uint32{ourID, oppID} {
if _, exists := actions[pid]; exists {
continue
}
cool.Loger.Debug(context.Background(), "玩家超时", pid)
f.Reason = info.BattleOverReason.PlayerOVerTime
f.closefight = true
//对方赢
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
f.WinnerId = f.GetInputByPlayer(f.getPlayerByID(pid), true).Player.GetInfo().UserID
}
}
}
// 根据动作类型执行一回合结算
func (f *FightC) resolveRound(p1Action, p2Action action.BattleActionI) {
if p1Action == nil || p2Action == nil {
cool.Loger.Debug(context.Background(), "某方未选择动作,自动跳过结算")
return
}
// fmt.Println("开始结算回合")
// 动作优先级排序
b1, b2 := f.Compare(p1Action, p2Action)
switch a := b1.(type) {
case *action.ActiveSwitchAction:
if f.GetInputByAction(a, false).CurrentPet.Info.Hp <= 0 {
f.GetInputByAction(a, false).CurrentPet.Info.Hp = 1
}
if b2k, ok := b2.(*action.SelectSkillAction); ok {
if b2k.SkillEntity != nil {
if b2k.CD != nil {
f.waittime = *b2k.CD
}
}
f.enterturn(b2.(*action.SelectSkillAction), nil)
} else {
f.enterturn(nil, nil)
}
case *action.UseItemAction:
f.handleItemAction(a)
if f.GetInputByAction(a, false).CurrentPet.Info.Hp <= 0 {
f.GetInputByAction(a, false).CurrentPet.Info.Hp = 1
}
if b2k, ok := b2.(*action.SelectSkillAction); ok {
if b2k.SkillEntity != nil {
if b2k.CD != nil {
f.waittime = *b2k.CD
}
}
f.enterturn(b2.(*action.SelectSkillAction), nil)
} else {
if a1, ok := b2.(*action.UseItemAction); ok {
f.handleItemAction(a1)
}
f.enterturn(nil, nil)
}
default:
f.handleSkillActions(b1, b2)
}
}
// 使用道具的逻辑封装
func (f *FightC) handleItemAction(a *action.UseItemAction) {
item, ok := xmlres.ItemsMAP[int(a.ItemID)]
if !ok {
return
}
r := f.GetInputByAction(a, false).Player.(*player.Player).Service.Item.CheakItem(a.ItemID)
if r < 1 {
return
}
f.GetInputByAction(a, false).Player.(*player.Player).Service.Item.UPDATE(a.ItemID, -1)
switch {
case gconv.Int(item.Bonus) != 0:
if f.Opp.CanCapture > 0 { //可以捕捉
f.Opp.CurrentPet.CatchRate = f.Opp.CanCapture
ok, _ := f.Our.Capture(f.Opp.CurrentPet, a.ItemID, -1)
our := f.Our.Player.(*player.Player)
if ok {
f.Reason = info.BattleOverReason.Cacthok
f.closefight = true
} else {
our.SendPack(common.NewTomeeHeader(2409, f.ownerID).Pack(&info.CatchMonsterOutboundInfo{}))
}
}
case gconv.Int(item.HP) != 0:
addhp := item.HP
f.GetInputByAction(a, false).Exec(func(rr input.Effect) bool {
rr.Heal_Pre(a, &addhp)
return true
})
f.GetInputByAction(a, false).Heal(f.GetInputByAction(a, false), a, alpacadecimal.NewFromInt(int64(addhp)))
f.Broadcast(func(ff *input.Input) {
ff.Player.SendPackCmd(2406, &info.UsePetIteminfo{
UserID: f.GetInputByAction(a, false).UserID,
ChangeHp: int32(addhp),
ItemID: uint32(item.ID),
UserHp: uint32(f.GetInputByAction(a, false).CurrentPet.Info.Hp),
})
})
case gconv.Int(item.PP) != 0:
f.GetInputByAction(a, false).HealPP(item.PP)
f.Broadcast(func(ff *input.Input) {
ff.Player.SendPackCmd(2406, &info.UsePetIteminfo{
UserID: f.GetInputByAction(a, false).UserID,
ItemID: uint32(item.ID),
UserHp: uint32(f.GetInputByAction(a, false).CurrentPet.Info.Hp),
})
})
default:
fmt.Println(a.ItemID, "ItemID 不在指定范围内")
}
}
// 双方都是技能时的结算逻辑
func (f *FightC) handleSkillActions(a1, a2 action.BattleActionI) {
s1, _ := a1.(*action.SelectSkillAction)
s2, _ := a2.(*action.SelectSkillAction)
switch {
case s1 == nil || s1.SkillEntity == nil:
if s2.SkillEntity != nil {
if s2.CD != nil {
f.waittime = *s2.CD
}
}
f.enterturn(s2, nil)
// fmt.Println("1 空过 2玩家执行技能:", s2.PlayerID, s2.Info.ID)
case s2 == nil || s2.SkillEntity == nil:
if s1.SkillEntity != nil {
if s1.CD != nil {
f.waittime = *s1.CD
}
}
f.enterturn(s1, nil)
//fmt.Println("2 空过 玩家执行技能:", s1.PlayerID, s1.Info.ID)
default:
if s1.CD != nil {
f.waittime = *s1.CD
}
if s2.CD != nil {
f.waittime += *s2.CD
}
f.enterturn(s1, s2)
fmt.Println("玩家执行技能:", s1.PlayerID, s1.Info.ID, s2.PlayerID, s2.Info.ID)
}
}
// 根据玩家ID返回对应对象
func (f *FightC) getPlayerByID(id uint32) common.PlayerI {
if id == f.Our.Player.GetInfo().UserID {
return f.Our.Player
}
return f.Opp.Player
}