lawless/server/internal/battle/engine_test.go

188 行
5.5 KiB
Go

package battle
import (
"math/rand"
"testing"
"github.com/honghuang-game/server/config"
)
// testConfig 返回带默认战斗参数的静态配置,保证单元测试不依赖 Nacos
func testConfig() *config.Config {
return config.NewStaticConfig(map[string]interface{}{
"combat.atb.gauge_max": 100,
"combat.atb.base_coefficient": 0.1,
"combat.battle.max_ticks": 3000,
"combat.battle.max_actions": 50,
"combat.mana.max": 100,
"combat.mana.regen_per_tick": 0.15,
"combat.mana.regen_per_action": 3,
"combat.mana.small_skill_cost": 20,
"combat.mana.big_skill_cost": 40,
"combat.skill.normal_attack_coef": 1.0,
"combat.skill.small_coef_max": 2.5,
"combat.skill.big_coef_min": 2.5,
"combat.skill.small_cd": 225,
"combat.skill.big_cd": 800,
"combat.skill.small_trigger_rate": 1.25,
"combat.skill.big_trigger_rate": 2.0,
"combat.crit.multiplier": 1.5,
"combat.crit.rate_cap": 0.6,
"combat.evade.rate_cap": 0.5,
"combat.defense.coefficient": 0.5,
"combat.atk.coefficient": 1.0,
"combat.magic_atk.coefficient": 1.0,
"combat.element.advantage": 1.2,
"combat.element.disadvantage": 0.85,
"combat.faction.cap": 0.15,
"combat.cc.slow_value": 0.2,
})
}
func makePlayer(id, name, side string, str, vit, agi, luk float64) *Fighter {
return NewFighter(id, name, "tiger_yao", side, 1, 1, BaseStats{
Str: str,
Vit: vit,
Wis: 10,
Agi: agi,
Spi: 10,
Luk: luk,
}, "neutral", "none", true)
}
// countSkillUses 统计某技能在战报中使用次数
func countSkillUses(report *BattleReport, skillID string) int {
cnt := 0
for _, r := range report.Rounds {
if r.SkillID == skillID {
cnt++
}
}
return cnt
}
// countActorRounds 统计某方行动次数
func countActorRounds(report *BattleReport, side string) int {
cnt := 0
for _, r := range report.Rounds {
if r.Actor == side {
cnt++
}
}
return cnt
}
// TestHighSpeedCrush 验证高速方可以碾压低速目标
func TestHighSpeedCrush(t *testing.T) {
config.Global = testConfig()
player := makePlayer("c-001", "铁爪传", "attacker", 50, 30, 300, 30)
monster := makePlayer("npc-001", "幽魂", "defender", 30, 25, 80, 10)
monster.IsPlayer = false
monster.Side = "defender"
eng := NewEngine(player, monster, rand.New(rand.NewSource(42)), "expedition_pve", 1)
report := eng.Run()
if report.Result.Winner != "attacker" {
t.Fatalf("expected attacker win, got %s", report.Result.Winner)
}
pa := countActorRounds(report, "attacker")
da := countActorRounds(report, "defender")
if pa <= da {
t.Fatalf("expected attacker actions > defender, got attacker=%d defender=%d", pa, da)
}
if report.Result.FinalHP["attacker"] <= 0 {
t.Fatalf("expected attacker survive, final hp=%d", report.Result.FinalHP["attacker"])
}
}
// TestLowSpeedTank 验证低速肉盾可凭 HP/防御耗死敌人
func TestLowSpeedTank(t *testing.T) {
config.Global = testConfig()
player := makePlayer("c-002", "石盾", "attacker", 35, 120, 80, 10)
monster := makePlayer("npc-002", "赤焰狼妖", "defender", 45, 40, 150, 15)
monster.IsPlayer = false
monster.Side = "defender"
// 给玩家一个中低速但高伤的简单技能,避免纯普攻超时
skill := &SkillInstance{
InstanceID: "s-001",
SkillID: "heavy_blow",
Name: "重击",
DamageType: DamageTypePhysical,
Element: "none",
Alignment: "neutral",
ScalingAttr: "str",
BaseCoef: 2.0,
CD: 300,
Cost: 25,
TriggerRate: 1.5,
TargetType: TargetSingle,
}
player.AddSkill(skill)
eng := NewEngine(player, monster, rand.New(rand.NewSource(7)), "expedition_pve", 1)
report := eng.Run()
if report.Result.Winner != "attacker" {
t.Fatalf("expected attacker win, got %s", report.Result.Winner)
}
if report.Result.FinalHP["attacker"] <= 0 {
t.Fatalf("expected tank survive, final hp=%d", report.Result.FinalHP["attacker"])
}
}
// TestSkillCDConstraint 验证技能受 CD 与内力双重约束,不能无限连发
func TestSkillCDConstraint(t *testing.T) {
config.Global = testConfig()
player := makePlayer("c-003", "剑修", "attacker", 60, 30, 150, 20)
monster := makePlayer("npc-003", "山魈", "defender", 30, 40, 60, 10)
monster.IsPlayer = false
monster.Side = "defender"
// 高伤大招,CD 225,消耗 40
bigSkill := &SkillInstance{
InstanceID: "s-002",
SkillID: "sky_sword",
Name: "天剑斩",
DamageType: DamageTypePhysical,
Element: "none",
Alignment: "neutral",
ScalingAttr: "str",
BaseCoef: 3.0,
CD: 225,
Cost: 40,
TriggerRate: 2.0,
TargetType: TargetSingle,
}
player.AddSkill(bigSkill)
eng := NewEngine(player, monster, rand.New(rand.NewSource(13)), "expedition_pve", 1)
report := eng.Run()
uses := countSkillUses(report, "sky_sword")
if uses == 0 {
t.Fatalf("expected big skill used at least once")
}
// 总 tick 数应远小于上限,理论上最多使用次数受 CD+回蓝限制
// 放宽判断:使用次数不超过 15,且大招不会连续出现
if uses > 15 {
t.Fatalf("expected skill uses <= 15 due to CD/mana, got %d", uses)
}
lastTick := -9999
for _, r := range report.Rounds {
if r.SkillID == "sky_sword" {
if r.Tick-lastTick < 225 {
t.Fatalf("skill reused too soon: last=%d current=%d", lastTick, r.Tick)
}
lastTick = r.Tick
}
}
}