refactor 8:
- CombatScheduler 不再反向访问 GameEntry.CombatNode。现在由 CombatNodeComponent.cs:322 在初始化时把完成回调传进 scheduler,运行时回调保存在 CombatSchedulerRuntimeContext.cs:27,完成时由 CombatSchedulerFlowCoordinator.cs:245 触发;主题解析也只看当前战斗上下文,不再回退查 facade,见 CombatSchedulerFlowCoordinator.cs:161。同时把 MapEntity 的 coin 回调改成直接连到资源真值来源 CombatLoadingState.cs:56,并把 ICombatSchedulerHost 上那两个资源转发接口删掉,见 ICombatSchedulerHost.cs:5。 - 地图加载入口也收紧了。CombatLoadSession 现在要求必须有 MapEntityLoadContext.InitialMapData,不再走隐式兜底,见 CombatLoadSession.cs:227。EntityExtension 删除了 ShowMap(MapData) 旧重载,当前只保留 ShowMap(MapEntityLoadContext),见 EntityExtension.cs:52;MapEntity 也不再接受裸 MapData 的遗留分支,见 MapEntity.cs:263。
This commit is contained in:
parent
703fd6f540
commit
380f901c1a
|
|
@ -319,7 +319,7 @@ namespace GeometryTD.CustomComponent
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_combatScheduler.OnInit();
|
_combatScheduler.OnInit(OnCombatEndedByScheduler);
|
||||||
_runtimeInitialized = true;
|
_runtimeInitialized = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,19 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GameFramework.DataTable;
|
|
||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
using GeometryTD.CustomUtility;
|
using GeometryTD.CustomUtility;
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Random = UnityEngine.Random;
|
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal sealed class CombatInRunResourceManager
|
internal sealed class CombatInRunResourceManager
|
||||||
{
|
{
|
||||||
// 每次击杀敌人后,进入“掉组件判定”的概率:
|
|
||||||
// chance = clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0, DropChanceCap)
|
|
||||||
private const float DropChanceBase = 0.05f;
|
|
||||||
private const float DropChancePerPhase = 0.2f;
|
|
||||||
private const float DropChanceCap = 0.2f;
|
|
||||||
|
|
||||||
// 用于把阶段映射到 [0,1] 的稀有度曲线输入:
|
|
||||||
// phaseT = clamp01((phaseIndex - 1) / RarityCurveScalePhase)
|
|
||||||
// 该值越大,稀有度随阶段提升的节奏越慢。
|
|
||||||
private const float RarityCurveScalePhase = 30f;
|
|
||||||
|
|
||||||
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
|
|
||||||
private readonly List<TowerStatsData> _buildTowerStatsSnapshot = new();
|
private readonly List<TowerStatsData> _buildTowerStatsSnapshot = new();
|
||||||
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
|
private readonly List<TowerItemData> _participantTowerSnapshot = new();
|
||||||
private readonly BackpackInventoryData _rewardInventory = new();
|
private readonly BackpackInventoryData _rewardInventory = new();
|
||||||
|
|
||||||
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
|
private BackpackInventoryData _combatInventorySnapshot = new();
|
||||||
private IDataTable<DRMuzzleComp> _drMuzzleComp;
|
|
||||||
private IDataTable<DRBearingComp> _drBearingComp;
|
|
||||||
private IDataTable<DRBaseComp> _drBaseComp;
|
|
||||||
private long _nextDropItemInstanceId = 1;
|
|
||||||
private bool _isCombatActive;
|
private bool _isCombatActive;
|
||||||
|
|
||||||
public int CurrentCoin { get; private set; }
|
public int CurrentCoin { get; private set; }
|
||||||
|
|
@ -54,7 +35,7 @@ namespace GeometryTD.CustomComponent
|
||||||
MaxBaseHp = Mathf.Max(0, level.BaseHp);
|
MaxBaseHp = Mathf.Max(0, level.BaseHp);
|
||||||
CurrentBaseHp = MaxBaseHp;
|
CurrentBaseHp = MaxBaseHp;
|
||||||
CurrentCoin = Mathf.Max(0, level.StartCoin);
|
CurrentCoin = Mathf.Max(0, level.StartCoin);
|
||||||
CacheBuildTowerStatsSnapshot();
|
CacheCombatSnapshots();
|
||||||
_isCombatActive = true;
|
_isCombatActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,13 +53,14 @@ namespace GeometryTD.CustomComponent
|
||||||
GainedCoin = 0;
|
GainedCoin = 0;
|
||||||
GainedGold = 0;
|
GainedGold = 0;
|
||||||
_buildTowerStatsSnapshot.Clear();
|
_buildTowerStatsSnapshot.Clear();
|
||||||
|
_participantTowerSnapshot.Clear();
|
||||||
|
_combatInventorySnapshot = new BackpackInventoryData();
|
||||||
_rewardInventory.Gold = 0;
|
_rewardInventory.Gold = 0;
|
||||||
_rewardInventory.MuzzleComponents.Clear();
|
_rewardInventory.MuzzleComponents.Clear();
|
||||||
_rewardInventory.BearingComponents.Clear();
|
_rewardInventory.BearingComponents.Clear();
|
||||||
_rewardInventory.BaseComponents.Clear();
|
_rewardInventory.BaseComponents.Clear();
|
||||||
_rewardInventory.Towers.Clear();
|
_rewardInventory.Towers.Clear();
|
||||||
_rewardInventory.ParticipantTowerInstanceIds.Clear();
|
_rewardInventory.ParticipantTowerInstanceIds.Clear();
|
||||||
_nextDropItemInstanceId = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BackpackInventoryData GetRewardInventorySnapshot()
|
public BackpackInventoryData GetRewardInventorySnapshot()
|
||||||
|
|
@ -86,6 +68,26 @@ namespace GeometryTD.CustomComponent
|
||||||
return InventoryCloneUtility.CloneInventory(_rewardInventory);
|
return InventoryCloneUtility.CloneInventory(_rewardInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BackpackInventoryData GetCombatInventorySnapshot()
|
||||||
|
{
|
||||||
|
return InventoryCloneUtility.CloneInventory(_combatInventorySnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<TowerItemData> GetParticipantTowerSnapshot()
|
||||||
|
{
|
||||||
|
List<TowerItemData> snapshot = new List<TowerItemData>(_participantTowerSnapshot.Count);
|
||||||
|
for (int i = 0; i < _participantTowerSnapshot.Count; i++)
|
||||||
|
{
|
||||||
|
TowerItemData tower = _participantTowerSnapshot[i];
|
||||||
|
if (tower != null)
|
||||||
|
{
|
||||||
|
snapshot.Add(InventoryCloneUtility.CloneTower(tower));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryConsumeCoin(int coin)
|
public bool TryConsumeCoin(int coin)
|
||||||
{
|
{
|
||||||
int requiredCoin = Mathf.Max(0, coin);
|
int requiredCoin = Mathf.Max(0, coin);
|
||||||
|
|
@ -188,9 +190,23 @@ namespace GeometryTD.CustomComponent
|
||||||
_rewardInventory.Gold += gold;
|
_rewardInventory.Gold += gold;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CacheBuildTowerStatsSnapshot()
|
public void AddEnemyDefeatedLoot(TowerCompItemData droppedItem)
|
||||||
|
{
|
||||||
|
if (!_isCombatActive || droppedItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendRewardItem(droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CacheCombatSnapshots()
|
||||||
{
|
{
|
||||||
_buildTowerStatsSnapshot.Clear();
|
_buildTowerStatsSnapshot.Clear();
|
||||||
|
_participantTowerSnapshot.Clear();
|
||||||
|
_combatInventorySnapshot = GameEntry.PlayerInventory != null
|
||||||
|
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
||||||
|
: new BackpackInventoryData();
|
||||||
if (GameEntry.PlayerInventory == null)
|
if (GameEntry.PlayerInventory == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -205,12 +221,16 @@ namespace GeometryTD.CustomComponent
|
||||||
for (int i = 0; i < towers.Count; i++)
|
for (int i = 0; i < towers.Count; i++)
|
||||||
{
|
{
|
||||||
TowerItemData tower = towers[i];
|
TowerItemData tower = towers[i];
|
||||||
if (tower?.Stats == null)
|
if (tower == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildTowerStatsSnapshot.Add(InventoryCloneUtility.CloneTowerStats(tower.Stats));
|
_participantTowerSnapshot.Add(InventoryCloneUtility.CloneTower(tower));
|
||||||
|
if (tower.Stats != null)
|
||||||
|
{
|
||||||
|
_buildTowerStatsSnapshot.Add(InventoryCloneUtility.CloneTowerStats(tower.Stats));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,427 +254,20 @@ namespace GeometryTD.CustomComponent
|
||||||
GameEntry.Event.Fire(this, CombatBaseHpChangedEventArgs.Create(CurrentBaseHp, deltaBaseHp));
|
GameEntry.Event.Fire(this, CombatBaseHpChangedEventArgs.Create(CurrentBaseHp, deltaBaseHp));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void AppendRewardItem(TowerCompItemData droppedItem)
|
||||||
/// 击杀敌人时的掉落入口。
|
|
||||||
/// displayPhaseIndex 会影响:
|
|
||||||
/// 1) 总掉落概率(由 DropChanceBase / DropChancePerPhase / DropChanceCap 决定)
|
|
||||||
/// 2) 稀有度倾向(通过 phaseT 进入 RollRarity)
|
|
||||||
/// themeType 会按关卡主题过滤掉落池(LevelThemeType 不匹配直接不参与)。
|
|
||||||
/// </summary>
|
|
||||||
public bool TryRollOutGameItemDrop(int displayPhaseIndex, LevelThemeType themeType)
|
|
||||||
{
|
{
|
||||||
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
switch (droppedItem)
|
||||||
float dropChance = Mathf.Clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0f, DropChanceCap);
|
|
||||||
if (Random.value > dropChance)
|
|
||||||
{
|
{
|
||||||
return false;
|
case MuzzleCompItemData muzzleComp:
|
||||||
}
|
_rewardInventory.MuzzleComponents.Add(InventoryCloneUtility.CloneMuzzleComp(muzzleComp));
|
||||||
|
|
||||||
if (!TryRollOutGameItem(phaseIndex, themeType, out TowerCompItemData droppedItem))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (droppedItem is MuzzleCompItemData muzzleCompItemData)
|
|
||||||
{
|
|
||||||
_rewardInventory.MuzzleComponents.Add(muzzleCompItemData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (droppedItem is BearingCompItemData bearingCompItemData)
|
|
||||||
{
|
|
||||||
_rewardInventory.BearingComponents.Add(bearingCompItemData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (droppedItem is BaseCompItemData baseCompItemData)
|
|
||||||
{
|
|
||||||
_rewardInventory.BaseComponents.Add(baseCompItemData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 直接抽取一个具体掉落物(不走前面的总掉率门槛)。
|
|
||||||
/// 但 displayPhaseIndex 和 themeType 仍会通过掉落池过滤、稀有度加权影响结果。
|
|
||||||
/// </summary>
|
|
||||||
public bool TryRollOutGameItem(int displayPhaseIndex, LevelThemeType themeType, out TowerCompItemData droppedItem)
|
|
||||||
{
|
|
||||||
droppedItem = null;
|
|
||||||
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
|
||||||
if (!TryPickDropPoolRow(phaseIndex, themeType, out DROutGameDropPool selectedRow))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TryBuildDropItem(selectedRow, out droppedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<TowerCompItemData> RollSettlementRewardCandidates(
|
|
||||||
int displayPhaseIndex,
|
|
||||||
LevelThemeType themeType,
|
|
||||||
int candidateCount)
|
|
||||||
{
|
|
||||||
int resolvedCount = Mathf.Max(0, candidateCount);
|
|
||||||
if (resolvedCount <= 0)
|
|
||||||
{
|
|
||||||
return Array.Empty<TowerCompItemData>();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TowerCompItemData> candidates = new List<TowerCompItemData>(resolvedCount);
|
|
||||||
HashSet<int> selectedPoolRowIds = new HashSet<int>();
|
|
||||||
int maxAttempts = Mathf.Max(resolvedCount * 6, resolvedCount);
|
|
||||||
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
|
||||||
|
|
||||||
int attempts = 0;
|
|
||||||
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
|
||||||
{
|
|
||||||
attempts++;
|
|
||||||
if (!TryPickDropPoolRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null)
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
case BearingCompItemData bearingComp:
|
||||||
|
_rewardInventory.BearingComponents.Add(InventoryCloneUtility.CloneBearingComp(bearingComp));
|
||||||
if (!selectedPoolRowIds.Add(selectedRow.Id))
|
break;
|
||||||
{
|
case BaseCompItemData baseComp:
|
||||||
continue;
|
_rewardInventory.BaseComponents.Add(InventoryCloneUtility.CloneBaseComp(baseComp));
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryBuildDropItem(selectedRow, out TowerCompItemData droppedItem) || droppedItem == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
candidates.Add(droppedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
attempts = 0;
|
|
||||||
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
|
||||||
{
|
|
||||||
attempts++;
|
|
||||||
if (!TryRollOutGameItem(phaseIndex, themeType, out TowerCompItemData droppedItem) || droppedItem == null)
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
candidates.Add(droppedItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 两阶段加权抽样:
|
|
||||||
/// 1) 先按主题 + 阶段区间过滤候选行
|
|
||||||
/// 2) 先抽稀有度(稀有度权重 = 同稀有度所有行的 row.Weight * 稀有度曲线权重 之和)
|
|
||||||
/// 3) 在该稀有度内再按 row.Weight 抽具体行
|
|
||||||
/// </summary>
|
|
||||||
private bool TryPickDropPoolRow(int displayPhaseIndex, LevelThemeType themeType, out DROutGameDropPool selectedRow)
|
|
||||||
{
|
|
||||||
selectedRow = null;
|
|
||||||
IDataTable<DROutGameDropPool> dropTable = EnsureOutGameDropPoolTable();
|
|
||||||
if (dropTable == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_eligibleDropPoolBuffer.Clear();
|
|
||||||
DROutGameDropPool[] allRows = dropTable.GetAllDataRows();
|
|
||||||
for (int i = 0; i < allRows.Length; i++)
|
|
||||||
{
|
|
||||||
DROutGameDropPool row = allRows[i];
|
|
||||||
if (row == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主题过滤:主题不匹配的行,概率为 0。
|
|
||||||
if (row.LevelThemeType != themeType)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 阶段过滤:不在 [MinPhase, MaxPhase] 的行,概率为 0。
|
|
||||||
if (displayPhaseIndex < row.MinPhase || displayPhaseIndex > row.MaxPhase)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_eligibleDropPoolBuffer.Add(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_eligibleDropPoolBuffer.Count <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RarityType selectedRarity = RollRarity(displayPhaseIndex, _eligibleDropPoolBuffer);
|
|
||||||
if (selectedRarity == RarityType.None)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalWeight = 0;
|
|
||||||
for (int i = 0; i < _eligibleDropPoolBuffer.Count; i++)
|
|
||||||
{
|
|
||||||
DROutGameDropPool row = _eligibleDropPoolBuffer[i];
|
|
||||||
if (row.Rarity != selectedRarity)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在已选稀有度内,row.Weight 是线性权重倍率。
|
|
||||||
totalWeight += Mathf.Max(1, row.Weight);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalWeight <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int randomWeight = Random.Range(1, totalWeight + 1);
|
|
||||||
int cumulativeWeight = 0;
|
|
||||||
for (int i = 0; i < _eligibleDropPoolBuffer.Count; i++)
|
|
||||||
{
|
|
||||||
DROutGameDropPool row = _eligibleDropPoolBuffer[i];
|
|
||||||
if (row.Rarity != selectedRarity)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cumulativeWeight += Mathf.Max(1, row.Weight);
|
|
||||||
if (randomWeight <= cumulativeWeight)
|
|
||||||
{
|
|
||||||
selectedRow = row;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedRow = _eligibleDropPoolBuffer[_eligibleDropPoolBuffer.Count - 1];
|
|
||||||
return selectedRow != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在过滤后的候选行里抽稀有度。
|
|
||||||
/// 实际稀有度权重公式:
|
|
||||||
/// rarityWeight = sum(max(1, row.Weight) * GetRarityCurveWeight(row.Rarity, phaseT))
|
|
||||||
/// 其中 phaseT = clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase)。
|
|
||||||
/// </summary>
|
|
||||||
private RarityType RollRarity(int displayPhaseIndex, List<DROutGameDropPool> candidates)
|
|
||||||
{
|
|
||||||
_rarityRollWeightBuffer.Clear();
|
|
||||||
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
|
|
||||||
for (int i = 0; i < candidates.Count; i++)
|
|
||||||
{
|
|
||||||
DROutGameDropPool row = candidates[i];
|
|
||||||
if (row == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float curveWeight = GetRarityCurveWeight(row.Rarity, phaseT);
|
|
||||||
if (curveWeight <= 0f)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_rarityRollWeightBuffer.TryGetValue(row.Rarity, out float existingWeight))
|
|
||||||
{
|
|
||||||
_rarityRollWeightBuffer[row.Rarity] = existingWeight + Mathf.Max(1, row.Weight) * curveWeight;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_rarityRollWeightBuffer[row.Rarity] = Mathf.Max(1, row.Weight) * curveWeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float totalWeight = 0f;
|
|
||||||
foreach (var pair in _rarityRollWeightBuffer)
|
|
||||||
{
|
|
||||||
totalWeight += Mathf.Max(0f, pair.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalWeight <= 0f)
|
|
||||||
{
|
|
||||||
return RarityType.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
float randomWeight = Random.value * totalWeight;
|
|
||||||
float cumulativeWeight = 0f;
|
|
||||||
foreach (var pair in _rarityRollWeightBuffer)
|
|
||||||
{
|
|
||||||
cumulativeWeight += Mathf.Max(0f, pair.Value);
|
|
||||||
if (randomWeight <= cumulativeWeight)
|
|
||||||
{
|
|
||||||
return pair.Key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var pair in _rarityRollWeightBuffer)
|
|
||||||
{
|
|
||||||
return pair.Key;
|
|
||||||
}
|
|
||||||
|
|
||||||
return RarityType.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 返回各稀有度在当前阶段的曲线权重。
|
|
||||||
/// 返回值越大,该稀有度在 RollRarity 中越容易被抽到。
|
|
||||||
/// phaseT 取值范围为 [0,1]:
|
|
||||||
/// - White/Green:钟形趋势(前中期权重更高)
|
|
||||||
/// - Blue/Purple:随阶段近似线性上升
|
|
||||||
/// - Red:随阶段二次上升(前期很低,后期明显抬升)
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDataTable<DROutGameDropPool> EnsureOutGameDropPoolTable()
|
|
||||||
{
|
|
||||||
if (_drOutGameDropPool != null)
|
|
||||||
{
|
|
||||||
return _drOutGameDropPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
_drOutGameDropPool = GameEntry.DataTable.GetDataTable<DROutGameDropPool>();
|
|
||||||
return _drOutGameDropPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryBuildDropItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
|
||||||
{
|
|
||||||
droppedItem = null;
|
|
||||||
if (row == null || row.ItemId <= 0 || string.IsNullOrWhiteSpace(row.ItemType))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string itemType = row.ItemType.Trim();
|
|
||||||
if (itemType.Equals("MuzzleComp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return TryBuildMuzzleCompItem(row, out droppedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemType.Equals("BearingComp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return TryBuildBearingCompItem(row, out droppedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemType.Equals("BaseComp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return TryBuildBaseCompItem(row, out droppedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryBuildMuzzleCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
|
||||||
{
|
|
||||||
droppedItem = null;
|
|
||||||
_drMuzzleComp ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
|
||||||
if (_drMuzzleComp == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DRMuzzleComp config = _drMuzzleComp.GetDataRow(row.ItemId);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
droppedItem = new MuzzleCompItemData
|
|
||||||
{
|
|
||||||
InstanceId = _nextDropItemInstanceId++,
|
|
||||||
ConfigId = config.Id,
|
|
||||||
Name = config.Name,
|
|
||||||
Rarity = row.Rarity,
|
|
||||||
Endurance = 100f,
|
|
||||||
Constraint = config.Constraint,
|
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
|
||||||
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
|
||||||
DamageRandomRate = config.DamageRandomRate,
|
|
||||||
AttackMethodType = config.AttackMethodType
|
|
||||||
};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryBuildBearingCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
|
||||||
{
|
|
||||||
droppedItem = null;
|
|
||||||
_drBearingComp ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
|
||||||
if (_drBearingComp == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DRBearingComp config = _drBearingComp.GetDataRow(row.ItemId);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
droppedItem = new BearingCompItemData
|
|
||||||
{
|
|
||||||
InstanceId = _nextDropItemInstanceId++,
|
|
||||||
ConfigId = config.Id,
|
|
||||||
Name = config.Name,
|
|
||||||
Rarity = row.Rarity,
|
|
||||||
Endurance = 100f,
|
|
||||||
Constraint = config.Constraint,
|
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
|
||||||
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
|
||||||
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
|
||||||
};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryBuildBaseCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
|
||||||
{
|
|
||||||
droppedItem = null;
|
|
||||||
_drBaseComp ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
|
||||||
if (_drBaseComp == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DRBaseComp config = _drBaseComp.GetDataRow(row.ItemId);
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
droppedItem = new BaseCompItemData
|
|
||||||
{
|
|
||||||
InstanceId = _nextDropItemInstanceId++,
|
|
||||||
ConfigId = config.Id,
|
|
||||||
Name = config.Name,
|
|
||||||
Rarity = row.Rarity,
|
|
||||||
Endurance = 100f,
|
|
||||||
Constraint = config.Constraint,
|
|
||||||
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
|
||||||
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
|
||||||
AttackPropertyType = config.AttackPropertyType
|
|
||||||
};
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,12 @@ namespace GeometryTD.CustomComponent
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mapLoadContext?.InitialMapData == null)
|
||||||
|
{
|
||||||
|
errorMessage = "CombatLoadSession map load failed. MapEntityLoadContext is missing initial map data.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
string mapAssetName = level.Id.ToString();
|
string mapAssetName = level.Id.ToString();
|
||||||
string mapAssetPath = AssetUtility.GetLevelMapAsset(mapAssetName);
|
string mapAssetPath = AssetUtility.GetLevelMapAsset(mapAssetName);
|
||||||
if (GameEntry.Resource.HasAsset(mapAssetPath) == HasAssetResult.NotExist)
|
if (GameEntry.Resource.HasAsset(mapAssetPath) == HasAssetResult.NotExist)
|
||||||
|
|
@ -242,13 +248,11 @@ namespace GeometryTD.CustomComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadingMapEntityId = _entity.GenerateSerialId();
|
_loadingMapEntityId = _entity.GenerateSerialId();
|
||||||
MapData resolvedMapData = mapLoadContext?.InitialMapData != null
|
MapData resolvedMapData = mapLoadContext.InitialMapData.CloneForEntity(_loadingMapEntityId, Vector3.zero);
|
||||||
? mapLoadContext.InitialMapData.CloneForEntity(_loadingMapEntityId, Vector3.zero)
|
|
||||||
: new MapData(_loadingMapEntityId, level.Id, Vector3.zero);
|
|
||||||
MapEntityLoadContext resolvedLoadContext = new MapEntityLoadContext(
|
MapEntityLoadContext resolvedLoadContext = new MapEntityLoadContext(
|
||||||
resolvedMapData,
|
resolvedMapData,
|
||||||
mapLoadContext?.TryConsumeCoin,
|
mapLoadContext.TryConsumeCoin,
|
||||||
mapLoadContext?.AddCoin);
|
mapLoadContext.AddCoin);
|
||||||
_entity.ShowMap(resolvedLoadContext, mapAssetName);
|
_entity.ShowMap(resolvedLoadContext, mapAssetName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.CustomEvent;
|
using GeometryTD.CustomEvent;
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
|
|
@ -39,8 +40,10 @@ namespace GeometryTD.CustomComponent
|
||||||
public int GainedCoin => _context.CombatInRunResourceManager.GainedCoin;
|
public int GainedCoin => _context.CombatInRunResourceManager.GainedCoin;
|
||||||
public int GainedGold => _context.CombatInRunResourceManager.GainedGold;
|
public int GainedGold => _context.CombatInRunResourceManager.GainedGold;
|
||||||
|
|
||||||
public void OnInit()
|
public void OnInit(Action<bool> combatEndedCallback)
|
||||||
{
|
{
|
||||||
|
_context.CombatEndedCallback = combatEndedCallback;
|
||||||
|
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
{
|
{
|
||||||
_context.Entity = GameEntry.Entity;
|
_context.Entity = GameEntry.Entity;
|
||||||
|
|
@ -142,6 +145,7 @@ namespace GeometryTD.CustomComponent
|
||||||
_context.EventBridge.Unbind();
|
_context.EventBridge.Unbind();
|
||||||
_context.CombatFinishFormUseCase = null;
|
_context.CombatFinishFormUseCase = null;
|
||||||
_context.RewardSelectFormUseCase = null;
|
_context.RewardSelectFormUseCase = null;
|
||||||
|
_context.CombatEndedCallback = null;
|
||||||
|
|
||||||
_context.Entity = null;
|
_context.Entity = null;
|
||||||
_initialized = false;
|
_initialized = false;
|
||||||
|
|
@ -187,15 +191,7 @@ namespace GeometryTD.CustomComponent
|
||||||
_flowCoordinator.ResolveCurrentThemeType());
|
_flowCoordinator.ResolveCurrentThemeType());
|
||||||
EnemyDropResolveResult result = _context.EnemyDropResolver.Resolve(context);
|
EnemyDropResolveResult result = _context.EnemyDropResolver.Resolve(context);
|
||||||
_context.CombatInRunResourceManager.AddEnemyDefeatedReward(result.Coin, result.Gold);
|
_context.CombatInRunResourceManager.AddEnemyDefeatedReward(result.Coin, result.Gold);
|
||||||
|
_context.CombatInRunResourceManager.AddEnemyDefeatedLoot(result.LootItem);
|
||||||
if (!result.ShouldRollOutGameItem)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.CombatInRunResourceManager.TryRollOutGameItemDrop(
|
|
||||||
context.DisplayPhaseIndex,
|
|
||||||
context.ThemeType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnCombatFinishReturnRequested()
|
public bool OnCombatFinishReturnRequested()
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ namespace GeometryTD.CustomComponent
|
||||||
_context.PhaseLoopRuntime.Reset();
|
_context.PhaseLoopRuntime.Reset();
|
||||||
_context.LoadSession.Reset();
|
_context.LoadSession.Reset();
|
||||||
_context.CombatInRunResourceManager.Reset();
|
_context.CombatInRunResourceManager.Reset();
|
||||||
|
_context.EnemyDropResolver.Reset();
|
||||||
_context.SettlementContext = null;
|
_context.SettlementContext = null;
|
||||||
_context.CurrentLevel = null;
|
_context.CurrentLevel = null;
|
||||||
_context.IsFinishAsVictory = true;
|
_context.IsFinishAsVictory = true;
|
||||||
|
|
@ -164,11 +165,6 @@ namespace GeometryTD.CustomComponent
|
||||||
return _context.CurrentLevel.LevelThemeType;
|
return _context.CurrentLevel.LevelThemeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameEntry.CombatNode != null)
|
|
||||||
{
|
|
||||||
return GameEntry.CombatNode.CurrentThemeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return LevelThemeType.None;
|
return LevelThemeType.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,16 +173,6 @@ namespace GeometryTD.CustomComponent
|
||||||
return _context.CombatInRunResourceManager.ApplyBaseDamage(damage);
|
return _context.CombatInRunResourceManager.ApplyBaseDamage(damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryConsumeCoin(int coin)
|
|
||||||
{
|
|
||||||
return _schedulerHost.TryConsumeCoin(coin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddCoin(int coin)
|
|
||||||
{
|
|
||||||
_schedulerHost.AddCoin(coin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetCurrentBaseHp()
|
public int GetCurrentBaseHp()
|
||||||
{
|
{
|
||||||
return Mathf.Max(0, _context.CombatInRunResourceManager.CurrentBaseHp);
|
return Mathf.Max(0, _context.CombatInRunResourceManager.CurrentBaseHp);
|
||||||
|
|
@ -261,7 +247,7 @@ namespace GeometryTD.CustomComponent
|
||||||
_context.IsCompleted = true;
|
_context.IsCompleted = true;
|
||||||
_context.CurrentState = null;
|
_context.CurrentState = null;
|
||||||
_context.CombatInRunResourceManager.MarkCombatEnded();
|
_context.CombatInRunResourceManager.MarkCombatEnded();
|
||||||
GameEntry.CombatNode?.OnCombatEndedByScheduler(succeeded);
|
_context.CombatEndedCallback?.Invoke(succeeded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
using GeometryTD.Entity;
|
using GeometryTD.Entity;
|
||||||
|
|
@ -23,6 +24,7 @@ namespace GeometryTD.CustomComponent
|
||||||
public CombatFinishFormUseCase CombatFinishFormUseCase { get; set; }
|
public CombatFinishFormUseCase CombatFinishFormUseCase { get; set; }
|
||||||
public RewardSelectFormUseCase RewardSelectFormUseCase { get; set; }
|
public RewardSelectFormUseCase RewardSelectFormUseCase { get; set; }
|
||||||
public CombatStateBase CurrentState { get; set; }
|
public CombatStateBase CurrentState { get; set; }
|
||||||
|
public Action<bool> CombatEndedCallback { get; set; }
|
||||||
public bool IsFinishAsVictory { get; set; } = true;
|
public bool IsFinishAsVictory { get; set; } = true;
|
||||||
public bool IsCompleted { get; set; }
|
public bool IsCompleted { get; set; }
|
||||||
public bool NodeEnterFired { get; set; }
|
public bool NodeEnterFired { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -87,19 +87,19 @@ namespace GeometryTD.CustomComponent
|
||||||
|
|
||||||
public bool TryPrepareRewardSelection(
|
public bool TryPrepareRewardSelection(
|
||||||
CombatSettlementContext settlementContext,
|
CombatSettlementContext settlementContext,
|
||||||
CombatInRunResourceManager resourceManager,
|
EnemyDropResolver enemyDropResolver,
|
||||||
int displayPhaseIndex,
|
int displayPhaseIndex,
|
||||||
LevelThemeType themeType,
|
LevelThemeType themeType,
|
||||||
RewardSelectFormUseCase rewardSelectFormUseCase,
|
RewardSelectFormUseCase rewardSelectFormUseCase,
|
||||||
Action<RewardSelectItemRawData> onRewardSelected,
|
Action<RewardSelectItemRawData> onRewardSelected,
|
||||||
Action onGiveUp)
|
Action onGiveUp)
|
||||||
{
|
{
|
||||||
if (settlementContext == null || resourceManager == null || rewardSelectFormUseCase == null)
|
if (settlementContext == null || enemyDropResolver == null || rewardSelectFormUseCase == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IReadOnlyList<TowerCompItemData> candidateItems = resourceManager.RollSettlementRewardCandidates(
|
IReadOnlyList<TowerCompItemData> candidateItems = enemyDropResolver.RollSettlementRewardCandidates(
|
||||||
displayPhaseIndex,
|
displayPhaseIndex,
|
||||||
themeType,
|
themeType,
|
||||||
RewardSelectDisplayCount);
|
RewardSelectDisplayCount);
|
||||||
|
|
|
||||||
|
|
@ -60,13 +60,12 @@ namespace GeometryTD.CustomComponent
|
||||||
position: Vector3.zero,
|
position: Vector3.zero,
|
||||||
initialCoin: Context.CombatInRunResourceManager.CurrentCoin,
|
initialCoin: Context.CombatInRunResourceManager.CurrentCoin,
|
||||||
buildTowerStatsSnapshot: buildTowerStatsSnapshot,
|
buildTowerStatsSnapshot: buildTowerStatsSnapshot,
|
||||||
inventorySnapshot: GameEntry.PlayerInventory != null
|
inventorySnapshot: Context.CombatInRunResourceManager.GetCombatInventorySnapshot(),
|
||||||
? GameEntry.PlayerInventory.GetInventorySnapshot()
|
participantTowerSnapshot: Context.CombatInRunResourceManager.GetParticipantTowerSnapshot());
|
||||||
: null,
|
return new MapEntityLoadContext(
|
||||||
participantTowerSnapshot: GameEntry.PlayerInventory != null
|
mapData,
|
||||||
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
|
Context.CombatInRunResourceManager.TryConsumeCoin,
|
||||||
: null);
|
Context.CombatInRunResourceManager.AddCoin);
|
||||||
return new MapEntityLoadContext(mapData, Flow.TryConsumeCoin, Flow.AddCoin);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace GeometryTD.CustomComponent
|
||||||
Flow.EnsureRewardSelectFormUseCaseBound();
|
Flow.EnsureRewardSelectFormUseCaseBound();
|
||||||
if (!Context.SettlementFlowService.TryPrepareRewardSelection(
|
if (!Context.SettlementFlowService.TryPrepareRewardSelection(
|
||||||
Context.SettlementContext,
|
Context.SettlementContext,
|
||||||
Context.CombatInRunResourceManager,
|
Context.EnemyDropResolver,
|
||||||
Context.PhaseLoopRuntime.DisplayPhaseIndex,
|
Context.PhaseLoopRuntime.DisplayPhaseIndex,
|
||||||
Flow.ResolveCurrentThemeType(),
|
Flow.ResolveCurrentThemeType(),
|
||||||
Context.RewardSelectFormUseCase,
|
Context.RewardSelectFormUseCase,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal readonly struct EnemyDropResolveResult
|
internal readonly struct EnemyDropResolveResult
|
||||||
{
|
{
|
||||||
public static EnemyDropResolveResult Empty => new(0, 0, false);
|
public static EnemyDropResolveResult Empty => new(0, 0, null);
|
||||||
|
|
||||||
public EnemyDropResolveResult(int coin, int gold, bool shouldRollOutGameItem)
|
public EnemyDropResolveResult(int coin, int gold, TowerCompItemData lootItem)
|
||||||
{
|
{
|
||||||
Coin = coin;
|
Coin = coin;
|
||||||
Gold = gold;
|
Gold = gold;
|
||||||
ShouldRollOutGameItem = shouldRollOutGameItem;
|
LootItem = lootItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Coin { get; }
|
public int Coin { get; }
|
||||||
|
|
||||||
public int Gold { get; }
|
public int Gold { get; }
|
||||||
|
|
||||||
public bool ShouldRollOutGameItem { get; }
|
public TowerCompItemData LootItem { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GameFramework.DataTable;
|
||||||
using GeometryTD.DataTable;
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Random = UnityEngine.Random;
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
|
|
@ -6,6 +10,27 @@ namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal sealed class EnemyDropResolver
|
internal sealed class EnemyDropResolver
|
||||||
{
|
{
|
||||||
|
private const float DropChanceBase = 0.05f;
|
||||||
|
private const float DropChancePerPhase = 0.2f;
|
||||||
|
private const float DropChanceCap = 0.2f;
|
||||||
|
private const float RarityCurveScalePhase = 30f;
|
||||||
|
|
||||||
|
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
|
||||||
|
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
|
||||||
|
|
||||||
|
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
|
||||||
|
private IDataTable<DRMuzzleComp> _drMuzzleComp;
|
||||||
|
private IDataTable<DRBearingComp> _drBearingComp;
|
||||||
|
private IDataTable<DRBaseComp> _drBaseComp;
|
||||||
|
private long _nextDropItemInstanceId = 1;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_eligibleDropPoolBuffer.Clear();
|
||||||
|
_rarityRollWeightBuffer.Clear();
|
||||||
|
_nextDropItemInstanceId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
public EnemyDropResolveResult Resolve(in EnemyDropResolveContext context)
|
public EnemyDropResolveResult Resolve(in EnemyDropResolveContext context)
|
||||||
{
|
{
|
||||||
DREnemy enemy = context.Enemy;
|
DREnemy enemy = context.Enemy;
|
||||||
|
|
@ -26,7 +51,375 @@ namespace GeometryTD.CustomComponent
|
||||||
gold = Mathf.Max(0, enemy.DropGold);
|
gold = Mathf.Max(0, enemy.DropGold);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EnemyDropResolveResult(coin, gold, shouldRollOutGameItem: true);
|
TowerCompItemData lootItem = null;
|
||||||
|
if (ShouldRollOutGameItem(context.DisplayPhaseIndex) &&
|
||||||
|
TryRollOutGameItem(context.DisplayPhaseIndex, context.ThemeType, out TowerCompItemData droppedItem))
|
||||||
|
{
|
||||||
|
lootItem = droppedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EnemyDropResolveResult(coin, gold, lootItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<TowerCompItemData> RollSettlementRewardCandidates(
|
||||||
|
int displayPhaseIndex,
|
||||||
|
LevelThemeType themeType,
|
||||||
|
int candidateCount)
|
||||||
|
{
|
||||||
|
int resolvedCount = Mathf.Max(0, candidateCount);
|
||||||
|
if (resolvedCount <= 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<TowerCompItemData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TowerCompItemData> candidates = new List<TowerCompItemData>(resolvedCount);
|
||||||
|
HashSet<int> selectedPoolRowIds = new HashSet<int>();
|
||||||
|
int maxAttempts = Mathf.Max(resolvedCount * 6, resolvedCount);
|
||||||
|
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
||||||
|
|
||||||
|
int attempts = 0;
|
||||||
|
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
||||||
|
{
|
||||||
|
attempts++;
|
||||||
|
if (!TryPickDropPoolRow(phaseIndex, themeType, out DROutGameDropPool selectedRow) || selectedRow == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedPoolRowIds.Add(selectedRow.Id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryBuildDropItem(selectedRow, out TowerCompItemData droppedItem) || droppedItem == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.Add(droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts = 0;
|
||||||
|
while (candidates.Count < resolvedCount && attempts < maxAttempts)
|
||||||
|
{
|
||||||
|
attempts++;
|
||||||
|
if (!TryRollOutGameItem(phaseIndex, themeType, out TowerCompItemData droppedItem) || droppedItem == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.Add(droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
|
||||||
|
if (!TryPickDropPoolRow(phaseIndex, themeType, out DROutGameDropPool selectedRow))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryBuildDropItem(selectedRow, out droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryPickDropPoolRow(int displayPhaseIndex, LevelThemeType themeType, out DROutGameDropPool selectedRow)
|
||||||
|
{
|
||||||
|
selectedRow = null;
|
||||||
|
IDataTable<DROutGameDropPool> dropTable = EnsureOutGameDropPoolTable();
|
||||||
|
if (dropTable == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_eligibleDropPoolBuffer.Clear();
|
||||||
|
DROutGameDropPool[] allRows = dropTable.GetAllDataRows();
|
||||||
|
for (int i = 0; i < allRows.Length; i++)
|
||||||
|
{
|
||||||
|
DROutGameDropPool row = allRows[i];
|
||||||
|
if (row == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.LevelThemeType != themeType)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayPhaseIndex < row.MinPhase || displayPhaseIndex > row.MaxPhase)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_eligibleDropPoolBuffer.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_eligibleDropPoolBuffer.Count <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RarityType selectedRarity = RollRarity(displayPhaseIndex, _eligibleDropPoolBuffer);
|
||||||
|
if (selectedRarity == RarityType.None)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalWeight = 0;
|
||||||
|
for (int i = 0; i < _eligibleDropPoolBuffer.Count; i++)
|
||||||
|
{
|
||||||
|
DROutGameDropPool row = _eligibleDropPoolBuffer[i];
|
||||||
|
if (row.Rarity != selectedRarity)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalWeight += Mathf.Max(1, row.Weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int randomWeight = Random.Range(1, totalWeight + 1);
|
||||||
|
int cumulativeWeight = 0;
|
||||||
|
for (int i = 0; i < _eligibleDropPoolBuffer.Count; i++)
|
||||||
|
{
|
||||||
|
DROutGameDropPool row = _eligibleDropPoolBuffer[i];
|
||||||
|
if (row.Rarity != selectedRarity)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulativeWeight += Mathf.Max(1, row.Weight);
|
||||||
|
if (randomWeight <= cumulativeWeight)
|
||||||
|
{
|
||||||
|
selectedRow = row;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedRow = _eligibleDropPoolBuffer[_eligibleDropPoolBuffer.Count - 1];
|
||||||
|
return selectedRow != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RarityType RollRarity(int displayPhaseIndex, List<DROutGameDropPool> candidates)
|
||||||
|
{
|
||||||
|
_rarityRollWeightBuffer.Clear();
|
||||||
|
float phaseT = Mathf.Clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase);
|
||||||
|
for (int i = 0; i < candidates.Count; i++)
|
||||||
|
{
|
||||||
|
DROutGameDropPool row = candidates[i];
|
||||||
|
if (row == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float curveWeight = GetRarityCurveWeight(row.Rarity, phaseT);
|
||||||
|
if (curveWeight <= 0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_rarityRollWeightBuffer.TryGetValue(row.Rarity, out float existingWeight))
|
||||||
|
{
|
||||||
|
_rarityRollWeightBuffer[row.Rarity] = existingWeight + Mathf.Max(1, row.Weight) * curveWeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rarityRollWeightBuffer[row.Rarity] = Mathf.Max(1, row.Weight) * curveWeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float totalWeight = 0f;
|
||||||
|
foreach (var pair in _rarityRollWeightBuffer)
|
||||||
|
{
|
||||||
|
totalWeight += Mathf.Max(0f, pair.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight <= 0f)
|
||||||
|
{
|
||||||
|
return RarityType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
float randomWeight = Random.value * totalWeight;
|
||||||
|
float cumulativeWeight = 0f;
|
||||||
|
foreach (var pair in _rarityRollWeightBuffer)
|
||||||
|
{
|
||||||
|
cumulativeWeight += Mathf.Max(0f, pair.Value);
|
||||||
|
if (randomWeight <= cumulativeWeight)
|
||||||
|
{
|
||||||
|
return pair.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pair in _rarityRollWeightBuffer)
|
||||||
|
{
|
||||||
|
return pair.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RarityType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDataTable<DROutGameDropPool> EnsureOutGameDropPoolTable()
|
||||||
|
{
|
||||||
|
if (_drOutGameDropPool != null)
|
||||||
|
{
|
||||||
|
return _drOutGameDropPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
_drOutGameDropPool = GameEntry.DataTable.GetDataTable<DROutGameDropPool>();
|
||||||
|
return _drOutGameDropPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryBuildDropItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
||||||
|
{
|
||||||
|
droppedItem = null;
|
||||||
|
if (row == null || row.ItemId <= 0 || string.IsNullOrWhiteSpace(row.ItemType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string itemType = row.ItemType.Trim();
|
||||||
|
if (itemType.Equals("MuzzleComp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return TryBuildMuzzleCompItem(row, out droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemType.Equals("BearingComp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return TryBuildBearingCompItem(row, out droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemType.Equals("BaseComp", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return TryBuildBaseCompItem(row, out droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryBuildMuzzleCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
||||||
|
{
|
||||||
|
droppedItem = null;
|
||||||
|
_drMuzzleComp ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
||||||
|
if (_drMuzzleComp == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DRMuzzleComp config = _drMuzzleComp.GetDataRow(row.ItemId);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedItem = new MuzzleCompItemData
|
||||||
|
{
|
||||||
|
InstanceId = _nextDropItemInstanceId++,
|
||||||
|
ConfigId = config.Id,
|
||||||
|
Name = config.Name,
|
||||||
|
Rarity = row.Rarity,
|
||||||
|
Endurance = 100f,
|
||||||
|
Constraint = config.Constraint,
|
||||||
|
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
||||||
|
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
|
||||||
|
DamageRandomRate = config.DamageRandomRate,
|
||||||
|
AttackMethodType = config.AttackMethodType
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryBuildBearingCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
||||||
|
{
|
||||||
|
droppedItem = null;
|
||||||
|
_drBearingComp ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
||||||
|
if (_drBearingComp == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DRBearingComp config = _drBearingComp.GetDataRow(row.ItemId);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedItem = new BearingCompItemData
|
||||||
|
{
|
||||||
|
InstanceId = _nextDropItemInstanceId++,
|
||||||
|
ConfigId = config.Id,
|
||||||
|
Name = config.Name,
|
||||||
|
Rarity = row.Rarity,
|
||||||
|
Endurance = 100f,
|
||||||
|
Constraint = config.Constraint,
|
||||||
|
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
||||||
|
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
|
||||||
|
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryBuildBaseCompItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
|
||||||
|
{
|
||||||
|
droppedItem = null;
|
||||||
|
_drBaseComp ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
||||||
|
if (_drBaseComp == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DRBaseComp config = _drBaseComp.GetDataRow(row.ItemId);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedItem = new BaseCompItemData
|
||||||
|
{
|
||||||
|
InstanceId = _nextDropItemInstanceId++,
|
||||||
|
ConfigId = config.Id,
|
||||||
|
Name = config.Name,
|
||||||
|
Rarity = row.Rarity,
|
||||||
|
Endurance = 100f,
|
||||||
|
Constraint = config.Constraint,
|
||||||
|
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
|
||||||
|
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
|
||||||
|
AttackPropertyType = config.AttackPropertyType
|
||||||
|
};
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ namespace GeometryTD.CustomComponent
|
||||||
bool CanEndCombat { get; }
|
bool CanEndCombat { get; }
|
||||||
|
|
||||||
void ChangeState(CombatStateBase nextState);
|
void ChangeState(CombatStateBase nextState);
|
||||||
bool TryConsumeCoin(int coin);
|
|
||||||
void AddCoin(int coin);
|
|
||||||
bool TryEndCombatByPlayer();
|
bool TryEndCombatByPlayer();
|
||||||
bool TryDebugFail(string errorMessage);
|
bool TryDebugFail(string errorMessage);
|
||||||
bool OnCombatFinishReturnRequested();
|
bool OnCombatFinishReturnRequested();
|
||||||
|
|
|
||||||
|
|
@ -49,16 +49,6 @@ namespace GeometryTD
|
||||||
entityComponent.ShowEntity(typeof(BulletEntity), "Bullet", Constant.AssetPriority.BulletAsset, data);
|
entityComponent.ShowEntity(typeof(BulletEntity), "Bullet", Constant.AssetPriority.BulletAsset, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ShowMap(this EntityComponent entityComponent, MapData data)
|
|
||||||
{
|
|
||||||
ShowMap(entityComponent, data, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ShowMap(this EntityComponent entityComponent, MapData data, string mapAssetName)
|
|
||||||
{
|
|
||||||
ShowMap(entityComponent, new MapEntityLoadContext(data, null, null), mapAssetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ShowMap(this EntityComponent entityComponent, MapEntityLoadContext loadContext)
|
public static void ShowMap(this EntityComponent entityComponent, MapEntityLoadContext loadContext)
|
||||||
{
|
{
|
||||||
ShowMap(entityComponent, loadContext, null);
|
ShowMap(entityComponent, loadContext, null);
|
||||||
|
|
|
||||||
|
|
@ -267,11 +267,6 @@ namespace GeometryTD.Entity
|
||||||
return loadContext;
|
return loadContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userData is MapData legacyMapData)
|
|
||||||
{
|
|
||||||
return new MapEntityLoadContext(legacyMapData, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,4 +435,3 @@ namespace GeometryTD.Entity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue