using NeonSprawl.Server.Game.Quests; namespace NeonSprawl.Server.Game.Factions; /// /// Evaluates minimum-standing gates (NEO-137). /// Quest accept wiring: . /// NEO-141 telemetry hook site: deny return paths below. /// public static class FactionGateOperations { /// /// All rules must pass (AND). Empty rules succeed immediately. /// Compares player standing vs with standing >= minStanding. /// public static FactionGateEvaluateOutcome TryEvaluate( string playerId, IReadOnlyList rules, IFactionStandingStore standingStore) { var normalizedPlayerId = FactionStandingIds.NormalizePlayerId(playerId); if (rules.Count == 0) { return Success(); } foreach (var rule in rules) { var factionId = FactionStandingIds.NormalizeFactionId(rule.FactionId); var read = standingStore.TryGetStanding(normalizedPlayerId, factionId); if (!read.Success) { // --- Telemetry hook site (NEO-141): future E9.M1 catalog event `faction_gate_blocked` --- // TODO(E9.M1): catalog emit — ops reasonCode (e.g. unknown_faction), playerId, factionId, minStanding, currentStanding (null). // No ingest or ILogger here (comments-only). return Deny(read.ReasonCode, factionId, rule.MinStanding, null); } if (read.Standing < rule.MinStanding) { // --- Telemetry hook site (NEO-141): future E9.M1 catalog event `faction_gate_blocked` --- // TODO(E9.M1): catalog emit — ops reasonCode (gate_blocked), playerId, factionId, minStanding, currentStanding. // No ingest or ILogger here (comments-only). return Deny( FactionGateEvaluateReasonCodes.GateBlocked, factionId, rule.MinStanding, read.Standing); } } return Success(); } private static FactionGateEvaluateOutcome Success() => new(true, null, null, null, null); private static FactionGateEvaluateOutcome Deny( string? reasonCode, string factionId, int minStanding, int? currentStanding) => new(false, reasonCode, factionId, minStanding, currentStanding); }