feat: 实现乱舞效果并完善战斗输入上下文
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

This commit is contained in:
xinian
2026-04-04 22:39:56 +08:00
committed by cnb
parent 39e1d4c42f
commit 2eba4b7915
6 changed files with 134 additions and 54 deletions

View File

@@ -0,0 +1,84 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"github.com/alpacahq/alpacadecimal"
)
// 504. 乱舞:群体攻击提升{0}%的攻击伤害并增加一个攻击目标
// 说明:
// 1) 作为 NewSel(魂印/特性)实现,避免影响技能侧的 Effect504 旧逻辑。
// 2) 目前战斗主链仍是单目标红伤,额外目标以“追加一次同等红伤”方式落地。
type NewSel504 struct {
NewSel0
}
func (e *NewSel504) isOwnerActive() bool {
our := e.Ctx().Our
if our == nil || our.CurrentPet() == nil {
return false
}
return e.ID().GetCatchTime() == our.CurrentPet().Info.CatchTime
}
func (e *NewSel504) Damage_Mul(zone *info.DamageZone) bool {
if !e.isOwnerActive() || zone == nil || zone.Type != info.DamageType.Red {
return true
}
if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS {
return true
}
if len(e.Args()) == 0 {
return true
}
percent := e.Args()[0]
if percent.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
zone.Damage = zone.Damage.Mul(alpacadecimal.NewFromInt(100).Add(percent)).Div(alpacadecimal.NewFromInt(100))
return true
}
func (e *NewSel504) Action_end() bool {
if !e.isOwnerActive() {
return true
}
if e.Ctx().SkillEntity == nil || e.Ctx().SkillEntity.Category() == info.Category.STATUS || e.Ctx().SkillEntity.AttackTime == 0 {
return true
}
our := e.Ctx().Our
if our == nil || our.SumDamage.Cmp(alpacadecimal.Zero) <= 0 {
return true
}
currentTarget := e.OpponentInput()
var extraTarget *input.Input
e.ForEachOpponentSlot(func(target *input.Input) bool {
if target == nil || target == currentTarget {
return true
}
pet := target.CurrentPet()
if pet == nil || !pet.Alive() {
return true
}
extraTarget = target
return false
})
if extraTarget == nil {
return true
}
extraTarget.Damage(our, &info.DamageZone{
Type: info.DamageType.Red,
Damage: our.SumDamage,
})
return true
}
func init() {
input.InitEffect(input.EffectType.NewSel, 504, &NewSel504{})
}

View File

@@ -15,8 +15,11 @@ func (e *Effect453) Skill_Use() bool {
ispwoer := false
for i, v := range e.OpponentInput().Prop[:] {
if v > 0 {
e.CarrierInput().SetProp(e.OpponentInput(), int8(i), 0)
ispwoer = true
if e.OpponentInput().SetProp(e.OpponentInput(), int8(i), 0) {
ispwoer = true
}
return true
}

View File

@@ -1,44 +0,0 @@
package effect
import (
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/alpacahq/alpacadecimal"
)
// {
// "id": 529,
// "argsNum": 0,
// "info": "使自身体力百分比与对手体力百分比对调"
// },
type Effect529 struct {
node.EffectNode
}
func (e *Effect529) OnSkill() bool {
// 计算自身当前体力百分比
ourCurrentHP := e.Ctx().Our.CurPet.Info.Hp
ourMaxHP := e.Ctx().Our.CurPet.GetMaxHP().IntPart()
ourHPPercentage := alpacadecimal.NewFromInt(ourCurrentHP).Div(alpacadecimal.NewFromInt(ourMaxHP))
// 计算对手当前体力百分比
oppCurrentHP := e.Ctx().Opp.CurPet.Info.Hp
oppMaxHP := e.Ctx().Opp.CurPet.GetMaxHP().IntPart()
oppHPPercentage := alpacadecimal.NewFromInt(int64(oppCurrentHP)).Div(alpacadecimal.NewFromInt(oppMaxHP))
// 计算新的体力值(按百分比对调)
newOurHP := alpacadecimal.NewFromInt(ourMaxHP).Mul(oppHPPercentage).IntPart()
newOppHP := alpacadecimal.NewFromInt(oppMaxHP).Mul(ourHPPercentage).IntPart()
// 设置新的体力值
e.Ctx().Our.CurPet.Info.Hp = int(newOurHP)
e.Ctx().Opp.CurPet.Info.Hp = int(newOppHP)
return true
}
func init() {
input.InitEffect(input.EffectType.Skill, 529, &Effect529{})
}

View File

@@ -152,8 +152,7 @@ func (f *FightC) buildNoteUseSkillOutboundInfo() info.NoteUseSkillOutboundInfo {
result := info.NoteUseSkillOutboundInfo{}
result.FirstAttackInfo = append(result.FirstAttackInfo, f.collectAttackValues(f.Our)...)
result.SecondAttackInfo = append(result.SecondAttackInfo, f.collectAttackValues(f.Opp)...)
result.FirstAttackInfoLen = uint32(len(result.FirstAttackInfo))
result.SecondAttackInfoLen = uint32(len(result.SecondAttackInfo))
return result
}

View File

@@ -3,18 +3,40 @@ package input
import "blazing/logic/service/fight/info"
type LegacySides struct {
Our *Input // 兼容旧 effect当前执行侧
Opp *Input // 兼容旧 effect:当前执行侧的对位
// Our 兼容旧 effect当前执行侧(历史语义里的“我方”)。
// 使用场景:
// 1) 旧 effect 仍通过 Ctx().Our/Ctx().Opp 取双方输入。
// 2) Source/Carrier/Target 未设置时作为默认回退。
Our *Input
// Opp 兼容旧 effect当前执行侧的对位历史语义里的“对手”
// 使用场景:
// 1) 单目标旧链路下,直接作为“对手输入”。
// 2) Source/Carrier/Target 语义未接入的旧 effect。
Opp *Input
}
type EffectBinding struct {
Source *Input // effect 绑定的输入源,等价于 e.GetInput()
// Source effect 来源输入(谁施加/创建了这个 effect通常等价于 e.GetInput()
// 该值一般在 effect 生命周期内保持不变。
// 使用场景:
// 1) 伤害归属/效果归属判定(谁是施法者)。
// 2) A 给 B 挂效果后,后续仍需要识别“来源是 A”。
Source *Input
Carrier *Input // 当前持有该 effect 的输入源
// Carrier 当前持有效果的输入effect 实际挂载在哪个输入上)。
// 例如 A 给 B 挂 debuff 时Source=ACarrier=B。
// 使用场景:
// 1) 回合开始/结束、切换、状态结算时,按“持有者”触发。
// 2) 需要区分“是谁挂的”(Source) 与 “挂在谁身上”(Carrier)。
Carrier *Input
Target *Input // 当前 effect 实际作用/挂载的输入源
// Target 本次结算的实际作用目标输入(瞬时语义,可在链式/群体结算中变化)。
// 为空时通常回退到 Carrier 或 LegacySides.Our。
// 使用场景:
// 1) 链式/群体/溅射:同一个 effect 逐个作用不同目标。
// 2) 重定向/反弹:效果仍由原 Carrier 持有,但本次作用对象发生变化。
Target *Input
}
type Ctx struct {
@@ -36,6 +58,10 @@ func (c *Ctx) Self() *Input {
return c.Our
}
// Receiver 返回本次结算的接收方输入Target > Carrier > Our。
// 使用场景:
// 1) 写 effect 时不关心目标来源,只取“当前实际受影响者”。
// 2) 统一兼容单目标与群体/重定向结算链路。
func (c *Ctx) Receiver() *Input {
if c == nil {
return nil

View File

@@ -51,6 +51,7 @@ func (e *EffectNode) GetInput() *input.Input {
}
// SourceInput 返回 effect 的来源输入(优先 Ctx.Source回退到绑定输入
func (e *EffectNode) SourceInput() *input.Input {
if e.Ctx().Source != nil {
return e.Ctx().Source
@@ -58,6 +59,7 @@ func (e *EffectNode) SourceInput() *input.Input {
return e.Input
}
// CarrierInput 返回当前持有该 effect 的输入(优先 Ctx.Carrier
func (e *EffectNode) CarrierInput() *input.Input {
if e.Ctx().Carrier != nil {
return e.Ctx().Carrier
@@ -65,6 +67,7 @@ func (e *EffectNode) CarrierInput() *input.Input {
return e.Ctx().Our
}
// TargetInput 返回当前 effect 作用目标输入(优先 Ctx.Target
func (e *EffectNode) TargetInput() *input.Input {
if e.Ctx().Target != nil {
return e.Ctx().Target
@@ -72,10 +75,12 @@ func (e *EffectNode) TargetInput() *input.Input {
return e.Ctx().Opp
}
// OpponentInput 返回当前上下文中的对位输入(兼容旧链路)。
func (e *EffectNode) OpponentInput() *input.Input {
return e.Ctx().Opp
}
// SourcePet 返回来源输入当前出战精灵。
func (e *EffectNode) SourcePet() *info.BattlePetEntity {
in := e.SourceInput()
if in == nil {
@@ -84,6 +89,7 @@ func (e *EffectNode) SourcePet() *info.BattlePetEntity {
return in.CurrentPet()
}
// CarrierPet 返回持有效果输入当前出战精灵。
func (e *EffectNode) CarrierPet() *info.BattlePetEntity {
in := e.CarrierInput()
if in == nil {
@@ -92,6 +98,7 @@ func (e *EffectNode) CarrierPet() *info.BattlePetEntity {
return in.CurrentPet()
}
// TargetPet 返回目标输入当前出战精灵。
func (e *EffectNode) TargetPet() *info.BattlePetEntity {
in := e.TargetInput()
if in == nil {
@@ -100,6 +107,7 @@ func (e *EffectNode) TargetPet() *info.BattlePetEntity {
return in.CurrentPet()
}
// OpponentPet 返回对位输入当前出战精灵。
func (e *EffectNode) OpponentPet() *info.BattlePetEntity {
in := e.OpponentInput()
if in == nil {
@@ -108,6 +116,8 @@ func (e *EffectNode) OpponentPet() *info.BattlePetEntity {
return in.CurrentPet()
}
// ForEachOpponentSlot 遍历对面全部站位;回调返回 false 时提前停止。
// 无组队视图时回退到单目标 OpponentInput。
func (e *EffectNode) ForEachOpponentSlot(fn func(*input.Input) bool) {
if fn == nil {
return
@@ -136,6 +146,8 @@ func (e *EffectNode) ForEachOpponentSlot(fn func(*input.Input) bool) {
}
}
// ForEachCarrierBenchPet 遍历持有效果方当前站位的后备精灵(不含当前出战)。
// 回调返回 false 时提前停止。
func (e *EffectNode) ForEachCarrierBenchPet(fn func(*info.BattlePetEntity) bool) {
if fn == nil {
return