[WL-816x] 피격 이펙트 상단·150 % · 몬스터 150 % · 사거리 안 적 있으면 대시 억제 (#816)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2ede177aa9
commit
8ec0ee8eb2
|
|
@ -94,6 +94,11 @@ namespace WL.Combat
|
|||
var cfg = WLCombatMotionSettings.Instance;
|
||||
if (cfg == null || !cfg.dashEnabled) return false;
|
||||
|
||||
// ── PD #816x 최종 게이트 — 사거리 안에 적이 있으면 대쉬 0, 그 적을 제자리에서 친다.
|
||||
// 대쉬의 유일한 진입점이 여기(MyActor.cs:242)라 어떤 경로로 와도 반드시 통과한다.
|
||||
if (cfg.dashBlockIfEnemyInReach && cfg.dashBlockFinalGate
|
||||
&& BlockByEnemyInReach(pc, target, reach, cfg)) return true; // true = 호출부가 Play_Run 을 부르지 않는다
|
||||
|
||||
// 거리 창 밖이면 기존 동작. (min 미만은 애초에 여기까지 오지 않지만 방어적으로 본다)
|
||||
float minDist = Mathf.Max(cfg.dashMinDistance, reach + Mathf.Max(cfg.dashMinBeyondReach, 0f)); // PD #790: 사정거리 + 여유 밖에서만 대쉬
|
||||
if (distance < minDist || distance > cfg.dashMaxDistance) return false;
|
||||
|
|
@ -111,6 +116,93 @@ namespace WL.Combat
|
|||
return driver.BeginInternal(pc, agent, cfg, target, distance, reach);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// PD #816x — 사거리 안 적 우선 · 대쉬 억제
|
||||
//
|
||||
// PD 원문: "플레이어의 공격 사정거리에 적이 있을 경우에는 대시(몬스터에게 접근)하지 않고
|
||||
// 현재 위치에서 공격 사거리에 닿는 적을 공격하도록 해줘."
|
||||
//
|
||||
// 왜 여기인가 — 기존 #790 차단은 MyActor(원본) 안에서 `Find_Target_Facing(RetargetSearchRange, true)`
|
||||
// 로만 돌았고, 그 '사거리'는 `Get_AttackReach`(정지거리 + 상대 반경 보정 + 크기 보정 + 여유)라
|
||||
// PD 가 화면에서 보는 **사거리 원**(WeaponTrailDriver.ResolveAttackReach = 기본 정지거리 + AttackRange_Up + 여유)
|
||||
// 과 같은 값이 아니다. 두 해석이 갈리면 "원 안에 있는데 대쉬" 가 그대로 난다.
|
||||
// 그래서 대쉬의 유일한 진입점에서 **두 해석의 큰 쪽 + 여유**로 다시 한 번 본다(값은 전부 SO).
|
||||
//
|
||||
// 🔴 원본 0줄 — 타겟 교체는 공개 API `Actor.Set_Target(Actor)` · 공격은 `PCActor.WL_PlayAttackNow()`.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>게이트가 대쉬를 막은 누적 횟수 · 그중 타겟을 바꾼 횟수(검증용 읽기 전용).</summary>
|
||||
public static int BlockedByReachCount, BlockedRetargetCount;
|
||||
/// <summary>마지막 차단 요약(검증용 문자열).</summary>
|
||||
public static string LastBlockInfo = "(none)";
|
||||
|
||||
/// <summary>게이트가 쓰는 사거리(m) — 전투 reach 와 화면 사거리 원 중 큰 쪽 + 여유.</summary>
|
||||
public static float GateReach(float reach, WLCombatMotionSettings cfg)
|
||||
{
|
||||
float r = reach;
|
||||
if (cfg.dashBlockUseRingReach)
|
||||
{
|
||||
float ring = WeaponTrailDriver.LastAttackReach; // 화면에 그려지는 그 값(816p 사거리 원과 동일 출처)
|
||||
if (ring > r) r = ring;
|
||||
}
|
||||
return r + Mathf.Max(cfg.dashBlockReachBonus, 0f);
|
||||
}
|
||||
|
||||
/// <summary>사거리 안에 적이 있으면 그 적을 제자리에서 치고 true(= 대쉬 안 함).</summary>
|
||||
private static bool BlockByEnemyInReach(PCActor pc, Actor target, float reach, WLCombatMotionSettings cfg)
|
||||
{
|
||||
if (ActorInfo.Ins == null) return false;
|
||||
|
||||
float gate = GateReach(reach, cfg);
|
||||
if (gate <= 0.01f) return false;
|
||||
|
||||
Vector3 my = pc.Get_position();
|
||||
var actors = ActorInfo.Ins.Get_EnemyActors(pc.IsEnemy(), my, gate);
|
||||
Actor best = null;
|
||||
float bestDist = float.MaxValue;
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
var a = actors[i];
|
||||
if (a == null || a.IsDead()) continue;
|
||||
Vector3 d = a.Get_position() - my; d.y = 0f;
|
||||
float dist = d.magnitude;
|
||||
if (dist > gate) continue; // Get_EnemyActors 는 수직 성분도 보므로 수평으로 다시 자른다
|
||||
if (dist < bestDist) { bestDist = dist; best = a; }
|
||||
}
|
||||
ActorInfo.Ins.Release_Actors(actors);
|
||||
|
||||
if (best == null) return false;
|
||||
|
||||
BlockedByReachCount++;
|
||||
bool retargeted = false;
|
||||
if (cfg.dashBlockRetarget && best != target)
|
||||
{
|
||||
if (pc.IsMainPC())
|
||||
{
|
||||
if (target != null) target.Set_Outline(false);
|
||||
best.Set_Outline(true);
|
||||
}
|
||||
pc.Set_Target(best); // 공개 API — 원본 수정 0줄
|
||||
BlockedRetargetCount++;
|
||||
retargeted = true;
|
||||
}
|
||||
|
||||
if (cfg.dashBlockLookAtTarget)
|
||||
{
|
||||
Vector3 dir = best.Get_position() - my; dir.y = 0f;
|
||||
if (dir.sqrMagnitude > 0.0001f) pc.transform.rotation = Quaternion.LookRotation(dir);
|
||||
}
|
||||
|
||||
pc.WL_PlayAttackNow(); // 제자리 공격 (호출부는 CurAnim < Attack 구간이라 콤보를 끊지 않는다)
|
||||
|
||||
LastBlockInfo = string.Format("gate={0:F2}m d={1:F2}m target={2} retarget={3}",
|
||||
gate, bestDist, best.name, retargeted);
|
||||
#if UNITY_EDITOR
|
||||
if (cfg.verboseLog) Debug.Log("[WL #816x] 대쉬 억제 " + LastBlockInfo, pc);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool BeginInternal(PCActor pc, NavMeshAgent agent, WLCombatMotionSettings cfg,
|
||||
Actor target, float distance, float reach)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ namespace WL.Combat.Reaction
|
|||
static bool[] s_applied;
|
||||
static TurnOff_GO[] s_off;
|
||||
static bool[] s_offResolved;
|
||||
static bool[] s_isHit; // #816x: 피격 이펙트인가(이름 접두사 판정 · 등록 때 1회)
|
||||
static int s_count;
|
||||
static int s_capacity;
|
||||
static readonly Dictionary<int, int> s_map = new Dictionary<int, int>(512);
|
||||
|
|
@ -162,6 +163,10 @@ namespace WL.Combat.Reaction
|
|||
s_factor[slot] = r >= 0 ? Mathf.Max(0f, st.rules[r].scale) : 0f;
|
||||
if (r >= 0) RuledCount++;
|
||||
|
||||
// #816x — 피격 이펙트 판정(이름 접두사 · 등록 때 1회). 규칙이 없어도 위치·배율은 적용한다.
|
||||
s_isHit[slot] = !string.IsNullOrEmpty(st.hitNamePrefix)
|
||||
&& nm.StartsWith(st.hitNamePrefix, StringComparison.Ordinal);
|
||||
|
||||
s_map[id] = slot;
|
||||
TrackedCount = s_count;
|
||||
return slot;
|
||||
|
|
@ -172,15 +177,18 @@ namespace WL.Combat.Reaction
|
|||
{
|
||||
var t = s_tf[slot];
|
||||
if (t == null) return;
|
||||
if (s_rule[slot] < 0) return; // 규칙 없음 = 원본 100 %
|
||||
bool hit = s_isHit[slot] && st.hitTopEnabled; // #816x
|
||||
if (s_rule[slot] < 0 && !hit) return; // 규칙 없음 + 피격 이펙트도 아님 = 원본 100 %
|
||||
|
||||
bool active = t.gameObject.activeSelf;
|
||||
if (active)
|
||||
{
|
||||
if (s_applied[slot]) return;
|
||||
float f = ResolveFactor(slot, st);
|
||||
float f = s_rule[slot] >= 0 ? ResolveFactor(slot, st) : 1f;
|
||||
if (hit) f *= Mathf.Max(0.01f, st.hitScaleMultiplier); // #816x 크기 ×1.5
|
||||
if (f <= 0f) return;
|
||||
t.localScale = s_orig[slot] * f; // 대입(멱등) — 누적 곱이 아니다
|
||||
if (hit) LiftToEnemyTop(t, st); // #816x 적 상단으로 이동
|
||||
s_applied[slot] = true;
|
||||
AppliedCount++; ActiveScaled++;
|
||||
LastName = t.name; LastFactor = f;
|
||||
|
|
@ -211,6 +219,55 @@ namespace WL.Combat.Reaction
|
|||
return Mathf.Clamp(byHeight, Mathf.Max(0.0001f, st.minScale), Mathf.Max(st.minScale, st.maxScale));
|
||||
}
|
||||
|
||||
static void EnsureReflection()
|
||||
{
|
||||
if (s_reflectDone) return;
|
||||
s_reflectDone = true;
|
||||
s_fiTarget2 = typeof(TurnOff_GO).GetField("m_target2", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
s_fiTfTop = typeof(Actor).GetField("tf_Top", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────── #816x 피격 이펙트 = 적 상단
|
||||
|
||||
/// <summary>적 상단으로 올린 횟수 · 주인 적을 못 찾아 원본 위치로 둔 횟수 · 마지막 높이(검증용).</summary>
|
||||
public static int HitTopApplied, HitTopMissed;
|
||||
public static float LastHitTopY;
|
||||
|
||||
/// <summary>
|
||||
/// 피격 이펙트를 그 적의 **상단**(Actor.tf_Top = 게임이 eEffectLocation.Top 에 쓰는 머리 앵커)으로 올린다.
|
||||
/// 원본은 몸 중심(`target.Get_Center_position()`)에 스폰하므로 y 만 갈아끼운다(x·z = 타격 지점 유지).
|
||||
/// 주인 적을 못 찾으면 **아무것도 하지 않는다**(원본 위치 그대로 · HitTopMissed 로 센다).
|
||||
/// </summary>
|
||||
static void LiftToEnemyTop(Transform t, WLEffectScaleSettings st)
|
||||
{
|
||||
if (ActorInfo.Ins == null) { HitTopMissed++; return; }
|
||||
EnsureReflection();
|
||||
if (s_fiTfTop == null) { HitTopMissed++; return; }
|
||||
|
||||
Vector3 p = t.position;
|
||||
float range = Mathf.Max(st.hitOwnerSearchRange, 0.1f);
|
||||
var pc = MyValue.MyPC;
|
||||
var actors = ActorInfo.Ins.Get_EnemyActors(pc != null && pc.IsEnemy(), p, range);
|
||||
Actor best = null; float bestSq = float.MaxValue;
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
var a = actors[i];
|
||||
if (a == null) continue;
|
||||
if (!st.hitApplyToBoss && a.IsSubRole(eSubRol.Boss)) continue;
|
||||
float sq = (a.Get_position() - p).sqrMagnitude;
|
||||
if (sq < bestSq) { bestSq = sq; best = a; }
|
||||
}
|
||||
ActorInfo.Ins.Release_Actors(actors);
|
||||
if (best == null) { HitTopMissed++; return; }
|
||||
|
||||
var top = s_fiTfTop.GetValue(best) as Transform; // 참조형 필드 → 박싱 없음
|
||||
if (top == null) { HitTopMissed++; return; }
|
||||
|
||||
p.y = top.position.y + st.hitTopExtraY;
|
||||
t.position = p;
|
||||
HitTopApplied++; LastHitTopY = p.y;
|
||||
}
|
||||
|
||||
/// <summary>이 이펙트가 따라다니는 대상 액터의 실높이(발밑→머리). 원본 private 필드라 리플렉션(캐시 1회).</summary>
|
||||
static bool TryTargetHeight(int slot, out float height)
|
||||
{
|
||||
|
|
@ -224,12 +281,7 @@ namespace WL.Combat.Reaction
|
|||
var off = s_off[slot];
|
||||
if (off == null) return false;
|
||||
|
||||
if (!s_reflectDone)
|
||||
{
|
||||
s_reflectDone = true;
|
||||
s_fiTarget2 = typeof(TurnOff_GO).GetField("m_target2", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
s_fiTfTop = typeof(Actor).GetField("tf_Top", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
}
|
||||
EnsureReflection();
|
||||
if (s_fiTarget2 == null || s_fiTfTop == null) return false;
|
||||
|
||||
var actor = s_fiTarget2.GetValue(off) as Actor; // 참조형 필드 → 박싱 없음
|
||||
|
|
@ -286,6 +338,7 @@ namespace WL.Combat.Reaction
|
|||
Array.Resize(ref s_applied, want);
|
||||
Array.Resize(ref s_off, want);
|
||||
Array.Resize(ref s_offResolved, want);
|
||||
Array.Resize(ref s_isHit, want);
|
||||
s_capacity = want;
|
||||
}
|
||||
|
||||
|
|
@ -300,7 +353,7 @@ namespace WL.Combat.Reaction
|
|||
{
|
||||
s_id[w] = s_id[i]; s_tf[w] = s_tf[i]; s_orig[w] = s_orig[i];
|
||||
s_factor[w] = s_factor[i]; s_rule[w] = s_rule[i]; s_applied[w] = s_applied[i];
|
||||
s_off[w] = s_off[i]; s_offResolved[w] = s_offResolved[i];
|
||||
s_off[w] = s_off[i]; s_offResolved[w] = s_offResolved[i]; s_isHit[w] = s_isHit[i];
|
||||
}
|
||||
w++;
|
||||
}
|
||||
|
|
@ -330,6 +383,7 @@ namespace WL.Combat.Reaction
|
|||
SweepCount = TrackedCount = RuledCount = 0;
|
||||
AppliedCount = RestoredCount = SkippedCount = ActiveScaled = 0;
|
||||
HeightResolved = HeightMissed = 0;
|
||||
HitTopApplied = HitTopMissed = 0; LastHitTopY = 0f;
|
||||
LastName = ""; LastFactor = 0f;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,6 +151,31 @@ namespace WL.Combat.Reaction
|
|||
[Tooltip("대상 높이 기반 계산의 배율 상한(보스처럼 큰 대상에서 원본보다 커지는 것 방지).")]
|
||||
public float maxScale = 1.5f;
|
||||
|
||||
// ── 피격 이펙트 위치·크기 (#816x) ────────────────────────────────────────
|
||||
// PD 지시 #816 (2026-09-15): "적 타격시 발생하는 피격 이펙트가 적 상단에 발생하게 하고, 크기를 50% 키워줘."
|
||||
// 원본은 `ProjectileBase.Show_HitEffect → Get_HitEffectPos`(= target.Get_Center_position() 또는 투사체 위치)
|
||||
// 로 **몸 중심**에 스폰한다. 원본 1줄도 못 고치므로 EffectScaleGuard 의 풀 스윕이
|
||||
// '활성화된 그 프레임에'(LateUpdate · 렌더 앞) 위치를 적 상단으로 올리고 배율을 곱한다.
|
||||
// '적 상단' = 게임이 eEffectLocation.Top 에 쓰는 그 앵커(Actor.tf_Top) = 머리 높이.
|
||||
[Header("피격 이펙트 위치·크기 (PD #816x)")]
|
||||
[Tooltip("켜면 이름이 hitNamePrefix 로 시작하는 이펙트를 적 상단(Actor.tf_Top)으로 올린다. 끄면 원본 위치(몸 중심) 그대로")]
|
||||
public bool hitTopEnabled = true;
|
||||
|
||||
[Tooltip("대상 이름 접두사. 원본 히트 이펙트는 전부 Effect_Hit_<클래스ID> · Effect_Hit_<보스스킬> 이다")]
|
||||
public string hitNamePrefix = "Effect_Hit_";
|
||||
|
||||
[Tooltip("피격 이펙트 크기 배율(위 rules 로 정해진 배율에 추가로 곱한다). 1.5 = PD 지시 '50% 크게'")]
|
||||
public float hitScaleMultiplier = 1.5f;
|
||||
|
||||
[Tooltip("적 상단(머리 끝)에서 더 올릴 높이(m). 0 이면 정확히 머리 끝")]
|
||||
public float hitTopExtraY = 0.1f;
|
||||
|
||||
[Tooltip("이펙트가 어느 적의 것인지 찾는 반경(m). 원본이 그 적의 몸 중심에 스폰하므로 짧아도 된다")]
|
||||
public float hitOwnerSearchRange = 2.5f;
|
||||
|
||||
[Tooltip("켜면 보스에게도 적용한다. 끄면 보스 피격 이펙트는 원본 위치·크기 그대로")]
|
||||
public bool hitApplyToBoss = true;
|
||||
|
||||
[Header("규칙 (배열 순서대로 검사 · 처음 맞는 것 · 안 맞으면 손대지 않는다 = 원본 100 %)")]
|
||||
public EffectScaleRule[] rules = new EffectScaleRule[0];
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ MonoBehaviour:
|
|||
referenceHeight: 1.191
|
||||
minScale: 0.05
|
||||
maxScale: 1.5
|
||||
hitTopEnabled: 1
|
||||
hitNamePrefix: Effect_Hit_
|
||||
hitScaleMultiplier: 1.5
|
||||
hitTopExtraY: 0.1
|
||||
hitOwnerSearchRange: 2.5
|
||||
hitApplyToBoss: 1
|
||||
rules:
|
||||
- prefab: Effect_Stun
|
||||
match: 0
|
||||
|
|
|
|||
|
|
@ -200,6 +200,25 @@ namespace WL.Combat
|
|||
public bool dashBlockIfEnemyInReach = true;
|
||||
public static bool DashBlockIfEnemyInReach { get { var i = Instance; return i != null && i.dashBlockIfEnemyInReach; } }
|
||||
|
||||
// ── PD #816x (2026-09-15) "사정거리에 적이 있으면 대쉬하지 말고 제자리에서 그 적을 공격" ──
|
||||
// 기존 #790 차단은 MyActor 안(원본)에서만 돌아 놓치는 경우가 있었다(락 타겟·사거리 해석 차이).
|
||||
// 아래 값들은 **대쉬의 유일한 진입점인 DashDriver.TryBegin** 에서 마지막으로 한 번 더 본다.
|
||||
[Tooltip("대쉬 직전 최종 게이트를 켠다. 사거리 안에 적이 하나라도 있으면 대쉬를 시작하지 않는다")]
|
||||
public bool dashBlockFinalGate = true;
|
||||
|
||||
[Tooltip("게이트가 보는 사거리에 화면 사거리 원(WeaponTrailDriver.LastAttackReach)을 함께 쓴다. " +
|
||||
"PD 화면에서 원 안에 보이는 적이 게이트에도 '안'으로 잡히게 하는 스위치")]
|
||||
public bool dashBlockUseRingReach = true;
|
||||
|
||||
[Tooltip("게이트 사거리에 더하는 여유(m). 원 테두리에 걸친 적까지 '안'으로 본다")]
|
||||
public float dashBlockReachBonus = 0.35f;
|
||||
|
||||
[Tooltip("게이트가 사거리 안 적을 찾으면 타겟을 그 적(가장 가까운)으로 바꾼다. 끄면 기존 타겟을 그대로 두고 대쉬만 막는다")]
|
||||
public bool dashBlockRetarget = true;
|
||||
|
||||
[Tooltip("제자리 공격 직전에 그 적 쪽으로 몸을 돌린다(수평 회전만)")]
|
||||
public bool dashBlockLookAtTarget = true;
|
||||
|
||||
[Header("■ 검증 (에디터 전용)")]
|
||||
[Tooltip("켜면 전진 시작·종료·이동량을 콘솔에 남긴다")]
|
||||
public bool verboseLog = false;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ MonoBehaviour:
|
|||
dashFailCooldownSeconds: 2
|
||||
dashMinBeyondReach: 0.8
|
||||
dashBlockIfEnemyInReach: 1
|
||||
dashBlockFinalGate: 1
|
||||
dashBlockUseRingReach: 1
|
||||
dashBlockReachBonus: 0.35
|
||||
dashBlockRetarget: 1
|
||||
dashBlockLookAtTarget: 1
|
||||
dashStateName: dash
|
||||
dashClipName: Chase_Start
|
||||
dashCrossFadeSeconds: 0.06
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ MonoBehaviour:
|
|||
eliteProjectileScale: 0.3
|
||||
bossProjectileScale: 0.3
|
||||
projectileScaleVisibleOnly: 1
|
||||
mobBodyScaleEnabled: 1
|
||||
mobBodyScale: 1.5
|
||||
eliteBodyScale: 1
|
||||
bossBodyScale: 1
|
||||
mobBodyScaleKeepUnderBossThreshold: 0
|
||||
bodyScaleBossThreshold: 2
|
||||
bodyScaleThresholdEpsilon: 0.02
|
||||
hideBattlePad: 0
|
||||
battlePadPaths:
|
||||
- IngameUIs/BattleUI
|
||||
|
|
|
|||
|
|
@ -61,7 +61,12 @@ namespace WL.Settings
|
|||
public static void ApplyMob(NavMeshAgent agent, eSubRol subRole)
|
||||
{
|
||||
var s = WLGameplaySettings.Instance;
|
||||
if (agent == null || s == null || !s.agentTuningEnabled) return;
|
||||
if (agent == null || s == null) return;
|
||||
|
||||
// #816x — 몸 크기(PD "기본적인 몬스터의 크기도 50% 키워줘"). 에이전트 튜닝 스위치와 독립이다.
|
||||
ApplyMobBodyScale(agent.transform, subRole, s);
|
||||
|
||||
if (!s.agentTuningEnabled) return;
|
||||
|
||||
bool isBoss = subRole == eSubRol.Boss;
|
||||
bool isElite = subRole == eSubRol.Elite;
|
||||
|
|
@ -81,6 +86,48 @@ namespace WL.Settings
|
|||
agent.obstacleAvoidanceType = (ObstacleAvoidanceType)(int)s.mobAvoidance;
|
||||
}
|
||||
|
||||
// ── #816x 몬스터 몸 크기 ────────────────────────────────────────────────
|
||||
/// <summary>몸 크기를 적용한 누적 횟수 · 보스 경계에서 잘린 횟수 · 마지막 값(검증용 읽기 전용).</summary>
|
||||
public static int BodyScaleApplied, BodyScaleClamped;
|
||||
public static float LastBodyScaleBase, LastBodyScaleApplied;
|
||||
|
||||
/// <summary>
|
||||
/// 몬스터 루트 스케일에 타입별 배율을 곱한다(#816x · PD "기본적인 몬스터의 크기도 50% 키워줘").
|
||||
///
|
||||
/// 호출 시점 = MobActor.Set 안, `transform.localScale = f_DefaultScale * Vector3.one`(:87) **직후**(:118).
|
||||
/// 따라서 base 는 언제나 테이블 기본값이고, 풀에서 재사용돼도 다시 기본값에서 곱한다 = **누적 없음**.
|
||||
/// 루트 스케일이므로 자식(콜라이더·tf_Top·HP 바)은 함께 커지고,
|
||||
/// NavMeshAgent.radius(월드 단위 · 바로 아래에서 별도 계산) 와 데이터 값(이동 속도 · f_BaseATKRange)은 **그대로다**.
|
||||
/// </summary>
|
||||
public static void ApplyMobBodyScale(Transform root, eSubRol subRole, WLGameplaySettings s)
|
||||
{
|
||||
if (root == null || s == null || !s.mobBodyScaleEnabled) return;
|
||||
|
||||
float m = subRole == eSubRol.Boss ? s.bossBodyScale
|
||||
: subRole == eSubRol.Elite ? s.eliteBodyScale
|
||||
: s.mobBodyScale;
|
||||
if (m <= 0f || Mathf.Abs(m - 1f) < 0.0001f) return;
|
||||
|
||||
float bas = root.localScale.x;
|
||||
if (bas <= 0.0001f) return;
|
||||
|
||||
float want = bas * m;
|
||||
|
||||
// 원본이 localScale.x > 2 를 '보스급' 판정에 쓴다(ProjectileBase:305/308 · Actor:950).
|
||||
// 스위치가 켜져 있을 때만 그 경계를 넘지 않게 자른다(기본은 꺼짐 = PD 지시 그대로 ×배율).
|
||||
float th = s.bodyScaleBossThreshold;
|
||||
if (s.mobBodyScaleKeepUnderBossThreshold && th > 0f && bas <= th && want > th)
|
||||
{
|
||||
want = th - Mathf.Max(s.bodyScaleThresholdEpsilon, 0f);
|
||||
BodyScaleClamped++;
|
||||
}
|
||||
|
||||
root.localScale = Vector3.one * want;
|
||||
BodyScaleApplied++;
|
||||
LastBodyScaleBase = bas;
|
||||
LastBodyScaleApplied = want;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 투사체에 곱할 크기 배율. 1 이면 변경하지 않는다.
|
||||
/// 플레이어·펫 투사체는 항상 1(무변경) — 몬스터(eRole.Mob)가 쏜 것만 대상.
|
||||
|
|
|
|||
|
|
@ -158,6 +158,36 @@ namespace WL.Settings
|
|||
"몹의 공격 사거리만 줄어 빗나간다. 끄면 판정 박스까지 전부 축소한다(= PD 지시 문자 그대로).")]
|
||||
public bool projectileScaleVisibleOnly = true;
|
||||
|
||||
// ── 몬스터 몸 크기 (#816x) ───────────────────────────────────────────────
|
||||
// PD 지시 #816 (2026-09-15): "기본적인 몬스터의 크기도 50% 키워줘" → 일반 몹 배율 1.5
|
||||
// 적용 지점 = MobActor.Set 이 이미 부르는 WLCollisionTuning.ApplyMob(원본 훅 재사용 · 새 훅 0줄).
|
||||
// MobActor.cs:87 이 transform.localScale = f_DefaultScale 을 대입한 **직후**(:118)라
|
||||
// 매 스폰·풀 재사용마다 테이블 기본값에서 다시 곱한다 = 누적 없음.
|
||||
// 데이터 표(MonsterList.json f_DefaultScale)는 손대지 않는다 — 런타임 루트 스케일만.
|
||||
[Header("■ 몬스터 몸 크기 (PD 지시 #816x)")]
|
||||
[Tooltip("켜면 몬스터 루트 스케일에 아래 배율을 곱한다. 끄면 테이블 f_DefaultScale 그대로(C8 롤백).")]
|
||||
public bool mobBodyScaleEnabled = true;
|
||||
|
||||
[Tooltip("일반 몹(None)의 몸 크기 배율. 1.5 = PD 지시 '50% 크게'. 1 이면 무변경.")]
|
||||
public float mobBodyScale = 1.5f;
|
||||
|
||||
[Tooltip("정예 몹(Elite)의 몸 크기 배율. PD 지시는 '기본적인 몬스터'라 기본 1(무변경).")]
|
||||
public float eliteBodyScale = 1f;
|
||||
|
||||
[Tooltip("보스(Boss)의 몸 크기 배율. PD 지시 범위 밖이라 기본 1(무변경).")]
|
||||
public float bossBodyScale = 1f;
|
||||
|
||||
[Tooltip("켜면 배율 결과가 보스 판정 경계(bodyScaleBossThreshold)를 넘지 않도록 자른다.\n" +
|
||||
"원본 ProjectileBase.Check_TargetBoss(:305/308) 와 Actor.Get_ScaleSizeDist(:950) 가 " +
|
||||
"localScale.x > 2 를 '보스급' 판정에 쓰기 때문이다. 끄면 PD 지시대로 곧이곧대로 ×배율(기본 = 끔).")]
|
||||
public bool mobBodyScaleKeepUnderBossThreshold = false;
|
||||
|
||||
[Tooltip("위 자르기의 경계값. 원본 코드가 쓰는 2 와 같아야 한다.")]
|
||||
public float bodyScaleBossThreshold = 2f;
|
||||
|
||||
[Tooltip("경계에서 얼마나 아래로 자를지(여유). 0.02 = 1.98 로 자른다.")]
|
||||
public float bodyScaleThresholdEpsilon = 0.02f;
|
||||
|
||||
// ── 인게임 HUD 오버라이드 (#775) ─────────────────────────────────────────
|
||||
// PD 지시 #775 (2026-09-06): "우측 하단 공격 버튼 및 스킬 아이콘 버튼은 추후 변경해서 사용할
|
||||
// 예정이므로 지금은 보이지 않게 숨기고, 포트레이트 뷰에 맞게 WL 프로젝트 UI를 배치해줘."
|
||||
|
|
|
|||
Loading…
Reference in New Issue