306 lines
11 KiB
C#
306 lines
11 KiB
C#
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<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)
|
|
{
|
|
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<TowerCompItemData> BuildRewardCandidates(
|
|
int displayPhaseIndex,
|
|
LevelThemeType themeType,
|
|
int candidateCount)
|
|
{
|
|
RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder();
|
|
return rewardCandidateBuilder.BuildCandidates(
|
|
displayPhaseIndex,
|
|
themeType,
|
|
candidateCount,
|
|
BuildRewardCandidateItem);
|
|
}
|
|
|
|
private void EnsureShopTables()
|
|
{
|
|
_shopPriceTable ??= GameEntry.DataTable.GetDataTable<DRShopPrice>();
|
|
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
|
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
|
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
|
|
|
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<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,
|
|
out RarityType selectedRarity) || selectedRow == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return TryBuildDropItem(
|
|
selectedRow,
|
|
selectedRarity,
|
|
InventoryTagSourceType.Drop,
|
|
AllocateDropTagOrdinal(),
|
|
out droppedItem);
|
|
}
|
|
|
|
private bool TryBuildDropItem(
|
|
DROutGameDropPool row,
|
|
RarityType rarity,
|
|
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(),
|
|
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(),
|
|
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(),
|
|
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, RarityType rarity)
|
|
{
|
|
if (!TryBuildDropItem(
|
|
row,
|
|
rarity,
|
|
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)
|
|
};
|
|
}
|
|
}
|
|
}
|