feat(fight): 调整技能与状态效果逻辑,优化战斗流程

- 注释掉状态 ID 13 的注册,暂不启用 DrainedHP 状态
- 为 Effect10 增加输入参数设置,标记目标单位
- 重构 Effect62 实现逻辑,新增子效果 Effect62_1 支持回合触发秒杀机制
- 引入 decimal 包以支持精确伤害计算
- 修改命中判断流程,修复部分技能命中异常问题
- 增加睡眠状态对空技能的防御处理
- 优化战斗死亡判定逻辑,支持同归于尽判定及战斗结束控制
This commit is contained in:
2025-11-08 23:20:48 +08:00
parent 4f421842f4
commit 8b48ce1c06
12 changed files with 333 additions and 68 deletions

View File

@@ -29,10 +29,10 @@ func init() {
func registerStatusEffects() {
statusList := map[int]info.EnumBattleStatus{
10: info.PetStatus.Paralysis,
11: info.PetStatus.Poisoned,
12: info.PetStatus.Burned,
13: info.PetStatus.DrainedHP,
10: info.PetStatus.Paralysis,
11: info.PetStatus.Poisoned,
12: info.PetStatus.Burned,
//13: info.PetStatus.DrainedHP,
14: info.PetStatus.Frozen,
15: info.PetStatus.Fear,
16: info.PetStatus.Sleep,
@@ -81,6 +81,7 @@ func (e *Effect10) OnSkill(ctx input.Ctx) bool {
}
eff.Duration(duration)
eff.SetArgs(ctx.Input) //输入参数是对方
ctx.AddEffect(eff)
return true
}

View File

@@ -0,0 +1,42 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
)
// -----------------------------------------------------------
// 通用状态效果(例如 麻痹 / 中毒 / 疲惫 / 混乱 等)
// -----------------------------------------------------------
type Effect13 struct {
node.EffectNode
Status info.EnumBattleStatus // 要施加的状态类型
}
func init() {
input.InitEffect(input.EffectType.Skill, 13, &Effect13{})
}
// -----------------------------------------------------------
// 技能触发时调用
// -----------------------------------------------------------
func (e *Effect13) OnSkill(ctx input.Ctx) bool {
if !e.Hit() {
return true
}
duration := e.EffectNode.SideEffectArgs[0]
//duration++
// 获取状态效果
eff := input.Geteffect(input.EffectType.Status, int(info.PetStatus.DrainedHP))
if eff == nil {
return true
}
eff.Duration(duration)
eff.SetArgs(ctx.Input)
ctx.AddEffect(eff)
return true
}

View File

@@ -0,0 +1,71 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"sync"
"github.com/shopspring/decimal"
)
/**
* m~n回合每回合反弹对手1/k的伤害
*/
func init() {
input.InitEffect(input.EffectType.Skill, 21, &Effect21{})
}
type Effect21 struct {
node.EffectNode
l sync.Once
}
// 使用技能时不可被继承继承Miss和Hit就行
func (e *Effect21) OnSkill(input.Ctx) bool {
if !e.Hit() {
return true
}
e.l.Do(func() { //保证技能使用后初始化一次就行
statIndex := e.SideEffectArgs[0]
endindex := e.SideEffectArgs[1]
//回合产生
n := int(e.Input.FightC.GetRand().Int31n(int32(endindex-statIndex+1))) + statIndex
e.Duration(n) //产生回合收益
//e.Input.AddEffect(input.Geteffect(input.EffectType.Status, int(info.PetStatus.Tired)))
})
return true
}
// 被攻击时候反弹
func (e *Effect21) Skill_Use(ctx input.Ctx) bool {
//未命中
if !e.Hit() {
return true
}
//不是技能
if ctx.SkillEntity == nil {
return true
}
//0血不触发
if e.Input.CurrentPet.Info.Hp <= 0 {
return true
}
eff := input.Ctx{
Input: e.Input,
SelectSkillAction: nil,
DamageZone: &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: decimal.NewFromInt(int64(ctx.DamageZone.Damage.IntPart())).Div(decimal.NewFromInt(int64(e.SideEffectArgs[0]))),
},
}
ctx.Input.Damage(eff)
return true
}

