修复已知问题

This commit is contained in:
2026-03-13 14:55:17 +08:00
parent aa44170d56
commit 777c58b812
11 changed files with 346 additions and 97 deletions
@@ -100,7 +100,8 @@ namespace GeometryTD.CustomComponent
_runtime.NodeId = nodeId;
_runtime.NodeType = nodeType;
_runtime.SequenceIndex = sequenceIndex;
GameEntry.InventoryGeneration.ConfigureRunContext(runSeed, sequenceIndex);
_runtime.NextDropOrdinal = 0;
_runtime.NextRewardOrdinal = 0;
_runtime.CombatRunResourceStore.InitializeForCombat(level);
for (int i = 0; i < phases.Count; i++)
{
@@ -201,7 +202,13 @@ namespace GeometryTD.CustomComponent
enemy,
_runtime.PhaseLoopRuntime.DisplayPhaseIndex,
_coordinator.ResolveCurrentThemeType());
EnemyDropResult result = GameEntry.InventoryGeneration.ResolveEnemyDrop(context);
int nextDropOrdinal = _runtime.NextDropOrdinal;
EnemyDropResult result = GameEntry.InventoryGeneration.ResolveEnemyDrop(
context,
_runtime.RunSeed,
_runtime.SequenceIndex,
ref nextDropOrdinal);
_runtime.NextDropOrdinal = nextDropOrdinal;
_runtime.CombatRunResourceStore.AddEnemyDefeatedReward(result.Coin, result.Gold);
_runtime.CombatRunResourceStore.AddEnemyDefeatedLoot(result.LootItem);
}
@@ -60,6 +60,8 @@ namespace GeometryTD.CustomComponent
_runtime.NodeId = 0;
_runtime.NodeType = RunNodeType.None;
_runtime.SequenceIndex = -1;
_runtime.NextDropOrdinal = 0;
_runtime.NextRewardOrdinal = 0;
}
public void CleanupAllCombatEntities()
@@ -34,5 +34,7 @@ namespace GeometryTD.CustomComponent
public int NodeId { get; set; }
public RunNodeType NodeType { get; set; }
public int SequenceIndex { get; set; }
public int NextDropOrdinal { get; set; }
public int NextRewardOrdinal { get; set; }
}
}
@@ -36,6 +36,9 @@ namespace GeometryTD.CustomComponent
CombatSettlementContext settlementContext,
int displayPhaseIndex,
LevelThemeType themeType,
int runSeed,
int sequenceIndex,
ref int nextRewardOrdinal,
RewardSelectFormUseCase rewardSelectFormUseCase,
Action<RewardSelectItemRawData> onRewardSelected,
Action onGiveUp)
@@ -48,7 +51,10 @@ namespace GeometryTD.CustomComponent
IReadOnlyList<TowerCompItemData> candidateItems = GameEntry.InventoryGeneration.BuildRewardCandidates(
displayPhaseIndex,
themeType,
RewardSelectDisplayCount);
RewardSelectDisplayCount,
runSeed,
sequenceIndex,
ref nextRewardOrdinal);
if (candidateItems == null || candidateItems.Count <= 0)
{
settlementContext.Flags.ShouldOpenRewardSelection = false;
@@ -60,7 +66,7 @@ namespace GeometryTD.CustomComponent
candidateItems,
displayCount: RewardSelectDisplayCount,
refreshCost: 0,
allowRefreshOnce: false,
allowRotateOnce: false,
allowGiveUp: false,
tipText: "基地满血奖励:请选择 1 个组件");
@@ -16,16 +16,23 @@ namespace GeometryTD.CustomComponent
}
Coordinator.EnsureRewardSelectFormUseCaseBound();
int nextRewardOrdinal = Runtime.NextRewardOrdinal;
if (!Runtime.CombatSettlementService.TryPrepareRewardSelection(
Runtime.SettlementContext,
Runtime.PhaseLoopRuntime.DisplayPhaseIndex,
Coordinator.ResolveCurrentThemeType(),
Runtime.RunSeed,
Runtime.SequenceIndex,
ref nextRewardOrdinal,
Runtime.RewardSelectFormUseCase,
Coordinator.OnFullBaseHpRewardSelected,
Coordinator.OnFullBaseHpRewardGiveUp))
{
Coordinator.ChangeState(new CombatFinishFormState(Runtime, Coordinator));
return;
}
Runtime.NextRewardOrdinal = nextRewardOrdinal;
}
public override void OnExit()
@@ -10,9 +10,17 @@ 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 Dictionary<RarityType, float> _rarityWeightBuffer = new();
private readonly float[] _rarityWeightBuffer = new float[OrderedRarities.Length];
private readonly IDataTable<DROutGameDropPool> _dropPoolTable;
public DropPoolRoller(IDataTable<DROutGameDropPool> dropPoolTable)
@@ -50,9 +58,8 @@ namespace GeometryTD.CustomComponent
int totalWeight = 0;
DROutGameDropPool fallbackRow = null;
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
foreach (var row in _eligibleRowBuffer)
{
DROutGameDropPool row = _eligibleRowBuffer[i];
if (!IsEligibleAtPhase(row, selectedRarity, displayPhaseIndex))
{
continue;
@@ -107,9 +114,8 @@ namespace GeometryTD.CustomComponent
{
_eligibleRowBuffer.Clear();
for (int i = 0; i < allRows.Length; i++)
foreach (var row in allRows)
{
DROutGameDropPool row = allRows[i];
if (row == null)
{
continue;
@@ -131,15 +137,18 @@ namespace GeometryTD.CustomComponent
private RarityType RollRarity(int displayPhaseIndex, Random random)
{
_rarityWeightBuffer.Clear();
for (int i = 0; i < _rarityWeightBuffer.Length; i++)
{
_rarityWeightBuffer[i] = 0f;
}
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
for (int i = 0; i < _eligibleRowBuffer.Count; i++)
foreach (var row in _eligibleRowBuffer)
{
DROutGameDropPool row = _eligibleRowBuffer[i];
for (int rarityIndex = (int)RarityType.White; rarityIndex <= (int)RarityType.Red; rarityIndex++)
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
{
RarityType rarity = (RarityType)rarityIndex;
RarityType rarity = OrderedRarities[rarityIndex];
if (!IsEligibleAtPhase(row, rarity, displayPhaseIndex))
{
continue;
@@ -157,22 +166,14 @@ namespace GeometryTD.CustomComponent
continue;
}
float rarityWeight = rowWeight * curveWeight;
if (_rarityWeightBuffer.TryGetValue(rarity, out float existingWeight))
{
_rarityWeightBuffer[rarity] = existingWeight + rarityWeight;
}
else
{
_rarityWeightBuffer[rarity] = rarityWeight;
}
_rarityWeightBuffer[rarityIndex] += rowWeight * curveWeight;
}
}
float totalWeight = 0f;
foreach (var pair in _rarityWeightBuffer)
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
totalWeight += Mathf.Max(0f, pair.Value);
totalWeight += Mathf.Max(0f, _rarityWeightBuffer[rarityIndex]);
}
if (totalWeight <= 0f)
@@ -182,18 +183,21 @@ namespace GeometryTD.CustomComponent
float randomWeight = (float)(random.NextDouble() * totalWeight);
float cumulativeWeight = 0f;
foreach (var pair in _rarityWeightBuffer)
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
cumulativeWeight += Mathf.Max(0f, pair.Value);
cumulativeWeight += Mathf.Max(0f, _rarityWeightBuffer[rarityIndex]);
if (randomWeight <= cumulativeWeight)
{
return pair.Key;
return OrderedRarities[rarityIndex];
}
}
foreach (var pair in _rarityWeightBuffer)
for (int rarityIndex = 0; rarityIndex < _rarityWeightBuffer.Length; rarityIndex++)
{
return pair.Key;
if (_rarityWeightBuffer[rarityIndex] > 0f)
{
return OrderedRarities[rarityIndex];
}
}
return RarityType.None;
@@ -201,9 +205,9 @@ namespace GeometryTD.CustomComponent
private static bool IsEligibleAtPhase(DROutGameDropPool row, int displayPhaseIndex)
{
for (int rarityIndex = (int)RarityType.White; rarityIndex <= (int)RarityType.Red; rarityIndex++)
for (int rarityIndex = 0; rarityIndex < OrderedRarities.Length; rarityIndex++)
{
RarityType rarity = (RarityType)rarityIndex;
RarityType rarity = OrderedRarities[rarityIndex];
if (IsEligibleAtPhase(row, rarity, displayPhaseIndex))
{
return true;
@@ -12,11 +12,6 @@ namespace GeometryTD.CustomComponent
{
public sealed class InventoryGenerationComponent : GameFrameworkComponent
{
private int _runSeed;
private int _nodeSequenceIndex = -1;
private int _nextDropOrdinal;
private int _nextRewardOrdinal;
private readonly List<DRShopPrice> _shopPriceRows = new();
private IDataTable<DRShopPrice> _shopPriceTable;
private IDataTable<DROutGameDropPool> _dropPoolTable;
@@ -28,21 +23,17 @@ namespace GeometryTD.CustomComponent
private RewardCandidateBuilder _rewardCandidateBuilder;
private OutGameDropItemBuilder _outGameDropItemBuilder;
public void ConfigureRunContext(int runSeed, int sequenceIndex)
{
_runSeed = runSeed;
_nodeSequenceIndex = sequenceIndex;
_nextDropOrdinal = 0;
_nextRewardOrdinal = 0;
}
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)
public EnemyDropResult ResolveEnemyDrop(
in EnemyDropContext context,
int runSeed,
int sequenceIndex,
ref int nextDropOrdinal)
{
DREnemy enemy = context.Enemy;
if (enemy == null)
@@ -56,9 +47,10 @@ namespace GeometryTD.CustomComponent
? Mathf.Clamp01(enemy.DropPercent * 0.01f)
: Mathf.Clamp01(enemy.DropPercent);
int dropOrdinal = AllocateDropOrdinal();
int dropOrdinal = nextDropOrdinal;
nextDropOrdinal++;
InventoryGenerationRandomContext randomContext =
new(_runSeed, _nodeSequenceIndex, InventoryTagSourceType.Drop, dropOrdinal);
new(runSeed, sequenceIndex, InventoryTagSourceType.Drop, dropOrdinal);
Random random = randomContext.CreateRandom();
if (enemy.DropGold > 0 && dropRate > 0f && random.NextDouble() <= dropRate)
@@ -84,15 +76,30 @@ namespace GeometryTD.CustomComponent
public IReadOnlyList<TowerCompItemData> BuildRewardCandidates(
int displayPhaseIndex,
LevelThemeType themeType,
int candidateCount)
int candidateCount,
int runSeed,
int sequenceIndex,
ref int nextRewardOrdinal)
{
RewardCandidateBuilder rewardCandidateBuilder = EnsureRewardCandidateBuilder();
return rewardCandidateBuilder.BuildCandidates(
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++);
}
}
private void EnsureShopTables()
@@ -205,25 +212,6 @@ namespace GeometryTD.CustomComponent
return EnsureOutGameDropItemBuilder().TryBuildItem(selectedRow, selectedRarity, randomContext, out droppedItem);
}
private int AllocateDropOrdinal()
{
return _nextDropOrdinal++;
}
private int AllocateRewardOrdinal()
{
return _nextRewardOrdinal++;
}
private InventoryGenerationRandomContext CreateNextRewardRandomContext()
{
return new InventoryGenerationRandomContext(
_runSeed,
_nodeSequenceIndex,
InventoryTagSourceType.Reward,
AllocateRewardOrdinal());
}
private TowerCompItemData BuildRewardCandidateItem(
DROutGameDropPool row,
RarityType rarity,
@@ -110,7 +110,7 @@ namespace GeometryTD.UI
return;
}
RewardSelectFormRawData nextRawData = _useCase.TryRefresh();
RewardSelectFormRawData nextRawData = _useCase.TryRotateSelection();
if (nextRawData == null)
{
CloseUI();
@@ -159,4 +159,4 @@ namespace GeometryTD.UI
return false;
}
}
}
}
@@ -15,17 +15,20 @@ namespace GeometryTD.UI
private int _displayCount = 3;
private int _refreshCost;
private bool _allowRefreshOnce = true;
private bool _allowRotateOnce = true;
private bool _allowGiveUp = true;
private bool _hasRefreshed;
private bool _hasRotated;
private int _selectionOffset;
private string _tipText = "Select one reward";
// RewardSelectForm keeps a fixed reward pool for the current node.
// The "refresh" action only rotates which slice of that pool is shown so save/load
// can reopen the same node with the exact same reward contents.
public void ConfigureRewardPool(
IReadOnlyList<RewardSelectItemRawData> rewardPool,
int displayCount = 3,
int refreshCost = 0,
bool allowRefreshOnce = true,
bool allowRotateOnce = true,
bool allowGiveUp = true,
string tipText = null)
{
@@ -45,10 +48,10 @@ namespace GeometryTD.UI
_displayCount = Mathf.Max(1, displayCount);
_refreshCost = Mathf.Max(0, refreshCost);
_allowRefreshOnce = allowRefreshOnce;
_allowRotateOnce = allowRotateOnce;
_allowGiveUp = allowGiveUp;
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
_hasRefreshed = false;
_hasRotated = false;
_selectionOffset = 0;
_currentModel = null;
}
@@ -57,7 +60,7 @@ namespace GeometryTD.UI
IReadOnlyList<TowerCompItemData> rewardCandidates,
int displayCount = 3,
int refreshCost = 0,
bool allowRefreshOnce = true,
bool allowRotateOnce = true,
bool allowGiveUp = true,
string tipText = null)
{
@@ -77,10 +80,10 @@ namespace GeometryTD.UI
_displayCount = Mathf.Max(1, displayCount);
_refreshCost = Mathf.Max(0, refreshCost);
_allowRefreshOnce = allowRefreshOnce;
_allowRotateOnce = allowRotateOnce;
_allowGiveUp = allowGiveUp;
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
_hasRefreshed = false;
_hasRotated = false;
_selectionOffset = 0;
_currentModel = null;
}
@@ -93,30 +96,30 @@ namespace GeometryTD.UI
public RewardSelectFormRawData CreateInitialModel()
{
_hasRefreshed = false;
_hasRotated = false;
_currentModel = BuildModel();
return _currentModel;
}
public RewardSelectFormRawData TryRefresh()
public RewardSelectFormRawData TryRotateSelection()
{
if (_currentModel == null)
{
return null;
}
if (!CanRefreshInternal())
if (!CanRotateSelectionInternal())
{
return _currentModel;
}
if (!TryConsumeRefreshCost())
if (!TryConsumeRotateCost())
{
_currentModel.CanRefresh = false;
return _currentModel;
}
_hasRefreshed = true;
_hasRotated = true;
if (_rewardPool.Count > 0)
{
_selectionOffset = (_selectionOffset + _displayCount) % _rewardPool.Count;
@@ -166,7 +169,7 @@ namespace GeometryTD.UI
TipText = _tipText,
RewardItems = selectedRewards,
RefreshCost = _refreshCost,
CanRefresh = selectedRewards.Length > 0 && CanRefreshInternal(),
CanRefresh = selectedRewards.Length > 0 && CanRotateSelectionInternal(),
CanGiveUp = _allowGiveUp
};
}
@@ -190,17 +193,17 @@ namespace GeometryTD.UI
return results;
}
private bool CanRefreshInternal()
private bool CanRotateSelectionInternal()
{
if (!_allowRefreshOnce || _hasRefreshed)
if (!_allowRotateOnce || _hasRotated)
{
return false;
}
return CanPayRefreshCost();
return CanPayRotateCost();
}
private bool CanPayRefreshCost()
private bool CanPayRotateCost()
{
if (_refreshCost <= 0)
{
@@ -215,7 +218,7 @@ namespace GeometryTD.UI
return GameEntry.PlayerInventory.Gold >= _refreshCost;
}
private bool TryConsumeRefreshCost()
private bool TryConsumeRotateCost()
{
if (_refreshCost <= 0)
{