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 } } }