using System; using System.Collections.Generic; using System.Linq; using Cielonos.MainGame.Inventory; using Cielonos.MainGame.Inventory.Collections; using Cielonos.MainGame.Items; using UnityEngine; namespace Cielonos.MainGame.Rewards { /// /// RewardProfile 的运行时生成器。它产出 ItemBase: /// - prefab 候选适用于商店; /// - 临时实例适用于确认后才能交给背包的奖励界面。 /// public static class RewardGenerator { public static List GeneratePrefabOffers(RewardProfile profile, System.Random rng, RewardGenerationContext context = null) { return BuildOffers(profile, rng, context, null, false); } public static List GenerateRuntimeOffers(RewardProfile profile, System.Random rng, Transform stagingRoot, RewardGenerationContext context = null) { if (stagingRoot == null) { Debug.LogError("[RewardGenerator] Runtime offers require a staging root."); return new List(); } return BuildOffers(profile, rng, context, stagingRoot, true); } private static List BuildOffers(RewardProfile profile, System.Random rng, RewardGenerationContext context, Transform stagingRoot, bool instantiate) { List results = new(); if (profile == null || rng == null) { return results; } context ??= new RewardGenerationContext(); HashSet consumedEntries = new(); HashSet selectedPrefabs = new(); List profileEntries = profile.entries ?? new List(); for (int slot = 0; slot < profile.offerCount; slot++) { List availableEntries = profileEntries .Where(entry => IsEntryAvailable(entry, context, selectedPrefabs, consumedEntries, instantiate)) .ToList(); RewardEntry selectedEntry = PickEntry(availableEntries, rng); if (selectedEntry == null) { break; } if (!TryResolvePrefab(selectedEntry, context, selectedPrefabs, rng, out ItemBase prefab, out ItemBase upgradeTarget)) { consumedEntries.Add(selectedEntry); continue; } ItemBase result = prefab; if (instantiate) { result = UnityEngine.Object.Instantiate(prefab, stagingRoot); result.name = prefab.name; ApplyRuntimeData(result, selectedEntry, upgradeTarget, rng); } else if (selectedEntry.mode == RewardEntryMode.UpgradeComponent) { Debug.LogWarning($"[RewardGenerator] Profile '{profile.name}' uses UpgradeComponent, which cannot be displayed as a prefab offer."); consumedEntries.Add(selectedEntry); continue; } results.Add(result); selectedPrefabs.Add(prefab); if (!selectedEntry.allowRepeat) { consumedEntries.Add(selectedEntry); } } return results; } private static bool IsEntryAvailable(RewardEntry entry, RewardGenerationContext context, ISet selectedPrefabs, ISet consumedEntries, bool instantiate) { if (entry == null || entry.weight <= 0 || consumedEntries.Contains(entry)) { return false; } return entry.mode switch { RewardEntryMode.FixedItem => CanSelectPrefab(entry.itemPrefab, context, selectedPrefabs, entry.allowRepeat), RewardEntryMode.ItemPool => entry.filter != null && ItemPool.HasCandidate( BuildRuntimeFilter(entry, context), selectedPrefabs, entry.filter.GetResourceFolders()), RewardEntryMode.UpgradeComponent => instantiate && entry.itemPrefab != null && context.GetUpgradeableItems().Any(), _ => false }; } private static RewardEntry PickEntry(List entries, System.Random rng) { int totalWeight = entries.Sum(entry => entry.weight); if (totalWeight <= 0) { return null; } int roll = rng.Next(totalWeight); int cumulative = 0; foreach (RewardEntry entry in entries) { cumulative += entry.weight; if (roll < cumulative) { return entry; } } return entries[^1]; } private static bool TryResolvePrefab(RewardEntry entry, RewardGenerationContext context, ISet selectedPrefabs, System.Random rng, out ItemBase prefab, out ItemBase upgradeTarget) { prefab = null; upgradeTarget = null; switch (entry.mode) { case RewardEntryMode.FixedItem: prefab = entry.itemPrefab; return prefab != null; case RewardEntryMode.ItemPool: { List rolled = ItemPool.Roll(1, rng, BuildRuntimeFilter(entry, context), selectedPrefabs, entry.filter.GetResourceFolders()); prefab = rolled.Count > 0 ? rolled[0] : null; return prefab != null; } case RewardEntryMode.UpgradeComponent: { List targets = context.GetUpgradeableItems(); if (targets.Count == 0) { return false; } prefab = entry.itemPrefab; upgradeTarget = targets[rng.Next(targets.Count)]; return prefab != null; } default: return false; } } private static bool CanSelectPrefab(ItemBase prefab, RewardGenerationContext context, ISet selectedPrefabs, bool allowRepeat) { if (prefab == null || context.ShouldExcludeOwnedNonConsumable(prefab)) { return false; } if (selectedPrefabs == null || !selectedPrefabs.Contains(prefab)) { return true; } // Consumable 可以按配置重复;主武器、支援装备、被动装备必须保持唯一性。 return allowRepeat && RewardGenerationContext.IsConsumable(prefab); } private static ItemFilter BuildRuntimeFilter(RewardEntry entry, RewardGenerationContext context) { ItemFilter filter = entry.filter.BuildRuntimeFilter(context.forcedRarity); if (!context.excludeOwnedNonConsumables) { return filter; } return filter.And(new ItemFilter(item => !context.ShouldExcludeOwnedNonConsumable(item))); } private static void ApplyRuntimeData(ItemBase item, RewardEntry entry, ItemBase upgradeTarget, System.Random rng) { if (item is ConsumableBase consumable) { int min = Mathf.Max(1, Mathf.Min(entry.quantityRange.x, entry.quantityRange.y)); int max = Mathf.Max(min, entry.quantityRange.x, entry.quantityRange.y); consumable.stackAmount = rng.Next(min, max + 1); } if (entry.mode == RewardEntryMode.UpgradeComponent && item is PrefabricatedComponent component) { component.targetEquipment = upgradeTarget; } } } public class RewardGenerationContext { public ItemRarity forcedRarity = ItemRarity.None; public List ownedItems = new(); public bool excludeOwnedNonConsumables; /// /// 开启后,奖励生成会排除玩家已经拥有的主武器、支援装备和被动装备。 /// Consumable 不参与该排除规则,仍然可以重复出现并堆叠。 /// public RewardGenerationContext ExcludeOwnedNonConsumables() { excludeOwnedNonConsumables = true; return this; } public bool ShouldExcludeOwnedNonConsumable(ItemBase item) { if (!excludeOwnedNonConsumables || item == null || IsConsumable(item)) { return false; } Type itemIdentity = GetItemIdentity(item); return itemIdentity != null && ownedItems.Any(owned => GetItemIdentity(owned) == itemIdentity); } public List GetUpgradeableItems() { return ownedItems.Where(item => item != null && item.IsUpgradable).ToList(); } public static bool IsConsumable(ItemBase item) { return item is ConsumableBase; } private static Type GetItemIdentity(ItemBase item) { if (item == null) { return null; } return item.contentData != null && item.contentData.itemClass != null ? item.contentData.itemClass : item.GetType(); } public static RewardGenerationContext FromPlayer(ItemRarity forcedRarity = ItemRarity.None) { RewardGenerationContext context = new() { forcedRarity = forcedRarity }; var backpack = MainGameManager.Player?.inventorySc?.backpackSm; if (backpack == null) { return context; } context.ownedItems.AddRange(backpack.mainWeapons); context.ownedItems.AddRange(backpack.supportEquipments); context.ownedItems.AddRange(backpack.passiveEquipments); return context; } } }