Merge branch 'wl/ui/WL-816zc-island-flow'
This commit is contained in:
commit
4d5aa6f546
|
|
@ -94,7 +94,7 @@ MonoBehaviour:
|
|||
campDecorSnapToGround: 1
|
||||
applyStartCoin: 1
|
||||
startCoin: 10
|
||||
saveResetToken: 816g-1
|
||||
saveResetToken: 816zc-1
|
||||
resetSaveEveryStart: 1
|
||||
dungeonCameraMatchIsland: 1
|
||||
camPitch: 45
|
||||
|
|
@ -128,7 +128,7 @@ MonoBehaviour:
|
|||
gatePrefabPath: Assets/WL/Island/Prefabs/WL_DungeonGate_Arch.prefab
|
||||
platformUseFarm: 1
|
||||
platformPosition: {x: 0, y: 0, z: 0}
|
||||
- enabled_: 1
|
||||
- enabled_: 0
|
||||
id: 2
|
||||
displayName: "\uB358\uC804 2"
|
||||
sceneName: WL_Dungeon02
|
||||
|
|
@ -141,7 +141,7 @@ MonoBehaviour:
|
|||
gatePrefabPath: Assets/WL/Island/Prefabs/WL_DungeonGate_Arch.prefab
|
||||
platformUseFarm: 0
|
||||
platformPosition: {x: -2, y: 0, z: -2}
|
||||
- enabled_: 1
|
||||
- enabled_: 0
|
||||
id: 3
|
||||
displayName: "\uB358\uC804 3"
|
||||
sceneName: WL_Dungeon01
|
||||
|
|
@ -154,7 +154,7 @@ MonoBehaviour:
|
|||
gatePrefabPath: Assets/WL/Island/Prefabs/WL_DungeonGate_Arch.prefab
|
||||
platformUseFarm: 0
|
||||
platformPosition: {x: -2.5, y: 0, z: 8.5}
|
||||
- enabled_: 1
|
||||
- enabled_: 0
|
||||
id: 4
|
||||
displayName: "\uB358\uC804 4"
|
||||
sceneName: WL_Dungeon02
|
||||
|
|
|
|||
|
|
@ -255,9 +255,14 @@ namespace WL.Island
|
|||
static void OnStageCleared(in StageClearResult r)
|
||||
{
|
||||
var cfg = WLIslandSettings.Instance;
|
||||
if (cfg == null || cfg.enabled_ == 0 || cfg.rewardEnabled == 0) return;
|
||||
if (cfg == null || cfg.enabled_ == 0) return;
|
||||
if (WLDungeonSceneSetup.Current == null) return; // 던전이 아닌 스테이지(#815 데모)는 건드리지 않는다
|
||||
|
||||
// 🔴 WL-816zc ② — 「전투 승리 후 섬 확장 노드 추가」. 보상과 무관하게 승리 그 자체로 연다.
|
||||
WLIslandExpansionGate.Unlock(cfg);
|
||||
|
||||
if (cfg.rewardEnabled == 0) return;
|
||||
|
||||
int gold = ComputeGold(cfg, r);
|
||||
SetPending(cfg, gold);
|
||||
ClearGrants++;
|
||||
|
|
|
|||
|
|
@ -36,9 +36,19 @@ namespace WL.Island
|
|||
{
|
||||
Clear();
|
||||
if (cfg == null || cfg.campDecorEnabled == 0) return;
|
||||
if (WLIslandGateFlow.UnlockedCount(cfg) < 1) { LastLog = "캠프 없음 — 첫 자리 미개방"; return; }
|
||||
|
||||
Vector3 center = WLIslandGateFlow.PlatformPosFor(cfg, 0);
|
||||
// WL-816zc — 초기 플로우 v2 에서는 「캠프파이어 설치」가 **1단계**다(단계 표의 첫 줄 자리).
|
||||
Vector3 center;
|
||||
if (WLIslandFlowV2.Active(cfg))
|
||||
{
|
||||
if (WLIslandFlowV2.Step(cfg) < 1) { LastLog = "캠프 없음 — 1단계(캠프파이어 설치) 미지불"; return; }
|
||||
center = WLIslandFlowV2.PosFor(cfg, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (WLIslandGateFlow.UnlockedCount(cfg) < 1) { LastLog = "캠프 없음 — 첫 자리 미개방"; return; }
|
||||
center = WLIslandGateFlow.PlatformPosFor(cfg, 0);
|
||||
}
|
||||
LastCenter = center;
|
||||
|
||||
var root = new GameObject(RootName);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,427 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLIslandFlowV2.cs — WL-816zc 「섬 초기 플로우 v2」
|
||||
//
|
||||
// PD 원문(2026-09-15)
|
||||
// 「원본 에셋과 같이 섬이 확장되는 방식과 순서는 유지하되, 내가 수정한 초기 플로우만 바뀌어야 해.
|
||||
// <최초 시작 시 밭 생성은 보류>
|
||||
// ① 캠프파이어 설치 → 상인 설치 → 사과 나무 설치 → 던전 생성
|
||||
// ② 전투 승리 후 섬 확장 노드 추가 → 이후 원본 에셋의 확장 순서대로 확장시켜야 함.」
|
||||
//
|
||||
// ■ 발판은 **한 번에 하나**다. 값을 치르면 그 자리에 그 단계의 설치물이 남고, 다음 단계의 발판이 선다.
|
||||
// 순서·문구·가격·자리·설치물은 전부 `WLIslandSettings.flowSteps[]` 값이다(코드 수정 0 · C45).
|
||||
// ■ 어디까지 왔는지는 PlayerPrefs 키 **1개**(`flowStepPrefsKey`)로만 센다.
|
||||
// 섬으로 돌아오면 이 숫자를 보고 이미 치른 설치물을 다시 놓는다(세이브 복원).
|
||||
// ■ 원본 섬 확장 가격판(FI `Purchaser`)은 **첫 던전 승리 전까지 숨긴다**(렌더러·콜라이더만 끈다).
|
||||
// 승리하면 그대로 다시 켜지고, 이후 확장 순서는 **FI 원본**(`Island.Constraint`)이 정한다 — 우리는 켜고 끄기만.
|
||||
// ■ 되돌리기 = `flowV2Enabled = 0` → 지금까지의 「던전 발판 4개」 방식 그대로.
|
||||
//
|
||||
// 🔴 원본(Assets/FarmingIsland/** · Assets/Script/**)은 한 줄도 고치지 않는다 — 인스턴스만 만든다.
|
||||
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 가 플레이어 팝업을 띄운다).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using FIPurchaser = CryingSnow.FarmingIsland.Purchaser;
|
||||
|
||||
namespace WL.Island
|
||||
{
|
||||
/// <summary>초기 플로우(단계 표) 진행 + 설치물 배치. 정적 · 씬에 남는 상태 없음.</summary>
|
||||
public static class WLIslandFlowV2
|
||||
{
|
||||
const string RootName = "~WL_FlowInstalls";
|
||||
|
||||
// ── 진단(프로브·보고가 읽는다 · 실측만)
|
||||
public static int InstallsMade;
|
||||
public static int LastStep;
|
||||
public static string LastLog = "";
|
||||
|
||||
static readonly List<GameObject> s_made = new List<GameObject>(8);
|
||||
|
||||
/// <summary>이 플로우가 지금 동작 중인가(꺼져 있으면 기존 발판 방식이 그대로 돈다).</summary>
|
||||
public static bool Active(WLIslandSettings cfg)
|
||||
{
|
||||
return cfg != null && cfg.gateFlowEnabled != 0 && cfg.flowV2Enabled != 0 && StepCount(cfg) > 0;
|
||||
}
|
||||
|
||||
/// <summary>켜져 있는 단계 수.</summary>
|
||||
public static int StepCount(WLIslandSettings cfg)
|
||||
{
|
||||
if (cfg == null || cfg.flowSteps == null) return 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < cfg.flowSteps.Length; i++)
|
||||
if (cfg.flowSteps[i] != null && cfg.flowSteps[i].enabled_ != 0) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
/// <summary>켜져 있는 것 중 n 번째 단계(없으면 null).</summary>
|
||||
public static WLFlowStepDef Nth(WLIslandSettings cfg, int n)
|
||||
{
|
||||
if (cfg == null || cfg.flowSteps == null || n < 0) return null;
|
||||
int k = 0;
|
||||
for (int i = 0; i < cfg.flowSteps.Length; i++)
|
||||
{
|
||||
var s = cfg.flowSteps[i];
|
||||
if (s == null || s.enabled_ == 0) continue;
|
||||
if (k == n) return s;
|
||||
k++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>지금까지 치른 단계 수(PlayerPrefs 1키 · FI 세이브를 건드리지 않는다).</summary>
|
||||
public static int Step(WLIslandSettings cfg)
|
||||
{
|
||||
if (cfg == null || string.IsNullOrEmpty(cfg.flowStepPrefsKey)) return 0;
|
||||
return Mathf.Max(0, PlayerPrefs.GetInt(cfg.flowStepPrefsKey, 0));
|
||||
}
|
||||
|
||||
public static void SetStep(WLIslandSettings cfg, int n)
|
||||
{
|
||||
if (cfg == null || string.IsNullOrEmpty(cfg.flowStepPrefsKey)) return;
|
||||
PlayerPrefs.SetInt(cfg.flowStepPrefsKey, Mathf.Max(0, n));
|
||||
PlayerPrefs.Save();
|
||||
LastStep = Mathf.Max(0, n);
|
||||
}
|
||||
|
||||
/// <summary>n 번째 단계의 발판 자리(표가 가리킨 던전 줄의 좌표 · -1 이면 밭 구역 중심).</summary>
|
||||
public static Vector3 PosFor(WLIslandSettings cfg, int n)
|
||||
{
|
||||
if (cfg == null) return Vector3.zero;
|
||||
|
||||
var s = Nth(cfg, n);
|
||||
Vector3 p;
|
||||
if (s == null) p = cfg.platformPosition;
|
||||
else if (s.platformFromDungeonRow >= 0)
|
||||
{
|
||||
var d = cfg.DungeonRow(s.platformFromDungeonRow);
|
||||
p = d != null ? d.platformPosition : s.platformPosition;
|
||||
}
|
||||
else if (cfg.platformUseFarmPosition != 0 && WLIslandGateFlow.HaveFarmCenter) p = WLIslandGateFlow.FarmCenter;
|
||||
else p = s.platformPosition;
|
||||
|
||||
p += cfg.platformOffset;
|
||||
return WLIslandGateFlow.SnapToGround(cfg, p);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 발판 + 설치물 다시 그리기 (섬 준비 직후 · 지불 직후 둘 다 여기로)
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
public static void Refresh(WLIslandSettings cfg, Scene scene)
|
||||
{
|
||||
if (!Active(cfg)) return;
|
||||
|
||||
int step = Step(cfg);
|
||||
int total = StepCount(cfg);
|
||||
LastStep = step;
|
||||
|
||||
// ① 이미 치른 단계의 설치물을 다시 놓는다(세이브 복원 · 매번 지우고 새로 놓는다).
|
||||
ClearInstalls();
|
||||
InstallsMade = 0;
|
||||
for (int i = 0; i < step && i < total; i++)
|
||||
if (Install(cfg, scene, Nth(cfg, i), PosFor(cfg, i))) InstallsMade++;
|
||||
|
||||
// 캠프 장식은 전용 코드가 자기 자리(1단계 자리)에 놓는다.
|
||||
WLIslandCampDecor.Refresh(cfg, scene);
|
||||
|
||||
// ② 다음 발판 1개
|
||||
if (WLGoldPlatform.Instance != null)
|
||||
{
|
||||
Object.Destroy(WLGoldPlatform.Instance.gameObject);
|
||||
WLGoldPlatform.Instance = null;
|
||||
}
|
||||
|
||||
if (step >= total)
|
||||
{
|
||||
LastLog = "발판 없음 — 초기 플로우 " + step + "/" + total + " 단계 완료";
|
||||
WLIslandBridge.Log(cfg, LastLog);
|
||||
return;
|
||||
}
|
||||
|
||||
var next = Nth(cfg, step);
|
||||
Vector3 pos = PosFor(cfg, step);
|
||||
string label = string.Format(cfg.flowLabelFormat, next.label, next.price);
|
||||
|
||||
// 🔴 값을 치른 자리에 다음 발판이 곧바로 생기면 **가만히 서 있기만 해도** 연달아 팔린다(816g 실측).
|
||||
// FI 섬 확장처럼 「한 번 내려왔다가 다시 올라오기」를 요구한다.
|
||||
var runner = WLIslandBridge.Runner;
|
||||
if (cfg.platformRequireStepOff != 0 && runner != null && WLIslandGateFlow.PlayerInRange(cfg, pos))
|
||||
{
|
||||
runner.StartCoroutine(Co_SpawnWhenClear(cfg, scene, pos, next.price, step, label, total));
|
||||
LastLog = "발판 대기 — 자리에서 내려오면 " + (step + 1) + "단계 「" + next.label + "」 발판을 놓는다";
|
||||
WLIslandBridge.Log(cfg, LastLog);
|
||||
return;
|
||||
}
|
||||
|
||||
Spawn(cfg, scene, pos, next.price, step, label, total);
|
||||
}
|
||||
|
||||
static void Spawn(WLIslandSettings cfg, Scene scene, Vector3 pos, int price, int step, string label, int total)
|
||||
{
|
||||
var p = WLGoldPlatform.Create(cfg, scene, pos, price, step, label);
|
||||
if (p != null) { WLIslandGateFlow.PlatformsMade++; WLIslandGateFlow.LastPlatformPos = pos; }
|
||||
LastLog = "플로우 발판 배치 — " + (step + 1) + "/" + total + " 「" + label.Replace("\n", " ") +
|
||||
"」 @ " + pos.ToString("F2");
|
||||
WLIslandBridge.Log(cfg, LastLog);
|
||||
}
|
||||
|
||||
static IEnumerator Co_SpawnWhenClear(WLIslandSettings cfg, Scene scene, Vector3 pos,
|
||||
int price, int step, string label, int total)
|
||||
{
|
||||
float t = 0f;
|
||||
while (t < cfg.platformStepOffTimeoutSeconds && WLIslandGateFlow.PlayerInRange(cfg, pos))
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
yield return null;
|
||||
}
|
||||
if (WLGoldPlatform.Instance != null) yield break; // 그 사이 다른 경로로 이미 생겼다
|
||||
Spawn(cfg, scene, pos, price, step, label, total);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 지불 완료 → 그 단계의 설치물이 그 자리에 남는다
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
public static void OnPaid(WLIslandSettings cfg, Vector3 platformPos)
|
||||
{
|
||||
if (!Active(cfg)) return;
|
||||
|
||||
int step = Step(cfg);
|
||||
int total = StepCount(cfg);
|
||||
if (step >= total) return;
|
||||
|
||||
var s = Nth(cfg, step);
|
||||
SetStep(cfg, step + 1);
|
||||
|
||||
var scene = SceneManager.GetSceneByName(cfg.islandSceneName);
|
||||
if (!scene.IsValid() || !scene.isLoaded) scene = SceneManager.GetActiveScene();
|
||||
|
||||
// 던전 단계 = 원래의 해금 카운터도 같이 올린다 → 섬 복귀 시 `SpawnGates` 가 입구를 다시 놓는다.
|
||||
if (s != null && s.kind == "dungeon")
|
||||
{
|
||||
WLIslandGateFlow.SetUnlockedCount(cfg, DungeonStepsDone(cfg, step + 1));
|
||||
WLIslandGateFlow.Unlocks++;
|
||||
}
|
||||
|
||||
LastLog = "🔴 지불 완료 — " + (step + 1) + "/" + total + " 「" + (s != null ? s.label : "?") +
|
||||
"」 · 이 자리(" + platformPos.ToString("F1") + ")의 발판은 영영 사라진다";
|
||||
WLIslandBridge.Log(cfg, LastLog);
|
||||
|
||||
Refresh(cfg, scene);
|
||||
}
|
||||
|
||||
/// <summary>치른 단계 중 던전 단계가 몇 개인지(= 열려 있어야 할 던전 수).</summary>
|
||||
static int DungeonStepsDone(WLIslandSettings cfg, int stepsDone)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < stepsDone; i++)
|
||||
{
|
||||
var s = Nth(cfg, i);
|
||||
if (s != null && s.kind == "dungeon") n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 설치물 하나
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
static bool Install(WLIslandSettings cfg, Scene scene, WLFlowStepDef s, Vector3 pos)
|
||||
{
|
||||
if (cfg == null || s == null) return false;
|
||||
|
||||
if (s.kind == "camp") return false; // 캠프는 WLIslandCampDecor 가 놓는다
|
||||
|
||||
if (s.kind == "dungeon")
|
||||
{
|
||||
var d = cfg.DungeonRow(s.dungeonRow);
|
||||
if (d != null) WLIslandBridge.SpawnOneGate(cfg, d, scene); // 이미 있으면 안에서 건너뛴다
|
||||
return d != null;
|
||||
}
|
||||
|
||||
// kind = prefab
|
||||
if (string.IsNullOrEmpty(s.prefabPath)) return false;
|
||||
var root = EnsureRoot(scene);
|
||||
var go = SpawnPrefab(cfg, s.prefabPath, root.transform, pos, s.yaw, s.scale, s.keepCollider != 0);
|
||||
if (go == null) return false;
|
||||
|
||||
// 덧붙일 것(예: 좌판에 상인이 안 들어 있을 때만 상인을 넣는다) — 원본 프리팹 그대로, 설치물의 자식으로.
|
||||
if (!string.IsNullOrEmpty(s.extraPrefabPath) && !HasComponentNamed(go, s.extraSkipIfComponent))
|
||||
{
|
||||
var rot = Quaternion.Euler(0f, s.yaw, 0f);
|
||||
SpawnPrefab(cfg, s.extraPrefabPath, go.transform, pos + rot * s.extraOffset,
|
||||
s.yaw, s.scale, s.keepCollider != 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static GameObject EnsureRoot(Scene scene)
|
||||
{
|
||||
for (int i = 0; i < s_made.Count; i++)
|
||||
if (s_made[i] != null && s_made[i].name == RootName) return s_made[i];
|
||||
|
||||
var root = new GameObject(RootName);
|
||||
if (scene.IsValid() && scene.isLoaded) SceneManager.MoveGameObjectToScene(root, scene);
|
||||
s_made.Add(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
static GameObject SpawnPrefab(WLIslandSettings cfg, string path, Transform parent, Vector3 pos,
|
||||
float yaw, float scale, bool keepCollider)
|
||||
{
|
||||
var prefab = WLIslandAssets.LoadPrefab(path);
|
||||
if (prefab == null) { WLIslandBridge.Log(cfg, "플로우 설치물 프리팹 없음 — " + path); return null; }
|
||||
|
||||
// 🔴 부모를 주고 만든다 — FI 스크립트(예: `Merchant.Awake` 의 `GetComponentInParent<Stall>()`)가
|
||||
// Awake 시점에 부모를 볼 수 있어야 한다. 부모의 씬에 그대로 들어가므로 씬 이동도 필요 없다.
|
||||
var go = Object.Instantiate(prefab, parent);
|
||||
go.name = "~WL_Flow_" + prefab.name;
|
||||
go.transform.position = Snap(cfg, pos);
|
||||
go.transform.rotation = Quaternion.Euler(0f, yaw, 0f);
|
||||
float sc = scale > 0.001f ? scale : 1f;
|
||||
go.transform.localScale = prefab.transform.localScale * sc;
|
||||
|
||||
if (!keepCollider)
|
||||
{
|
||||
// 장식 = 이동 방해 0 · NavMesh 재베이크 없음(816y 캠프 장식과 같은 원칙).
|
||||
var cols = go.GetComponentsInChildren<Collider>(true);
|
||||
for (int i = 0; i < cols.Length; i++) if (cols[i] != null) cols[i].enabled = false;
|
||||
}
|
||||
return go;
|
||||
}
|
||||
|
||||
static bool HasComponentNamed(GameObject go, string typeName)
|
||||
{
|
||||
if (go == null || string.IsNullOrEmpty(typeName)) return false;
|
||||
var comps = go.GetComponentsInChildren<Component>(true);
|
||||
for (int i = 0; i < comps.Length; i++)
|
||||
if (comps[i] != null && comps[i].GetType().Name == typeName) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static Vector3 Snap(WLIslandSettings cfg, Vector3 p)
|
||||
{
|
||||
if (cfg.platformSnapToGround == 0) return p;
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(new Vector3(p.x, p.y + 30f, p.z), Vector3.down, out hit, 60f, ~0, QueryTriggerInteraction.Ignore))
|
||||
return new Vector3(p.x, hit.point.y, p.z);
|
||||
return p;
|
||||
}
|
||||
|
||||
public static void ClearInstalls()
|
||||
{
|
||||
for (int i = 0; i < s_made.Count; i++)
|
||||
if (s_made[i] != null) Object.Destroy(s_made[i]);
|
||||
s_made.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 원본 섬 확장 노드 — 첫 던전 승리 전까지 숨김
|
||||
//
|
||||
// 🔴 FI 의 확장 **순서·조건은 건드리지 않는다**(`IslandManager.RefreshIsland` · `Island.Constraint` 그대로).
|
||||
// 우리는 이미 만들어진 가격판의 **렌더러·콜라이더만** 껐다 켠다 = FI 세이브·격자에 흔적 0.
|
||||
// `SetActive(false)` 를 쓰지 않는 이유는 816g 와 같다(꺼진 오브젝트에서 코루틴이 시작되면 에러).
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
public static class WLIslandExpansionGate
|
||||
{
|
||||
// ── 진단(프로브·보고가 읽는다 · 실측만)
|
||||
public static int PurchasersHidden;
|
||||
public static string LastLog = "";
|
||||
|
||||
static readonly List<Renderer> s_rends = new List<Renderer>(64);
|
||||
static readonly List<Collider> s_cols = new List<Collider>(64);
|
||||
|
||||
/// <summary>첫 던전 승리를 이미 했는가(기능이 꺼져 있으면 항상 true = 원본 그대로).</summary>
|
||||
public static bool IsUnlocked(WLIslandSettings cfg)
|
||||
{
|
||||
if (cfg == null || cfg.expansionAfterFirstWin == 0) return true;
|
||||
if (string.IsNullOrEmpty(cfg.expansionUnlockPrefsKey)) return true;
|
||||
return PlayerPrefs.GetInt(cfg.expansionUnlockPrefsKey, 0) != 0;
|
||||
}
|
||||
|
||||
/// <summary>전투 승리 시 1회 — 이후 섬 확장은 FI 원본 순서대로 열린다.</summary>
|
||||
public static void Unlock(WLIslandSettings cfg)
|
||||
{
|
||||
if (cfg == null || cfg.expansionAfterFirstWin == 0) return;
|
||||
if (string.IsNullOrEmpty(cfg.expansionUnlockPrefsKey)) return;
|
||||
if (PlayerPrefs.GetInt(cfg.expansionUnlockPrefsKey, 0) != 0) return;
|
||||
|
||||
PlayerPrefs.SetInt(cfg.expansionUnlockPrefsKey, 1);
|
||||
PlayerPrefs.Save();
|
||||
LastLog = "🔴 첫 전투 승리 — 원본 섬 확장 노드 개방(이후 순서는 FI 원본이 정한다)";
|
||||
WLIslandBridge.Log(cfg, LastLog);
|
||||
}
|
||||
|
||||
/// <summary>섬이 준비될 때마다 1회 — 아직 승리 전이면 원본 가격판을 숨기고, 승리했으면 그대로 둔다.</summary>
|
||||
public static void Apply(WLIslandSettings cfg, Scene scene)
|
||||
{
|
||||
if (cfg == null || cfg.gateFlowEnabled == 0 || cfg.expansionAfterFirstWin == 0) return;
|
||||
|
||||
if (IsUnlocked(cfg))
|
||||
{
|
||||
Restore();
|
||||
LastLog = "확장 노드 표시 — 첫 승리 완료(FI 원본 순서)";
|
||||
WLIslandBridge.Log(cfg, LastLog);
|
||||
return;
|
||||
}
|
||||
|
||||
HideNow(cfg, scene);
|
||||
|
||||
// FI 는 세이브 복원·이웃 갱신 시점에 가격판을 **나중에** 만들기도 한다 — 잠시 더 훑는다.
|
||||
var runner = WLIslandBridge.Runner;
|
||||
if (runner != null && cfg.expansionHideSeconds > 0f)
|
||||
runner.StartCoroutine(Co_Guard(cfg, scene));
|
||||
}
|
||||
|
||||
public static int HideNow(WLIslandSettings cfg, Scene scene)
|
||||
{
|
||||
int n = 0;
|
||||
var all = Object.FindObjectsByType<FIPurchaser>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < all.Length; i++)
|
||||
{
|
||||
var p = all[i];
|
||||
if (p == null) continue;
|
||||
if (scene.IsValid() && p.gameObject.scene != scene) continue;
|
||||
if (p.GetComponentInParent<WLGoldPlatform>() != null) continue; // 우리 발판은 건드리지 않는다
|
||||
|
||||
var rends = p.GetComponentsInChildren<Renderer>(true);
|
||||
for (int r = 0; r < rends.Length; r++)
|
||||
if (rends[r] != null && rends[r].enabled) { rends[r].enabled = false; s_rends.Add(rends[r]); n++; }
|
||||
|
||||
var cols = p.GetComponentsInChildren<Collider>(true);
|
||||
for (int c = 0; c < cols.Length; c++)
|
||||
if (cols[c] != null && cols[c].enabled) { cols[c].enabled = false; s_cols.Add(cols[c]); }
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
PurchasersHidden += n;
|
||||
LastLog = "원본 확장 가격판 숨김 — 렌더러 " + n + "개(첫 던전 승리 전)";
|
||||
WLIslandBridge.Log(cfg, LastLog);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static IEnumerator Co_Guard(WLIslandSettings cfg, Scene scene)
|
||||
{
|
||||
float wait = Mathf.Max(0.1f, cfg.expansionHideInterval);
|
||||
float t = 0f;
|
||||
while (t < cfg.expansionHideSeconds)
|
||||
{
|
||||
yield return new WaitForSeconds(wait);
|
||||
t += wait;
|
||||
if (cfg == null || cfg.expansionAfterFirstWin == 0) yield break;
|
||||
if (IsUnlocked(cfg)) { Restore(); yield break; }
|
||||
HideNow(cfg, scene);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>우리가 껐던 것만 되돌린다(FI 가 스스로 끈 것은 손대지 않는다).</summary>
|
||||
public static void Restore()
|
||||
{
|
||||
for (int i = 0; i < s_rends.Count; i++) if (s_rends[i] != null) s_rends[i].enabled = true;
|
||||
for (int i = 0; i < s_cols.Count; i++) if (s_cols[i] != null) s_cols[i].enabled = true;
|
||||
s_rends.Clear();
|
||||
s_cols.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c3f1a274b6d4e1f9a05d7c2e6b83f41
|
||||
|
|
@ -100,13 +100,22 @@ namespace WL.Island
|
|||
s_farmCenter = farmCenter;
|
||||
s_haveFarm = haveFarm;
|
||||
|
||||
// WL-816zc ③ — 원본 섬 확장 가격판은 첫 던전 승리 전까지 숨긴다(켜고 끄기만 · FI 순서 그대로).
|
||||
WLIslandExpansionGate.Apply(cfg, scene);
|
||||
|
||||
RefreshPlatform(cfg, scene);
|
||||
// v2 는 RefreshPlatform 안에서 캠프까지 같이 그린다 — 두 번 그리지 않는다.
|
||||
if (!WLIslandFlowV2.Active(cfg))
|
||||
WLIslandCampDecor.Refresh(cfg, scene); // WL-816y — 세이브 복원(해금 ≥ 1 이면 캠프 재생성)
|
||||
}
|
||||
|
||||
static Vector3 s_farmCenter;
|
||||
static bool s_haveFarm;
|
||||
|
||||
/// <summary>밭 구역 중심(WL-816zc 플로우가 첫 자리로 쓴다).</summary>
|
||||
public static Vector3 FarmCenter { get { return s_farmCenter; } }
|
||||
public static bool HaveFarmCenter { get { return s_haveFarm; } }
|
||||
|
||||
/// <summary>
|
||||
/// 🔴 던전 하나 = **발판 한 자리**(WL-816g · PD 정정 2026-09-13
|
||||
/// 「던전 열기는 한 번만 · 이후 같은 자리에 또 등장해서는 안 된다」).
|
||||
|
|
@ -126,7 +135,7 @@ namespace WL.Island
|
|||
}
|
||||
|
||||
/// <summary>좌표 위에서 아래로 쏘아 지면 높이를 맞춘다(맞는 것이 없으면 준 값 그대로).</summary>
|
||||
static Vector3 SnapToGround(WLIslandSettings cfg, Vector3 p)
|
||||
public static Vector3 SnapToGround(WLIslandSettings cfg, Vector3 p)
|
||||
{
|
||||
if (cfg.platformSnapToGround == 0) return p;
|
||||
RaycastHit hit;
|
||||
|
|
@ -139,6 +148,9 @@ namespace WL.Island
|
|||
/// <summary>발판을 지금 해금 상태에 맞게 만들거나 지운다.</summary>
|
||||
public static void RefreshPlatform(WLIslandSettings cfg, Scene scene)
|
||||
{
|
||||
// WL-816zc — 초기 플로우 v2 가 켜져 있으면 발판 순서는 단계 표가 정한다(flowV2Enabled = 0 이면 아래 그대로).
|
||||
if (WLIslandFlowV2.Active(cfg)) { WLIslandFlowV2.Refresh(cfg, scene); return; }
|
||||
|
||||
int unlocked = UnlockedCount(cfg);
|
||||
int usable = UsableDungeonCount(cfg);
|
||||
|
||||
|
|
@ -188,7 +200,7 @@ namespace WL.Island
|
|||
WLIslandBridge.Log(cfg, LastLog);
|
||||
}
|
||||
|
||||
static bool PlayerInRange(WLIslandSettings cfg, Vector3 pos)
|
||||
public static bool PlayerInRange(WLIslandSettings cfg, Vector3 pos)
|
||||
{
|
||||
var fi = Object.FindFirstObjectByType<CryingSnow.FarmingIsland.PlayerController>();
|
||||
if (fi == null) return false;
|
||||
|
|
@ -205,6 +217,9 @@ namespace WL.Island
|
|||
{
|
||||
if (cfg == null) return;
|
||||
|
||||
// WL-816zc — 초기 플로우 v2 가 켜져 있으면 「다음 단계」가 이 자리를 이어받는다.
|
||||
if (WLIslandFlowV2.Active(cfg)) { WLIslandFlowV2.OnPaid(cfg, platformPos); return; }
|
||||
|
||||
int unlocked = UnlockedCount(cfg);
|
||||
int usable = UsableDungeonCount(cfg);
|
||||
if (unlocked >= usable) return;
|
||||
|
|
@ -555,7 +570,9 @@ namespace WL.Island
|
|||
|
||||
public const string ObjectName = "~WL_GoldPlatform";
|
||||
|
||||
public static WLGoldPlatform Create(WLIslandSettings cfg, Scene scene, Vector3 pos, int price, int nextIndex)
|
||||
/// <param name="labelOverride">비우면 `platformLabelFormat`(던전 열기) · 채우면 그 글자를 그대로 띄운다(WL-816zc 단계 문구).</param>
|
||||
public static WLGoldPlatform Create(WLIslandSettings cfg, Scene scene, Vector3 pos, int price, int nextIndex,
|
||||
string labelOverride = null)
|
||||
{
|
||||
GameObject root = null;
|
||||
|
||||
|
|
@ -584,7 +601,9 @@ namespace WL.Island
|
|||
// — 모델이 아니라 글자 하나고, `platformShowLabel = 0` 이면 FI 원본 모습 100 % 가 된다.
|
||||
if (cfg.platformShowLabel != 0)
|
||||
WLIslandLabel.Attach(root.transform, new Vector3(0f, cfg.platformLabelHeight, 0f),
|
||||
string.Format(cfg.platformLabelFormat, price));
|
||||
string.IsNullOrEmpty(labelOverride)
|
||||
? string.Format(cfg.platformLabelFormat, price)
|
||||
: labelOverride);
|
||||
return self;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ namespace WL.Island
|
|||
|
||||
PlayerPrefs.SetString(kTokenKey, cfg.saveResetToken);
|
||||
PlayerPrefs.SetInt(cfg.dungeonUnlockPrefsKey, 0); // 해금한 던전도 0 개로
|
||||
// WL-816zc — 초기 플로우 단계·확장 개방도 같이 0 으로(시작마다 초기화 대상)
|
||||
if (!string.IsNullOrEmpty(cfg.flowStepPrefsKey)) PlayerPrefs.SetInt(cfg.flowStepPrefsKey, 0);
|
||||
if (!string.IsNullOrEmpty(cfg.expansionUnlockPrefsKey)) PlayerPrefs.SetInt(cfg.expansionUnlockPrefsKey, 0);
|
||||
PlayerPrefs.Save();
|
||||
WLIslandBridge.ClearPending(); // 보류 보상도 비운다(정적 값까지)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,58 @@ namespace WL.Island
|
|||
public Vector3 platformPosition = Vector3.zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WL-816zc — 「섬 초기 플로우」 한 단계 = 이 표의 한 행.
|
||||
/// 발판 1개가 이 행의 값(문구·가격·자리)으로 서고, 값을 치르면 이 행의 설치물이 그 자리에 남는다.
|
||||
/// 코드 수정 0 — 순서·가격·설치물은 전부 여기 값이다(C45).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WLFlowStepDef
|
||||
{
|
||||
[Tooltip("0 이면 이 단계는 없는 것으로 친다(발판도 안 선다).")]
|
||||
public int enabled_ = 1;
|
||||
|
||||
[Tooltip("발판 위에 뜨는 문구(가격은 아래 flowLabelFormat 이 붙인다).")]
|
||||
public string label = "설치";
|
||||
|
||||
[Tooltip("이 단계를 여는 값(골드).")]
|
||||
public int price = 10;
|
||||
|
||||
[Tooltip("발판 자리 = `dungeons[이 번호]` 행의 platformPosition 을 그대로 쓴다(기존 발판 #2·#3·#4 좌표 재사용).\n" +
|
||||
"-1 이면 밭 구역 중심(= 지금의 첫 발판 자리) · 표 번호는 enabled_ 와 무관한 **줄 번호**다.")]
|
||||
public int platformFromDungeonRow = -1;
|
||||
|
||||
[Tooltip("위가 -1 이고 밭 자리도 못 찾았을 때 쓸 월드 좌표.")]
|
||||
public Vector3 platformPosition = Vector3.zero;
|
||||
|
||||
[Tooltip("설치물 종류 — camp(캠프 장식) · prefab(아래 프리팹) · dungeon(던전 입구).")]
|
||||
public string kind = "prefab";
|
||||
|
||||
[Tooltip("kind=prefab 일 때 그 자리에 놓을 프리팹 경로.")]
|
||||
public string prefabPath = "";
|
||||
|
||||
[Tooltip("같이 놓을 것이 있을 때(예: 좌판 프리팹에 상인이 안 들어 있으면 상인을 덧붙인다). 비우면 안 놓는다.")]
|
||||
public string extraPrefabPath = "";
|
||||
|
||||
[Tooltip("덧붙일 것이 이미 설치물 안에 있으면 건너뛸 컴포넌트 이름(비우면 항상 덧붙인다).")]
|
||||
public string extraSkipIfComponent = "";
|
||||
|
||||
[Tooltip("덧붙일 것의 설치물 기준 위치 차이.")]
|
||||
public Vector3 extraOffset = new Vector3(0f, 0f, -1.1f);
|
||||
|
||||
[Tooltip("설치물의 Y 회전(도).")]
|
||||
public float yaw;
|
||||
|
||||
[Tooltip("설치물 크기 배수(1 = 프리팹 원본).")]
|
||||
public float scale = 1f;
|
||||
|
||||
[Tooltip("kind=dungeon 일 때 열 던전의 **줄 번호**(`dungeons[이 번호]` · enabled_ 와 무관).")]
|
||||
public int dungeonRow;
|
||||
|
||||
[Tooltip("1 이면 설치물의 콜라이더를 그대로 둔다. 0 = 전부 끈다(이동 방해 0 · NavMesh 재베이크 없음).")]
|
||||
public int keepCollider;
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "WLIslandSettings", menuName = "WL/Island Settings", order = 340)]
|
||||
public sealed class WLIslandSettings : ScriptableObject
|
||||
{
|
||||
|
|
@ -469,6 +521,68 @@ namespace WL.Island
|
|||
[Tooltip("위 대기의 상한(초). 이 시간이 지나면 조건과 무관하게 로딩 칸을 채운다(로딩 화면이 영영 안 닫히는 것 방지).")]
|
||||
public float dungeonLoadingMaxWaitSeconds = 8f;
|
||||
|
||||
// ───────────────────────────────── WL-816zc 섬 초기 플로우 v2
|
||||
[Header("WL-816zc — 섬 초기 플로우(캠프파이어 → 상인 → 사과나무 → 던전)")]
|
||||
|
||||
[Tooltip("1 = 아래 단계 표 순서대로 발판이 하나씩 선다(PD 2026-09-15).\n" +
|
||||
"0 = 되돌리기 — 지금까지의 「던전 발판 4개」 방식으로 돌아간다.")]
|
||||
public int flowV2Enabled = 1;
|
||||
|
||||
[Tooltip("어디까지 치렀는지 세는 PlayerPrefs 키 1개(시작마다 초기화 대상).")]
|
||||
public string flowStepPrefsKey = "WL_Island_FlowStep";
|
||||
|
||||
[Tooltip("발판 문구 서식 — {0} = 단계 문구 · {1} = 가격.")]
|
||||
public string flowLabelFormat = "{0}\n{1} 골드";
|
||||
|
||||
[Tooltip("한 줄 = 한 단계. 위에서부터 차례로 발판이 선다.")]
|
||||
public WLFlowStepDef[] flowSteps = new WLFlowStepDef[]
|
||||
{
|
||||
new WLFlowStepDef
|
||||
{
|
||||
label = "캠프파이어 설치", price = 10,
|
||||
platformFromDungeonRow = -1, // 밭 구역 중심
|
||||
kind = "camp",
|
||||
},
|
||||
new WLFlowStepDef
|
||||
{
|
||||
label = "상인 설치", price = 30,
|
||||
platformFromDungeonRow = 1, // 기존 발판 #2 좌표
|
||||
kind = "prefab",
|
||||
prefabPath = "Assets/FarmingIsland/Prefabs/Buildings/Stall01.prefab",
|
||||
extraPrefabPath = "Assets/FarmingIsland/Prefabs/Characters/Merchant.prefab",
|
||||
extraSkipIfComponent = "Merchant", // 좌판 안에 이미 상인이 있으면 안 덧붙인다
|
||||
extraOffset = new Vector3(0f, 0f, -1.1f),
|
||||
yaw = 180f,
|
||||
},
|
||||
new WLFlowStepDef
|
||||
{
|
||||
label = "사과나무 설치", price = 60,
|
||||
platformFromDungeonRow = 2, // 기존 발판 #3 좌표
|
||||
kind = "prefab",
|
||||
prefabPath = "Assets/FarmingIsland/Prefabs/FruitTrees/Tree_Apple.prefab",
|
||||
},
|
||||
new WLFlowStepDef
|
||||
{
|
||||
label = "던전 생성", price = 100,
|
||||
platformFromDungeonRow = 3, // 기존 발판 #4 좌표
|
||||
kind = "dungeon",
|
||||
dungeonRow = 0, // 던전 1 입구(Arch 포탈)
|
||||
},
|
||||
};
|
||||
|
||||
[Tooltip("1 = 원본 에셋의 섬 확장 가격판(FI Purchaser)을 **첫 던전 승리 전까지** 숨긴다.\n" +
|
||||
"승리하면 그대로 다시 켜져서 이후는 FI 원본 순서(Island.Constraint)대로 확장된다. 0 = 처음부터 보인다.")]
|
||||
public int expansionAfterFirstWin = 1;
|
||||
|
||||
[Tooltip("첫 승리 여부를 적는 PlayerPrefs 키(시작마다 초기화 대상).")]
|
||||
public string expansionUnlockPrefsKey = "WL_Island_ExpansionUnlocked";
|
||||
|
||||
[Tooltip("확장 가격판 재숨김 주기(초) — FI 가 나중에 만들어 내는 가격판까지 잡는다.")]
|
||||
public float expansionHideInterval = 0.5f;
|
||||
|
||||
[Tooltip("위 재숨김을 계속하는 시간(초). 0 이면 처음 1회만 숨긴다.")]
|
||||
public float expansionHideSeconds = 20f;
|
||||
|
||||
// ───────────────────────────────── 던전 표
|
||||
[Header("던전 표 — 한 줄 = 던전 하나")]
|
||||
public WLDungeonDef[] dungeons = new WLDungeonDef[0];
|
||||
|
|
@ -497,6 +611,13 @@ namespace WL.Island
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>던전 표의 **줄 번호**로 그 행을 그대로 준다(enabled_ 와 무관 — 좌표만 빌려 쓸 때 쓴다 · WL-816zc).</summary>
|
||||
public WLDungeonDef DungeonRow(int rawIndex)
|
||||
{
|
||||
if (dungeons == null || rawIndex < 0 || rawIndex >= dungeons.Length) return null;
|
||||
return dungeons[rawIndex];
|
||||
}
|
||||
|
||||
/// <summary>씬 이름으로 던전 행을 찾는다(같은 씬을 여러 던전이 써도 되므로 맵 ID 우선).</summary>
|
||||
public WLDungeonDef FindBySceneName(string sceneName)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue