refactor(fight): 重构战斗系统

- 优化了战斗逻辑和数据结构
- 修复了一些战斗相关的错误
- 提高了代码的可读性和可维护性
This commit is contained in:
2025-09-06 00:31:08 +08:00
parent f81593bfaf
commit ca8c4bcd04
18 changed files with 176 additions and 157 deletions

View File

@@ -31,7 +31,10 @@ func (h Controller) OnPlayerFightNpcMonster(data *fight.FightNpcMonsterInboundIn
ttt.OpponentInfo = info.FightUserInfo{UserID: 0}
refpet := c.OgreInfo.Data[data.Number]
if refpet.Id == 0 {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
mo := model.GenPetInfo(
int(refpet.Id), []int{0, 31},
[]int{0, 24},
@@ -46,11 +49,12 @@ func (h Controller) OnPlayerFightNpcMonster(data *fight.FightNpcMonsterInboundIn
}
if c.FightC != nil {
return nil, -1
return nil, errorcode.ErrorCodes.ErrOnlineOver6HoursCannotFight
}
c.FightC = &service.FightC{}
c.FightC.NewFight(&ttt, c) //把两个玩家都传进去
c.FightC.NewFight(&ttt, c, mo) //把两个玩家都传进去
c.FightC.OwnerID = c.Info.UserID
return nil, -1
}
@@ -69,7 +73,7 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
// 使用技能包
func (h Controller) UseSkill(data *fight.UseSkillInboundInfo, c *service.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
c.FightC.UseSkill(c, data.SkillId)
c.FightC.UseSkill(c, int32(data.SkillId))
return nil, 0
}

View File

@@ -21,6 +21,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mcuadros/go-defaults v1.2.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/panjf2000/ants/v2 v2.11.3 // indirect
github.com/samber/lo v1.51.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect

View File

@@ -41,6 +41,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=

View File

@@ -2,6 +2,7 @@ package service
import (
"blazing/logic/service/fight/info"
"blazing/modules/blazing/model"
"fmt"
)
@@ -46,10 +47,15 @@ func (f *AI_player) SendFightEndInfo(_ info.FightOverInfo) {
}
func (f *AI_player) GetAction() {
f.FightC.UseSkill(f, f.fightinfo.OpponentPetList[0].SkillList[0].ID) //使用1#技能,实际上要按照四个技能权重去使用
f.FightC.UseSkill(f, int32(f.fightinfo.OpponentPetList[0].SkillList[0].ID)) //使用1#技能,实际上要按照四个技能权重去使用
}
func (p *AI_player) End() {
p.FightC = nil
return
}
func (p *AI_player) GetPetInfo() []*model.PetInfo {
return nil
}

View File

@@ -3,8 +3,6 @@ package effect
import (
"blazing/logic/service/fight/battle/node"
"blazing/logic/service/fight/info"
"github.com/shopspring/decimal"
)
/**
@@ -15,20 +13,15 @@ type Effect1 struct {
}
func init() {
info.NodeM.AddEffect(&Effect1{})
}
info.InitEffect(1, &Effect1{})
// 重写EFFectID
func (this *Effect1) ID() int {
this.EffectNode.ParamSize(0) //设置参数个数
return 1
}
// 重写POST_DAMAGE ,伤害结束后触发回血
func (this *Effect1) PostDamage() bool {
off := this.GetSkill().DamageValue.Div(decimal.NewFromInt(2)) //伤害的一半
this.GetOwnerPet().HP += int(off.IntPart()) //这里是effect在对方挂载,故回血给自己回血
// off := this.GetSkill().DamageValue.Div(decimal.NewFromInt(2)) //伤害的一半
//this.GetOwnerPet().HP += int(off.IntPart()) //这里是effect在对方挂载,故回血给自己回血
//待重写实现
return true
}

View File

@@ -14,13 +14,12 @@ type Effect62 struct {
}
func init() {
info.NodeM.AddEffect(&Effect62{})
}
info.InitEffect(62, &Effect62{
EffectNode: node.EffectNode{
ArgSize: 1,
},
})
// 重写EFFectID
func (this *Effect62) ID() int {
this.EffectNode.ParamSize(1) //设置参数个数
return 62
}
func (this *Effect62) OnDamage() bool {
@@ -48,7 +47,7 @@ func (this *Effect62) SkillUseEnd() bool {
return true
}
defer func() { //延迟处理
this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this) //如果生效就移除
//this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this) //如果生效就移除
}()
//否则触发秒杀 在对面使用技能后
return true
@@ -60,7 +59,7 @@ func (this *Effect62) OnSwitchIn() bool {
if this.Hide { //如果还在隐藏,就直接返回
return true
}
this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this)
//this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this)
//否则触发秒杀 在对面使用技能后
return true

View File

@@ -13,13 +13,12 @@ type Effect67 struct {
}
func init() {
info.NodeM.AddEffect(&Effect67{})
}
info.InitEffect(67, &Effect67{
EffectNode: node.EffectNode{
ArgSize: 1,
},
})
// 重写EFFectID
func (this *Effect67) ID() int {
this.EffectNode.ParamSize(1) //设置参数个数
return 67
}
// 重写死亡,如果击败,就出触发死亡事件,判断是目标精灵

View File

@@ -8,18 +8,18 @@ import (
/**
* 连续使用每次威力增加n最高威力m
*/
func init() {
info.InitEffect(9, &Effect9{
EffectNode: node.EffectNode{
ArgSize: 2,
},
})
}
type Effect9 struct {
node.EffectNode
skillid int //记录使用的技能 如果技能变了就删除effect
UseSkillCount int //技能使用了多少次切换后置0
}
func init() {
info.NodeM.AddEffect(&Effect9{})
}
// 重写EFFectID
func (this *Effect9) ID() int {
this.EffectNode.ParamSize(2) //设置参数个数
return 9
}

View File

@@ -3,14 +3,25 @@ package effect
import (
"blazing/logic/service/fight/battle/node"
"blazing/logic/service/fight/info"
"unsafe"
)
func init() {
//技能使用成功时m%自身XX等级+/-n
info.NodeM.AddEffect(&Effect4{}) //注册4
info.InitEffect(4, NewEffectStat(false))
//技能使用成功时m%对方XX等级+/-n
info.NodeM.AddEffect(&Effect5{}) //注册5
info.InitEffect(4, NewEffectStat(true))
}
func NewEffectStat(b bool) info.Effect {
return &EffectStat{
node.EffectNode{
ArgSize: 3,
},
b,
}
}
type EffectStat struct {
@@ -18,34 +29,21 @@ type EffectStat struct {
etype bool
}
func (this *EffectStat) GetPet() {
ff := this.EffectNode.GetOwnerPet()
offsetC := unsafe.Offsetof(ff.Attack) // c字段的偏移量通常为4+16=20
// 2. 将结构体指针转换为原始内存地址uintptr
baseAddr := uintptr(unsafe.Pointer(&offsetC))
// func (this *EffectStat) GetPet() {
// ff := this.EffectNode.GetOwnerPet()
// offsetC := unsafe.Offsetof(ff.Atk) // c字段的偏移量通常为4+16=20
// // 2. 将结构体指针转换为原始内存地址uintptr
// baseAddr := uintptr(unsafe.Pointer(&offsetC))
// 3. 计算字段地址并赋值
// 给a字段赋值通过偏移量
addrA := unsafe.Pointer(baseAddr + 4) //根据攻击算其他字段
*(*uint32)(addrA) = 100
}
// // 3. 计算字段地址并赋值
// // 给a字段赋值通过偏移量
// addrA := unsafe.Pointer(baseAddr + 4) //根据攻击算其他字段
// *(*uint32)(addrA) = 100
// }
func (this *EffectStat) SkillUseEnd() {
if !this.etype { //自身
func (this *EffectStat) TYPE() bool {
return this.etype
}
} else { //对方
type Effect4 struct {
EffectStat
}
func (this *Effect4) TYPE() bool {
return true //提升
}
type Effect5 struct {
EffectStat
}
func (this *Effect5) TYPE() bool {
return false //下降
}
}

View File

@@ -13,7 +13,7 @@ func (this *EffectNode) OnSwitchOut() bool {
func (this *EffectNode) OnOwnerSwitchIn() bool {
//自身下场清除掉自身的回合效果
this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this)
//this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this)
return true
}

View File

@@ -14,12 +14,11 @@ func (this *EffectNode) OnTurnStart() bool {
// 回合结束一次性effect清楚掉
func (this *EffectNode) TurnEnd() bool {
if this.duration != 0 { // 保留 (负数表示永久)
this.GetBattle().Effects[this.GetInput().UserID].AddEffect(this) //重新添加buff到上下文
//this.GetBattle().Effects[this.GetInput().UserID].AddEffect(this) //重新添加buff到上下文
}
this.duration--
this.duration--
return true
}

View File

@@ -1,7 +1,6 @@
package node
import (
"blazing/logic/service/fight/info"
"context"
)
@@ -14,12 +13,12 @@ type EffectNode struct {
duration int // 默认为-1 持续回合/次0 = 即时生效,>0 = 回合数 ,负数是永久) 次数相当于重写回合
stacks int // 当前层数
paramSize int
ArgSize int
maxStack int // 最大叠加层数 ,正常都是不允许叠加的,除了衰弱特殊效果 ,异常和能力的叠层
SideEffectArgs []int // 附加效果参数
Success bool // 是否执行成功 成功XXX失败XXX
target int // 传出作用对象,默认0是自身,1是作用于对面
target bool // 传出作用对象,默认0是自身,1是作用于对面
Flag int //过滤掉的战斗类型 pvp pve boss战斗,野怪全部生效
//增加owner target如果owner target都为自身就回合效果结束后再使用回合效果
}
@@ -31,12 +30,12 @@ func (this *EffectNode) ID() int {
}
// 传出作用对象,默认0是自身,1是作用于对面
func (this *EffectNode) Target() int {
func (this *EffectNode) Target() bool {
return this.target
}
func (this *EffectNode) SetTarget(t int) {
func (this *EffectNode) SetTarget(t bool) {
this.target = t
@@ -64,45 +63,8 @@ func (this *EffectNode) SetArgs(args []int) {
this.SideEffectArgs = args
}
func (this *EffectNode) ParamSize(t int) int {
if t != 0 {
this.paramSize = t
}
func (this *EffectNode) GetArgSize() int {
return this.paramSize
}
func (this *EffectNode) GetSkill() *info.BattleSkillEntity {
pet, ok := this.Ctx.Value(info.BattleSkillEntityCtx).(*info.BattleSkillEntity)
if !ok { //effect不一定来自技能,也有特性 能量珠 boss effect
return nil
}
return pet
}
// 获取对方精灵
// // 获取我方输入源
// func (this *EffectNode) GetInput() *info.BattleInputSourceEntity {
// pet, _ := this.Ctx.Value(info.Input_ctx).(*info.BattleInputSourceEntity)
// return pet
// }
// 获取自身精灵
func (this *EffectNode) GetOwnerPet() *info.BattlePetEntity {
pet, _ := this.Ctx.Value(info.Pet_O_Ctx).(*info.BattlePetEntity)
return pet
}
func (this *EffectNode) GetTargetPet() *info.BattlePetEntity {
pet, _ := this.Ctx.Value(info.Pet_T_Ctx).(*info.BattlePetEntity)
return pet
return this.ArgSize
}

View File

@@ -2,6 +2,7 @@ package fight
import (
"blazing/logic/service"
_ "blazing/logic/service/fight/battle/effect"
)
// 野怪对战包

View File

@@ -21,13 +21,18 @@ var PlayerOperations = enum.New[struct {
}]()
// Compare 比较两个1v1战斗动作的执行优先级核心逻辑
func Compare(a, b BattleActionI) BattleActionI {
func Compare(a, b BattleActionI) (BattleActionI, BattleActionI) {
// 动作本身的优先级比较
p1 := b.Priority() - a.Priority()
if p1 > 0 { // 对手优先级更高
return b
return b, a
} else if p1 < 0 {
return a
return a, b
}
_, ok := b.(*SystemGiveUpAction)
if ok {
return a, b
}
bskill := b.(*SelectSkillAction)
@@ -36,20 +41,20 @@ func Compare(a, b BattleActionI) BattleActionI {
p2 := bskill.Skill.Priority - askill.Skill.Priority
if p2 > 0 {
return b
return b, a
} else if p2 < 0 {
return a
return a, b
}
// 比较宠物相关属性(假设Value(4)返回速度相关值)
p2 = int(bskill.PetInfo.Value(4)) - int(askill.PetInfo.Value(4))
if p2 > 0 {
return b
return b, a
} else if p2 < 0 {
return a
return a, b
}
return a // 速度相同时,发起方优先
return a, b // 速度相同时,发起方优先
}
// BattleActionI 战斗动作接口

View File

@@ -20,7 +20,7 @@ const Pet_T_Ctx = "PET_T"
// * battle_attr: 0:hp, 1:atk, 2:def, 3:sp_atk, 4:sp_def, 5:spd
func (a *BattlePetEntity) Value(tt uint32) uint32 {
offsetAtk := unsafe.Offsetof(a.info.Attack) // c字段的偏移量通常为4+16=20
offsetAtk := unsafe.Offsetof(a.Info.Attack) // c字段的偏移量通常为4+16=20
// 2. 将结构体指针转换为原始内存地址uintptr
baseAddr := uintptr(unsafe.Pointer(&offsetAtk))
@@ -35,21 +35,24 @@ func (a *BattlePetEntity) Value(tt uint32) uint32 {
type BattlePetEntity struct {
xmlres.PetInfo
info model.PetInfo //通过偏移赋值
Info *model.PetInfo //通过偏移赋值
Capturable bool // 是否可捕获
statusConditions sync.Map // key: StatusCondition, value: int (剩余回合)
Skills [4]BattleSkillEntity // 技能槽最多4个技能
Status StatusDict //精灵的状态
statusConditions sync.Map // key: StatusCondition, value: int (剩余回合)
Skills [4]*BattleSkillEntity // 技能槽最多4个技能
Status StatusDict //精灵的状态
//能力提升属性
Prop PropDict
}
// 创建精灵实例
func CreateBattlePetEntity(info ReadyFightPetInfo) *BattlePetEntity {
func CreateBattlePetEntity(info *model.PetInfo) *BattlePetEntity {
ret := &BattlePetEntity{}
ret.PetInfo = xmlres.PetMAP[int(info.ID)] //注入精灵信息
ret.Info = info
for i := 0; i < 4; i++ {
ret.Skills[i] = CreateBattleSkillWithInfinity(info.SkillList[i].ID)
}
return ret

View File

@@ -1,5 +1,9 @@
package info
import (
"reflect"
)
type Effect interface {
OnBattleStart() bool //战斗开始
@@ -13,10 +17,10 @@ type Effect interface {
PreDamage() bool // 技能伤害计算前触发(增伤 / 减伤等)
OnBeforeCalculateDamage() bool // 最终伤害计算前触发
OnDamage() bool // 造成伤害时触发
SetParam(param []int) //设置参数
GetParam() int //获取参数
Shield() bool // 护盾值变化时触发
PostDamage() bool // 伤害结算后触发(血量扣除后)
SetArgs(param []int) //设置参数
Shield() bool // 护盾值变化时触发
PostDamage() bool // 伤害结算后触发(血量扣除后)
OnCritPostDamage() bool // 暴击伤害结算后触发
@@ -56,7 +60,7 @@ type Effect interface {
Duration(int) int
ID() int
GetArgSize() int
Stack(int) int
MaxStack() int
//GetSkill() *BattleSkillEntity //获得技能ctx
@@ -70,7 +74,29 @@ type NodeManager struct {
Effects []Effect //effects 实际上全局就是effect无限回合
}
var NodeM = &NodeManager{}
var NodeM = make(map[int]Effect, 0)
func InitEffect(id int, t Effect) {
NodeM[id] = t
}
func getTypeName(v interface{}) string {
// 获取类型信息
t := reflect.TypeOf(v)
// 如果是指针类型,需要先获取其指向的元素类型
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
// 如果是结构体类型,返回其名称
if t.Kind() == reflect.Struct {
return t.Name()
}
// 非结构体类型返回空或对应的类型名
return t.Kind().String()
}
func (c *NodeManager) AddEffect(e Effect) {

View File

@@ -2,20 +2,22 @@ package service
import (
"blazing/common/data/xmlres"
"blazing/common/utils"
"blazing/logic/service/fight/info"
"blazing/modules/blazing/model"
"fmt"
"math/rand"
"time"
"github.com/gogf/gf/v2/util/gconv"
"github.com/jinzhu/copier"
"github.com/mohae/deepcopy"
)
type PlayerI interface {
ID() uint32
MapID() uint32
GetAction()
GetPetInfo() []*model.PetInfo
End() //结束战斗
SendPack(b []byte) error
SendReadyToFightInfo(info.FightStartOutboundInfo)
@@ -54,11 +56,9 @@ func (f *FightC) Escape(c PlayerI) {
}
// 玩家使用技能
func (f *FightC) UseSkill(c PlayerI, id uint32) {
func (f *FightC) UseSkill(c PlayerI, id int32) {
ret := &info.SelectSkillAction{
PlayerID: c.ID(),
Skill: info.CreateBattleSkillWithInfinity(id),
}
if c == f.Our {
@@ -69,6 +69,13 @@ func (f *FightC) UseSkill(c PlayerI, id uint32) {
ret.PetInfo = f.OppCurrentPet
}
for _, v := range ret.PetInfo.Skills {
if v != nil && v.ID == int(id) {
ret.Skill = v
}
}
f.actionChan <- ret
}
@@ -98,14 +105,14 @@ func (f *FightC) ReadyFight(c PlayerI) {
}
} else {
f.Opp = c
f.OppCurrentPet = info.CreateBattlePetEntity(f.Info.OpponentPetList[0])
f.OppCurrentPet = info.CreateBattlePetEntity(c.GetPetInfo()[0])
f.BFinished = true
if f.AFinished {
rrsult()
}
}
case 3: // 野怪战斗
f.OppCurrentPet = info.CreateBattlePetEntity(f.Info.OpponentPetList[0])
//判断捕捉率大于0
if gconv.Int(xmlres.PetMAP[int(f.Info.OpponentPetList[0].ID)].CatchRate) > 0 {
rett.Info2.Catchable = 1
@@ -124,13 +131,17 @@ func (f *FightC) Random() {
}
// 创建新战斗
func (f *FightC) NewFight(i *info.NoteReadyToFightInfo, plays PlayerI) {
func (f *FightC) NewFight(i *info.NoteReadyToFightInfo, plays PlayerI, mo *model.PetInfo) {
f.Our = plays
f.Info = i
f.StartTime = time.Now()
f.actionChan = make(chan info.BattleActionI, 2) // 初始化全局操作通道
f.OurCurrentPet = info.CreateBattlePetEntity(f.Info.OurPetList[0])
f.OurCurrentPet = info.CreateBattlePetEntity(plays.GetPetInfo()[0])
if mo != nil {
f.OppCurrentPet = info.CreateBattlePetEntity(mo)
}
switch i.FightId {
case 1:
// 1v1等双方进入
@@ -205,10 +216,8 @@ func (f *FightC) battleLoop() {
p1Action := actions[f.Our.ID()]
p2Action := actions[f.Opp.ID()]
fmt.Println("开始结算回合")
fmt.Println(p1Action)
fmt.Println(p2Action)
firstaction := info.Compare(p1Action, p2Action)
firstaction, secaction := info.Compare(p1Action, p2Action)
if firstaction.Priority() > 0 { //如果优先级大于0,说明是在技能之上的
t := f.Broadcast(firstaction)
@@ -217,24 +226,31 @@ func (f *FightC) battleLoop() {
break
}
}
if firstaction.Priority() < 0 && secaction.Priority() < 0 { //双方都空过
break
}
// TODO: 在这里调用技能结算逻辑
firstskill, _ := firstaction.(*info.SelectSkillAction)
temparg := firstskill.Skill.SideEffectArgS
for _, v := range firstskill.Skill.SideEffectS {
t, ok := effectmap[v]
t, ok := info.NodeM[v]
if ok {
if ok { //获取成功
args := t.GetArgSize()
t.SetArgs(temparg[:args]) //设置入参
args := t.GetParam()
t.SetParam(temparg[:args]) //设置入参
//如果不是是房主方,说明施加的对象是反的,比如本来是false,实际上是给邀请方施加的
//所以这里要对target取反
if firstaction.GetPlayerID() != f.OwnerID {
t.SetTarget(!t.Target())
}
temparg = temparg[args:]
f.EffectS.AddEffect(deepcopy.Copy(t).(info.Effect))
}
@@ -246,8 +262,3 @@ func (f *FightC) battleLoop() {
f.Our.End()
f.Opp.End()
}
var effectmap = utils.ToMap[info.Effect, int](info.NodeM.Effects,
func(t info.Effect) int {
return t.ID()
})

View File

@@ -168,6 +168,16 @@ func (p *Player) SendFightEndInfo(b info.FightOverInfo) {
p.SendPack(t1.Pack(&b))
}
func (p *Player) GetPetInfo() []*model.PetInfo {
var ret = make([]*model.PetInfo, 0)
for _, v := range p.Info.PetList {
ret = append(ret, &v)
}
return ret
}
// Save 保存玩家数据
func (p *Player) Save() {