400 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|