Merge branch 'wl/systems/WL-816u-pad-and-retry'
This commit is contained in:
commit
1d11d8470d
|
|
@ -0,0 +1,170 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ActiveSkillLock.cs — 액티브 스킬 사용 임시 OFF (WL-816u ③ · #816)
|
||||
//
|
||||
// PD 지시 원문(2026-09-15) 「우선 엑티브 스킬은 사용 Off 해줘.」
|
||||
//
|
||||
// ■ 무엇을 끄나 / 무엇을 남기나
|
||||
// 끈다 : 자동전투 AI 의 스킬 시전 · 전투 패드 스킬 버튼 4칸
|
||||
// 남긴다: 기본 공격 · 이동 · 물약 · 회피 · 자동전투 토글 자체 · 몹/보스 스킬
|
||||
//
|
||||
// ■ 왜 이 지점인가 (코드 실측 · 원본 0줄)
|
||||
// `PCActor.Update()`(PCActor.cs:79~101) 의 자동 스킬 루프는 매 프레임
|
||||
// `if (DSUtil.CheckNull(arr_magicData[i]) || arr_magicData[i].e_SkillType == eSkillType.Passive) continue;`
|
||||
// 로 시작한다. 즉 **`arr_magicData[i]` 가 null 이면 그 슬롯은 쿨다운도 안 돌고 `Use_Skill` 도 안 불린다.**
|
||||
// `arr_magicData` 는 `ReSet()`(PCActor.cs:189)에서
|
||||
// `table_skilllist.Ins.Get_Data_orNull(m_ServerData.Get_EquipSkillID(i))`
|
||||
// 로 다시 채워지므로, **매 프레임 비워 두면** 언제 ReSet 이 돌아도 다음 프레임에 다시 잠긴다.
|
||||
// · `arr_magicData` 는 PCActor 의 private 필드다 → 원본을 고치지 않으려면 리플렉션뿐이다
|
||||
// (FieldInfo 는 1회만 찾아 캐시 · 프레임당 비용 = 배열 6칸 쓰기).
|
||||
// · `ServerData.Equip` 은 **건드리지 않는다** — 장착 데이터를 지우면 스킬 UI·미션·서버 프리셋까지
|
||||
// 영향이 가고, 되돌릴 때 원래 ID 를 복원해야 한다. 여기서는 **이번 PC 인스턴스의 런타임 캐시만** 비운다.
|
||||
//
|
||||
// ■ 되돌리기(C8) — `WLCombatCoreSettings.activeSkillsEnabled = true` **값 하나**.
|
||||
// 그러면 이 러너가 아예 뜨지 않고(오브젝트 0), 다음 `ReSet()` 이 원래 스킬을 그대로 다시 읽는다.
|
||||
//
|
||||
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 가 플레이어 팝업을 띄운다).
|
||||
// 🔴 `Assets/Script/**` · `Assets/FarmingIsland/**` 는 이 파일 때문에 한 줄도 바뀌지 않는다.
|
||||
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(PCActor 가 Assembly-CSharp 에 있다).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using WL.Combat.Core;
|
||||
|
||||
namespace WL.Combat.Auto
|
||||
{
|
||||
/// <summary>액티브 스킬 사용 잠금. 정적 · 새 Manager/Singleton 0(런너는 숨김 GameObject 1개).</summary>
|
||||
public static class ActiveSkillLock
|
||||
{
|
||||
static WLCombatCoreSettings Cfg { get { return WLCombatCoreSettings.Instance; } }
|
||||
|
||||
/// <summary>잠겨 있는가(= 액티브 스킬 OFF). 에셋이 없으면 false = 원본 동작 100 %.</summary>
|
||||
public static bool Locked
|
||||
{
|
||||
get { var c = Cfg; return c != null && !c.activeSkillsEnabled && !RuntimeDisabled; }
|
||||
}
|
||||
|
||||
/// <summary>진단·A/B 전용 런타임 스위치. Play 종료 시 도메인 리로드로 자동 false.</summary>
|
||||
public static bool RuntimeDisabled;
|
||||
|
||||
// ── 진단 카운터 (프로브·보고가 읽는다 · 실측만)
|
||||
public static int Ticks, Cleared, PcSwaps;
|
||||
public static string LastLog = "";
|
||||
/// <summary>마지막으로 비우기 직전에 본 스킬 ID 들(= 잠그지 않았다면 시전됐을 스킬).</summary>
|
||||
public static int[] LastSeenSkillIds = new int[0];
|
||||
|
||||
static FieldInfo s_magicField;
|
||||
static Object s_lastPc;
|
||||
static float s_nextPoll;
|
||||
static bool s_faulted;
|
||||
|
||||
/// <summary>PCActor 의 private `arr_magicData` (SkillListTableData[6]). 1회만 찾는다.</summary>
|
||||
static FieldInfo MagicField
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_magicField == null)
|
||||
s_magicField = typeof(PCActor).GetField("arr_magicData",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
return s_magicField;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>런너가 매 프레임 부른다(프로브도 직접 부를 수 있다). 비운 슬롯 수를 돌려준다.</summary>
|
||||
public static int Tick()
|
||||
{
|
||||
if (s_faulted || !Locked) return 0;
|
||||
var cfg = Cfg;
|
||||
float poll = cfg != null ? cfg.activeSkillLockPollSeconds : 0f;
|
||||
if (poll > 0f)
|
||||
{
|
||||
float now = Time.unscaledTime;
|
||||
if (now < s_nextPoll) return 0;
|
||||
s_nextPoll = now + poll;
|
||||
}
|
||||
Ticks++;
|
||||
|
||||
var pc = MyValue.MyPC;
|
||||
if (DSUtil.CheckNull(pc)) { s_lastPc = null; return 0; }
|
||||
if (!ReferenceEquals(pc, s_lastPc)) { s_lastPc = pc; PcSwaps++; }
|
||||
|
||||
// 🔴 Debug.LogException 을 쓰지 않는다 — ErrorLogHookManager 가 플레이어 팝업으로 띄운다.
|
||||
try { return ClearOn(pc); }
|
||||
catch (System.Exception ex) { s_faulted = true; Log("중단(예외) — " + ex.Message); return 0; }
|
||||
}
|
||||
|
||||
/// <summary>한 PC 의 자동 스킬 캐시를 비운다(이미 비어 있으면 아무 것도 쓰지 않는다 = GC 0).</summary>
|
||||
public static int ClearOn(PCActor pc)
|
||||
{
|
||||
var f = MagicField;
|
||||
if (f == null || DSUtil.CheckNull(pc)) return 0;
|
||||
var arr = f.GetValue(pc) as SkillListTableData[];
|
||||
if (arr == null) return 0;
|
||||
|
||||
int n = 0;
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
{
|
||||
if (arr[i] == null) continue;
|
||||
if (LastSeenSkillIds.Length != arr.Length) LastSeenSkillIds = new int[arr.Length];
|
||||
LastSeenSkillIds[i] = arr[i].n_SkillID;
|
||||
arr[i] = null;
|
||||
n++;
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
Cleared += n;
|
||||
Log("액티브 스킬 잠금 — 슬롯 " + n + "칸 비움(누적 " + Cleared + ")");
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static void Log(string msg)
|
||||
{
|
||||
LastLog = msg;
|
||||
var c = Cfg;
|
||||
if (c != null && c.verboseLog) Debug.Log("[ActiveSkillLock] " + msg);
|
||||
}
|
||||
|
||||
/// <summary>프로브용 — 카운터 초기화.</summary>
|
||||
public static void ResetDiagnostics()
|
||||
{
|
||||
Ticks = Cleared = PcSwaps = 0; LastLog = "";
|
||||
s_lastPc = null; s_nextPoll = 0f; s_faulted = false;
|
||||
}
|
||||
|
||||
public static string Dump()
|
||||
{
|
||||
var c = Cfg;
|
||||
return "[ActiveSkillLock] 잠금=" + Locked +
|
||||
" (설정 activeSkillsEnabled=" + (c != null ? c.activeSkillsEnabled.ToString() : "에셋없음") +
|
||||
" · RuntimeDisabled=" + RuntimeDisabled + ")" +
|
||||
" · 틱 " + Ticks + " · 비운 슬롯 " + Cleared + " · PC 교체 " + PcSwaps +
|
||||
" · 필드 " + (MagicField != null) + " · " + LastLog;
|
||||
}
|
||||
|
||||
// ── 런너 (숨김 GameObject 1개 · 플레이 모드 전용) ────────────────────
|
||||
static ActiveSkillLockRunner s_runner;
|
||||
|
||||
internal static void EnsureRunner()
|
||||
{
|
||||
if (!Application.isPlaying || !DSUtil.CheckNull(s_runner)) return;
|
||||
var go = new GameObject("[WL816u] ActiveSkillLockRunner");
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
s_runner = go.AddComponent<ActiveSkillLockRunner>();
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
static void Boot()
|
||||
{
|
||||
if (Locked) EnsureRunner(); // 꺼져 있으면(= 스킬 사용 ON) 오브젝트 0 (C8)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>ActiveSkillLock 의 시간 축. 숨김 GameObject 1개 · 코루틴 0.</summary>
|
||||
internal sealed class ActiveSkillLockRunner : MonoBehaviour
|
||||
{
|
||||
void Update() { ActiveSkillLock.Tick(); }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b05df5564c97a154e9763358004191e6
|
||||
|
|
@ -146,5 +146,16 @@ namespace WL.Combat.Core
|
|||
|
||||
[Tooltip("적용 뒤 전투 패드 자동 버튼 아이콘(BattleUI.images[0])을 이 시간 동안 재시도로 맞춘다(초). UI 가 늦게 뜨는 경우 대비 · 0 이면 아이콘을 건드리지 않는다.")]
|
||||
public float autoCombatIconRetrySeconds = 8f;
|
||||
|
||||
// ── WL-816u ③ (2026-09-15 PD) 「우선 엑티브 스킬은 사용 Off 해줘.」
|
||||
[Header("액티브 스킬 사용 (WL-816u ③ · PD 임시 지시)")]
|
||||
[Tooltip("🔴 **되돌리기 = 이 값 하나**. false 면 액티브 스킬 사용을 전부 끈다 — " +
|
||||
"① 자동전투 AI 의 스킬 시전(PCActor.Update 의 자동 스킬 루프가 읽는 arr_magicData 를 비운다) " +
|
||||
"② 전투 패드의 스킬 버튼 4칸(WLBattlePadLayout 이 활성 슬롯을 0 으로 본다). " +
|
||||
"기본 공격·이동·물약·회피·자동전투 자체는 그대로다. true = 816t 까지의 동작 100 %.")]
|
||||
public bool activeSkillsEnabled = false;
|
||||
|
||||
[Tooltip("위가 false 일 때 스킬 배열을 비우는 주기(초). 0 이하면 매 프레임.")]
|
||||
public float activeSkillLockPollSeconds = 0f;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,3 +38,5 @@ MonoBehaviour:
|
|||
autoCombatInLobby: 0
|
||||
autoCombatPollSeconds: 0.25
|
||||
autoCombatIconRetrySeconds: 8
|
||||
activeSkillsEnabled: 0
|
||||
activeSkillLockPollSeconds: 0
|
||||
|
|
|
|||
|
|
@ -21,18 +21,20 @@ MonoBehaviour:
|
|||
fixIslandDuplicates: 1
|
||||
islandCameraOwner: 1
|
||||
hidePcWatchSeconds: 6
|
||||
islandSaveKey:
|
||||
islandSaveKey:
|
||||
swapIslandPlayer: 1
|
||||
pcPrefabPath: Assets/Res_Addr/PC/LH_M05.prefab
|
||||
islandPcScale: 0.7
|
||||
mirrorFiAnimator: 1
|
||||
fiPlayerModelName: Stickman
|
||||
interactRadius: 3.5
|
||||
promptFormat: "{0} 들어가기"
|
||||
enterButtonText: "들어가기"
|
||||
promptFormat: "{0} \uB4E4\uC5B4\uAC00\uAE30"
|
||||
enterButtonText: "\uB4E4\uC5B4\uAC00\uAE30"
|
||||
enterKey: 101
|
||||
gateHeight: 3.2
|
||||
gateWidth: 2.4
|
||||
fontAssetPath: Assets/ThirdParty/TextMesh Pro/Addressables/Fonts & Materials/Font
|
||||
SDF.asset
|
||||
rewardEnabled: 1
|
||||
goldPerKill: 12
|
||||
goldClearBonus: 60
|
||||
|
|
@ -50,16 +52,12 @@ MonoBehaviour:
|
|||
farmReHideDelaySeconds: 3
|
||||
matchNewTileMaterials: 1
|
||||
dungeonUnlockPrefsKey: WL_Island_DungeonsUnlocked
|
||||
dungeonUnlockPrices:
|
||||
- 10
|
||||
- 30
|
||||
- 60
|
||||
- 100
|
||||
dungeonUnlockPrices: 0a0000001e0000003c00000064000000
|
||||
maxDungeons: 4
|
||||
platformUseFarmPosition: 1
|
||||
platformPosition: {x: 0, y: 0, z: 0}
|
||||
platformOffset: {x: 0, y: 0, z: 0}
|
||||
platformPrefabPath:
|
||||
platformPrefabPath:
|
||||
platformShowPad: 0
|
||||
platformCenterOnPlate: 1
|
||||
platformPlateLift: 0.03
|
||||
|
|
@ -67,12 +65,15 @@ MonoBehaviour:
|
|||
platformRadius: 1.7
|
||||
platformColor: {r: 0.92, g: 0.72, b: 0.24, a: 1}
|
||||
platformUseToon: 1
|
||||
platformLabelFormat: "던전 열기"
|
||||
platformLabelFormat: "\uB358\uC804 \uC5F4\uAE30"
|
||||
platformLabelHeight: 2.2
|
||||
platformTriggerSize: 3.4
|
||||
platformTriggerHeight: 3
|
||||
platformRespawnDelaySeconds: 1.3
|
||||
platformSnapToGround: 1
|
||||
platformScale: 0.5
|
||||
platformCenterOnZone: 1
|
||||
platformCenterVisibleOnly: 1
|
||||
platformRequireStepOff: 1
|
||||
platformStepOffTimeoutSeconds: 120
|
||||
applyStartCoin: 1
|
||||
|
|
@ -100,7 +101,7 @@ MonoBehaviour:
|
|||
dungeons:
|
||||
- enabled_: 1
|
||||
id: 1
|
||||
displayName: "던전 1"
|
||||
displayName: "\uB358\uC804 1"
|
||||
sceneName: WL_Dungeon01
|
||||
mapId: 901
|
||||
stageIndex: 10
|
||||
|
|
@ -108,12 +109,12 @@ MonoBehaviour:
|
|||
gateYaw: 180
|
||||
gateScale: 1
|
||||
gateColor: {r: 0.45, g: 0.32, b: 0.62, a: 1}
|
||||
gatePrefabPath:
|
||||
gatePrefabPath:
|
||||
platformUseFarm: 1
|
||||
platformPosition: {x: 0, y: 0, z: 0}
|
||||
- enabled_: 1
|
||||
id: 2
|
||||
displayName: "던전 2"
|
||||
displayName: "\uB358\uC804 2"
|
||||
sceneName: WL_Dungeon02
|
||||
mapId: 902
|
||||
stageIndex: 11
|
||||
|
|
@ -121,12 +122,12 @@ MonoBehaviour:
|
|||
gateYaw: 0
|
||||
gateScale: 1
|
||||
gateColor: {r: 0.28, g: 0.5, b: 0.66, a: 1}
|
||||
gatePrefabPath:
|
||||
gatePrefabPath:
|
||||
platformUseFarm: 0
|
||||
platformPosition: {x: -2, y: 0, z: -2}
|
||||
- enabled_: 1
|
||||
id: 3
|
||||
displayName: "던전 3"
|
||||
displayName: "\uB358\uC804 3"
|
||||
sceneName: WL_Dungeon01
|
||||
mapId: 903
|
||||
stageIndex: 12
|
||||
|
|
@ -134,12 +135,12 @@ MonoBehaviour:
|
|||
gateYaw: 180
|
||||
gateScale: 1
|
||||
gateColor: {r: 0.62, g: 0.42, b: 0.24, a: 1}
|
||||
gatePrefabPath:
|
||||
gatePrefabPath:
|
||||
platformUseFarm: 0
|
||||
platformPosition: {x: -2.5, y: 0, z: 8.5}
|
||||
- enabled_: 1
|
||||
id: 4
|
||||
displayName: "던전 4"
|
||||
displayName: "\uB358\uC804 4"
|
||||
sceneName: WL_Dungeon02
|
||||
mapId: 904
|
||||
stageIndex: 13
|
||||
|
|
@ -147,6 +148,6 @@ MonoBehaviour:
|
|||
gateYaw: 0
|
||||
gateScale: 1
|
||||
gateColor: {r: 0.55, g: 0.24, b: 0.3, a: 1}
|
||||
gatePrefabPath:
|
||||
gatePrefabPath:
|
||||
platformUseFarm: 0
|
||||
platformPosition: {x: 6, y: 0, z: -2}
|
||||
|
|
|
|||
|
|
@ -251,6 +251,11 @@ namespace WL.Island
|
|||
var farms = Object.FindObjectsByType<FIFarm>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
bool haveCenter = false;
|
||||
|
||||
// 🔴 WL-816u — 「열림 구역」 = 이 발판이 여는 자리 **전부**(보이는 밭 전체)다.
|
||||
// 816g 는 **처음 만난 밭 하나**의 중심을 썼다 → 밭이 여러 칸이면 구역 정중앙이 아니다(PD 지적).
|
||||
Bounds zone = new Bounds(); bool zoneSet = false;
|
||||
ZoneBoundsValid = false;
|
||||
|
||||
for (int i = 0; i < farms.Length; i++)
|
||||
{
|
||||
var f = farms[i];
|
||||
|
|
@ -270,6 +275,8 @@ namespace WL.Island
|
|||
{
|
||||
if (!boundsSet) { b = rends[r].bounds; boundsSet = true; }
|
||||
else b.Encapsulate(rends[r].bounds);
|
||||
if (!zoneSet) { zone = rends[r].bounds; zoneSet = true; } // WL-816u — 구역 전체
|
||||
else zone.Encapsulate(rends[r].bounds);
|
||||
}
|
||||
rends[r].enabled = false; s_hiddenRends.Add(rends[r]); RenderersHidden++;
|
||||
}
|
||||
|
|
@ -289,9 +296,22 @@ namespace WL.Island
|
|||
haveCenter = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 🔴 WL-816u — 구역 전체의 XZ 정중앙으로 덮어쓴다(값 1개로 816g 동작 복귀).
|
||||
if (zoneSet)
|
||||
{
|
||||
ZoneBounds = zone;
|
||||
ZoneBoundsValid = true;
|
||||
if (cfg.platformCenterOnZone != 0 && haveCenter)
|
||||
center = new Vector3(zone.center.x, zone.min.y, zone.center.z);
|
||||
}
|
||||
return haveCenter;
|
||||
}
|
||||
|
||||
/// <summary>🔴 WL-816u 진단 — 「열림 구역」(숨긴 밭 전부)의 월드 바운즈. 보고·프로브가 오차를 잰다.</summary>
|
||||
public static Bounds ZoneBounds;
|
||||
public static bool ZoneBoundsValid;
|
||||
|
||||
/// <summary>섬이 나중에 열리면 ① 그 섬의 밭을 다시 숨기고 ② 타일 재질을 기존 타일과 맞춘다(§1).</summary>
|
||||
static void HookIslandActivation(WLIslandSettings cfg, Scene scene)
|
||||
{
|
||||
|
|
@ -541,37 +561,43 @@ namespace WL.Island
|
|||
p.transform.SetParent(transform, true);
|
||||
Purchaser = p;
|
||||
|
||||
// 🔴 WL-816u — PD 「프레임과 글자 아이콘 크기 모두 50%로 줄여줘」.
|
||||
// **인스턴스 스케일만** 건드린다(프리팹 원본 0줄). 프레임·가격 글자·코인 아이콘이 한 덩어리로 줄어든다.
|
||||
// 스케일은 **바운즈 재기 전에** 적용해야 중심 보정이 줄어든 크기 기준으로 맞는다.
|
||||
float ps = cfg.platformScale > 0.001f ? cfg.platformScale : 1f;
|
||||
AppliedScale = ps;
|
||||
if (ps != 1f) p.transform.localScale = p.transform.localScale * ps;
|
||||
|
||||
// 🔴 FI 가격판은 프리팹 안에서 **루트보다 앞·아래**에 놓여 있다(실측 2026-09-13:
|
||||
// Frame/Price/Coin 이 전부 y = -0.49 · z = 루트+(-1/0/+1) — 잠긴 섬 자리의 **물 위**에 눕는 판이다).
|
||||
// 섬 위(y≈0)에 그대로 놓으면 **지면 아래로 들어가 안 보인다** → 프리팹 전체의 렌더 범위를 재서
|
||||
// 「밭 한가운데 · 바닥 바로 위」로 맞춘다. 프리팹은 한 글자도 고치지 않는다.
|
||||
if (cfg.platformCenterOnPlate != 0)
|
||||
{
|
||||
var rends = p.GetComponentsInChildren<Renderer>(true);
|
||||
Bounds b = new Bounds(); bool set = false;
|
||||
for (int i = 0; i < rends.Length; i++)
|
||||
{
|
||||
if (rends[i] == null) continue;
|
||||
if (!set) { b = rends[i].bounds; set = true; } else b.Encapsulate(rends[i].bounds);
|
||||
}
|
||||
if (set)
|
||||
Bounds b;
|
||||
if (VisibleBounds(p.transform, cfg.platformCenterVisibleOnly != 0, out b))
|
||||
{
|
||||
var t = transform.position;
|
||||
p.transform.position += new Vector3(t.x - b.center.x,
|
||||
t.y + cfg.platformPlateLift - b.min.y,
|
||||
t.z - b.center.z);
|
||||
VisibleBounds(p.transform, cfg.platformCenterVisibleOnly != 0, out PlateBounds);
|
||||
PlateBoundsValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 트리거를 **발판(가격판) 크기**로 줄인다(원본 프리팹은 섬 1칸 8 m 를 덮는 十자 두 개다).
|
||||
// 🔴 WL-816u — 판을 50 % 로 줄여도 **밟는 자리는 그대로**여야 한다(PD 는 크기만 말했다).
|
||||
// size 는 부모(p) 로컬 단위라 스케일만큼 역보정하면 월드 미터가 유지된다.
|
||||
var boxes = p.GetComponentsInChildren<BoxCollider>(true);
|
||||
float s = cfg.platformTriggerSize <= 0f ? 3.2f : cfg.platformTriggerSize;
|
||||
float inv = 1f / ps;
|
||||
var localCenter = p.transform.InverseTransformPoint(transform.position);
|
||||
for (int i = 0; i < boxes.Length; i++)
|
||||
{
|
||||
if (boxes[i] == null) continue;
|
||||
boxes[i].size = new Vector3(s, cfg.platformTriggerHeight, s);
|
||||
boxes[i].center = new Vector3(localCenter.x, cfg.platformTriggerHeight * 0.5f, localCenter.z);
|
||||
boxes[i].size = new Vector3(s * inv, cfg.platformTriggerHeight * inv, s * inv);
|
||||
boxes[i].center = new Vector3(localCenter.x, cfg.platformTriggerHeight * inv * 0.5f, localCenter.z);
|
||||
}
|
||||
|
||||
// 🔴 `Init` 은 `Purchaser.Start()` 보다 먼저여야 한다(Start 가 가격판을 그린다) — 같은 프레임이라 안전.
|
||||
|
|
@ -581,6 +607,34 @@ namespace WL.Island
|
|||
" · 트리거 " + s.ToString("F1") + " m · 더미 Island 연결");
|
||||
}
|
||||
|
||||
// ── WL-816u 진단(프로브·보고가 읽는다 · 실측만)
|
||||
public static float AppliedScale = 1f;
|
||||
public static Bounds PlateBounds;
|
||||
public static bool PlateBoundsValid;
|
||||
|
||||
/// <summary>
|
||||
/// 🔴 WL-816u — 「**보이는** 프레임」의 월드 바운즈.
|
||||
/// visibleOnly 면 파티클·트레일·꺼진 렌더러·꺼진 오브젝트를 제외한다(FI Purchaser 는 물보라 파티클을 품고 있다).
|
||||
/// </summary>
|
||||
public static bool VisibleBounds(Transform root, bool visibleOnly, out Bounds b)
|
||||
{
|
||||
b = new Bounds();
|
||||
bool set = false;
|
||||
var rends = root.GetComponentsInChildren<Renderer>(true);
|
||||
for (int i = 0; i < rends.Length; i++)
|
||||
{
|
||||
var r = rends[i];
|
||||
if (r == null) continue;
|
||||
if (visibleOnly)
|
||||
{
|
||||
if (r is ParticleSystemRenderer || r is TrailRenderer || r is LineRenderer) continue;
|
||||
if (!r.enabled || !r.gameObject.activeInHierarchy) continue;
|
||||
}
|
||||
if (!set) { b = r.bounds; set = true; } else b.Encapsulate(r.bounds);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
static System.Reflection.FieldInfo s_purchaserField;
|
||||
|
||||
/// <summary>FI 가 섬 구입에 쓰는 **바로 그 프리팹**(가격판·코인 아이콘·물보라·SFX 가 전부 들어 있다).</summary>
|
||||
|
|
|
|||
|
|
@ -295,6 +295,23 @@ namespace WL.Island
|
|||
[Tooltip("1 이면 발판 자리를 지면 높이에 맞춘다(위에서 아래로 레이).")]
|
||||
public int platformSnapToGround = 1;
|
||||
|
||||
// ── WL-816u (2026-09-15 PD) 「열림 발판의 크기는 열림 구역으로 설정 된 위치의 정중앙에 배치하고,
|
||||
// 프레임과 글자 아이콘 크기 모두 50%로 줄여줘 (현재는 너무 커)」
|
||||
[Tooltip("🔴 WL-816u — 발판(FI 가격판 프레임 + 가격 글자 + 코인 아이콘)의 인스턴스 스케일 배수. " +
|
||||
"PD 「50 %」 = 0.5. **되돌리기 = 1**(= 816t 까지의 크기). 프리팹 원본은 한 글자도 고치지 않는다. " +
|
||||
"트리거 한 변(platformTriggerSize)은 이 값과 무관하게 **월드 미터 그대로** 유지된다(스케일 역보정).")]
|
||||
public float platformScale = 0.5f;
|
||||
|
||||
[Tooltip("🔴 WL-816u — 1 이면 발판을 「열림 구역」 **전체**의 XZ 정중앙에 놓는다. " +
|
||||
"열림 구역 = 이 발판이 여는 자리(던전 1 = 숨겨 둔 밭 구역 전부 · 그 외 = 표의 platformPosition). " +
|
||||
"0 이면 816g 동작(= 처음 만난 밭 **하나**의 중심).")]
|
||||
public int platformCenterOnZone = 1;
|
||||
|
||||
[Tooltip("🔴 WL-816u — 1 이면 「보이는 프레임의 중심」을 맞춘다: 파티클·트레일·꺼진 렌더러를 " +
|
||||
"중심 계산에서 제외한다(FI Purchaser 프리팹에는 물보라 파티클이 들어 있어 전부 세면 중심이 밀린다). " +
|
||||
"0 이면 816g 동작(= 모든 렌더러 바운즈).")]
|
||||
public int platformCenterVisibleOnly = 1;
|
||||
|
||||
[Tooltip("1 이면 새 발판은 플레이어가 그 자리에서 **내려온 뒤에** 생긴다 — " +
|
||||
"골드가 많을 때 가만히 서서 던전이 연달아 팔리는 것을 막는다(816g 실측).")]
|
||||
public int platformRequireStepOff = 1;
|
||||
|
|
|
|||
|
|
@ -1173,6 +1173,11 @@ namespace WL.UI
|
|||
leftText = last ? g815.restartFromFirstText : g815.nextStageText;
|
||||
rightText = g815.toLobbyText;
|
||||
leftOn = !last || g815.showRestartOnLastStage;
|
||||
// 🔴 WL-816u — 던전은 왼쪽 버튼이 「같은 던전 재입장」이라 문구도 그에 맞춘다.
|
||||
// s_dungeonName == null(= #815 스테이지 모드)이면 이 두 줄은 한 번도 돌지 않는다.
|
||||
if (s_dungeonName != null && g815.dungeonModeEnabled && g815.dungeonPrimaryRetry &&
|
||||
!string.IsNullOrEmpty(g815.dungeonPrimaryText))
|
||||
{ leftText = g815.dungeonPrimaryText; leftOn = true; }
|
||||
}
|
||||
LayoutButton(s_nextButton, s_nextBg, s_nextLabel, s, u, leftOn ? -1 : 0, leftText, s.resultNextBgColor, s.resultNextLabelColor);
|
||||
LayoutButton(s_closeButton, s_closeBg, s_closeLabel, s, u, leftOn ? 1 : 0, rightText, s.resultCloseBgColor, s.resultCloseLabelColor);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ namespace WL.UI
|
|||
public static int MenuDeferred;
|
||||
public static int MenuReassertCalls, MenuReassertHits; // 원본이 다시 켠 것을 도로 끈 횟수
|
||||
public static int RetryCalls, NextCalls, LobbyCalls, RestartCalls;
|
||||
/// <summary>WL-816u — 던전 결과창 왼쪽 버튼이 「같은 던전 재입장」으로 돈 횟수(실측 진단).</summary>
|
||||
public static int DungeonRetryCalls;
|
||||
public static string LastLog = "";
|
||||
public static string LastMissingPath = "";
|
||||
public static bool InStage { get { return s_inStage; } }
|
||||
|
|
@ -382,6 +384,20 @@ namespace WL.UI
|
|||
/// <summary>[다음 스테이지] — 마지막 스테이지면 [처음부터](= Enter(0)).</summary>
|
||||
public static string OnNextStage()
|
||||
{
|
||||
// 🔴 WL-816u — PD 2026-09-15 「던전 진입 후 다음 던전으로 가기를 선택할 경우 몬스터가 등장하지 않고
|
||||
// 아무것도 할 수 없어. 임시로 스테이지 전환 없이 스테이지 1에 등장한 몬스터가 재등장하게 해줘.」
|
||||
// → 던전 모드에서는 왼쪽 버튼이 **같은 인덱스 재입장**(실패 팝업의 [다시 도전]과 **같은 경로**)이다.
|
||||
// 스테이지 모드(#815 · CurrentDungeon == null)는 한 줄도 지나가지 않는다.
|
||||
var gDg = St;
|
||||
if (gDg != null && gDg.dungeonPrimaryRetry && CurrentDungeon != null)
|
||||
{
|
||||
DungeonRetryCalls++;
|
||||
ClearFailPopup();
|
||||
bool okd = WL.Combat.Stage.StageDirector.Retry();
|
||||
LastLog = "던전 재입장 — StageDirector.Retry() = " + okd;
|
||||
return LastLog;
|
||||
}
|
||||
|
||||
var last = WL.Combat.Stage.StageDirector.LastResult;
|
||||
if (last.isLastStage)
|
||||
{
|
||||
|
|
@ -557,7 +573,7 @@ namespace WL.UI
|
|||
EnteredSeen = CountdownSeen = StartedSeen = EnemySeen = TimeSeen = ClearedSeen = FailedSeen = ExitedSeen = 0;
|
||||
MenuHideCalls = MenuRestoreCalls = MenuMissing = 0;
|
||||
MenuReassertCalls = MenuReassertHits = 0; s_nextReassert = 0f;
|
||||
RetryCalls = NextCalls = LobbyCalls = RestartCalls = 0;
|
||||
RetryCalls = NextCalls = LobbyCalls = RestartCalls = DungeonRetryCalls = 0;
|
||||
s_hits = s_maxChain = 0; s_potionsAtStart = PotionUse.UsedCount;
|
||||
LastLog = ""; LastMissingPath = "";
|
||||
return "상태 초기화";
|
||||
|
|
|
|||
|
|
@ -105,7 +105,10 @@ namespace WL.UI
|
|||
|
||||
var cards = target.skillCards;
|
||||
int total = cards != null ? cards.Length : 0;
|
||||
int active = Mathf.Clamp(s.activeSkillSlots, 0, total);
|
||||
// 🔴 WL-816u ③ — PD 「우선 엑티브 스킬은 사용 Off 해줘」. 잠금이 걸려 있으면 활성 슬롯 0
|
||||
// = 스킬 버튼 4칸이 전부 비활성(아래 SetActive(isSkill)). 되돌리기는 설정 값 하나.
|
||||
int want = WL.Combat.Auto.ActiveSkillLock.Locked ? 0 : s.activeSkillSlots;
|
||||
int active = Mathf.Clamp(want, 0, total);
|
||||
LastActiveSlots = active;
|
||||
LastReserveSlots = Mathf.Max(0, total - active);
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ MonoBehaviour:
|
|||
dungeonGoldRowEnabled: 1
|
||||
rowDungeonGoldLabel: "\uD68D\uB4DD \uACE8\uB4DC"
|
||||
rowDungeonGoldFormat: +{0}
|
||||
dungeonPrimaryRetry: 1
|
||||
dungeonPrimaryText: "\uB2E4\uC2DC \uB3C4\uC804"
|
||||
stageFailEnabled: 1
|
||||
failDeathMessage: "\uC4F0\uB7EC\uC84C\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC2DC \uB3C4\uC804\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?"
|
||||
failTimeoutMessage: "\uC2DC\uAC04 \uCD08\uACFC!\n\uB0A8\uC740 \uC801 {0}\uB9C8\uB9AC"
|
||||
|
|
|
|||
|
|
@ -311,6 +311,16 @@ namespace WL.UI
|
|||
[Tooltip("획득 골드 행 값 문구 — {0} = 코인 수.")]
|
||||
public string rowDungeonGoldFormat = "+{0}";
|
||||
|
||||
// ── WL-816u (2026-09-15 PD 임시 지시) 「던전 진입 후 다음 던전으로 가기를 선택할 경우 몬스터가
|
||||
// 등장하지 않고 아무것도 할 수 없어. 임시로 스테이지 전환 없이 스테이지 1에 등장한 몬스터가 재등장하게 해줘.」
|
||||
[Tooltip("🔴 WL-816u — 켜면 **던전에서만** 결과창 왼쪽 버튼이 「같은 던전 재입장」(StageDirector.Retry · " +
|
||||
"실패 팝업 [다시 도전]과 같은 경로)이 된다. 끄면 816m 까지의 동작(= StageDirector.Next · 표의 다음 줄). " +
|
||||
"스테이지 모드(#815)는 이 값과 무관하다. **되돌리기 = 이 값 0**.")]
|
||||
public bool dungeonPrimaryRetry = true;
|
||||
|
||||
[Tooltip("위가 켜졌을 때 던전 결과창 왼쪽 버튼 문구. 비우면 nextStageText(「다음 스테이지」) 그대로.")]
|
||||
public string dungeonPrimaryText = "다시 도전";
|
||||
|
||||
// ── 실패 팝업 (기준서 §E) ────────────────────────────────────────────
|
||||
[Header("실패 팝업 (기준서 §E · 813z ReviveDialog 2버튼)")]
|
||||
[Tooltip("끄면 스테이지 실패에 813z 부활 팝업이 **원래대로**(1버튼 「부활」) 뜬다.")]
|
||||
|
|
|
|||
Loading…
Reference in New Issue