using System.Collections.Concurrent; namespace NeonSprawl.Server.Game.AbilityInput; /// Thread-safe in-memory cooldown map (NEO-32); not persisted across process restarts. public sealed class InMemoryPlayerAbilityCooldownStore : IPlayerAbilityCooldownStore { private readonly ConcurrentDictionary> ends = new(StringComparer.Ordinal); public bool IsOnCooldown(string playerId, int slotIndex, DateTimeOffset now) { if (!ends.TryGetValue(playerId, out var perSlot)) { return false; } if (!perSlot.TryGetValue(slotIndex, out var end)) { return false; } if (now >= end) { perSlot.TryRemove(slotIndex, out _); return false; } return true; } public void StartCooldown(string playerId, int slotIndex, DateTimeOffset now, TimeSpan duration) { var perSlot = ends.GetOrAdd(playerId, _ => new ConcurrentDictionary()); perSlot[slotIndex] = now + duration; } public bool TryGetCooldownEndUtc(string playerId, int slotIndex, out DateTimeOffset endUtc) { endUtc = default; if (!ends.TryGetValue(playerId, out var perSlot)) { return false; } return perSlot.TryGetValue(slotIndex, out endUtc); } }