using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TKS
{
/// 유닛 1기의 스킬 상태(액티브 쿨·예약 · 패시브 · 행동 뒤에도 남는 효과).
public class UnitSkills
{
public Unit Unit;
public string HeroId = "";
/// 무장(영웅·보스·엘리트 장수) — 참장·파로(dmg_vs_general_mult) 대상 판정.
public bool IsGeneral;
public SkillDef Active;
public int ActiveLv = 1;
public List ActiveEffects;
public SkillDef Passive;
public int PassiveLv = 1;
public float PassiveMult = 1f;
public List PassiveEffects;
/// 액티브를 쓸 수 있는 첫 아군 턴(전투 시작 = 1 · 사용 턴 + 쿨).
public int ReadyTurn = 1;
/// next_action 예약(다음 슬링샷 1회) · 행동 전 다시 탭 = 취소.
public bool Reserved;
/// 예약한 효과가 지금 걸려 있다(그 유닛의 행동 처리·조준 미리보기 동안만).
public bool Armed;
/// 추가 이동: 지금 것 다음에 더 남은 횟수 · 추가 이동에서 공격 가능.
public int BonusMovesLeft;
public bool BonusAttack;
/// 대기(견제) 중 액티브로 얻은 반경 배율·가산·무제한(그 적 턴까지).
public float OwMult = 1f;
public float OwAdd;
public bool OwUnlimited;
/// 전투 중 관통한 적 누적(장료 팔백 돌격).
public int PierceHits;
public bool HasActive
{
get { return Active != null && ActiveEffects != null; }
}
public int CooldownLeft(int turn)
{
return Mathf.Max(0, ReadyTurn - turn);
}
}
/// 스킬 효과 1건이 어느 유닛·스킬에서 왔는가(SkillMods 질의 결과).
public struct SkillHit
{
public UnitSkills Owner;
public SkillDef Def;
public SkillEffect E;
public bool Passive;
}
///
/// 전투 1판의 스킬 처리(PD #1093 B · 시스템 기획 §5): 유닛별 스킬 상태 · 즉시형 액티브의 시한 효과(이번 아군 턴 / 이번 적 턴) ·
/// 행동 전후 훅(예약 효과 장착·쿨 소모) · 피해 적용(호위 분담) · HUD 한 줄 기록. 수치 질의는 SkillMods.
///
public class SkillSystem
{
public static SkillSystem Current;
public class Timed
{
public UnitSkills Owner;
public Faction Faction;
public SkillDef Def;
public SkillEffect E;
/// true = 이번 적 턴에만 걸림(다음 아군 턴 시작에 사라짐) · false = 이번 아군 턴(적 턴 시작에 사라짐).
public bool EnemyWindow;
/// 대상 고정(장판 일갈 = 밀어낸 적) · null = 키 규칙대로.
public HashSet Targets;
}
public struct LogLine
{
public string Text;
public float Time;
}
readonly BattleManager battle;
readonly Dictionary map = new Dictionary();
readonly List entries = new List();
public readonly List TimedEffects = new List();
public readonly List Log = new List();
readonly HashSet noted = new HashSet();
readonly int[] startCount = new int[2];
/// 장합 변진: 목표 지점 탭 대기 중인 유닛.
public Unit PendingTeleport { get; private set; }
public SkillSystem(BattleManager b)
{
battle = b;
}
public BattleManager Battle
{
get { return battle; }
}
public IReadOnlyList Entries
{
get { return entries; }
}
// ---------- 부착 ----------
public UnitSkills Of(Unit u)
{
if (u == null) return null;
UnitSkills s;
return map.TryGetValue(u, out s) ? s : null;
}
UnitSkills Ensure(Unit u)
{
var s = Of(u);
if (s != null) return s;
s = new UnitSkills { Unit = u };
map[u] = s;
entries.Add(s);
return s;
}
/// 스킬 부착(액티브·패시브 id · 레벨 · ★5 패시브 배율 · 무장 여부). 최대 HP 패시브(hp_max_pct)는 바로 반영.
public UnitSkills Attach(Unit u, string activeId, int activeLv, string passiveId, int passiveLv, float passiveMult, bool general)
{
if (u == null) return null;
var s = Ensure(u);
s.IsGeneral = general;
s.Active = SkillCatalog.Get(activeId);
s.ActiveLv = Mathf.Max(1, activeLv);
s.ActiveEffects = s.Active != null ? s.Active.ScaledEffects(s.ActiveLv, 1f) : null;
s.Passive = SkillCatalog.Get(passiveId);
s.PassiveLv = Mathf.Max(1, passiveLv);
s.PassiveMult = passiveMult > 0f ? passiveMult : 1f;
s.PassiveEffects = s.Passive != null ? s.Passive.ScaledEffects(s.PassiveLv, s.PassiveMult) : null;
if (s.Passive != null) s.HeroId = s.Passive.HeroId;
else if (s.Active != null) s.HeroId = s.Active.HeroId;
float hp = SkillMods.Sum(u, SkillKeys.HpMaxPct);
if (Mathf.Abs(hp) > 1e-4f)
{
u.MaxHp = Mathf.Max(1, Mathf.RoundToInt(u.MaxHp * (1f + hp)));
u.Hp = u.MaxHp;
if (u.View != null) u.View.Refresh();
}
return s;
}
/// heroes.csv 영웅 id 로 부착(테스트·도구용).
public UnitSkills AttachHero(Unit u, string heroId, int activeLv = 1, int passiveLv = 1, float passiveMult = 1f, bool general = true)
{
var h = SkillCatalog.Hero(heroId);
if (h == null) return null;
return Attach(u, h.skillActive, activeLv, h.skillPassive, passiveLv, passiveMult, general && !h.isSoldier);
}
/// 출전 칸(편성 → 스킬 id·레벨·★ 배율이 채워져 있음).
public UnitSkills AttachLineup(Unit u, LineupEntry e)
{
if (e == null) return null;
if (string.IsNullOrEmpty(e.skillActive) && string.IsNullOrEmpty(e.skillPassive))
{
if (e.general) Ensure(u).IsGeneral = true;
return Of(u);
}
return Attach(u, e.skillActive, e.activeLv, e.skillPassive, e.passiveLv, e.passiveMult, e.general);
}
/// 적 무장(보스·엘리트) = heroes.csv 에 같은 아트·이름 영웅이 있으면 그 패시브만(Lv1 · 액티브 AI 사용은 2차 · PD 확인 대상).
public UnitSkills AttachEnemy(Unit u, BalanceConfig.EnemySpawn e)
{
if (u == null || e == null || !(e.isBoss || e.isGeneral)) return null;
var h = SkillCatalog.HeroForEnemy(e.art, e.name);
if (h == null)
{
var s = Ensure(u);
s.IsGeneral = true;
return s;
}
return Attach(u, null, 1, h.skillPassive, 1, 1f, true);
}
public void MarkGeneral(Unit u)
{
if (u != null) Ensure(u).IsGeneral = true;
}
public bool IsGeneral(Unit u)
{
if (u == null) return false;
if (u.IsBoss) return true;
var s = Of(u);
return s != null && s.IsGeneral;
}
public void Clear()
{
map.Clear();
entries.Clear();
TimedEffects.Clear();
noted.Clear();
PendingTeleport = null;
startCount[0] = startCount[1] = 0;
}
/// 전투 시작(배치 뒤): 출전 인원 기록(패자 · team_atk_pct_full) · 쿨 = 바로 사용 가능 · 시한 효과 비움.
public void BeginBattle()
{
Prune();
TimedEffects.Clear();
noted.Clear();
PendingTeleport = null;
startCount[0] = battle.AliveCount(Faction.Player);
startCount[1] = battle.AliveCount(Faction.Enemy);
foreach (var s in entries)
{
s.ReadyTurn = 1;
s.Reserved = false;
s.Armed = false;
s.BonusMovesLeft = 0;
s.BonusAttack = false;
s.OwMult = 1f;
s.OwAdd = 0f;
s.OwUnlimited = false;
s.PierceHits = 0;
}
}
public int StartCount(Faction f)
{
return startCount[(int)f];
}
void Prune()
{
for (int i = entries.Count - 1; i >= 0; i--)
{
if (entries[i].Unit != null) continue;
entries.RemoveAt(i);
}
var dead = new List();
foreach (var kv in map)
if (kv.Key == null) dead.Add(kv.Key);
foreach (var k in dead) map.Remove(k);
}
// ---------- 턴 ----------
/// 적 턴 시작: 이번 아군 턴 효과(동남풍 · 고육계)와 쓰지 않은 예약·추가 이동 정리.
public void OnEnemyTurnStart()
{
TimedEffects.RemoveAll(t => !t.EnemyWindow);
PendingTeleport = null;
foreach (var s in entries)
{
s.Reserved = false;
s.Armed = false;
s.BonusMovesLeft = 0;
s.BonusAttack = false;
}
}
/// 아군 턴 시작: 이번 적 턴 효과(호위 · 철벽 · 간웅 · 일갈 · 안개)와 아군 견제 강화 정리.
public void OnPlayerTurnStart()
{
TimedEffects.RemoveAll(t => t.EnemyWindow);
foreach (var s in entries)
{
if (s.Unit == null || s.Unit.Faction != Faction.Player) continue;
s.OwMult = 1f;
s.OwAdd = 0f;
s.OwUnlimited = false;
}
}
public bool WindowActive(Timed t)
{
return !t.EnemyWindow || battle.Phase == BattlePhase.EnemyTurn;
}
// ---------- 액티브: 상태 · 탭 ----------
public enum ActiveState
{
None,
Ready,
Reserved,
Cooldown,
Acted,
BonusMove,
NotTurn,
Targeting
}
public ActiveState StateOf(Unit u)
{
var s = Of(u);
if (u == null || s == null || !s.HasActive || u.Faction != Faction.Player || !u.IsAlive) return ActiveState.None;
if (PendingTeleport == u) return ActiveState.Targeting;
if (s.Reserved) return ActiveState.Reserved;
if (battle.Phase != BattlePhase.PlayerTurn) return ActiveState.NotTurn;
if (s.CooldownLeft(battle.TurnNumber) > 0) return ActiveState.Cooldown;
if (s.Active.Trigger == SkillTrigger.NextAction)
{
if (u.BonusMoveAvailable) return ActiveState.BonusMove;
if (!battle.CanAct(u)) return ActiveState.Acted;
}
return ActiveState.Ready;
}
/// HUD 스킬 버튼 탭: next_action = 예약/취소 · instant = 즉시 발동(변진 = 목표 지점 탭 대기). 반환 = 안내 문구.
public string TapActive(Unit u)
{
var s = Of(u);
var st = StateOf(u);
switch (st)
{
case ActiveState.None: return null;
case ActiveState.Targeting:
PendingTeleport = null;
return s.Active.NameKo + " 취소";
case ActiveState.Reserved:
s.Reserved = false;
if (u.View != null) u.View.Refresh();
return s.Active.NameKo + " 예약 취소(쿨 소모 없음)";
case ActiveState.NotTurn: return "아군 턴에만 쓸 수 있습니다";
case ActiveState.Cooldown: return s.Active.NameKo + " — 쿨 " + s.CooldownLeft(battle.TurnNumber) + "턴";
case ActiveState.Acted: return s.Active.NameKo + " — 이미 행동한 유닛";
case ActiveState.BonusMove: return s.Active.NameKo + " — 추가 이동 중에는 예약할 수 없음";
}
if (s.Active.Trigger == SkillTrigger.NextAction)
{
s.Reserved = true;
if (u.View != null) u.View.Refresh();
return s.Active.NameKo + " 예약 — 다음 슬링샷에 발동 · 다시 탭 = 취소";
}
if (s.Active.First(SkillKeys.Teleport) != null)
{
PendingTeleport = u;
return s.Active.NameKo + " — 반경 " + TeleportRadius(s).ToString("0.#") + " 안 빈 곳을 탭(다른 곳 = 취소)";
}
battle.RunSkillRoutine(Instant(s));
return s.Active.NameKo + " 발동!";
}
// ---------- 행동 훅(ActionResolver.Resolve) ----------
/// 행동 시작: 예약한 next_action 효과를 장착(이동만인 추가 이동은 제외) · 발동 연출.
public void BeginAction(Unit u, bool moveOnly, ActionOutcome o)
{
noted.Clear();
var s = Of(u);
if (s == null || !s.Reserved || !s.HasActive || moveOnly) return;
s.Armed = true;
o.SkillUsed = s.Active;
Announce(s, s.Active);
}
/// 행동 끝: 예약 소모 · 쿨(아군 턴 기준) · 견제 강화 이월.
public void EndAction(Unit u, ActionOutcome o)
{
var s = Of(u);
if (s == null || !s.Armed) return;
if (o.Overwatch)
{
foreach (var e in s.ActiveEffects)
{
if (e.Key == SkillKeys.OverwatchRadiusMult) s.OwMult *= e.P;
else if (e.Key == SkillKeys.OverwatchRadiusAdd) s.OwAdd += e.P;
else if (e.Key == SkillKeys.OverwatchUnlimited) s.OwUnlimited = true;
}
}
s.Armed = false;
s.Reserved = false;
s.ReadyTurn = battle.TurnNumber + s.Active.Cooldown;
if (u.View != null) u.View.Refresh();
}
/// 조준 미리보기 동안만 예약 효과를 장착(사거리 · 사선 · 광역 표시). 반환 = 복원값.
public bool PreviewBegin(Unit u)
{
var s = Of(u);
if (s == null) return false;
bool prev = s.Armed;
if (s.Reserved && !u.BonusMoveAvailable) s.Armed = true;
return prev;
}
public void PreviewEnd(Unit u, bool prev)
{
var s = Of(u);
if (s != null) s.Armed = prev;
}
// ---------- 추가 이동 ----------
public bool BonusCanAttack(Unit u)
{
var s = Of(u);
return s != null && s.BonusAttack;
}
/// 행동 뒤 추가 이동을 (또) 주는가 — 처음 명중 = 추가 이동 n 회(인덕) · 추가 이동 뒤 = 남은 횟수.
public bool NextBonus(Unit u, bool wasBonus, ActionOutcome o)
{
var s = Of(u);
if (!wasBonus)
{
if (!o.BonusMove) return false;
if (s != null)
{
s.BonusMovesLeft = Mathf.Max(0, o.BonusMoves - 1);
s.BonusAttack = o.BonusAttack;
}
return true;
}
if (s != null && s.BonusMovesLeft > 0)
{
s.BonusMovesLeft--;
return true;
}
ClearBonus(u);
return false;
}
public void ClearBonus(Unit u)
{
var s = Of(u);
if (s == null) return;
s.BonusMovesLeft = 0;
s.BonusAttack = false;
}
// ---------- 적 턴: 교전 반응 · 교전 범위 ----------
/// 이번 적 턴에 교전 반응이 없는가(장판 일갈 공포 · 태평요술 안개).
public bool Suppressed(Unit e)
{
if (e == null) return false;
foreach (var t in TimedEffects)
{
if (!WindowActive(t)) continue;
if (t.E.Key == SkillKeys.AoePushFear && t.Targets != null && t.Targets.Contains(e)) return true;
if (t.E.Key == SkillKeys.EnemyAggroOffTurn && t.Faction != e.Faction) return true;
}
return false;
}
/// 상대 진영 패시브(신산)가 actingFaction 의 교전 범위에 더하는 값.
public float AggroAdd(Faction actingFaction)
{
float add = 0f;
foreach (var s in entries)
{
if (s.Unit == null || !s.Unit.IsAlive || s.Unit.Faction == actingFaction || s.PassiveEffects == null) continue;
foreach (var e in s.PassiveEffects)
{
if (e.Key != SkillKeys.EnemyAggroAdd) continue;
add += e.P;
NotePassive(s, s.Passive);
}
}
return add;
}
// ---------- 피해 적용(호위) ----------
/// 피해 적용 — 관평 호위(이번 적 턴 반경 안 같은 편 피해 일부를 관평이 대신) 반영. 반환 = d 가 실제 받은 피해.
public int Hit(Unit d, int dmg)
{
if (d == null || !d.IsAlive || dmg <= 0) return 0;
foreach (var t in TimedEffects)
{
if (t.E.Key != SkillKeys.GuardShare || !WindowActive(t)) continue;
var g = t.Owner.Unit;
if (g == null || !g.IsAlive || g == d || g.Faction != d.Faction) continue;
float r = t.E.HasA2 ? t.E.A2 : 0f;
if (Vector2.Distance(g.Position, d.Position) > r + d.Radius) continue;
int share = Mathf.Clamp(Mathf.RoundToInt(dmg * Mathf.Clamp01(t.E.P)), 0, dmg);
if (share <= 0) continue;
g.TakeDamage(share);
Fx.Popup(g.Position, share + " 호위", SkillFx.GuardColor);
dmg -= share;
break;
}
d.TakeDamage(dmg);
return dmg;
}
/// 스킬 시스템이 없어도 쓰는 피해 적용.
public static int Deal(Unit d, int dmg)
{
if (Current != null) return Current.Hit(d, dmg);
if (d == null) return 0;
d.TakeDamage(dmg);
return dmg;
}
// ---------- 즉시형 액티브 ----------
/// 즉시 발동(행동 소모 없음): 효과마다 처리 → 쿨.
public IEnumerator Instant(UnitSkills s)
{
var u = s.Unit;
Announce(s, s.Active);
s.ReadyTurn = battle.TurnNumber + s.Active.Cooldown;
foreach (var e in s.ActiveEffects)
{
switch (e.Key)
{
case SkillKeys.AoePushFear:
yield return PushFear(s, e);
break;
case SkillKeys.HpCostAtk:
PayHp(u, e.HasA2 ? e.A2 : 0f);
AddTimed(s, e, false, null);
break;
case SkillKeys.GuardShare:
case SkillKeys.TeamDmgTakenPctTurn:
case SkillKeys.DmgTakenAura:
case SkillKeys.EnemyAggroOffTurn:
AddTimed(s, e, true, null);
break;
case SkillKeys.Teleport:
break; // 목표 지점 탭(TryTeleport)
default:
AddTimed(s, e, false, null); // 동남풍(sling_dist_pct) 등 = 이번 아군 턴
break;
}
}
yield return Fx.Wait(0.25f);
battle.NotifyChanged();
}
void AddTimed(UnitSkills s, SkillEffect e, bool enemyWindow, HashSet targets)
{
TimedEffects.Add(new Timed { Owner = s, Faction = s.Unit.Faction, Def = s.Active, E = e, EnemyWindow = enemyWindow, Targets = targets });
}
static void PayHp(Unit u, float ratio)
{
int cost = Mathf.RoundToInt(u.MaxHp * Mathf.Max(0f, ratio));
if (cost <= 0) return;
u.Hp = Mathf.Max(1, u.Hp - cost); // 스스로 쓰러지지는 않는다
if (u.View != null)
{
u.View.OnHit();
u.View.Refresh();
}
Fx.Popup(u.Position, "HP −" + cost, SkillFx.CostColor);
}
/// 장판 일갈: 반경(p2) 안 상대 전원을 바깥으로 p1 밀고 이번 적 턴 교전 반응 없음.
IEnumerator PushFear(UnitSkills s, SkillEffect e)
{
var u = s.Unit;
float radius = e.HasA2 ? e.A2 : 0f;
var targets = new HashSet();
int running = 0;
Faction other = u.Faction == Faction.Player ? Faction.Enemy : Faction.Player;
foreach (var t in battle.AliveUnits(other))
{
if (Vector2.Distance(u.Position, t.Position) > radius + t.Radius) continue;
targets.Add(t);
Vector2 dir = t.Position - u.Position;
dir = dir.sqrMagnitude > 1e-6f ? dir.normalized : Vector2.up;
float force = e.P * SkillMods.KnockbackTakenMult(t, true);
float slide = Combat.FreeTravel(t, t.Position, dir, force, true, true, u);
if (slide <= 0.01f) continue;
Vector2 end = battle.Board.ClampInside(t.Position + dir * slide, t.Radius);
running++;
var unit = t;
if (unit.View != null) unit.View.SetFacing(dir);
Fx.Run(Track(Fx.MoveAlong(unit.transform, unit.Position, end, battle.Config.moveSpeed, null), () =>
{
unit.Position = end;
running--;
}));
}
Fx.Burst(u.Position, radius, SkillFx.FearColor);
while (running > 0) yield return null;
var t2 = new Timed { Owner = s, Faction = u.Faction, Def = s.Active, E = e, EnemyWindow = true, Targets = targets };
TimedEffects.Add(t2);
battle.NotifyChanged();
}
static IEnumerator Track(IEnumerator inner, System.Action onDone)
{
yield return inner;
onDone();
}
// ---------- 변진(장합): 목표 지점 탭 ----------
public static float TeleportRadius(UnitSkills s)
{
if (s == null || s.ActiveEffects == null) return 0f;
foreach (var e in s.ActiveEffects)
if (e.Key == SkillKeys.Teleport) return e.P;
return 0f;
}
/// 목표 지점이 반경 안 빈 곳인가(벽·유닛과 겹치지 않음 · 전장 안).
public bool TeleportValid(Unit u, Vector2 w)
{
var s = Of(u);
if (s == null) return false;
if (Vector2.Distance(u.Position, w) > TeleportRadius(s) + 1e-3f) return false;
if ((battle.Board.ClampInside(w, u.Radius) - w).sqrMagnitude > 1e-4f) return false;
Physics2D.SyncTransforms();
foreach (var c in Physics2D.OverlapCircleAll(w, u.Radius))
{
if (c.GetComponent() != null) return false;
var o = c.GetComponent();
if (o != null && o != u && o.IsAlive) return false;
}
return true;
}
/// 변진 대기 중 화면 탭 → 처리했으면 true(유효 = 이동 · 아니면 취소).
public bool TryTeleportTap(Vector2 w, out string message)
{
message = null;
var u = PendingTeleport;
if (u == null) return false;
PendingTeleport = null;
var s = Of(u);
if (s == null || !u.IsAlive || battle.Phase != BattlePhase.PlayerTurn) return true;
if (!TeleportValid(u, w))
{
message = s.Active.NameKo + " 취소 — 반경 안 빈 곳이 아님";
return true;
}
battle.RunSkillRoutine(TeleportRoutine(s, w));
message = s.Active.NameKo + " 발동!";
return true;
}
public IEnumerator TeleportRoutine(UnitSkills s, Vector2 w)
{
var u = s.Unit;
Announce(s, s.Active);
s.ReadyTurn = battle.TurnNumber + s.Active.Cooldown;
if (u.View != null) u.View.SetFacing(w - u.Position);
yield return Fx.MoveAlong(u.transform, u.Position, w, battle.Config.moveSpeed * 2f, null);
u.Position = w;
battle.NotifyChanged();
}
// ---------- 연출 · HUD 기록 ----------
/// 액티브 발동 = 유닛 위 스킬 이름 + 플래시 + HUD 한 줄.
public void Announce(UnitSkills s, SkillDef def)
{
if (s == null || def == null) return;
if (s.Unit != null) SkillFx.Announce(s.Unit, def.NameKo);
AddLog("액티브 · " + (s.Unit != null ? s.Unit.DisplayName : "") + " 「" + def.NameKo + "」");
}
/// 패시브 발동 HUD 한 줄(같은 행동 안에서는 스킬마다 1번).
public void NotePassive(UnitSkills s, SkillDef def)
{
if (s == null || def == null || s.Unit == null) return;
string key = s.Unit.GetInstanceID() + "/" + def.Id;
if (!noted.Add(key)) return;
AddLog("패시브 · " + s.Unit.DisplayName + " 「" + def.NameKo + "」");
}
public void AddLog(string text)
{
Log.Add(new LogLine { Text = text, Time = Time.unscaledTime });
if (Log.Count > 8) Log.RemoveAt(0);
}
/// 최근 seconds 초 안의 기록(최신이 마지막).
public List RecentLog(float seconds, int max)
{
var res = new List();
float now = Time.unscaledTime;
for (int i = Log.Count - 1; i >= 0 && res.Count < max; i--)
if (now - Log[i].Time <= seconds) res.Insert(0, Log[i].Text);
return res;
}
}
/// 스킬 연출(이름 글자 · 플래시).
public static class SkillFx
{
public static readonly Color NameColor = new Color(1f, 0.85f, 0.3f);
public static readonly Color FlashColor = new Color(1f, 0.9f, 0.45f, 0.55f);
public static readonly Color GuardColor = new Color(0.55f, 0.85f, 1f);
public static readonly Color FearColor = new Color(1f, 0.4f, 0.25f, 0.4f);
public static readonly Color CostColor = new Color(1f, 0.55f, 0.35f);
public static readonly Color HealColor = new Color(0.45f, 1f, 0.5f);
public static void Announce(Unit u, string name)
{
Fx.Burst(u.Position, u.Radius * 2.2f, FlashColor);
Fx.Run(NameRoutine(u.Position + new Vector2(0f, u.ArtHeight > 0f ? u.ArtHeight * 0.6f : u.Radius + 0.6f), name));
}
static IEnumerator NameRoutine(Vector2 pos, string text)
{
var tm = TextUtil.CreateWorldText(null, text, 0.55f, NameColor, 21);
tm.gameObject.name = "SkillName";
var tr = tm.transform;
float t = 0f;
const float dur = 1.1f;
while (t < dur)
{
t += Time.deltaTime;
float k = Mathf.Clamp01(t / dur);
tr.position = new Vector3(pos.x, pos.y + 0.4f * k, 0f);
var c = NameColor;
c.a = k < 0.7f ? 1f : 1f - (k - 0.7f) / 0.3f;
tm.color = c;
yield return null;
}
Object.Destroy(tm.gameObject);
}
}
}