using System.Collections.Generic; using GameFramework.DataTable; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.Factory; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; using Random = System.Random; namespace GeometryTD.CustomComponent { public sealed class InventoryGenerationComponent : GameFrameworkComponent { private readonly List _shopPriceRows = new(); private IDataTable _shopPriceTable; private IDataTable _dropPoolTable; private IDataTable _muzzleCompTable; private IDataTable _bearingCompTable; private IDataTable _baseCompTable; private ShopGoodsBuilder _shopGoodsBuilder; private DropPoolRoller _dropPoolRoller; private RewardCandidateBuilder _rewardCandidateBuilder; private OutGameDropItemBuilder _outGameDropItemBuilder; public List BuildShopGoods(int goodsCount, int runSeed = 0, int sequenceIndex = -1) { EnsureShopBuilder(); return _shopGoodsBuilder.BuildGoods(goodsCount, runSeed, sequenceIndex); } public EnemyDropResult ResolveEnemyDrop( in EnemyDropContext context, int runSeed, int sequenceIndex, ref int nextDropOrdinal) { DREnemy enemy = context.Enemy; if (enemy == null) { return EnemyDropResult.Empty; } int coin = Mathf.Max(0, enemy.DropCoin); int gold = 0; float dropRate = enemy.DropPercent > 1f ? Mathf.Clamp01(enemy.DropPercent * 0.01f) : Mathf.Clamp01(enemy.DropPercent); int dropOrdinal = nextDropOrdinal; nextDropOrdinal++; InventoryGenerationRandomContext randomContext = new(runSeed, sequenceIndex, InventoryTagSourceType.Drop, dropOrdinal); Random random = randomContext.CreateRandom(); if (enemy.DropGold > 0 && dropRate > 0f && random.NextDouble() <= dropRate) { gold = Mathf.Max(0, enemy.DropGold); } TowerCompItemData lootItem = null; if (OutGameDropRuleService.ShouldRollOutGameItem(context.DisplayPhaseIndex, random) && TryRollOutGameItem( context.DisplayPhaseIndex, context.ThemeType, randomContext, random, out TowerCompItemData droppedItem)) { lootItem = droppedItem; } return new EnemyDropResult(coin, gold, lootItem); } public IReadOnlyList BuildRewardCandidates( int displayPhaseIndex, LevelThemeType themeType, int candidateCount, int runSeed, int sequenceIndex, ref int nextRewardOrdinal) { RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder(); int rewardOrdinal = nextRewardOrdinal; IReadOnlyList candidates = rewardCandidateBuilder.BuildCandidates( displayPhaseIndex, themeType, candidateCount, CreateNextRewardRandomContext, BuildRewardCandidateItem); nextRewardOrdinal = rewardOrdinal; return candidates; InventoryGenerationRandomContext CreateNextRewardRandomContext() { return new InventoryGenerationRandomContext( runSeed, sequenceIndex, InventoryTagSourceType.Reward, rewardOrdinal++); } } public IReadOnlyList BuildEventRewardComponents( int count, RarityType rarity, int runSeed, int sequenceIndex, int eventId, int optionIndex, int effectIndex) { return BuildEventRewardComponents( count, rarity, rarity, runSeed, sequenceIndex, eventId, optionIndex, effectIndex); } public IReadOnlyList BuildEventRewardComponents( int count, RarityType minRarity, RarityType maxRarity, int runSeed, int sequenceIndex, int eventId, int optionIndex, int effectIndex) { EnsureComponentTables(); int resolvedCount = Mathf.Max(0, count); if (resolvedCount <= 0) { return System.Array.Empty(); } RarityType normalizedMinRarity = InventoryRarityRuleService.NormalizeComponentRarity(minRarity); RarityType normalizedMaxRarity = InventoryRarityRuleService.NormalizeComponentRarity(maxRarity); if (normalizedMinRarity > normalizedMaxRarity) { throw new System.InvalidOperationException( $"Event reward rarity range is invalid: {normalizedMinRarity} > {normalizedMaxRarity}."); } List result = new List(resolvedCount); for (int i = 0; i < resolvedCount; i++) { InventoryGenerationRandomContext randomContext = CreateEventRandomContext( runSeed, sequenceIndex, eventId, optionIndex, effectIndex, i); Random random = randomContext.CreateRandom(); result.Add(BuildRandomEventComponentItem(normalizedMinRarity, normalizedMaxRarity, randomContext, random)); } return result; } private void EnsureShopTables() { _shopPriceTable ??= GameEntry.DataTable.GetDataTable(); EnsureComponentTables(); if (_shopPriceTable == null) { throw new System.InvalidOperationException( "InventoryGenerationComponent requires ShopPrice data table."); } if (_shopPriceRows.Count > 0) { return; } DRShopPrice[] rows = _shopPriceTable.GetAllDataRows(); if (rows == null || rows.Length <= 0) { throw new System.InvalidOperationException( "InventoryGenerationComponent requires at least one shop price row."); } foreach (var row in rows) { if (row != null) { _shopPriceRows.Add(row); } } if (_shopPriceRows.Count <= 0) { throw new System.InvalidOperationException("InventoryGenerationComponent requires non-null shop price rows."); } } private void EnsureShopBuilder() { EnsureShopTables(); _shopGoodsBuilder ??= new ShopGoodsBuilder( _shopPriceRows, _muzzleCompTable, _bearingCompTable, _baseCompTable); } private void EnsureDropTables() { _dropPoolTable ??= GameEntry.DataTable.GetDataTable(); EnsureComponentTables(); if (_dropPoolTable == null) { throw new System.InvalidOperationException( "InventoryGenerationComponent requires OutGameDropPool data table."); } } private DropPoolRoller EnsureDropPoolRoller() { EnsureDropTables(); _dropPoolRoller ??= new DropPoolRoller(_dropPoolTable); return _dropPoolRoller; } private RewardCandidateBuilder EnsureRewardCandidateBuilder() { _rewardCandidateBuilder ??= new RewardCandidateBuilder(EnsureDropPoolRoller()); return _rewardCandidateBuilder; } private OutGameDropItemBuilder EnsureOutGameDropItemBuilder() { EnsureDropTables(); _outGameDropItemBuilder ??= new OutGameDropItemBuilder( _muzzleCompTable, _bearingCompTable, _baseCompTable); return _outGameDropItemBuilder; } private bool TryRollOutGameItem( int displayPhaseIndex, LevelThemeType themeType, InventoryGenerationRandomContext randomContext, Random random, out TowerCompItemData droppedItem) { droppedItem = null; DropPoolRoller dropPoolRoller = EnsureDropPoolRoller(); int phaseIndex = Mathf.Max(1, displayPhaseIndex); if (!dropPoolRoller.TryRollRow( phaseIndex, themeType, random, out DROutGameDropPool selectedRow, out RarityType selectedRarity) || selectedRow == null) { return false; } return EnsureOutGameDropItemBuilder().TryBuildItem(selectedRow, selectedRarity, randomContext, out droppedItem); } private TowerCompItemData BuildRewardCandidateItem( DROutGameDropPool row, RarityType rarity, InventoryGenerationRandomContext randomContext) { if (!EnsureOutGameDropItemBuilder().TryBuildItem(row, rarity, randomContext, out TowerCompItemData droppedItem)) { return null; } return droppedItem; } private void EnsureComponentTables() { _muzzleCompTable ??= GameEntry.DataTable.GetDataTable(); _bearingCompTable ??= GameEntry.DataTable.GetDataTable(); _baseCompTable ??= GameEntry.DataTable.GetDataTable(); if (_muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null) { throw new System.InvalidOperationException( "InventoryGenerationComponent requires MuzzleComp, BearingComp, and BaseComp data tables."); } } private TowerCompItemData BuildRandomEventComponentItem( RarityType minRarity, RarityType maxRarity, InventoryGenerationRandomContext randomContext, Random random) { RarityType rarity = ResolveEventRewardRarity(minRarity, maxRarity, random); int slotRoll = random.Next(0, 3); return slotRoll switch { 0 => BuildRandomEventMuzzleItem(rarity, randomContext, random), 1 => BuildRandomEventBearingItem(rarity, randomContext, random), _ => BuildRandomEventBaseItem(rarity, randomContext, random) }; } private static RarityType ResolveEventRewardRarity( RarityType minRarity, RarityType maxRarity, Random random) { if (minRarity >= maxRarity) { return minRarity; } int rarityValue = random.Next((int)minRarity, (int)maxRarity + 1); return (RarityType)rarityValue; } private MuzzleCompItemData BuildRandomEventMuzzleItem( RarityType rarity, InventoryGenerationRandomContext randomContext, Random random) { DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows(); DRMuzzleComp config = rows[random.Next(0, rows.Length)]; return ComponentItemFactory.CreateMuzzle( config, randomContext.CreateStableItemInstanceId(), rarity, randomContext.CreateTagRandomContext(config.Id)); } private BearingCompItemData BuildRandomEventBearingItem( RarityType rarity, InventoryGenerationRandomContext randomContext, Random random) { DRBearingComp[] rows = _bearingCompTable.GetAllDataRows(); DRBearingComp config = rows[random.Next(0, rows.Length)]; return ComponentItemFactory.CreateBearing( config, randomContext.CreateStableItemInstanceId(), rarity, randomContext.CreateTagRandomContext(config.Id)); } private BaseCompItemData BuildRandomEventBaseItem( RarityType rarity, InventoryGenerationRandomContext randomContext, Random random) { DRBaseComp[] rows = _baseCompTable.GetAllDataRows(); DRBaseComp config = rows[random.Next(0, rows.Length)]; return ComponentItemFactory.CreateBase( config, randomContext.CreateStableItemInstanceId(), rarity, randomContext.CreateTagRandomContext(config.Id)); } private static InventoryGenerationRandomContext CreateEventRandomContext( int runSeed, int sequenceIndex, int eventId, int optionIndex, int effectIndex, int itemIndex) { return new InventoryGenerationRandomContext( runSeed, sequenceIndex, InventoryTagSourceType.Event, BuildEventLocalOrdinal(eventId, optionIndex, effectIndex, itemIndex)); } private static int BuildEventLocalOrdinal(int eventId, int optionIndex, int effectIndex, int itemIndex) { unchecked { int value = 17; value = value * 31 + eventId; value = value * 31 + optionIndex; value = value * 31 + effectIndex; value = value * 31 + itemIndex; return value & int.MaxValue; } } } }