geometry-tower-defense/Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/InventoryGenerationComponen...

400 lines
14 KiB
C#

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 = System.Random;
namespace GeometryTD.CustomComponent
{
public sealed class InventoryGenerationComponent : GameFrameworkComponent
{
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;
private OutGameDropItemBuilder _outGameDropItemBuilder;
public List<GoodsItemRawData> BuildShopGoods(int goodsCount, int runSeed = 0, int sequenceIndex = -1)
{
EnsureShopBuilder();
return _shopGoodsBuilder.BuildGoods(goodsCount, runSeed, sequenceIndex);
}
public EnemyDropResult ResolveEnemyDrop(
in EnemyDropContext context,
int runSeed,
int sequenceIndex,
ref int nextDropOrdinal)
{
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);
int dropOrdinal = nextDropOrdinal;
nextDropOrdinal++;
InventoryGenerationRandomContext randomContext =
new(runSeed, sequenceIndex, InventoryTagSourceType.Drop, dropOrdinal);
Random random = randomContext.CreateRandom();
if (enemy.DropGold > 0 && dropRate > 0f && random.NextDouble() <= dropRate)
{
gold = Mathf.Max(0, enemy.DropGold);
}
TowerCompItemData lootItem = null;
if (OutGameDropRuleService.ShouldRollOutGameItem(context.DisplayPhaseIndex, random) &&
TryRollOutGameItem(
context.DisplayPhaseIndex,
context.ThemeType,
randomContext,
random,
out TowerCompItemData droppedItem))
{
lootItem = droppedItem;
}
return new EnemyDropResult(coin, gold, lootItem);
}
public IReadOnlyList<TowerCompItemData> BuildRewardCandidates(
int displayPhaseIndex,
LevelThemeType themeType,
int candidateCount,
int runSeed,
int sequenceIndex,
ref int nextRewardOrdinal)
{
RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder();
int rewardOrdinal = nextRewardOrdinal;
IReadOnlyList<TowerCompItemData> candidates = rewardCandidateBuilder.BuildCandidates(
displayPhaseIndex,
themeType,
candidateCount,
CreateNextRewardRandomContext,
BuildRewardCandidateItem);
nextRewardOrdinal = rewardOrdinal;
return candidates;
InventoryGenerationRandomContext CreateNextRewardRandomContext()
{
return new InventoryGenerationRandomContext(
runSeed,
sequenceIndex,
InventoryTagSourceType.Reward,
rewardOrdinal++);
}
}
public IReadOnlyList<TowerCompItemData> BuildEventRewardComponents(
int count,
RarityType rarity,
int runSeed,
int sequenceIndex,
int eventId,
int optionIndex,
int effectIndex)
{
return BuildEventRewardComponents(
count,
rarity,
rarity,
runSeed,
sequenceIndex,
eventId,
optionIndex,
effectIndex);
}
public IReadOnlyList<TowerCompItemData> BuildEventRewardComponents(
int count,
RarityType minRarity,
RarityType maxRarity,
int runSeed,
int sequenceIndex,
int eventId,
int optionIndex,
int effectIndex)
{
EnsureComponentTables();
int resolvedCount = Mathf.Max(0, count);
if (resolvedCount <= 0)
{
return System.Array.Empty<TowerCompItemData>();
}
RarityType normalizedMinRarity = InventoryRarityRuleService.NormalizeComponentRarity(minRarity);
RarityType normalizedMaxRarity = InventoryRarityRuleService.NormalizeComponentRarity(maxRarity);
if (normalizedMinRarity > normalizedMaxRarity)
{
throw new System.InvalidOperationException(
$"Event reward rarity range is invalid: {normalizedMinRarity} > {normalizedMaxRarity}.");
}
List<TowerCompItemData> result = new List<TowerCompItemData>(resolvedCount);
for (int i = 0; i < resolvedCount; i++)
{
InventoryGenerationRandomContext randomContext = CreateEventRandomContext(
runSeed,
sequenceIndex,
eventId,
optionIndex,
effectIndex,
i);
Random random = randomContext.CreateRandom();
result.Add(BuildRandomEventComponentItem(normalizedMinRarity, normalizedMaxRarity, randomContext, random));
}
return result;
}
private void EnsureShopTables()
{
_shopPriceTable ??= GameEntry.DataTable.GetDataTable<DRShopPrice>();
EnsureComponentTables();
if (_shopPriceTable == null)
{
throw new System.InvalidOperationException(
"InventoryGenerationComponent requires ShopPrice data table.");
}
if (_shopPriceRows.Count > 0)
{
return;
}
DRShopPrice[] rows = _shopPriceTable.GetAllDataRows();
if (rows == null || rows.Length <= 0)
{
throw new System.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 System.InvalidOperationException("InventoryGenerationComponent requires non-null shop price rows.");
}
}
private void EnsureShopBuilder()
{
EnsureShopTables();
_shopGoodsBuilder ??= new ShopGoodsBuilder(
_shopPriceRows,
_muzzleCompTable,
_bearingCompTable,
_baseCompTable);
}
private void EnsureDropTables()
{
_dropPoolTable ??= GameEntry.DataTable.GetDataTable<DROutGameDropPool>();
EnsureComponentTables();
if (_dropPoolTable == null)
{
throw new System.InvalidOperationException(
"InventoryGenerationComponent requires OutGameDropPool data table.");
}
}
private DropPoolRoller EnsureDropPoolRoller()
{
EnsureDropTables();
_dropPoolRoller ??= new DropPoolRoller(_dropPoolTable);
return _dropPoolRoller;
}
private RewardCandidateBuilder EnsureRewardCandidateBuilder()
{
_rewardCandidateBuilder ??= new RewardCandidateBuilder(EnsureDropPoolRoller());
return _rewardCandidateBuilder;
}
private OutGameDropItemBuilder EnsureOutGameDropItemBuilder()
{
EnsureDropTables();
_outGameDropItemBuilder ??= new OutGameDropItemBuilder(
_muzzleCompTable,
_bearingCompTable,
_baseCompTable);
return _outGameDropItemBuilder;
}
private bool TryRollOutGameItem(
int displayPhaseIndex,
LevelThemeType themeType,
InventoryGenerationRandomContext randomContext,
Random random,
out TowerCompItemData droppedItem)
{
droppedItem = null;
DropPoolRoller dropPoolRoller = EnsureDropPoolRoller();
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
if (!dropPoolRoller.TryRollRow(
phaseIndex,
themeType,
random,
out DROutGameDropPool selectedRow,
out RarityType selectedRarity) || selectedRow == null)
{
return false;
}
return EnsureOutGameDropItemBuilder().TryBuildItem(selectedRow, selectedRarity, randomContext, out droppedItem);
}
private TowerCompItemData BuildRewardCandidateItem(
DROutGameDropPool row,
RarityType rarity,
InventoryGenerationRandomContext randomContext)
{
if (!EnsureOutGameDropItemBuilder().TryBuildItem(row, rarity, randomContext, out TowerCompItemData droppedItem))
{
return null;
}
return droppedItem;
}
private void EnsureComponentTables()
{
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
if (_muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null)
{
throw new System.InvalidOperationException(
"InventoryGenerationComponent requires MuzzleComp, BearingComp, and BaseComp data tables.");
}
}
private TowerCompItemData BuildRandomEventComponentItem(
RarityType minRarity,
RarityType maxRarity,
InventoryGenerationRandomContext randomContext,
Random random)
{
RarityType rarity = ResolveEventRewardRarity(minRarity, maxRarity, random);
int slotRoll = random.Next(0, 3);
return slotRoll switch
{
0 => BuildRandomEventMuzzleItem(rarity, randomContext, random),
1 => BuildRandomEventBearingItem(rarity, randomContext, random),
_ => BuildRandomEventBaseItem(rarity, randomContext, random)
};
}
private static RarityType ResolveEventRewardRarity(
RarityType minRarity,
RarityType maxRarity,
Random random)
{
if (minRarity >= maxRarity)
{
return minRarity;
}
int rarityValue = random.Next((int)minRarity, (int)maxRarity + 1);
return (RarityType)rarityValue;
}
private MuzzleCompItemData BuildRandomEventMuzzleItem(
RarityType rarity,
InventoryGenerationRandomContext randomContext,
Random random)
{
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
DRMuzzleComp config = rows[random.Next(0, rows.Length)];
return ComponentItemFactory.CreateMuzzle(
config,
randomContext.CreateStableItemInstanceId(),
rarity,
randomContext.CreateTagRandomContext(config.Id));
}
private BearingCompItemData BuildRandomEventBearingItem(
RarityType rarity,
InventoryGenerationRandomContext randomContext,
Random random)
{
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
DRBearingComp config = rows[random.Next(0, rows.Length)];
return ComponentItemFactory.CreateBearing(
config,
randomContext.CreateStableItemInstanceId(),
rarity,
randomContext.CreateTagRandomContext(config.Id));
}
private BaseCompItemData BuildRandomEventBaseItem(
RarityType rarity,
InventoryGenerationRandomContext randomContext,
Random random)
{
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
DRBaseComp config = rows[random.Next(0, rows.Length)];
return ComponentItemFactory.CreateBase(
config,
randomContext.CreateStableItemInstanceId(),
rarity,
randomContext.CreateTagRandomContext(config.Id));
}
private static InventoryGenerationRandomContext CreateEventRandomContext(
int runSeed,
int sequenceIndex,
int eventId,
int optionIndex,
int effectIndex,
int itemIndex)
{
return new InventoryGenerationRandomContext(
runSeed,
sequenceIndex,
InventoryTagSourceType.Event,
BuildEventLocalOrdinal(eventId, optionIndex, effectIndex, itemIndex));
}
private static int BuildEventLocalOrdinal(int eventId, int optionIndex, int effectIndex, int itemIndex)
{
unchecked
{
int value = 17;
value = value * 31 + eventId;
value = value * 31 + optionIndex;
value = value * 31 + effectIndex;
value = value * 31 + itemIndex;
return value & int.MaxValue;
}
}
}
}