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}");
}
}
}
}