fix: 修复 Effect201 在单人战斗中误生效的问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

This commit is contained in:
xinian
2026-04-11 22:22:23 +08:00
parent f6aa0c3339
commit 3a7f593105
4 changed files with 211 additions and 8 deletions

View File

@@ -110,6 +110,10 @@ func (e *Effect201) OnSkill() bool {
return true
}
if !carrier.IsMultiInputBattle() {
return true
}
team := carrier.Team
if len(team) == 0 {
team = []*input.Input{carrier}

View File

@@ -0,0 +1,70 @@
package effect
import (
"testing"
fightinfo "blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/player/model"
)
func newEffect201TestInput(hp, maxHP uint32) *input.Input {
in := &input.Input{
CurPet: []*fightinfo.BattlePetEntity{{
Info: model.PetInfo{
Hp: hp,
MaxHp: maxHP,
},
}},
}
in.AttackValue = fightinfo.NewAttackValue(0)
return in
}
func TestEffect201HealAllIgnoredInSingleInputBattle(t *testing.T) {
carrier := newEffect201TestInput(40, 100)
opponent := newEffect201TestInput(60, 100)
carrier.Team = []*input.Input{carrier}
carrier.OppTeam = []*input.Input{opponent}
eff := &Effect201{}
eff.SetArgs(carrier, 1, 2)
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
}
if !eff.OnSkill() {
t.Fatalf("expected effect to finish successfully")
}
if got := carrier.CurrentPet().Info.Hp; got != 40 {
t.Fatalf("expected single-input full-team heal to be ignored, got hp %d", got)
}
}
func TestEffect201HealAllWorksInMultiInputBattle(t *testing.T) {
carrier := newEffect201TestInput(40, 100)
ally := newEffect201TestInput(10, 80)
opponent := newEffect201TestInput(60, 100)
carrier.Team = []*input.Input{carrier, ally}
carrier.OppTeam = []*input.Input{opponent}
ally.Team = carrier.Team
ally.OppTeam = carrier.OppTeam
eff := &Effect201{}
eff.SetArgs(carrier, 1, 2)
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
}
if !eff.OnSkill() {
t.Fatalf("expected effect to finish successfully")
}
if got := carrier.CurrentPet().Info.Hp; got != 90 {
t.Fatalf("expected carrier hp 90 after full-team heal, got %d", got)
}
if got := ally.CurrentPet().Info.Hp; got != 50 {
t.Fatalf("expected ally hp 50 after full-team heal, got %d", got)
}
}

View File

