Merge branch 'wl/gameplay/WL-813x-hotfix'
This commit is contained in:
commit
f408618b31
|
|
@ -0,0 +1,221 @@
|
|||
// PD 지시 #813 · 발주서 WL-813x — 핫픽스 검증 프로브 (에디트 모드 전용 · Play 0 · 로그인 0)
|
||||
//
|
||||
// 실행 (CLAUDE.md §1: using 금지 · 네임스페이스 풀어 쓰기)
|
||||
// unity command run_script --file AgentScripts/WL813x_Probe.cs --entry WL813x_Probe.Edit --timeout 300
|
||||
//
|
||||
// 무엇을 재는가
|
||||
// A. 결함 재현·수정 — NavMesh 밖 NavMeshAgent 에 대한 `isStopped = false`(= Resume) 가
|
||||
// Q3 D-1 / Q2 D-4 의 문구를 실제로 뱉는지, 그리고 MobActor.cs:339 에 들어간 가드
|
||||
// (`if (agent.isOnNavMesh)`)가 그 예외를 0으로 만드는지를 같은 에이전트로 A/B 측정한다.
|
||||
// B. MobNavRecovery — SO 값 로드 · Active · 에디트 모드 ForceTick 예외 0 · 러너 오브젝트 0
|
||||
// C. AutoCombatOnEnter — SO 값 로드 · Active · 에디트 모드 ForceTick 예외 0 · ServerInfo 오염 0
|
||||
// D. 원본 소스 가드 문자열 확인(MobActor.cs 스킬 케이스)
|
||||
//
|
||||
// 게임 수치는 전부 SO 에서 읽는다(C45). 프로브가 만드는 임시 오브젝트는 HideAndDontSave + DestroyImmediate.
|
||||
public static class WL813x_Probe
|
||||
{
|
||||
const string kOut = "AgentScripts/staging/WL813x/EDIT_PROBE.txt";
|
||||
const string kMobActor = "Assets/Script/Character/Mob/MobActor.cs";
|
||||
|
||||
static System.Collections.Generic.List<string> s_log = new System.Collections.Generic.List<string>();
|
||||
static bool s_hooked;
|
||||
|
||||
static void OnLog(string cond, string stack, UnityEngine.LogType t)
|
||||
{
|
||||
if (t == UnityEngine.LogType.Error || t == UnityEngine.LogType.Exception || t == UnityEngine.LogType.Assert)
|
||||
s_log.Add(t + " | " + cond);
|
||||
}
|
||||
static void Hook() { if (!s_hooked) { UnityEngine.Application.logMessageReceived += OnLog; s_hooked = true; } }
|
||||
static void Unhook() { if (s_hooked) { UnityEngine.Application.logMessageReceived -= OnLog; s_hooked = false; } }
|
||||
static int Count() { return s_log.Count; }
|
||||
static void Clear() { s_log.Clear(); }
|
||||
|
||||
public static string Edit()
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.Append("# WL-813x 핫픽스 프로브 (에디트 모드)\n");
|
||||
sb.Append("unity=").Append(UnityEngine.Application.unityVersion)
|
||||
.Append(" isPlaying=").Append(UnityEngine.Application.isPlaying)
|
||||
.Append(" utc=").Append(System.DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")).Append("\n\n");
|
||||
|
||||
SectionA(sb);
|
||||
SectionB(sb);
|
||||
SectionC(sb);
|
||||
SectionD(sb);
|
||||
|
||||
var dir = System.IO.Path.GetDirectoryName(kOut);
|
||||
if (!System.IO.Directory.Exists(dir)) System.IO.Directory.CreateDirectory(dir);
|
||||
System.IO.File.WriteAllText(kOut, sb.ToString(), new System.Text.UTF8Encoding(false));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// A. 결함 재현 → 가드 효과 (같은 에이전트로 A/B)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static void SectionA(System.Text.StringBuilder sb)
|
||||
{
|
||||
sb.Append("## A. Resume 예외 재현·가드 (NavMesh 밖 에이전트)\n");
|
||||
|
||||
UnityEngine.GameObject go = null;
|
||||
try
|
||||
{
|
||||
go = new UnityEngine.GameObject("[WL813x] probe agent");
|
||||
go.hideFlags = UnityEngine.HideFlags.HideAndDontSave;
|
||||
var agent = go.AddComponent<UnityEngine.AI.NavMeshAgent>();
|
||||
|
||||
sb.Append("agent.enabled=").Append(agent.enabled)
|
||||
.Append(" isOnNavMesh=").Append(agent.isOnNavMesh).Append("\n");
|
||||
|
||||
// A-1 원본 패턴(가드 없음) = MobActor.cs:339 수정 전
|
||||
Hook(); Clear();
|
||||
try { agent.isStopped = false; } catch (System.Exception e) { s_log.Add("THROW | " + e.Message); }
|
||||
int a1 = Count();
|
||||
string a1msg = a1 > 0 ? s_log[0] : "";
|
||||
Unhook();
|
||||
|
||||
// A-2 수정 후 패턴(가드) = 현재 MobActor.cs:339
|
||||
Hook(); Clear();
|
||||
try { if (agent.isOnNavMesh) agent.isStopped = false; } catch (System.Exception e) { s_log.Add("THROW | " + e.Message); }
|
||||
int a2 = Count();
|
||||
Unhook();
|
||||
|
||||
sb.Append("A-1 가드 없음 : 에러 ").Append(a1).Append("건 ").Append(a1msg).Append("\n");
|
||||
sb.Append("A-2 가드 있음 : 에러 ").Append(a2).Append("건\n");
|
||||
bool qaMatch = a1msg.Contains("Resume") && a1msg.Contains("NavMesh");
|
||||
sb.Append("판정 : ").Append(a1 >= 1 && a2 == 0 ? "PASS" : "FAIL")
|
||||
.Append(" (QA 문구 일치=").Append(qaMatch).Append(")\n\n");
|
||||
}
|
||||
catch (System.Exception e) { Unhook(); sb.Append("A 실패 : ").Append(e.GetType().Name).Append(" ").Append(e.Message).Append("\n\n"); }
|
||||
finally { if (go != null) UnityEngine.Object.DestroyImmediate(go); TryClearConsole(); }
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// B. MobNavRecovery
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static void SectionB(System.Text.StringBuilder sb)
|
||||
{
|
||||
sb.Append("## B. MobNavRecovery (SO · Active · 에디트 모드 안전)\n");
|
||||
try
|
||||
{
|
||||
var cfg = WL.Combat.Core.WLCombatCoreSettings.Instance;
|
||||
sb.Append("SO=").Append(cfg == null ? "null" : cfg.name).Append("\n");
|
||||
if (cfg != null)
|
||||
sb.Append(" mobNavRecoveryEnabled=").Append(cfg.mobNavRecoveryEnabled)
|
||||
.Append(" grace=").Append(cfg.mobNavRecoveryGraceSeconds)
|
||||
.Append(" rescan=").Append(cfg.mobNavRecoveryRescanSeconds)
|
||||
.Append(" check=").Append(cfg.mobNavRecoveryCheckSeconds)
|
||||
.Append(" near=").Append(cfg.mobNavRecoveryNearRadius)
|
||||
.Append(" far=").Append(cfg.mobNavRecoveryFarRadius).Append("\n");
|
||||
|
||||
sb.Append("Active=").Append(WL.Combat.Nav.MobNavRecovery.Active).Append("\n");
|
||||
|
||||
WL.Combat.Nav.MobNavRecovery.ResetDiagnostics();
|
||||
Hook(); Clear();
|
||||
WL.Combat.Nav.MobNavRecovery.ForceTick();
|
||||
WL.Combat.Nav.MobNavRecovery.ForceTick();
|
||||
int errs = Count();
|
||||
Unhook();
|
||||
|
||||
sb.Append("ForceTick x2 : 에러 ").Append(errs)
|
||||
.Append("건 · scans=").Append(WL.Combat.Nav.MobNavRecovery.Scans)
|
||||
.Append(" anomalies=").Append(WL.Combat.Nav.MobNavRecovery.AnomaliesSeen)
|
||||
.Append(" repaired=").Append(WL.Combat.Nav.MobNavRecovery.Repaired)
|
||||
.Append(" failed=").Append(WL.Combat.Nav.MobNavRecovery.RepairFailed).Append("\n");
|
||||
|
||||
int runners = CountByName("[WL813x] MobNavRecoveryRunner");
|
||||
sb.Append("에디트 모드 러너 오브젝트=").Append(runners).Append(" (기대 0)\n");
|
||||
sb.Append("판정 : ").Append(errs == 0 && runners == 0 ? "PASS" : "FAIL").Append("\n\n");
|
||||
}
|
||||
catch (System.Exception e) { Unhook(); sb.Append("B 실패 : ").Append(e.GetType().Name).Append(" ").Append(e.Message).Append("\n\n"); }
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// C. AutoCombatOnEnter
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static void SectionC(System.Text.StringBuilder sb)
|
||||
{
|
||||
sb.Append("## C. AutoCombatOnEnter (SO · Active · 에디트 모드 안전)\n");
|
||||
try
|
||||
{
|
||||
var cfg = WL.Combat.Core.WLCombatCoreSettings.Instance;
|
||||
if (cfg != null)
|
||||
sb.Append(" autoCombatOnEnter=").Append(cfg.autoCombatOnEnter)
|
||||
.Append(" inLobby=").Append(cfg.autoCombatInLobby)
|
||||
.Append(" poll=").Append(cfg.autoCombatPollSeconds)
|
||||
.Append(" iconRetry=").Append(cfg.autoCombatIconRetrySeconds).Append("\n");
|
||||
|
||||
sb.Append("Active=").Append(WL.Combat.Auto.AutoCombatOnEnter.Active).Append("\n");
|
||||
|
||||
int serverBefore = CountByName("ServerInfo");
|
||||
WL.Combat.Auto.AutoCombatOnEnter.ResetDiagnostics();
|
||||
Hook(); Clear();
|
||||
bool autoRead = WL.Combat.Auto.AutoCombatOnEnter.IsAutoOn; // 에디트 모드에서는 무조건 false + 오염 0
|
||||
WL.Combat.Auto.AutoCombatOnEnter.ForceTick();
|
||||
WL.Combat.Auto.AutoCombatOnEnter.ForceTick();
|
||||
int errs = Count();
|
||||
Unhook();
|
||||
int serverAfter = CountByName("ServerInfo");
|
||||
|
||||
sb.Append("IsAutoOn(에디트)=").Append(autoRead).Append(" (기대 false · ServerInfo 생성 금지)\n");
|
||||
sb.Append("ForceTick x2 : 에러 ").Append(errs)
|
||||
.Append("건 · boundaries=").Append(WL.Combat.Auto.AutoCombatOnEnter.Boundaries)
|
||||
.Append(" applied=").Append(WL.Combat.Auto.AutoCombatOnEnter.Applied)
|
||||
.Append(" alreadyOn=").Append(WL.Combat.Auto.AutoCombatOnEnter.AlreadyOn)
|
||||
.Append(" skippedLobby=").Append(WL.Combat.Auto.AutoCombatOnEnter.SkippedLobby).Append("\n");
|
||||
sb.Append("ServerInfo 오브젝트 ").Append(serverBefore).Append(" → ").Append(serverAfter).Append(" (기대 동일)\n");
|
||||
int runners = CountByName("[WL813x] AutoCombatOnEnterRunner");
|
||||
sb.Append("에디트 모드 러너 오브젝트=").Append(runners).Append(" (기대 0)\n");
|
||||
sb.Append("판정 : ").Append(errs == 0 && !autoRead && serverBefore == serverAfter && runners == 0 ? "PASS" : "FAIL").Append("\n");
|
||||
sb.Append("🔴 인게임 실값(진입 후 auto=1 · 킬수)은 Play 로그인이 필요해 이 프로브 범위 밖 = 「미확인」. QA 재현 절차는 완료보고 참조.\n\n");
|
||||
}
|
||||
catch (System.Exception e) { Unhook(); sb.Append("C 실패 : ").Append(e.GetType().Name).Append(" ").Append(e.Message).Append("\n\n"); }
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// D. 원본 소스 가드 확인
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static void SectionD(System.Text.StringBuilder sb)
|
||||
{
|
||||
sb.Append("## D. 원본 가드 확인\n");
|
||||
try
|
||||
{
|
||||
var lines = System.IO.File.ReadAllLines(kMobActor);
|
||||
int guarded = 0, bare = 0, skillLine = -1;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (lines[i].Contains("isStopped = false"))
|
||||
{
|
||||
if (lines[i].Contains("isOnNavMesh")) guarded++; else bare++;
|
||||
}
|
||||
if (lines[i].Contains("case eMobStatus.Skill")) skillLine = i + 1;
|
||||
}
|
||||
sb.Append(kMobActor).Append(" : isStopped=false 가드 ").Append(guarded)
|
||||
.Append("곳 · 무가드 ").Append(bare).Append("곳 · case eMobStatus.Skill = ").Append(skillLine).Append("줄\n");
|
||||
sb.Append("판정 : ").Append(bare == 0 ? "PASS" : "FAIL").Append("\n");
|
||||
}
|
||||
catch (System.Exception e) { sb.Append("D 실패 : ").Append(e.GetType().Name).Append(" ").Append(e.Message).Append("\n"); }
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
static int CountByName(string name)
|
||||
{
|
||||
int n = 0;
|
||||
var all = UnityEngine.Object.FindObjectsByType<UnityEngine.GameObject>(
|
||||
UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None);
|
||||
for (int i = 0; i < all.Length; i++) if (all[i].name == name) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void TryClearConsole()
|
||||
{
|
||||
// A-1 은 의도적으로 에러 1건을 만든다 — 뒤따르는 콘솔 판정(조직 C39)을 오염시키지 않도록 지운다.
|
||||
try
|
||||
{
|
||||
var t = System.Type.GetType("UnityEditor.LogEntries,UnityEditor");
|
||||
if (t == null) return;
|
||||
var m = t.GetMethod("Clear", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
|
||||
if (m != null) m.Invoke(null, null);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
|
@ -336,7 +336,7 @@ public class MobActor : Actor
|
|||
break;
|
||||
case eMobStatus.Skill:
|
||||
StopMove_Imm(false);
|
||||
m_NavMeshAgent.isStopped = false;
|
||||
if (m_NavMeshAgent.isOnNavMesh) m_NavMeshAgent.isStopped = false; // #813x — 위 PatrolWait/PatrolRot(:304·:309)·StopMove_Imm 과 같은 가드. NavMesh 밖/비활성 에이전트면 "Resume" 예외 → 에러 팝업(Q3 D-1 · Q2 D-4)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2deda96b331d854418a5c8ed206a6fdd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AutoCombatOnEnter.cs — 맵 진입 시 자동전투 기본 ON (Q3 D-2)
|
||||
//
|
||||
// PD 지시 #813 · 발주서 WL-813x §1-2 (2026-09-09) · 근거 = qa/W2-3_바퀴2.md D-2
|
||||
//
|
||||
// ■ 관측된 결함 (QA 실측)
|
||||
// 신규 계정 기본값이 `eCommon.Auto = 0` 이라 인게임에 들어가도 자동전투가 꺼져 있다.
|
||||
// 존1 60 s 방치 = **0킬 · skillCast 0**. `Actor.Set_Auto()` 1회 뒤 같은 조건 60 s = **23킬**.
|
||||
// (W2-1 의 「스킬 0발」 미확인 항목이 여기서 해소됐다.) 이 값이 0인 채로 PD 가 Play 하면
|
||||
// "스킬도 안 나가고 적도 안 죽는 게임"을 보게 된다.
|
||||
//
|
||||
// ■ 원인 (코드 실측)
|
||||
// PCActor.cs:185 `m_Auto = m_ServerData.Get_Common(eCommon.Auto) == 1;` — ReSet() 마다 서버 값에서 다시 읽는다.
|
||||
// PCActor.cs:93 의 자동 스킬 루프가 `m_Auto` 로 게이트된다 → false 면 스킬을 한 발도 쏘지 않는다.
|
||||
// Actor.cs:2260 `Set_Auto()` 는 m_Auto 를 토글하고 같은 값을 eCommon.Auto 에 넣는다(둘은 항상 동기).
|
||||
//
|
||||
// ■ 이 파일이 하는 것 — 원본 훅 0 · 폴링만(Survival.PollRunBoundary 선례)
|
||||
// 런(맵) 진입 경계를 관측해서 그 시점에 자동전투가 꺼져 있으면 `MyValue.MyPC.Set_Auto()` 를 **1회** 부른다.
|
||||
// · 경계 = 메인 PC 교체(PCActor.ReSet 이 MyValue.MyPC 를 갈아 끼운다) 또는 맵 ID 변경.
|
||||
// · 진입 뒤 플레이어가 직접 끈 것은 다시 켜지 않는다(경계당 1회이므로).
|
||||
// · 로비는 기본 제외(적이 없어 자동 타겟팅이 의미가 없다) — autoCombatInLobby 로 켤 수 있다.
|
||||
// · Set_Auto() 는 m_Auto 와 eCommon.Auto 를 함께 세우므로, 뒤늦게 도는 PCActor.ReSet() 이
|
||||
// 다시 읽어도 ON 이 유지된다.
|
||||
//
|
||||
// ■ 계정 영구 변경 아님
|
||||
// ServerClass.Set_Common(ServerClass.cs:1469) 은 로컬 캐시 배열만 쓴다(서버 저장 호출 없음).
|
||||
// QA 도 "슬롯 2에서 켠 값이 슬롯 3 로그인에 안 남았다 = 세션 간 비영속"을 실측했다.
|
||||
// → 이 핫픽스는 매 진입마다 다시 켜 주는 방식이지 계정 기본값을 바꾸는 것이 아니다.
|
||||
// 계정 기본값(신규 계정 Auto=1)은 서버/테이블 소관 = S 트랙 · PD 결정.
|
||||
//
|
||||
// ■ 전투 패드 아이콘
|
||||
// BattleUI.Set_Auto()(private)가 `Get_Common(eCommon.Auto)` 로 스프라이트를 고른다.
|
||||
// UI 가 먼저 떴다면 아이콘이 OFF 로 굳으므로, 적용 뒤 잠시 동안 public 필드
|
||||
// `BattleUI.images[0]` 의 스프라이트만 맞춘다(UI 파일 수정 0 · 813c 패드 배치와 무관).
|
||||
//
|
||||
// ■ 값(C45) 전부 WLCombatCoreSettings. autoCombatOnEnter = 0 이면 러너 자체가 뜨지 않는다(C8 롤백).
|
||||
// 🔴 코어 스위치(WLCombatCoreSettings.Enabled)에는 의존하지 않는다 — 코어 A/B 가 킬 수를 0으로 만들면 안 된다.
|
||||
//
|
||||
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(Actor/PCActor 가 Assembly-CSharp 에 있다).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using WL.Combat.Core;
|
||||
|
||||
namespace WL.Combat.Auto
|
||||
{
|
||||
/// <summary>맵 진입 시 자동전투 1회 ON. 정적 · 새 Manager/Singleton 0(런너는 숨김 GameObject 1개).</summary>
|
||||
public static class AutoCombatOnEnter
|
||||
{
|
||||
static WLCombatCoreSettings Cfg { get { return WLCombatCoreSettings.Instance; } }
|
||||
|
||||
/// <summary>기능이 살아 있는가. 에셋이 없거나 플래그가 꺼져 있으면 false = 원본 동작 100%.</summary>
|
||||
public static bool Active
|
||||
{
|
||||
get { var c = Cfg; return c != null && c.autoCombatOnEnter && !RuntimeDisabled; }
|
||||
}
|
||||
|
||||
/// <summary>진단·A/B 전용 런타임 스위치. Play 종료 시 도메인 리로드로 자동 false.</summary>
|
||||
public static bool RuntimeDisabled;
|
||||
|
||||
// ───────────────────────────────────────── 진단 카운터 (프로브가 읽는다)
|
||||
public static int Boundaries, Applied, AlreadyOn, SkippedLobby, IconFixed;
|
||||
public static string LastReason = "", LastLog = "";
|
||||
|
||||
/// <summary>현재 자동전투 값(서버 데이터 캐시). 프로브가 「진입 후 true」를 확인하는 지점.</summary>
|
||||
public static bool IsAutoOn
|
||||
{
|
||||
get
|
||||
{
|
||||
// 🔴 ServerInfo.Ins 는 없으면 GameObject 를 만든다(ServerInfo.cs:19) — 에디트 모드를 오염시키지 않는다.
|
||||
if (!Application.isPlaying) return false;
|
||||
var si = ServerInfo.Ins;
|
||||
if (DSUtil.CheckNull(si) || si.m_ServerData == null) return false;
|
||||
return si.m_ServerData.Get_Common(eCommon.Auto) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────────────────────────── 내부 상태
|
||||
static PCActor s_lastPc;
|
||||
static int s_lastMapId = int.MinValue;
|
||||
static float s_nextPoll, s_iconDeadline;
|
||||
static bool s_faulted;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 틱
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>런너가 매 프레임 부른다(에디트 모드에서는 프로브가 직접 부를 수 있다).</summary>
|
||||
internal static void Tick()
|
||||
{
|
||||
if (s_faulted || !Active) return;
|
||||
var cfg = Cfg;
|
||||
float now = Time.unscaledTime;
|
||||
if (now < s_nextPoll) return;
|
||||
s_nextPoll = now + Mathf.Max(0.02f, cfg.autoCombatPollSeconds);
|
||||
|
||||
// 🔴 Debug.LogException 을 쓰지 않는다 — ErrorLogHookManager 가 그것을 플레이어 팝업으로 띄운다(같은 커밋 §D-1).
|
||||
try
|
||||
{
|
||||
PollBoundary(cfg);
|
||||
if (now < s_iconDeadline) SyncPadIcon();
|
||||
}
|
||||
catch (System.Exception ex) { s_faulted = true; Log("중단(예외) — " + ex.Message); }
|
||||
}
|
||||
|
||||
static void PollBoundary(WLCombatCoreSettings cfg)
|
||||
{
|
||||
var pc = MyValue.MyPC; // 파괴된 참조는 Unity != 로 걸러진다
|
||||
if (DSUtil.CheckNull(pc)) { s_lastPc = null; return; } // PC 없음(타이틀·로딩) — 다음 등장을 경계로 본다
|
||||
|
||||
// InGameInfo 가 아직 없으면 경계를 「소비」하지 않는다 — 다음 폴링에서 같은 경계를 다시 본다.
|
||||
var info = InGameInfo.Ins;
|
||||
if (DSUtil.CheckNull(info)) return;
|
||||
|
||||
int map = MyValue.MyChoiceMapData != null ? MyValue.MyChoiceMapData.n_MapID : int.MinValue;
|
||||
bool pcChanged = !ReferenceEquals(pc, s_lastPc);
|
||||
bool mapChanged = map != s_lastMapId;
|
||||
if (!pcChanged && !mapChanged) return;
|
||||
|
||||
s_lastPc = pc; s_lastMapId = map;
|
||||
Boundaries++;
|
||||
LastReason = pcChanged ? "pc" : ("map " + map);
|
||||
|
||||
// WL 인게임 맵(WL_Nature)은 BattleMapConfig n_MapID −1 · e_MapType = Stage 로 실측 — 로비가 아니다.
|
||||
if (!cfg.autoCombatInLobby && info.Get_GameMode() == eGameMode.Lobby)
|
||||
{
|
||||
SkippedLobby++;
|
||||
Log("로비 — 건너뜀(" + LastReason + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsAutoOn) { AlreadyOn++; Log("이미 ON — 무동작(" + LastReason + ")"); ArmIcon(cfg); return; }
|
||||
|
||||
pc.Set_Auto(); // m_Auto = true + eCommon.Auto = 1 (Actor.cs:2260)
|
||||
Applied++;
|
||||
Log("자동전투 ON 적용 #" + Applied + " (" + LastReason + ") → auto=" + (IsAutoOn ? "1" : "0"));
|
||||
ArmIcon(cfg);
|
||||
}
|
||||
|
||||
static void ArmIcon(WLCombatCoreSettings cfg)
|
||||
{
|
||||
if (cfg.autoCombatIconRetrySeconds > 0f)
|
||||
s_iconDeadline = Time.unscaledTime + cfg.autoCombatIconRetrySeconds;
|
||||
}
|
||||
|
||||
/// <summary>전투 패드 자동 버튼 아이콘을 현재 값에 맞춘다. UI 파일 수정 0 — public 필드만 읽고 쓴다.</summary>
|
||||
static void SyncPadIcon()
|
||||
{
|
||||
var atlas = UIAtlasMgr.Ins;
|
||||
if (DSUtil.CheckNull(atlas)) return;
|
||||
var want = atlas.Get_Sprite(IsAutoOn ? "autotarget_on" : "autotarget_off");
|
||||
if (want == null) return;
|
||||
|
||||
var uis = Object.FindObjectsByType<BattleUI>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < uis.Length; i++)
|
||||
{
|
||||
var imgs = uis[i].images;
|
||||
if (imgs == null || imgs.Length == 0) continue;
|
||||
Image img = imgs[0];
|
||||
if (img == null || img.sprite == want) continue;
|
||||
img.sprite = want;
|
||||
IconFixed++;
|
||||
s_iconDeadline = 0f; // 한 번 맞췄으면 폴링을 멈춘다
|
||||
}
|
||||
}
|
||||
|
||||
static void Log(string msg)
|
||||
{
|
||||
LastLog = msg;
|
||||
var c = Cfg;
|
||||
if (c != null && c.verboseLog) Debug.Log("[AutoCombatOnEnter] " + msg);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 진단 · 초기화
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>프로브용: 카운터·경계 상태 초기화.</summary>
|
||||
public static void ResetDiagnostics()
|
||||
{
|
||||
Boundaries = Applied = AlreadyOn = SkippedLobby = IconFixed = 0;
|
||||
LastReason = LastLog = "";
|
||||
s_lastPc = null; s_lastMapId = int.MinValue;
|
||||
s_nextPoll = s_iconDeadline = 0f; s_faulted = false;
|
||||
}
|
||||
|
||||
/// <summary>프로브용: 러너 없이 1회 폴링(강제 틱).</summary>
|
||||
public static void ForceTick() { s_nextPoll = 0f; Tick(); }
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 런너 (숨김 GameObject 1개 · 플레이 모드 전용)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
static AutoCombatOnEnterRunner s_runner;
|
||||
|
||||
internal static void EnsureRunner()
|
||||
{
|
||||
if (!Application.isPlaying || !DSUtil.CheckNull(s_runner)) return;
|
||||
var go = new GameObject("[WL813x] AutoCombatOnEnterRunner");
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
s_runner = go.AddComponent<AutoCombatOnEnterRunner>();
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
static void Boot()
|
||||
{
|
||||
if (Active) EnsureRunner(); // 꺼져 있으면 오브젝트 0(C8)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>AutoCombatOnEnter 의 시간 축. 숨김 GameObject 1개 · 코루틴 0.</summary>
|
||||
internal sealed class AutoCombatOnEnterRunner : MonoBehaviour
|
||||
{
|
||||
void Update() { AutoCombatOnEnter.Tick(); }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2fa8f592a4729ce45916d5458f8882ce
|
||||
|
|
@ -107,5 +107,44 @@ namespace WL.Combat.Core
|
|||
|
||||
[Tooltip("화면 밖 판정 여유(뷰포트 비율). IsInView 는 [−margin, 1+margin] 안이면 화면 안으로 본다.")]
|
||||
public float viewMargin = 0.1f;
|
||||
|
||||
// ─────────────────────────────────────────── 핫픽스 (WL-813x · PD 지시 #813)
|
||||
// 🔴 아래 두 기능은 위 `enabled`(코어 스위치)·RuntimeDisabled 와 무관하게 각자의 플래그만 본다.
|
||||
// 이유 = 코어를 끈 A/B 중에도 에러 팝업이 되살아나면 안 된다(Q3 D-1 은 PD 시연 차단 등급).
|
||||
// 롤백은 이 플래그 각각을 0 으로(에셋만 수정 · 코드 무수정 · C8).
|
||||
[Header("몹 NavMesh 복구 (MobNavRecovery · Q3 D-1 / Q2 D-4 근본 수정)")]
|
||||
[Tooltip("몬스터가 점프·넉백으로 NavMesh 밖에 남거나 에이전트가 꺼진 채 굳으면 가장 가까운 NavMesh 위로 되돌린다. " +
|
||||
"끄면 원본 동작 100%(= Actor.Snap_toNavMesh 의 맵 반경 2 m 스냅만 남는다).")]
|
||||
public bool mobNavRecoveryEnabled = true;
|
||||
|
||||
[Tooltip("이상 상태(에이전트 off 또는 NavMesh 밖)를 이 시간 이상 유지할 때만 손댄다(초). " +
|
||||
"점프(Co_JumpToPosition 0.25 s · Co_Jump)·넉백처럼 원본이 스스로 복구하는 구간을 건드리지 않기 위한 유예. 실측 근거 = Anubis Skill2 점프 0.25 s.")]
|
||||
public float mobNavRecoveryGraceSeconds = 1.5f;
|
||||
|
||||
[Tooltip("전수 재수집(FindObjectsByType<MobActor>) 주기(초). 그 사이에는 캐시된 목록만 검사한다.")]
|
||||
public float mobNavRecoveryRescanSeconds = 3f;
|
||||
|
||||
[Tooltip("캐시된 목록을 검사하는 주기(초).")]
|
||||
public float mobNavRecoveryCheckSeconds = 0.25f;
|
||||
|
||||
[Tooltip("복귀 지점을 찾을 1차 NavMesh 표집 반경(m). 실패하면 아래 원거리 반경으로 한 번 더 시도한다.")]
|
||||
public float mobNavRecoveryNearRadius = 4f;
|
||||
|
||||
[Tooltip("1차 실패 시의 원거리 표집 반경(m). 보스가 아레나 밖 지형으로 크게 튄 경우를 위한 값.")]
|
||||
public float mobNavRecoveryFarRadius = 30f;
|
||||
|
||||
[Header("자동전투 기본 ON (AutoCombatOnEnter · Q3 D-2)")]
|
||||
[Tooltip("맵(런) 진입 시 자동전투가 꺼져 있으면 1회 켠다. 진입 뒤 플레이어가 직접 끈 것은 다시 켜지 않는다. " +
|
||||
"근거 = Q3 실측 자동 OFF 60 s 0킬 / ON 60 s 23킬.")]
|
||||
public bool autoCombatOnEnter = true;
|
||||
|
||||
[Tooltip("로비에서도 켤지. 기본 off — 로비는 적이 없어 자동 타겟팅이 의미가 없다.")]
|
||||
public bool autoCombatInLobby = false;
|
||||
|
||||
[Tooltip("진입 경계 폴링 주기(초).")]
|
||||
public float autoCombatPollSeconds = 0.25f;
|
||||
|
||||
[Tooltip("적용 뒤 전투 패드 자동 버튼 아이콘(BattleUI.images[0])을 이 시간 동안 재시도로 맞춘다(초). UI 가 늦게 뜨는 경우 대비 · 0 이면 아이콘을 건드리지 않는다.")]
|
||||
public float autoCombatIconRetrySeconds = 8f;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b34af33dcf64cd549a2c2f4dab4fedd9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MobNavRecovery.cs — 몬스터 NavMeshAgent 이탈 복구 (Q3 D-1 / Q2 D-4 근본 수정)
|
||||
//
|
||||
// PD 지시 #813 · 발주서 WL-813x §1-1 (2026-09-09) · 근거 = qa/W2-3_바퀴2.md D-1 · qa/W2-2_스모크.md D-4
|
||||
//
|
||||
// ■ 관측된 결함 (QA 실측)
|
||||
// 플레이 중 화면 중앙에 모달 에러 팝업 —
|
||||
// `"Resume" can only be called on an active agent that has been placed on a NavMesh.`
|
||||
// 보스(Anubis)가 Skill2 를 쓰는 순간 발생하고, 한 번 뜬 뒤로는 세션 끝까지 반복 출력된다.
|
||||
//
|
||||
// ■ 원인 사슬 (코드 실측)
|
||||
// ① Boss_Anubis.Co_Skill2 → Co_ShootSkill2 가 JumpToPosition(Get_PositionBack/Right/Foward(5f), onNavMesh:false)
|
||||
// 로 3회 점프한다(Boss_Anubis.cs:140).
|
||||
// ② Actor.Co_JumpToPosition(Actor.cs:1867) 은 점프 동안 `m_NavMeshAgent.enabled = false` 로 끄고
|
||||
// 트랜스폼을 직접 옮긴 뒤 다시 켠다. 착지 보정은 Snap_toNavMesh() = 맵 데이터 mobNavSnapRadius(WL_Nature = 2 m)
|
||||
// 뿐이라, 착지 지점이 NavMesh 에서 2 m 넘게 벗어나면 스냅이 실패하고 에이전트는 **켜졌지만 NavMesh 밖**으로 남는다.
|
||||
// (Q3 D-4 실측 — 이 보스는 아레나(139,−20) 를 벗어나 (128,−9) · 존2(147,30) 까지 나가 있었다.)
|
||||
// ③ 다음 Co_Skill2 첫 줄의 Change_MobStatus(eMobStatus.Skill) → MobActor.cs:339 이 가드 없이
|
||||
// `m_NavMeshAgent.isStopped = false`(= Resume) 를 대입 → 예외.
|
||||
// ④ Util/ErrorLogHookManager.cs:26-35 가 모든 Error/Exception 을 Popup.Ins.Set(ePopupType.One, 174, …) 로
|
||||
// 플레이어 팝업으로 띄운다(#if !FGB_LIVE) → 화면 중앙 모달.
|
||||
//
|
||||
// ■ 이 파일이 고치는 것 = ②
|
||||
// MobActor.cs:339 의 1줄 가드(같은 커밋)는 **예외만** 막는다. 그 가드만으로는 보스가 NavMesh 밖에 굳은 채
|
||||
// 영영 못 움직이는 상태(추격·순찰 정지)가 남는다. 여기서 그 상태 자체를 되돌린다 —
|
||||
// 에이전트가 꺼졌거나 NavMesh 밖인 몬스터를 유예 시간 뒤 가장 가까운 NavMesh 위로 Set_Warp 한다.
|
||||
//
|
||||
// ■ 원본 훅 0 — 폴링만 쓴다(Survival.PollRunBoundary · BossArenaRunner 선례).
|
||||
// 유예(graceSeconds)를 두는 이유: 점프·넉백 중에는 원본이 의도적으로 에이전트를 끈다.
|
||||
// 그 구간을 건드리면 점프가 깨진다. 실측 = Anubis 점프 0.25 s · 기본 유예 1.5 s.
|
||||
//
|
||||
// ■ 값(C45) 전부 WLCombatCoreSettings(에셋 Assets/WL/Combat/Settings/Resources/WL/WLCombatCoreSettings.asset).
|
||||
// 게임 수치 상수 0. mobNavRecoveryEnabled = 0 이면 러너 자체가 뜨지 않는다(C8 롤백).
|
||||
// 🔴 코어 스위치(WLCombatCoreSettings.Enabled)에는 의존하지 않는다 — 코어를 끈 A/B 중에도 팝업이 되살아나면 안 된다.
|
||||
//
|
||||
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(Actor/MobActor 가 Assembly-CSharp 에 있다).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using WL.Combat.Core;
|
||||
|
||||
namespace WL.Combat.Nav
|
||||
{
|
||||
/// <summary>몬스터 NavMeshAgent 이탈 복구. 정적 · 새 Manager/Singleton 0(런너는 숨김 GameObject 1개).</summary>
|
||||
public static class MobNavRecovery
|
||||
{
|
||||
static WLCombatCoreSettings Cfg { get { return WLCombatCoreSettings.Instance; } }
|
||||
|
||||
/// <summary>기능이 살아 있는가. 에셋이 없거나 플래그가 꺼져 있으면 false = 원본 동작 100%.</summary>
|
||||
public static bool Active
|
||||
{
|
||||
get { var c = Cfg; return c != null && c.mobNavRecoveryEnabled && !RuntimeDisabled; }
|
||||
}
|
||||
|
||||
/// <summary>진단·A/B 전용 런타임 스위치(WLHitFeel.RuntimeDisabled 선례). Play 종료 시 도메인 리로드로 자동 false.</summary>
|
||||
public static bool RuntimeDisabled;
|
||||
|
||||
// ───────────────────────────────────────── 진단 카운터 (프로브가 읽는다)
|
||||
public static int Repaired, RepairFailed, Scans, AnomaliesSeen;
|
||||
public static string LastLog = "";
|
||||
public static Vector3 LastRepairFrom, LastRepairTo;
|
||||
|
||||
// ───────────────────────────────────────── 내부 상태
|
||||
static MobActor[] s_mobs = new MobActor[0];
|
||||
static readonly Dictionary<int, float> s_anomalySince = new Dictionary<int, float>();
|
||||
static float s_nextRescan, s_nextCheck;
|
||||
static bool s_faulted;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 틱
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>런너가 매 프레임 부른다(에디트 모드에서는 프로브가 직접 부를 수 있다).</summary>
|
||||
internal static void Tick()
|
||||
{
|
||||
if (s_faulted || !Active) return;
|
||||
var cfg = Cfg;
|
||||
float now = Time.unscaledTime;
|
||||
|
||||
if (now >= s_nextRescan)
|
||||
{
|
||||
s_nextRescan = now + Mathf.Max(0.1f, cfg.mobNavRecoveryRescanSeconds);
|
||||
Rescan();
|
||||
}
|
||||
if (now < s_nextCheck) return;
|
||||
s_nextCheck = now + Mathf.Max(0.02f, cfg.mobNavRecoveryCheckSeconds);
|
||||
|
||||
// 🔴 Debug.LogException 을 쓰지 않는다 — ErrorLogHookManager 가 그것을 다시 플레이어 팝업으로 띄운다.
|
||||
// 이 파일의 목적이 그 팝업을 없애는 것이므로, 사고가 나면 조용히 자기 자신만 끈다(원본 동작으로 복귀).
|
||||
try { CheckAll(cfg, now); }
|
||||
catch (System.Exception ex) { s_faulted = true; Log("중단(예외) — " + ex.Message); }
|
||||
}
|
||||
|
||||
static void Rescan()
|
||||
{
|
||||
// FindObjectsInactive.Exclude — 비활성 몹은 풀에 들어간 사체다(Actor.cs:633 에서 에이전트를 끈다).
|
||||
s_mobs = Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
Scans++;
|
||||
if (s_anomalySince.Count > 64) s_anomalySince.Clear(); // 풀 재사용으로 쌓인 죽은 키 정리
|
||||
}
|
||||
|
||||
static void CheckAll(WLCombatCoreSettings cfg, float now)
|
||||
{
|
||||
// 보스는 목록 갱신(3 s)을 기다리지 않고 매 검사마다 본다 — 결함의 실제 피해자다.
|
||||
var boss = WL.Combat.Boss.BossArena.Boss;
|
||||
if (!DSUtil.CheckNull(boss)) CheckOne(cfg, boss, now);
|
||||
|
||||
var mobs = s_mobs;
|
||||
for (int i = 0; i < mobs.Length; i++)
|
||||
{
|
||||
var m = mobs[i];
|
||||
if (DSUtil.CheckNull(m) || ReferenceEquals(m, boss)) continue;
|
||||
CheckOne(cfg, m, now);
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckOne(WLCombatCoreSettings cfg, MobActor mob, float now)
|
||||
{
|
||||
if (mob.IsDead()) { s_anomalySince.Remove(mob.GetInstanceID()); return; }
|
||||
|
||||
var agent = mob.Get_Agent();
|
||||
if (DSUtil.CheckNull(agent)) return;
|
||||
|
||||
int key = mob.GetInstanceID();
|
||||
|
||||
// 정상 = 켜져 있고 NavMesh 위. isOnNavMesh 는 꺼진 에이전트에도 false 를 준다.
|
||||
if (agent.enabled && agent.isOnNavMesh) { s_anomalySince.Remove(key); return; }
|
||||
|
||||
float since;
|
||||
if (!s_anomalySince.TryGetValue(key, out since))
|
||||
{
|
||||
s_anomalySince[key] = now; // 점프·넉백일 수 있다 — 유예를 준다
|
||||
AnomaliesSeen++;
|
||||
return;
|
||||
}
|
||||
if (now - since < Mathf.Max(0f, cfg.mobNavRecoveryGraceSeconds)) return;
|
||||
|
||||
Repair(cfg, mob, agent, key);
|
||||
}
|
||||
|
||||
static void Repair(WLCombatCoreSettings cfg, MobActor mob, NavMeshAgent agent, int key)
|
||||
{
|
||||
Vector3 from = mob.Get_position();
|
||||
NavMeshHit hit;
|
||||
bool found = NavMesh.SamplePosition(from, out hit, Mathf.Max(0.1f, cfg.mobNavRecoveryNearRadius), NavMesh.AllAreas)
|
||||
|| NavMesh.SamplePosition(from, out hit, Mathf.Max(0.1f, cfg.mobNavRecoveryFarRadius), NavMesh.AllAreas);
|
||||
|
||||
s_anomalySince.Remove(key); // 성공이든 실패든 창을 다시 연다(실패 시 다음 유예 뒤 재시도)
|
||||
|
||||
if (!found)
|
||||
{
|
||||
RepairFailed++;
|
||||
Log("복구 실패 — 반경 " + cfg.mobNavRecoveryFarRadius + " m 안에 NavMesh 없음 " + from);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set_Warp 이 에이전트를 다시 켜고(Actor.cs:2116) 맵 스냅 반경을 한 번 더 적용한 뒤 Warp 한다 — 이동 로직 복제 0.
|
||||
mob.Set_Warp(hit.position);
|
||||
Repaired++;
|
||||
LastRepairFrom = from; LastRepairTo = hit.position;
|
||||
Log("복구 #" + Repaired + " " + mob.name + " " + from + " → " + hit.position);
|
||||
}
|
||||
|
||||
static void Log(string msg)
|
||||
{
|
||||
LastLog = msg;
|
||||
var c = Cfg;
|
||||
if (c != null && c.verboseLog) Debug.Log("[MobNavRecovery] " + msg);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 진단 · 초기화
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>프로브용: 카운터·유예 창 초기화.</summary>
|
||||
public static void ResetDiagnostics()
|
||||
{
|
||||
Repaired = RepairFailed = Scans = AnomaliesSeen = 0;
|
||||
LastLog = ""; LastRepairFrom = LastRepairTo = Vector3.zero;
|
||||
s_anomalySince.Clear(); s_mobs = new MobActor[0];
|
||||
s_nextRescan = s_nextCheck = 0f; s_faulted = false;
|
||||
}
|
||||
|
||||
/// <summary>프로브용: 러너 없이 1회 검사(에디트 모드 · 강제 틱).</summary>
|
||||
public static void ForceTick()
|
||||
{
|
||||
s_nextRescan = s_nextCheck = 0f;
|
||||
Tick();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 런너 (숨김 GameObject 1개 · 플레이 모드 전용)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
static MobNavRecoveryRunner s_runner;
|
||||
|
||||
internal static void EnsureRunner()
|
||||
{
|
||||
if (!Application.isPlaying || !DSUtil.CheckNull(s_runner)) return;
|
||||
var go = new GameObject("[WL813x] MobNavRecoveryRunner");
|
||||
go.hideFlags = HideFlags.HideAndDontSave; // 씬 전환에도 살아남고 씬·프리팹에 저장되지 않는다
|
||||
s_runner = go.AddComponent<MobNavRecoveryRunner>();
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
static void Boot()
|
||||
{
|
||||
if (Active) EnsureRunner(); // 꺼져 있으면 오브젝트 0(C8)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>MobNavRecovery 의 시간 축. 숨김 GameObject 1개 · 코루틴 0.</summary>
|
||||
internal sealed class MobNavRecoveryRunner : MonoBehaviour
|
||||
{
|
||||
void Update() { MobNavRecovery.Tick(); }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 862de09b7a5752e4283ac14655136423
|
||||
|
|
@ -28,3 +28,13 @@ MonoBehaviour:
|
|||
clusterBurstMax: 1
|
||||
killCamCooldownSeconds: 8
|
||||
viewMargin: 0.1
|
||||
mobNavRecoveryEnabled: 1
|
||||
mobNavRecoveryGraceSeconds: 1.5
|
||||
mobNavRecoveryRescanSeconds: 3
|
||||
mobNavRecoveryCheckSeconds: 0.25
|
||||
mobNavRecoveryNearRadius: 4
|
||||
mobNavRecoveryFarRadius: 30
|
||||
autoCombatOnEnter: 1
|
||||
autoCombatInLobby: 0
|
||||
autoCombatPollSeconds: 0.25
|
||||
autoCombatIconRetrySeconds: 8
|
||||
|
|
|
|||
Loading…
Reference in New Issue