Files
WhaleAdventure_VR_Diet/Assets/My project/Fishing Scripts/UI/FishingRewardSystem.cs
2026-06-29 14:25:49 +09:00

279 lines
8.2 KiB
C#

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<LootEntry> dirtyPondLootTable = new List<LootEntry>();
[Header("Loot Table - Clean Pond")]
[Tooltip("연못이 정화된 뒤에는 기억의 조각이 낚입니다.")]
[SerializeField] private List<LootEntry> cleanPondLootTable = new List<LootEntry>();
[Header("Debug")]
[SerializeField] private bool showDebugLog = true;
public IReadOnlyList<LootEntry> DirtyPondLootTable => dirtyPondLootTable;
public IReadOnlyList<LootEntry> 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<LootEntry>();
if (cleanPondLootTable == null)
cleanPondLootTable = new List<LootEntry>();
// 이전 버전의 상한 생선/병/봉지/나침반 등이 인스펙터에 남아 있어도
// 런타임에서는 물고기 / 쓰레기 / 기억의 조각 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<LootEntry> 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<LootEntry>
{
new LootEntry(FishingItemType.Fish, "물고기", 35f),
new LootEntry(FishingItemType.Trash, "쓰레기", 65f)
};
}
private void SetDefaultCleanLootTable()
{
cleanPondLootTable = new List<LootEntry>
{
new LootEntry(FishingItemType.MemoryPiece, "기억의 조각", 100f)
};
}
private void ClampLootTable(List<LootEntry> 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<LootEntry> 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<LootEntry> 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 "없음";
}
}
}