2026-11-05 14:31:27 +00:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using NUnit.Framework;
|
|
|
|
|
|
using TKS.OutGame;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.SceneManagement;
|
|
|
|
|
|
using UnityEngine.TestTools;
|
|
|
|
|
|
|
|
|
|
|
|
namespace TKS.Tests
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2026-11-10 10:00:18 +00:00
|
|
|
|
/// 스킬(2026-11-10 #1112 · PD 엑셀 「장수 목록」 새 기획 · 효과 표 skill_effects.csv) — 효과 묶음별 동작:
|
|
|
|
|
|
/// 표 무결 · 진영 버프·부활 · 즉시 광역(피해·밀기·출혈 / 피해 없는 기절) · 재행동·반격 면역 · 무조건 치명타·흡혈 · 중독 중첩 · 혼란 · 출혈 · 무적 ·
|
|
|
|
|
|
/// 호위 · 기병 복귀 · 거대 화살 · 화염구(화상) · 회복·정화 · 위치 교환 · 아군 통과·탭 밀치기 · 보병 관통 · 가상 실행 복원.
|
|
|
|
|
|
/// 기대값은 효과 표 수치(SkillCatalog · Eff)와 자산 값에서 읽는다(C45 · 테스트에 수치 상수 없음 · 스킬 id 만 지정).
|
2026-11-05 14:31:27 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class SkillTests
|
|
|
|
|
|
{
|
|
|
|
|
|
BattleManager bm;
|
|
|
|
|
|
BalanceConfig cfg;
|
|
|
|
|
|
|
|
|
|
|
|
[UnitySetUp]
|
|
|
|
|
|
public IEnumerator SetUp()
|
|
|
|
|
|
{
|
|
|
|
|
|
WorldProgress.Reset();
|
|
|
|
|
|
SaveStore.UseMemory(true);
|
|
|
|
|
|
SceneManager.LoadScene("Battle");
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
bm = BattleManager.Instance;
|
|
|
|
|
|
Assert.IsNotNull(bm);
|
|
|
|
|
|
var wm = WorldMapScreen.Instance;
|
|
|
|
|
|
if (wm != null && wm.HasData) wm.EnterStage(0, 0);
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
cfg = bm.Config;
|
2026-11-10 10:00:18 +00:00
|
|
|
|
SkillSystem.AutoTap = true;
|
2026-11-05 14:31:27 +00:00
|
|
|
|
Time.timeScale = 20f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTearDown]
|
|
|
|
|
|
public IEnumerator TearDown()
|
|
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
SkillSystem.AutoTap = false;
|
2026-11-05 14:31:27 +00:00
|
|
|
|
Time.timeScale = 1f;
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IEnumerator Fresh()
|
|
|
|
|
|
{
|
|
|
|
|
|
bm.StopAllCoroutines();
|
|
|
|
|
|
bm.ClearUnits();
|
|
|
|
|
|
bm.ForcePhase(BattlePhase.PlayerTurn);
|
|
|
|
|
|
bm.SetTurnForTest(1);
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 도우미 ----------
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
static SkillEffect Eff(string skillId, string key)
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
var d = SkillCatalog.Get(skillId);
|
|
|
|
|
|
Assert.IsNotNull(d, skillId);
|
2026-11-10 10:00:18 +00:00
|
|
|
|
foreach (var e in d.ScaledEffects(1, 1f))
|
2026-11-05 14:31:27 +00:00
|
|
|
|
if (e.Key == key) return e;
|
|
|
|
|
|
Assert.Fail(skillId + " 에 " + key + " 없음");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
Unit Spawn(UnitType t, Faction f, float x, float y, string name)
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
return bm.SpawnUnit(t, f, new Vector2(x, y), name, 1f, 1f, false);
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
UnitSkills Give(Unit u, string heroId)
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var s = bm.Skills.AttachHero(u, heroId);
|
2026-11-05 14:31:27 +00:00
|
|
|
|
Assert.IsNotNull(s, heroId);
|
|
|
|
|
|
return s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
void Begin()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
bm.Skills.BeginBattle();
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
IEnumerator Act(Unit u, Vector2 target, ActionOutcome o)
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
yield return bm.ResolveForTest(u, target, false, o);
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
IEnumerator ActArmed(Unit u, Vector2 target, ActionOutcome o)
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
bm.Skills.Of(u).Reserved = true;
|
|
|
|
|
|
yield return bm.ResolveForTest(u, target, false, o);
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 표 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Data_EveryHeroSkillHasEffects()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var db = OutGameData.Default;
|
|
|
|
|
|
Assert.IsNotNull(db);
|
|
|
|
|
|
int skills = 0;
|
|
|
|
|
|
foreach (var h in db.Heroes.heroes)
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
if (h.isSoldier) continue;
|
|
|
|
|
|
foreach (var sid in new[] { h.skillActive, h.skillPassive })
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var d = SkillCatalog.Get(sid);
|
|
|
|
|
|
Assert.IsNotNull(d, sid);
|
|
|
|
|
|
Assert.Greater(d.Effects.Count, 0, sid + " 효과 줄");
|
|
|
|
|
|
foreach (var e in d.Effects) Assert.IsTrue(SkillKeys.Known(e.Key), sid + " " + e.Key);
|
|
|
|
|
|
skills++;
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-11-10 10:00:18 +00:00
|
|
|
|
Assert.AreEqual(db.Skills.skills.Count, skills);
|
|
|
|
|
|
Assert.IsTrue(db.Heroes.Get("diaochan").Female);
|
|
|
|
|
|
Assert.IsFalse(db.Heroes.Get("lvbu").Female);
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 패시브: 진영 공격 · 부활 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Passive_CampAttack_And_Revive()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var liu = Spawn(UnitType.Infantry, Faction.Player, 0f, -3f, "유비");
|
|
|
|
|
|
Give(liu, "liubei");
|
|
|
|
|
|
var shu = Spawn(UnitType.Spear, Faction.Player, 2f, -3f, "촉");
|
|
|
|
|
|
Give(shu, "guanyu");
|
|
|
|
|
|
var wei = Spawn(UnitType.Spear, Faction.Player, 4f, -3f, "위");
|
|
|
|
|
|
Give(wei, "zhanghe");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
float camp = Eff("liubei_p", SkillKeys.AtkPct).P;
|
|
|
|
|
|
Assert.AreEqual(1f + camp, SkillMods.AttackMult(liu), 1e-4f, "촉 진영 공격 +");
|
|
|
|
|
|
Assert.AreEqual(1f, SkillMods.AttackMult(wei), 1e-4f, "위 진영은 안 받음");
|
|
|
|
|
|
|
|
|
|
|
|
var rv = Eff("liubei_p", SkillKeys.Revive);
|
|
|
|
|
|
int got = bm.Skills.Hit(liu, liu.Hp + 50, null, HitKind.Dot);
|
|
|
|
|
|
Assert.IsTrue(liu.IsAlive, "부활");
|
|
|
|
|
|
Assert.AreEqual(Mathf.RoundToInt(liu.MaxHp * rv.P), liu.Hp);
|
|
|
|
|
|
Assert.Greater(got, 0);
|
|
|
|
|
|
bm.Skills.Hit(liu, liu.Hp + 50, null, HitKind.Dot);
|
|
|
|
|
|
Assert.IsFalse(liu.IsAlive, "부활은 전투당 " + rv.Get(1) + "회");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 즉시 광역 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Instant_AoeStrike_DamagePushBleed()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var gy = Spawn(UnitType.Spear, Faction.Player, 0f, 0f, "관우");
|
|
|
|
|
|
var s = Give(gy, "guanyu");
|
|
|
|
|
|
var aoe = Eff("guanyu_a", SkillKeys.AoeStrike);
|
|
|
|
|
|
var near = Spawn(UnitType.Infantry, Faction.Enemy, aoe.P * 0.5f, 0f, "근처");
|
|
|
|
|
|
var far = Spawn(UnitType.Infantry, Faction.Enemy, aoe.P + 3f, 0f, "멀리");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int hp0 = near.Hp, farHp = far.Hp;
|
|
|
|
|
|
float d0 = Vector2.Distance(gy.Position, near.Position);
|
|
|
|
|
|
yield return bm.Skills.Instant(s);
|
|
|
|
|
|
yield return new WaitForSeconds(0.3f);
|
|
|
|
|
|
Assert.Less(near.Hp, hp0, "반경 안 피해");
|
|
|
|
|
|
Assert.AreEqual(farHp, far.Hp, "반경 밖 무사");
|
|
|
|
|
|
Assert.Greater(Vector2.Distance(gy.Position, near.Position), d0 + 0.05f, "바깥으로 밀림");
|
|
|
|
|
|
Assert.IsTrue(SkillStatus.Bleeding(near), "출혈");
|
|
|
|
|
|
Assert.AreEqual(1 + s.Active.Cooldown, s.ReadyTurn, "쿨");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Instant_AoeStun_NoDamage()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var zf = Spawn(UnitType.Cavalry, Faction.Player, 0f, 0f, "장비");
|
|
|
|
|
|
var s = Give(zf, "zhangfei");
|
|
|
|
|
|
var aoe = Eff("zhangfei_a", SkillKeys.AoeStrike);
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, aoe.P * 0.5f, 0f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int hp0 = foe.Hp;
|
|
|
|
|
|
yield return bm.Skills.Instant(s);
|
|
|
|
|
|
Assert.AreEqual(hp0, foe.Hp, "피해 없음(p2 0)");
|
|
|
|
|
|
Assert.IsTrue(SkillStatus.Stunned(foe), "기절");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 다음 행동: 재행동 · 반격 면역 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator NextAction_ExtraAction_CounterImmune()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var liu = Spawn(UnitType.Infantry, Faction.Player, 0f, -3f, "유비");
|
|
|
|
|
|
var s = Give(liu, "liubei");
|
|
|
|
|
|
var foe = Spawn(UnitType.Spear, Faction.Enemy, 0f, 0f, "적");
|
|
|
|
|
|
Begin();
|
2026-11-05 14:31:27 +00:00
|
|
|
|
var o = new ActionOutcome();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
yield return ActArmed(liu, new Vector2(0f, 1f), o);
|
|
|
|
|
|
Assert.AreEqual(0, o.CounterTaken, "반격 안 받음");
|
|
|
|
|
|
Assert.AreEqual(Mathf.RoundToInt(Eff("liubei_a", SkillKeys.ExtraAction).P), s.ReactCredits, "재행동");
|
|
|
|
|
|
Assert.IsTrue(bm.Skills.TakeReaction(liu));
|
|
|
|
|
|
Assert.AreEqual(0f, SkillMods.CounterTakenMult(liu, foe), 1e-4f, "1턴 반격 면역은 재행동에도");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 치명타 · 흡혈 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator CritAlways_And_Lifesteal()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var mc = Spawn(UnitType.Cavalry, Faction.Player, 0f, -3f, "마초");
|
|
|
|
|
|
var s = Give(mc, "machao");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, 0f, "적");
|
2026-11-10 10:00:18 +00:00
|
|
|
|
Begin();
|
|
|
|
|
|
s.Armed = true;
|
|
|
|
|
|
Combat.Damage(cfg, mc, foe, false, new System.Random(1), true);
|
|
|
|
|
|
Assert.IsTrue(Combat.LastCrit, "무조건 치명타");
|
|
|
|
|
|
s.Armed = false;
|
|
|
|
|
|
mc.Hp = mc.MaxHp / 2;
|
|
|
|
|
|
int before = mc.Hp;
|
|
|
|
|
|
int dealt = bm.Skills.Hit(foe, 40, mc, HitKind.Attack);
|
|
|
|
|
|
Assert.AreEqual(Mathf.RoundToInt(dealt * Eff("machao_p", SkillKeys.Lifesteal).P), mc.Hp - before, "흡혈");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 상태 이상 ----------
|
|
|
|
|
|
|
2026-11-05 14:31:27 +00:00
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Poison_Stacks_And_Ticks()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var jx = Spawn(UnitType.Strategist, Faction.Player, 0f, -3f, "가후");
|
|
|
|
|
|
Give(jx, "jiaxu");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, 3f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
bm.Skills.ApplyHitStatuses(jx, foe, false, HitKind.Attack);
|
|
|
|
|
|
bm.Skills.ApplyHitStatuses(jx, foe, false, HitKind.Attack);
|
|
|
|
|
|
var st = bm.Skills.StatusOf(foe);
|
|
|
|
|
|
Assert.AreEqual(2, st.PoisonStacks, "중첩");
|
|
|
|
|
|
var hp = Eff("jiaxu_p", SkillKeys.HitPoison);
|
|
|
|
|
|
int before = foe.Hp;
|
|
|
|
|
|
yield return bm.Skills.TurnBegin(Faction.Enemy);
|
|
|
|
|
|
Assert.AreEqual(Mathf.Max(1, Mathf.RoundToInt(foe.MaxHp * hp.Get(1) * 2)), before - foe.Hp, "턴 시작 중독 × 중첩");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Confuse_HalvesSling_ThenExpires()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, 3f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
var c = Eff("zhangliao_a", SkillKeys.HitConfuse);
|
|
|
|
|
|
float base0 = SkillMods.SlingMax(foe);
|
|
|
|
|
|
bm.Skills.ApplyConfuse(foe, Mathf.RoundToInt(c.P), c.Get(1));
|
|
|
|
|
|
Assert.AreEqual(base0 * c.Get(1), SkillMods.SlingMax(foe), 1e-3f, "혼란 = 이동 × 배율");
|
|
|
|
|
|
yield return bm.Skills.TurnBegin(Faction.Player); // 적 턴 끝
|
|
|
|
|
|
Assert.AreEqual(base0, SkillMods.SlingMax(foe), 1e-3f, "적 턴 1번 뒤 해제");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Bleed_DamageByMovedDistance()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, 3f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
var b = Eff("xuhuang_p", SkillKeys.HitBleed);
|
|
|
|
|
|
bm.Skills.ApplyBleedStatus(foe, Mathf.RoundToInt(b.P), b.Get(1));
|
|
|
|
|
|
int before = foe.Hp;
|
|
|
|
|
|
float moved = foe.SlingMaxDistance * 0.5f;
|
|
|
|
|
|
bm.Skills.ApplyBleed(new Dictionary<Unit, float> { { foe, moved } });
|
|
|
|
|
|
Assert.AreEqual(Mathf.Max(1, Mathf.RoundToInt(foe.MaxHp * b.Get(1) * 0.5f)), before - foe.Hp, "출혈 = 거리 비례");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 무적 · 호위 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Invuln_Instant_BlocksDamageForRound()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var zj = Spawn(UnitType.Infantry, Faction.Player, 0f, -3f, "장각");
|
|
|
|
|
|
var s = Give(zj, "zhangjiao");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
yield return bm.Skills.Instant(s);
|
|
|
|
|
|
int hp = zj.Hp;
|
|
|
|
|
|
Assert.AreEqual(0, bm.Skills.Hit(zj, 30, null, HitKind.Attack));
|
|
|
|
|
|
Assert.AreEqual(hp, zj.Hp, "무적");
|
|
|
|
|
|
yield return bm.Skills.TurnBegin(Faction.Player);
|
|
|
|
|
|
Assert.Greater(bm.Skills.Hit(zj, 30, null, HitKind.Attack), 0, "다음 자기 턴 시작에 끝");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Guard_TakesAllyDamage()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var gp = Spawn(UnitType.Spear, Faction.Player, 0f, -3f, "관평");
|
|
|
|
|
|
Give(gp, "guanping");
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var g = Eff("guanping_p", SkillKeys.Guard);
|
|
|
|
|
|
var ally = Spawn(UnitType.Archer, Faction.Player, g.P * 0.5f, -3f, "궁병");
|
|
|
|
|
|
var far = Spawn(UnitType.Archer, Faction.Player, g.P + 4f, -3f, "먼 궁병");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int a0 = ally.Hp, g0 = gp.Hp, f0 = far.Hp;
|
|
|
|
|
|
bm.Skills.Hit(ally, 40, null, HitKind.Attack);
|
|
|
|
|
|
Assert.AreEqual(a0 - Mathf.RoundToInt(40 * (1f - Mathf.Clamp01(g.Get(1)))), ally.Hp, "호위 비율만큼 대신");
|
|
|
|
|
|
Assert.Less(gp.Hp, g0, "관평이 받음");
|
|
|
|
|
|
bm.Skills.Hit(far, 40, null, HitKind.Attack);
|
|
|
|
|
|
Assert.AreEqual(f0 - 40, far.Hp, "반경 밖 = 그대로");
|
|
|
|
|
|
}
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 기병 복귀 · 보병 관통 · 교환 · 아군 통과 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Cavalry_ReturnsToOriginOnHit()
|
|
|
|
|
|
{
|
2026-11-05 14:31:27 +00:00
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var zy = Spawn(UnitType.Cavalry, Faction.Player, 0f, -4f, "조운");
|
|
|
|
|
|
Give(zy, "zhaoyun");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
Vector2 origin = zy.Position;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return Act(zy, origin + Vector2.up * zy.SlingMaxDistance * 0.9f, o);
|
|
|
|
|
|
Assert.Contains(foe, o.Attacked);
|
|
|
|
|
|
Assert.IsTrue(o.Returned, "복귀");
|
|
|
|
|
|
Assert.Less(Vector2.Distance(zy.Position, origin), 0.3f, "출발 위치");
|
|
|
|
|
|
Assert.AreEqual(0, o.CounterTaken, "반격 안 받음");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Infantry_PierceThrough_HitsAllOnPath()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var sj = Spawn(UnitType.Infantry, Faction.Player, 0f, -4f, "손견");
|
|
|
|
|
|
Give(sj, "sunjian");
|
|
|
|
|
|
var a = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -2.5f, "적1");
|
|
|
|
|
|
var b = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1f, "적2");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(sj, sj.Position + Vector2.up * sj.SlingMaxDistance * 0.95f, o);
|
|
|
|
|
|
Assert.Contains(a, o.Attacked);
|
|
|
|
|
|
Assert.Contains(b, o.Attacked);
|
|
|
|
|
|
Assert.IsTrue(SkillStatus.Bleeding(a) || !a.IsAlive, "출혈");
|
|
|
|
|
|
Assert.AreEqual(0, bm.Skills.Hit(sj, 30, null, HitKind.Attack), "행동 뒤 1턴 무적");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Swap_OnHit_TargetToOrigin()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var wy = Spawn(UnitType.Infantry, Faction.Player, 0f, -4f, "위연");
|
|
|
|
|
|
var s = Give(wy, "weiyan");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
Vector2 origin = wy.Position;
|
|
|
|
|
|
Vector2 foe0 = foe.Position;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(wy, foe0, o);
|
|
|
|
|
|
Assert.Less(Vector2.Distance(foe.Position, origin), 0.05f, "적 → 출발 위치");
|
|
|
|
|
|
Assert.Less(Vector2.Distance(wy.Position, foe0), 0.05f, "위연 → 적 자리");
|
|
|
|
|
|
Assert.AreEqual(Mathf.RoundToInt(Eff("weiyan_a", SkillKeys.ExtraAction).P), s.ReactCredits, "재행동");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator PassAllies_ThenAimPush_NoSelfBounce()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var cc = Spawn(UnitType.Infantry, Faction.Player, 0f, -5f, "조조");
|
|
|
|
|
|
Give(cc, "caocao");
|
|
|
|
|
|
var ally = Spawn(UnitType.Archer, Faction.Player, 0f, -3.2f, "아군");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1.4f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
Vector2 foe0 = foe.Position;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(cc, cc.Position + Vector2.up * cc.SlingMaxDistance, o);
|
|
|
|
|
|
Assert.Contains(foe, o.Attacked, "아군 통과 → 적 명중");
|
|
|
|
|
|
Assert.IsFalse(o.Pushed.Contains(ally), "아군은 밀지 않음");
|
|
|
|
|
|
Assert.Greater(foe.Position.y, foe0.y + 0.05f, "자동 탭 = 슬링 방향으로 밀침");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 원거리 특수 사격 ----------
|
2026-11-05 14:31:27 +00:00
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator GiantArrow_IgnoresRange_Stuns()
|
2026-11-05 14:31:27 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var hz = Spawn(UnitType.Archer, Faction.Player, 0f, -10f, "황충");
|
|
|
|
|
|
Give(hz, "huangzhong");
|
|
|
|
|
|
var ally = Spawn(UnitType.Infantry, Faction.Player, 0f, -7f, "아군");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, hz.Position.y + SkillMods.Range(hz) * 2f + 2f, "먼 적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int ally0 = ally.Hp, foe0 = foe.Hp;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(hz, hz.Position + Vector2.up * 2f, o);
|
|
|
|
|
|
Assert.AreEqual(ally0, ally.Hp, "아군 통과");
|
|
|
|
|
|
Assert.Less(foe.Hp, foe0, "사거리 밖 적 명중");
|
|
|
|
|
|
Assert.IsTrue(SkillStatus.Stunned(foe) || !foe.IsAlive, "기절");
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
2026-11-05 15:22:54 +00:00
|
|
|
|
|
2026-11-09 02:11:20 +00:00
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Fireball_Blast_Burns()
|
2026-11-09 02:11:20 +00:00
|
|
|
|
{
|
2026-11-10 10:00:18 +00:00
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var xs = Spawn(UnitType.Strategist, Faction.Player, 0f, -4f, "서서");
|
|
|
|
|
|
Give(xs, "xushu");
|
|
|
|
|
|
var bl = Eff("xushu_a", SkillKeys.ShotBlast);
|
|
|
|
|
|
var a = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1f, "적1");
|
|
|
|
|
|
var b = Spawn(UnitType.Infantry, Faction.Enemy, bl.P * 0.6f, -1f, "적2");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int a0 = a.Hp, b0 = b.Hp;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(xs, xs.Position + Vector2.up * 2f, o);
|
|
|
|
|
|
Assert.Less(a.Hp, a0);
|
|
|
|
|
|
Assert.Less(b.Hp, b0, "폭발 반경");
|
|
|
|
|
|
Assert.Greater(bm.Skills.StatusOf(b).BurnTurns, 0, "화상");
|
2026-11-09 02:11:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 11:36:58 +00:00
|
|
|
|
// ---------- PD 결정 반영(2026-11-10 #1112 · ⑧ ⑩ ⑬ ⑭) ----------
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Firewall_HitsAlliesToo()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var lx = Spawn(UnitType.Strategist, Faction.Player, 0f, -5f, "육손");
|
|
|
|
|
|
Give(lx, "luxun");
|
|
|
|
|
|
var ally = Spawn(UnitType.Infantry, Faction.Player, 0f, -3f, "아군");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int a0 = ally.Hp, f0 = foe.Hp;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(lx, lx.Position + Vector2.up * 2f, o);
|
|
|
|
|
|
Assert.Less(foe.Hp, f0, "적 피해");
|
|
|
|
|
|
Assert.Less(ally.Hp, a0, "아군도 피해(결정 ⑬)");
|
|
|
|
|
|
Assert.Greater(bm.Skills.StatusOf(ally).BurnTurns, 0, "아군도 화상");
|
|
|
|
|
|
Assert.AreEqual(1, bm.Skills.Zones.Count, "장벽 지대");
|
|
|
|
|
|
Assert.IsTrue(bm.Skills.Zones[0].HitsAllies);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator SplashHeal_HealsTargetWithoutDamage()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var sq = Spawn(UnitType.Strategist, Faction.Player, 0f, -4f, "손건");
|
|
|
|
|
|
Give(sq, "sunqian");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
foe.Hp = foe.MaxHp / 2;
|
|
|
|
|
|
int f0 = foe.Hp;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(sq, foe.Position, o);
|
|
|
|
|
|
Assert.Greater(foe.Hp, f0, "명중 대상도 피해 없이 회복(결정 ⑧)");
|
|
|
|
|
|
Assert.AreEqual(0, o.DamageDealt);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Whirl_DealsDamageAndPushes()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var zj = Spawn(UnitType.Strategist, Faction.Player, 0f, -5f, "제갈근");
|
|
|
|
|
|
Give(zj, "zhugejin");
|
|
|
|
|
|
var foe = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -2f, "적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int f0 = foe.Hp;
|
|
|
|
|
|
float y0 = foe.Position.y;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(zj, zj.Position + Vector2.up * 2f, o);
|
|
|
|
|
|
Assert.Less(foe.Hp, f0, "공격력 × p3 피해(결정 ⑭)");
|
|
|
|
|
|
Assert.Greater(foe.Position.y, y0 + 0.5f, "진행 방향으로 밀림");
|
|
|
|
|
|
Assert.IsTrue(SkillStatus.Confused(foe), "혼란");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Zombie_ActsAtNextOwnTurnThenVanishes()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var zl = Spawn(UnitType.Strategist, Faction.Player, 0f, -5f, "장로");
|
|
|
|
|
|
Give(zl, "zhanglu");
|
|
|
|
|
|
var victim = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -2f, "약한 적");
|
|
|
|
|
|
var other = Spawn(UnitType.Infantry, Faction.Enemy, 3f, 1f, "다른 적");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
victim.Hp = 1;
|
|
|
|
|
|
int other0 = other.Hp;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return Act(zl, victim.Position, o);
|
|
|
|
|
|
Assert.IsFalse(victim.IsAlive);
|
|
|
|
|
|
Unit zombie = null;
|
|
|
|
|
|
foreach (var u in bm.Units)
|
|
|
|
|
|
if (u != null && u.IsAlive && SkillSystem.IsSummon(u)) zombie = u;
|
|
|
|
|
|
Assert.IsNotNull(zombie, "망령 생성");
|
|
|
|
|
|
Assert.AreEqual(Faction.Player, zombie.Faction);
|
|
|
|
|
|
Assert.AreEqual(other0, other.Hp, "처치 직후에는 공격하지 않음(결정 ⑩)");
|
|
|
|
|
|
yield return bm.Skills.TurnBegin(Faction.Player);
|
|
|
|
|
|
Assert.IsFalse(SkillSystem.IsSummon(zombie) && zombie != null && zombie.IsAlive, "자기 턴 시작에 1번 행동 후 소멸");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- Fable 검토 지적 수정(2026-11-10 · A1 · A2 · B1) ----------
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Infantry_SwordWave_PiercesLine()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var zt = Spawn(UnitType.Infantry, Faction.Player, 0f, -5f, "주태");
|
|
|
|
|
|
Give(zt, "zhoutai");
|
|
|
|
|
|
var a = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -3f, "적1");
|
|
|
|
|
|
var b = Spawn(UnitType.Infantry, Faction.Enemy, 0f, -1.5f, "적2");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
Vector2 start = zt.Position;
|
|
|
|
|
|
int a0 = a.Hp, b0 = b.Hp;
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
yield return ActArmed(zt, zt.Position + Vector2.up * 2f, o);
|
|
|
|
|
|
Assert.IsTrue(o.Fired, "보병 검기 = 사격");
|
|
|
|
|
|
Assert.Less(a.Hp, a0);
|
|
|
|
|
|
Assert.Less(b.Hp, b0, "직선 위 전원");
|
|
|
|
|
|
Assert.Less(Vector2.Distance(zt.Position, start), 0.05f, "주태는 제자리");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator GrantReaction_ToSoldierWithoutSkills()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var gj = Spawn(UnitType.Strategist, Faction.Player, 0f, -3f, "곽가");
|
|
|
|
|
|
var s = Give(gj, "guojia");
|
|
|
|
|
|
var soldier = Spawn(UnitType.Infantry, Faction.Player, 1.5f, -3f, "병사");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
Assert.IsNull(bm.Skills.Of(soldier), "스킬 없는 병사");
|
|
|
|
|
|
yield return bm.Skills.Instant(s);
|
|
|
|
|
|
Assert.IsTrue(bm.Skills.TakeReaction(soldier), "행동 안 한 병사 = 행동 뒤 재행동");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
|
|
|
|
|
public IEnumerator Hot_HealsEveryRoundForDuration()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
|
|
|
|
|
var pt = Spawn(UnitType.Strategist, Faction.Player, 0f, -3f, "방통");
|
|
|
|
|
|
var s = Give(pt, "pangtong");
|
|
|
|
|
|
var ally = Spawn(UnitType.Infantry, Faction.Player, 1.5f, -3f, "아군");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
int rounds = Eff("pangtong_a", SkillKeys.Hot).Dur;
|
|
|
|
|
|
yield return bm.Skills.Instant(s);
|
|
|
|
|
|
int heals = 0;
|
|
|
|
|
|
for (int i = 0; i < rounds + 1; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ally.Hp = 1;
|
|
|
|
|
|
yield return bm.Skills.TurnBegin(Faction.Player);
|
|
|
|
|
|
if (ally.Hp > 1) heals++;
|
|
|
|
|
|
}
|
|
|
|
|
|
Assert.AreEqual(rounds, heals, "지속 턴 수만큼 회복");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 회복 · 정화 ----------
|
2026-11-05 15:22:54 +00:00
|
|
|
|
|
|
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Instant_HealAndCleanse_InRange()
|
2026-11-05 15:22:54 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var zg = Spawn(UnitType.Strategist, Faction.Player, 0f, -3f, "제갈량");
|
|
|
|
|
|
var s = Give(zg, "zhugeliang");
|
|
|
|
|
|
var ally = Spawn(UnitType.Infantry, Faction.Player, 1.5f, -3f, "아군");
|
|
|
|
|
|
var far = Spawn(UnitType.Infantry, Faction.Player, zg.SlingMaxDistance + 4f, -3f, "먼 아군");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
ally.Hp = ally.MaxHp / 3;
|
|
|
|
|
|
far.Hp = far.MaxHp / 3;
|
|
|
|
|
|
var burn = Eff("xushu_a", SkillKeys.HitBurn);
|
|
|
|
|
|
bm.Skills.ApplyBurn(ally, Mathf.RoundToInt(burn.P), burn.Get(1));
|
|
|
|
|
|
int a0 = ally.Hp, f0 = far.Hp;
|
|
|
|
|
|
yield return bm.Skills.Instant(s);
|
|
|
|
|
|
Assert.Greater(ally.Hp, a0, "범위 안 회복");
|
|
|
|
|
|
Assert.AreEqual(f0, far.Hp, "범위 밖");
|
|
|
|
|
|
Assert.IsFalse(SkillStatus.HasAny(ally), "정화");
|
2026-11-05 15:22:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-10 10:00:18 +00:00
|
|
|
|
// ---------- 가상 실행(적 AI 예측) 복원 ----------
|
|
|
|
|
|
|
2026-11-05 15:22:54 +00:00
|
|
|
|
[UnityTest]
|
2026-11-10 10:00:18 +00:00
|
|
|
|
public IEnumerator Sim_RestoresSkillState()
|
2026-11-05 15:22:54 +00:00
|
|
|
|
{
|
|
|
|
|
|
yield return Fresh();
|
2026-11-10 10:00:18 +00:00
|
|
|
|
var jx = Spawn(UnitType.Strategist, Faction.Enemy, 0f, 3f, "가후");
|
|
|
|
|
|
Give(jx, "jiaxu");
|
|
|
|
|
|
var p = Spawn(UnitType.Infantry, Faction.Player, 0f, 0f, "아군");
|
|
|
|
|
|
Begin();
|
|
|
|
|
|
var snap = Sim.Take(bm);
|
|
|
|
|
|
Sim.Active = true;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var o = new ActionOutcome();
|
|
|
|
|
|
Sim.Run(bm.Resolver.Resolve(jx, p.Position, false, o));
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
Sim.Active = false;
|
|
|
|
|
|
Sim.Restore(bm, snap);
|
|
|
|
|
|
}
|
|
|
|
|
|
Assert.IsFalse(SkillStatus.HasAny(p), "가상 실행 중 걸린 중독이 남지 않음");
|
|
|
|
|
|
Assert.AreEqual(p.MaxHp, p.Hp);
|
2026-11-05 15:22:54 +00:00
|
|
|
|
}
|
2026-11-05 14:31:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|