using System; 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 = UnityEngine.Random; namespace GeometryTD.CustomComponent { public sealed class InventoryGenerationComponent : GameFrameworkComponent { private const float DropChanceBase = 0.05f; private const float DropChancePerPhase = 0.2f; private const float DropChanceCap = 0.2f; private long _nextTempInstanceId = 1000000; private int _runSeed; private int _nodeSequenceIndex = -1; private int _nextDropTagOrdinal; private int _nextRewardTagOrdinal; 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; public void ConfigureRunContext(int runSeed, int sequenceIndex) { _runSeed = runSeed; _nodeSequenceIndex = sequenceIndex; _nextDropTagOrdinal = 0; _nextRewardTagOrdinal = 0; } public List BuildShopGoods(int goodsCount, int runSeed = 0, int sequenceIndex = -1) { EnsureShopBuilder(); return _shopGoodsBuilder.BuildGoods(goodsCount, runSeed, sequenceIndex, AllocateTempInstanceId); } public EnemyDropResult ResolveEnemyDrop(in EnemyDropContext context) { 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); if (enemy.DropGold > 0 && dropRate > 0f && Random.value <= dropRate) { gold = Mathf.Max(0, enemy.DropGold); } TowerCompItemData lootItem = null; if (ShouldRollOutGameItem(context.DisplayPhaseIndex) && TryRollOutGameItem(context.DisplayPhaseIndex, context.ThemeType, out TowerCompItemData droppedItem)) { lootItem = droppedItem; } return new EnemyDropResult(coin, gold, lootItem); } public IReadOnlyList BuildRewardCandidates( int displayPhaseIndex, LevelThemeType themeType, int candidateCount) { RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder(); return rewardCandidateBuilder.BuildCandidates( displayPhaseIndex, themeType, candidateCount, BuildRewardCandidateItem); } private void EnsureShopTables() { _shopPriceTable ??= GameEntry.DataTable.GetDataTable(); _muzzleCompTable ??= GameEntry.DataTable.GetDataTable(); _bearingCompTable ??= GameEntry.DataTable.GetDataTable(); _baseCompTable ??= GameEntry.DataTable.GetDataTable(); if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null) { throw new InvalidOperationException( "InventoryGenerationComponent requires ShopPrice, MuzzleComp, BearingComp, and BaseComp data tables."); } if (_shopPriceRows.Count > 0) { return; } DRShopPrice[] rows = _shopPriceTable.GetAllDataRows(); if (rows == null || rows.Length <= 0) { throw new 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 InvalidOperationException("InventoryGenerationComponent requires non-null shop price rows."); } } private void EnsureShopBuilder() { EnsureShopTables(); _shopGoodsBuilder ??= new ShopGoodsBuilder( _shopPriceRows, _muzzleCompTable, _bearingCompTable, _baseCompTable); } private long AllocateTempInstanceId() { return _nextTempInstanceId++; } private void EnsureDropTables() { _dropPoolTable ??= GameEntry.DataTable.GetDataTable(); _muzzleCompTable ??= GameEntry.DataTable.GetDataTable(); _bearingCompTable ??= GameEntry.DataTable.GetDataTable(); _baseCompTable ??= GameEntry.DataTable.GetDataTable(); if (_dropPoolTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null) { throw new InvalidOperationException( "InventoryGenerationComponent requires OutGameDropPool, MuzzleComp, BearingComp, and BaseComp data tables."); } } private DropPoolRoller EnsureDropPoolRoller() { EnsureDropTables(); _dropPoolRoller ??= new DropPoolRoller(_dropPoolTable); return _dropPoolRoller; } private RewardCandidateBuilder EnsureRewardCandidateBuilder() { _rewardCandidateBuilder ??= new RewardCandidateBuilder(EnsureDropPoolRoller()); return _rewardCandidateBuilder; } private static bool ShouldRollOutGameItem(int displayPhaseIndex) { int phaseIndex = Mathf.Max(1, displayPhaseIndex); float dropChance = Mathf.Clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0f, DropChanceCap); return Random.value <= dropChance; } private bool TryRollOutGameItem(int displayPhaseIndex, LevelThemeType themeType, out TowerCompItemData droppedItem) { droppedItem = null; DropPoolRoller dropPoolRoller = EnsureDropPoolRoller(); int phaseIndex = Mathf.Max(1, displayPhaseIndex); if (!dropPoolRoller.TryRollRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null) { return false; } return TryBuildDropItem(selectedRow, InventoryTagSourceType.Drop, AllocateDropTagOrdinal(), out droppedItem); } private bool TryBuildDropItem( DROutGameDropPool row, InventoryTagSourceType sourceType, int localOrdinal, out TowerCompItemData droppedItem) { droppedItem = null; if (row.ItemId <= 0 || string.IsNullOrWhiteSpace(row.ItemType)) { return false; } string itemType = row.ItemType.Trim(); if (itemType.Equals("MuzzleComp", StringComparison.OrdinalIgnoreCase)) { DRMuzzleComp config = _muzzleCompTable.GetDataRow(row.ItemId); if (config == null) { return false; } droppedItem = ComponentItemFactory.CreateMuzzle( config, AllocateTempInstanceId(), row.Rarity, CreateRandomContext(sourceType, localOrdinal, config.Id)); return true; } if (itemType.Equals("BearingComp", StringComparison.OrdinalIgnoreCase)) { DRBearingComp config = _bearingCompTable.GetDataRow(row.ItemId); if (config == null) { return false; } droppedItem = ComponentItemFactory.CreateBearing( config, AllocateTempInstanceId(), row.Rarity, CreateRandomContext(sourceType, localOrdinal, config.Id)); return true; } if (itemType.Equals("BaseComp", StringComparison.OrdinalIgnoreCase)) { DRBaseComp config = _baseCompTable.GetDataRow(row.ItemId); if (config == null) { return false; } droppedItem = ComponentItemFactory.CreateBase( config, AllocateTempInstanceId(), row.Rarity, CreateRandomContext(sourceType, localOrdinal, config.Id)); return true; } return false; } private int AllocateDropTagOrdinal() { return _nextDropTagOrdinal++; } private int AllocateRewardTagOrdinal() { return _nextRewardTagOrdinal++; } private TowerCompItemData BuildRewardCandidateItem(DROutGameDropPool row) { if (!TryBuildDropItem(row, InventoryTagSourceType.Reward, AllocateRewardTagOrdinal(), out TowerCompItemData droppedItem)) { return null; } return droppedItem; } private InventoryTagRandomContext CreateRandomContext( InventoryTagSourceType sourceType, int localOrdinal, int configId) { return sourceType switch { InventoryTagSourceType.Reward => InventoryTagRandomContext.CreateReward(_runSeed, _nodeSequenceIndex, localOrdinal, configId), _ => InventoryTagRandomContext.CreateDrop(_runSeed, _nodeSequenceIndex, localOrdinal, configId) }; } } }