471 lines
16 KiB
C#
471 lines
16 KiB
C#
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<DROutGameDropPool> _eligibleDropPoolBuffer = new();
|
|
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
|
|
private readonly BackpackInventoryData _rewardInventory = new();
|
|
|
|
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
|
|
private IDataTable<DRMuzzleComp> _drMuzzleComp;
|
|
private IDataTable<DRBearingComp> _drBearingComp;
|
|
private IDataTable<DRBaseComp> _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<MuzzleCompItemData>(_rewardInventory.MuzzleComponents),
|
|
BearingComponents = new List<BearingCompItemData>(_rewardInventory.BearingComponents),
|
|
BaseComponents = new List<BaseCompItemData>(_rewardInventory.BaseComponents),
|
|
Towers = new List<DefenseTowerItemData>(_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<TowerCompItemData> RollSettlementRewardCandidates(
|
|
int displayPhaseIndex,
|
|
LevelThemeType themeType,
|
|
int candidateCount)
|
|
{
|
|
int resolvedCount = Mathf.Max(0, candidateCount);
|
|
if (resolvedCount <= 0)
|
|
{
|
|
return Array.Empty<TowerCompItemData>();
|
|
}
|
|
|
|
List<TowerCompItemData> candidates = new List<TowerCompItemData>(resolvedCount);
|
|
HashSet<int> selectedPoolRowIds = new HashSet<int>();
|
|
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<DROutGameDropPool> 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<DROutGameDropPool> 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<DROutGameDropPool> EnsureOutGameDropPoolTable()
|
|
{
|
|
if (_drOutGameDropPool != null)
|
|
{
|
|
return _drOutGameDropPool;
|
|
}
|
|
|
|
_drOutGameDropPool = GameEntry.DataTable.GetDataTable<DROutGameDropPool>();
|
|
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<DRMuzzleComp>();
|
|
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<TagType>(),
|
|
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
|
DamageRandomRate = config.DamageRandomRate,
|
|
AttackMethodType = config.AttackMethodType
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private bool TryBuildBearingCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
|
{
|
|
droppedItem = null;
|
|
_drBearingComp ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
|
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<TagType>(),
|
|
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
|
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private bool TryBuildBaseCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
|
{
|
|
droppedItem = null;
|
|
_drBaseComp ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
|
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<TagType>(),
|
|
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
|
AttackPropertyType = config.AttackPropertyType
|
|
};
|
|
return true;
|
|
}
|
|
}
|
|
}
|