using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Hiro; using Nakama; namespace XPGuide { public class XPController { private readonly NakamaSystem _nakamaSystem; private readonly IAchievementsSystem _achievementsSystem; private readonly IEconomySystem _economySystem; private readonly IClient _client; private readonly ISession _session; public XPController( NakamaSystem nakamaSystem, IAchievementsSystem achievementsSystem, IEconomySystem economySystem, IClient client, ISession session) { _nakamaSystem = nakamaSystem ?? throw new ArgumentNullException(nameof(nakamaSystem)); _achievementsSystem = achievementsSystem ?? throw new ArgumentNullException(nameof(achievementsSystem)); _economySystem = economySystem ?? throw new ArgumentNullException(nameof(economySystem)); _client = client ?? throw new ArgumentNullException(nameof(client)); _session = session ?? throw new ArgumentNullException(nameof(session)); } // Load state on first open public async Task LoadAsync() { await _achievementsSystem.RefreshAsync(); await _economySystem.RefreshAsync(); } // Call after any XP grant to keep local state current public async Task RefreshAsync() { await _achievementsSystem.RefreshAsync(); await _economySystem.RefreshAsync(); } // Calls rpc_grant_xp then refreshes state public async Task GrantXPAsync(long amount) { if (amount <= 0) throw new ArgumentException("XP amount must be greater than 0."); await _client.RpcAsync(_session, "rpc_grant_xp", $"{{\"amount\":{amount}}}"); await RefreshAsync(); } // Total lifetime XP from the wallet public long GetCurrentXP() { return _economySystem.Wallet.TryGetValue("xp", out var xp) ? xp : 0; } // Highest claimed level number; 0 if no level has been reached yet public int GetCurrentLevel() { int level = 0; foreach (var (key, sub) in GetOrderedLevels()) { if (sub.ClaimTimeSec > 0) { int num = ExtractLevelNumber(key); if (num > level) level = num; } } return level; } // (xpIntoLevel, xpRequiredForLevel) for the next unclaimed level. // Returns (totalXP, totalXP) when all levels are complete. public (long current, long max) GetProgressToNextLevel() { foreach (var (_, sub) in GetOrderedLevels()) { if (sub.ClaimTimeSec <= 0) return (sub.Count, sub.MaxCount); } long xp = GetCurrentXP(); return (xp, xp); } // Level sub-achievements sorted by level number public List<(string key, ISubAchievement sub)> GetOrderedLevels() { var playerLevels = _achievementsSystem.GetAchievements("progression") .FirstOrDefault(a => a.Id == "player_levels"); if (playerLevels?.SubAchievements == null) return new List<(string, ISubAchievement)>(); return playerLevels.SubAchievements .OrderBy(kv => ExtractLevelNumber(kv.Key)) .Select(kv => (kv.Key, kv.Value)) .ToList(); } private static int ExtractLevelNumber(string key) { if (int.TryParse(key.Replace("level_", ""), out int num)) return num; return 0; } } }