View File

@@ -0,0 +1,35 @@
package effect
import (
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
)
/**
* n%降低对手所有技能m点PP值然后若对手所选择的技能PP值为0则当回合对手无法行动
*/
func init() {
input.InitEffect(input.EffectType.Skill, 39, &Effect39{
EffectNode: node.EffectNode{},
})
}
type Effect39 struct {
node.EffectNode
}
func (e *Effect39) OnSkill(ctx input.Ctx) bool {
if !e.Hit() {
return true
}
// 概率判定
ok, _, _ := e.Input.Player.Roll(e.SideEffectArgs[0], 100)
if !ok {
return true
}
ctx.DelPP(e.SideEffectArgs[1])
return true
}

View File

@@ -1,8 +1,11 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/shopspring/decimal"
)
/**
@@ -11,9 +14,37 @@ import (
type Effect62 struct {
node.EffectNode
Hide bool // 是否隐藏 正常是命中就可用,镇魂歌是回合数到才可用
opp *input.Input
e *Effect62_1
}
type Effect62_1 struct {
node.EffectNode
bindpet *info.BattlePetEntity
opp *input.Input
// Hide bool // 是否隐藏 正常是命中就可用,镇魂歌是回合数到才可用
}
func (e *Effect62_1) OnSkill(ctx input.Ctx) bool {
defer e.Alive(false)
if e.bindpet == e.opp.CurrentPet { //说明对方没有切换精灵
//直接扣除所有血量OnSkill
eff := input.Ctx{
Input: e.Input,
SelectSkillAction: nil,
DamageZone: &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: decimal.NewFromInt(int64(e.Input.CurrentPet.Info.MaxHp)),
},
}
e.opp.Damage(eff)
}
return true
}
func init() {
t := &Effect62{
EffectNode: node.EffectNode{},
@@ -22,19 +53,20 @@ func init() {
input.InitEffect(input.EffectType.Skill, 62, t)
}
func (e *Effect62) OnSkill(ctx input.Ctx) bool {
if !e.Hit() {
return true
func (e *Effect62) Turn_Start(ctx input.Ctx) {
if ctx.Player != e.Input.Player {
return
}
if e.Duration() != 1 { //说明还没到生效节点
e.Hide = true //隐藏效果
} else {
e.Hide = false
}
if !e.Hide { //说明是自身回合//如果还在隐藏,就直接返回
//t.Duration(e.SideEffectArgs[0])
e.opp.AddEffect(e.e)
//defer e.EffectNode.NotALive() //失效
//应该是对方固定伤害等于自身血量
//e.Input.Death() //本只死亡
@@ -42,24 +74,38 @@ func (e *Effect62) OnSkill(ctx input.Ctx) bool {
//否则触发秒杀 在对面使用技能后
//return true
}
return false
}
func (e *Effect62) OnSkill(ctx input.Ctx) bool {
if !e.Hit() {
e.Alive(false)
return true
}
e.opp = ctx.Input
e.e = &Effect62_1{
EffectNode: node.EffectNode{},
bindpet: ctx.CurrentPet,
opp: ctx.Input,
}
//给对方添加我方施加的buff
e.e.SetArgs(e.Input, e.SideEffectArgs...)
return true
}
// 默认添加回合
func (e *Effect62) SetArgs(t *input.Input, a ...int) {
e.EffectNode.SetArgs(t, a...)
e.EffectNode.Duration(e.EffectNode.SideEffectArgs[0])
e.EffectNode.Duration(e.EffectNode.SideEffectArgs[0] + 1)
}
// 因为对方切精灵,这个效果也要无效掉
func (this *Effect62) OnSwitchIn(input.Ctx) bool {
if this.Hide { //如果还在隐藏,就直接返回
return true
}
//this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this)
//否则触发秒杀 在对面使用技能后
return true
// // 因为对方切精灵,这个效果也要无效掉
// func (this *Effect62) OnSwitchIn(input.Ctx) bool {
// if this.Hide { //如果还在隐藏,就直接返回
// return true
// }
// //this.GetBattle().Effects[this.GetInput().UserID].RemoveEffect(this)
// //否则触发秒杀 在对面使用技能后
// return true
}
// }

View File

@@ -31,7 +31,7 @@ type Effect96 struct {
StatusID int
}
func (e *Effect96) Skill_Hit(opp *input.Ctx) bool {
func (e *Effect96) Skill_Hit(opp input.Ctx) bool {
if f := statusFuncRegistry.Get(e.StatusID); f != nil && f(e.Input, opp.Input) {
opp.Power *= 2
}
@@ -70,6 +70,9 @@ func init() {
registerStatusFunc(102, func(i, o *input.Input) bool {
return i.StatEffect_Exist(int(info.PetStatus.Paralysis))
})
registerStatusFunc(132, func(i, o *input.Input) bool {
return i.CurrentPet.Info.Hp < o.CurrentPet.Info.MaxHp
})
registerStatusFunc(168, func(i, o *input.Input) bool {
return i.StatEffect_Exist(int(info.PetStatus.Sleep))
})

View File

@@ -30,6 +30,9 @@ type StatusSleep struct { //睡眠不能出手 ,这个挂载到对面来实现
}
func (e *StatusSleep) Skill_Use(ctx input.Ctx) bool {
if ctx.SkillEntity == nil {
return true
}
if ctx.SkillEntity.Category() != info.Category.STATUS {
t := e.Input.GetEffect(input.EffectType.Status, int(info.PetStatus.Sleep))
if t != nil {
@@ -83,13 +86,13 @@ func init() {
f.Status = t
input.InitEffect(input.EffectType.Status, int(t), f)
}
input.InitEffect(input.EffectType.Status, int(info.PetStatus.DrainHP), &DrainedHP{}) //寄生种子
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Poisoned), &DrainHP{}) //中毒
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Frozen), &DrainHP{}) //冻伤
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Burned), &DrainHP{}) //烧伤
tt(info.PetStatus.Paralysis, &StatusNotSkill{}) //麻痹
tt(info.PetStatus.Tired, &StatusNotSkill{}) //疲惫
tt(info.PetStatus.Fear, &StatusNotSkill{}) //害怕
tt(info.PetStatus.Petrified, &StatusNotSkill{}) //石化
input.InitEffect(input.EffectType.Status, 8, &StatusSleep{}) //睡眠
input.InitEffect(input.EffectType.Status, int(info.PetStatus.DrainedHP), &DrainedHP{}) //寄生种子
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Poisoned), &DrainHP{}) //中毒
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Frozen), &DrainHP{}) //冻伤
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Burned), &DrainHP{}) //烧伤
tt(info.PetStatus.Paralysis, &StatusNotSkill{}) //麻痹
tt(info.PetStatus.Tired, &StatusNotSkill{}) //疲惫
tt(info.PetStatus.Fear, &StatusNotSkill{}) //害怕
tt(info.PetStatus.Petrified, &StatusNotSkill{}) //石化
input.InitEffect(input.EffectType.Status, 8, &StatusSleep{}) //睡眠
}

View File

@@ -242,15 +242,6 @@ func (f *FightC) Broadcast(t func(ff *input.Input)) {
// 处理技能攻击逻辑
func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *action.SelectSkillAction) {
attacker.Exec(func(t input.Effect) bool { //计算命中 miss改命中
t.Skill_Hit_Pre(input.Ctx{ //调基础命中
Input: defender,
SelectSkillAction: a,
}) //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
return true
})
a.AttackTimeC(attacker.GetProp(5, true)) //计算命中
defender.Exec(func(t input.Effect) bool { //计算闪避 ,然后修改对方命中),同时相当于计算属性无效这种
t.Skill_Hit_to(input.Ctx{ //计算命中后,我方强制改命中效果
@@ -361,10 +352,6 @@ func copyskill(t *action.SelectSkillAction) *action.SelectSkillAction {
func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
if f.closefight { //战斗结束
return
}
// 伤害值
// 根据攻击方归属设置当前战斗的主/次攻击方属性
@@ -464,18 +451,18 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
}
}
// canuseskillok := attacker.Exec(func(t input.Effect) bool { //这个是能否使用技能
// //结算状态
// //然后这里还可以处理自爆类
// return t.Skill_Hit_Pre(input.Ctx{
// Input: defender,
// SelectSkillAction: currentskill,
// }) //返回本身结算,如果false,说明不能使用技能了
canuseskillok := attacker.Exec(func(t input.Effect) bool { //这个是能否使用技能
//结算状态
//然后这里还可以处理自爆类
return t.Skill_Hit_Pre(input.Ctx{
Input: defender,
SelectSkillAction: currentskill,
}) //返回本身结算,如果false,说明不能使用技能了
// })
//结算状态
//然后这里还可以处理自爆类
if canuseskill { //可以使用技能
})
// 结算状态
// 然后这里还可以处理自爆类
if canuseskill && canuseskillok { //可以使用技能
f.processSkillAttack(attacker, defender, currentskill)
currentskill = oldskill
@@ -491,7 +478,10 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
//技能使用后
defender.Exec(func(t input.Effect) bool {
t.Skill_Use(input.Ctx{Input: attacker, SelectSkillAction: currentskill})
t.Skill_Use(input.Ctx{Input: attacker, SelectSkillAction: currentskill, DamageZone: &info.DamageZone{
Type: info.DamageType.Red,
Damage: attacker.DamageZone.Damage,
}})
return true
})
@@ -512,9 +502,26 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
"自身剩余血量:", attacker.CurrentPet.Info.Hp,
"对手剩余血量:", defender.CurrentPet.Info.Hp,
)
if attacker.CurrentPet.Info.Hp <= 0 {
if defender.CurrentPet.Info.Hp == 0 { //先手方死亡,触发反同归于尽
defender.CurrentPet.Info.Hp = 1
}
if f.IsWin(defender, attacker.CurrentPet.Info.CatchTime) { //然后检查是否战斗结束
f.FightOverInfo.WinnerId = defender.UserID
f.closefight = true
break
}
}
if defender.CurrentPet.Info.Hp == 0 {
// defender.AttackValue.SkillID = 0
//todo 解耦成战斗循环defer
if attacker.CurrentPet.Info.Hp == 0 { //先手方死亡,触发反同归于尽
attacker.CurrentPet.Info.Hp = 1
}
defender.CanChange = true //被打死就可以切精灵了
if f.IsWin(attacker, defender.CurrentPet.Info.CatchTime) { //然后检查是否战斗结束
var WinnerId uint32
@@ -526,7 +533,7 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
f.FightOverInfo.WinnerId = WinnerId
f.closefight = true
break
}
}
@@ -547,11 +554,11 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
})
return true
})
f.First.AttackValue.RemainHp = int32(f.First.CurrentPet.Info.Hp)
f.First.AttackValue.SkillList = f.First.CurrentPet.Info.SkillList
f.First.RemainHp = int32(f.First.CurrentPet.Info.Hp)
f.First.SkillList = f.First.CurrentPet.Info.SkillList
f.Second.AttackValue.SkillList = f.Second.CurrentPet.Info.SkillList
f.Second.AttackValue.RemainHp = int32(f.Second.CurrentPet.Info.Hp)
f.Second.SkillList = f.Second.CurrentPet.Info.SkillList
f.Second.RemainHp = int32(f.Second.CurrentPet.Info.Hp)
ret := info.AttackValueS{
FAttack: *f.First.AttackValue,
SAttack: *f.Second.AttackValue,
@@ -586,3 +593,49 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
})
f.Switch = []*action.ActiveSwitchAction{}
}
// 处理战斗中宠物死亡后的逻辑(同归于尽+胜利判定)
// i=0 表示当前攻击者是先手方i=1 表示当前攻击者是后手方
func (f *FightC) handlePetDeath(attacker, defender *input.Input, i int) {
// 辅助函数根据攻击者顺序获取胜利者ID
getWinnerID := func() uint32 {
if i == 0 {
// 若攻击者是先手方胜利方为后手方defender对应的玩家
return defender.Player.GetInfo().UserID
}
// 若攻击者是后手方胜利方为先手方attacker对应的玩家
return attacker.Player.GetInfo().UserID
}
// 1. 处理攻击方宠物死亡的情况
if attacker.CurrentPet.Info.Hp <= 0 {
// 同归于尽攻击方死亡时若防守方也死亡给防守方留1点血
if defender.CurrentPet.Info.Hp == 0 {
defender.CurrentPet.Info.Hp = 1
}
// 检查是否战斗结束(攻击方死亡后,判定防守方胜利)
if f.IsWin(defender, attacker.CurrentPet.Info.CatchTime) {
f.FightOverInfo.WinnerId = getWinnerID()
f.closefight = true
return // 战斗结束,提前返回
}
}
// 2. 处理防守方宠物死亡的情况(若战斗未结束)
if !f.closefight && defender.CurrentPet.Info.Hp == 0 {
// 同归于尽防守方死亡时若攻击方也死亡给攻击方留1点血
if attacker.CurrentPet.Info.Hp == 0 {
attacker.CurrentPet.Info.Hp = 1
}
// 防守方被击败,允许切换精灵
defender.CanChange = true
// 检查是否战斗结束(防守方死亡后,判定攻击方胜利)
if f.IsWin(attacker, defender.CurrentPet.Info.CatchTime) {
f.FightOverInfo.WinnerId = getWinnerID()
f.closefight = true
}
}
}

View File

@@ -48,7 +48,7 @@ func (u *Input) UseSkill(opp *Input, skill *action.SelectSkillAction) {
func (u *Input) Heal(ac action.BattleActionI, value decimal.Decimal) {
//使用道具回血
if _, ok := ac.(*action.UseItemAction); !ok {
if _, ok := ac.(*action.UseItemAction); !ok && ac != nil {
u.AttackValue.GainHp = int32(value.IntPart()) //道具有专门的回血包
}
@@ -76,6 +76,18 @@ func (u *Input) HealPP(value int) {
}
}
func (u *Input) DelPP(value int) {
for i := 0; i < len(u.CurrentPet.Info.SkillList); i++ {
if uint32(value) > u.CurrentPet.Info.SkillList[i].PP {
u.CurrentPet.Info.SkillList[i].PP = 0
} else {
u.CurrentPet.Info.SkillList[i].PP -= uint32(value)
}
}
}
// 伤害落实 // 血量扣减节点比如触发回神,反弹也在这里实现

View File

@@ -126,7 +126,7 @@ func (c *Input) AddEffect(e Effect) {
//如果效果相同,id相同,参数相同,就是同一个,确认是否可以叠加,正常来说本身就可以共存
//衰弱本身参数也是相同的,区别只是传入的回合数不一样和层数不一样
if v.ID() == e.ID() &&
if e.ID() != 0 && v.ID() == e.ID() &&
v.Alive() &&
equalInts(v.GetArgs(), e.GetArgs()) &&
v.MaxStack() != 0 { //如果层数可以叠加或者是无限层数

View File

@@ -23,9 +23,10 @@ func (f *FightC) battleLoop() {
for {
if f.closefight {
f.Broadcast(func(ff *input.Input) {
//todo 将血量和技能pp传回enterturn
//<-time.After(10000)
ff.Player.SendFightEndInfo(f.FightOverInfo)
})
@@ -37,9 +38,6 @@ func (f *FightC) battleLoop() {
fmt.Printf("—— 第 %d 回合开始 ——\n", f.Round)
actions := f.collectPlayerActions(ourID, oppID)
if f.closefight {
break
}
f.resolveRound(actions[ourID], actions[oppID])
}

View File

@@ -21,6 +21,7 @@ type EffectNode struct {
Flag int //过滤掉的战斗类型 pvp pve boss战斗,野怪全部生效
alive bool // 是否失效 effect返回值是否被取消是否被删除
hit bool
//增加owner target如果owner target都为自身就回合效果结束后再使用回合效果
}