255 lines
14 KiB
C#
255 lines
14 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLPowerDeltaSettings.cs — 성장 체감 표시(전투력 % 토스트 · 팝업 전투력 줄 · 처치 타수 비교) 값의 단일 출처
|
||
// (WL-813l · PD 지시 #813)
|
||
//
|
||
// 기준서 v1 §D-2 813l 행 + §E 5-3 「강화 후 체감(전투력 % + 처치 타수 변화) = 표시 + 타수 −1 로그」.
|
||
// 발주서 WL-813l §1(Lead 설계 결정) · 구독 계약 = 813k `GrowthEvents.LevelUp`(완료보고 §3) ·
|
||
// 811b `CombatEvents`(AttackStarted/HitConfirmed/Killed) · 813p `RunEvents.ZoneCleared`(완료보고 §4).
|
||
//
|
||
// ■ 🔴 전투력 산식은 「추정」이다
|
||
// 원본(`Assets/Script/**`)에는 전투력·CP 개념이 **없다**(실측: `combatpower|battlepower|전투력` 전수 검색 0건).
|
||
// 그래서 발주서 §1-1 대안인 WL 산식을 쓴다:
|
||
// power = ATK × (1 + 치명 기여) × (1 + 공속 기여) + MaxHP × hpWeight
|
||
// 구성 스탯도 계수도 전부 이 에셋 값이다 — 코드 상수 0. 기획이 바꾸면 표시가 따라 바뀐다.
|
||
//
|
||
// ■ 값 읽기는 원본 API 만 쓴다 — `Actor.Get_StatInfo()` → `ActorStatInfo.Get_Stat(eStat)`. 원본 훅 0 · 쓰기 0.
|
||
//
|
||
// ■ 좌표계 (px)
|
||
// 813c·813g·813q 와 같은 **세로 1080×1920 기준 화면 픽셀**. px → 캔버스 유닛 환산은
|
||
// `WLCombatTextSettings.UnitsPerPx(canvas)` **하나만** 쓴다(산식 중복 0 · 813tj 선례).
|
||
//
|
||
// ■ C8 롤백
|
||
// `enabled_ = 0` → 구독 0 · 폴링 0 · 노드 0 · 표시 0. 팝업 전투력 줄도 붙지 않는다.
|
||
// 개별 스위치(`toastEnabled` · `statPopupLineEnabled` · `hitsPerKillEnabled`)로 셋을 따로 끌 수도 있다.
|
||
//
|
||
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using UnityEngine;
|
||
|
||
namespace WL.UI
|
||
{
|
||
/// <summary>「몇 방에 죽었나」를 세는 분자(타수)의 출처.</summary>
|
||
public enum WLHitSource
|
||
{
|
||
/// <summary>메인 PC 가 **실제로 적중시킨** 타격 수(`CombatEvents.HitConfirmed.byMainPC`).
|
||
/// 광역기로 여러 마리를 때리면 마리 수만큼 센다 = 「한 마리를 몇 방에」에 가장 가깝다.</summary>
|
||
HitConfirmed = 0,
|
||
/// <summary>메인 PC 의 **공격 모션 시작** 수(`CombatEvents.AttackStarted.isMainPC`).
|
||
/// 기준서 §E 5-3 의 검증 방법(`AttackStarted`/`Killed` 비)과 같은 분자다.</summary>
|
||
AttackStarted = 1,
|
||
}
|
||
|
||
[CreateAssetMenu(fileName = "WLPowerDeltaSettings", menuName = "WL/Power Delta Settings", order = 34)]
|
||
public sealed class WLPowerDeltaSettings : ScriptableObject
|
||
{
|
||
/// <summary>Resources 경로 — 런타임 로드 키.</summary>
|
||
public const string ResourcesPath = "WL/WLPowerDeltaSettings";
|
||
|
||
private static WLPowerDeltaSettings s_instance;
|
||
private static bool s_tried;
|
||
|
||
/// <summary>에셋이 없으면 null(호출부는 null 이면 전부 건너뛴다 = C8 롤백).</summary>
|
||
public static WLPowerDeltaSettings Instance
|
||
{
|
||
get
|
||
{
|
||
if (s_instance == null && !s_tried)
|
||
{
|
||
s_tried = true;
|
||
s_instance = Resources.Load<WLPowerDeltaSettings>(ResourcesPath);
|
||
}
|
||
return s_instance;
|
||
}
|
||
}
|
||
|
||
/// <summary>에디터에서 에셋을 새로 만들거나 값을 바꾼 뒤 캐시를 버린다.</summary>
|
||
public static void ClearCache() { s_instance = null; s_tried = false; }
|
||
|
||
/// <summary>프로브·QA 용 런타임 스위치(에셋을 건드리지 않고 끈다).</summary>
|
||
public static bool RuntimeDisabled;
|
||
|
||
/// <summary>성장 체감 축이 살아 있는가 = 에셋 존재 · enabled_ · 런타임 스위치 off.</summary>
|
||
public static bool Enabled
|
||
{
|
||
get
|
||
{
|
||
if (RuntimeDisabled) return false;
|
||
var s = Instance;
|
||
return s != null && s.enabled_;
|
||
}
|
||
}
|
||
|
||
// ── 공통 ──────────────────────────────────────────────────────────────
|
||
[Header("공통 (C8 롤백)")]
|
||
[Tooltip("끄면 전투력 토스트·팝업 전투력 줄·타수 비교가 전부 무동작(구독 0 · 노드 0 · 표시 0).")]
|
||
// ScriptableObject 에는 `enabled` 멤버가 없지만(그건 Behaviour) 혼동을 피해 뒤에 _ 를 붙인다 —
|
||
// 813q WLRunUiSettings 와 같은 규약. 인스펙터·에셋 YAML 에는 `enabled_` 로 나온다.
|
||
public bool enabled_ = true;
|
||
|
||
[Tooltip("콘솔 로그(전투력 변화 · 타수 창 마감). QA·프로브 때만 켠다.")]
|
||
public bool verboseLog = false;
|
||
|
||
// ── 전투력 산식 (🔴 원본에 CP 없음 = 「추정」 · 발주서 §1-1 대안) ──────
|
||
[Header("전투력 산식 — 원본에 CP 개념이 없어 WL 산식을 쓴다(「추정」)")]
|
||
[Tooltip("공격력 스탯. 기본 BaseDmg = 원본이 ATK × ATK_Multiplier × DMG_Multiplier 로 만든 최종 공격력.")]
|
||
public eStat atkStat = eStat.BaseDmg;
|
||
|
||
[Tooltip("위 값이 0 일 때 쓰는 대체 공격력 스탯(전투 밖·로비에서 BaseDmg 가 안 채워진 경우).")]
|
||
public eStat atkFallbackStat = eStat.ATK;
|
||
|
||
[Tooltip("치명타 확률 스탯. 원본 런타임 값은 0~1 비율이다(실측: DSUtil.RandomTrue(p) 가 Random.Range(0,1) 와 비교).")]
|
||
public eStat critRateStat = eStat.CRI;
|
||
|
||
[Tooltip("치명타 피해 증가량 스탯(0~1 비율). useCritDamage 가 켜져 있을 때만 읽는다.")]
|
||
public eStat critDamageStat = eStat.CRI_DMG_Multiplier;
|
||
|
||
[Tooltip("켜면 치명 기여 = 확률 × 피해증가(기대값). 끄면 확률만 쓴다(발주서 §1-1 문자 그대로의 (1 + CRIT%)).")]
|
||
public bool useCritDamage = true;
|
||
|
||
[Tooltip("치명 기여에 곱하는 가중치. 0 이면 치명을 무시한다.")]
|
||
public float critWeight = 1f;
|
||
|
||
[Tooltip("공격 속도 배율 스탯. 원본 기본값은 1 이다(실측: MyValue.cs Set_Stat(ATK_SPD_Multiplier, 1) · FinalAttackSpeed = HERO_ATK_SPD × (배율 − 1)).")]
|
||
public eStat attackSpeedStat = eStat.ATK_SPD_Multiplier;
|
||
|
||
[Tooltip("공속 배율의 기준값(이 값을 빼서 증가분을 만든다). 원본 실측 기본값 1.")]
|
||
public float attackSpeedBase = 1f;
|
||
|
||
[Tooltip("공속 기여 가중치. 0 이면 공속을 무시한다.")]
|
||
public float attackSpeedWeight = 1f;
|
||
|
||
[Tooltip("체력 스탯(최종 최대 체력).")]
|
||
public eStat hpStat = eStat.MaxHP;
|
||
|
||
[Tooltip("체력 계수. 발주서 §1-1 = 0.1.")]
|
||
public float hpWeight = 0.1f;
|
||
|
||
[Tooltip("최종 전투력에 곱하는 표시 배율(자릿수 조정용). 1 = 산식 그대로.")]
|
||
public float powerScale = 1f;
|
||
|
||
// ── 전투력 % 토스트 ───────────────────────────────────────────────────
|
||
[Header("전투력 % 토스트")]
|
||
[Tooltip("끄면 토스트만 표시 안 함(팝업 줄·타수 비교는 그대로).")]
|
||
public bool toastEnabled = true;
|
||
|
||
[Tooltip("토스트를 붙일 HUD 경로(NewGameUI 루트 기준). 813q RunHud 와 같은 부모.")]
|
||
public string hudParentPath = "IngameUIs/WL_HUD";
|
||
|
||
[Tooltip("화면 중심에서의 세로 오프셋(px · +위). 실측 대역: 스탯 팝업(+180 · 전투력 줄 포함 시 위 끝 +328) 과 연쇄 처치(+520 · 아래 끝 +472) 사이의 빈 구간에 앉힌다.")]
|
||
public float toastCenterOffsetPx = 430f;
|
||
|
||
[Tooltip("줄 수(전투력 줄 + 타수 비교 줄이 같이 뜰 수 있다).")]
|
||
[Range(1, 4)] public int toastLines = 2;
|
||
|
||
[Tooltip("줄 간격(px).")]
|
||
public float toastLineHeightPx = 60f;
|
||
|
||
[Tooltip("글자 크기(px).")]
|
||
public float toastFontPx = 52f;
|
||
|
||
[Tooltip("표시 시간(초). 발주서 §1-2 = 2.0.")]
|
||
public float toastSeconds = 2f;
|
||
|
||
[Tooltip("이 비율이 지난 뒤부터 사라진다(0.6 = 마지막 40%).")]
|
||
[Range(0f, 1f)] public float toastFadeStartRatio = 0.6f;
|
||
|
||
[Tooltip("등장 펀치 배율(1 = 없음).")]
|
||
public float toastPunchScale = 1.25f;
|
||
|
||
[Tooltip("펀치가 1 로 수렴하는 시간(초).")]
|
||
public float toastPunchSeconds = 0.18f;
|
||
|
||
[Tooltip("전투력이 오를 때 색.")]
|
||
public Color powerUpColor = new Color(1f, 0.84f, 0.35f, 1f);
|
||
|
||
[Tooltip("전투력이 내릴 때 색(showOnPowerDown 이 켜져 있을 때만 쓴다).")]
|
||
public Color powerDownColor = new Color(0.72f, 0.78f, 0.88f, 1f);
|
||
|
||
[Tooltip("타수 비교 줄 색.")]
|
||
public Color hitsCompareColor = new Color(0.62f, 1f, 0.78f, 1f);
|
||
|
||
[Tooltip("전투력 상승 문구. {0} = 퍼센트.")]
|
||
public string powerUpFormat = "전투력 +{0}%";
|
||
|
||
[Tooltip("전투력 하락 문구. {0} = 퍼센트(부호 없는 절대값).")]
|
||
public string powerDownFormat = "전투력 -{0}%";
|
||
|
||
[Tooltip("퍼센트 소수 자릿수.")]
|
||
[Range(0, 3)] public int percentDigits = 1;
|
||
|
||
[Tooltip("이 퍼센트 미만의 변화는 띄우지 않는다(반올림 잡음 차단).")]
|
||
public float minPercentToShow = 0.5f;
|
||
|
||
[Tooltip("전투력이 내려갈 때도 띄울지(장비 해제 등). 기본 off = 「성장 체감」만 보여준다.")]
|
||
public bool showOnPowerDown = false;
|
||
|
||
[Tooltip("전투력 값 변화를 확인하는 주기(초). 원본에 장비·강화 변경 이벤트가 없어 폴링한다(실측: Assets/Script 훅 0).")]
|
||
public float pollIntervalSeconds = 0.5f;
|
||
|
||
[Tooltip("직전 토스트 뒤 이 시간 안의 변화는 한 줄로 합친다(강화 연타 대비).")]
|
||
public float announceCooldownSeconds = 1f;
|
||
|
||
[Tooltip("레벨업 프레임에는 토스트를 생략한다 — 같은 값을 스탯 팝업의 전투력 줄이 이미 보여 준다(중복 표시 0).")]
|
||
public bool suppressToastOnLevelUp = true;
|
||
|
||
[Tooltip("토스트 테두리(813y WLTextEdge 규약 · 밝은 배경 대비). enabled=0 이면 원본 머티리얼 그대로.")]
|
||
public WLTextEdge toastEdge = new WLTextEdge
|
||
{
|
||
enabled = true,
|
||
outlineWidth = 0.26f,
|
||
outlineColor = new Color(0.03f, 0.03f, 0.06f, 1f),
|
||
shadowOffset = new Vector2(0.55f, -0.55f),
|
||
shadowColor = new Color(0f, 0f, 0f, 0f), // 알파 0 = 그림자 없음(813y 규약)
|
||
shadowSoftness = 0.2f,
|
||
faceDilate = 0f,
|
||
};
|
||
|
||
// ── 레벨업 팝업 전투력 줄 (813y StatPopup) ───────────────────────────
|
||
[Header("레벨업 팝업 전투력 줄 (813y StatPopup 에 1줄)")]
|
||
[Tooltip("끄면 팝업에 전투력 줄이 붙지 않는다(팝업 자체는 813y 그대로).")]
|
||
public bool statPopupLineEnabled = true;
|
||
|
||
[Tooltip("변화가 있을 때 문구. {0} = 이전 전투력 · {1} = 새 전투력 · {2} = 퍼센트(부호 포함).")]
|
||
public string statPopupLineFormat = "전투력 {0} → {1} ({2}%)";
|
||
|
||
[Tooltip("변화가 없을 때 문구(원본은 레벨업으로 스탯이 바로 오르지 않는다 — 813k 보고 ①). {0} = 현재 전투력.")]
|
||
public string statPopupLineFormatNoChange = "전투력 {0}";
|
||
|
||
[Tooltip("전투력 줄 글자 크기(px).")]
|
||
public float statPopupLineFontPx = 44f;
|
||
|
||
[Tooltip("전투력 줄 색.")]
|
||
public Color statPopupLineColor = new Color(1f, 0.84f, 0.35f, 1f);
|
||
|
||
// ── 처치 타수 비교 ────────────────────────────────────────────────────
|
||
[Header("처치 타수 비교 (기준서 §E 5-3 「타수 −1」)")]
|
||
[Tooltip("끄면 타수 계측·비교 토스트가 전부 무동작.")]
|
||
public bool hitsPerKillEnabled = true;
|
||
|
||
[Tooltip("타수의 분자. HitConfirmed = 실제 적중 수(권장) · AttackStarted = 기준서 §E 5-3 검증 방법과 동일.")]
|
||
public WLHitSource hitsSource = WLHitSource.HitConfirmed;
|
||
|
||
[Tooltip("한 창(window)의 길이(초). 발주서 §1-4 = 60.")]
|
||
public float windowSeconds = 60f;
|
||
|
||
[Tooltip("이 처치 수를 못 채운 창은 버린다(표본 부족).")]
|
||
public int minKillsPerWindow = 5;
|
||
|
||
[Tooltip("비교 문구. {0} = 이전 타수 · {1} = 새 타수.")]
|
||
public string hitsCompareFormat = "이제 {0}방 → {1}방";
|
||
|
||
[Tooltip("켜면 다음 존 클리어(813p RunEvents.ZoneCleared) 때 띄운다 — 발주서 §1-4.")]
|
||
public bool showCompareOnZoneCleared = true;
|
||
|
||
[Tooltip("런 축이 안 돌 때(ZoneCleared 가 안 옴) 무장 즉시 띄울지.")]
|
||
public bool showCompareImmediatelyIfNoRun = true;
|
||
|
||
[Tooltip("무장한 비교 문구가 이 시간 안에 표시되지 않으면 버린다(초).")]
|
||
public float compareArmSeconds = 120f;
|
||
|
||
[Tooltip("타수 감소가 이 값 이상일 때만 띄운다(방). 1 = 「타수 −1」 = 기준서 §E 5-3 2점 조건.")]
|
||
public int compareMinDropHits = 1;
|
||
}
|
||
}
|