using System; using System.Collections.Generic; using UnityEngine; public class FishingRewardSystem : MonoBehaviour { [Serializable] public class LootEntry { public FishingItemType itemType = FishingItemType.Fish; public string displayName = "물고기"; [Min(0f)] public float weight = 1f; public LootEntry(FishingItemType itemType, string displayName, float weight) { this.itemType = itemType; this.displayName = displayName; this.weight = Mathf.Max(0f, weight); } } public struct CatchResult { public FishingItemType ItemType; public string DisplayName; public bool CountsAsCleanupItem; public bool IsMemoryPiece; public CatchResult(FishingItemType itemType, string displayName, bool countsAsCleanupItem, bool isMemoryPiece) { ItemType = itemType; DisplayName = displayName; CountsAsCleanupItem = countsAsCleanupItem; IsMemoryPiece = isMemoryPiece; } } [Header("Loot Table - Dirty Pond")] [Tooltip("연못이 정화되기 전에는 물고기와 쓰레기만 낚입니다. 기억의 조각은 자동 제외됩니다.")] [SerializeField] private List dirtyPondLootTable = new List(); [Header("Loot Table - Clean Pond")] [Tooltip("연못이 정화된 뒤에는 기억의 조각이 낚입니다.")] [SerializeField] private List cleanPondLootTable = new List(); [Header("Debug")] [SerializeField] private bool showDebugLog = true; public IReadOnlyList DirtyPondLootTable => dirtyPondLootTable; public IReadOnlyList CleanPondLootTable => cleanPondLootTable; private void Reset() { SetDefaultLootTables(); } private void Awake() { EnsureLootTables(); } private void OnValidate() { EnsureLootTables(); ClampLootTable(dirtyPondLootTable); ClampLootTable(cleanPondLootTable); } private void EnsureLootTables() { if (dirtyPondLootTable == null) dirtyPondLootTable = new List(); if (cleanPondLootTable == null) cleanPondLootTable = new List(); // 이전 버전의 상한 생선/병/봉지/나침반 등이 인스펙터에 남아 있어도 // 런타임에서는 물고기 / 쓰레기 / 기억의 조각 3종만 남도록 정리합니다. SanitizeLootTables(); if (dirtyPondLootTable.Count == 0) SetDefaultDirtyLootTable(); if (cleanPondLootTable.Count == 0) SetDefaultCleanLootTable(); } private void SanitizeLootTables() { SanitizeTable(dirtyPondLootTable, allowFish: true, allowTrash: true, allowMemoryPiece: false); SanitizeTable(cleanPondLootTable, allowFish: false, allowTrash: false, allowMemoryPiece: true); } private void SanitizeTable(List table, bool allowFish, bool allowTrash, bool allowMemoryPiece) { if (table == null) return; for (int i = table.Count - 1; i >= 0; i--) { LootEntry entry = table[i]; if (entry == null || !IsAllowedInTable(entry.itemType, allowFish, allowTrash, allowMemoryPiece)) table.RemoveAt(i); } } private bool IsAllowedInTable(FishingItemType itemType, bool allowFish, bool allowTrash, bool allowMemoryPiece) { switch (itemType) { case FishingItemType.Fish: return allowFish; case FishingItemType.Trash: return allowTrash; case FishingItemType.MemoryPiece: return allowMemoryPiece; case FishingItemType.None: default: return false; } } private void SetDefaultLootTables() { SetDefaultDirtyLootTable(); SetDefaultCleanLootTable(); } private void SetDefaultDirtyLootTable() { dirtyPondLootTable = new List { new LootEntry(FishingItemType.Fish, "물고기", 35f), new LootEntry(FishingItemType.Trash, "쓰레기", 65f) }; } private void SetDefaultCleanLootTable() { cleanPondLootTable = new List { new LootEntry(FishingItemType.MemoryPiece, "기억의 조각", 100f) }; } private void ClampLootTable(List table) { if (table == null) return; for (int i = 0; i < table.Count; i++) { if (table[i] == null) { table[i] = new LootEntry(FishingItemType.Fish, "물고기", 1f); continue; } table[i].weight = Mathf.Max(0f, table[i].weight); // 이전 버전에서 남은 표시 이름을 3종 아이템 이름으로 정리합니다. table[i].displayName = GetDefaultDisplayName(table[i].itemType); } } public CatchResult RollCatch(bool pondCleaned, bool forceMemoryPiece) { if (forceMemoryPiece) return CreateCatchResult(FishingItemType.MemoryPiece, GetDefaultDisplayName(FishingItemType.MemoryPiece)); List table = pondCleaned ? cleanPondLootTable : dirtyPondLootTable; LootEntry selectedEntry = RollFromTable(table, pondCleaned); if (selectedEntry == null) return CreateCatchResult(pondCleaned ? FishingItemType.MemoryPiece : FishingItemType.Fish, GetDefaultDisplayName(pondCleaned ? FishingItemType.MemoryPiece : FishingItemType.Fish)); return CreateCatchResult(selectedEntry.itemType, selectedEntry.displayName); } private LootEntry RollFromTable(List table, bool pondCleaned) { if (table == null || table.Count == 0) return null; float totalWeight = 0f; for (int i = 0; i < table.Count; i++) { LootEntry entry = table[i]; if (!IsRollableEntry(entry, pondCleaned)) continue; totalWeight += entry.weight; } if (totalWeight <= 0f) return null; float roll = UnityEngine.Random.Range(0f, totalWeight); float current = 0f; for (int i = 0; i < table.Count; i++) { LootEntry entry = table[i]; if (!IsRollableEntry(entry, pondCleaned)) continue; current += entry.weight; if (roll <= current) return entry; } return null; } private bool IsRollableEntry(LootEntry entry, bool pondCleaned) { if (entry == null) return false; if (entry.weight <= 0f) return false; if (entry.itemType == FishingItemType.None) return false; if (!pondCleaned && entry.itemType == FishingItemType.MemoryPiece) return false; return entry.itemType == FishingItemType.Fish || entry.itemType == FishingItemType.Trash || entry.itemType == FishingItemType.MemoryPiece; } private CatchResult CreateCatchResult(FishingItemType itemType, string displayName) { string finalDisplayName = string.IsNullOrWhiteSpace(displayName) ? GetDefaultDisplayName(itemType) : displayName; bool countsAsCleanupItem = IsCleanupItem(itemType); bool isMemoryPiece = itemType == FishingItemType.MemoryPiece; if (showDebugLog) Debug.Log($"낚시 획득: {finalDisplayName}"); return new CatchResult(itemType, finalDisplayName, countsAsCleanupItem, isMemoryPiece); } public bool IsCleanupItem(FishingItemType itemType) { return itemType == FishingItemType.Trash; } public string GetDefaultDisplayName(FishingItemType itemType) { switch (itemType) { case FishingItemType.Fish: return "물고기"; case FishingItemType.Trash: return "쓰레기"; case FishingItemType.MemoryPiece: return "기억의 조각"; case FishingItemType.None: default: return "없음"; } } }