```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

feat(fight): 新增疲惫状态并优化睡眠状态机制

- 实现疲惫状态(StatusTired),仅限制攻击技能,允许属性技能正常使用
- 重构睡眠状态,改为在被攻击且未miss时立即解除,而非技能使用后
- 修复寄生种子效果触发时机,改为回合开始时触发
- 调整寄生效果的目标为技能施放者而非
This commit is contained in:
昔念
2026-04-13 21:06:45 +08:00
parent ce1a2a3588
commit f95fd49efd
12 changed files with 322 additions and 42 deletions

View File

@@ -311,7 +311,7 @@ func (e *Effect2194) OnSkill() bool {
if e.Ctx().Opp.CurPet[0] == nil {
return true
}
addStatusByID(e.Ctx().Our, e.Ctx().Opp, int(info.PetStatus.DrainedHP))
addTimedStatus(e.Ctx().Our, e.Ctx().Opp, int(info.PetStatus.DrainedHP), 4)
return true
}

View File

@@ -34,7 +34,7 @@ func (e *Effect13) OnSkill() bool {
if eff == nil {
return true
}
eff.Duration(e.EffectNode.SideEffectArgs[0] - 1)
eff.Duration(e.EffectNode.SideEffectArgs[0])
e.Ctx().Opp.AddEffect(e.Ctx().Our, eff)
return true

View File

@@ -36,30 +36,59 @@ func (e *StatusCannotAct) ActionStart(attacker, defender *action.SelectSkillActi
return false
}
// 疲惫状态:仅限制攻击技能,本回合属性技能仍可正常使用。
type StatusTired struct {
BaseStatus
}
func (e *StatusTired) ActionStart(attacker, defender *action.SelectSkillAction) bool {
if e.Ctx().SkillEntity == nil {
return false
}
return e.Ctx().SkillEntity.Category() == info.Category.STATUS
}
// 睡眠状态:受击后解除
type StatusSleep struct {
StatusCannotAct
hasTriedAct bool // 标记是否尝试过行动
hasTriedAct bool
}
// 睡眠在“被攻击且未 miss”后立即解除而不是等到技能使用后节点。
func (e *StatusSleep) DamageSubEx(zone *info.DamageZone) bool {
if zone == nil || e.Ctx().SkillEntity == nil {
return true
}
if e.Ctx().SkillEntity.Category() != info.Category.STATUS {
e.Alive(false)
}
return true
}
// 尝试出手时标记状态
func (e *StatusSleep) ActionStart(attacker, defender *action.SelectSkillAction) bool {
if e.Duration() <= 0 {
e.hasTriedAct = false
return true
}
e.hasTriedAct = true
return e.StatusCannotAct.ActionStart(attacker, defender)
}
// 技能使用后处理:非状态类技能触发后解除睡眠
func (e *StatusSleep) Skill_Use_ex() bool {
if !e.hasTriedAct {
return true
}
// 技能实体存在且非状态类型技能,解除睡眠
if e.Ctx().SkillEntity != nil && e.Ctx().Category() != info.Category.STATUS {
if e.Duration() <= 0 && e.Ctx().SkillEntity != nil && e.Ctx().Category() != info.Category.STATUS {
e.Alive(false)
}
e.hasTriedAct = false
return true
}
func (e *StatusSleep) TurnEnd() {
e.hasTriedAct = false
}
// 持续伤害状态基类(中毒、冻伤、烧伤等)
type ContinuousDamage struct {
BaseStatus
@@ -131,15 +160,13 @@ func (e *ParasiticSeed) SwitchOut(in *input.Input) bool {
return true
}
// 技能命中前触发寄生效果
func (e *ParasiticSeed) ActionStartEx(attacker, defender *action.SelectSkillAction) bool {
// 回合开始触发寄生效果。寄生属于完整回合流程的一部分,不依赖本回合是否成功出手。
func (e *ParasiticSeed) TurnStart(attacker, defender *action.SelectSkillAction) {
carrier := e.CarrierInput()
source := e.SourceInput()
opp := e.TargetInput()
if carrier == nil {
return true
return
}
// 过滤特定类型单位假设1是植物类型使用枚举替代魔法数字
damage := alpacadecimal.NewFromInt(int64(carrier.CurPet[0].Info.MaxHp)).
Div(alpacadecimal.NewFromInt(8))
@@ -149,13 +176,12 @@ func (e *ParasiticSeed) ActionStartEx(attacker, defender *action.SelectSkillActi
Type: info.DamageType.True,
Damage: damage,
})
if opp == nil || opp.CurPet[0].GetHP().IntPart() == 0 {
return true
if source == nil || source.CurPet[0] == nil || source.CurPet[0].GetHP().IntPart() == 0 {
return
}
// 给对方回血(不受回血限制影响)
opp.Heal(carrier, nil, damage)
return true
// 给寄生种子的施放者回血(不受回血限制影响)
source.Heal(carrier, nil, damage)
}
type Flammable struct {
@@ -271,7 +297,6 @@ func init() {
// 批量注册不能行动的状态
nonActingStatuses := []info.EnumPetStatus{
info.PetStatus.Paralysis, // 麻痹
info.PetStatus.Tired, // 疲惫
info.PetStatus.Fear, // 害怕
info.PetStatus.Petrified, // 石化
}
@@ -281,6 +306,10 @@ func init() {
input.InitEffect(input.EffectType.Status, int(status), effect)
}
tired := &StatusTired{}
tired.Status = info.PetStatus.Tired
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Tired), tired)
// 注册睡眠状态使用枚举常量替代硬编码8
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Sleep), &StatusSleep{})
}

View File

@@ -292,14 +292,11 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
}
}
if firstAttack == nil && secondAttack == nil {
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
f.First, f.Second = f.Second, f.First
}
skipActionStage := firstAttack == nil && secondAttack == nil
var attacker, defender *input.Input
f.TrueFirst = f.First
//开始回合操作
for i := 0; i < 2; i++ {
//开始回合操作。若双方本回合都未出手,则只走完整回合流程,不进入动作阶段。
for i := 0; !skipActionStage && i < 2; i++ {
var originalSkill *info.SkillEntity //原始技能
var currentSkill *info.SkillEntity //当前技能
var currentAction *action.SelectSkillAction

View File

@@ -17,7 +17,6 @@ func (player *Player) WarehousePetList() []model.PetInfo {
return make([]model.PetInfo, 0)
}
result := make([]model.PetInfo, 0, len(allPets))
return result
@@ -25,7 +24,17 @@ func (player *Player) WarehousePetList() []model.PetInfo {
// AddPetExp 添加宠物经验
func (p *Player) AddPetExp(petInfo *model.PetInfo, addExp int64) {
if addExp < 0 {
if petInfo == nil || addExp <= 0 {
return
}
if petInfo.Level >= 100 {
petInfo.Level = 100
petInfo.Exp = 0
petInfo.Update(false)
petInfo.CalculatePetPane(100)
if petInfo.Hp > petInfo.MaxHp {
petInfo.Hp = petInfo.MaxHp
}
return
}
addExp = utils.Min(addExp, p.Info.ExpPool)
@@ -33,19 +42,17 @@ func (p *Player) AddPetExp(petInfo *model.PetInfo, addExp int64) {
exp := int64(petInfo.Exp) + addExp
p.Info.ExpPool -= addExp //减去已使用的经验
gainedExp := exp //已获得的经验
for exp >= int64(petInfo.NextLvExp) {
for petInfo.Level < 100 && exp >= int64(petInfo.NextLvExp) {
petInfo.Level++
exp -= int64(petInfo.LvExp)
petInfo.Update(true)
if originalLevel < 100 && petInfo.Level == 100 { //升到100了
p.Info.ExpPool += exp //减去已使用的经验
gainedExp -= exp
exp = 0
break //停止升级
}
}
if petInfo.Level >= 100 {
p.Info.ExpPool += exp // 超出100级上限的经验退回经验池
gainedExp -= exp
exp = 0
}
petInfo.Exp = (exp)
// 重新计算面板

View File

@@ -0,0 +1,80 @@
package player
import (
"blazing/common/data/xmlres"
playermodel "blazing/modules/player/model"
"testing"
)
func firstPetIDForTest(t *testing.T) int {
t.Helper()
for id := range xmlres.PetMAP {
return id
}
t.Fatal("xmlres.PetMAP is empty")
return 0
}
func TestAddPetExpStopsAtLevel100(t *testing.T) {
petID := firstPetIDForTest(t)
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 99, nil, 0)
if petInfo == nil {
t.Fatalf("failed to generate test pet")
}
player := &Player{
baseplayer: baseplayer{
Info: &playermodel.PlayerInfo{
ExpPool: 1_000_000,
},
},
}
player.AddPetExp(petInfo, petInfo.NextLvExp+10_000)
if petInfo.Level != 100 {
t.Fatalf("expected pet level to stop at 100, got %d", petInfo.Level)
}
if petInfo.Exp != 0 {
t.Fatalf("expected pet exp to reset at level cap, got %d", petInfo.Exp)
}
}
func TestAddPetExpDoesNotConsumePoolAboveLevel100(t *testing.T) {
petID := firstPetIDForTest(t)
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
if petInfo == nil {
t.Fatalf("failed to generate test pet")
}
petInfo.Level = 101
petInfo.MaxHp = 1
petInfo.Hp = 999999
player := &Player{
baseplayer: baseplayer{
Info: &playermodel.PlayerInfo{
ExpPool: 50_000,
},
},
}
player.AddPetExp(petInfo, 12_345)
if petInfo.Level != 100 {
t.Fatalf("expected level to be normalized to 100, got %d", petInfo.Level)
}
if player.Info.ExpPool != 50_000 {
t.Fatalf("expected exp pool to remain unchanged, got %d", player.Info.ExpPool)
}
if petInfo.Exp != 0 {
t.Fatalf("expected exp to reset after normalization, got %d", petInfo.Exp)
}
if petInfo.MaxHp <= 1 {
t.Fatalf("expected pet panel to be recalculated, got max hp %d", petInfo.MaxHp)
}
if petInfo.Hp != petInfo.MaxHp {
t.Fatalf("expected hp to be clamped to recalculated max hp, got hp=%d maxHp=%d", petInfo.Hp, petInfo.MaxHp)
}
}