@@ -2,6 +2,40 @@ package input
import "github.com/gogf/gf/v2/util/grand"
func compactSlots(slots []*Input) []*Input {
if len(slots) == 0 {
return nil
}
ret := make([]*Input, 0, len(slots))
for _, slot := range slots {
if slot == nil {
continue
}
ret = append(ret, slot)
}
return ret
}
func uniqueControllerCount(slots []*Input) int {
if len(slots) == 0 {
return 0
}
seen := make(map[uint32]struct{}, len(slots))
count := 0
for _, slot := range slots {
if slot == nil || slot.Player == nil {
continue
}
userID := slot.Player.GetInfo().UserID
if _, ok := seen[userID]; ok {
continue
}
seen[userID] = struct{}{}
count++
}
return count
}
// TeamSlots 返回当前输入所在阵营的全部有效战斗位视图。
func (our *Input) TeamSlots() []*Input {
if our == nil {
@@ -10,14 +44,7 @@ func (our *Input) TeamSlots() []*Input {
if len(our.Team) == 0 {
return []*Input{our}
}
slots := make([]*Input, 0, len(our.Team))
for _, teammate := range our.Team {
if teammate == nil {
continue
}
slots = append(slots, teammate)
}
return slots
return compactSlots(our.Team)
}
// TeamSlotIndex 返回当前输入在本阵营中的原始站位下标。
@@ -69,6 +96,51 @@ func (our *Input) HasLivingTeammate() bool {
return len(our.LivingTeammates()) > 0
}
// TeamInputCount 返回当前阵营有效 input 数量。
func (our *Input) TeamInputCount() int {
return len(our.TeamSlots())
}
// OpponentInputCount 返回敌方阵营有效 input 数量。
func (our *Input) OpponentInputCount() int {
if our == nil {
return 0
}
if len(our.OppTeam) == 0 {
if our.Opp != nil {
return 1
}
return 0
}
return len(compactSlots(our.OppTeam))
}
// IsMultiInputSide 判断当前阵营是否为多 input。
func (our *Input) IsMultiInputSide() bool {
return our.TeamInputCount() > 1
}
// IsMultiInputBattle 判断当前战斗是否包含多 input 站位。
func (our *Input) IsMultiInputBattle() bool {
if our == nil {
return false
}
return our.TeamInputCount() > 1 || our.OpponentInputCount() > 1
}
// TeamControllerCount 返回当前阵营实际操作者数量。
func (our *Input) TeamControllerCount() int {
if our == nil {
return 0
}
return uniqueControllerCount(our.TeamSlots())
}
// IsSingleControllerMultiInputSide 判断当前阵营是否为“单人控制的多 input”。
func (our *Input) IsSingleControllerMultiInputSide() bool {
return our.IsMultiInputSide() && our.TeamControllerCount() <= 1
}
// TeamSlotAt 返回指定己方站位。
func (our *Input) TeamSlotAt(index int) *Input {
if our == nil {

View File

@@ -3,10 +3,32 @@ package input
import (
"testing"
"blazing/common/socket/errorcode"
"blazing/logic/service/common"
fightinfo "blazing/logic/service/fight/info"
spaceinfo "blazing/logic/service/space/info"
"blazing/modules/player/model"
)
type teamTestPlayer struct {
info model.PlayerInfo
fightInfo fightinfo.Fightinfo
}
func (p *teamTestPlayer) ApplyPetDisplayInfo(*spaceinfo.SimpleInfo) {}
func (p *teamTestPlayer) GetPlayerCaptureContext() *fightinfo.PlayerCaptureContext { return nil }
func (p *teamTestPlayer) Roll(int, int) (bool, float64, float64) { return false, 0, 0 }
func (p *teamTestPlayer) Getfightinfo() fightinfo.Fightinfo { return p.fightInfo }
func (p *teamTestPlayer) ItemAdd(int64, int64) bool { return false }
func (p *teamTestPlayer) GetInfo() *model.PlayerInfo { return &p.info }
func (p *teamTestPlayer) InvitePlayer(common.PlayerI) {}
func (p *teamTestPlayer) SetFightC(common.FightI) {}
func (p *teamTestPlayer) QuitFight() {}
func (p *teamTestPlayer) MessWin(bool) {}
func (p *teamTestPlayer) CanFight() errorcode.ErrorCode { return 0 }
func (p *teamTestPlayer) SendPackCmd(uint32, any) {}
func (p *teamTestPlayer) GetPetInfo(uint32) []model.PetInfo { return nil }
func TestLivingTeammatesFiltersSelfAndDeadSlots(t *testing.T) {
owner := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 10}}}}
aliveMate := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 5}}}}
@@ -58,3 +80,38 @@ func TestRandomOpponentSlotIndexPrefersLivingTarget(t *testing.T) {
t.Fatalf("expected opponent slot 1 to return live opponent")
}
}
func TestInputModeHelpers(t *testing.T) {
playerA := &teamTestPlayer{info: model.PlayerInfo{UserID: 1001}}
playerB := &teamTestPlayer{info: model.PlayerInfo{UserID: 1002}}
solo := &Input{}
soloMate := &Input{}
groupMate := &Input{}
solo.Player = playerA
soloMate.Player = playerA
groupMate.Player = playerB
solo.Team = []*Input{solo}
solo.OppTeam = []*Input{{}}
if solo.IsMultiInputBattle() {
t.Fatalf("expected single-input battle to be false")
}
solo.Team = []*Input{solo, soloMate}
if !solo.IsMultiInputBattle() {
t.Fatalf("expected multi-input battle to be true")
}
if !solo.IsSingleControllerMultiInputSide() {
t.Fatalf("expected same-controller double side to be single-controller multi-input")
}
solo.Team = []*Input{solo, groupMate}
if solo.TeamControllerCount() != 2 {
t.Fatalf("expected group side controller count 2, got %d", solo.TeamControllerCount())
}
if solo.IsSingleControllerMultiInputSide() {
t.Fatalf("expected grouped side not to be single-controller multi-input")
}
}