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

C# Counterstrikesharp-MapChooser-для-CS2 1.0.0

MapChooser простой плагин для CS2 на CounterStrikeSharp, добавляющий команду !rtv для голосования за смену карты. Поддерживает обычные карты и карты из Steam Workshop.

Путь хранения плагина(Установка): Скопируйте плагин в указанный путь и перезапустите сервер. : addons/counterstrikesharp/plugins/MapChooser

При первом запуске автоматически создаётся настраиваемый файл settings.json со списком карт и параметрами плагина.

Исходный код:
C-подобный:
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using System.Text.Json;

namespace MapChooser;

[MinimumApiVersion(200)]
public class MapChooser : BasePlugin
{
    public override string ModuleName => "MapChooser";
    public override string ModuleVersion => "1.0.0";
    public override string ModuleAuthor => "Alley";

    private Config _config = null!;
    private readonly Dictionary<int, UserSettings> _users = new();
    private ChatMenu? _voteMenu;

    private readonly Dictionary<string, int> _mapVotes = new();
    private readonly HashSet<string> _nominatedMaps = new();
    private readonly HashSet<string> _recentMaps = new();

    private int _rtvCount = 0;
    private bool _voteInProgress = false;
    private bool _rtvAllowedByTime = true;
    private string? _nextMap = null;

    private float _mapStartTime = 0f;

    public override void Load(bool hotReload)
    {
        _config = LoadConfig();

        RegisterListener<Listeners.OnMapStart>(name =>
        {
            _mapStartTime = Server.CurrentTime;

            _rtvCount = 0;
            _voteInProgress = false;
            _rtvAllowedByTime = true;
            _nextMap = null;
            _mapVotes.Clear();
            _nominatedMaps.Clear();
            _users.Clear();

            var currentMapKey = _config.Maps.FirstOrDefault(m => m.MapId.Equals(name, StringComparison.OrdinalIgnoreCase))?.MapId ?? name;
            _recentMaps.Add(currentMapKey);

            if (_recentMaps.Count > _config.RecentMapsCount)
            {
                _recentMaps.Remove(_recentMaps.First());
            }

            if (_config.RtvDelayMinutes > 0)
            {
                _rtvAllowedByTime = false;
                AddTimer(_config.RtvDelayMinutes * 60f, () => _rtvAllowedByTime = true);
            }

            var timeLimit = ConVar.Find("mp_timelimit")?.GetPrimitiveValue<float>() ?? 0f;
            if (timeLimit > 0)
            {
                var delay = (timeLimit - _config.VoteStartMinutes) * 60f;
                if (delay > 0)
                {
                    AddTimer(delay, () => StartMapVote());
                }
            }
        });

        AddCommand("rtv", "Rock the vote", OnRtvCommand);
        AddCommand("rockthevote", "Rock the vote", OnRtvCommand);
        AddCommand("nominate", "Nominate a map", OnNominateCommand);
        AddCommand("timeleft", "Show time left", OnTimeleftCommand);
        AddCommand("nextmap", "Show next map", OnNextmapCommand);
        AddCommand("maplist", "Show available maps", OnMapListCommand);
    }

    private void OnMapListCommand(CCSPlayerController? player, CommandInfo info)
    {
        if (player == null) return;

        var available = GetAvailableMaps();
        if (available.Count == 0)
        {
            player.PrintToChat($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} Нет доступных карт.");
            return;
        }

        player.PrintToChat($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        player.PrintToChat($"{ChatColors.Gold}        ДОСТУПНЫЕ КАРТЫ");
        player.PrintToChat($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

        int index = 1;
        foreach (var map in available)
        {
            var status = _nominatedMaps.Contains(map.MapId) ? $"{ChatColors.LightPurple} (номинирована)" : "";
            player.PrintToChat($" {ChatColors.Grey}{index++}. {ChatColors.Green}{map.DisplayName} {ChatColors.Grey}→ {ChatColors.DarkBlue}{map.MapId}{status}");
        }

        player.PrintToChat($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    }

    private void OnRtvCommand(CCSPlayerController? player, CommandInfo info)
    {
        if (player == null || !player.IsValid || player.IsBot) return;

        var alivePlayers = Utilities.GetPlayers().Count(p => p is { IsValid: true, IsBot: false });

        if (alivePlayers < _config.RtvMinPlayers && !_config.RtvAllowWhenLessThanMin)
        {
            player.PrintToChat($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} RTV доступен с {_config.RtvMinPlayers} игроков! (сейчас: {alivePlayers})");
            return;
        }

        if (!_rtvAllowedByTime)
        {
            player.PrintToChat($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} RTV доступен через {_config.RtvDelayMinutes} мин после начала карты.");
            return;
        }

        if (_voteInProgress)
        {
            player.PrintToChat($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} Голосование уже идёт!");
            return;
        }

        if (_users.ContainsKey(player.Slot))
        {
            player.PrintToChat($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} Вы уже использовали RTV!");
            return;
        }

        _users[player.Slot] = new UserSettings { HasRtved = true };
        _rtvCount++;

        var required = Math.Max(1, (int)Math.Ceiling(alivePlayers * _config.RtvRequiredPercent));
        Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] {ChatColors.LightBlue}{player.PlayerName} {ChatColors.Green}хочет сменить карту! {ChatColors.Gold}({_rtvCount}/{required})");

        if (_rtvCount >= required)
        {
            StartMapVote(true);
        }
    }

    private void OnNominateCommand(CCSPlayerController? player, CommandInfo info)
    {
        if (player == null || !player.IsValid || player.IsBot) return;

        var available = GetAvailableMaps();
        if (available.Count == 0)
        {
            player.PrintToChat($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} Нет карт для номинации.");
            return;
        }

        var menu = new ChatMenu("Номинировать карту");

        foreach (var map in available)
        {
            menu.AddMenuOption(map.DisplayName, (p, option) =>
            {
                if (_nominatedMaps.Contains(map.MapId)) return;

                _nominatedMaps.Add(map.MapId);
                Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] {ChatColors.LightBlue}{p.PlayerName} {ChatColors.Green}номинировал: {ChatColors.Gold}{map.DisplayName}");
            });
        }

        MenuManager.OpenChatMenu(player, menu);
    }

    private void OnTimeleftCommand(CCSPlayerController? player, CommandInfo info)
    {
        if (player == null) return;

        var timeLimit = ConVar.Find("mp_timelimit")?.GetPrimitiveValue<float>() ?? 0f;
        if (timeLimit > 0)
        {
            var remaining = timeLimit * 60f - (Server.CurrentTime - _mapStartTime);
            if (remaining < 0) remaining = 0;
            var mins = (int)remaining / 60;
            var secs = (int)remaining % 60;
            player.PrintToChat($"{ChatColors.Green}[MapChooser] Осталось: {ChatColors.Gold}{mins:D2}:{secs:D2}");
        }
        else
        {
            player.PrintToChat($"{ChatColors.Green}[MapChooser] Без лимита времени.");
        }
    }

    private void OnNextmapCommand(CCSPlayerController? player, CommandInfo info)
    {
        if (player == null) return;

        if (string.IsNullOrEmpty(_nextMap))
            player.PrintToChat($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} Карта не выбрана.");
        else
        {
            var map = _config.Maps.FirstOrDefault(m => m.MapId == _nextMap);
            var name = map?.DisplayName ?? _nextMap;
            player.PrintToChat($"{ChatColors.Green}[MapChooser] Следующая: {ChatColors.Gold}{name}");
        }
    }

    private void StartMapVote(bool forced = false)
    {
        if (_voteInProgress) return;
        _voteInProgress = true;

        var maps = GetVoteMaps();

        Server.PrintToChatAll($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        Server.PrintToChatAll($"{ChatColors.Gold}        ГОЛОСОВАНИЕ ЗА КАРТУ");
        Server.PrintToChatAll($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

        _voteMenu = new ChatMenu("Голосуйте за карту");

        _voteMenu.AddMenuOption("Extend current map", (p, option) => VoteForMap("extend", p));

        int index = 1;
        foreach (var map in maps)
        {
            _voteMenu.AddMenuOption(map.DisplayName, (p, option) => VoteForMap(map.MapId, p));
            Server.PrintToChatAll($" {ChatColors.Grey}{index++}. {ChatColors.Green}{map.DisplayName}");
        }

        Server.PrintToChatAll($" {ChatColors.Grey}0. {ChatColors.LightPurple}Extend current map");
        Server.PrintToChatAll($"{ChatColors.Gold}Голосуйте в меню! Время: {_config.VoteTime} сек");
        Server.PrintToChatAll($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

        foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot))
        {
            MenuManager.OpenChatMenu(player, _voteMenu);
        }

        AddTimer(_config.VoteTime, EndVote);
    }

    private void VoteForMap(string mapId, CCSPlayerController player)
    {
        if (!_mapVotes.ContainsKey(mapId))
            _mapVotes[mapId] = 0;

        _mapVotes[mapId]++;

        var map = _config.Maps.FirstOrDefault(m => m.MapId == mapId);
        var name = mapId == "extend" ? "Extend current map" : (map?.DisplayName ?? mapId);
        Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] {ChatColors.LightBlue}{player.PlayerName} {ChatColors.Green}voted for {ChatColors.Gold}{name}");
    }

    private void EndVote()
    {
        Server.PrintToChatAll($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        Server.PrintToChatAll($"{ChatColors.Gold}        VOTING ENDED!");
        Server.PrintToChatAll($"{ChatColors.LightBlue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

        var winner = _mapVotes.OrderByDescending(kv => kv.Value).FirstOrDefault();

        if (winner.Key == "extend")
        {
            Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] Map extended for {_config.ExtendMinutes} minutes!");
            var current = ConVar.Find("mp_timelimit")!.GetPrimitiveValue<float>();
            ConVar.Find("mp_timelimit")?.SetValue(current + _config.ExtendMinutes);
        }
        else if (!string.IsNullOrEmpty(winner.Key))
        {
            var map = _config.Maps.First(m => m.MapId == winner.Key);
            _nextMap = winner.Key;

            Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] Winner: {ChatColors.Gold}{map.DisplayName} {ChatColors.Green}(votes: {winner.Value})!");
            Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] Map change in 5 seconds...");

            AddTimer(5f, () => ChangeToMap(winner.Key));
        }
        else
        {
            var randomMap = GetAvailableMaps().OrderBy(_ => Random.Shared.Next()).FirstOrDefault();
            if (randomMap != null)
            {
                _nextMap = randomMap.MapId;
                Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] Randomly selected: {ChatColors.Gold}{randomMap.DisplayName}");
                Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] Map change in 5 seconds...");

                AddTimer(5f, () => ChangeToMap(randomMap.MapId));
            }
        }

        _voteInProgress = false;
    }

    private List<MapEntry> GetVoteMaps()
    {
        var available = GetAvailableMaps();
        var selected = _nominatedMaps
            .Where(id => available.Any(m => m.MapId == id))
            .Select(id => available.First(m => m.MapId == id))
            .ToList();

        while (selected.Count < 6 && available.Count > selected.Count)
        {
            var randomMap = available.Except(selected).OrderBy(_ => Random.Shared.Next()).FirstOrDefault();
            if (randomMap != null) selected.Add(randomMap);
        }

        return selected;
    }

    private List<MapEntry> GetAvailableMaps()
    {
        return _config.Maps.Where(m => !_recentMaps.Contains(m.MapId)).ToList();
    }

    private void ChangeToMap(string mapId)
    {
        var map = _config.Maps.FirstOrDefault(m => m.MapId == mapId);
        if (map == null)
        {
            Server.PrintToChatAll($"{ChatColors.Red}{ChatColors.Green}[MapChooser]{ChatColors.Red} Error: map not found!");
            return;
        }

        Server.PrintToChatAll($"{ChatColors.Green}[MapChooser] Loading map: {ChatColors.Gold}{map.DisplayName}");

        if (map.MapId.StartsWith("workshop_id/"))
        {
            var workshopId = map.MapId.Replace("workshop_id/", "").Trim();
            Server.ExecuteCommand($"host_workshop_map {workshopId}");
        }
        else if (map.MapId.StartsWith("collection/"))
        {
            var collectionId = map.MapId.Replace("collection/", "");
            Server.ExecuteCommand($"host_workshop_collection {collectionId}");
            Server.ExecuteCommand("map random");
        }
        else if (map.MapId.StartsWith("workshop_start_map/"))
        {
            var startMapId = map.MapId.Replace("workshop_start_map/", "");
            Server.ExecuteCommand($"workshop_start_map {startMapId}");
        }
        else if (map.MapId.StartsWith("workshop/"))
        {
            var parts = map.MapId.Split('/', 3);
            if (parts.Length == 3)
            {
                var mapName = parts[2];
                Server.ExecuteCommand($"ds_workshop_changelevel {mapName}");
            }
        }
        else
        {
            Server.ExecuteCommand($"changelevel {map.MapId}");
        }
    }

    private Config LoadConfig()
    {
        var path = Path.Combine(ModuleDirectory, "settings.json");

        if (!File.Exists(path))
        {
            var defaultConfig = new Config
            {
                Maps = new List<MapEntry>
                {
                    new() { DisplayName = "Mirage FPS", MapId = "workshop_id/3349182536" },
                    new() { DisplayName = "Mirage", MapId = "de_mirage" },
                    new() { DisplayName = "Italy", MapId = "cs_italy" },
                    new() { DisplayName = "Office", MapId = "cs_office" },
                    new() { DisplayName = "Inferno", MapId = "de_inferno" }
                },
                RtvMinPlayers = 4,
                RtvAllowWhenLessThanMin = false,
                RtvRequiredPercent = 0.6,
                RtvDelayMinutes = 5,
                VoteTime = 20f,
                RecentMapsCount = 5,
                VoteStartMinutes = 5f,
                ExtendMinutes = 15f
            };

            var jsonOptions = new JsonSerializerOptions { WriteIndented = true };
            File.WriteAllText(path, JsonSerializer.Serialize(defaultConfig, jsonOptions));

            return defaultConfig;
        }

        var json = File.ReadAllText(path);
        return JsonSerializer.Deserialize<Config>(json)!;
    }
}

public class UserSettings
{
    public bool HasRtved { get; set; }
}

public class MapEntry
{
    public string DisplayName { get; set; } = "";
    public string MapId { get; set; } = "";
}

public class Config
{
    public List<MapEntry> Maps { get; set; } = new();
    public int RtvMinPlayers { get; set; } = 4;
    public bool RtvAllowWhenLessThanMin { get; set; } = false;
    public double RtvRequiredPercent { get; set; } = 0.6;
    public int RtvDelayMinutes { get; set; } = 5;
    public float VoteTime { get; set; } = 20f;
    public int RecentMapsCount { get; set; } = 5;
    public float VoteStartMinutes { get; set; } = 5f;
    public float ExtendMinutes { get; set; } = 15f;
}
Требования
CounterStrikeSharp.API
Переменные
settings.json список карт и параметры плагина.
Команды
!rtv — запуск голосования за смену карты
Установка
Путь хранения плагина(Установка): Скопируйте плагин в указанный путь и перезапустите сервер. : addons/counterstrikesharp/plugins/MapChooser
Автор
Alley
Скачивания
15
Просмотры
207
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок

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

Сверху Снизу