using NeonSprawl.Server.Game.Skills; namespace NeonSprawl.Server.Game.Mastery; /// Applies dev-only mastery fixture resets (NEO-48). public static class MasteryFixtureOperations { /// Validates entries; returns false when any key is blank or XP is negative. public static bool TryNormalizeSkillXp( IReadOnlyDictionary? skillXp, out Dictionary normalized) { normalized = new Dictionary(StringComparer.Ordinal); if (skillXp is null || skillXp.Count == 0) { return true; } foreach (var (skillId, xp) in skillXp) { var sid = skillId.Trim(); if (sid.Length == 0 || xp < 0) { normalized.Clear(); return false; } normalized[sid] = xp; } return true; } /// Applies skill XP (batch, then perk reset). Rolls back XP when perk reset fails after XP writes. public static bool TryApply( string playerId, MasteryFixtureRequest body, IPlayerPerkStateStore perkStore, IPlayerSkillProgressionStore xpStore) { if (!TryNormalizeSkillXp(body.SkillXp, out var skillXp)) { return false; } var previousXp = new Dictionary(xpStore.GetXpTotals(playerId), StringComparer.Ordinal); if (skillXp.Count > 0 && !xpStore.TrySetSkillXpTotals(playerId, skillXp)) { return false; } if (!body.ResetPerkState) { return true; } if (perkStore.TryResetPerkState(playerId)) { return true; } if (skillXp.Count > 0) { _ = xpStore.TrySetSkillXpTotals(playerId, previousXp); } return false; } }