neon-sprawl/server/NeonSprawl.Server/Game/Contracts/ContractEconomyValidation.cs

66 lines
2.5 KiB
C#

using NeonSprawl.Server.Game.Encounters;
using NeonSprawl.Server.Game.Factions;
using NeonSprawl.Server.Game.Items;
using NeonSprawl.Server.Game.Skills;
namespace NeonSprawl.Server.Game.Contracts;
/// <summary>
/// Issue-time economy and reward-bundle validation for contract templates (NEO-150).
/// Mirrors startup catalog gates for defense in depth at <see cref="ContractGeneratorOperations.TryIssue"/>.
/// </summary>
public static class ContractEconomyValidation
{
/// <summary>
/// Validates band caps and cross-refs for one template against live registries.
/// </summary>
public static bool TryValidateTemplate(
ContractTemplateRow template,
IEncounterDefinitionRegistry encounterRegistry,
IItemDefinitionRegistry itemRegistry,
ISkillDefinitionRegistry skillRegistry,
IFactionDefinitionRegistry factionRegistry,
out string? reasonCode)
{
ArgumentNullException.ThrowIfNull(template);
ArgumentNullException.ThrowIfNull(encounterRegistry);
ArgumentNullException.ThrowIfNull(itemRegistry);
ArgumentNullException.ThrowIfNull(skillRegistry);
ArgumentNullException.ThrowIfNull(factionRegistry);
reasonCode = null;
var rowsById = new Dictionary<string, ContractTemplateRow>(StringComparer.Ordinal)
{
[template.Id] = template,
};
// Band-cap before cross-ref so deny maps to economy_cap_exceeded (startup loader runs cross-ref first).
if (PrototypeE7M4ContractCatalogRules.TryGetBandCapGateError(rowsById) is not null)
{
reasonCode = ContractGeneratorReasonCodes.EconomyCapExceeded;
return false;
}
var knownEncounterIds = encounterRegistry.GetDefinitionsInIdOrder()
.Select(static d => d.Id)
.ToHashSet(StringComparer.Ordinal);
var knownFactionIds = factionRegistry.GetDefinitionsInIdOrder()
.Select(static d => d.Id)
.ToHashSet(StringComparer.Ordinal);
var skillDefsById = skillRegistry.GetDefinitionsInIdOrder()
.ToDictionary(static d => d.Id, StringComparer.Ordinal);
if (PrototypeE7M4ContractCatalogRules.TryGetCrossRefError(
rowsById,
knownEncounterIds,
knownFactionIds,
skillDefsById) is not null)
{
reasonCode = ContractGeneratorReasonCodes.InvalidRewardBundle;
return false;
}
return true;
}
}