426 lines
14 KiB
C#
426 lines
14 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 EnemyDropResolver
|
|
{
|
|
private const float DropChanceBase = 0.05f;
|
|
private const float DropChancePerPhase = 0.2f;
|
|
private const float DropChanceCap = 0.2f;
|
|
private const float RarityCurveScalePhase = 30f;
|
|
|
|
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
|
|
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
|
|
|
|
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
|
|
private IDataTable<DRMuzzleComp> _drMuzzleComp;
|
|
private IDataTable<DRBearingComp> _drBearingComp;
|
|
private IDataTable<DRBaseComp> _drBaseComp;
|
|
private long _nextDropItemInstanceId = 1;
|
|
|
|
public void Reset()
|
|
{
|
|
_eligibleDropPoolBuffer.Clear();
|
|
_rarityRollWeightBuffer.Clear();
|
|
_nextDropItemInstanceId = 1;
|
|
}
|
|
|
|
public EnemyDropResult Resolve(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<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 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;
|
|
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
|
if (!TryPickDropPoolRow(phaseIndex, themeType, out DROutGameDropPool selectedRow))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return TryBuildDropItem(selectedRow, out droppedItem);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|