using System.Collections.Generic;
using Cielonos.MainGame.Inventory;
using UnityEngine;
namespace Cielonos.MainGame.Items
{
///
/// 从 Resources/Items 目录动态加载物品预制体,并提供加权随机抽取功能。
/// 替代手动维护的 ScriptableObject 物品池,新增装备只需放入对应目录即可自动进入候选池。
///
public static class ItemPoolHelper
{
private const string ITEMS_ROOT_PATH = "Items";
private static readonly Dictionary prefabCache = new Dictionary();
///
/// 从 Resources/Items 的指定子目录中加载所有带 ItemBase 的预制体,
/// 按 ItemFilter 过滤后进行加权随机抽取(不重复)。
///
/// 需要抽取的数量。
/// 随机数生成器,确保同 seed 可复现。
/// 过滤器,传 null 则不过滤。
/// Resources/Items 下的子目录名。不传则搜索整个 Items 目录。
/// 抽中的物品预制体列表(不重复)。
public static List Roll(int count, System.Random rng, ItemFilter filter = null, params string[] subFolders)
{
List candidates = BuildCandidateList(filter, subFolders);
return WeightedPick(candidates, count, rng);
}
///
/// 清除预制体缓存。可在场景切换或 Run 结束时调用。
///
public static void ClearCache()
{
prefabCache.Clear();
}
private static List BuildCandidateList(ItemFilter filter, string[] subFolders)
{
List candidates = new List();
string[] paths = subFolders != null && subFolders.Length > 0
? subFolders
: new[] { string.Empty };
foreach (string sub in paths)
{
string resourcePath = string.IsNullOrEmpty(sub)
? ITEMS_ROOT_PATH
: $"{ITEMS_ROOT_PATH}/{sub}";
GameObject[] prefabs = LoadPrefabs(resourcePath);
foreach (GameObject prefab in prefabs)
{
ItemBase item = prefab.GetComponent();
if (item == null || item.contentData == null) continue;
ContentData data = item.contentData;
// 权重为 0 的物品不进入随机池
if (data.dropWeight <= 0f) continue;
// 应用过滤器
if (filter != null && !filter.Match(item)) continue;
candidates.Add(new CandidateEntry(prefab, data));
}
}
return candidates;
}
private static GameObject[] LoadPrefabs(string resourcePath)
{
if (prefabCache.TryGetValue(resourcePath, out GameObject[] cached))
{
return cached;
}
GameObject[] loaded = Resources.LoadAll(resourcePath);
prefabCache[resourcePath] = loaded;
return loaded;
}
private static List WeightedPick(List candidates, int count, System.Random rng)
{
List results = new List();
int pickCount = Mathf.Min(count, candidates.Count);
for (int i = 0; i < pickCount; i++)
{
float totalWeight = 0f;
foreach (CandidateEntry entry in candidates)
{
totalWeight += entry.data.dropWeight;
}
if (totalWeight <= 0f) break;
float roll = (float)(rng.NextDouble() * totalWeight);
float cumulative = 0f;
CandidateEntry picked = null;
foreach (CandidateEntry entry in candidates)
{
cumulative += entry.data.dropWeight;
if (roll <= cumulative)
{
picked = entry;
break;
}
}
// 安全回退
picked ??= candidates[^1];
results.Add(picked.prefab);
candidates.Remove(picked);
}
return results;
}
private sealed class CandidateEntry
{
public readonly GameObject prefab;
public readonly ContentData data;
public CandidateEntry(GameObject prefab, ContentData data)
{
this.prefab = prefab;
this.data = data;
}
}
}
}