package game import ( "testing" "time" "github.com/denisovdennis/autohero/internal/model" ) func TestOrcWarriorBurstEveryThirdAttack(t *testing.T) { enemy := &model.Enemy{ Type: model.EnemyOrc, Attack: 12, Speed: 1.0, SpecialAbilities: []model.SpecialAbility{model.AbilityBurst}, } // Attacks 1 and 2 should have multiplier 1.0 m1 := EnemyAttackDamageMultiplier(enemy) if m1 != 1.0 { t.Fatalf("attack 1: expected multiplier 1.0, got %.2f", m1) } m2 := EnemyAttackDamageMultiplier(enemy) if m2 != 1.0 { t.Fatalf("attack 2: expected multiplier 1.0, got %.2f", m2) } // Attack 3 should deal 1.5x m3 := EnemyAttackDamageMultiplier(enemy) if m3 != 1.5 { t.Fatalf("attack 3: expected multiplier 1.5, got %.2f", m3) } // Attack 4 back to normal m4 := EnemyAttackDamageMultiplier(enemy) if m4 != 1.0 { t.Fatalf("attack 4: expected multiplier 1.0, got %.2f", m4) } } func TestLightningTitanChainLightning(t *testing.T) { enemy := &model.Enemy{ Type: model.EnemyLightningTitan, Attack: 30, Speed: 1.5, SpecialAbilities: []model.SpecialAbility{model.AbilityStun, model.AbilityChainLightning}, } // Attacks 1-5 should be normal (no chain lightning) for i := 1; i <= 5; i++ { m := EnemyAttackDamageMultiplier(enemy) if m != 1.0 { t.Fatalf("attack %d: expected multiplier 1.0, got %.2f", i, m) } } // Attack 6 triggers chain lightning (3x) m6 := EnemyAttackDamageMultiplier(enemy) if m6 != 3.0 { t.Fatalf("attack 6: expected multiplier 3.0, got %.2f", m6) } } func TestIceGuardianAppliesIceSlow(t *testing.T) { hero := &model.Hero{ ID: 1, HP: 100, MaxHP: 100, Attack: 10, Defense: 5, Speed: 1.0, Strength: 5, Constitution: 5, Agility: 5, } enemy := &model.Enemy{ Type: model.EnemyIceGuardian, Attack: 14, Defense: 15, Speed: 0.7, SpecialAbilities: []model.SpecialAbility{model.AbilityIceSlow}, } now := time.Now() applied := false for i := 0; i < 200; i++ { hero.HP = hero.MaxHP hero.Debuffs = nil ProcessEnemyAttack(hero, enemy, now) for _, d := range hero.Debuffs { if d.Debuff.Type == model.DebuffIceSlow { applied = true if d.Debuff.Magnitude != 0.20 { t.Fatalf("IceSlow magnitude should be 0.20, got %.2f", d.Debuff.Magnitude) } break } } if applied { break } } if !applied { t.Fatal("Ice Guardian never applied IceSlow debuff in 200 attacks") } } func TestSkeletonKingSummonDamage(t *testing.T) { hero := &model.Hero{ ID: 1, HP: 100, MaxHP: 100, } enemy := &model.Enemy{ Type: model.EnemySkeletonKing, Attack: 18, SpecialAbilities: []model.SpecialAbility{model.AbilityRegen, model.AbilitySummon}, } start := time.Now() // Before 15 seconds: no summon damage. dmg := ProcessSummonDamage(hero, enemy, start, start, start.Add(10*time.Second)) if dmg != 0 { t.Fatalf("expected no summon damage before 15s, got %d", dmg) } // At 15 seconds: summon damage should occur. dmg = ProcessSummonDamage(hero, enemy, start, start.Add(14*time.Second), start.Add(16*time.Second)) if dmg == 0 { t.Fatal("expected summon damage after 15s boundary crossed") } expectedDmg := max(1, enemy.Attack/4) if dmg != expectedDmg { t.Fatalf("expected summon damage %d, got %d", expectedDmg, dmg) } } func TestLootGenerationOnEnemyDeath(t *testing.T) { drops := model.GenerateLoot(model.EnemyWolf, 1.0) if len(drops) == 0 { t.Fatal("expected at least one loot drop (gold)") } hasGold := false for _, d := range drops { if d.ItemType == "gold" { hasGold = true if d.GoldAmount <= 0 { t.Fatal("gold drop should have positive amount") } } } if !hasGold { t.Fatal("expected gold drop from GenerateLoot") } } func TestLuckMultiplierWithBuff(t *testing.T) { now := time.Now() hero := &model.Hero{ Buffs: []model.ActiveBuff{{ Buff: model.DefaultBuffs[model.BuffLuck], AppliedAt: now.Add(-time.Second), ExpiresAt: now.Add(10 * time.Second), }}, } mult := LuckMultiplier(hero, now) if mult != 2.5 { t.Fatalf("expected luck multiplier 2.5, got %.1f", mult) } } func TestLuckMultiplierWithoutBuff(t *testing.T) { hero := &model.Hero{} mult := LuckMultiplier(hero, time.Now()) if mult != 1.0 { t.Fatalf("expected luck multiplier 1.0 without buff, got %.1f", mult) } } func TestProcessDebuffDamageAppliesPoison(t *testing.T) { now := time.Now() hero := &model.Hero{ HP: 100, MaxHP: 100, Debuffs: []model.ActiveDebuff{{ Debuff: model.DefaultDebuffs[model.DebuffPoison], AppliedAt: now.Add(-time.Second), ExpiresAt: now.Add(4 * time.Second), }}, } dmg := ProcessDebuffDamage(hero, time.Second, now) if dmg == 0 { t.Fatal("expected poison to deal damage over time") } if hero.HP >= 100 { t.Fatal("expected hero HP to decrease from poison") } } func TestDodgeAbilityCanAvoidDamage(t *testing.T) { now := time.Now() hero := &model.Hero{ ID: 1, HP: 100, MaxHP: 100, Attack: 50, Defense: 0, Speed: 1.0, Strength: 10, Agility: 5, } enemy := &model.Enemy{ Type: model.EnemySkeletonArcher, MaxHP: 1000, HP: 1000, Attack: 10, Defense: 0, SpecialAbilities: []model.SpecialAbility{model.AbilityDodge}, } dodged := false for i := 0; i < 200; i++ { enemy.HP = enemy.MaxHP evt := ProcessAttack(hero, enemy, now) if evt.Damage == 0 { dodged = true break } } if !dodged { t.Fatal("expected at least one dodge in 200 hero attacks against Skeleton Archer") } }