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 이면 아이콘을 건드리지 않는다.")]
|
[Tooltip("적용 뒤 전투 패드 자동 버튼 아이콘(BattleUI.images[0])을 이 시간 동안 재시도로 맞춘다(초). UI 가 늦게 뜨는 경우 대비 · 0 이면 아이콘을 건드리지 않는다.")]
|
||||||
public float autoCombatIconRetrySeconds = 8f;
|
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
|
autoCombatInLobby: 0
|
||||||
autoCombatPollSeconds: 0.25
|
autoCombatPollSeconds: 0.25
|
||||||
autoCombatIconRetrySeconds: 8
|
autoCombatIconRetrySeconds: 8
|
||||||
|
activeSkillsEnabled: 0
|
||||||
|
activeSkillLockPollSeconds: 0
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,20 @@ MonoBehaviour:
|
||||||
fixIslandDuplicates: 1
|
fixIslandDuplicates: 1
|
||||||
islandCameraOwner: 1
|
islandCameraOwner: 1
|
||||||
hidePcWatchSeconds: 6
|
hidePcWatchSeconds: 6
|
||||||
islandSaveKey:
|
islandSaveKey:
|
||||||
swapIslandPlayer: 1
|
swapIslandPlayer: 1
|
||||||
pcPrefabPath: Assets/Res_Addr/PC/LH_M05.prefab
|
pcPrefabPath: Assets/Res_Addr/PC/LH_M05.prefab
|
||||||
islandPcScale: 0.7
|
islandPcScale: 0.7
|
||||||
mirrorFiAnimator: 1
|
mirrorFiAnimator: 1
|
||||||
fiPlayerModelName: Stickman
|
fiPlayerModelName: Stickman
|
||||||
interactRadius: 3.5
|
interactRadius: 3.5
|
||||||
promptFormat: "{0} 들어가기"
|
promptFormat: "{0} \uB4E4\uC5B4\uAC00\uAE30"
|
||||||
enterButtonText: "들어가기"
|
enterButtonText: "\uB4E4\uC5B4\uAC00\uAE30"
|
||||||
enterKey: 101
|
enterKey: 101
|
||||||
gateHeight: 3.2
|
gateHeight: 3.2
|
||||||
gateWidth: 2.4
|
gateWidth: 2.4
|
||||||
|
fontAssetPath: Assets/ThirdParty/TextMesh Pro/Addressables/Fonts & Materials/Font
|
||||||
|
SDF.asset
|
||||||
rewardEnabled: 1
|
rewardEnabled: 1
|
||||||
goldPerKill: 12
|
goldPerKill: 12
|
||||||
goldClearBonus: 60
|
goldClearBonus: 60
|
||||||
|
|
@ -50,16 +52,12 @@ MonoBehaviour:
|
||||||
farmReHideDelaySeconds: 3
|
farmReHideDelaySeconds: 3
|
||||||
matchNewTileMaterials: 1
|
matchNewTileMaterials: 1
|
||||||
dungeonUnlockPrefsKey: WL_Island_DungeonsUnlocked
|
dungeonUnlockPrefsKey: WL_Island_DungeonsUnlocked
|
||||||
dungeonUnlockPrices:
|
dungeonUnlockPrices: 0a0000001e0000003c00000064000000
|
||||||
- 10
|
|
||||||
- 30
|
|
||||||
- 60
|
|
||||||
- 100
|
|
||||||
maxDungeons: 4
|
maxDungeons: 4
|
||||||
platformUseFarmPosition: 1
|
platformUseFarmPosition: 1
|
||||||
platformPosition: {x: 0, y: 0, z: 0}
|
platformPosition: {x: 0, y: 0, z: 0}
|
||||||
platformOffset: {x: 0, y: 0, z: 0}
|
platformOffset: {x: 0, y: 0, z: 0}
|
||||||
platformPrefabPath:
|
platformPrefabPath:
|
||||||
platformShowPad: 0
|
platformShowPad: 0
|
||||||
platformCenterOnPlate: 1
|
platformCenterOnPlate: 1
|
||||||
platformPlateLift: 0.03
|
platformPlateLift: 0.03
|
||||||
|
|
@ -67,12 +65,15 @@ MonoBehaviour:
|
||||||
platformRadius: 1.7
|
platformRadius: 1.7
|
||||||
platformColor: {r: 0.92, g: 0.72, b: 0.24, a: 1}
|
platformColor: {r: 0.92, g: 0.72, b: 0.24, a: 1}
|
||||||
platformUseToon: 1
|
platformUseToon: 1
|
||||||
platformLabelFormat: "던전 열기"
|
platformLabelFormat: "\uB358\uC804 \uC5F4\uAE30"
|
||||||
platformLabelHeight: 2.2
|
platformLabelHeight: 2.2
|
||||||
platformTriggerSize: 3.4
|
platformTriggerSize: 3.4
|
||||||
platformTriggerHeight: 3
|
platformTriggerHeight: 3
|
||||||
platformRespawnDelaySeconds: 1.3
|
platformRespawnDelaySeconds: 1.3
|
||||||
platformSnapToGround: 1
|
platformSnapToGround: 1
|
||||||
|
platformScale: 0.5
|
||||||
|
platformCenterOnZone: 1
|
||||||
|
platformCenterVisibleOnly: 1
|
||||||
platformRequireStepOff: 1
|
platformRequireStepOff: 1
|
||||||
platformStepOffTimeoutSeconds: 120
|
platformStepOffTimeoutSeconds: 120
|
||||||
applyStartCoin: 1
|
applyStartCoin: 1
|
||||||
|
|
@ -100,7 +101,7 @@ MonoBehaviour:
|
||||||
dungeons:
|
dungeons:
|
||||||
- enabled_: 1
|
- enabled_: 1
|
||||||
id: 1
|
id: 1
|
||||||
displayName: "던전 1"
|
displayName: "\uB358\uC804 1"
|
||||||
sceneName: WL_Dungeon01
|
sceneName: WL_Dungeon01
|
||||||
mapId: 901
|
mapId: 901
|
||||||
stageIndex: 10
|
stageIndex: 10
|
||||||
|
|
@ -108,12 +109,12 @@ MonoBehaviour:
|
||||||
gateYaw: 180
|
gateYaw: 180
|
||||||
gateScale: 1
|
gateScale: 1
|
||||||
gateColor: {r: 0.45, g: 0.32, b: 0.62, a: 1}
|
gateColor: {r: 0.45, g: 0.32, b: 0.62, a: 1}
|
||||||
gatePrefabPath:
|
gatePrefabPath:
|
||||||
platformUseFarm: 1
|
platformUseFarm: 1
|
||||||
platformPosition: {x: 0, y: 0, z: 0}
|
platformPosition: {x: 0, y: 0, z: 0}
|
||||||
- enabled_: 1
|
- enabled_: 1
|
||||||
id: 2
|
id: 2
|
||||||
displayName: "던전 2"
|
displayName: "\uB358\uC804 2"
|
||||||
sceneName: WL_Dungeon02
|
sceneName: WL_Dungeon02
|
||||||
mapId: 902
|
mapId: 902
|
||||||
stageIndex: 11
|
stageIndex: 11
|
||||||
|
|
@ -121,12 +122,12 @@ MonoBehaviour:
|
||||||
gateYaw: 0
|
gateYaw: 0
|
||||||
gateScale: 1
|
gateScale: 1
|
||||||
gateColor: {r: 0.28, g: 0.5, b: 0.66, a: 1}
|
gateColor: {r: 0.28, g: 0.5, b: 0.66, a: 1}
|
||||||
gatePrefabPath:
|
gatePrefabPath:
|
||||||
platformUseFarm: 0
|
platformUseFarm: 0
|
||||||
platformPosition: {x: -2, y: 0, z: -2}
|
platformPosition: {x: -2, y: 0, z: -2}
|
||||||
- enabled_: 1
|
- enabled_: 1
|
||||||
id: 3
|
id: 3
|
||||||
displayName: "던전 3"
|
displayName: "\uB358\uC804 3"
|
||||||
sceneName: WL_Dungeon01
|
sceneName: WL_Dungeon01
|
||||||
mapId: 903
|
mapId: 903
|
||||||
stageIndex: 12
|
stageIndex: 12
|
||||||
|
|
@ -134,12 +135,12 @@ MonoBehaviour:
|
||||||
gateYaw: 180
|
gateYaw: 180
|
||||||
gateScale: 1
|
gateScale: 1
|
||||||
gateColor: {r: 0.62, g: 0.42, b: 0.24, a: 1}
|
gateColor: {r: 0.62, g: 0.42, b: 0.24, a: 1}
|
||||||
gatePrefabPath:
|
gatePrefabPath:
|
||||||
platformUseFarm: 0
|
platformUseFarm: 0
|
||||||
platformPosition: {x: -2.5, y: 0, z: 8.5}
|
platformPosition: {x: -2.5, y: 0, z: 8.5}
|
||||||
- enabled_: 1
|
- enabled_: 1
|
||||||
id: 4
|
id: 4
|
||||||
displayName: "던전 4"
|
displayName: "\uB358\uC804 4"
|
||||||
sceneName: WL_Dungeon02
|
sceneName: WL_Dungeon02
|
||||||
mapId: 904
|
mapId: 904
|
||||||
stageIndex: 13
|
stageIndex: 13
|
||||||
|
|
@ -147,6 +148,6 @@ MonoBehaviour:
|
||||||
gateYaw: 0
|
gateYaw: 0
|
||||||
gateScale: 1
|
gateScale: 1
|
||||||
gateColor: {r: 0.55, g: 0.24, b: 0.3, a: 1}
|
gateColor: {r: 0.55, g: 0.24, b: 0.3, a: 1}
|
||||||
gatePrefabPath:
|
gatePrefabPath:
|
||||||
platformUseFarm: 0
|
platformUseFarm: 0
|
||||||
platformPosition: {x: 6, y: 0, z: -2}
|
platformPosition: {x: 6, y: 0, z: -2}
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,11 @@ namespace WL.Island
|
||||||
var farms = Object.FindObjectsByType<FIFarm>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
var farms = Object.FindObjectsByType<FIFarm>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||||
bool haveCenter = false;
|
bool haveCenter = false;
|
||||||
|
|
||||||
|
// 🔴 WL-816u — 「열림 구역」 = 이 발판이 여는 자리 **전부**(보이는 밭 전체)다.
|
||||||
|
// 816g 는 **처음 만난 밭 하나**의 중심을 썼다 → 밭이 여러 칸이면 구역 정중앙이 아니다(PD 지적).
|
||||||
|
Bounds zone = new Bounds(); bool zoneSet = false;
|
||||||
|
ZoneBoundsValid = false;
|
||||||
|
|
||||||
for (int i = 0; i < farms.Length; i++)
|
for (int i = 0; i < farms.Length; i++)
|
||||||
{
|
{
|
||||||
var f = farms[i];
|
var f = farms[i];
|
||||||
|
|
@ -270,6 +275,8 @@ namespace WL.Island
|
||||||
{
|
{
|
||||||
if (!boundsSet) { b = rends[r].bounds; boundsSet = true; }
|
if (!boundsSet) { b = rends[r].bounds; boundsSet = true; }
|
||||||
else b.Encapsulate(rends[r].bounds);
|
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++;
|
rends[r].enabled = false; s_hiddenRends.Add(rends[r]); RenderersHidden++;
|
||||||
}
|
}
|
||||||
|
|
@ -289,9 +296,22 @@ namespace WL.Island
|
||||||
haveCenter = true;
|
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;
|
return haveCenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>🔴 WL-816u 진단 — 「열림 구역」(숨긴 밭 전부)의 월드 바운즈. 보고·프로브가 오차를 잰다.</summary>
|
||||||
|
public static Bounds ZoneBounds;
|
||||||
|
public static bool ZoneBoundsValid;
|
||||||
|
|
||||||
/// <summary>섬이 나중에 열리면 ① 그 섬의 밭을 다시 숨기고 ② 타일 재질을 기존 타일과 맞춘다(§1).</summary>
|
/// <summary>섬이 나중에 열리면 ① 그 섬의 밭을 다시 숨기고 ② 타일 재질을 기존 타일과 맞춘다(§1).</summary>
|
||||||
static void HookIslandActivation(WLIslandSettings cfg, Scene scene)
|
static void HookIslandActivation(WLIslandSettings cfg, Scene scene)
|
||||||
{
|
{
|
||||||
|
|
@ -541,37 +561,43 @@ namespace WL.Island
|
||||||
p.transform.SetParent(transform, true);
|
p.transform.SetParent(transform, true);
|
||||||
Purchaser = p;
|
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:
|
// 🔴 FI 가격판은 프리팹 안에서 **루트보다 앞·아래**에 놓여 있다(실측 2026-09-13:
|
||||||
// Frame/Price/Coin 이 전부 y = -0.49 · z = 루트+(-1/0/+1) — 잠긴 섬 자리의 **물 위**에 눕는 판이다).
|
// Frame/Price/Coin 이 전부 y = -0.49 · z = 루트+(-1/0/+1) — 잠긴 섬 자리의 **물 위**에 눕는 판이다).
|
||||||
// 섬 위(y≈0)에 그대로 놓으면 **지면 아래로 들어가 안 보인다** → 프리팹 전체의 렌더 범위를 재서
|
// 섬 위(y≈0)에 그대로 놓으면 **지면 아래로 들어가 안 보인다** → 프리팹 전체의 렌더 범위를 재서
|
||||||
// 「밭 한가운데 · 바닥 바로 위」로 맞춘다. 프리팹은 한 글자도 고치지 않는다.
|
// 「밭 한가운데 · 바닥 바로 위」로 맞춘다. 프리팹은 한 글자도 고치지 않는다.
|
||||||
if (cfg.platformCenterOnPlate != 0)
|
if (cfg.platformCenterOnPlate != 0)
|
||||||
{
|
{
|
||||||
var rends = p.GetComponentsInChildren<Renderer>(true);
|
Bounds b;
|
||||||
Bounds b = new Bounds(); bool set = false;
|
if (VisibleBounds(p.transform, cfg.platformCenterVisibleOnly != 0, out b))
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var t = transform.position;
|
var t = transform.position;
|
||||||
p.transform.position += new Vector3(t.x - b.center.x,
|
p.transform.position += new Vector3(t.x - b.center.x,
|
||||||
t.y + cfg.platformPlateLift - b.min.y,
|
t.y + cfg.platformPlateLift - b.min.y,
|
||||||
t.z - b.center.z);
|
t.z - b.center.z);
|
||||||
|
VisibleBounds(p.transform, cfg.platformCenterVisibleOnly != 0, out PlateBounds);
|
||||||
|
PlateBoundsValid = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 트리거를 **발판(가격판) 크기**로 줄인다(원본 프리팹은 섬 1칸 8 m 를 덮는 十자 두 개다).
|
// 트리거를 **발판(가격판) 크기**로 줄인다(원본 프리팹은 섬 1칸 8 m 를 덮는 十자 두 개다).
|
||||||
|
// 🔴 WL-816u — 판을 50 % 로 줄여도 **밟는 자리는 그대로**여야 한다(PD 는 크기만 말했다).
|
||||||
|
// size 는 부모(p) 로컬 단위라 스케일만큼 역보정하면 월드 미터가 유지된다.
|
||||||
var boxes = p.GetComponentsInChildren<BoxCollider>(true);
|
var boxes = p.GetComponentsInChildren<BoxCollider>(true);
|
||||||
float s = cfg.platformTriggerSize <= 0f ? 3.2f : cfg.platformTriggerSize;
|
float s = cfg.platformTriggerSize <= 0f ? 3.2f : cfg.platformTriggerSize;
|
||||||
|
float inv = 1f / ps;
|
||||||
var localCenter = p.transform.InverseTransformPoint(transform.position);
|
var localCenter = p.transform.InverseTransformPoint(transform.position);
|
||||||
for (int i = 0; i < boxes.Length; i++)
|
for (int i = 0; i < boxes.Length; i++)
|
||||||
{
|
{
|
||||||
if (boxes[i] == null) continue;
|
if (boxes[i] == null) continue;
|
||||||
boxes[i].size = new Vector3(s, cfg.platformTriggerHeight, s);
|
boxes[i].size = new Vector3(s * inv, cfg.platformTriggerHeight * inv, s * inv);
|
||||||
boxes[i].center = new Vector3(localCenter.x, cfg.platformTriggerHeight * 0.5f, localCenter.z);
|
boxes[i].center = new Vector3(localCenter.x, cfg.platformTriggerHeight * inv * 0.5f, localCenter.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔴 `Init` 은 `Purchaser.Start()` 보다 먼저여야 한다(Start 가 가격판을 그린다) — 같은 프레임이라 안전.
|
// 🔴 `Init` 은 `Purchaser.Start()` 보다 먼저여야 한다(Start 가 가격판을 그린다) — 같은 프레임이라 안전.
|
||||||
|
|
@ -581,6 +607,34 @@ namespace WL.Island
|
||||||
" · 트리거 " + s.ToString("F1") + " m · 더미 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;
|
static System.Reflection.FieldInfo s_purchaserField;
|
||||||
|
|
||||||
/// <summary>FI 가 섬 구입에 쓰는 **바로 그 프리팹**(가격판·코인 아이콘·물보라·SFX 가 전부 들어 있다).</summary>
|
/// <summary>FI 가 섬 구입에 쓰는 **바로 그 프리팹**(가격판·코인 아이콘·물보라·SFX 가 전부 들어 있다).</summary>
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,23 @@ namespace WL.Island
|
||||||
[Tooltip("1 이면 발판 자리를 지면 높이에 맞춘다(위에서 아래로 레이).")]
|
[Tooltip("1 이면 발판 자리를 지면 높이에 맞춘다(위에서 아래로 레이).")]
|
||||||
public int platformSnapToGround = 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 이면 새 발판은 플레이어가 그 자리에서 **내려온 뒤에** 생긴다 — " +
|
[Tooltip("1 이면 새 발판은 플레이어가 그 자리에서 **내려온 뒤에** 생긴다 — " +
|
||||||
"골드가 많을 때 가만히 서서 던전이 연달아 팔리는 것을 막는다(816g 실측).")]
|
"골드가 많을 때 가만히 서서 던전이 연달아 팔리는 것을 막는다(816g 실측).")]
|
||||||
public int platformRequireStepOff = 1;
|
public int platformRequireStepOff = 1;
|
||||||
|
|
|
||||||
|
|
@ -1173,6 +1173,11 @@ namespace WL.UI
|
||||||
leftText = last ? g815.restartFromFirstText : g815.nextStageText;
|
leftText = last ? g815.restartFromFirstText : g815.nextStageText;
|
||||||
rightText = g815.toLobbyText;
|
rightText = g815.toLobbyText;
|
||||||
leftOn = !last || g815.showRestartOnLastStage;
|
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_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);
|
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 MenuDeferred;
|
||||||
public static int MenuReassertCalls, MenuReassertHits; // 원본이 다시 켠 것을 도로 끈 횟수
|
public static int MenuReassertCalls, MenuReassertHits; // 원본이 다시 켠 것을 도로 끈 횟수
|
||||||
public static int RetryCalls, NextCalls, LobbyCalls, RestartCalls;
|
public static int RetryCalls, NextCalls, LobbyCalls, RestartCalls;
|
||||||
|
/// <summary>WL-816u — 던전 결과창 왼쪽 버튼이 「같은 던전 재입장」으로 돈 횟수(실측 진단).</summary>
|
||||||
|
public static int DungeonRetryCalls;
|
||||||
public static string LastLog = "";
|
public static string LastLog = "";
|
||||||
public static string LastMissingPath = "";
|
public static string LastMissingPath = "";
|
||||||
public static bool InStage { get { return s_inStage; } }
|
public static bool InStage { get { return s_inStage; } }
|
||||||
|
|
@ -382,6 +384,20 @@ namespace WL.UI
|
||||||
/// <summary>[다음 스테이지] — 마지막 스테이지면 [처음부터](= Enter(0)).</summary>
|
/// <summary>[다음 스테이지] — 마지막 스테이지면 [처음부터](= Enter(0)).</summary>
|
||||||
public static string OnNextStage()
|
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;
|
var last = WL.Combat.Stage.StageDirector.LastResult;
|
||||||
if (last.isLastStage)
|
if (last.isLastStage)
|
||||||
{
|
{
|
||||||
|
|
@ -557,7 +573,7 @@ namespace WL.UI
|
||||||
EnteredSeen = CountdownSeen = StartedSeen = EnemySeen = TimeSeen = ClearedSeen = FailedSeen = ExitedSeen = 0;
|
EnteredSeen = CountdownSeen = StartedSeen = EnemySeen = TimeSeen = ClearedSeen = FailedSeen = ExitedSeen = 0;
|
||||||
MenuHideCalls = MenuRestoreCalls = MenuMissing = 0;
|
MenuHideCalls = MenuRestoreCalls = MenuMissing = 0;
|
||||||
MenuReassertCalls = MenuReassertHits = 0; s_nextReassert = 0f;
|
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;
|
s_hits = s_maxChain = 0; s_potionsAtStart = PotionUse.UsedCount;
|
||||||
LastLog = ""; LastMissingPath = "";
|
LastLog = ""; LastMissingPath = "";
|
||||||
return "상태 초기화";
|
return "상태 초기화";
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,10 @@ namespace WL.UI
|
||||||
|
|
||||||
var cards = target.skillCards;
|
var cards = target.skillCards;
|
||||||
int total = cards != null ? cards.Length : 0;
|
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;
|
LastActiveSlots = active;
|
||||||
LastReserveSlots = Mathf.Max(0, total - active);
|
LastReserveSlots = Mathf.Max(0, total - active);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ MonoBehaviour:
|
||||||
dungeonGoldRowEnabled: 1
|
dungeonGoldRowEnabled: 1
|
||||||
rowDungeonGoldLabel: "\uD68D\uB4DD \uACE8\uB4DC"
|
rowDungeonGoldLabel: "\uD68D\uB4DD \uACE8\uB4DC"
|
||||||
rowDungeonGoldFormat: +{0}
|
rowDungeonGoldFormat: +{0}
|
||||||
|
dungeonPrimaryRetry: 1
|
||||||
|
dungeonPrimaryText: "\uB2E4\uC2DC \uB3C4\uC804"
|
||||||
stageFailEnabled: 1
|
stageFailEnabled: 1
|
||||||
failDeathMessage: "\uC4F0\uB7EC\uC84C\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC2DC \uB3C4\uC804\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?"
|
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"
|
failTimeoutMessage: "\uC2DC\uAC04 \uCD08\uACFC!\n\uB0A8\uC740 \uC801 {0}\uB9C8\uB9AC"
|
||||||
|
|
|
||||||
|
|
@ -311,6 +311,16 @@ namespace WL.UI
|
||||||
[Tooltip("획득 골드 행 값 문구 — {0} = 코인 수.")]
|
[Tooltip("획득 골드 행 값 문구 — {0} = 코인 수.")]
|
||||||
public string rowDungeonGoldFormat = "+{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) ────────────────────────────────────────────
|
// ── 실패 팝업 (기준서 §E) ────────────────────────────────────────────
|
||||||
[Header("실패 팝업 (기준서 §E · 813z ReviveDialog 2버튼)")]
|
[Header("실패 팝업 (기준서 §E · 813z ReviveDialog 2버튼)")]
|
||||||
[Tooltip("끄면 스테이지 실패에 813z 부활 팝업이 **원래대로**(1버튼 「부활」) 뜬다.")]
|
[Tooltip("끄면 스테이지 실패에 813z 부활 팝업이 **원래대로**(1버튼 「부활」) 뜬다.")]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue