Иконка ресурса

[FREE] C# BhopVip v1.0 - Авто бхоп для VIP с MySQL 1.0.1

Простой плагин для CounterStrikeSharp, который позволяет VIP-игрокам использовать автоматический бхоп на сервере CS2. работает напрямую с VIP CORE

Команды​

  • !bhop - включить/выключить бхоп (только для VIP)
  • !checkvip - проверить VIP статус
  • !bhopcheck - принудительная проверка VIP (если выдали во время игры)

Установка​

  1. Скопировать файлы из Bhopvip_CS2-main/bin/Release/net8.0/ в папку плагинов: /addons/counterstrikesharp/plugins/BhopVip/
  2. Настроить config.json (БД, ServerID, язык)
  3. Перезагрузить плагины: cssharp plugins reload

Поддерживаемые языки​

  • Українська (ua)
  • Русский (ru)
  • English (en)
Видео работы плагина:

Исходный код плагина:
C-подобный:
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Timers;
using MySqlConnector;
using System.Text.Json;

namespace BhopVip;

public class BhopVip : BasePlugin
{
    public override string ModuleName => "BhopVip";
    public override string ModuleVersion => "1.0.1";
    public override string ModuleAuthor => "Alley";
    public override string ModuleDescription => "VIP Per-Player Auto Bhop";

    private const ulong SteamId64Base = 76561197960265728UL;
    private const float BhopJumpVelocity = 290f;

    private class DbConfig
    {
        public string Host { get; set; } = "127.0.0.1";
        public int Port { get; set; } = 3306;
        public string Database { get; set; } = "cs2";
        public string User { get; set; } = "root";
        public string Password { get; set; } = "";
    }

    private class PluginConfig
    {
        public DbConfig Database { get; set; } = new();
        public int ServerID { get; set; } = 0;
        public bool RoundDelayEnabled { get; set; } = true;
        public int RoundDelaySeconds { get; set; } = 5;
        public bool DamageCooldownEnabled { get; set; } = false;
        public int DamageCooldownSeconds { get; set; } = 3;
        public List<string> AllowedGroups { get; set; } = new() { "vip" };
        public string Language { get; set; } = "ru";
        public bool Debug { get; set; } = false;
    }

    private PluginConfig _config = new();
    private MySqlConnection? _db;
    private HashSet<uint> _activeVips = new();
    private Dictionary<uint, bool> _playerVipStatus = new();
    private Dictionary<uint, string> _playerGroup = new();
    private Dictionary<uint, CounterStrikeSharp.API.Modules.Timers.Timer> _damageCooldownTimers = new();
    private bool _roundActive = false;

    private string ConfigPath => Path.Combine(ModuleDirectory, "../../configs/plugins", ModuleName, "config.json");
    private string LangDir => Path.Combine(ModuleDirectory, "../../lang/bhopvip");
    private Dictionary<string, string> _translations = new();

    public override void Load(bool hotReload)
    {
        CreateConfigIfNotExists();
        LoadConfig();
        CreateLanguageFiles();
        LoadTranslations();
        ConnectDatabase();

        Server.ExecuteCommand("sv_autobunnyhopping 1");
        Server.ExecuteCommand("sv_enablebunnyhopping 1");

        Server.ExecuteCommand("sv_airaccelerate 1000");
        Server.ExecuteCommand("sv_maxvelocity 7000");
        Server.ExecuteCommand("sv_staminamax 0");
        Server.ExecuteCommand("sv_staminalandcost 0");
        Server.ExecuteCommand("sv_staminajumpcost 0");

        RegisterListener<Listeners.OnTick>(OnTick);
        RegisterEventHandler<EventPlayerConnectFull>(OnPlayerConnectFull);
        RegisterEventHandler<EventPlayerDisconnect>(OnPlayerDisconnect);
        RegisterEventHandler<EventRoundStart>(OnRoundStart);
        RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
        RegisterEventHandler<EventPlayerSpawn>(OnPlayerSpawn);

        AddCommand("bhop", "Toggle bunnyhop for VIPs", OnBhopCommand);
        AddCommand("checkvip", "Check VIP status", OnCheckVipCommand);
        AddCommand("bhopcheck", "Force check VIP status", OnBhopCheckCommand);

        AddTimer(60.0f, CheckAllPlayersVip, TimerFlags.REPEAT);
        AddTimer(30.0f, EnsureDbConnection, TimerFlags.REPEAT);

        Console.WriteLine($"[{ModuleName}] Loaded v{ModuleVersion}");
    }

