S5-01 + S5-02 + S5-03

This commit is contained in:
2026-03-11 15:36:18 +08:00
parent c019f9f527
commit 113decb414
18 changed files with 570 additions and 57 deletions
@@ -7,7 +7,7 @@ using UnityEngine;
namespace GeometryTD.CustomComponent
{
internal sealed class CombatRunResourceStore
public sealed class CombatRunResourceStore
{
private readonly List<TowerStatsData> _buildTowerStatsSnapshot = new();
private readonly List<TowerItemData> _participantTowerSnapshot = new();
@@ -88,6 +88,21 @@ namespace GeometryTD.CustomComponent
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)
{
int requiredCoin = Mathf.Max(0, coin);
@@ -1,22 +1,23 @@
using System.Collections.Generic;
using GeometryTD.Definition;
namespace GeometryTD.CustomComponent
{
internal sealed class CombatSettlementContext
public sealed class CombatSettlementContext
{
public CombatSettlementFlags Flags { get; } = new();
public CombatSettlementResult Result { get; } = new();
public CombatSettlementSummary Summary { get; } = new();
}
internal sealed class CombatSettlementFlags
public sealed class CombatSettlementFlags
{
public bool ShouldOpenRewardSelection;
public bool DidEnterRewardSelection;
public bool IsCommitted;
}
internal sealed class CombatSettlementResult
public sealed class CombatSettlementResult
{
public bool DidCombatWin;
public int FinalCoin;
@@ -25,17 +26,17 @@ namespace GeometryTD.CustomComponent
public int DefeatedEnemyCount;
public int GainedGold;
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 float LowBaseHpEndurancePenaltyValue;
public List<long> TargetTowerInstanceIds { get; } = new();
public float EnduranceLossPerComponent;
public int AffectedTowerCount;
}
internal sealed class CombatSettlementSummary
public sealed class CombatSettlementSummary
{
public int DefeatedEnemyCount;
public int GainedGold;
@@ -8,14 +8,13 @@ using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
internal sealed class CombatSettlementService
public sealed class CombatSettlementService
{
private const int RewardSelectDisplayCount = 3;
private const float FullBaseHpGoldBonusRate = 0.3f;
private const float HighBaseHpGoldBonusRate = 0.1f;
private const float HighBaseHpThreshold = 0.8f;
private const float MidBaseHpThreshold = 0.5f;
private const float LowBaseHpTowerEndurancePenalty = 10f;
private const float SettlementTowerEnduranceLoss = 1f;
public CombatSettlementContext BuildSettlementContext(
bool didCombatWin,
@@ -33,7 +32,6 @@ namespace GeometryTD.CustomComponent
out int levelRewardGold,
out float bonusRate,
out int bonusGold,
out bool appliedLowBaseHpPenalty,
out shouldOpenFullBaseHpRewardSelect);
CombatSettlementContext settlementContext = new CombatSettlementContext
@@ -48,9 +46,7 @@ namespace GeometryTD.CustomComponent
settlementContext.Result.RewardInventory = resourceStore != null
? resourceStore.GetRewardInventorySnapshot()
: new BackpackInventoryData();
settlementContext.Result.Penalty.ShouldApplyLowBaseHpPenalty = appliedLowBaseHpPenalty;
settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue =
appliedLowBaseHpPenalty ? LowBaseHpTowerEndurancePenalty : 0f;
PopulateEnduranceSettlement(settlementContext, resourceStore);
settlementContext.Flags.ShouldOpenRewardSelection = shouldOpenFullBaseHpRewardSelect;
settlementContext.Flags.DidEnterRewardSelection = false;
settlementContext.Summary.DefeatedEnemyCount = settlementContext.Result.DefeatedEnemyCount;
@@ -58,7 +54,7 @@ namespace GeometryTD.CustomComponent
settlementContext.Summary.RewardInventory = settlementContext.Result.RewardInventory;
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,
currentBaseHp,
maxBaseHp,
@@ -66,7 +62,7 @@ namespace GeometryTD.CustomComponent
bonusRate,
bonusGold,
shouldOpenFullBaseHpRewardSelect,
appliedLowBaseHpPenalty);
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count);
return settlementContext;
}
@@ -81,7 +77,7 @@ namespace GeometryTD.CustomComponent
GameEntry.PlayerInventory?.MergeInventory(rewardInventory);
settlementContext.Result.RewardInventory = rewardInventory;
settlementContext.Summary.RewardInventory = rewardInventory;
settlementContext.Result.Penalty.AffectedTowerCount = ApplyDeferredSettlementPenalty(settlementContext);
settlementContext.Result.Endurance.AffectedTowerCount = ApplyDeferredSettlementEndurance(settlementContext);
settlementContext.Flags.IsCommitted = true;
}
@@ -181,7 +177,6 @@ namespace GeometryTD.CustomComponent
out int levelRewardGold,
out float bonusRate,
out int bonusGold,
out bool appliedLowBaseHpPenalty,
out bool shouldOpenFullBaseHpRewardSelect)
{
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;
bonusRate = 0f;
bonusGold = 0;
appliedLowBaseHpPenalty = false;
shouldOpenFullBaseHpRewardSelect = false;
if (!didCombatWin || resourceStore == null)
@@ -214,10 +208,6 @@ namespace GeometryTD.CustomComponent
{
bonusRate = HighBaseHpGoldBonusRate;
}
else if (hpRate < MidBaseHpThreshold)
{
appliedLowBaseHpPenalty = true;
}
}
int goldForBonusCalculation = Mathf.Max(0, resourceStore.GainedGold) + levelRewardGold;
@@ -226,11 +216,40 @@ namespace GeometryTD.CustomComponent
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 ||
!settlementContext.Result.Penalty.ShouldApplyLowBaseHpPenalty ||
settlementContext.Result.Penalty.LowBaseHpEndurancePenaltyValue <= 0f)
settlementContext.Result.Endurance.EnduranceLossPerComponent <= 0f ||
settlementContext.Result.Endurance.TargetTowerInstanceIds.Count <= 0)
{
return 0;
}
@@ -241,7 +260,9 @@ namespace GeometryTD.CustomComponent
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)
@@ -3,7 +3,7 @@ using GeometryTD.Definition;
namespace GeometryTD.CustomComponent
{
internal readonly struct EnemyDropContext
public readonly struct EnemyDropContext
{
public EnemyDropContext(DREnemy enemy, int displayPhaseIndex, LevelThemeType themeType)
{
@@ -8,7 +8,7 @@ using Random = UnityEngine.Random;
namespace GeometryTD.CustomComponent
{
internal sealed class EnemyDropResolver
public sealed class EnemyDropResolver
{
private const float DropChanceBase = 0.05f;
private const float DropChancePerPhase = 0.2f;
@@ -2,7 +2,7 @@ using GeometryTD.Definition;
namespace GeometryTD.CustomComponent
{
internal readonly struct EnemyDropResult
public readonly struct EnemyDropResult
{
public static EnemyDropResult Empty => new(0, 0, null);
@@ -132,10 +132,10 @@ namespace GeometryTD.CustomComponent
out assembledTower);
}
public int ReduceAllTowerEndurance(float enduranceLoss)
public int ReduceTowerEndurance(IReadOnlyList<long> towerInstanceIds, float enduranceLoss)
{
EnsureInitialized();
return _towerRosterService.ReduceAllTowerEndurance(enduranceLoss);
return _towerRosterService.ReduceTowerEndurance(towerInstanceIds, enduranceLoss);
}
private void EnsureInitialized()
@@ -158,4 +158,3 @@ namespace GeometryTD.CustomComponent
}
}
}
@@ -6,7 +6,7 @@ using UnityEngine;
namespace GeometryTD.CustomComponent
{
internal struct PlayerInventoryMergeSummary
public struct PlayerInventoryMergeSummary
{
public int GainedGold;
public int GainedMuzzleCount;
@@ -19,14 +19,14 @@ namespace GeometryTD.CustomComponent
GainedTowerCount > 0;
}
internal sealed class PlayerInventoryState
public sealed class PlayerInventoryState
{
public BackpackInventoryData Inventory = new BackpackInventoryData();
public long NextInstanceId = 1;
public bool IsInitialized;
}
internal sealed class PlayerInventoryQueryModel
public sealed class PlayerInventoryQueryModel
{
private readonly PlayerInventoryState _state;
@@ -72,7 +72,7 @@ namespace GeometryTD.CustomComponent
}
}
internal sealed class PlayerInventoryCommandModel
public sealed class PlayerInventoryCommandModel
{
private readonly PlayerInventoryState _state;
@@ -5,7 +5,7 @@ using UnityEngine;
namespace GeometryTD.CustomComponent
{
internal sealed class PlayerInventoryTowerRosterService
public sealed class PlayerInventoryTowerRosterService
{
private readonly PlayerInventoryQueryModel _queryModel;
private readonly int _maxParticipantTowerCount;
@@ -34,11 +34,15 @@ namespace GeometryTD.CustomComponent
_maxParticipantTowerCount);
}
public int ReduceAllTowerEndurance(float enduranceLoss)
public int ReduceTowerEndurance(IReadOnlyList<long> towerInstanceIds, float enduranceLoss)
{
float resolvedLoss = Mathf.Max(0f, enduranceLoss);
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;
}
@@ -46,12 +50,19 @@ namespace GeometryTD.CustomComponent
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventory.MuzzleComponents);
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventory.BearingComponents);
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventory.BaseComponents);
HashSet<long> processedTowerIds = new HashSet<long>();
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];
if (tower == null)
long towerInstanceId = towerInstanceIds[i];
if (towerInstanceId <= 0 || !processedTowerIds.Add(towerInstanceId))
{
continue;
}
if (!InventoryParticipantUtility.TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower) ||
tower == null)
{
continue;
}