289 lines
17 KiB
C#
289 lines
17 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;
|
||
|
||
// ── 성장 입력 (WL-813l2 · Q6 D-28) ────────────────────────────────────
|
||
// 🔴 실측: 원본은 **레벨업으로 스탯을 1도 올리지 않는다**.
|
||
// · `ActorStatInfo.Reset`(`Assets/Script/Util/MyValue.cs:286-453`)은 `SD_PC.Lv` 를 한 번도 읽지 않는다.
|
||
// · PC 스탯은 `table_classconfig.Get_PureStat`(`Table/PC/table_classconfig.cs:232-248`
|
||
// = 보유 직업 기본치 + **사용한** 스탯 포인트 `SD_PC.Get_Stat`) + 장비·각성·초월·유물에서만 온다.
|
||
// · 레벨업은 `SD_PC.Get_Point()`(`Server/ServerClass.cs:2770` = `(Lv−1)*5 − 사용분`)로
|
||
// **미사용 스탯 포인트만** 늘린다. 포인트를 찍는 곳은 `PCInfoStatMgrUI.cs:81` 수동 UI 하나뿐이다.
|
||
// → 그래서 813l 이 `ActorStatInfo` 만 보면 레벨 1→16 에도 전투력이 상수다(Q6 D-28 `LastPower 52` 불변).
|
||
// 발주서 WL-813l2 §1-2 대로 **813k `LevelUp` 페이로드(prev/new 스탯 · statPointTotal)** 를 입력으로 쓴다.
|
||
[Header("성장 입력 (WL-813l2 · D-28) — 🔴 원본은 레벨업으로 스탯을 안 올린다(위 주석 = 실측)")]
|
||
[Tooltip("레벨업 경로에서 813k GrowthEvents.LevelUp 페이로드(prev/new 스탯 · prevLevel/newLevel · statPointTotal)로 전투력을 만든다. 끄면 813l 원래 동작(레벨업 때 ActorStatInfo 재읽기 = D-28 재발).")]
|
||
public bool useLevelUpPayload = true;
|
||
|
||
[Tooltip("폴링 경로에서 SD_PC 의 레벨·미사용 스탯 포인트를 읽는다(장비/강화 갱신과 같은 주기). 끄면 성장 입력 0.")]
|
||
public bool readGrowthFromServerData = true;
|
||
|
||
[Tooltip("미사용 스탯 포인트를 얼마나 「이미 얻은 힘」으로 셀지(0~1). 1 = 찍은 것과 똑같이 센다 → 포인트를 실제로 찍어도 전투력이 튀지 않는다(잠재→실현 연속). 0 = 813l 원래 산식 그대로(= D-28 재발).")]
|
||
public float statPointWeight = 1f;
|
||
|
||
[Tooltip("스탯 포인트 1점의 공격력 환산. 원본 실측 = 1 (MyValue.cs:335/343/351 · 주스탯 1점 → ATK += 1).")]
|
||
public float statPointAtkGain = 1f;
|
||
|
||
[Tooltip("스탯 포인트 1점의 체력 환산. 원본 실측 = 22 (MyValue.cs:336 · STR 1점 → MaxHP += 22). DEX/INT 주스탯 직업은 이 몫이 없다 = 「추정」.")]
|
||
public float statPointHpGain = 22f;
|
||
|
||
[Tooltip("레벨 1당 주는 스탯 포인트. 원본 실측 = 5 (ServerClass.cs:2770 Get_Point() = (Lv−1)*5 − 사용분). 페이로드에서 「레벨업 직전 포인트」를 역산할 때도 쓴다.")]
|
||
public int statPointsPerLevel = 5;
|
||
|
||
[Tooltip("레벨 1당 전투력 기여(스탯·포인트와 별개의 보정). 기본 0 = 끔 — 켜면 포인트를 다 쓴 뒤에도 레벨만으로 전투력이 오른다.")]
|
||
public float levelWeight = 0f;
|
||
|
||
[Tooltip("레벨 기여의 기준 레벨(이 레벨을 빼서 증가분을 만든다). 원본 시작 레벨 1.")]
|
||
public int levelBase = 1;
|
||
|
||
// ── 전투력 % 토스트 ───────────────────────────────────────────────────
|
||
[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;
|
||
}
|
||
}
|