refactor(fight): 重构战斗系统

- 重构了 BattleSkillEntity 结构,改名为 SkillEntity
- 优化了 Input 结构,移除了冗余的 Effect 容器
- 调整了 Effect 接口,增加了 SetInput 和 Alive 方法
- 重构了战斗逻辑中的技能使用和效果处理流程
- 优化了代码结构,提高了可读性和可维护性
This commit is contained in:
2025-09-15 00:40:19 +08:00
parent 906bad9e21
commit d9f09aa96a
10 changed files with 108 additions and 94 deletions

View File

@@ -71,9 +71,9 @@ type BattleActionI interface {
// SelectSkillAction 选择技能的战斗动作
type SelectSkillAction struct {
PlayerID uint32 // 玩家ID
Skill *info.BattleSkillEntity // 使用的技能
PetInfo *info.BattlePetEntity // 使用技能的宠物
PlayerID uint32 // 玩家ID
Skill *info.SkillEntity // 使用的技能
PetInfo *info.BattlePetEntity // 使用技能的宠物
Attack info.AttackValue
}

View File

@@ -13,32 +13,33 @@ type Effect0 struct {
}
// 技能命中计算
func (this *Effect0) IsHit(attacker, defender *input.Input, skill *info.BattleSkillEntity) {
func (this *Effect0) IsHit(opp *input.Input, skill *info.SkillEntity) {
skill.AttackTimeC() //计算命中
}
func (this *Effect0) UseSkill(attacker, defender *input.Input) bool {
// 比如xx技能无效
func (this *Effect0) UseSkill(opp *input.Input, skill *info.SkillEntity) bool {
return true
}
func (this *Effect0) IsCrit(attacker, defender *input.Input, skill *info.BattleSkillEntity) {
func (this *Effect0) IsCrit(opp *input.Input, skill *info.SkillEntity) {
skill.Crit = 0
CritRate := utils.Max(skill.CritRate, 1)
CritRateR := attacker.FightC.GetRand().Int31n(16)
CritRateR := this.Input.FightC.GetRand().Int31n(16)
//CritAtkFirst: 先出手时必定致命一击; 默认: 0
if skill.CritAtkFirst != 0 && attacker.First {
if skill.CritAtkFirst != 0 && this.Input.First {
CritRate = 16
}
//CritAtkSecond: 后出手时必定致命一击; 默认: 0
if skill.CritAtkSecond != 0 && !attacker.First {
if skill.CritAtkSecond != 0 && !this.Input.First {
CritRate = 16
}
// CritSelfHalfHp: 自身体力低于一半时必定致命一击; 默认: 0
if skill.CritSelfHalfHp != 0 && (attacker.CurrentPet.HP < int(attacker.CurrentPet.Info.MaxHp)/2) {
if skill.CritSelfHalfHp != 0 && (this.Input.CurrentPet.HP < int(this.Input.CurrentPet.Info.MaxHp)/2) {
CritRate = 16
}
// CritFoeHalfHp: 对方体力低于一半时必定致命一击; 默认: 0
if skill.CritSelfHalfHp != 0 && (defender.CurrentPet.HP < int(defender.CurrentPet.Info.MaxHp)/2) {
if skill.CritSelfHalfHp != 0 && (opp.CurrentPet.HP < int(opp.CurrentPet.Info.MaxHp)/2) {
CritRate = 16
}

View File

@@ -376,9 +376,9 @@ func (f *FightC) parseskill(attacker, defender *input.Input, id *SelectSkillActi
temparg = temparg[args:]
if t.GetOwner() { //如果取反,说明是给对方添加的回合效果
//实际上,owner永远为反,说明是对方给我添加的
defender.Effect.AddEffect(eff)
defender.AddEffect(eff)
} else {
attacker.Effect.AddEffect(eff)
attacker.AddEffect(eff)
}
}
@@ -409,31 +409,31 @@ func (f *FightC) initAttackers(fattack, sattack BattleActionI) {
}
// 处理技能攻击逻辑
func (f *FightC) processSkillAttack(attacker, defender *input.Input, skill *SelectSkillAction) {
func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *SelectSkillAction) {
// 记录技能信息
attacker.AttackValue.SkillID = uint32(skill.Skill.ID) //获取技能ID
attacker.AttackValue.SkillID = uint32(a.Skill.ID) //获取技能ID
attacker.Effect.Exec(func(t input.Effect) bool { //计算命中
attacker.Exec(func(t input.Effect) bool { //计算命中
t.IsHit(attacker, defender, skill.Skill) //相当于先调整基础命中
t.IsHit(defender, a.Skill) //相当于先调整基础命中
return attacker.AttackTime == 0 //等于0,继续处理
})
defender.Effect.Exec(func(t input.Effect) bool { //计算闪避
t.TakeHit(attacker, defender, skill.Skill)
defender.Exec(func(t input.Effect) bool { //计算闪避
t.TakeHit(attacker, a.Skill)
return attacker.AttackTime > 0 //
})
attacker.AttackValue.AttackTime = skill.Skill.AttackTime
attacker.AttackValue.AttackTime = a.Skill.AttackTime
if attacker.AttackValue.AttackTime > 0 { //如果命中
spower := skill.Skill.CalculatePower(defender.CurrentPet)
f.parseskill(attacker, defender, a) //命中后解析effect
spower := a.Skill.CalculatePower(defender.CurrentPet)
attacker.Damage = spower
attacker.Effect.Exec(func(t input.Effect) bool { //计算暴击率加成
attacker.Exec(func(t input.Effect) bool { //计算暴击率加成
t.IsCrit(attacker, defender, skill.Skill)
attacker.AttackValue.IsCritical = skill.Skill.Crit
t.IsCrit(defender, a.Skill)
attacker.AttackValue.IsCritical = a.Skill.Crit
return attacker.AttackValue.IsCritical == 0
})
if attacker.AttackValue.IsCritical == 1 {
@@ -482,20 +482,20 @@ func (f *FightC) enterturn(fattack, sattack BattleActionI) {
//**回合开始前enterturn
attacker.Effect.Exec(func(t input.Effect) bool { //计算命中
attacker.Exec(func(t input.Effect) bool { //计算命中
//结算状态
t.OnTurnStart(attacker, defender)
t.OnTurnStart(defender)
return true
})
canuseskill := attacker.Effect.Exec(func(t input.Effect) bool { //计算命中
canuseskill := attacker.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
return t.UseSkill(attacker, defender) //返回本身结算,如果false,说明不能使用技能了
return t.UseSkill(defender) //返回本身结算,如果false,说明不能使用技能了
})
if canuseskill { //可以使用技能
f.parseskill(attacker, defender, skill) //解析effect
f.processSkillAttack(attacker, defender, skill)
skill.Skill.Info.PP-- //减少PP

View File

@@ -109,9 +109,9 @@ type BattlePetEntity struct {
xmlres.PetInfo
Info *model.PetInfo //通过偏移赋值
statusConditions sync.Map // key: StatusCondition, value: int (剩余回合)
Skills [4]*BattleSkillEntity // 技能槽最多4个技能
Status StatusDict //精灵的状态
statusConditions sync.Map // key: StatusCondition, value: int (剩余回合)
Skills [4]*SkillEntity // 技能槽最多4个技能
Status StatusDict //精灵的状态
//能力提升属性
Prop PropDict
NotAlive bool `struc:"skip"`
@@ -188,7 +188,7 @@ func CreateBattlePetEntity(info *model.PetInfo, rand *rand.Rand) *BattlePetEntit
ret.Info = info
for i := 0; i < 4; i++ {
//todo 技能信息应该每回合进行深拷贝,保证每次的技能效果都是不一样的
ret.Skills[i] = CreateBattleSkillWithInfinity(&info.SkillList[i], rand, ret)
ret.Skills[i] = CreateSkill(&info.SkillList[i], rand, ret)
}
ret.DamageZone = make(map[EnumCategory]map[EnumsZoneType]map[EnumsZoneType][]float64) //初始化第一层类型

View File

@@ -32,22 +32,23 @@ var Category = enum.New[struct {
ALL EnumCategory //任何类型
}]()
// BattleSkillEntity 战斗技能实体
// SkillEntity 战斗技能实体
// 实现了战斗中技能的所有属性和行为包括PP管理、技能使用、属性获取等
// 战斗中可以修改技能实体值,比如是否暴击,是否必中等
type BattleSkillEntity struct {
type SkillEntity struct {
xmlres.Move
Info *model.SkillInfo
DamageValue decimal.Decimal // 伤害值
Rand *rand.Rand
Pet *BattlePetEntity
Crit uint32
AttackTime uint32
//MaxValue func(ahp, bhp uint32) decimal.Decimal
Crit uint32
AttackTime uint32
}
// CreateBattleSkillWithInfinity 创建战斗技能实例可指定是否无限PP
func CreateBattleSkillWithInfinity(skill *model.SkillInfo, rand *rand.Rand, pet *BattlePetEntity) *BattleSkillEntity {
// CreateSkill 创建战斗技能实例可指定是否无限PP
func CreateSkill(skill *model.SkillInfo, rand *rand.Rand, pet *BattlePetEntity) *SkillEntity {
//如果PP是-1 ,那就是无限PP
// ID小于10001的视为无效技能
@@ -55,7 +56,7 @@ func CreateBattleSkillWithInfinity(skill *model.SkillInfo, rand *rand.Rand, pet
return nil
}
var ret BattleSkillEntity
var ret SkillEntity
ret.Rand = rand
ret.Pet = pet
// 从资源仓库获取技能数据
@@ -96,17 +97,17 @@ func strSliceToIntSlice(strs []string) ([]int, error) {
}
// CanUse 检查技能是否可以使用PP是否充足
func (s *BattleSkillEntity) CanUse() bool {
func (s *SkillEntity) CanUse() bool {
return s.Info.PP > 0
}
// 获取技能类型
func (s *BattleSkillEntity) Category() EnumCategory {
func (s *SkillEntity) Category() EnumCategory {
return EnumCategory(s.Move.Category)
}
// 获取技能属性
func (s *BattleSkillEntity) Type() *element.ElementCombination {
func (s *SkillEntity) Type() *element.ElementCombination {
ret, _ := element.NewElementCombination(s.Move.Type)
return ret
}
@@ -122,7 +123,7 @@ func (s *BattleSkillEntity) Type() *element.ElementCombination {
// 解析副作用参数字符串为整数列表
// 获取技能名称为空时使用ID
func getSkillName(move *BattleSkillEntity) string {
func getSkillName(move *SkillEntity) string {
if move.Name == "" {
return strconv.FormatInt(int64(move.ID), 10)
}
@@ -164,7 +165,7 @@ var DamageC = enum.New[struct {
// }
// 计算是否命中
func (s *BattleSkillEntity) AttackTimeC() {
func (s *SkillEntity) AttackTimeC() {
s.AttackTime = 0 //先重置上一次的
if s.MustHit != 0 {
s.AttackTime = 2
@@ -175,7 +176,7 @@ func (s *BattleSkillEntity) AttackTimeC() {
}
}
func (s *BattleSkillEntity) CriticalsameTypeBonus() decimal.Decimal {
func (s *SkillEntity) CriticalsameTypeBonus() decimal.Decimal {
// 6. 同系加成属性相同则乘以同系加成倍率否则1
sameTypeBonus := decimal.NewFromFloat(1.0)
@@ -192,7 +193,7 @@ func (s *BattleSkillEntity) CriticalsameTypeBonus() decimal.Decimal {
return sameTypeBonus
}
func (s *BattleSkillEntity) criticalrandom() decimal.Decimal {
func (s *SkillEntity) criticalrandom() decimal.Decimal {
randomnum := s.Rand.Int31n(39) + 217
// 10. 随机倍率随机值除以255
@@ -202,7 +203,7 @@ func (s *BattleSkillEntity) criticalrandom() decimal.Decimal {
}
// 计算技能威力
func (s *BattleSkillEntity) CalculatePower(deftype *BattlePetEntity) decimal.Decimal {
func (s *SkillEntity) CalculatePower(deftype *BattlePetEntity) decimal.Decimal {
// 1. 计算等级因子 (level * 0.4 + 2)
levelFactor := decimal.NewFromInt(int64(s.Pet.Info.Level)).

View File

@@ -18,7 +18,7 @@ type Input struct {
*info.AttackValue
FightC common.FightI
// info.BattleActionI
Effect NodeManager //effects容器 技能的
Effects []Effect //effects 实际上全局就是effect无限回合 //effects容器 技能的
// Prop NodeManager //属性容器
// Status NodeManager //状态容器
//NewSeIdx NodeManager //全局容器
@@ -29,8 +29,8 @@ type Input struct {
func NewInput(c common.FightI, p common.PlayerI) *Input {
ret := &Input{FightC: c, Player: p}
t := NodeM[1000000]
ret.Effect.AddEffect(deepcopy.Copy(t).(Effect)) //添加默认基类,实现继承
p.SetFightC(c) //给玩家设置战斗容器
ret.AddEffect(deepcopy.Copy(t).(Effect)) //添加默认基类,实现继承
p.SetFightC(c) //给玩家设置战斗容器
return ret
}

View File

@@ -8,9 +8,9 @@ import (
type Effect interface {
OnBattleStart() bool //战斗开始
OnTurnStart(attacker, defender *Input) bool //回合开始
OnTurnStart(opp *Input) bool //回合开始
UseSkill(attacker, defender *Input) bool //使用技能 可以取消用技能节点
UseSkill(opp *Input) bool //使用技能 可以取消用技能节点
// OnSkillPP() bool //技能PP减少节点
// BeforeMultiHit() bool //多段攻击前
// BeforeHit() bool //命中前
@@ -19,13 +19,15 @@ type Effect interface {
// OnBeforeCalculateDamage() bool // 最终伤害计算前触发
// OnDamage() bool // 造成伤害时触发
//使用技能 可以取消用技能节点
SetInput(input *Input)
SetArgs(param []int)
IsCrit(attacker, defender *Input, skill *info.BattleSkillEntity) //是否暴击
CalculateDamage(attacker, defender *Input, skill *info.BattleSkillEntity) //击判定成功且伤害计算前触发
IsCrit(opp *Input, skill *info.SkillEntity) //是否暴击
CalculateDamage(opp *Input, skill *info.SkillEntity) //击判定成功且伤害计算前触发
OnBeforeCalculateDamage(opp *Input, skill *info.SkillEntity) // 最终伤害计算前触发
// Shield() bool // 护盾值变化时触发
// PostDamage() bool // 伤害结算后触发(血量扣除后)
IsHit(attacker, defender *Input, skill *info.BattleSkillEntity) //闪避率计算,,实际上是修改命中的判断
TakeHit(attacker, defender *Input, skill *info.BattleSkillEntity) //闪避率计算,,实际上是修改命中的判断
IsHit(opp *Input, skill *info.SkillEntity) //闪避率计算,,实际上是修改命中的判断
TakeHit(opp *Input, skill *info.SkillEntity) //闪避率计算,,实际上是修改命中的判断
//() bool // 暴击伤害结算后触发
// OnHit() bool // 技能命中时触发
@@ -65,20 +67,13 @@ type Effect interface {
Duration(int) int
ID() int
GetArgSize() int
Alive() bool
Stack(int) int
MaxStack() int
GetOwner() bool // 技能属主,比如寄生和镇魂歌,属主是对方)
//GetSkill() *BattleSkillEntity //获得技能ctx
}
// ========================
// 容器:存放多个效果
// ========================
type NodeManager struct {
//GlobalEffects []*Effect // 全局常驻/回合/次数效果
Effects []Effect //effects 实际上全局就是effect无限回合
}
var NodeM = make(map[int]Effect, 0)
func InitSkillEffect(id int, t Effect) {
@@ -103,7 +98,7 @@ func getTypeName(v interface{}) string {
return t.Kind().String()
}
func (c *NodeManager) AddEffect(e Effect) {
func (c *Input) AddEffect(e Effect) {
// 如果已有同 ID 的效果,尝试叠加
for _, eff := range c.Effects {
@@ -116,6 +111,7 @@ func (c *NodeManager) AddEffect(e Effect) {
eff.Duration(eff.Duration(0))
}
eff.SetInput(c) //设置输入源
return
}
}
@@ -124,7 +120,7 @@ func (c *NodeManager) AddEffect(e Effect) {
}
// 删除
func (c *NodeManager) RemoveEffect(e Effect) {
func (c *Input) RemoveEffect(e Effect) {
var remain []Effect
for _, eff := range c.Effects {
if eff.ID() != e.ID() {
@@ -137,28 +133,29 @@ func (c *NodeManager) RemoveEffect(e Effect) {
// ForEachEffectBool 遍历所有 Effect执行“无参数、返回 bool”的方法
// 参数 fn接收单个 Effect返回 bool如 func(e Effect) bool { return e.OnBattleStart() }
// 返回值:所有 Effect 的方法返回值列表
func (c *NodeManager) Exec(fn func(Effect) bool) bool {
func (c *Input) Exec(fn func(Effect) bool) bool {
var results bool
// if len(c.Effects) == 0 {
// return true
// }
for _, effect := range c.Effects {
result := fn(effect)
if !result {
results = result //如果是false,说明存在阻止向下执行的effect比如免疫能力提升效果
if effect.Alive() {
result := fn(effect)
if !result {
results = result //如果是false,说明存在阻止向下执行的effect比如免疫能力提升效果
}
if result {
results = true
}
}
if result {
results = true
}
}
return results
}
// 消除回合类效果 efftype 输入是消对方的还是自己的,false是自己,true是对方
func (c *NodeManager) CancelTurn(efftype bool) {
func (c *Input) CancelTurn(efftype bool) {
var remain []Effect
for _, eff := range c.Effects {

View File

@@ -9,7 +9,10 @@ func (this *EffectNode) PreTurnStart() bool {
}
// 回合开始
func (this *EffectNode) OnTurnStart(attacker, defender *input.Input) bool {
func (this *EffectNode) OnTurnStart(opp *input.Input) bool {
//处理异常状态
return true
}
@@ -17,9 +20,8 @@ func (this *EffectNode) OnTurnStart(attacker, defender *input.Input) bool {
func (this *EffectNode) TurnEnd() bool {
if this.duration != 0 { // 保留 (负数表示永久)
//this.GetBattle().Effects[this.GetInput().UserID].AddEffect(this) //重新添加buff到上下文
if this.duration == 0 { // 保留 (负数表示永久)
this.NotAlive = true
}
this.duration--
return true

View File

@@ -6,17 +6,18 @@ import (
)
// 技能命中计算
func (this *EffectNode) IsHit(attacker, defender *input.Input, skill *info.BattleSkillEntity) {
func (this *EffectNode) IsHit(opp *input.Input, skill *info.SkillEntity) {
}
// 被命中计算,默认直接返回,重写这个来实现闪避率
func (this *EffectNode) TakeHit(attacker, defender *input.Input, skill *info.BattleSkillEntity) {
func (this *EffectNode) TakeHit(opp *input.Input, skill *info.SkillEntity) {
}
func (this *EffectNode) UseSkill(attacker, defender *input.Input) bool {
return true
func (this *EffectNode) UseSkill(opp *input.Input) bool {
return this.Input.CurrentPet.HP != 0
}
func (this *EffectNode) OnSkillPP() bool {
panic("not implemented") // TODO: Implement
@@ -33,7 +34,7 @@ func (this *EffectNode) BeforeHit() bool {
panic("not implemented") // TODO: Implement
}
func (this *EffectNode) OnCritPreDamage() bool {
func (this *EffectNode) OnBeforeCalculateDamage(opp *input.Input, skill *info.SkillEntity) {
panic("not implemented") // TODO: Implement
}
@@ -41,7 +42,7 @@ func (this *EffectNode) PreDamage() bool {
panic("not implemented") // TODO: Implement
}
func (this *EffectNode) CalculateDamage(attacker, defender *input.Input, skill *info.BattleSkillEntity) {
func (this *EffectNode) CalculateDamage(opp *input.Input, skill *info.SkillEntity) {
panic("not implemented") // TODO: Implement
}
@@ -58,7 +59,7 @@ func (this *EffectNode) PostDamage() bool {
}
// 正常来说,什么都不做
func (this *EffectNode) IsCrit(attacker, defender *input.Input, skill *info.BattleSkillEntity) {
func (this *EffectNode) IsCrit(opp *input.Input, skill *info.SkillEntity) {
//return skill.Crit
}

View File

@@ -2,7 +2,6 @@ package node
import (
"blazing/logic/service/fight/input"
"context"
)
// 检查,激活,延后
@@ -10,9 +9,11 @@ import (
type EffectNode struct {
//Turn int // 当前回合数 ,回合数其实从战斗的上下文中获取
//本质上Ctx还要传入战斗双方数据来判断是否是本精灵切换
Ctx context.Context //节点上下文
duration int // 默认为-1 持续回合/次0 = 即时生效,>0 = 回合数 ,负数是永久) 次数相当于重写回合
//Ctx context.Context //节点上下文
//次数相当于重写回合
duration int // 默认为-1 持续回合/次0 = 即时生效,>0 = 回合数 ,负数是永久) \
Input *input.Input
stacks int // 当前层数
ArgSize int
maxStack int // 最大叠加层数 ,正常都是不允许叠加的,除了衰弱特殊效果 ,异常和能力的叠层
@@ -21,10 +22,15 @@ type EffectNode struct {
Success bool // 是否执行成功 成功XXX失败XXX
arget bool // 传出作用对象,默认0是自身,1是作用于对面
Flag int //过滤掉的战斗类型 pvp pve boss战斗,野怪全部生效
Alive bool // 是否失效 effect返回值是否被取消是否被删除
NotAlive bool // 是否失效 effect返回值是否被取消是否被删除
//增加owner target如果owner target都为自身就回合效果结束后再使用回合效果
}
func (this *EffectNode) Alive() bool {
return !this.NotAlive
}
func (this *EffectNode) ID() int {
return 0
@@ -58,10 +64,16 @@ func (this *EffectNode) Duration(t int) int {
return this.duration
}
// 设置参数,加上设置输入源
func (this *EffectNode) SetArgs(args []int) {
this.SideEffectArgs = args
}
func (this *EffectNode) SetInput(args *input.Input) {
this.Input = args
}
func (this *EffectNode) GetArgSize() int {