    private void OnTick()
    {
        if (!_roundActive) return;

        foreach (var player in Utilities.GetPlayers())
        {
            if (player == null || !player.IsValid || player.IsBot || !player.PawnIsAlive) continue;

            uint accId = (uint)(player.SteamID - SteamId64Base);
            if (!_activeVips.Contains(accId) || _damageCooldownTimers.ContainsKey(accId)) continue;

            var pawn = player.PlayerPawn.Value;
            if (pawn == null) continue;

            var flags = (PlayerFlags)pawn.Flags;

            if (flags.HasFlag(PlayerFlags.FL_ONGROUND) &&
                player.Buttons.HasFlag(PlayerButtons.Jump) &&
                pawn.AbsVelocity.Z <= 8.0f)
            {
                pawn.AbsVelocity.Z = BhopJumpVelocity;
            }
        }
    }

    private void CreateConfigIfNotExists()
    {
        var dir = Path.GetDirectoryName(ConfigPath);
        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir!);
        if (!File.Exists(ConfigPath))
        {
            var defaultConfig = new PluginConfig();
            string json = JsonSerializer.Serialize(defaultConfig, new JsonSerializerOptions { WriteIndented = true });
            File.WriteAllText(ConfigPath, json);
            Console.WriteLine($"[{ModuleName}] Created default config at {ConfigPath}");
        }
    }

    private void LoadConfig()
    {
        try
        {
            string json = File.ReadAllText(ConfigPath);
            _config = JsonSerializer.Deserialize<PluginConfig>(json) ?? new PluginConfig();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[{ModuleName}] Config error: {ex.Message}");
        }
    }

    private void CreateLanguageFiles()
    {
        if (!Directory.Exists(LangDir))
            Directory.CreateDirectory(LangDir);

        var languages = new[] { "en", "ru", "ua" };
        foreach (var lang in languages)
        {
            var file = Path.Combine(LangDir, $"{lang}.json");
            if (!File.Exists(file))
            {
                var translations = lang switch
                {
                    "en" => new Dictionary<string, string>
                    {
                        ["vip_yes_bhop_on"] = "[VIP] You have VIP status! Bunnyhop automatically enabled",
                        ["bhop_on"] = "[VIP] Bunnyhop ENABLED",
                        ["bhop_off"] = "[VIP] Bunnyhop DISABLED",
                        ["only_vip"] = "[VIP] Only VIP can use !bhop",
                        ["vip_check_yes"] = "[VIP] You have VIP status! Bunnyhop: {status}",
                        ["vip_check_no"] = "[VIP] You don't have VIP status!",
                        ["vip_found"] = "[VIP] Congratulations! You got VIP status! Bunnyhop automatically enabled",
                        ["bhop_status_on"] = "ENABLED",
                        ["bhop_status_off"] = "DISABLED",
                        ["db_error"] = "[VIP] Database connection error",
                        ["round_delay_wait"] = "[VIP] Bunnyhop will be available in {delay} seconds",
                        ["round_delay_active"] = "[VIP] Bunnyhop is now available!",
                        ["damage_cooldown_start"] = "[VIP] Bunnyhop disabled for {seconds} seconds due to damage!",
                        ["damage_cooldown_end"] = "[VIP] Bunnyhop re-enabled!"
                    },
                    "ru" => new Dictionary<string, string>
                    {
                        ["vip_yes_bhop_on"] = "[VIP] У вас есть VIP статус! Бхоп автоматически включён",
                        ["bhop_on"] = "[VIP] Бхоп ВКЛЮЧЁН",
                        ["bhop_off"] = "[VIP] Бхоп ВЫКЛЮЧЕН",
                        ["only_vip"] = "[VIP] Только VIP могут использовать !bhop",
                        ["vip_check_yes"] = "[VIP] У вас есть VIP статус! Бхоп: {status}",
                        ["vip_check_no"] = "[VIP] У вас нет VIP статуса!",
                        ["vip_found"] = "[VIP] Поздравляем! Вы получили VIP статус! Бхоп автоматически включён",
                        ["bhop_status_on"] = "ВКЛЮЧЁН",
                        ["bhop_status_off"] = "ВЫКЛЮЧЕН",
                        ["db_error"] = "[VIP] Ошибка подключения к БД",
                        ["round_delay_wait"] = "[VIP] Бхоп станет доступен через {delay} секунд",
                        ["round_delay_active"] = "[VIP] Бхоп теперь доступен!",
                        ["damage_cooldown_start"] = "[VIP] Бхоп отключён на {seconds} секунд из-за урона!",
                        ["damage_cooldown_end"] = "[VIP] Бхоп снова включён!"
                    },
                    _ => new Dictionary<string, string>
                    {
                        ["vip_yes_bhop_on"] = "[VIP] У вас є VIP статус! Бхоп автоматично включений",
                        ["bhop_on"] = "[VIP] Бхоп УВІМКНЕНО",
                        ["bhop_off"] = "[VIP] Бхоп ВИМКНЕНО",
                        ["only_vip"] = "[VIP] Тільки VIP можуть використовувати !bhop",
                        ["vip_check_yes"] = "[VIP] У вас є VIP статус! Бхоп: {status}",
                        ["vip_check_no"] = "[VIP] У вас немає VIP статусу!",
                        ["vip_found"] = "[VIP] Вітаю! Ви отримали VIP статус! Бхоп автоматично включений",
                        ["bhop_status_on"] = "УВІМКНЕНО",
                        ["bhop_status_off"] = "ВИМКНЕНО",
                        ["db_error"] = "[VIP] Помилка підключення до БД",
                        ["round_delay_wait"] = "[VIP] Бхоп стане доступним через {delay} секунд",
                        ["round_delay_active"] = "[VIP] Бхоп тепер доступний!",
                        ["damage_cooldown_start"] = "[VIP] Бхоп вимкнено на {seconds} секунд через пошкодження!",
                        ["damage_cooldown_end"] = "[VIP] Бхоп знову включено!"
                    }
                };

                string json = JsonSerializer.Serialize(translations, new JsonSerializerOptions { WriteIndented = true });
                File.WriteAllText(file, json);
            }
        }
    }

    private void LoadTranslations()
    {
        string lang = _config.Language;
        if (!new[] { "en", "ru", "ua" }.Contains(lang))
            lang = "ru";

            var file = Path.Combine(LangDir, $"{lang}.json");
            if (File.Exists(file))
            {
                try
                {
                    string json = File.ReadAllText(file);
                    var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
                    if (dict != null) _translations = dict;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[{ModuleName}] Failed to load language file: {ex.Message}");
                }
            }
    }

    private string Localize(string key, Dictionary<string, string>? replacements = null)
    {
        if (!_translations.TryGetValue(key, out var text))
            return key;

        if (replacements != null)
        {
            foreach (var rep in replacements)
                text = text.Replace($"{{{rep.Key}}}", rep.Value);
        }
        return text;
    }

    private void ConnectDatabase()
    {
        try
        {
            var builder = new MySqlConnectionStringBuilder
            {
                Server = _config.Database.Host,
                Port = (uint)_config.Database.Port,
                Database = _config.Database.Database,
                UserID = _config.Database.User,
                Password = _config.Database.Password,
                ConnectionTimeout = 5,
                SslMode = MySqlSslMode.None,
                Pooling = true
            };
            _db = new MySqlConnection(builder.ConnectionString);
            _db.Open();
            Console.WriteLine($"[{ModuleName}] DB connected");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[{ModuleName}] DB connection failed: {ex.Message}");
        }
    }

    private void EnsureDbConnection()
    {
        if (_db != null && _db.State == System.Data.ConnectionState.Open)
            return;

        ConnectDatabase();
        if (_db?.State == System.Data.ConnectionState.Open)
            CheckAllPlayersVip();
    }

    private (bool isVip, string group)? GetVipInfoFromDb(uint accountId)
    {
        if (_db?.State != System.Data.ConnectionState.Open) return null;

        try
        {
            using var cmd = new MySqlCommand("SELECT `group`, `expires` FROM `vip_users` WHERE `account_id` = @id AND `sid` = @sid LIMIT 1", _db);
            cmd.Parameters.AddWithValue("@id", accountId);
            cmd.Parameters.AddWithValue("@sid", _config.ServerID);
            cmd.CommandTimeout = 5;

            using var reader = cmd.ExecuteReader();
            if (reader.Read())
            {
                string group = reader.GetString(0);
                long expires = reader.GetInt64(1);
                bool validExpires = expires == 0 || expires > DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                bool groupAllowed = _config.AllowedGroups.Contains(group);
                return (validExpires && groupAllowed, group);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[{ModuleName}] SQL error: {ex.Message}");
        }
        return null;
    }

    private void CheckAllPlayersVip()
    {
        if (_db?.State != System.Data.ConnectionState.Open) return;

        foreach (var player in Utilities.GetPlayers())
        {
            if (player == null || player.SteamID == 0) continue;

            uint accId = (uint)(player.SteamID - SteamId64Base);
            var info = GetVipInfoFromDb(accId);
            bool isVip = info?.isVip ?? false;

            _playerVipStatus[accId] = isVip;
            if (info != null) _playerGroup[accId] = info.Value.group;

            if (isVip && !_activeVips.Contains(accId) && _roundActive && !_damageCooldownTimers.ContainsKey(accId))
            {
                _activeVips.Add(accId);
                player.PrintToChat(Localize("vip_found"));
            }
            else if (!isVip && _activeVips.Contains(accId))
            {
                _activeVips.Remove(accId);
            }

            SyncPlayerBhop(player, accId);
        }
    }

    private void ApplyBhopState(CCSPlayerController? player, bool enabled)
    {
        if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return;

        try
        {
            player.ReplicateConVar("sv_autobunnyhopping", enabled ? "1" : "0");
            player.ReplicateConVar("sv_enablebunnyhopping", enabled ? "1" : "0");
        }
        catch (Exception ex)
        {
            if (_config.Debug)
                Console.WriteLine($"[{ModuleName}] ReplicateConVar failed for {player.PlayerName}: {ex.Message}");
        }
    }

    private void SyncPlayerBhop(CCSPlayerController? player, uint accId)
    {
        bool shouldBhop = _activeVips.Contains(accId) &&
        _playerVipStatus.GetValueOrDefault(accId) &&
        _roundActive &&
        !_damageCooldownTimers.ContainsKey(accId);

        ApplyBhopState(player, shouldBhop);
    }

    private HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info)
    {
        var player = @event.Userid;
        if (player?.SteamID == 0) return HookResult.Continue;

        uint accId = (uint)(player!.SteamID - SteamId64Base);

        AddTimer(0.25f, () =>
        {
            if (player.IsValid)
                SyncPlayerBhop(player, accId);
        }, TimerFlags.STOP_ON_MAPCHANGE);

        return HookResult.Continue;
    }

    private void StartDamageCooldown(CCSPlayerController player, uint accId)
    {
        if (!_config.DamageCooldownEnabled) return;
        if (!_playerVipStatus.GetValueOrDefault(accId)) return;

        if (_damageCooldownTimers.TryGetValue(accId, out var existing))
        {
            existing.Kill();
            _damageCooldownTimers.Remove(accId);
        }

        if (_activeVips.Contains(accId))
        {
            _activeVips.Remove(accId);
        }

        player.PrintToChat(Localize("damage_cooldown_start", new Dictionary<string, string> { ["seconds"] = _config.DamageCooldownSeconds.ToString() }));
        ApplyBhopState(player, false);

        var newTimer = AddTimer(_config.DamageCooldownSeconds, () =>
        {
            _damageCooldownTimers.Remove(accId);
            if (!player.IsValid) return;

            if (_playerVipStatus.GetValueOrDefault(accId) && _roundActive && !_activeVips.Contains(accId))
            {
                _activeVips.Add(accId);
                player.PrintToChat(Localize("damage_cooldown_end"));
            }
            SyncPlayerBhop(player, accId);
        }, TimerFlags.STOP_ON_MAPCHANGE);

        _damageCooldownTimers[accId] = newTimer;
    }

    private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
    {
        var victim = @event.Userid;
        if (victim?.SteamID == 0) return HookResult.Continue;

        uint accId = (uint)(victim!.SteamID - SteamId64Base);
        StartDamageCooldown(victim, accId);
        return HookResult.Continue;
    }

    private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
    {
        _roundActive = false;
        _activeVips.Clear();

        foreach (var p in Utilities.GetPlayers())
            ApplyBhopState(p, false);

        if (!_config.RoundDelayEnabled)
        {
            _roundActive = true;
            CheckAllPlayersVip();
            return HookResult.Continue;
        }

        foreach (var player in Utilities.GetPlayers())
        {
            if (player?.SteamID == 0) continue;
            if (_playerVipStatus.GetValueOrDefault((uint)(player!.SteamID - SteamId64Base)))
                player.PrintToChat(Localize("round_delay_wait", new Dictionary<string, string> { ["delay"] = _config.RoundDelaySeconds.ToString() }));
        }

        AddTimer(_config.RoundDelaySeconds, () =>
        {
            _roundActive = true;
            CheckAllPlayersVip();

            foreach (var player in Utilities.GetPlayers())
            {
                if (player?.SteamID == 0) continue;
                uint accId = (uint)(player!.SteamID - SteamId64Base);

                if (_playerVipStatus.GetValueOrDefault(accId) && !_activeVips.Contains(accId) && !_damageCooldownTimers.ContainsKey(accId))
                {
                    _activeVips.Add(accId);
                    player.PrintToChat(Localize("round_delay_active"));
                }
                SyncPlayerBhop(player, accId);
            }
        }, TimerFlags.STOP_ON_MAPCHANGE);

        return HookResult.Continue;
    }

    private HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo info)
    {
        var player = @event.Userid;
        if (player?.SteamID == 0) return HookResult.Continue;

        uint accId = (uint)(player!.SteamID - SteamId64Base);
        var vipInfo = GetVipInfoFromDb(accId);
        bool isVip = vipInfo?.isVip ?? false;

        _playerVipStatus[accId] = isVip;
        if (vipInfo != null) _playerGroup[accId] = vipInfo.Value.group;

        if (isVip)
        {
            if (_roundActive && !_activeVips.Contains(accId) && !_damageCooldownTimers.ContainsKey(accId))
            {
                _activeVips.Add(accId);
                player.PrintToChat(Localize("vip_yes_bhop_on"));
            }
            else if (!_roundActive)
            {
                player.PrintToChat(Localize("vip_yes_bhop_on"));
                player.PrintToChat(Localize("round_delay_wait", new Dictionary<string, string> { ["delay"] = _config.RoundDelaySeconds.ToString() }));
            }
        }

        SyncPlayerBhop(player, accId);
        return HookResult.Continue;
    }

    private HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
    {
        var player = @event.Userid;
        if (player?.SteamID == 0) return HookResult.Continue;

        uint accId = (uint)(player!.SteamID - SteamId64Base);
        _activeVips.Remove(accId);

        if (_damageCooldownTimers.TryGetValue(accId, out var timer))
        {
            timer.Kill();
            _damageCooldownTimers.Remove(accId);
        }

        _playerVipStatus.Remove(accId);
        _playerGroup.Remove(accId);
        return HookResult.Continue;
    }

    private void OnBhopCommand(CCSPlayerController? player, CommandInfo command)
    {
        if (player == null) return;
        uint accId = (uint)(player.SteamID - SteamId64Base);

        if (!_playerVipStatus.GetValueOrDefault(accId))
        {
            player.PrintToChat(Localize("only_vip"));
            return;
        }

        if (!_roundActive && _config.RoundDelayEnabled)
        {
            player.PrintToChat(Localize("round_delay_wait", new Dictionary<string, string> { ["delay"] = _config.RoundDelaySeconds.ToString() }));
            return;
        }

        if (_damageCooldownTimers.ContainsKey(accId))
        {
            player.PrintToChat(Localize("damage_cooldown_start", new Dictionary<string, string> { ["seconds"] = _config.DamageCooldownSeconds.ToString() }));
            return;
        }

        if (_activeVips.Contains(accId))
        {
            _activeVips.Remove(accId);
            player.PrintToChat(Localize("bhop_off"));
        }
        else
        {
            _activeVips.Add(accId);
            player.PrintToChat(Localize("bhop_on"));
        }

        SyncPlayerBhop(player, accId);
    }

    private void OnCheckVipCommand(CCSPlayerController? player, CommandInfo command)
    {
        if (player == null) return;
        uint accId = (uint)(player.SteamID - SteamId64Base);
        bool isVip = _playerVipStatus.GetValueOrDefault(accId);

        if (isVip)
        {
            string status;
            if (!_roundActive && _config.RoundDelayEnabled)
                status = Localize("bhop_status_off") + " (waiting round)";
            else if (_damageCooldownTimers.ContainsKey(accId))
                status = Localize("bhop_status_off") + " (cooldown)";
            else
                status = _activeVips.Contains(accId) ? Localize("bhop_status_on") : Localize("bhop_status_off");

            player.PrintToChat(Localize("vip_check_yes", new Dictionary<string, string> { ["status"] = status }));
        }
        else
            player.PrintToChat(Localize("vip_check_no"));
    }

    private void OnBhopCheckCommand(CCSPlayerController? player, CommandInfo command)
    {
        if (player == null) return;

        uint accId = (uint)(player.SteamID - SteamId64Base);
        var vipInfo = GetVipInfoFromDb(accId);
        bool isVip = vipInfo?.isVip ?? false;

        _playerVipStatus[accId] = isVip;
        if (vipInfo != null) _playerGroup[accId] = vipInfo.Value.group;

        if (isVip && !_activeVips.Contains(accId) && _roundActive && !_damageCooldownTimers.ContainsKey(accId))
        {
            _activeVips.Add(accId);
            player.PrintToChat(Localize("vip_found"));
            SyncPlayerBhop(player, accId);
        }
        else if (isVip)
        {
            string status = _activeVips.Contains(accId) ? Localize("bhop_status_on") : Localize("bhop_status_off");
            if (!_roundActive && _config.RoundDelayEnabled)
                status += " (waiting round)";
            else if (_damageCooldownTimers.ContainsKey(accId))
                status += " (cooldown)";

            player.PrintToChat(Localize("vip_check_yes", new Dictionary<string, string> { ["status"] = status }));
        }
        else
            player.PrintToChat(Localize("vip_check_no"));
    }

    public override void Unload(bool hotReload)
    {
        foreach (var p in Utilities.GetPlayers())
            ApplyBhopState(p, false);

        _db?.Close();
        foreach (var timer in _damageCooldownTimers.Values)
            timer.Kill();

        _damageCooldownTimers.Clear();
        _activeVips.Clear();
        _playerVipStatus.Clear();
        _playerGroup.Clear();
    }
}
Требования

Требования​

И всё.
Переменные
ПеременнаяОписание
Database.HostХост MySQL
Database.PortПорт MySQL
Database.DatabaseИмя БД
Database.UserПользователь MySQL
Database.PasswordПароль MySQL
ServerIDID сервера
LanguageЯзык (ua, ru, en)
Команды
КомандаОписание
!bhopВключить/выключить бхоп для себя
!checkvipПроверить VIP статус
!bhopcheckПринудительно перепроверить VIP
Установка
  1. Скомпилировать BhopVip.cs в .dll (если хотите)
  2. Поместить в addons/counterstrikesharp/plugins/BhopVip/ все файлы скомпилированные тут bin/Release/net8.0/
  3. Запустить сервер — создастся configs/plugins/BhopVip/config.json
  4. Отредактировать конфиг (указать данные БД)
  5. Перезагрузить сервер.
Автор
Alley
Скачивания
0
Просмотры
48
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок

Другие ресурсы пользователя Alley

Сверху Снизу