S5-01 + S5-02 + S5-03
This commit is contained in:
parent
c019f9f527
commit
113decb414
|
|
@ -7,7 +7,7 @@ using UnityEngine;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal sealed class CombatRunResourceStore
|
public sealed class CombatRunResourceStore
|
||||||
{
|
{
|
||||||
private readonly List<TowerStatsData> _buildTowerStatsSnapshot = new();
|
private readonly List<TowerStatsData> _buildTowerStatsSnapshot = new();
|
||||||
private readonly List<TowerItemData> _participantTowerSnapshot = new();
|
private readonly List<TowerItemData> _participantTowerSnapshot = new();
|
||||||
|
|
@ -88,6 +88,21 @@ namespace GeometryTD.CustomComponent
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<long> GetParticipantTowerInstanceIdSnapshot()
|
||||||
|
{
|
||||||
|
List<long> snapshot = new List<long>(_participantTowerSnapshot.Count);
|
||||||
|
for (int i = 0; i < _participantTowerSnapshot.Count; i++)
|
||||||
|
{
|
||||||
|
TowerItemData tower = _participantTowerSnapshot[i];
|
||||||
|
if (tower != null && tower.InstanceId > 0)
|
||||||
|
{
|
||||||
|
snapshot.Add(tower.InstanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryConsumeCoin(int coin)
|
public bool TryConsumeCoin(int coin)
|
||||||
{
|
{
|
||||||
int requiredCoin = Mathf.Max(0, coin);
|
int requiredCoin = Mathf.Max(0, coin);
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using GeometryTD.Definition;
|
using GeometryTD.Definition;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal sealed class CombatSettlementContext
|
public sealed class CombatSettlementContext
|
||||||
{
|
{
|
||||||
public CombatSettlementFlags Flags { get; } = new();
|
public CombatSettlementFlags Flags { get; } = new();
|
||||||
public CombatSettlementResult Result { get; } = new();
|
public CombatSettlementResult Result { get; } = new();
|
||||||
public CombatSettlementSummary Summary { get; } = new();
|
public CombatSettlementSummary Summary { get; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class CombatSettlementFlags
|
public sealed class CombatSettlementFlags
|
||||||
{
|
{
|
||||||
public bool ShouldOpenRewardSelection;
|
public bool ShouldOpenRewardSelection;
|
||||||
public bool DidEnterRewardSelection;
|
public bool DidEnterRewardSelection;
|
||||||
public bool IsCommitted;
|
public bool IsCommitted;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class CombatSettlementResult
|
public sealed class CombatSettlementResult
|
||||||
{
|
{
|
||||||
public bool DidCombatWin;
|
public bool DidCombatWin;
|
||||||
public int FinalCoin;
|
public int FinalCoin;
|
||||||
|
|
@ -25,17 +26,17 @@ namespace GeometryTD.CustomComponent
|
||||||
public int DefeatedEnemyCount;
|
public int DefeatedEnemyCount;
|
||||||
public int GainedGold;
|
public int GainedGold;
|
||||||
public BackpackInventoryData RewardInventory;
|
public BackpackInventoryData RewardInventory;
|
||||||
public CombatSettlementPenaltyResult Penalty { get; } = new();
|
public CombatSettlementEnduranceResult Endurance { get; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class CombatSettlementPenaltyResult
|
public sealed class CombatSettlementEnduranceResult
|
||||||
{
|
{
|
||||||
public bool ShouldApplyLowBaseHpPenalty;
|
public List<long> TargetTowerInstanceIds { get; } = new();
|
||||||
public float LowBaseHpEndurancePenaltyValue;
|
public float EnduranceLossPerComponent;
|
||||||
public int AffectedTowerCount;
|
public int AffectedTowerCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class CombatSettlementSummary
|
public sealed class CombatSettlementSummary
|
||||||
{
|
{
|
||||||
public int DefeatedEnemyCount;
|
public int DefeatedEnemyCount;
|
||||||
public int GainedGold;
|
public int GainedGold;
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,13 @@ using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal sealed class CombatSettlementService
|
public sealed class CombatSettlementService
|
||||||
{
|
{
|
||||||
private const int RewardSelectDisplayCount = 3;
|
private const int RewardSelectDisplayCount = 3;
|
||||||
private const float FullBaseHpGoldBonusRate = 0.3f;
|
private const float FullBaseHpGoldBonusRate = 0.3f;
|
||||||
private const float HighBaseHpGoldBonusRate = 0.1f;
|
private const float HighBaseHpGoldBonusRate = 0.1f;
|
||||||
private const float HighBaseHpThreshold = 0.8f;
|
private const float HighBaseHpThreshold = 0.8f;
|
||||||
private const float MidBaseHpThreshold = 0.5f;
|
private const float SettlementTowerEnduranceLoss = 1f;
|
||||||
private const float LowBaseHpTowerEndurancePenalty = 10f;
|
|
||||||
|
|
||||||
public CombatSettlementContext BuildSettlementContext(
|
public CombatSettlementContext BuildSettlementContext(
|
||||||
bool didCombatWin,
|
bool didCombatWin,
|
||||||
|
|
@ -33,7 +32,6 @@ namespace GeometryTD.CustomComponent
|
||||||
out int levelRewardGold,
|
out int levelRewardGold,
|
||||||
out float bonusRate,
|
out float bonusRate,
|
||||||
out int bonusGold,
|
out int bonusGold,
|
||||||
out bool appliedLowBaseHpPenalty,
|
|
||||||
out shouldOpenFullBaseHpRewardSelect);
|
out shouldOpenFullBaseHpRewardSelect);
|
||||||
|
|
||||||
CombatSettlementContext settlementContext = new CombatSettlementContext
|
CombatSettlementContext settlementContext = new CombatSettlementContext
|
||||||
|
|
@ -48,9 +46,7 @@ namespace GeometryTD.CustomComponent
|
||||||
settlementContext.Result.RewardInventory = resourceStore != null
|
settlementContext.Result.RewardInventory = resourceStore != null
|
||||||
? resourceStore.GetRewardInventorySnapshot()
|
? resourceStore.GetRewardInventorySnapshot()
|
||||||
: new BackpackInventoryData();
|
: new BackpackInventoryData();
|
||||||
settlementContext.Result.Penalty.ShouldApplyLowBaseHpPenalty = appliedLowBaseHpPenalty;
|
PopulateEnduranceSettlement(settlementContext, resourceStore);
|
||||||
settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue =
|
|
||||||
appliedLowBaseHpPenalty ? LowBaseHpTowerEndurancePenalty : 0f;
|
|
||||||
settlementContext.Flags.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect;
|
settlementContext.Flags.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect;
|
||||||
settlementContext.Flags.DidEnterRewardSelection = false;
|
settlementContext.Flags.DidEnterRewardSelection = false;
|
||||||
settlementContext.Summary.DefeatedEnemyCount = settlementContext.Result.DefeatedEnemyCount;
|
settlementContext.Summary.DefeatedEnemyCount = settlementContext.Result.DefeatedEnemyCount;
|
||||||
|
|
@ -58,7 +54,7 @@ namespace GeometryTD.CustomComponent
|
||||||
settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory;
|
settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory;
|
||||||
|
|
||||||
Log.Info(
|
Log.Info(
|
||||||
"Combat settlement resolved. Level={0}, BaseHp={1}/{2}, LevelReward={3}, BonusRate={4:P0}, BonusGold={5}, FullHpRewardSelect={6}, LowHpPenalty={7}.",
|
"Combat settlement resolved. Level={0}, BaseHp={1}/{2}, LevelReward={3}, BonusRate={4:P0}, BonusGold={5}, FullHpRewardSelect={6}, EnduranceTargets={7}.",
|
||||||
currentLevel != null ? currentLevel.Id : 0,
|
currentLevel != null ? currentLevel.Id : 0,
|
||||||
currentBaseHp,
|
currentBaseHp,
|
||||||
maxBaseHp,
|
maxBaseHp,
|
||||||
|
|
@ -66,7 +62,7 @@ namespace GeometryTD.CustomComponent
|
||||||
bonusRate,
|
bonusRate,
|
||||||
bonusGold,
|
bonusGold,
|
||||||
shouldOpenFullBaseHpRewardSelect,
|
shouldOpenFullBaseHpRewardSelect,
|
||||||
appliedLowBaseHpPenalty);
|
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count);
|
||||||
return settlementContext;
|
return settlementContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +77,7 @@ namespace GeometryTD.CustomComponent
|
||||||
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
|
||||||
settlementContext.Result.RewardInventory = rewardInventory;
|
settlementContext.Result.RewardInventory = rewardInventory;
|
||||||
settlementContext.Summary.RewardInventory = rewardInventory;
|
settlementContext.Summary.RewardInventory = rewardInventory;
|
||||||
settlementContext.Result.Penalty.AffectedTowerCount = ApplyDeferredSettlementPenalty(settlementContext);
|
settlementContext.Result.Endurance.AffectedTowerCount = ApplyDeferredSettlementEndurance(settlementContext);
|
||||||
settlementContext.Flags.IsCommitted = true;
|
settlementContext.Flags.IsCommitted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,7 +177,6 @@ namespace GeometryTD.CustomComponent
|
||||||
out int levelRewardGold,
|
out int levelRewardGold,
|
||||||
out float bonusRate,
|
out float bonusRate,
|
||||||
out int bonusGold,
|
out int bonusGold,
|
||||||
out bool appliedLowBaseHpPenalty,
|
|
||||||
out bool shouldOpenFullBaseHpRewardSelect)
|
out bool shouldOpenFullBaseHpRewardSelect)
|
||||||
{
|
{
|
||||||
currentBaseHp = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentBaseHp : 0);
|
currentBaseHp = Mathf.Max(0, resourceStore != null ? resourceStore.CurrentBaseHp : 0);
|
||||||
|
|
@ -194,7 +189,6 @@ namespace GeometryTD.CustomComponent
|
||||||
levelRewardGold = currentLevel != null ? Mathf.Max(0, currentLevel.RewardGold) : 0;
|
levelRewardGold = currentLevel != null ? Mathf.Max(0, currentLevel.RewardGold) : 0;
|
||||||
bonusRate = 0f;
|
bonusRate = 0f;
|
||||||
bonusGold = 0;
|
bonusGold = 0;
|
||||||
appliedLowBaseHpPenalty = false;
|
|
||||||
shouldOpenFullBaseHpRewardSelect = false;
|
shouldOpenFullBaseHpRewardSelect = false;
|
||||||
|
|
||||||
if (!didCombatWin || resourceStore == null)
|
if (!didCombatWin || resourceStore == null)
|
||||||
|
|
@ -214,10 +208,6 @@ namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
bonusRate = HighBaseHpGoldBonusRate;
|
bonusRate = HighBaseHpGoldBonusRate;
|
||||||
}
|
}
|
||||||
else if (hpRate < MidBaseHpThreshold)
|
|
||||||
{
|
|
||||||
appliedLowBaseHpPenalty = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int goldForBonusCalculation = Mathf.Max(0, resourceStore.GainedGold) + levelRewardGold;
|
int goldForBonusCalculation = Mathf.Max(0, resourceStore.GainedGold) + levelRewardGold;
|
||||||
|
|
@ -226,11 +216,40 @@ namespace GeometryTD.CustomComponent
|
||||||
resourceStore.AddSettlementGold(settlementGold);
|
resourceStore.AddSettlementGold(settlementGold);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ApplyDeferredSettlementPenalty(CombatSettlementContext settlementContext)
|
private static void PopulateEnduranceSettlement(
|
||||||
|
CombatSettlementContext settlementContext,
|
||||||
|
CombatRunResourceStore resourceStore)
|
||||||
|
{
|
||||||
|
if (settlementContext == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CombatSettlementEnduranceResult enduranceResult = settlementContext.Result.Endurance;
|
||||||
|
enduranceResult.TargetTowerInstanceIds.Clear();
|
||||||
|
enduranceResult.EnduranceLossPerComponent = SettlementTowerEnduranceLoss;
|
||||||
|
|
||||||
|
IReadOnlyList<long> participantTowerIds = resourceStore?.GetParticipantTowerInstanceIdSnapshot();
|
||||||
|
if (participantTowerIds == null || participantTowerIds.Count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < participantTowerIds.Count; i++)
|
||||||
|
{
|
||||||
|
long towerId = participantTowerIds[i];
|
||||||
|
if (towerId > 0)
|
||||||
|
{
|
||||||
|
enduranceResult.TargetTowerInstanceIds.Add(towerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ApplyDeferredSettlementEndurance(CombatSettlementContext settlementContext)
|
||||||
{
|
{
|
||||||
if (settlementContext == null ||
|
if (settlementContext == null ||
|
||||||
!settlementContext.Result.Penalty.ShouldApplyLowBaseHpPenalty ||
|
settlementContext.Result.Endurance.EnduranceLossPerComponent <= 0f ||
|
||||||
settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue <= 0f)
|
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count <= 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -241,7 +260,9 @@ namespace GeometryTD.CustomComponent
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return inventory.ReduceAllTowerEndurance(settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue);
|
return inventory.ReduceTowerEndurance(
|
||||||
|
settlementContext.Result.Endurance.TargetTowerInstanceIds,
|
||||||
|
settlementContext.Result.Endurance.EnduranceLossPerComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem)
|
private static bool TryAppendRewardComponent(BackpackInventoryData rewardInventory, TowerCompItemData selectedItem)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using GeometryTD.Definition;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal readonly struct EnemyDropContext
|
public readonly struct EnemyDropContext
|
||||||
{
|
{
|
||||||
public EnemyDropContext(DREnemy enemy, int displayPhaseIndex, LevelThemeType themeType)
|
public EnemyDropContext(DREnemy enemy, int displayPhaseIndex, LevelThemeType themeType)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using Random = UnityEngine.Random;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal sealed class EnemyDropResolver
|
public sealed class EnemyDropResolver
|
||||||
{
|
{
|
||||||
private const float DropChanceBase = 0.05f;
|
private const float DropChanceBase = 0.05f;
|
||||||
private const float DropChancePerPhase = 0.2f;
|
private const float DropChancePerPhase = 0.2f;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ using GeometryTD.Definition;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal readonly struct EnemyDropResult
|
public readonly struct EnemyDropResult
|
||||||
{
|
{
|
||||||
public static EnemyDropResult Empty => new(0, 0, null);
|
public static EnemyDropResult Empty => new(0, 0, null);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,10 +132,10 @@ namespace GeometryTD.CustomComponent
|
||||||
out assembledTower);
|
out assembledTower);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReduceAllTowerEndurance(float enduranceLoss)
|
public int ReduceTowerEndurance(IReadOnlyList<long> towerInstanceIds, float enduranceLoss)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
EnsureInitialized();
|
||||||
return _towerRosterService.ReduceAllTowerEndurance(enduranceLoss);
|
return _towerRosterService.ReduceTowerEndurance(towerInstanceIds, enduranceLoss);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureInitialized()
|
private void EnsureInitialized()
|
||||||
|
|
@ -158,4 +158,3 @@ namespace GeometryTD.CustomComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ using UnityEngine;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal struct PlayerInventoryMergeSummary
|
public struct PlayerInventoryMergeSummary
|
||||||
{
|
{
|
||||||
public int GainedGold;
|
public int GainedGold;
|
||||||
public int GainedMuzzleCount;
|
public int GainedMuzzleCount;
|
||||||
|
|
@ -19,14 +19,14 @@ namespace GeometryTD.CustomComponent
|
||||||
GainedTowerCount > 0;
|
GainedTowerCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class PlayerInventoryState
|
public sealed class PlayerInventoryState
|
||||||
{
|
{
|
||||||
public BackpackInventoryData Inventory = new BackpackInventoryData();
|
public BackpackInventoryData Inventory = new BackpackInventoryData();
|
||||||
public long NextInstanceId = 1;
|
public long NextInstanceId = 1;
|
||||||
public bool IsInitialized;
|
public bool IsInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class PlayerInventoryQueryModel
|
public sealed class PlayerInventoryQueryModel
|
||||||
{
|
{
|
||||||
private readonly PlayerInventoryState _state;
|
private readonly PlayerInventoryState _state;
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ namespace GeometryTD.CustomComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class PlayerInventoryCommandModel
|
public sealed class PlayerInventoryCommandModel
|
||||||
{
|
{
|
||||||
private readonly PlayerInventoryState _state;
|
private readonly PlayerInventoryState _state;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using UnityEngine;
|
||||||
|
|
||||||
namespace GeometryTD.CustomComponent
|
namespace GeometryTD.CustomComponent
|
||||||
{
|
{
|
||||||
internal sealed class PlayerInventoryTowerRosterService
|
public sealed class PlayerInventoryTowerRosterService
|
||||||
{
|
{
|
||||||
private readonly PlayerInventoryQueryModel _queryModel;
|
private readonly PlayerInventoryQueryModel _queryModel;
|
||||||
private readonly int _maxParticipantTowerCount;
|
private readonly int _maxParticipantTowerCount;
|
||||||
|
|
@ -34,11 +34,15 @@ namespace GeometryTD.CustomComponent
|
||||||
_maxParticipantTowerCount);
|
_maxParticipantTowerCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReduceAllTowerEndurance(float enduranceLoss)
|
public int ReduceTowerEndurance(IReadOnlyList<long> towerInstanceIds, float enduranceLoss)
|
||||||
{
|
{
|
||||||
float resolvedLoss = Mathf.Max(0f, enduranceLoss);
|
float resolvedLoss = Mathf.Max(0f, enduranceLoss);
|
||||||
BackpackInventoryData inventory = _queryModel.Inventory;
|
BackpackInventoryData inventory = _queryModel.Inventory;
|
||||||
if (resolvedLoss <= 0f || inventory.Towers == null || inventory.Towers.Count <= 0)
|
if (resolvedLoss <= 0f ||
|
||||||
|
towerInstanceIds == null ||
|
||||||
|
towerInstanceIds.Count <= 0 ||
|
||||||
|
inventory.Towers == null ||
|
||||||
|
inventory.Towers.Count <= 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -46,12 +50,19 @@ namespace GeometryTD.CustomComponent
|
||||||
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventory.MuzzleComponents);
|
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventory.MuzzleComponents);
|
||||||
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventory.BearingComponents);
|
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventory.BearingComponents);
|
||||||
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventory.BaseComponents);
|
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventory.BaseComponents);
|
||||||
|
HashSet<long> processedTowerIds = new HashSet<long>();
|
||||||
|
|
||||||
int affectedCount = 0;
|
int affectedCount = 0;
|
||||||
for (int i = 0; i < inventory.Towers.Count; i++)
|
for (int i = 0; i < towerInstanceIds.Count; i++)
|
||||||
{
|
{
|
||||||
TowerItemData tower = inventory.Towers[i];
|
long towerInstanceId = towerInstanceIds[i];
|
||||||
if (tower == null)
|
if (towerInstanceId <= 0 || !processedTowerIds.Add(towerInstanceId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InventoryParticipantUtility.TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower) ||
|
||||||
|
tower == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ namespace GeometryTD.Definition
|
||||||
TowerMissing = 1,
|
TowerMissing = 1,
|
||||||
MissingMuzzleComponent = 2,
|
MissingMuzzleComponent = 2,
|
||||||
MissingBearingComponent = 3,
|
MissingBearingComponent = 3,
|
||||||
MissingBaseComponent = 4
|
MissingBaseComponent = 4,
|
||||||
|
BrokenMuzzleComponent = 5,
|
||||||
|
BrokenBearingComponent = 6,
|
||||||
|
BrokenBaseComponent = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CombatParticipantTowerValidationResult
|
public sealed class CombatParticipantTowerValidationResult
|
||||||
|
|
@ -64,7 +67,7 @@ namespace GeometryTD.Definition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasComponent(inventory?.MuzzleComponents, tower.MuzzleComponentInstanceId))
|
if (!TryGetComponentById(inventory?.MuzzleComponents, tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComponent))
|
||||||
{
|
{
|
||||||
return new CombatParticipantTowerValidationResult
|
return new CombatParticipantTowerValidationResult
|
||||||
{
|
{
|
||||||
|
|
@ -74,7 +77,17 @@ namespace GeometryTD.Definition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasComponent(inventory?.BearingComponents, tower.BearingComponentInstanceId))
|
if (muzzleComponent.Endurance <= 0f)
|
||||||
|
{
|
||||||
|
return new CombatParticipantTowerValidationResult
|
||||||
|
{
|
||||||
|
TowerInstanceId = tower.InstanceId,
|
||||||
|
Tower = tower,
|
||||||
|
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetComponentById(inventory?.BearingComponents, tower.BearingComponentInstanceId, out BearingCompItemData bearingComponent))
|
||||||
{
|
{
|
||||||
return new CombatParticipantTowerValidationResult
|
return new CombatParticipantTowerValidationResult
|
||||||
{
|
{
|
||||||
|
|
@ -84,7 +97,17 @@ namespace GeometryTD.Definition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasComponent(inventory?.BaseComponents, tower.BaseComponentInstanceId))
|
if (bearingComponent.Endurance <= 0f)
|
||||||
|
{
|
||||||
|
return new CombatParticipantTowerValidationResult
|
||||||
|
{
|
||||||
|
TowerInstanceId = tower.InstanceId,
|
||||||
|
Tower = tower,
|
||||||
|
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenBearingComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetComponentById(inventory?.BaseComponents, tower.BaseComponentInstanceId, out BaseCompItemData baseComponent))
|
||||||
{
|
{
|
||||||
return new CombatParticipantTowerValidationResult
|
return new CombatParticipantTowerValidationResult
|
||||||
{
|
{
|
||||||
|
|
@ -94,6 +117,16 @@ namespace GeometryTD.Definition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (baseComponent.Endurance <= 0f)
|
||||||
|
{
|
||||||
|
return new CombatParticipantTowerValidationResult
|
||||||
|
{
|
||||||
|
TowerInstanceId = tower.InstanceId,
|
||||||
|
Tower = tower,
|
||||||
|
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenBaseComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return new CombatParticipantTowerValidationResult
|
return new CombatParticipantTowerValidationResult
|
||||||
{
|
{
|
||||||
TowerInstanceId = tower.InstanceId,
|
TowerInstanceId = tower.InstanceId,
|
||||||
|
|
@ -165,9 +198,13 @@ namespace GeometryTD.Definition
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool HasComponent<TComponent>(IReadOnlyList<TComponent> components, long componentInstanceId)
|
private static bool TryGetComponentById<TComponent>(
|
||||||
|
IReadOnlyList<TComponent> components,
|
||||||
|
long componentInstanceId,
|
||||||
|
out TComponent resolvedComponent)
|
||||||
where TComponent : TowerCompItemData
|
where TComponent : TowerCompItemData
|
||||||
{
|
{
|
||||||
|
resolvedComponent = null;
|
||||||
if (components == null || componentInstanceId <= 0)
|
if (components == null || componentInstanceId <= 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -178,6 +215,7 @@ namespace GeometryTD.Definition
|
||||||
TComponent component = components[i];
|
TComponent component = components[i];
|
||||||
if (component != null && component.InstanceId == componentInstanceId)
|
if (component != null && component.InstanceId == componentInstanceId)
|
||||||
{
|
{
|
||||||
|
resolvedComponent = component;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,15 @@ namespace GeometryTD.Procedure
|
||||||
return "缺少轴承组件。";
|
return "缺少轴承组件。";
|
||||||
case CombatParticipantTowerValidationFailureReason.MissingBaseComponent:
|
case CombatParticipantTowerValidationFailureReason.MissingBaseComponent:
|
||||||
return "缺少底座组件。";
|
return "缺少底座组件。";
|
||||||
|
case CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent:
|
||||||
|
return "枪口组件耐久为 0,无法参战。";
|
||||||
|
case CombatParticipantTowerValidationFailureReason.BrokenBearingComponent:
|
||||||
|
return "轴承组件耐久为 0,无法参战。";
|
||||||
|
case CombatParticipantTowerValidationFailureReason.BrokenBaseComponent:
|
||||||
|
return "底座组件耐久为 0,无法参战。";
|
||||||
default:
|
default:
|
||||||
return "不满足当前参战条件。";
|
return "不满足当前参战条件。";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,48 @@ namespace GeometryTD.Tests.EditMode
|
||||||
Is.EqualTo(CombatParticipantTowerValidationFailureReason.MissingBaseComponent));
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.MissingBaseComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ValidateTower_Returns_BrokenMuzzle_When_Muzzle_Endurance_Is_Zero()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateInventory();
|
||||||
|
inventory.MuzzleComponents[0].Endurance = 0f;
|
||||||
|
|
||||||
|
CombatParticipantTowerValidationResult result =
|
||||||
|
CombatParticipantTowerValidationService.ValidateTower(inventory, inventory.Towers[0]);
|
||||||
|
|
||||||
|
Assert.That(result.IsValid, Is.False);
|
||||||
|
Assert.That(result.FailureReason,
|
||||||
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ValidateTower_Returns_BrokenBearing_When_Bearing_Endurance_Is_Zero()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateInventory();
|
||||||
|
inventory.BearingComponents[0].Endurance = 0f;
|
||||||
|
|
||||||
|
CombatParticipantTowerValidationResult result =
|
||||||
|
CombatParticipantTowerValidationService.ValidateTower(inventory, inventory.Towers[0]);
|
||||||
|
|
||||||
|
Assert.That(result.IsValid, Is.False);
|
||||||
|
Assert.That(result.FailureReason,
|
||||||
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenBearingComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ValidateTower_Returns_BrokenBase_When_Base_Endurance_Is_Zero()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateInventory();
|
||||||
|
inventory.BaseComponents[0].Endurance = 0f;
|
||||||
|
|
||||||
|
CombatParticipantTowerValidationResult result =
|
||||||
|
CombatParticipantTowerValidationService.ValidateTower(inventory, inventory.Towers[0]);
|
||||||
|
|
||||||
|
Assert.That(result.IsValid, Is.False);
|
||||||
|
Assert.That(result.FailureReason,
|
||||||
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenBaseComponent));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ValidateParticipantTowers_Splits_Valid_And_Invalid_Towers()
|
public void ValidateParticipantTowers_Splits_Valid_And_Invalid_Towers()
|
||||||
{
|
{
|
||||||
|
|
@ -103,19 +145,19 @@ namespace GeometryTD.Tests.EditMode
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ValidateParticipantTowers_Treats_Zero_Endurance_As_Valid_For_S3_01()
|
public void ValidateParticipantTowers_Treats_Zero_Endurance_As_Invalid_For_S5_02()
|
||||||
{
|
{
|
||||||
BackpackInventoryData inventory = CreateInventory();
|
BackpackInventoryData inventory = CreateInventory();
|
||||||
inventory.MuzzleComponents[0].Endurance = 0f;
|
inventory.MuzzleComponents[0].Endurance = 0f;
|
||||||
inventory.BearingComponents[0].Endurance = 0f;
|
|
||||||
inventory.BaseComponents[0].Endurance = 0f;
|
|
||||||
|
|
||||||
CombatParticipantTowerValidationSummary summary =
|
CombatParticipantTowerValidationSummary summary =
|
||||||
CombatParticipantTowerValidationService.ValidateParticipantTowers(inventory);
|
CombatParticipantTowerValidationService.ValidateParticipantTowers(inventory);
|
||||||
|
|
||||||
Assert.That(summary.HasAnyValidParticipantTower, Is.True);
|
Assert.That(summary.HasAnyValidParticipantTower, Is.False);
|
||||||
Assert.That(summary.ValidTowers.Count, Is.EqualTo(1));
|
Assert.That(summary.ValidTowers.Count, Is.EqualTo(0));
|
||||||
Assert.That(summary.InvalidResults.Count, Is.EqualTo(0));
|
Assert.That(summary.InvalidResults.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(summary.InvalidResults[0].FailureReason,
|
||||||
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace GeometryTD.Tests.EditMode
|
||||||
|
{
|
||||||
|
public sealed class CombatSettlementServiceTests
|
||||||
|
{
|
||||||
|
private const int MaxParticipantTowerCount = 4;
|
||||||
|
|
||||||
|
private GameObject _inventoryObject;
|
||||||
|
private PlayerInventoryComponent _originalPlayerInventory;
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
SetStaticPlayerInventory(_originalPlayerInventory);
|
||||||
|
if (_inventoryObject != null)
|
||||||
|
{
|
||||||
|
Object.DestroyImmediate(_inventoryObject);
|
||||||
|
_inventoryObject = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BuildSettlementContext_Captures_CombatStart_Participant_Tower_Ids_And_Fixed_Endurance_Loss()
|
||||||
|
{
|
||||||
|
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||||
|
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||||
|
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||||
|
resourceStore.InitializeForCombat(level);
|
||||||
|
|
||||||
|
BackpackInventoryData changedInventory = inventoryComponent.GetInventorySnapshot();
|
||||||
|
changedInventory.ParticipantTowerInstanceIds.Clear();
|
||||||
|
changedInventory.ParticipantTowerInstanceIds.Add(90003);
|
||||||
|
inventoryComponent.ReplaceInventorySnapshot(changedInventory);
|
||||||
|
|
||||||
|
CombatSettlementContext settlementContext = new CombatSettlementService().BuildSettlementContext(
|
||||||
|
didCombatWin: false,
|
||||||
|
currentLevel: level,
|
||||||
|
defeatedEnemyCount: 4,
|
||||||
|
resourceStore: resourceStore);
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
|
new long[] { 90001, 90002 },
|
||||||
|
settlementContext.Result.Endurance.TargetTowerInstanceIds);
|
||||||
|
Assert.That(settlementContext.Result.Endurance.EnduranceLossPerComponent, Is.EqualTo(1f));
|
||||||
|
Assert.That(settlementContext.Flags.ShouldOpenRewardSelection, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CommitSettlementInventory_Reduces_Only_CombatStart_Participant_Towers()
|
||||||
|
{
|
||||||
|
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||||
|
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||||
|
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||||
|
resourceStore.InitializeForCombat(level);
|
||||||
|
|
||||||
|
BackpackInventoryData changedInventory = inventoryComponent.GetInventorySnapshot();
|
||||||
|
changedInventory.ParticipantTowerInstanceIds.Clear();
|
||||||
|
changedInventory.ParticipantTowerInstanceIds.Add(90003);
|
||||||
|
inventoryComponent.ReplaceInventorySnapshot(changedInventory);
|
||||||
|
|
||||||
|
CombatSettlementService settlementService = new CombatSettlementService();
|
||||||
|
CombatSettlementContext settlementContext = settlementService.BuildSettlementContext(
|
||||||
|
didCombatWin: false,
|
||||||
|
currentLevel: level,
|
||||||
|
defeatedEnemyCount: 2,
|
||||||
|
resourceStore: resourceStore);
|
||||||
|
|
||||||
|
settlementService.CommitSettlementInventory(settlementContext);
|
||||||
|
|
||||||
|
BackpackInventoryData committedInventory = inventoryComponent.GetInventorySnapshot();
|
||||||
|
Assert.That(GetTowerComponents(committedInventory, 90001).Muzzle.Endurance, Is.EqualTo(99f));
|
||||||
|
Assert.That(GetTowerComponents(committedInventory, 90002).Muzzle.Endurance, Is.EqualTo(99f));
|
||||||
|
Assert.That(GetTowerComponents(committedInventory, 90003).Muzzle.Endurance, Is.EqualTo(100f));
|
||||||
|
Assert.That(settlementContext.Result.Endurance.AffectedTowerCount, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CommitSettlementInventory_Reduces_Endurance_To_Zero_And_Next_Validation_Fails()
|
||||||
|
{
|
||||||
|
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateInventory(1f, 100f, 100f));
|
||||||
|
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||||
|
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||||
|
resourceStore.InitializeForCombat(level);
|
||||||
|
|
||||||
|
CombatSettlementService settlementService = new CombatSettlementService();
|
||||||
|
CombatSettlementContext settlementContext = settlementService.BuildSettlementContext(
|
||||||
|
didCombatWin: true,
|
||||||
|
currentLevel: level,
|
||||||
|
defeatedEnemyCount: 6,
|
||||||
|
resourceStore: resourceStore);
|
||||||
|
|
||||||
|
settlementService.CommitSettlementInventory(settlementContext);
|
||||||
|
|
||||||
|
BackpackInventoryData committedInventory = inventoryComponent.GetInventorySnapshot();
|
||||||
|
CombatParticipantTowerValidationSummary summary =
|
||||||
|
CombatParticipantTowerValidationService.ValidateParticipantTowers(committedInventory);
|
||||||
|
|
||||||
|
Assert.That(GetTowerComponents(committedInventory, 90001).Muzzle.Endurance, Is.EqualTo(0f));
|
||||||
|
Assert.That(summary.HasAnyValidParticipantTower, Is.True);
|
||||||
|
Assert.That(summary.InvalidResults.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(
|
||||||
|
summary.InvalidResults[0].FailureReason,
|
||||||
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerInventoryComponent CreateBoundPlayerInventory(BackpackInventoryData inventory)
|
||||||
|
{
|
||||||
|
_originalPlayerInventory = GameEntry.PlayerInventory;
|
||||||
|
_inventoryObject = new GameObject("TestPlayerInventory");
|
||||||
|
PlayerInventoryComponent inventoryComponent = _inventoryObject.AddComponent<PlayerInventoryComponent>();
|
||||||
|
SetStaticPlayerInventory(inventoryComponent);
|
||||||
|
inventoryComponent.ReplaceInventorySnapshot(inventory);
|
||||||
|
return inventoryComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetStaticPlayerInventory(PlayerInventoryComponent inventoryComponent)
|
||||||
|
{
|
||||||
|
FieldInfo backingField = typeof(GameEntry).GetField(
|
||||||
|
"<PlayerInventory>k__BackingField",
|
||||||
|
BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
Assert.That(backingField, Is.Not.Null);
|
||||||
|
backingField.SetValue(null, inventoryComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DRLevel CreateLevel(int baseHp, int startCoin, int rewardGold)
|
||||||
|
{
|
||||||
|
DRLevel level = new DRLevel();
|
||||||
|
bool parsed = level.ParseDataRow(
|
||||||
|
$"\t1\t测试关卡\tPlain\t{baseHp}\t{startCoin}\tWaveCount\t10\t{rewardGold}",
|
||||||
|
null);
|
||||||
|
Assert.That(parsed, Is.True);
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BackpackInventoryData CreateInventory(
|
||||||
|
float firstTowerEndurance,
|
||||||
|
float secondTowerEndurance,
|
||||||
|
float thirdTowerEndurance)
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = new BackpackInventoryData();
|
||||||
|
AddTower(inventory, 90001, 10001, 20001, 30001, firstTowerEndurance, isParticipant: true);
|
||||||
|
AddTower(inventory, 90002, 10002, 20002, 30002, secondTowerEndurance, isParticipant: true);
|
||||||
|
AddTower(inventory, 90003, 10003, 20003, 30003, thirdTowerEndurance, isParticipant: false);
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddTower(
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
long towerId,
|
||||||
|
long muzzleId,
|
||||||
|
long bearingId,
|
||||||
|
long baseId,
|
||||||
|
float endurance,
|
||||||
|
bool isParticipant)
|
||||||
|
{
|
||||||
|
inventory.MuzzleComponents.Add(new MuzzleCompItemData { InstanceId = muzzleId, Endurance = endurance });
|
||||||
|
inventory.BearingComponents.Add(new BearingCompItemData { InstanceId = bearingId, Endurance = endurance });
|
||||||
|
inventory.BaseComponents.Add(new BaseCompItemData { InstanceId = baseId, Endurance = endurance });
|
||||||
|
inventory.Towers.Add(new TowerItemData
|
||||||
|
{
|
||||||
|
InstanceId = towerId,
|
||||||
|
MuzzleComponentInstanceId = muzzleId,
|
||||||
|
BearingComponentInstanceId = bearingId,
|
||||||
|
BaseComponentInstanceId = baseId,
|
||||||
|
IsParticipatingInCombat = isParticipant,
|
||||||
|
Stats = new TowerStatsData()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isParticipant && inventory.ParticipantTowerInstanceIds.Count < MaxParticipantTowerCount)
|
||||||
|
{
|
||||||
|
inventory.ParticipantTowerInstanceIds.Add(towerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (MuzzleCompItemData Muzzle, BearingCompItemData Bearing, BaseCompItemData Base) GetTowerComponents(
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
long towerInstanceId)
|
||||||
|
{
|
||||||
|
TowerItemData tower = inventory.Towers.Find(item => item.InstanceId == towerInstanceId);
|
||||||
|
Assert.That(tower, Is.Not.Null);
|
||||||
|
MuzzleCompItemData muzzle = inventory.MuzzleComponents.Find(item => item.InstanceId == tower.MuzzleComponentInstanceId);
|
||||||
|
BearingCompItemData bearing = inventory.BearingComponents.Find(item => item.InstanceId == tower.BearingComponentInstanceId);
|
||||||
|
BaseCompItemData baseComp = inventory.BaseComponents.Find(item => item.InstanceId == tower.BaseComponentInstanceId);
|
||||||
|
Assert.That(muzzle, Is.Not.Null);
|
||||||
|
Assert.That(bearing, Is.Not.Null);
|
||||||
|
Assert.That(baseComp, Is.Not.Null);
|
||||||
|
return (muzzle, bearing, baseComp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7a9e6c3fb1d84d6db2455d4c13f0e2b9
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -51,6 +51,21 @@ namespace GeometryTD.Tests.EditMode
|
||||||
Assert.That(inventory.ParticipantTowerInstanceIds.Count, Is.EqualTo(0));
|
Assert.That(inventory.ParticipantTowerInstanceIds.Count, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TryAddParticipantTower_Returns_InvalidTower_With_Broken_Component_Reason()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateValidInventory();
|
||||||
|
inventory.MuzzleComponents[0].Endurance = 0f;
|
||||||
|
|
||||||
|
ParticipantTowerAssignResult result = InventoryParticipantUtility.TryAddParticipantTower(inventory, 90001, 4);
|
||||||
|
|
||||||
|
Assert.That(result.IsSuccess, Is.False);
|
||||||
|
Assert.That(result.FailureReason, Is.EqualTo(ParticipantTowerAssignFailureReason.InvalidTower));
|
||||||
|
Assert.That(result.ValidationFailureReason,
|
||||||
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent));
|
||||||
|
Assert.That(inventory.ParticipantTowerInstanceIds.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TryAddParticipantTower_Returns_AlreadyAssigned_Without_Duplicating_List()
|
public void TryAddParticipantTower_Returns_AlreadyAssigned_Without_Duplicating_List()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
using GeometryTD.CustomComponent;
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GeometryTD.Tests.EditMode
|
||||||
|
{
|
||||||
|
public sealed class PlayerInventoryTowerRosterServiceTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void ReduceTowerEndurance_Reduces_Only_Target_Towers_And_Deduplicates_Ids()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateInventory(100f, 100f, 100f);
|
||||||
|
PlayerInventoryTowerRosterService service = CreateService(inventory, out PlayerInventoryQueryModel queryModel);
|
||||||
|
|
||||||
|
int affectedTowerCount = service.ReduceTowerEndurance(new long[] { 90001, 90001, 90002 }, 1f);
|
||||||
|
|
||||||
|
Assert.That(affectedTowerCount, Is.EqualTo(2));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90001).Muzzle.Endurance, Is.EqualTo(99f));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90001).Bearing.Endurance, Is.EqualTo(99f));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90001).Base.Endurance, Is.EqualTo(99f));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90002).Muzzle.Endurance, Is.EqualTo(99f));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90003).Muzzle.Endurance, Is.EqualTo(100f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReduceTowerEndurance_Clamps_Component_Endurance_To_Zero()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateInventory(1f, 1f, 1f);
|
||||||
|
PlayerInventoryTowerRosterService service = CreateService(inventory, out PlayerInventoryQueryModel queryModel);
|
||||||
|
|
||||||
|
int affectedTowerCount = service.ReduceTowerEndurance(new long[] { 90001 }, 5f);
|
||||||
|
|
||||||
|
Assert.That(affectedTowerCount, Is.EqualTo(1));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90001).Muzzle.Endurance, Is.EqualTo(0f));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90001).Bearing.Endurance, Is.EqualTo(0f));
|
||||||
|
Assert.That(GetTowerComponents(queryModel.Inventory, 90001).Base.Endurance, Is.EqualTo(0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlayerInventoryTowerRosterService CreateService(
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
out PlayerInventoryQueryModel queryModel)
|
||||||
|
{
|
||||||
|
PlayerInventoryState state = new PlayerInventoryState();
|
||||||
|
queryModel = new PlayerInventoryQueryModel(state);
|
||||||
|
PlayerInventoryCommandModel commandModel = new PlayerInventoryCommandModel(state);
|
||||||
|
commandModel.Initialize(inventory, 4);
|
||||||
|
return new PlayerInventoryTowerRosterService(queryModel, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BackpackInventoryData CreateInventory(
|
||||||
|
float firstTowerEndurance,
|
||||||
|
float secondTowerEndurance,
|
||||||
|
float thirdTowerEndurance)
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = new BackpackInventoryData();
|
||||||
|
AddTower(inventory, 90001, 10001, 20001, 30001, firstTowerEndurance);
|
||||||
|
AddTower(inventory, 90002, 10002, 20002, 30002, secondTowerEndurance);
|
||||||
|
AddTower(inventory, 90003, 10003, 20003, 30003, thirdTowerEndurance);
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddTower(
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
long towerId,
|
||||||
|
long muzzleId,
|
||||||
|
long bearingId,
|
||||||
|
long baseId,
|
||||||
|
float endurance)
|
||||||
|
{
|
||||||
|
inventory.MuzzleComponents.Add(new MuzzleCompItemData { InstanceId = muzzleId, Endurance = endurance });
|
||||||
|
inventory.BearingComponents.Add(new BearingCompItemData { InstanceId = bearingId, Endurance = endurance });
|
||||||
|
inventory.BaseComponents.Add(new BaseCompItemData { InstanceId = baseId, Endurance = endurance });
|
||||||
|
inventory.Towers.Add(new TowerItemData
|
||||||
|
{
|
||||||
|
InstanceId = towerId,
|
||||||
|
MuzzleComponentInstanceId = muzzleId,
|
||||||
|
BearingComponentInstanceId = bearingId,
|
||||||
|
BaseComponentInstanceId = baseId,
|
||||||
|
Stats = new TowerStatsData()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (MuzzleCompItemData Muzzle, BearingCompItemData Bearing, BaseCompItemData Base) GetTowerComponents(
|
||||||
|
BackpackInventoryData inventory,
|
||||||
|
long towerInstanceId)
|
||||||
|
{
|
||||||
|
Assert.That(InventoryParticipantUtility.TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower), Is.True);
|
||||||
|
MuzzleCompItemData muzzle = inventory.MuzzleComponents.Find(item => item.InstanceId == tower.MuzzleComponentInstanceId);
|
||||||
|
BearingCompItemData bearing = inventory.BearingComponents.Find(item => item.InstanceId == tower.BearingComponentInstanceId);
|
||||||
|
BaseCompItemData baseComp = inventory.BaseComponents.Find(item => item.InstanceId == tower.BaseComponentInstanceId);
|
||||||
|
Assert.That(muzzle, Is.Not.Null);
|
||||||
|
Assert.That(bearing, Is.Not.Null);
|
||||||
|
Assert.That(baseComp, Is.Not.Null);
|
||||||
|
return (muzzle, bearing, baseComp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d3f4e9c1b7a24a1f8e0a9db7c2e5416b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -217,6 +217,24 @@ namespace GeometryTD.Tests.EditMode
|
||||||
Is.EqualTo(CombatParticipantTowerValidationFailureReason.MissingBaseComponent));
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.MissingBaseComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ValidateCombatEntry_Returns_NoValidParticipantTower_When_AllParticipantTowers_Are_Broken()
|
||||||
|
{
|
||||||
|
BackpackInventoryData inventory = CreateCombatInventory();
|
||||||
|
inventory.MuzzleComponents[0].Endurance = 0f;
|
||||||
|
|
||||||
|
ProcedureMainCombatEntryValidationResult result =
|
||||||
|
ProcedureMainCombatEntryValidationService.Validate(inventory);
|
||||||
|
|
||||||
|
Assert.That(result.CanEnterCombat, Is.False);
|
||||||
|
Assert.That(result.BlockReason, Is.EqualTo(ProcedureMainCombatEntryBlockReason.NoValidParticipantTower));
|
||||||
|
Assert.That(result.ValidationSummary.ValidTowers.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(result.ValidationSummary.InvalidResults.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(
|
||||||
|
result.ValidationSummary.InvalidResults[0].FailureReason,
|
||||||
|
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ValidateCombatEntry_Returns_CanEnter_When_At_Least_One_ParticipantTower_Is_Valid()
|
public void ValidateCombatEntry_Returns_CanEnter_When_At_Least_One_ParticipantTower_Is_Valid()
|
||||||
{
|
{
|
||||||
|
|
@ -336,6 +354,36 @@ namespace GeometryTD.Tests.EditMode
|
||||||
Is.EqualTo("参战区没有可出战的完整塔。\n塔 #90002 缺少底座组件。\n塔 #90003 缺少枪口组件。"));
|
Is.EqualTo("参战区没有可出战的完整塔。\n塔 #90002 缺少底座组件。\n塔 #90003 缺少枪口组件。"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BuildBlockedCombatDialogRawData_Lists_Broken_Component_Reasons()
|
||||||
|
{
|
||||||
|
DialogFormRawData rawData = ProcedureMainCombatEntryValidationService.BuildBlockedCombatDialogRawData(
|
||||||
|
new ProcedureMainCombatEntryValidationResult
|
||||||
|
{
|
||||||
|
BlockReason = ProcedureMainCombatEntryBlockReason.NoValidParticipantTower,
|
||||||
|
ValidationSummary = new CombatParticipantTowerValidationSummary
|
||||||
|
{
|
||||||
|
InvalidResults = new[]
|
||||||
|
{
|
||||||
|
new CombatParticipantTowerValidationResult
|
||||||
|
{
|
||||||
|
TowerInstanceId = 90002,
|
||||||
|
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenBaseComponent
|
||||||
|
},
|
||||||
|
new CombatParticipantTowerValidationResult
|
||||||
|
{
|
||||||
|
TowerInstanceId = 90003,
|
||||||
|
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
rawData.Message,
|
||||||
|
Is.EqualTo("参战区没有可出战的完整塔。\n塔 #90002 底座组件耐久为 0,无法参战。\n塔 #90003 枪口组件耐久为 0,无法参战。"));
|
||||||
|
}
|
||||||
|
|
||||||
private static RunState CreateTwoNodeRun()
|
private static RunState CreateTwoNodeRun()
|
||||||
{
|
{
|
||||||
return RunStateFactory.Create(
|
return RunStateFactory.Create(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue