S3-01 + S3-02

This commit is contained in:
2026-03-09 17:50:49 +08:00
parent 793a87c171
commit 88641f17b0
19 changed files with 838 additions and 46 deletions
@@ -106,7 +106,7 @@ namespace GeometryTD.CustomComponent
_commandModel.AddGold(gainGold);
}
public bool TryAddParticipantTower(long towerInstanceId, int maxCount = 4)
public ParticipantTowerAssignResult TryAddParticipantTower(long towerInstanceId, int maxCount = 4)
{
EnsureInitialized();
return _towerRosterService.TryAddParticipantTower(towerInstanceId, maxCount);
@@ -16,7 +16,7 @@ namespace GeometryTD.CustomComponent
_maxParticipantTowerCount = Mathf.Max(1, maxParticipantTowerCount);
}
public bool TryAddParticipantTower(long towerInstanceId, int maxCount)
public ParticipantTowerAssignResult TryAddParticipantTower(long towerInstanceId, int maxCount)
{
int resolvedMaxCount = Mathf.Max(1, maxCount);
resolvedMaxCount = Mathf.Min(resolvedMaxCount, _maxParticipantTowerCount);
@@ -0,0 +1,188 @@
using System.Collections.Generic;
namespace GeometryTD.Definition
{
public enum CombatParticipantTowerValidationFailureReason
{
None = 0,
TowerMissing = 1,
MissingMuzzleComponent = 2,
MissingBearingComponent = 3,
MissingBaseComponent = 4
}
public sealed class CombatParticipantTowerValidationResult
{
public long TowerInstanceId { get; set; }
public bool IsValid => FailureReason == CombatParticipantTowerValidationFailureReason.None;
public CombatParticipantTowerValidationFailureReason FailureReason { get; set; }
public TowerItemData Tower { get; set; }
}
public sealed class CombatParticipantTowerValidationSummary
{
public IReadOnlyList<TowerItemData> ValidTowers { get; set; } = System.Array.Empty<TowerItemData>();
public IReadOnlyList<CombatParticipantTowerValidationResult> InvalidResults { get; set; } =
System.Array.Empty<CombatParticipantTowerValidationResult>();
public bool HasAnyValidParticipantTower => ValidTowers != null && ValidTowers.Count > 0;
}
public static class CombatParticipantTowerValidationService
{
public static CombatParticipantTowerValidationResult ValidateTower(
BackpackInventoryData inventory,
long towerInstanceId)
{
if (!TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower))
{
return new CombatParticipantTowerValidationResult
{
TowerInstanceId = towerInstanceId,
FailureReason = CombatParticipantTowerValidationFailureReason.TowerMissing
};
}
return ValidateTower(inventory, tower);
}
public static CombatParticipantTowerValidationResult ValidateTower(
BackpackInventoryData inventory,
TowerItemData tower)
{
if (tower == null || tower.InstanceId <= 0)
{
return new CombatParticipantTowerValidationResult
{
TowerInstanceId = tower != null ? tower.InstanceId : 0,
Tower = tower,
FailureReason = CombatParticipantTowerValidationFailureReason.TowerMissing
};
}
if (!HasComponent(inventory?.MuzzleComponents, tower.MuzzleComponentInstanceId))
{
return new CombatParticipantTowerValidationResult
{
TowerInstanceId = tower.InstanceId,
Tower = tower,
FailureReason = CombatParticipantTowerValidationFailureReason.MissingMuzzleComponent
};
}
if (!HasComponent(inventory?.BearingComponents, tower.BearingComponentInstanceId))
{
return new CombatParticipantTowerValidationResult
{
TowerInstanceId = tower.InstanceId,
Tower = tower,
FailureReason = CombatParticipantTowerValidationFailureReason.MissingBearingComponent
};
}
if (!HasComponent(inventory?.BaseComponents, tower.BaseComponentInstanceId))
{
return new CombatParticipantTowerValidationResult
{
TowerInstanceId = tower.InstanceId,
Tower = tower,
FailureReason = CombatParticipantTowerValidationFailureReason.MissingBaseComponent
};
}
return new CombatParticipantTowerValidationResult
{
TowerInstanceId = tower.InstanceId,
Tower = tower,
FailureReason = CombatParticipantTowerValidationFailureReason.None
};
}
public static CombatParticipantTowerValidationSummary ValidateParticipantTowers(BackpackInventoryData inventory)
{
List<TowerItemData> validTowers = new List<TowerItemData>();
List<CombatParticipantTowerValidationResult> invalidResults =
new List<CombatParticipantTowerValidationResult>();
HashSet<long> processedTowerIds = new HashSet<long>();
if (inventory?.ParticipantTowerInstanceIds == null || inventory.ParticipantTowerInstanceIds.Count <= 0)
{
return new CombatParticipantTowerValidationSummary
{
ValidTowers = validTowers,
InvalidResults = invalidResults
};
}
for (int i = 0; i < inventory.ParticipantTowerInstanceIds.Count; i++)
{
long towerInstanceId = inventory.ParticipantTowerInstanceIds[i];
if (towerInstanceId <= 0 || !processedTowerIds.Add(towerInstanceId))
{
continue;
}
CombatParticipantTowerValidationResult result = ValidateTower(inventory, towerInstanceId);
if (result.IsValid)
{
validTowers.Add(result.Tower);
}
else
{
invalidResults.Add(result);
}
}
return new CombatParticipantTowerValidationSummary
{
ValidTowers = validTowers,
InvalidResults = invalidResults
};
}
private static bool TryGetTowerById(BackpackInventoryData inventory, long towerInstanceId, out TowerItemData tower)
{
tower = null;
if (inventory?.Towers == null || towerInstanceId <= 0)
{
return false;
}
for (int i = 0; i < inventory.Towers.Count; i++)
{
TowerItemData candidate = inventory.Towers[i];
if (candidate != null && candidate.InstanceId == towerInstanceId)
{
tower = candidate;
return true;
}
}
return false;
}
private static bool HasComponent<TComponent>(IReadOnlyList<TComponent> components, long componentInstanceId)
where TComponent : TowerCompItemData
{
if (components == null || componentInstanceId <= 0)
{
return false;
}
for (int i = 0; i < components.Count; i++)
{
TComponent component = components[i];
if (component != null && component.InstanceId == componentInstanceId)
{
return true;
}
}
return false;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a8f51ad44094d59af6618736cd6a0d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
namespace GeometryTD.Definition
{
public enum ParticipantTowerAssignFailureReason
{
None = 0,
TowerMissing = 1,
InvalidTower = 2,
AlreadyAssigned = 3,
ParticipantAreaFull = 4
}
public sealed class ParticipantTowerAssignResult
{
public long TowerInstanceId { get; set; }
public bool IsSuccess => FailureReason == ParticipantTowerAssignFailureReason.None;
public ParticipantTowerAssignFailureReason FailureReason { get; set; }
public CombatParticipantTowerValidationFailureReason ValidationFailureReason { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00c667d942b94e23a4933fda5c5eaf8e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -75,6 +75,26 @@ namespace GeometryTD.Procedure
}
}
public static class ProcedureMainNodeEventGuardService
{
public static bool MatchesCurrentNode(
RunState runState,
int nodeId,
RunNodeType nodeType,
int sequenceIndex)
{
RunNodeState currentNode = runState?.CurrentNode;
if (currentNode == null)
{
return false;
}
return (nodeId <= 0 || nodeId == currentNode.NodeId) &&
(nodeType == RunNodeType.None || nodeType == currentNode.NodeType) &&
(sequenceIndex < 0 || sequenceIndex == currentNode.SequenceIndex);
}
}
public class ProcedureMain : ProcedureBase
{
public override bool UseNativeDialog => false;
@@ -177,19 +197,21 @@ namespace GeometryTD.Procedure
}
RunNodeState currentNode = _currentRunState?.CurrentNode;
if (currentNode != null &&
((args.NodeId > 0 && args.NodeId != currentNode.NodeId) ||
(args.NodeType != RunNodeType.None && args.NodeType != currentNode.NodeType) ||
(args.SequenceIndex >= 0 && args.SequenceIndex != currentNode.SequenceIndex)))
if (!ProcedureMainNodeEventGuardService.MatchesCurrentNode(
_currentRunState,
args.NodeId,
args.NodeType,
args.SequenceIndex))
{
Log.Warning(
"ProcedureMain.OnNodeEnter() node mismatch. EventNodeId={0}, EventNodeType={1}, EventSequenceIndex={2}; CurrentNodeId={3}, CurrentNodeType={4}, CurrentSequenceIndex={5}.",
args.NodeId,
args.NodeType,
args.SequenceIndex,
currentNode.NodeId,
currentNode.NodeType,
currentNode.SequenceIndex);
currentNode?.NodeId ?? 0,
currentNode?.NodeType ?? RunNodeType.None,
currentNode?.SequenceIndex ?? -1);
return;
}
Log.Info(
@@ -220,6 +242,24 @@ namespace GeometryTD.Procedure
return;
}
RunNodeState currentNode = _currentRunState?.CurrentNode;
if (!ProcedureMainNodeEventGuardService.MatchesCurrentNode(
_currentRunState,
args.NodeId,
args.NodeType,
args.SequenceIndex))
{
Log.Warning(
"ProcedureMain.OnNodeComplete() node mismatch. EventNodeId={0}, EventNodeType={1}, EventSequenceIndex={2}; CurrentNodeId={3}, CurrentNodeType={4}, CurrentSequenceIndex={5}.",
args.NodeId,
args.NodeType,
args.SequenceIndex,
currentNode?.NodeId ?? 0,
currentNode?.NodeType ?? RunNodeType.None,
currentNode?.SequenceIndex ?? -1);
return;
}
Log.Info(
"ProcedureMain.OnNodeComplete() accepted. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}, CompletionStatus={4}, CombatWon={5}.",
string.IsNullOrWhiteSpace(args.RunId) ? _currentRunState?.RunId : args.RunId,
@@ -261,8 +261,18 @@ namespace GeometryTD.UI
return;
}
if (!_useCase.TryAddParticipantTower(args.TowerItemId))
ParticipantTowerAssignResult result = _useCase.TryAddParticipantTower(args.TowerItemId);
if (result == null || !result.IsSuccess)
{
if (result != null)
{
Log.Warning(
"RepoFormController denied participant assignment. TowerId={0}, FailureReason={1}, ValidationFailureReason={2}.",
result.TowerInstanceId,
result.FailureReason,
result.ValidationFailureReason);
}
return;
}
@@ -33,7 +33,7 @@ namespace GeometryTD.UI
out _);
}
public bool TryAddParticipantTower(long towerItemId)
public ParticipantTowerAssignResult TryAddParticipantTower(long towerItemId)
{
if (GameEntry.PlayerInventory == null)
{
@@ -6,35 +6,75 @@ namespace GeometryTD.CustomUtility
{
public static class InventoryParticipantUtility
{
public static bool TryAddParticipantTower(BackpackInventoryData inventory, long towerInstanceId, int maxCount)
public static ParticipantTowerAssignResult TryAddParticipantTower(
BackpackInventoryData inventory,
long towerInstanceId,
int maxCount)
{
if (inventory == null || towerInstanceId <= 0)
{
return false;
return new ParticipantTowerAssignResult
{
TowerInstanceId = towerInstanceId,
FailureReason = ParticipantTowerAssignFailureReason.TowerMissing,
ValidationFailureReason = CombatParticipantTowerValidationFailureReason.None
};
}
int resolvedMaxCount = Mathf.Max(1, maxCount);
NormalizeParticipantState(inventory, resolvedMaxCount);
if (!TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower))
CombatParticipantTowerValidationResult validationResult =
CombatParticipantTowerValidationService.ValidateTower(inventory, towerInstanceId);
if (!validationResult.IsValid)
{
return false;
return new ParticipantTowerAssignResult
{
TowerInstanceId = towerInstanceId,
FailureReason = validationResult.FailureReason == CombatParticipantTowerValidationFailureReason.TowerMissing
? ParticipantTowerAssignFailureReason.TowerMissing
: ParticipantTowerAssignFailureReason.InvalidTower,
ValidationFailureReason = validationResult.FailureReason
};
}
inventory.ParticipantTowerInstanceIds ??= new List<long>();
if (inventory.ParticipantTowerInstanceIds.Contains(towerInstanceId))
{
tower.IsParticipatingInCombat = true;
return false;
if (validationResult.Tower != null)
{
validationResult.Tower.IsParticipatingInCombat = true;
}
return new ParticipantTowerAssignResult
{
TowerInstanceId = towerInstanceId,
FailureReason = ParticipantTowerAssignFailureReason.AlreadyAssigned,
ValidationFailureReason = CombatParticipantTowerValidationFailureReason.None
};
}
if (inventory.ParticipantTowerInstanceIds.Count >= resolvedMaxCount)
{
return false;
return new ParticipantTowerAssignResult
{
TowerInstanceId = towerInstanceId,
FailureReason = ParticipantTowerAssignFailureReason.ParticipantAreaFull,
ValidationFailureReason = CombatParticipantTowerValidationFailureReason.None
};
}
inventory.ParticipantTowerInstanceIds.Add(towerInstanceId);
tower.IsParticipatingInCombat = true;
return true;
if (validationResult.Tower != null)
{
validationResult.Tower.IsParticipatingInCombat = true;
}
return new ParticipantTowerAssignResult
{
TowerInstanceId = towerInstanceId,
FailureReason = ParticipantTowerAssignFailureReason.None,
ValidationFailureReason = CombatParticipantTowerValidationFailureReason.None
};
}
public static bool TryRemoveParticipantTower(BackpackInventoryData inventory, long towerInstanceId, int maxCount)