├── .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 |     
2 |
3 | # CS2 Custom Votes (1.1.3)
4 |  
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 | 
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 |
--------------------------------------------------------------------------------