feat(fight): 增加战斗模式枚举并重构战斗逻辑判断

- 引入完整的 BattleMode 枚举定义,替代原有的 BattleStatus,明确区分各类战斗场景
- 在多个控制器中替换对旧 Status 字段的依赖,统一使用 Mode 判断战斗状态
- 修复部分函数调用前未检查 FightC 是否为空的问题,增加 ErrBattleEnded 错误返回
- 调整
This commit is contained in:
2025-11-21 02:40:27 +08:00
parent 105c6f5a23
commit e54d4bacaa
18 changed files with 217 additions and 123 deletions

View File

@@ -1,17 +1,16 @@
package fight
import (
"blazing/common/data/xmlres"
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/player"
"context"
"log"
"github.com/gogf/gf/v2/util/gconv"
"github.com/jinzhu/copier"
"github.com/panjf2000/ants/v2"
)
@@ -148,15 +147,20 @@ func (f *FightC) ReadyFight(c common.PlayerI) {
// 3. 根据战斗类型判断是否满足战斗启动条件,满足则启动
switch f.Info.Status {
case info.BattleStatus.FIGHT_WITH_PLAYER: // PVP战斗需双方都准备完成
case info.BattleMode.FIGHT_WITH_NPC: // NPC/野怪战斗:处理捕捉相关逻辑后启动
//f.handleNPCFightSpecial(&fightStartInfo)
if f.Opp.Player.(*player.AI_player).CanCapture > 0 {
f.Opp.CanCapture = f.Opp.Player.(*player.AI_player).CanCapture
fightStartInfo.Info2.Catchable = 1 //可以捕捉就置1
}
f.startBattle(fightStartInfo)
default: // PVP战斗需双方都准备完成
if f.checkBothPlayersReady(c) {
f.startBattle(fightStartInfo)
}
case info.BattleStatus.FIGHT_WITH_BOSS: // BOSS战单方准备完成即可启动
f.startBattle(fightStartInfo)
case info.BattleStatus.FIGHT_WITH_NPC: // NPC/野怪战斗:处理捕捉相关逻辑后启动
f.handleNPCFightSpecial(&fightStartInfo)
f.startBattle(fightStartInfo)
}
}
@@ -185,28 +189,6 @@ func (f *FightC) checkBothPlayersReady(currentPlayer common.PlayerI) bool {
return opponentInput.Finished
}
// handleNPCFightSpecial 处理NPC战斗的特殊逻辑如可捕捉标记
func (f *FightC) handleNPCFightSpecial(startInfo *info.FightStartOutboundInfo) {
// 检查野怪是否可捕捉根据宠物ID获取捕捉率
if len(f.ReadyInfo.OpponentPetList) == 0 {
return
}
npcPetID := int(f.ReadyInfo.OpponentPetList[0].ID)
petCfg, ok := xmlres.PetMAP[npcPetID]
if !ok {
// log.Error(context.Background(), "NPC宠物配置不存在", "petID", npcPetID)
return
}
catchRate := gconv.Int(petCfg.CatchRate)
if catchRate > 0 {
startInfo.Info2.Catchable = 1 // 标记为可捕捉
// 标记AI对手允许被捕捉类型断言确保安全
f.Opp.CanCapture = true
}
}
// startBattle 启动战斗核心逻辑:提交战斗循环任务并通知双方
func (f *FightC) startBattle(startInfo info.FightStartOutboundInfo) {

View File

@@ -24,18 +24,44 @@ import (
// 战斗模式
// BattleMode 战斗模式对应AS中的战斗类型常量
var BattleMode = enum.New[struct {
PET_MELEE uint32 `enum:"3"` //乱斗模式
SINGLE_MODE uint32 `enum:"1"` // 单人模式
MULTI_MODE uint32 `enum:"2"` // 人模式
}]()
var BattleStatus = enum.New[struct {
// 原ActionScript中的常量映射
FIGHT_WITH_NPC uint32 `enum:"3"` // 与NPC战斗
FIGHT_WITH_BOSS uint32 `enum:"2"` // 与BOSS战斗
FIGHT_WITH_PLAYER uint32 `enum:"1"` // 与玩家战斗PVP
Null uint32 `enum:"0"`
// 基础模式
FIGHT_WITH_NPC uint32 `enum:"0"` //NPC模式
SINGLE_MODE uint32 `enum:"1"` // 人模式
MULTI_MODE uint32 `enum:"2"` // 多人模式
PET_MELEE uint32 `enum:"3"` // 宠物乱斗模式
DARK_FIGHT uint32 `enum:"4"` // 暗黑战斗
PET_TOPLEVEL uint32 `enum:"5"` // 宠物顶级战
PET_ELEMENT_FIGHT uint32 `enum:"6"` // 宠物元素战斗修正原拼写错误ELMENT → ELEMENT
FIGHT_DEMON_SPACE uint32 `enum:"7"` // 魔域战斗
BATTLE_LAB uint32 `enum:"8"` // 战斗实验室
LUCKY_BATTLE uint32 `enum:"9"` // 幸运战斗
FIGHT_ARENA uint32 `enum:"10"` // 竞技场战斗
CORE_FIGHT uint32 `enum:"11"` // 核心战斗
IMAGE_FIGHT uint32 `enum:"12"` // 镜像战斗
TOP_WAR_BEYOND uint32 `enum:"13"` // 巅峰之战·超越
HIGHER_FIGHT uint32 `enum:"14"` // 高阶战斗
GOLDEN_PALACE_FIGHT uint32 `enum:"15"` // 金殿战斗
FANTASY_PET_MELEE uint32 `enum:"16"` // 梦幻宠物乱斗
REFRACTIVE_MAGIC uint32 `enum:"17"` // 折射魔法战
PET_MELEE_FIGHT_BOSS uint32 `enum:"18"` // 宠物乱斗·打BOSS
PET_TRY_FIGHT uint32 `enum:"19"` // 宠物试玩战斗
PET_TOPLEVEL_NEW uint32 `enum:"20"` // 宠物顶级战(新版)
CATCH_LENNY_GAME uint32 `enum:"21"` // 捕捉莱尼小游戏
PEAK_JIHAD_FREE uint32 `enum:"22"` // 巅峰圣战·自由战
PEAK_JIHAD_3V3 uint32 `enum:"23"` // 巅峰圣战·3V3
GOBLINKING_BATTLE uint32 `enum:"24"` // 哥布林王战斗
DOOM_FIGHT uint32 `enum:"25"` // 末日战斗
SHOW_ROUND uint32 `enum:"26"` // 展示回合(试玩/演示)
PEAK_JIHAD_6V6 uint32 `enum:"27"` // 巅峰圣战·6V6
PEAK_JIHAD_FREE_PLAN uint32 `enum:"28"` // 巅峰圣战·自由战(计划版)
PEAK_JIHAD_6V6_JJ uint32 `enum:"29"` // 巅峰圣战·6V6竞技版
WIZARDKING_BIGFIGHT uint32 `enum:"30"` // 法老王大战
QINGLONG_COMPLETE_FIGHT uint32 `enum:"83"` // 青龙全套战斗修正原拼写COMPLELETE → COMPLETE
PEAK_JIHAD_FIGHT_WITH_FIGURE uint32 `enum:"87"` // 巅峰圣战·角色对战
PEAK_JIHAD_BIG_MELEE uint32 `enum:"88"` // 巅峰圣战·大乱斗
PEAK_JIHAD_LIMIT_AC uint32 `enum:"999"` // 巅峰圣战·限AC账号类型限制
}]()
// 玩家离线数据

View File

@@ -45,11 +45,10 @@ type S2C_NOTE_HANDLE_FIGHT_INVITE struct {
}
type Fightinfo struct {
///PlayerID uint32
// 战斗模式 1 = 1v1 2 = 6v6 3大乱斗
// 战斗模式 1 = 1v1 2 = 6v6 3大乱斗 0 什么都不做
Mode uint32
Type uint32 //战斗类型
//Type uint32 //战斗类型
//0无战斗1PVP2,BOOS,3PVE
Status uint32
}

View File

@@ -105,10 +105,6 @@ func (f *FightC) LoadPercent(c common.PlayerI, percent int32) {
}
func (f *FightC) initplayer(c common.PlayerI) (*input.Input, errorcode.ErrorCode) {
if len(c.GetInfo().PetList) == 0 {
return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon
}
if !c.CanFight() {
return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon
@@ -221,12 +217,12 @@ func NewFight(p1, p2 common.PlayerI, fn func(*info.FightOverInfo)) (*FightC, err
f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.Our)
f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.Opp)
var loadtime time.Duration = 120 * time.Second
//说明是PVE
if f.Info.Mode == info.BattleMode.FIGHT_WITH_NPC {
switch f.Info.Mode {
case info.BattleStatus.FIGHT_WITH_PLAYER:
default:
f.Opp.Finished = true //PVE 默认boss数据直接加载完成
loadtime = 60 * time.Second
}
f.Broadcast(func(ff *input.Input) {
@@ -240,16 +236,7 @@ func NewFight(p1, p2 common.PlayerI, fn func(*info.FightOverInfo)) (*FightC, err
})
var t time.Duration
// 60秒后判断战斗是否开始
switch f.Info.Mode {
case info.BattleMode.PET_MELEE:
t = 120 * time.Second
default:
t = 60 * time.Second
}
cool.Cron.AfterFunc(t, func() {
cool.Cron.AfterFunc(loadtime, func() {
if !f.Our.Finished || !f.Opp.Finished { //如果有任一没有加载完成
f.closefight = true //阻止继续添加action
f.Reason = info.BattleOverReason.PlayerOVerTime

View File

@@ -18,7 +18,7 @@ type Input struct {
AllPet []*info.BattlePetEntity
Player common.PlayerI
Opp *Input
CanCapture bool
CanCapture int
Finished bool //是否加载完成
*info.AttackValue
FightC common.FightI

View File

@@ -125,8 +125,7 @@ func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.Bat
}
// AI自动技能
if pid != 0 && (f.Info.Status == info.BattleStatus.FIGHT_WITH_BOSS ||
f.Info.Status == info.BattleStatus.FIGHT_WITH_NPC) {
if pid != 0 && (f.Info.Status == info.BattleMode.FIGHT_WITH_NPC) {
f.GetInputByAction(paction, true).GetAction(f.Our)
//panic("AI自动技能")
}
@@ -223,7 +222,8 @@ func (f *FightC) handleItemAction(a *action.UseItemAction) {
switch {
case gconv.Int(item.Bonus) != 0:
if f.Opp.CanCapture { //可以捕捉
if f.Opp.CanCapture > 0 { //可以捕捉
f.Opp.CurrentPet.CatchRate = f.Opp.CanCapture
ok, res := f.Our.Capture(f.Opp.CurrentPet, a.ItemID, -1)
our := f.Our.Player.(*player.Player)
if ok {

View File

@@ -8,8 +8,8 @@ import (
type AI_player struct {
baseplayer
petinfo []model.PetInfo //精灵信息
petinfo []model.PetInfo //精灵信息
CanCapture int
}
func (p *AI_player) Getfightinfo() info.Fightinfo {

View File

@@ -24,7 +24,8 @@ func NewDone(P *Player) Done {
// MAPID 地图ID
// BOSSID 地图BOSSID
// 注册胜利次数
func (d *Done) SPT(mapid, bossid, count uint32, fn func()) *bus.Listener[*model.MilestoneEX] {
// 监听器返回奖励是否发送完成,完成就done
func (d *Done) SPT(mapid, bossid, count uint32, fn func() bool) *bus.Listener[*model.MilestoneEX] {
return d.Topic.Sub(func(v *model.MilestoneEX) {
if v.DoneType == model.MilestoneMode.BOSS && EqualBasicSlice(v.Args, []uint32{mapid, bossid}) && v.Count == count {
@@ -34,8 +35,11 @@ func (d *Done) SPT(mapid, bossid, count uint32, fn func()) *bus.Listener[*model.
return v1 == count
})
if !ok { //说明没有触发过
v.Results = append(v.Results, count) //把本次的记录添加
fn()
if fn() {
v.Results = append(v.Results, count) //把本次的记录添加
}
}
}

View File

@@ -4,13 +4,20 @@ import (
"blazing/common/socket/errorcode"
"blazing/logic/service/common"
"blazing/logic/service/fight/info"
"sync/atomic"
)
func (p *Player) JoinFight(fn func(p common.PlayerI) bool) errorcode.ErrorCode {
//加入队列前就开始判断一次
if !p.CanFight() {
return errorcode.ErrorCodes.ErrNoEligiblePokemon
}
if p.GetSpace().Owner.UserID == p.Info.UserID {
return errorcode.ErrorCodes.ErrSystemError
}
//修复发包进入,如果此时是擂台主
p.GetSpace().User.Range(func(key uint32, v common.PlayerI) bool {
@@ -24,7 +31,11 @@ func (p *Player) JoinFight(fn func(p common.PlayerI) bool) errorcode.ErrorCode {
ttt := fn(v)
if ttt {
atomic.StoreUint32(&v.(*Player).Fightinfo.Mode, 0)
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
}
// lw = value
return ttt //如果发起成功就停止,否则继续遍历队列
}

View File

@@ -103,8 +103,6 @@ func (p *Player) Getfightinfo() info.Fightinfo {
return p.Fightinfo
}
func (p *Player) QuitFight() {
//将战斗标记设置为0 这里的标记是
atomic.StoreUint32(&p.Fightinfo.Status, 0)
p.FightC = nil
@@ -116,11 +114,14 @@ func (p *Player) GetSpace() *space.Space {
// 0无战斗1PVP2,BOOS,3PVE
func (p *Player) CanFight() bool {
// if atomic.CompareAndSwapUint32(&p.Fightinfo.Status, 0, staus) { //先判断是否竞态条件被挑战
if len(p.Info.PetList) == 0 {
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return false
//成功,继续判断
}
if p.FightC != nil {
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return false
}
@@ -133,7 +134,7 @@ func (p *Player) CanFight() bool {
}
}
// 遍历完所有宠物都没有血量大于0的才不能战斗
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return false
// }