This commit is contained in:
2026-04-30 19:33:16 +08:00
commit fb2252f688
556 changed files with 69721 additions and 0 deletions
@@ -0,0 +1,252 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using UnityEngine;
using Random = System.Random;
namespace GeometryTD.CustomComponent
{
public sealed class DropPoolRoller
{
private const float RarityCurveScalePhase = 30f;
private static readonly RarityType[] OrderedRarities =
{
RarityType.White,
RarityType.Green,
RarityType.Blue,
RarityType.Purple,
RarityType.Red
};
private readonly List<DROutGameDropPool> _eligibleRowBuffer = new();
private readonly float[] _rarityWeightBuffer = new float[OrderedRarities.Length];
private readonly IDataTable<DROutGameDropPool> _dropPoolTable;
public DropPoolRoller(IDataTable<DROutGameDropPool> dropPoolTable)
{
_dropPoolTable = dropPoolTable;
}
public bool TryRollRow(
int displayPhaseIndex,
LevelThemeType themeType,
Random random,
out DROutGameDropPool selectedRow,
out RarityType selectedRarity)
{
selectedRow = null;
selectedRarity = RarityType.None;
DROutGameDropPool[] allRows = _dropPoolTable.GetAllDataRows();
if (allRows == null || allRows.Length <= 0)
{
return false;
}
CollectEligibleRows(allRows, displayPhaseIndex, themeType);
if (_eligibleRowBuffer.Count <= 0)
{
return false;
}
selectedRarity = RollRarity(displayPhaseIndex, random);
if (selectedRarity == RarityType.None)
{
return false;
}
int totalWeight = 0;
DROutGameDropPool fallbackRow = null;
foreach (var row in _eligibleRowBuffer)
{
if (!IsEligibleAtPhase(row, selectedRarity, displayPhaseIndex))
{
continue;
}
int rowWeight = row.GetWeight(selectedRarity);
if (rowWeight <= 0)
{
continue;
}
fallbackRow = row;
totalWeight += rowWeight;
}
if (totalWeight <= 0)
{
return false;
}
int randomWeight = random.Next(1, totalWeight + 1);
int cumulativeWeight = 0;
foreach (var row in _eligibleRowBuffer)
{
if (!IsEligibleAtPhase(row, selectedRarity, displayPhaseIndex))
{
continue;
}
int rowWeight = row.GetWeight(selectedRarity);
if (rowWeight <= 0)
{
continue;
}
cumulativeWeight += rowWeight;
if (randomWeight <= cumulativeWeight)
{
selectedRow = row;
return true;
}
}
selectedRow = fallbackRow;
return selectedRow != null;
}
private void CollectEligibleRows(
DROutGameDropPool[] allRows,
int displayPhaseIndex,
LevelThemeType themeType)
{
_eligibleRowBuffer.Clear();
foreach (var row in allRows)
{
if (row == null)
{
continue;
}
if (row.LevelThemeType != themeType)
{
continue;
}
if (!IsEligibleAtPhase(row, displayPhaseIndex))
{
continue;
}
_eligibleRowBuffer.Add(row);
}
}
private RarityType RollRarity(int displayPhaseIndex, Random random)
{
for (int i = 0; i < _rarityWeightBuffer.Length; i++)
{
_rarityWeightBuffer[i] = 0f;
}
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
foreach (var row in _eligibleRowBuffer)
{
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
{
RarityType rarity = OrderedRarities[rarityIndex];
if (!IsEligibleAtPhase(row, rarity, displayPhaseIndex))
{
continue;
}
int rowWeight = row.GetWeight(rarity);
if (rowWeight <= 0)
{
continue;
}
float curveWeight = GetRarityCurveWeight(rarity, phaseT);
if (curveWeight <= 0f)
{
continue;
}
_rarityWeightBuffer[rarityIndex] += rowWeight * curveWeight;
}
}
float totalWeight = 0f;
foreach (var weight in _rarityWeightBuffer)
{
totalWeight += Mathf.Max(0f, weight);
}
if (totalWeight <= 0f)
{
return RarityType.None;
}
float randomWeight = (float)(random.NextDouble() * totalWeight);
float cumulativeWeight = 0f;
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
cumulativeWeight += Mathf.Max(0f, _rarityWeightBuffer[rarityIndex]);
if (randomWeight <= cumulativeWeight)
{
return OrderedRarities[rarityIndex];
}
}
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
if (_rarityWeightBuffer[rarityIndex] > 0f)
{
return OrderedRarities[rarityIndex];
}
}
return RarityType.None;
}
private static bool IsEligibleAtPhase(DROutGameDropPool row, int displayPhaseIndex)
{
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
{
RarityType rarity = OrderedRarities[rarityIndex];
if (IsEligibleAtPhase(row, rarity, displayPhaseIndex))
{
return true;
}
}
return false;
}
private static bool IsEligibleAtPhase(DROutGameDropPool row, RarityType rarity, int displayPhaseIndex)
{
if (row.GetWeight(rarity) <= 0)
{
return false;
}
int minPhase = row.GetMinPhase(rarity);
int maxPhase = row.GetMaxPhase(rarity);
return displayPhaseIndex >= minPhase && displayPhaseIndex <= maxPhase;
}
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,399 @@
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;
}
}
}
}
@@ -0,0 +1,65 @@
using System;
using Random = System.Random;
namespace GeometryTD.Definition
{
public readonly struct InventoryGenerationRandomContext
{
public InventoryGenerationRandomContext(
int runSeed,
int nodeSequenceIndex,
InventoryTagSourceType sourceType,
int localOrdinal)
{
RunSeed = runSeed;
NodeSequenceIndex = nodeSequenceIndex;
SourceType = sourceType;
LocalOrdinal = localOrdinal;
}
public int RunSeed { get; }
public int NodeSequenceIndex { get; }
public InventoryTagSourceType SourceType { get; }
public int LocalOrdinal { get; }
public Random CreateRandom()
{
return new Random(BuildSeed());
}
public long CreateStableItemInstanceId()
{
long normalizedSource = ((long)Math.Max(0, (int)SourceType) + 1L) << 48;
long normalizedSequence = ((long)Math.Max(0, NodeSequenceIndex) + 1L) << 24;
long normalizedOrdinal = (uint)(Math.Max(0, LocalOrdinal) + 1);
return normalizedSource | normalizedSequence | normalizedOrdinal;
}
public InventoryTagRandomContext CreateTagRandomContext(int configId)
{
return SourceType switch
{
InventoryTagSourceType.Shop => InventoryTagRandomContext.CreateShop(RunSeed, NodeSequenceIndex, LocalOrdinal, configId),
InventoryTagSourceType.Reward => InventoryTagRandomContext.CreateReward(RunSeed, NodeSequenceIndex, LocalOrdinal, configId),
InventoryTagSourceType.Event => InventoryTagRandomContext.CreateEvent(RunSeed, NodeSequenceIndex, LocalOrdinal, configId),
_ => InventoryTagRandomContext.CreateDrop(RunSeed, NodeSequenceIndex, LocalOrdinal, configId)
};
}
private int BuildSeed()
{
unchecked
{
int seed = 17;
seed = seed * 31 + RunSeed;
seed = seed * 31 + NodeSequenceIndex;
seed = seed * 31 + (int)SourceType;
seed = seed * 31 + LocalOrdinal;
return seed;
}
}
}
}
@@ -0,0 +1,19 @@
using UnityEngine;
using Random = System.Random;
namespace GeometryTD.CustomComponent
{
public static class OutGameDropRuleService
{
private const float DropChanceBase = 0.05f;
private const float DropChancePerPhase = 0.2f;
private const float DropChanceCap = 0.2f;
public static bool ShouldRollOutGameItem(int displayPhaseIndex, Random random)
{
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
float dropChance = Mathf.Clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0f, DropChanceCap);
return random.NextDouble() <= dropChance;
}
}
}
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using UnityEngine;
using Random = System.Random;
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<InventoryGenerationRandomContext> createRandomContext,
Func<DROutGameDropPool, RarityType, InventoryGenerationRandomContext, TowerCompItemData> buildRewardItem)
{
int resolvedCount = Mathf.Max(0, candidateCount);
if (resolvedCount <= 0)
{
return Array.Empty<TowerCompItemData>();
}
List<TowerCompItemData> candidates = new List<TowerCompItemData>(resolvedCount);
HashSet<long> selectedPoolEntryKeys = new HashSet<long>();
int maxAttempts = Mathf.Max(resolvedCount * 6, resolvedCount);
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
int attempts = 0;
while (candidates.Count < resolvedCount && attempts < maxAttempts)
{
attempts++;
InventoryGenerationRandomContext randomContext = createRandomContext();
Random random = randomContext.CreateRandom();
if (!_dropPoolRoller.TryRollRow(
phaseIndex,
themeType,
random,
out DROutGameDropPool selectedRow,
out RarityType selectedRarity) || selectedRow == null)
{
break;
}
if (!selectedPoolEntryKeys.Add(BuildSelectionKey(selectedRow.Id, selectedRarity)))
{
continue;
}
TowerCompItemData candidate = buildRewardItem(selectedRow, selectedRarity, randomContext);
if (candidate == null)
{
continue;
}
candidates.Add(candidate);
}
attempts = 0;
while (candidates.Count < resolvedCount && attempts < maxAttempts)
{
attempts++;
InventoryGenerationRandomContext randomContext = createRandomContext();
Random random = randomContext.CreateRandom();
if (!_dropPoolRoller.TryRollRow(
phaseIndex,
themeType,
random,
out DROutGameDropPool selectedRow,
out RarityType selectedRarity) || selectedRow == null)
{
break;
}
TowerCompItemData candidate = buildRewardItem(selectedRow, selectedRarity, randomContext);
if (candidate == null)
{
continue;
}
candidates.Add(candidate);
}
return candidates;
}
private static long BuildSelectionKey(int rowId, RarityType rarity)
{
return ((long)rowId << 32) | (uint)rarity;
}
}
}
@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.CustomUtility;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using GeometryTD.Factory;
using GeometryTD.UI;
using UnityEngine;
using Random = System.Random;
namespace GeometryTD.CustomComponent
{
public sealed class ShopGoodsBuilder
{
private readonly IReadOnlyList<DRShopPrice> _shopPriceRows;
private readonly IDataTable<DRMuzzleComp> _muzzleCompTable;
private readonly IDataTable<DRBearingComp> _bearingCompTable;
private readonly IDataTable<DRBaseComp> _baseCompTable;
public ShopGoodsBuilder(
IReadOnlyList<DRShopPrice> shopPriceRows,
IDataTable<DRMuzzleComp> muzzleCompTable,
IDataTable<DRBearingComp> bearingCompTable,
IDataTable<DRBaseComp> baseCompTable)
{
_shopPriceRows = shopPriceRows;
_muzzleCompTable = muzzleCompTable;
_bearingCompTable = bearingCompTable;
_baseCompTable = baseCompTable;
}
public List<GoodsItemRawData> BuildGoods(
int goodsCount,
int runSeed,
int sequenceIndex)
{
if (goodsCount <= 0)
{
return new List<GoodsItemRawData>();
}
List<GoodsItemRawData> goodsItems = new(goodsCount);
for (int i = 0; i < goodsCount; i++)
{
InventoryGenerationRandomContext randomContext =
new(runSeed, sequenceIndex, InventoryTagSourceType.Shop, i);
goodsItems.Add(BuildGoodsItem(i, randomContext));
}
return goodsItems;
}
private GoodsItemRawData BuildGoodsItem(
int goodsIndex,
InventoryGenerationRandomContext randomContext)
{
Random random = randomContext.CreateRandom();
TowerCompItemData sourceItem = BuildRandomComponentItem(randomContext, random);
return new GoodsItemRawData
{
GoodsIndex = goodsIndex,
Title = sourceItem.Name,
TypeText = BuildTypeText(sourceItem.SlotType),
Description = BuildDescription(sourceItem),
Price = ResolveRandomPrice(sourceItem.Rarity, random),
Tags = sourceItem.Tags != null ? (TagType[])sourceItem.Tags.Clone() : Array.Empty<TagType>(),
IconAreaContext = BuildIconAreaContext(sourceItem),
SourceItem = sourceItem,
IsPurchased = false
};
}
private TowerCompItemData BuildRandomComponentItem(
InventoryGenerationRandomContext randomContext,
Random random)
{
int slotRoll = random.Next(0, 3);
DRShopPrice priceRow = _shopPriceRows[random.Next(0, _shopPriceRows.Count)];
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(
priceRow != null ? priceRow.Rarity : RarityType.White);
return slotRoll switch
{
0 => BuildRandomMuzzleItem(rarity, randomContext, random),
1 => BuildRandomBearingItem(rarity, randomContext, random),
_ => BuildRandomBaseItem(rarity, randomContext, random)
};
}
private MuzzleCompItemData BuildRandomMuzzleItem(
RarityType rarity,
InventoryGenerationRandomContext randomContext,
Random random)
{
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
DRMuzzleComp config = rows[random.Next(0, rows.Length)];
long instanceId = randomContext.CreateStableItemInstanceId();
return ComponentItemFactory.CreateMuzzle(config, instanceId, rarity, randomContext.CreateTagRandomContext(config.Id));
}
private BearingCompItemData BuildRandomBearingItem(
RarityType rarity,
InventoryGenerationRandomContext randomContext,
Random random)
{
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
DRBearingComp config = rows[random.Next(0, rows.Length)];
long instanceId = randomContext.CreateStableItemInstanceId();
return ComponentItemFactory.CreateBearing(config, instanceId, rarity, randomContext.CreateTagRandomContext(config.Id));
}
private BaseCompItemData BuildRandomBaseItem(
RarityType rarity,
InventoryGenerationRandomContext randomContext,
Random random)
{
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
DRBaseComp config = rows[random.Next(0, rows.Length)];
long instanceId = randomContext.CreateStableItemInstanceId();
return ComponentItemFactory.CreateBase(config, instanceId, rarity, randomContext.CreateTagRandomContext(config.Id));
}
private int ResolveRandomPrice(RarityType rarity, Random random)
{
return ShopPriceRuleService.ResolveRandomBuyPrice(_shopPriceRows, rarity, random);
}
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
{
return new IconAreaContext
{
Rarity = item.Rarity,
ComponentSlotType = item.SlotType,
Color = IconColorGenerator.GenerateForComponent(item)
};
}
private static string BuildTypeText(TowerCompSlotType slotType)
{
return slotType switch
{
TowerCompSlotType.Muzzle => "枪口组件",
TowerCompSlotType.Bearing => "轴承组件",
TowerCompSlotType.Base => "底座组件",
_ => "组件"
};
}
private static string BuildDescription(TowerCompItemData item)
{
if (item is MuzzleCompItemData muzzleComp)
{
return ItemDescUtility.BuildMuzzleDesc(muzzleComp);
}
if (item is BearingCompItemData bearingComp)
{
return ItemDescUtility.BuildBearingDesc(bearingComp);
}
if (item is BaseCompItemData baseComp)
{
return ItemDescUtility.BuildBaseDesc(baseComp);
}
return string.Empty;
}
}
}