InventoryGenerationComponent G2
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
using System.Collections.Generic;
|
||||
using GameFramework.DataTable;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public sealed class DropPoolRoller
|
||||
{
|
||||
private const float RarityCurveScalePhase = 30f;
|
||||
|
||||
private readonly List<DROutGameDropPool> _eligibleRowBuffer = new();
|
||||
private readonly Dictionary<RarityType, float> _rarityWeightBuffer = new();
|
||||
private readonly IDataTable<DROutGameDropPool> _dropPoolTable;
|
||||
|
||||
public DropPoolRoller(IDataTable<DROutGameDropPool> dropPoolTable)
|
||||
{
|
||||
_dropPoolTable = dropPoolTable;
|
||||
}
|
||||
|
||||
public bool TryRollRow(int displayPhaseIndex, LevelThemeType themeType, out DROutGameDropPool selectedRow)
|
||||
{
|
||||
selectedRow = null;
|
||||
|
||||
DROutGameDropPool[] allRows = _dropPoolTable.GetAllDataRows();
|
||||
if (allRows == null || allRows.Length <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CollectEligibleRows(allRows, displayPhaseIndex, themeType);
|
||||
if (_eligibleRowBuffer.Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RarityType selectedRarity = RollRarity(displayPhaseIndex);
|
||||
if (selectedRarity == RarityType.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int totalWeight = 0;
|
||||
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
|
||||
{
|
||||
DROutGameDropPool row = _eligibleRowBuffer[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;
|
||||
foreach (var row in _eligibleRowBuffer)
|
||||
{
|
||||
if (row.Rarity != selectedRarity)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cumulativeWeight += Mathf.Max(1, row.Weight);
|
||||
if (randomWeight <= cumulativeWeight)
|
||||
{
|
||||
selectedRow = row;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
selectedRow = _eligibleRowBuffer[_eligibleRowBuffer.Count - 1];
|
||||
return selectedRow != null;
|
||||
}
|
||||
|
||||
private void CollectEligibleRows(
|
||||
DROutGameDropPool[] allRows,
|
||||
int displayPhaseIndex,
|
||||
LevelThemeType themeType)
|
||||
{
|
||||
_eligibleRowBuffer.Clear();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_eligibleRowBuffer.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
private RarityType RollRarity(int displayPhaseIndex)
|
||||
{
|
||||
_rarityWeightBuffer.Clear();
|
||||
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
|
||||
|
||||
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
|
||||
{
|
||||
DROutGameDropPool row = _eligibleRowBuffer[i];
|
||||
float curveWeight = GetRarityCurveWeight(row.Rarity, phaseT);
|
||||
if (curveWeight <= 0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float rowWeight = Mathf.Max(1, row.Weight) * curveWeight;
|
||||
if (_rarityWeightBuffer.TryGetValue(row.Rarity, out float existingWeight))
|
||||
{
|
||||
_rarityWeightBuffer[row.Rarity] = existingWeight + rowWeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
_rarityWeightBuffer[row.Rarity] = rowWeight;
|
||||
}
|
||||
}
|
||||
|
||||
float totalWeight = 0f;
|
||||
foreach (var pair in _rarityWeightBuffer)
|
||||
{
|
||||
totalWeight += Mathf.Max(0f, pair.Value);
|
||||
}
|
||||
|
||||
if (totalWeight <= 0f)
|
||||
{
|
||||
return RarityType.None;
|
||||
}
|
||||
|
||||
float randomWeight = Random.value * totalWeight;
|
||||
float cumulativeWeight = 0f;
|
||||
foreach (var pair in _rarityWeightBuffer)
|
||||
{
|
||||
cumulativeWeight += Mathf.Max(0f, pair.Value);
|
||||
if (randomWeight <= cumulativeWeight)
|
||||
{
|
||||
return pair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in _rarityWeightBuffer)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 807de988c6a1431ab7b4b6d4d5a7d92f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+196
-4
@@ -3,21 +3,43 @@ 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<DRShopPrice> _shopPriceRows = new();
|
||||
private IDataTable<DRShopPrice> _shopPriceTable;
|
||||
private IDataTable<DROutGameDropPool> _dropPoolTable;
|
||||
private IDataTable<DRMuzzleComp> _muzzleCompTable;
|
||||
private IDataTable<DRBearingComp> _bearingCompTable;
|
||||
private IDataTable<DRBaseComp> _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<GoodsItemRawData> BuildShopGoods(int goodsCount, int runSeed = 0, int sequenceIndex = -1)
|
||||
{
|
||||
@@ -27,8 +49,31 @@ namespace GeometryTD.CustomComponent
|
||||
|
||||
public EnemyDropResult ResolveEnemyDrop(in EnemyDropContext context)
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
"InventoryGenerationComponent.ResolveEnemyDrop() will be implemented in G2.");
|
||||
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> BuildRewardCandidates(
|
||||
@@ -36,8 +81,12 @@ namespace GeometryTD.CustomComponent
|
||||
LevelThemeType themeType,
|
||||
int candidateCount)
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
"InventoryGenerationComponent.BuildRewardCandidates() will be implemented in G2.");
|
||||
RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder();
|
||||
return rewardCandidateBuilder.BuildCandidates(
|
||||
displayPhaseIndex,
|
||||
themeType,
|
||||
candidateCount,
|
||||
BuildRewardCandidateItem);
|
||||
}
|
||||
|
||||
private void EnsureShopTables()
|
||||
@@ -94,5 +143,148 @@ namespace GeometryTD.CustomComponent
|
||||
{
|
||||
return _nextTempInstanceId++;
|
||||
}
|
||||
|
||||
private void EnsureDropTables()
|
||||
{
|
||||
_dropPoolTable ??= GameEntry.DataTable.GetDataTable<DROutGameDropPool>();
|
||||
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
||||
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
||||
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.CustomComponent
|
||||
{
|
||||
public sealed class RewardCandidateBuilder
|
||||
{
|
||||
private readonly DropPoolRoller _dropPoolRoller;
|
||||
|
||||
public RewardCandidateBuilder(DropPoolRoller dropPoolRoller)
|
||||
{
|
||||
_dropPoolRoller = dropPoolRoller;
|
||||
}
|
||||
|
||||
public IReadOnlyList<TowerCompItemData> BuildCandidates(
|
||||
int displayPhaseIndex,
|
||||
LevelThemeType themeType,
|
||||
int candidateCount,
|
||||
Func<DROutGameDropPool, TowerCompItemData> buildRewardItem)
|
||||
{
|
||||
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 (!_dropPoolRoller.TryRollRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!selectedPoolRowIds.Add(selectedRow.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TowerCompItemData candidate = buildRewardItem(selectedRow);
|
||||
if (candidate == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.Add(candidate);
|
||||
}
|
||||
|
||||
attempts = 0;
|
||||
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
||||
{
|
||||
attempts++;
|
||||
if (!_dropPoolRoller.TryRollRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
TowerCompItemData candidate = buildRewardItem(selectedRow);
|
||||
if (candidate == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.Add(candidate);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fe3f5eaa2194d13b4f6f2ff4b2f7de2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user