71 lines
1.9 KiB
C#
71 lines
1.9 KiB
C#
using NeonSprawl.Server.Game.Skills;
|
|
|
|
namespace NeonSprawl.Server.Game.Mastery;
|
|
|
|
/// <summary>Applies dev-only mastery fixture resets (NEO-48).</summary>
|
|
public static class MasteryFixtureOperations
|
|
{
|
|
/// <summary>Validates <paramref name="skillXp"/> entries; returns false when any key is blank or XP is negative.</summary>
|
|
public static bool TryNormalizeSkillXp(
|
|
IReadOnlyDictionary<string, int>? skillXp,
|
|
out Dictionary<string, int> normalized)
|
|
{
|
|
normalized = new Dictionary<string, int>(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;
|
|
}
|
|
|
|
/// <summary>Applies skill XP (batch, then perk reset). Rolls back XP when perk reset fails after XP writes.</summary>
|
|
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<string, int>(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;
|
|
}
|
|
}
|