├── .github └── workflows │ └── build-publish.yml ├── .gitignore ├── CS2-CustomVotes.Shared ├── CS2-CustomVotes.Shared.csproj ├── ICustomVoteApi.cs └── Models │ └── VoteOptions.cs ├── CS2-CustomVotes.json.example ├── CS2-CustomVotes.sln ├── CS2-CustomVotes ├── CS2-CustomVotes.csproj ├── Extensions │ └── PlayerControllerExtensions.cs ├── Factories │ └── ActiveVoteFactory.cs ├── Models │ ├── ActiveVote.cs │ └── CustomVote.cs ├── Plugin.cs ├── PluginConfig.cs ├── Services │ ├── CustomVoteApi.cs │ └── VoteManager.cs └── lang │ ├── cz.json │ ├── de.json │ ├── en.json │ ├── pt-br.json │ ├── pt-pt.json │ └── ru.json ├── LICENSE └── README.MD /.github/workflows/build-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - 'README.MD' 9 | - '.github/workflows/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | steps: 17 | - name: Prepare env 18 | shell: bash 19 | run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV 20 | 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | 24 | - name: Extract version number from README.MD 25 | id: extract_version 26 | run: | 27 | VERSION=$(grep -oP '(?<=# CS2 Custom Votes \()[0-9]+\.[0-9]+\.[0-9]+' README.MD) 28 | if [ -z "$VERSION" ]; then 29 | echo "No version number found in README.MD. Exiting." 30 | exit 1 31 | fi 32 | echo "VERSION_NUMBER=$VERSION" >> $GITHUB_ENV 33 | 34 | - name: Check if version already exists 35 | id: check_version 36 | run: | 37 | if gh release view "${{ env.VERSION_NUMBER }}" > /dev/null 2>&1; then 38 | echo "Version ${{ env.VERSION_NUMBER }} already exists. Exiting." 39 | exit 0 40 | fi 41 | 42 | - name: Setup .NET 43 | uses: actions/setup-dotnet@v3 44 | with: 45 | dotnet-version: '8.0.x' 46 | 47 | - name: Restore dependencies 48 | run: dotnet restore 49 | 50 | - name: Build projects 51 | run: dotnet build -c Release --no-restore /p:Version=${{ env.VERSION_NUMBER }} 52 | 53 | - name: Pack CS2-CustomVotes.Shared 54 | run: dotnet pack CS2-CustomVotes.Shared/CS2-CustomVotes.Shared.csproj -c Release --no-build --output ./nupkg /p:Version=${{ env.VERSION_NUMBER }} 55 | 56 | - name: Check contents of output directories 57 | run: | 58 | echo "Listing contents of CS2-CustomVotes/bin/Release/net8.0/" 59 | ls -la CS2-CustomVotes/bin/Release/net8.0/ 60 | echo "Listing contents of CS2-CustomVotes.Shared/bin/Release/net8.0/" 61 | ls -la CS2-CustomVotes.Shared/bin/Release/net8.0/ 62 | 63 | - name: Create necessary directory structure and copy files 64 | run: | 65 | mkdir -p release/addons/counterstrikesharp/configs/plugins/CS2-CustomVotes 66 | mkdir -p release/addons/counterstrikesharp/plugins/CS2-CustomVotes/lang 67 | mkdir -p release/addons/counterstrikesharp/shared/CS2-CustomVotes.Shared 68 | cp CS2-CustomVotes.json.example release/addons/counterstrikesharp/configs/plugins/CS2-CustomVotes/CS2-CustomVotes.json 69 | cp -r CS2-CustomVotes/bin/Release/net8.0/* release/addons/counterstrikesharp/plugins/CS2-CustomVotes/ 70 | cp -r CS2-CustomVotes.Shared/bin/Release/net8.0/* release/addons/counterstrikesharp/shared/CS2-CustomVotes.Shared/ 71 | # Ensure the lang directory is copied 72 | cp -r CS2-CustomVotes/lang/* release/addons/counterstrikesharp/plugins/CS2-CustomVotes/lang/ 73 | 74 | - name: Create a ZIP file with binaries 75 | run: | 76 | cd release 77 | zip -r ../CS2-CustomVotes-${{ env.VERSION_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip addons 78 | 79 | - name: Publish NuGet package 80 | run: dotnet nuget push ./nupkg/CS2-CustomVotes.Shared.${{ env.VERSION_NUMBER }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate 81 | 82 | - name: Create GitHub Release 83 | id: create_release 84 | uses: actions/create-release@v1 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 87 | with: 88 | tag_name: v${{ env.VERSION_NUMBER }} 89 | release_name: ${{ env.VERSION_NUMBER }} 90 | draft: false 91 | prerelease: false 92 | body: | 93 | ## Changes 94 | - Auto-generated release 95 | ${{ github.event.head_commit.message }} 96 | 97 | - name: Upload Release Asset 98 | uses: actions/upload-release-asset@v1 99 | env: 100 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | with: 102 | upload_url: ${{ steps.create_release.outputs.upload_url }} 103 | asset_path: ./CS2-CustomVotes-${{ env.VERSION_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip 104 | asset_name: CS2-CustomVotes-${{ env.VERSION_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip 105 | asset_content_type: application/zip 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | CS2-CustomVotes/bin 3 | CS2-CustomVotes/obj 4 | CS2-CustomVotes.Shared/bin 5 | CS2-CustomVotes.Shared/obj 6 | Public 7 | -------------------------------------------------------------------------------- /CS2-CustomVotes.Shared/CS2-CustomVotes.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | CS2_CustomVotes.Shared 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CS2-CustomVotes.Shared/ICustomVoteApi.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CS2_CustomVotes.Shared.Models; 3 | 4 | namespace CS2_CustomVotes.Shared; 5 | 6 | public interface ICustomVoteApi 7 | { 8 | public void AddCustomVote(string name, string description, string defaultOption, float timeToVote, Dictionary options, string style); 9 | public void AddCustomVote(string name, List aliases, string description, string defaultOption, float timeToVote, Dictionary options, string style); 10 | public void AddCustomVote(string name, string description, string defaultOption, float timeToVote, Dictionary options, string style, int minVotePercentage); 11 | public void AddCustomVote(string name, List aliases, string description, string defaultOption, float timeToVote, Dictionary options, string style, int minVotePercentage); 12 | public void StartCustomVote(CCSPlayerController? player, string name); 13 | public void EndCustomVote(string name); 14 | public void RemoveCustomVote(string name); 15 | } 16 | -------------------------------------------------------------------------------- /CS2-CustomVotes.Shared/Models/VoteOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CS2_CustomVotes.Shared.Models; 4 | 5 | public class VoteOption 6 | { 7 | public VoteOption(string text, List commands) 8 | { 9 | Text = text; 10 | Commands = commands; 11 | } 12 | 13 | [JsonPropertyName("Text")] public string Text { get; set; } 14 | 15 | [JsonPropertyName("Commands")] public List Commands { get; set; } 16 | } -------------------------------------------------------------------------------- /CS2-CustomVotes.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "CustomVotesEnabled": true, 3 | "VoteCooldown": 60, 4 | "ChatPrefix": "[{DarkBlue}CustomVotes{Default}]", 5 | "ForceStyle": "none", 6 | "CustomVotes": [ 7 | { 8 | "Command": "cheats", 9 | "CommandAliases": [], 10 | "Description": "Vote to enable sv_cheats", 11 | "TimeToVote": 30, 12 | "Options": { 13 | "Enable": { 14 | "Text": "{Green}Enable", 15 | "Commands": [ 16 | "sv_cheats 1" 17 | ] 18 | }, 19 | "Disable": { 20 | "Text": "{Red}Disable", 21 | "Commands": [ 22 | "sv_cheats 0" 23 | ] 24 | } 25 | }, 26 | "DefaultOption": "Disable", 27 | "Style": "chat", 28 | "MinVotePercentage": -1, 29 | "Permission": { 30 | "RequiresAll": false, 31 | "Permissions": [] 32 | } 33 | } 34 | ], 35 | "ConfigVersion": 2 36 | } -------------------------------------------------------------------------------- /CS2-CustomVotes.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-CustomVotes", "CS2-CustomVotes\CS2-CustomVotes.csproj", "{D7805B32-A457-4DFE-A955-920F4A39F506}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CS2-CustomVotes.Shared", "CS2-CustomVotes.Shared\CS2-CustomVotes.Shared.csproj", "{31457B2B-09A2-48F9-98EA-F30F22732BC6}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {D7805B32-A457-4DFE-A955-920F4A39F506}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {D7805B32-A457-4DFE-A955-920F4A39F506}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {D7805B32-A457-4DFE-A955-920F4A39F506}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {D7805B32-A457-4DFE-A955-920F4A39F506}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {31457B2B-09A2-48F9-98EA-F30F22732BC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {31457B2B-09A2-48F9-98EA-F30F22732BC6}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {31457B2B-09A2-48F9-98EA-F30F22732BC6}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {31457B2B-09A2-48F9-98EA-F30F22732BC6}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /CS2-CustomVotes/CS2-CustomVotes.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | CS2_CustomVotes 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /CS2-CustomVotes/Extensions/PlayerControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Modules.Utils; 3 | 4 | namespace CS2_CustomVotes.Extensions; 5 | 6 | public static class PlayerControllerExtensions 7 | { 8 | internal static bool IsPlayer(this CCSPlayerController? player) 9 | { 10 | return player is { IsValid: true, IsHLTV: false, IsBot: false, UserId: not null, SteamID: >0 }; 11 | } 12 | 13 | internal static string GetTeamString(this CCSPlayerController? player, bool abbreviate = false) 14 | { 15 | var teamNum = player != null ? (int)player.Team : -1; 16 | var teamStr = teamNum switch 17 | { 18 | (int)CsTeam.None => "team.none", 19 | (int)CsTeam.CounterTerrorist => "team.ct", 20 | (int)CsTeam.Terrorist => "team.t", 21 | (int)CsTeam.Spectator => "team.spec", 22 | _ => "" 23 | }; 24 | 25 | return abbreviate ? teamStr + ".short" : teamStr + ".long"; 26 | } 27 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/Factories/ActiveVoteFactory.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Modules.Menu; 3 | using CS2_CustomVotes.Models; 4 | using CSSharpUtils.Utils; 5 | using Microsoft.Extensions.Localization; 6 | 7 | namespace CS2_CustomVotes.Factories; 8 | 9 | public interface IActiveVoteFactory 10 | { 11 | public ActiveVote Create(CustomVote vote, Action onEndVote, Action onPlayerVoted); 12 | } 13 | 14 | public class ActiveVoteFactory : IActiveVoteFactory 15 | { 16 | private readonly CustomVotes _plugin; 17 | private readonly IStringLocalizer _localizer; 18 | 19 | public ActiveVoteFactory(CustomVotes plugin, IStringLocalizer localizer) 20 | { 21 | _plugin = plugin; 22 | _localizer = localizer; 23 | } 24 | 25 | public ActiveVote Create(CustomVote vote, Action onEndVote, Action onPlayerVoted) 26 | { 27 | var activeVote = new ActiveVote(_plugin, vote); 28 | 29 | // start vote timeout and save handle 30 | activeVote.VoteTimeout = _plugin.AddTimer(activeVote.Vote.TimeToVote, () => onEndVote(vote.Command)); 31 | 32 | // set vote style (global override always takes priority) 33 | var style = _plugin.Config.ForceStyle == "none" ? vote.Style : _plugin.Config.ForceStyle; 34 | 35 | if (style == "center") 36 | activeVote.VoteMenu = new CenterHtmlMenu(activeVote.Vote.Description, _plugin); 37 | else 38 | activeVote.VoteMenu = new ChatMenu(activeVote.Vote.Description); 39 | 40 | foreach (var voteOption in activeVote.Vote.Options) 41 | activeVote.VoteMenu.AddMenuOption(style == "center" ? _localizer[voteOption.Key] : ChatUtils.FormatMessage(_localizer[voteOption.Value.Text]), 42 | (caller, option) => onPlayerVoted(caller, option.Text)); 43 | 44 | return activeVote; 45 | } 46 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/Models/ActiveVote.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Modules.Menu; 3 | using CS2_CustomVotes.Extensions; 4 | using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; 5 | 6 | namespace CS2_CustomVotes.Models; 7 | 8 | public class ActiveVote 9 | { 10 | private readonly CustomVotes _plugin; 11 | public ActiveVote(CustomVotes plugin, CustomVote vote) 12 | { 13 | _plugin = plugin; 14 | Vote = vote; 15 | OptionVotes = vote.Options.ToDictionary(x => x.Key, _ => new List()); 16 | } 17 | 18 | public CustomVote Vote { get; set; } 19 | public Dictionary> OptionVotes { get; set; } 20 | 21 | public Timer? VoteTimeout { get; set; } 22 | public BaseMenu? VoteMenu { get; set; } 23 | 24 | public void OpenMenuForAll() 25 | { 26 | var players = Utilities.GetPlayers().Where(p => p.IsPlayer()).ToList(); 27 | foreach (var player in players) 28 | { 29 | // open vote menu for player 30 | if (VoteMenu is CenterHtmlMenu) 31 | MenuManager.OpenCenterHtmlMenu(_plugin, player, (VoteMenu! as CenterHtmlMenu)!); 32 | else 33 | MenuManager.OpenChatMenu(player, (VoteMenu! as ChatMenu)!); 34 | } 35 | } 36 | public void CloseMenuForAll() 37 | { 38 | if (VoteMenu is null) 39 | return; 40 | 41 | var players = Utilities.GetPlayers().Where(p => p.IsPlayer()).ToList(); 42 | foreach (var player in players) 43 | MenuManager.CloseActiveMenu(player); 44 | } 45 | 46 | public KeyValuePair> GetWinningOption() 47 | { 48 | if (OptionVotes.All(o => o.Value.Count == 0)) 49 | return new KeyValuePair>(Vote.DefaultOption, []); 50 | 51 | var winningOption = OptionVotes.MaxBy(x => x.Value.Count); 52 | var totalVotes = OptionVotes.Sum(x => x.Value.Count); 53 | 54 | if (Vote.MinVotePercentage < 0 || winningOption.Value.Count >= totalVotes * Vote.MinVotePercentage / 100) 55 | return winningOption; 56 | 57 | return new KeyValuePair>(Vote.DefaultOption, []); 58 | } 59 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/Models/CustomVote.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CounterStrikeSharp.API; 3 | using CounterStrikeSharp.API.Core; 4 | using CounterStrikeSharp.API.Modules.Admin; 5 | using CS2_CustomVotes.Shared.Models; 6 | 7 | namespace CS2_CustomVotes.Models; 8 | 9 | public class Permission 10 | { 11 | [JsonPropertyName("RequiresAll")] 12 | public bool RequiresAll { get; init; } 13 | 14 | [JsonPropertyName("Permissions")] 15 | public List Permissions { get; init; } = []; 16 | } 17 | 18 | public class CustomVote 19 | { 20 | [JsonPropertyName("Command")] 21 | public string Command { get; init; } = null!; 22 | [JsonPropertyName("CommandAliases")] 23 | public List CommandAliases { get; init; } = new(); 24 | 25 | [JsonPropertyName("Description")] 26 | public string Description { get; init; } = null!; 27 | 28 | [JsonPropertyName("TimeToVote")] 29 | public float TimeToVote { get; init; } 30 | 31 | [JsonPropertyName("Options")] 32 | public Dictionary Options { get; init; } = new(); 33 | 34 | [JsonPropertyName("DefaultOption")] 35 | public string DefaultOption { get; init; } = null!; 36 | 37 | [JsonPropertyName("Style")] 38 | public string Style { get; init; } = null!; 39 | 40 | [JsonPropertyName("MinVotePercentage")] 41 | public int MinVotePercentage { get; init; } = -1; 42 | 43 | [JsonPropertyName("Permission")] 44 | public Permission Permission { get; init; } = new(); 45 | 46 | [JsonIgnore] 47 | public float LastVoteTime { get; set; } = 0; 48 | 49 | public void ExecuteCommand(string? optionName = null) 50 | { 51 | optionName ??= DefaultOption; 52 | 53 | var optionCommands = Options.TryGetValue(optionName, out var option ) ? option.Commands : new List(); 54 | 55 | foreach (var command in optionCommands) 56 | Server.ExecuteCommand(command); 57 | } 58 | 59 | public bool CheckPermissions(CCSPlayerController? player) 60 | { 61 | if (Permission.Permissions.Count == 0) 62 | return true; 63 | 64 | return Permission.RequiresAll 65 | ? AdminManager.PlayerHasPermissions(player, Permission.Permissions.ToArray()) 66 | : Permission.Permissions.Any(permission => AdminManager.PlayerHasPermissions(player, permission)); 67 | } 68 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/Plugin.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Core.Attributes; 3 | using CounterStrikeSharp.API.Core.Attributes.Registration; 4 | using CounterStrikeSharp.API.Core.Capabilities; 5 | using CounterStrikeSharp.API.Modules.Admin; 6 | using CounterStrikeSharp.API.Modules.Commands; 7 | using CS2_CustomVotes.Factories; 8 | using CS2_CustomVotes.Services; 9 | using CS2_CustomVotes.Shared; 10 | using CSSharpUtils.Extensions; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace CS2_CustomVotes; 15 | 16 | [MinimumApiVersion(191)] 17 | public class CustomVotes : BasePlugin, IPluginConfig 18 | { 19 | public override string ModuleName => "Custom Votes"; 20 | public override string ModuleDescription => "Allows you to create custom votes for your server."; 21 | public override string ModuleVersion => "1.1.3"; 22 | public override string ModuleAuthor => "imi-tat0r"; 23 | 24 | public CustomVotesConfig Config { get; set; } = null!; 25 | 26 | private readonly ILogger _logger; 27 | private readonly IServiceProvider _serviceProvider; 28 | 29 | private static PluginCapability CustomVoteCapability { get; } = new("custom_votes:api"); 30 | 31 | public CustomVotes(ILogger logger, IServiceProvider serviceProvider) 32 | { 33 | _logger = logger; 34 | _serviceProvider = serviceProvider; 35 | } 36 | 37 | public override void Load(bool hotReload) 38 | { 39 | base.Load(hotReload); 40 | 41 | _logger.LogInformation("[CustomVotes] Registering custom vote API"); 42 | var customVoteApi = _serviceProvider.GetRequiredService(); 43 | Capabilities.RegisterPluginCapability(CustomVoteCapability, () => customVoteApi); 44 | 45 | _logger.LogInformation("[CustomVotes] Registering event handlers"); 46 | var voteManager = _serviceProvider.GetRequiredService(); 47 | RegisterEventHandler(voteManager.OnPlayerConnectFull); 48 | RegisterEventHandler(voteManager.OnPlayerDisconnect); 49 | } 50 | 51 | public void OnConfigParsed(CustomVotesConfig config) 52 | { 53 | Config = config; 54 | 55 | var voteManager = _serviceProvider.GetRequiredService(); 56 | 57 | foreach (var customVote in Config.CustomVotes) 58 | voteManager.AddVote(customVote); 59 | 60 | config.Update(); 61 | } 62 | 63 | [ConsoleCommand("css_reload_cfg", "Reload the config in the current session without restarting the server")] 64 | [RequiresPermissions("@css/generic")] 65 | [CommandHelper(minArgs: 0, whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] 66 | public void OnReloadConfigCommand(CCSPlayerController? player, CommandInfo info) 67 | { 68 | try 69 | { 70 | OnConfigParsed(new CustomVotesConfig().Reload()); 71 | } 72 | catch (Exception e) 73 | { 74 | info.ReplyToCommand($"[DiscordChatSync] Failed to reload config: {e.Message}"); 75 | } 76 | } 77 | } 78 | 79 | public class CustomVotesServiceCollection : IPluginServiceCollection 80 | { 81 | public void ConfigureServices(IServiceCollection serviceCollection) 82 | { 83 | serviceCollection.AddSingleton(this); 84 | serviceCollection.AddSingleton(); 85 | serviceCollection.AddSingleton(); 86 | serviceCollection.AddScoped(); 87 | 88 | serviceCollection.AddLogging(options => 89 | { 90 | options.AddConsole(); 91 | }); 92 | } 93 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/PluginConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using CounterStrikeSharp.API.Core; 3 | using CS2_CustomVotes.Models; 4 | using CS2_CustomVotes.Shared.Models; 5 | 6 | namespace CS2_CustomVotes; 7 | 8 | public class CustomVotesConfig : BasePluginConfig 9 | { 10 | [JsonPropertyName("CustomVotesEnabled")] 11 | public bool CustomVotesEnabled { get; set; } = true; 12 | 13 | [JsonPropertyName("VoteCooldown")] 14 | public float VoteCooldown { get; set; } = 60; 15 | 16 | [JsonPropertyName("ChatPrefix")] 17 | public string ChatPrefix { get; set; } = "[{DarkBlue}CustomVotes{Default}]"; 18 | 19 | [JsonPropertyName("ForceStyle")] 20 | public string ForceStyle { get; set; } = "none"; 21 | 22 | [JsonPropertyName("CustomVotes")] 23 | public List CustomVotes { get; set; } = 24 | [ 25 | new CustomVote() 26 | { 27 | Command = "cheats", 28 | Description = "Vote to enable sv_cheats", 29 | TimeToVote = 30, 30 | Options = new Dictionary() 31 | { 32 | { "Enable", new("{Green}Enable", ["sv_cheats 1"]) }, 33 | { "Disable", new("{Red}Disable", ["sv_cheats 0"]) } 34 | }, 35 | DefaultOption = "Disable", 36 | Style = "center", 37 | MinVotePercentage = 50, 38 | Permission = new Permission 39 | { 40 | RequiresAll = false, 41 | Permissions = [] 42 | } 43 | } 44 | ]; 45 | 46 | [JsonPropertyName("ConfigVersion")] 47 | public override int Version { get; set; } = 2; 48 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/Services/CustomVoteApi.cs: -------------------------------------------------------------------------------- 1 | using CS2_CustomVotes.Shared; 2 | using CounterStrikeSharp.API.Core; 3 | using CS2_CustomVotes.Shared.Models; 4 | 5 | namespace CS2_CustomVotes.Services; 6 | 7 | public class CustomVoteApi : ICustomVoteApi 8 | { 9 | private readonly IVoteManager _voteManager; 10 | 11 | public CustomVoteApi(IVoteManager voteManager) 12 | { 13 | _voteManager = voteManager; 14 | } 15 | 16 | public void AddCustomVote(string name, string description, string defaultOption, float timeToVote, Dictionary options, string style) 17 | { 18 | _voteManager.AddVote(name, [], description, defaultOption, timeToVote, options, style, -1); 19 | } 20 | 21 | public void AddCustomVote(string name, List aliases, string description, string defaultOption, float timeToVote, 22 | Dictionary options, string style) 23 | { 24 | _voteManager.AddVote(name, aliases, description, defaultOption, timeToVote, options, style, -1); 25 | } 26 | 27 | public void AddCustomVote(string name, string description, string defaultOption, float timeToVote, Dictionary options, string style, int minVotePercentage) 28 | { 29 | _voteManager.AddVote(name, [], description, defaultOption, timeToVote, options, style, minVotePercentage); 30 | } 31 | 32 | public void AddCustomVote(string name, List aliases, string description, string defaultOption, float timeToVote, 33 | Dictionary options, string style, int minVotePercentage) 34 | { 35 | _voteManager.AddVote(name, aliases, description, defaultOption, timeToVote, options, style, minVotePercentage); 36 | } 37 | 38 | public void StartCustomVote(CCSPlayerController? player, string name) 39 | { 40 | _voteManager.StartVote(player, name, out string baseName); 41 | } 42 | 43 | public void EndCustomVote(string name) 44 | { 45 | _voteManager.EndVote(name); 46 | } 47 | 48 | public void RemoveCustomVote(string name) 49 | { 50 | _voteManager.RemoveVote(name); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CS2-CustomVotes/Services/VoteManager.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Commands; 4 | using CounterStrikeSharp.API.Modules.Menu; 5 | using CS2_CustomVotes.Extensions; 6 | using CS2_CustomVotes.Factories; 7 | using CS2_CustomVotes.Models; 8 | using CS2_CustomVotes.Shared.Models; 9 | using CSSharpUtils.Utils; 10 | using Microsoft.Extensions.Localization; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace CS2_CustomVotes.Services; 14 | 15 | public interface IVoteManager 16 | { 17 | public void AddVote(CustomVote vote); 18 | public void AddVote(string name, List aliases, string description, string defaultOption, float timeToVote, Dictionary options, string style = "center", int minVotePercentage = 50); 19 | public void RemoveVote(string name); 20 | 21 | public bool StartVote(CCSPlayerController? player, string name, out string baseName); 22 | public void EndVote(string name); 23 | 24 | public void OnPlayerVoted(CCSPlayerController? player, string option); 25 | public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo _); 26 | public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo _); 27 | } 28 | 29 | public class VoteManager : IVoteManager 30 | { 31 | private readonly ILogger _logger; 32 | private readonly CustomVotes _plugin; 33 | private readonly IStringLocalizer _localizer; 34 | private readonly IActiveVoteFactory _activeVoteFactory; 35 | 36 | public VoteManager(ILogger logger, CustomVotes plugin, IStringLocalizer localizer, IActiveVoteFactory activeVoteFactory) 37 | { 38 | _logger = logger; 39 | _plugin = plugin; 40 | _localizer = localizer; 41 | _activeVoteFactory = activeVoteFactory; 42 | } 43 | private Dictionary Votes { get; set; } = new(); 44 | private ActiveVote? ActiveVote { get; set; } 45 | private float _nextVoteTime; 46 | 47 | public void AddVote(CustomVote vote) 48 | { 49 | if (vote.Options.Count < 2) 50 | { 51 | _logger.LogWarning("[CustomVotes] Vote {Name} must have at least 2 options", vote.Command); 52 | return; 53 | } 54 | 55 | if (!vote.Options.TryGetValue(vote.DefaultOption, out _)) 56 | { 57 | _logger.LogWarning("[CustomVotes] Default option {Option} of {Name} is invalid", vote.DefaultOption, vote.Command); 58 | return; 59 | } 60 | 61 | if (!Votes.TryAdd(vote.Command, vote)) 62 | { 63 | _logger.LogWarning("[CustomVotes] Vote {Name} already exists", vote.Command); 64 | return; 65 | } 66 | 67 | _logger.LogInformation("[CustomVotes] Vote {Name} added", vote.Command); 68 | 69 | _plugin.AddCommand(vote.Command, vote.Description, HandleVoteStartRequest); 70 | foreach (var alias in vote.CommandAliases) 71 | _plugin.AddCommand(alias, vote.Description, HandleVoteStartRequest); 72 | 73 | vote.ExecuteCommand(); 74 | } 75 | public void AddVote(string name, List aliases, string description, string defaultOption, float timeToVote, Dictionary options, string style = "center", int minVotePercentage = 50) 76 | { 77 | var customVote = new CustomVote 78 | { 79 | Command = name, 80 | CommandAliases = aliases, 81 | Description = description, 82 | DefaultOption = defaultOption, 83 | TimeToVote = timeToVote, 84 | Options = options, 85 | Style = style, 86 | MinVotePercentage = minVotePercentage, 87 | }; 88 | AddVote(customVote); 89 | } 90 | public void RemoveVote(string name) 91 | { 92 | if (!Votes.ContainsKey(name)) 93 | _logger.LogWarning("[CustomVotes] Vote {Name} does not exist", name); 94 | 95 | _plugin.RemoveCommand(name, HandleVoteStartRequest); 96 | foreach (var alias in Votes[name].CommandAliases) 97 | _plugin.RemoveCommand(alias, HandleVoteStartRequest); 98 | 99 | if (!Votes.Remove(name)) 100 | _logger.LogWarning("[CustomVotes] Could not remove {Name}", name); 101 | 102 | _logger.LogInformation("[CustomVotes] Vote {Name} removed", name); 103 | } 104 | 105 | public bool StartVote(CCSPlayerController? player, string name, out string baseName) 106 | { 107 | // check if vote exists 108 | if (!Votes.TryGetValue(name, out var vote)) 109 | // might be an alias 110 | vote ??= Votes.FirstOrDefault(v => v.Value.CommandAliases.Contains(name)).Value; 111 | 112 | // set base name for logging and chat message 113 | baseName = vote?.Command ?? string.Empty; 114 | 115 | if (vote == null) 116 | { 117 | _logger.LogWarning("[CustomVotes] Vote {Name} does not exist", name); 118 | return false; 119 | } 120 | 121 | if (_nextVoteTime > Server.CurrentTime) 122 | { 123 | player!.PrintToChat($"{ChatUtils.FormatMessage(_plugin.Config.ChatPrefix)} {ChatUtils.FormatMessage(_localizer["vote.cooldown", _plugin.Config.VoteCooldown])}"); 124 | return false; 125 | } 126 | 127 | if (!vote.CheckPermissions(player)) 128 | { 129 | player!.PrintToChat($"{ChatUtils.FormatMessage(_plugin.Config.ChatPrefix)} {ChatUtils.FormatMessage(_localizer["vote.no_permission"])}"); 130 | return false; 131 | } 132 | 133 | // create new active vote 134 | ActiveVote = _activeVoteFactory.Create(vote, EndVote, OnPlayerVoted); 135 | ActiveVote.OpenMenuForAll(); 136 | 137 | _logger.LogInformation("[CustomVotes] Vote {Name} started", name); 138 | return true; 139 | } 140 | public void EndVote(string name) 141 | { 142 | if (ActiveVote == null) 143 | { 144 | _logger.LogWarning("[CustomVotes] No vote is active"); 145 | return; 146 | } 147 | 148 | if (ActiveVote.Vote.Command != name && 149 | !ActiveVote.Vote.CommandAliases.Contains(name)) 150 | { 151 | _logger.LogWarning("[CustomVotes] Vote {Name} is not active", name); 152 | return; 153 | } 154 | 155 | ProcessVoteResults(); 156 | 157 | // kill vote timeout timer and reset active vote 158 | ActiveVote.CloseMenuForAll(); 159 | ActiveVote.VoteTimeout?.Kill(); 160 | ActiveVote = null; 161 | 162 | // set next vote time to prevent spam 163 | _nextVoteTime = Server.CurrentTime + _plugin.Config.VoteCooldown; 164 | 165 | _logger.LogInformation("[CustomVotes] Vote {Name} ended", name); 166 | } 167 | 168 | public void OnPlayerVoted(CCSPlayerController? player, string option) 169 | { 170 | if (ActiveVote == null) 171 | { 172 | _logger.LogWarning("[CustomVotes] No vote is active"); 173 | return; 174 | } 175 | 176 | // clean any color codes etc 177 | option = ChatUtils.CleanMessage(option); 178 | 179 | if (!ActiveVote.Vote.Options.ContainsKey(option)) 180 | { 181 | _logger.LogWarning("[CustomVotes] Option {Option} does not exist", option); 182 | return; 183 | } 184 | 185 | if (!player.IsPlayer()) 186 | { 187 | _logger.LogDebug("[CustomVotes] Voter is not a valid player"); 188 | return; 189 | } 190 | 191 | // mark player index as voted 192 | if (!ActiveVote.OptionVotes[option].Contains(player!.Pawn.Index)) 193 | ActiveVote.OptionVotes[option].Add(player.Pawn.Index); 194 | else 195 | { 196 | _logger.LogDebug("[CustomVotes] Player {Name} already voted", player.PlayerName); 197 | player.PrintToChat($"{ChatUtils.FormatMessage(_plugin.Config.ChatPrefix)} {ChatUtils.FormatMessage(_localizer["vote.already_voted"])}"); 198 | } 199 | 200 | var players = Utilities.GetPlayers().Select(p => p.Pawn.Index); 201 | var votePlayers = ActiveVote.OptionVotes.Values.SelectMany(p => p).Distinct(); 202 | 203 | // if all players voted, end vote early 204 | if (votePlayers.All(players.Contains)) 205 | EndVote(ActiveVote.Vote.Command); 206 | } 207 | public HookResult OnPlayerConnectFull(EventPlayerConnectFull @event, GameEventInfo _) 208 | { 209 | var player = @event.Userid; 210 | 211 | if (!player.IsPlayer()) 212 | return HookResult.Continue; 213 | 214 | if (ActiveVote == null) 215 | return HookResult.Continue; 216 | 217 | // open vote menu for player 218 | if (ActiveVote.VoteMenu is CenterHtmlMenu) 219 | MenuManager.OpenCenterHtmlMenu(_plugin, player!, (ActiveVote.VoteMenu! as CenterHtmlMenu)!); 220 | else 221 | MenuManager.OpenChatMenu(player!, (ActiveVote.VoteMenu! as ChatMenu)!); 222 | 223 | return HookResult.Continue; 224 | } 225 | public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo _) 226 | { 227 | var player = @event.Userid; 228 | 229 | if (!player.IsPlayer()) 230 | return HookResult.Continue; 231 | 232 | if (ActiveVote == null) 233 | return HookResult.Continue; 234 | 235 | // remove player vote from all options 236 | foreach (var option in ActiveVote.OptionVotes.Where(option => option.Value.Contains(player!.Pawn.Index))) 237 | option.Value.Remove(player!.Pawn.Index); 238 | 239 | return HookResult.Continue; 240 | } 241 | 242 | private void ProcessVoteResults() 243 | { 244 | if (ActiveVote == null) 245 | { 246 | _logger.LogWarning("[CustomVotes] No vote is active"); 247 | return; 248 | } 249 | 250 | var winningOption = ActiveVote.GetWinningOption(); 251 | 252 | // announce winner and execute commands 253 | Server.PrintToChatAll($"{ChatUtils.FormatMessage(_plugin.Config.ChatPrefix)} {ChatUtils.FormatMessage(_localizer["vote.finished_with", ActiveVote.Vote.Command, winningOption.Key, winningOption.Value.Count])}"); 254 | ActiveVote.Vote.ExecuteCommand(winningOption.Key); 255 | 256 | _logger.LogInformation("[CustomVotes] Vote for {Name} ended with {Option}", ActiveVote.Vote.Command, ChatUtils.CleanMessage(winningOption.Key)); 257 | } 258 | 259 | private void HandleVoteStartRequest(CCSPlayerController? player, CommandInfo info) 260 | { 261 | if (!_plugin.Config.CustomVotesEnabled) 262 | { 263 | player!.PrintToChat($"{ChatUtils.FormatMessage(_plugin.Config.ChatPrefix)} {ChatUtils.FormatMessage(_localizer["vote.disabled"])}"); 264 | _logger.LogWarning("[CustomVotes] Custom votes are disabled"); 265 | return; 266 | } 267 | 268 | if (!player.IsPlayer()) 269 | { 270 | _logger.LogDebug("[CustomVotes] Voter is not a valid player"); 271 | return; 272 | } 273 | 274 | if (ActiveVote != null) 275 | { 276 | player!.PrintToChat($"{ChatUtils.FormatMessage(_plugin.Config.ChatPrefix)} {ChatUtils.FormatMessage(_localizer["vote.active"])}"); 277 | return; 278 | } 279 | 280 | if (StartVote(player, info.GetArg(0), out var baseName)) 281 | Server.PrintToChatAll($"{ChatUtils.FormatMessage(_plugin.Config.ChatPrefix)} {ChatUtils.FormatMessage(_localizer["vote.started", player!.PlayerName, baseName])}"); 282 | } 283 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/lang/cz.json: -------------------------------------------------------------------------------- 1 | { 2 | "vote.disabled": "Speciální hlasování jsou {DarkBlue}vypnuta{Default}.", 3 | "vote.active": "Hlasování již probíhá.", 4 | "vote.started": "Hráč {DarkBlue}{0}{Default} spustil hlasování pro {DarkBlue}{1}{Default}.", 5 | "vote.cooldown": "Prosím, počkejte {DarkBlue}{0}{Default} sekund mezi hlasováním.", 6 | "vote.already_voted": "Již jste hlasovali.", 7 | "vote.finished_with": "Hlasování pro {DarkBlue}{0}{Default} skončilo s výsledkem {DarkBlue}{1}{Default} ({2} hlasy).", 8 | "vote.no_permission": "Nemáte potřebné oprávnění k zahájení tohoto hlasování." 9 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/lang/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "vote.disabled": "Custom Votes sind {DarkBlue}deaktiviert{Default}.", 3 | "vote.active": "Es läuft bereits eine Abstimmung.", 4 | "vote.started": "Spieler {DarkBlue}{0}{Default} hat eine Abstimmung über {DarkBlue}{1}{Default} gestartet.", 5 | "vote.cooldown": "Bitte warte {DarkBlue}{0}{Default} Sekunden zwischen den Abstimmungen.", 6 | "vote.already_voted": "Du hast bereits abgestimmt.", 7 | "vote.finished_with": "Abstimmung für {DarkBlue}{0}{Default} wurde mit {DarkBlue}{1}{Default} ({2} Stimmen) beendet.", 8 | "vote.no_permission": "Du hast nicht die nötigen Berechtigungen, um diese Abstimmung zu starten." 9 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "vote.disabled": "Custom Votes are {DarkBlue}disabled{Default}.", 3 | "vote.active": "A vote is already in progress.", 4 | "vote.started": "Player {DarkBlue}{0}{Default} started vote for {DarkBlue}{1}{Default}.", 5 | "vote.cooldown": "Please wait {DarkBlue}{0}{Default} seconds between votes.", 6 | "vote.already_voted": "You have already voted.", 7 | "vote.finished_with": "Vote for {DarkBlue}{0}{Default} ended with {DarkBlue}{1}{Default} ({2} votes).", 8 | "vote.no_permission": "You do not have the required permission to start this vote." 9 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/lang/pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "vote.disabled": "As Votações Customizadas estão {DarkBlue}desabilitadas{Default}.", 3 | "vote.active": "Uma votação está em progresso.", 4 | "vote.started": "O Player {DarkBlue}{0}{Default} iniciou uma votação para {DarkBlue}{1}{Default}.", 5 | "vote.cooldown": "Espere {DarkBlue}{0}{Default} segundos entre votações.", 6 | "vote.already_voted": "Você ja votou!", 7 | "vote.finished_with": "Vote para {DarkBlue}{0}{Default}. Acaba em {DarkBlue}{1}{Default} ({2} votos).", 8 | "vote.no_permission": "Você não tem permissões suficientes para iniciar uma votação." 9 | } 10 | -------------------------------------------------------------------------------- /CS2-CustomVotes/lang/pt-pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "vote.disabled": "Os votos personalizados estão {DarkBlue}DESATIVADOS{Default}!", 3 | "vote.active": "Já está a decorrer uma votação!", 4 | "vote.started": "O jogador {DarkBlue}{0}{Default} começou uma votação para {DarkBlue}{1}{Default}", 5 | "vote.cooldown": "Por favor espere {DarkBlue}{0}{Default} segundos entre as votações!", 6 | "vote.already_voted": "Já votou!", 7 | "vote.finished_with": "Votar para {DarkBlue}{0}{Default} acaba em {DarkBlue}{1}{Default} ({2} votos).", 8 | "vote.no_permission": "Não tem a permissão necessária para iniciar esta votação..." 9 | } -------------------------------------------------------------------------------- /CS2-CustomVotes/lang/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "vote.disabled": "Custom Votes {DarkBlue}отключен{Default}.", 3 | "vote.active": "Голосование уже началось.", 4 | "vote.started": "Игрок {DarkBlue}{0}{Default} начал голосование {DarkBlue}{1}{Default}.", 5 | "vote.cooldown": "Пожалуйста, подождите {DarkBlue}{0}{Default} секунд между голосованиями.", 6 | "vote.already_voted": "Вы уже проголосовали.", 7 | "vote.finished_with": "Голосование {DarkBlue}{0}{Default} закончилось с {DarkBlue}{1}{Default} ({2} голосов).", 8 | "vote.no_permission": "У вас нет прав для начала этого голосования." 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 imi-tat0r 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ![Copyright ev0lve Digital](https://img.shields.io/badge/Copyright-ev0lve%20Digital-blue) ![GitHub License](https://img.shields.io/github/license/imi-tat0r/CS2-CustomVotes) ![Issues](https://img.shields.io/github/issues/imi-tat0r/CS2-CustomVotes) ![Downloads](https://img.shields.io/github/downloads/imi-tat0r/CS2-CustomVotes/total) ![Stars](https://img.shields.io/github/stars/imi-tat0r/CS2-CustomVotes) 2 | 3 | # CS2 Custom Votes (1.1.3) 4 | ![image](https://du.hurenso.hn/r/X4jQaq.png) ![image](https://du.hurenso.hn/r/81Imq8.png) 5 | 6 | # About 7 | CS2-CustomVotes is a plugin for Counter-Strike 2 that allows you to easily create preference votes for your server. Create votes with any number of options and let your players decide on the settings of your server. 8 | Every vote options can trigger multiple commands at once allowing for a wide range of possibilities. 9 | 10 | # Features 11 | - Custom votes with any number of options 12 | - Execute multiple commands per vote option 13 | - Customizable duration for each vote 14 | - Cooldown between votes 15 | - Localized chat messages 16 | - **CS2-CustomVotes.Shared API** for other plugins to register votes 17 | 18 | # Dependencies 19 | [Metamod:Source (2.x)](https://www.sourcemm.net/downloads.php/?branch=master) 20 | [CounterStrikeSharp(v191)](https://github.com/roflmuffin/CounterStrikeSharp/releases) 21 | 22 | # Installation 23 | 1. Install Metamod:Source and CounterStrikeSharp 24 | 2. Place the `addons` folder in your servers `game/csgo/` directory 25 | ![extract](https://du.hurenso.hn/r/0NyFPY.png) 26 | 3. Add your custom votes to the config file 27 | 3.1. Located at `addons/counterstrikesharp/configs/plugins/CS2-CustomVotes/CS2-CustomVotes.json` 28 | 4. Restart your server 29 | 30 | # Config 31 | ```json 32 | { 33 | "CustomVotesEnabled": true, // global enable/disable for custom votes 34 | "VoteCooldown": 60, // cooldown between votes in seconds 35 | "ChatPrefix": "[{DarkBlue}CustomVotes{Default}]", // chat prefix for plugin messages, supports all ChatColors 36 | "ForceStyle": "none", // "none", "center" or "chat" - none will use the style from the vote settings 37 | "CustomVotes": [ // list of custom votes 38 | { 39 | "Command": "cheats", // command to trigger the vote 40 | "CommandAliases": [ // aliases for the command 41 | "sv_cheats" 42 | ], 43 | "Description": "Vote to enable sv_cheats", // description of the vote, will be displayed in the vote menu 44 | "TimeToVote": 30, // time in seconds players have to vote 45 | "Options": { // vote options 46 | "Enable": { // name of the option 47 | "Text": "{Green}Enable", // text to display in the vote menu (supports ChatColors when using the "chat" style) 48 | "Commands": [ // commands to execute when the option is selected 49 | "sv_cheats 1" 50 | ] 51 | }, 52 | "Disable": { 53 | "Text": "{Red}Disable", 54 | "Commands": [ 55 | "sv_cheats 0" 56 | ] 57 | } 58 | }, 59 | "DefaultOption": "Disable", // default option (must be a key from the "Options" object) 60 | "Style": "chat", // Menu style - "center" or "chat" (will be overridden by the global ForceStyle if not "none") 61 | "MinVotePercentage": -1, // minimum percentage of votes required to pass the vote (-1 behaves like 50%) 62 | "Permission": { 63 | "RequiresAll": false, // if true, all permissions must be present to vote 64 | "Permissions": [] // list of permissions required to start this vote (empty list allows everyone to start the vote) 65 | } 66 | } 67 | ], 68 | "ConfigVersion": 2 69 | } 70 | ``` 71 | 72 | # API 73 | CS2-CustomVotes provides a shared API for other plugins to register custom votes. 74 | 75 | Add a reference to `CS2-CustomVotes.Shared` to your project in one of the following ways: 76 | 1. Download the source code and build it yourself 77 | 2. Download the latest release from the [releases page](https://github.com/imi-tat0r/CS2-CustomVotes/releases). 78 | 3. Install the package using the .NET CLI, run: `dotnet add package CS2-CustomVotes.Shared` 79 | 80 | After adding the reference to your project, you can create custom votes like this: 81 | ```csharp 82 | public interface ICustomVoteApi 83 | { 84 | public void AddCustomVote(string name, string description, string defaultOption, float timeToVote, Dictionary options, string style); 85 | public void AddCustomVote(string name, List aliases, string description, string defaultOption, float timeToVote, Dictionary options, string style); 86 | public void AddCustomVote(string name, string description, string defaultOption, float timeToVote, Dictionary options, string style, int minVotePercentage); 87 | public void AddCustomVote(string name, List aliases, string description, string defaultOption, float timeToVote, Dictionary options, string style, int minVotePercentage); 88 | public void StartCustomVote(CCSPlayerController? player, string name); 89 | public void EndCustomVote(string name); 90 | public void RemoveCustomVote(string name); 91 | } 92 | 93 | customVotesApi.AddCustomVote( 94 | "cheats", // command to trigger the vote 95 | new List(), // aliases for the command (optional) 96 | "Vote to enable sv_cheats", // description of the vote, will be displayed in the vote menu 97 | "Disable", // default option (must be a key from the "Options" object) 98 | 30, // time in seconds players have to vote 99 | new Dictionary // vote options 100 | { 101 | { "Enable", new VoteOption("{Green}Enable", new List { "sv_cheats 1" })}, 102 | { "Disable", new VoteOption("{Red}Disable", new List { "sv_cheats 0" })}, 103 | }, 104 | "chat", // Menu style - "center" or "chat" 105 | -1); // minimum percentage of votes required to pass the vote (-1 behaves like 50%) 106 | 107 | customVotesApi.RemoveCustomVote("cheats"); 108 | ``` 109 | 110 | # Credits 111 | - [Metamod:Source](https://www.sourcemm.net/) 112 | - [CounterStrikeSharp](https://github.com/roflmuffin/CounterStrikeSharp) 113 | --------------------------------------------------------------------------------