using System; using System.Collections.Generic; using GameFramework.DataTable; using GeometryTD.DataTable; using GeometryTD.Definition; using UnityEngine; using Random = UnityEngine.Random; namespace GeometryTD.CustomComponent { internal sealed class CombatResourceManager { private const float DropChanceBase = 0.05f; private const float DropChancePerPhase = 0.0125f; private const float DropChanceCap = 0.45f; private const float RarityCurveScalePhase = 30f; private readonly List _eligibleDropPoolBuffer = new(); private readonly Dictionary _rarityRollWeightBuffer = new(); private readonly BackpackInventoryData _rewardInventory = new(); private IDataTable _drOutGameDropPool; private IDataTable _drMuzzleComp; private IDataTable _drBearingComp; private IDataTable _drBaseComp; private long _nextDropItemInstanceId = 1; public int GainedCoin { get; private set; } public int GainedGold { get; private set; } public void Reset() { GainedCoin = 0; GainedGold = 0; _rewardInventory.Gold = 0; _rewardInventory.MuzzleComponents.Clear(); _rewardInventory.BearingComponents.Clear(); _rewardInventory.BaseComponents.Clear(); _rewardInventory.Towers.Clear(); _nextDropItemInstanceId = 1; } public BackpackInventoryData GetRewardInventorySnapshot() { return new BackpackInventoryData { Gold = _rewardInventory.Gold, MuzzleComponents = new List(_rewardInventory.MuzzleComponents), BearingComponents = new List(_rewardInventory.BearingComponents), BaseComponents = new List(_rewardInventory.BaseComponents), Towers = new List(_rewardInventory.Towers) }; } public void AddEnemyDefeatedReward(int gainedCoin, int gainedGold) { int coin = Mathf.Max(0, gainedCoin); int gold = Mathf.Max(0, gainedGold); GainedCoin += coin; GainedGold += gold; if (gold > 0) { _rewardInventory.Gold += gold; } GameEntry.CombatNode?.ApplyEnemyDropReward(coin, gold); } public void AddSettlementGold(int gainedGold) { int gold = Mathf.Max(0, gainedGold); if (gold <= 0) { return; } GainedGold += gold; _rewardInventory.Gold += gold; } public bool TryRollOutGameItemDrop(int displayPhaseIndex, LevelThemeType themeType) { int phaseIndex = Mathf.Max(1, displayPhaseIndex); float dropChance = Mathf.Clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0f, DropChanceCap); if (Random.value > dropChance) { return false; } if (!TryRollOutGameItem(phaseIndex, themeType, out TowerCompItemData droppedItem)) { return false; } if (droppedItem is MuzzleCompItemData muzzleCompItemData) { _rewardInventory.MuzzleComponents.Add(muzzleCompItemData); return true; } if (droppedItem is BearingCompItemData bearingCompItemData) { _rewardInventory.BearingComponents.Add(bearingCompItemData); return true; } if (droppedItem is BaseCompItemData baseCompItemData) { _rewardInventory.BaseComponents.Add(baseCompItemData); return true; } return false; } public bool TryRollOutGameItem(int displayPhaseIndex, LevelThemeType themeType, out TowerCompItemData droppedItem) { droppedItem = null; int phaseIndex = Mathf.Max(1, displayPhaseIndex); if (!TryPickDropPoolRow(phaseIndex, themeType, out DROutGameDropPool selectedRow)) { return false; } return TryBuildDropItem(selectedRow, out droppedItem); } public IReadOnlyList RollSettlementRewardCandidates( int displayPhaseIndex, LevelThemeType themeType, int candidateCount) { int resolvedCount = Mathf.Max(0, candidateCount); if (resolvedCount <= 0) { return Array.Empty(); } List candidates = new List(resolvedCount); HashSet selectedPoolRowIds = new HashSet(); int maxAttempts = Mathf.Max(resolvedCount * 6, resolvedCount); int phaseIndex = Mathf.Max(1, displayPhaseIndex); int attempts = 0; while (candidates.Count < resolvedCount && attempts < maxAttempts) { attempts++; if (!TryPickDropPoolRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null) { break; } if (!selectedPoolRowIds.Add(selectedRow.Id)) { continue; } if (!TryBuildDropItem(selectedRow, out TowerCompItemData droppedItem) || droppedItem == null) { continue; } candidates.Add(droppedItem); } attempts = 0; while (candidates.Count < resolvedCount && attempts < maxAttempts) { attempts++; if (!TryRollOutGameItem(phaseIndex, themeType, out TowerCompItemData droppedItem) || droppedItem == null) { break; } candidates.Add(droppedItem); } return candidates; } private bool TryPickDropPoolRow(int displayPhaseIndex, LevelThemeType themeType, out DROutGameDropPool selectedRow) { selectedRow = null; IDataTable dropTable = EnsureOutGameDropPoolTable(); if (dropTable == null) { return false; } _eligibleDropPoolBuffer.Clear(); DROutGameDropPool[] allRows = dropTable.GetAllDataRows(); for (int i = 0; i < allRows.Length; i++) { DROutGameDropPool row = allRows[i]; if (row == null) { continue; } if (row.LevelThemeType != themeType) { continue; } if (displayPhaseIndex < row.MinPhase || displayPhaseIndex > row.MaxPhase) { continue; } _eligibleDropPoolBuffer.Add(row); } if (_eligibleDropPoolBuffer.Count <= 0) { return false; } RarityType selectedRarity = RollRarity(displayPhaseIndex, _eligibleDropPoolBuffer); if (selectedRarity == RarityType.None) { return false; } int totalWeight = 0; for (int i = 0; i < _eligibleDropPoolBuffer.Count; i++) { DROutGameDropPool row = _eligibleDropPoolBuffer[i]; if (row.Rarity != selectedRarity) { continue; } totalWeight += Mathf.Max(1, row.Weight); } if (totalWeight <= 0) { return false; } int randomWeight = Random.Range(1, totalWeight + 1); int cumulativeWeight = 0; for (int i = 0; i < _eligibleDropPoolBuffer.Count; i++) { DROutGameDropPool row = _eligibleDropPoolBuffer[i]; if (row.Rarity != selectedRarity) { continue; } cumulativeWeight += Mathf.Max(1, row.Weight); if (randomWeight <= cumulativeWeight) { selectedRow = row; return true; } } selectedRow = _eligibleDropPoolBuffer[_eligibleDropPoolBuffer.Count - 1]; return selectedRow != null; } private RarityType RollRarity(int displayPhaseIndex, List candidates) { _rarityRollWeightBuffer.Clear(); float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase); for (int i = 0; i < candidates.Count; i++) { DROutGameDropPool row = candidates[i]; if (row == null) { continue; } float curveWeight = GetRarityCurveWeight(row.Rarity, phaseT); if (curveWeight <= 0f) { continue; } if (_rarityRollWeightBuffer.TryGetValue(row.Rarity, out float existingWeight)) { _rarityRollWeightBuffer[row.Rarity] = existingWeight + Mathf.Max(1, row.Weight) * curveWeight; } else { _rarityRollWeightBuffer[row.Rarity] = Mathf.Max(1, row.Weight) * curveWeight; } } float totalWeight = 0f; foreach (var pair in _rarityRollWeightBuffer) { totalWeight += Mathf.Max(0f, pair.Value); } if (totalWeight <= 0f) { return RarityType.None; } float randomWeight = Random.value * totalWeight; float cumulativeWeight = 0f; foreach (var pair in _rarityRollWeightBuffer) { cumulativeWeight += Mathf.Max(0f, pair.Value); if (randomWeight <= cumulativeWeight) { return pair.Key; } } foreach (var pair in _rarityRollWeightBuffer) { return pair.Key; } return RarityType.None; } private static float GetRarityCurveWeight(RarityType rarityType, float phaseT) { float hump = Mathf.Exp(-Mathf.Pow((phaseT - 0.35f) / 0.28f, 2f)); switch (rarityType) { case RarityType.White: return Mathf.Max(0.05f, 0.18f + 1.25f * hump); case RarityType.Green: return Mathf.Max(0.05f, 0.35f + 0.55f * hump); case RarityType.Blue: return 0.18f + 0.55f * phaseT; case RarityType.Purple: return 0.05f + 0.22f * phaseT; case RarityType.Red: return 0.01f + 0.08f * phaseT * phaseT; default: return 0f; } } private IDataTable EnsureOutGameDropPoolTable() { if (_drOutGameDropPool != null) { return _drOutGameDropPool; } _drOutGameDropPool = GameEntry.DataTable.GetDataTable(); return _drOutGameDropPool; } private bool TryBuildDropItem(DROutGameDropPool row, out TowerCompItemData droppedItem) { droppedItem = null; if (row == null || row.ItemId <= 0 || string.IsNullOrWhiteSpace(row.ItemType)) { return false; } string itemType = row.ItemType.Trim(); if (itemType.Equals("MuzzleComp", StringComparison.OrdinalIgnoreCase)) { return TryBuildMuzzleCompItem(row, out droppedItem); } if (itemType.Equals("BearingComp", StringComparison.OrdinalIgnoreCase)) { return TryBuildBearingCompItem(row, out droppedItem); } if (itemType.Equals("BaseComp", StringComparison.OrdinalIgnoreCase)) { return TryBuildBaseCompItem(row, out droppedItem); } return false; } private bool TryBuildMuzzleCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem) { droppedItem = null; _drMuzzleComp ??= GameEntry.DataTable.GetDataTable(); if (_drMuzzleComp == null) { return false; } DRMuzzleComp config = _drMuzzleComp.GetDataRow(row.ItemId); if (config == null) { return false; } droppedItem = new MuzzleCompItemData { InstanceId = _nextDropItemInstanceId++, ConfigId = config.Id, Name = config.Name, Rarity = row.Rarity, Endurance = 100f, Constraint = config.Constraint, Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty(), AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty(), DamageRandomRate = config.DamageRandomRate, AttackMethodType = config.AttackMethodType }; return true; } private bool TryBuildBearingCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem) { droppedItem = null; _drBearingComp ??= GameEntry.DataTable.GetDataTable(); if (_drBearingComp == null) { return false; } DRBearingComp config = _drBearingComp.GetDataRow(row.ItemId); if (config == null) { return false; } droppedItem = new BearingCompItemData { InstanceId = _nextDropItemInstanceId++, ConfigId = config.Id, Name = config.Name, Rarity = row.Rarity, Endurance = 100f, Constraint = config.Constraint, Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty(), RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty(), AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty() }; return true; } private bool TryBuildBaseCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem) { droppedItem = null; _drBaseComp ??= GameEntry.DataTable.GetDataTable(); if (_drBaseComp == null) { return false; } DRBaseComp config = _drBaseComp.GetDataRow(row.ItemId); if (config == null) { return false; } droppedItem = new BaseCompItemData { InstanceId = _nextDropItemInstanceId++, ConfigId = config.Id, Name = config.Name, Rarity = row.Rarity, Endurance = 100f, Constraint = config.Constraint, Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty(), AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty(), AttackPropertyType = config.AttackPropertyType }; return true; } } }