using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using WL.Feel;
///
/// PD #797 — WLHitFeel 의 코루틴 호스트. WLHitFeel 이 런타임에 1 개만 만들고
/// HideFlags.HideAndDontSave + DontDestroyOnLoad 로 들고 있는다. 씬·프리팹을 건드리지 않는다.
///
/// 여기 있는 3 개 코루틴은 전부 **되돌리기 안전** 을 최우선으로 짰다.
/// · 히트스톱 : 우리가 넣은 값이 그대로 남아 있을 때만 원래 timeScale 로 복원한다.
/// (그 사이 일시정지·서버 팝업·보스 연출이 timeScale 을 바꿨으면 건드리지 않는다)
/// · 스케일 펀치: 우리가 쓴 스케일이 그대로 남아 있을 때만 원래 스케일로 복원한다.
/// (몹이 풀로 반환·재사용되면 복원을 포기한다 — MobActor.Set() 이 어차피 다시 세팅한다)
/// · 플래시 : flashEnabled 가 켜졌을 때만 오버레이 Canvas 를 만든다(드로우 +1). 꺼져 있으면 아무것도 안 만든다.
///
public class WLHitFeelRunner : MonoBehaviour
{
// ── ① 히트스톱 ─────────────────────────────────────────────────────────
bool _hitStopActive;
float _hitStopPrev = 1f;
float _hitStopTarget;
public bool HitStopActive { get { return _hitStopActive; } }
public void RunHitStop(WLHitFeelSettings st, float duration)
{
// 이미 진행 중이면 새로 걸지 않는다. 중간에 StopCoroutine 으로 끊으면
// timeScale 이 0 인 채로 영구히 남는 사고가 나므로 '겹치면 무시' 가 유일하게 안전하다.
if (_hitStopActive) return;
StartCoroutine(CoHitStop(st, duration));
}
IEnumerator CoHitStop(WLHitFeelSettings st, float duration)
{
float prev = Time.timeScale;
// 이미 멈춰 있거나(일시정지 0) 느려진 상태면 건드리지 않는다.
// Feel 의 MMF_FreezeFrame 도 같은 임계값(MinimumTimescaleThreshold 0.1)을 쓴다.
if (prev < st.hitStopMinTimescale) yield break;
if (st.hitStopMode == WLHitFeelSettings.eHitStopMode.FeelFreezeFrame)
{
// Feel 경로 — MMTimeManager 가 timeScale 을 소유하고 해제 시 NormalTimeScale 로 되돌린다.
EnsureMMTimeManager();
MoreMountains.Feedbacks.MMFreezeFrameEvent.Trigger(duration);
WLHitFeel.LastHitStopMeasured = duration;
yield break;
}
// WLSafe 경로 (기본)
_hitStopActive = true;
_hitStopPrev = prev;
_hitStopTarget = st.hitStopTimeScale;
Time.timeScale = _hitStopTarget;
float t0 = Time.realtimeSinceStartup;
yield return new WaitForSecondsRealtime(duration);
// 우리가 넣은 값이 그대로일 때만 복원한다.
if (Mathf.Approximately(Time.timeScale, _hitStopTarget))
{
Time.timeScale = _hitStopPrev;
WLHitFeel.LastHitStopMeasured = Time.realtimeSinceStartup - t0;
}
else
{
// 외부(일시정지·서버 팝업·TimeMgr·보스 연출)가 이미 바꿨다 — 덮어쓰지 않는다.
WLHitFeel.LastHitStopMeasured = -1f;
}
_hitStopActive = false;
}
static void EnsureMMTimeManager()
{
if (Object.FindAnyObjectByType() != null) return;
var go = new GameObject("MMTimeManager");
go.AddComponent();
Object.DontDestroyOnLoad(go);
}
// ── ③ 피격 몹 스케일 펀치 ───────────────────────────────────────────────
public void RunScalePunch(WLHitFeelSettings st, Actor victim)
{
if (victim == null) return;
StartCoroutine(CoScalePunch(st, victim));
}
IEnumerator CoScalePunch(WLHitFeelSettings st, Actor victim)
{
if (victim == null) yield break;
Transform tf = victim.transform;
Vector3 baseScale = tf.localScale;
float punch = Mathf.Max(1f, st.scalePunch);
if (punch <= 1.0001f || baseScale.x <= 0.0001f) yield break;
// ProjectileBase.cs:305/308 은 transform.localScale.x > 2f 로 **보스를 판정**하고
// Actor.Get_ScaleSizeDist() 도 같은 임계값을 쓴다.
// 펀치가 그 경계를 넘나들면 0.1 초 동안 보스 판정이 흔들리므로 아예 건너뛴다.
if (baseScale.x <= 2f && baseScale.x * punch > 2f) yield break;
float dur = Mathf.Max(0.01f, st.scalePunchDuration);
float t = 0f;
Vector3 written = baseScale;
while (t < dur)
{
t += Time.unscaledDeltaTime;
if (victim == null || !victim.isActiveAndEnabled) yield break; // 풀 반환 — 복원 포기
// 외부가 스케일을 바꿨다면(리스폰·버프) 우리 것을 덮어쓰지 않는다.
if ((tf.localScale - written).sqrMagnitude > 1e-6f) yield break;
float k = Mathf.Clamp01(t / dur);
float s = Mathf.Sin(k * Mathf.PI); // 0 → 1 → 0
written = baseScale * (1f + (punch - 1f) * s);
tf.localScale = written;
yield return null;
}
if (victim != null && victim.isActiveAndEnabled && (tf.localScale - written).sqrMagnitude <= 1e-6f)
tf.localScale = baseScale;
}
// ── ④ 화면 플래시 (기본 OFF) ────────────────────────────────────────────
Canvas _flashCanvas;
Image _flashImage;
bool _flashActive;
public void RunFlash(WLHitFeelSettings st)
{
if (_flashActive) return;
StartCoroutine(CoFlash(st));
}
IEnumerator CoFlash(WLHitFeelSettings st)
{
_flashActive = true;
EnsureFlashUI();
float dur = Mathf.Max(0.01f, st.flashDuration);
Color c = st.flashColor;
float peak = Mathf.Min(c.a, 0.12f); // 발주 규정 상한
float t = 0f;
_flashCanvas.enabled = true;
while (t < dur)
{
t += Time.unscaledDeltaTime;
float k = Mathf.Clamp01(t / dur);
c.a = peak * (1f - k); // 즉시 최대 → 선형 감쇠
_flashImage.color = c;
yield return null;
}
_flashCanvas.enabled = false; // 꺼 두면 드로우콜도 0
_flashActive = false;
}
void EnsureFlashUI()
{
if (_flashCanvas != null) return;
var go = new GameObject("__WLHitFeelFlash");
go.transform.SetParent(transform, false);
_flashCanvas = go.AddComponent