namespace Supercell.Laser.Server.Utils { using System; using Supercell.Laser.Bot; using Supercell.Laser.Logic.Avatar; using Supercell.Laser.Logic.Message.Account.Auth; using Supercell.Laser.Logic.Util; using Supercell.Laser.Server.Database; using Supercell.Laser.Server.Database.Models; using Supercell.Laser.Server.Networking.Session; /// /// Мост между сервером и Supercell.Laser.Bot: телеграм-бот не видит ни Account, /// ни ClientAvatar (это сервер ссылается на бота, обратная ссылка дала бы цикл), /// поэтому вся работа с аккаунтами живёт здесь, а бот получает готовые PlayerInfo /// и ResourceChange. Методы подставляются в ServerLink в Program.Main. /// public static class BotBridge { /// Данные игрока по id аккаунта. null — такого аккаунта в базе нет. public static PlayerInfo GetPlayer(long accountId) { Account account = LoadAccount(accountId); return account == null ? null : Describe(account); } /// Начислить (delta > 0) или списать (delta < 0) кристаллы. public static ResourceChange ChangeGems(long accountId, int delta) { return Change(accountId, delta, gems: true); } /// То же для золота (монет). public static ResourceChange ChangeGold(long accountId, int delta) { return Change(accountId, delta, gems: false); } private static ResourceChange Change(long accountId, int delta, bool gems) { Account account = LoadAccount(accountId); if (account == null) return null; ClientAvatar avatar = account.Avatar; int oldValue = gems ? avatar.Diamonds : avatar.Gold; // Считаем в long и обрезаем: ниже нуля баланс уходить не должен, // выше int.MaxValue — не влезет в поле аватара. long target = (long)oldValue + delta; if (target < 0) target = 0; if (target > int.MaxValue) target = int.MaxValue; int newValue = (int)target; if (gems) avatar.Diamonds = newValue; else avatar.Gold = newValue; // Accounts.Load отдаёт тот же объект, что и у живой сессии игрока, так что // правка видна сразу; Save нужен, чтобы не ждать таймера AccountCache. Accounts.Save(account); int applied = Math.Abs(newValue - oldValue); if (applied > 0) KickToRefresh(accountId, gems, delta > 0, applied); return new ResourceChange { Player = Describe(account), OldValue = oldValue, NewValue = newValue, Applied = applied }; } private static Account LoadAccount(long accountId) { if (accountId <= 0) return null; Account account = Accounts.Load(accountId); return account?.Avatar == null ? null : account; } private static PlayerInfo Describe(Account account) { ClientAvatar avatar = account.Avatar; string code = LogicLongCodeGenerator.ToCode(account.AccountId); return new PlayerInfo { AccountId = account.AccountId, Tag = string.IsNullOrEmpty(code) ? null : code.TrimStart('#'), Name = avatar.Name, Gems = avatar.Diamonds, Gold = avatar.Gold }; } /// /// Клиент держит баланс у себя и увидит новое значение только на новом заходе, /// поэтому онлайн-игрока отключаем с понятным текстом — так же, как это делают /// консольные /givediamonds и /givegold (см. CmdHandler). /// private static void KickToRefresh(long accountId, bool gems, bool added, int amount) { try { if (!Sessions.IsSessionActive(accountId)) return; string resource = gems ? "кристаллов" : "монет"; string message = added ? $"Вам выдано {amount} {resource}! Переподключитесь." : $"С вашего аккаунта списано {amount} {resource}! Переподключитесь."; Sessions.GetSession(accountId).GameListener.SendTCPMessage(new AuthenticationFailedMessage { Message = message }); Sessions.Remove(accountId); } catch (Exception ex) { Logger.Print($"[BotBridge] не удалось переподключить игрока {accountId}: {ex.Message}"); } } } }