├── .gitignore ├── Modules ├── Config.cs ├── Core.cs ├── Database.cs ├── Handlers │ ├── Commands.cs │ ├── Events.cs │ └── Listeners.cs ├── Models │ └── Player.cs ├── RetakeCapability.cs ├── Utils.cs ├── Votes │ ├── AsyncVoteManager.cs │ ├── AsyncVoteValidator.cs │ ├── Config.cs │ └── Votes.cs └── Weapons │ ├── Allocator.cs │ ├── Config.cs │ └── Menu.cs ├── MySqlConnector.dll ├── README.md └── RetakesAllocator.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vscode/ 4 | .idea/ 5 | -------------------------------------------------------------------------------- /Modules/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using CounterStrikeSharp.API.Modules.Utils; 3 | using MySqlConnector; 4 | 5 | using static RetakesAllocator.Modules.Core; 6 | 7 | namespace RetakesAllocator.Modules; 8 | 9 | public class Config 10 | { 11 | public ConnectionConfig DbConnection { get; init; } = null!; 12 | public PrefixConfig Prefix { get; init; } = null!; 13 | public PistolRoundConfig PistolRound { get; init; } = null!; 14 | 15 | public bool GiveArmor {get; init;} = true; 16 | public string[] triggerWords {get; init;} = { "guns", "gun", "weapon", "weapons"}; 17 | public bool AddSkipOption {get; init;} = true; 18 | 19 | public Config() 20 | { 21 | DbConnection = new ConnectionConfig(); 22 | Prefix = new PrefixConfig(); 23 | PistolRound = new PistolRoundConfig(); 24 | GiveArmor = true; 25 | triggerWords = new string[] { "guns", "gun", "weapon", "weapons"}; 26 | AddSkipOption = true; 27 | } 28 | 29 | public bool IsValid() 30 | { 31 | return DbConnection.Database != string.Empty && DbConnection.Host != string.Empty && DbConnection.User != string.Empty && DbConnection.Password != string.Empty && 0 < DbConnection.Port && DbConnection.Port < 65535; 32 | } 33 | 34 | public string BuildConnectionString() 35 | { 36 | var builder = new MySqlConnectionStringBuilder 37 | { 38 | Database = DbConnection.Database, 39 | UserID = DbConnection.User, 40 | Password = DbConnection.Password, 41 | Server = DbConnection.Host, 42 | Port = DbConnection.Port, 43 | }; 44 | 45 | return builder.ConnectionString; 46 | } 47 | } 48 | 49 | public class ConnectionConfig 50 | { 51 | public string Host { get; init; } = string.Empty; 52 | public string Database { get; init; } = string.Empty; 53 | public string User { get; init; } = string.Empty; 54 | public string Password { get; init; } = string.Empty; 55 | public uint Port { get; init; } = 3306; 56 | } 57 | 58 | public class PrefixConfig 59 | { 60 | public string Prefix { get; set; } = " [\x04Retakes\x01]"; 61 | public string PrefixCon { get; set; } = "[RetakesAllocator]"; 62 | } 63 | 64 | public class PistolRoundConfig 65 | { 66 | public int RoundAmount { get; init; } = 2; 67 | public string weapon_t { get; init; } = "weapon_glock"; 68 | public string weapon_ct { get; init; } = "weapon_usp_silencer"; 69 | 70 | public string GetWeaponByTeam(CsTeam team) 71 | { 72 | return (team == CsTeam.Terrorist) ? weapon_t : weapon_ct; 73 | } 74 | } 75 | 76 | public static class Configs 77 | { 78 | public const string ConfigDirectory = "configs"; 79 | private const string ConfigPath = "retakes_allocator.json"; 80 | 81 | public static Config LoadConfig() 82 | { 83 | var configPath = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, ConfigPath); 84 | if (!File.Exists(configPath)) 85 | { 86 | return CreateConfig(configPath); 87 | } 88 | 89 | var config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; 90 | 91 | return config; 92 | } 93 | 94 | private static Config CreateConfig(string configPath) 95 | { 96 | var config = new Config(); 97 | 98 | File.WriteAllText(configPath, 99 | JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); 100 | return config; 101 | } 102 | 103 | public static void CreateConfigsDirectory() 104 | { 105 | var configDirectory = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory); 106 | 107 | if (!Directory.Exists(configDirectory)) 108 | { 109 | Directory.CreateDirectory(configDirectory); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Modules/Core.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Core.Attributes; 4 | using CounterStrikeSharp.API.Modules.Cvars; 5 | 6 | using MySqlConnector; 7 | 8 | using RetakesAllocator.Modules.Models; 9 | using RetakesAllocator.Modules.Weapons; 10 | using RetakesAllocator.Modules.Votes; 11 | 12 | using static RetakesAllocator.Modules.RetakeCapability; 13 | using static RetakesAllocator.Modules.Database; 14 | using static RetakesAllocator.Modules.Utils; 15 | using static RetakesAllocator.Modules.Configs; 16 | using static RetakesAllocator.Modules.Handlers.Commands; 17 | using static RetakesAllocator.Modules.Handlers.Events; 18 | using static RetakesAllocator.Modules.Handlers.Listeners; 19 | using static RetakesAllocator.Modules.Votes.Votes; 20 | using static RetakesAllocator.Modules.Weapons.Allocator; 21 | 22 | namespace RetakesAllocator.Modules; 23 | 24 | [MinimumApiVersion(215)] 25 | public class Core : BasePlugin 26 | { 27 | public static Core Plugin = null!; 28 | 29 | public override string ModuleName => "[Retakes] Weapons Allocator"; 30 | public override string ModuleVersion => "1.2.0"; 31 | public override string ModuleAuthor => "Ravid & B3none"; 32 | public override string ModuleDescription => "Weapons Allocator plugin for retakes"; 33 | 34 | public static Config Config = null!; 35 | public static NadesConfig NadesConfig = null!; 36 | 37 | public static Database Db = null!; 38 | public static List Players = new(); 39 | public static int RoundsCounter = 0; 40 | public static AsyncVoteManager currentVote = null!; 41 | public static ConVar mp_damage_headshot_only = null!; 42 | 43 | public override void Load(bool hotReload) 44 | { 45 | Plugin = this; 46 | 47 | mp_damage_headshot_only = ConVar.Find("mp_damage_headshot_only")!; 48 | 49 | if(mp_damage_headshot_only == null!) 50 | { 51 | ThrowError("Failed to find mp_damage_headshot_only"); 52 | return; 53 | } 54 | 55 | LoadConfigs(); 56 | 57 | Connect(SQL_ConnectCallback); 58 | 59 | RegisterCommands(); 60 | RegisterEvents(); 61 | RegisterListeners(); 62 | 63 | RetakeCapability_OnLoad(); 64 | 65 | if (hotReload) 66 | { 67 | Utilities.GetPlayers().ForEach(AddPlayerToList); 68 | } 69 | } 70 | 71 | public override void Unload(bool hotReload) 72 | { 73 | UnRegisterCommands(); 74 | Votes_OnPluginUnload(); 75 | Utilities.GetPlayers().ForEach(RemovePlayerFromList); 76 | 77 | RetakeCapability_OnUnload(); 78 | } 79 | 80 | public static CCSGameRules GetGameRules() 81 | { 82 | var gameRulesEntities = Utilities.FindAllEntitiesByDesignerName("cs_gamerules"); 83 | var gameRules = gameRulesEntities.First().GameRules; 84 | 85 | if(gameRules == null!) 86 | { 87 | ThrowError("Failed to get game rules"); 88 | return null!; 89 | } 90 | 91 | return gameRules; 92 | } 93 | 94 | private static void SQL_ConnectCallback(string connectionString, Exception exception, dynamic data) 95 | { 96 | if (connectionString == null!) 97 | { 98 | ThrowError($"Failed to connect to database: {exception.Message}"); 99 | return; 100 | } 101 | 102 | Db = new Database(connectionString); 103 | 104 | PrintToServer("Connected to database"); 105 | 106 | CreateTables(); 107 | } 108 | 109 | public static void SQL_FetchUser_CB(MySqlDataReader reader, Exception exception, dynamic data) 110 | { 111 | if (exception != null!) 112 | { 113 | ThrowError($"Database error, {exception.Message}"); 114 | return; 115 | } 116 | 117 | if (reader.HasRows) 118 | { 119 | while(reader.Read()) 120 | { 121 | var tPrimary = reader.GetInt32("t_primary"); 122 | var ctPrimary = reader.GetInt32("ct_primary"); 123 | var tsecondary = reader.GetInt32("t_secondary"); 124 | var ctSecondary = reader.GetInt32("ct_secondary"); 125 | var giveAwp = (GiveAwp)reader.GetInt32("give_awp"); 126 | 127 | Player player = Players[data]; 128 | 129 | player.WeaponsAllocator.PrimaryWeaponT = tPrimary > PrimaryT.Count ? 0 : tPrimary; 130 | player.WeaponsAllocator.PrimaryWeaponCt = ctPrimary > PrimaryCt.Count ? 0 : ctPrimary; 131 | player.WeaponsAllocator.SecondaryWeaponT = tsecondary > PistolsT.Count ? 0 : tsecondary; 132 | player.WeaponsAllocator.SecondaryWeaponCt = ctSecondary > PistolsCT.Count ? 0 : ctSecondary; 133 | player.WeaponsAllocator.GiveAwp = giveAwp; 134 | } 135 | } 136 | else 137 | { 138 | Player player = Players[data]; 139 | 140 | Query(SQL_CheckForErrors, $"INSERT INTO `weapons` (`auth`, `name`) VALUES ('{player.GetSteamId2()}', '{EscapeString(player.GetName())}')"); 141 | } 142 | } 143 | 144 | public static void LoadConfigs(bool fullReload = true) 145 | { 146 | CreateConfigsDirectory(); 147 | 148 | if(fullReload) 149 | { 150 | Config = LoadConfig(); 151 | 152 | if (!Config.IsValid()) 153 | { 154 | ThrowError("Invalid config, please check your config file."); 155 | return; 156 | } 157 | 158 | Votes.Config.LoadConfig(); 159 | } 160 | 161 | Weapons.Config.LoadConfig(); 162 | 163 | PrintToServer("Configs loaded"); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Modules/Database.cs: -------------------------------------------------------------------------------- 1 | // Not in use but... **DO NOT REMOVE** 2 | using System.Linq; 3 | using MySqlConnector; 4 | 5 | using static RetakesAllocator.Modules.Utils; 6 | 7 | namespace RetakesAllocator.Modules; 8 | 9 | public class Database 10 | { 11 | private static string _connectionString = string.Empty; 12 | 13 | public delegate void ConnectCallback(string connectionString, Exception exception, dynamic data); 14 | public delegate void QueryCallback(MySqlDataReader reader, Exception exception, dynamic data); 15 | 16 | public Database(string connectionString) 17 | { 18 | _connectionString = connectionString; 19 | } 20 | 21 | public static void Connect(ConnectCallback callback, dynamic data = null!) 22 | { 23 | _connectionString = Core.Config.BuildConnectionString(); 24 | 25 | try 26 | { 27 | var connection = new MySqlConnection(_connectionString); 28 | 29 | connection.Open(); 30 | 31 | callback(_connectionString, null!, data); 32 | 33 | connection.Close(); 34 | } 35 | catch (Exception e) 36 | { 37 | callback(null!, e, data); 38 | } 39 | } 40 | 41 | public static void CreateTables() 42 | { 43 | const string query = "CREATE TABLE IF NOT EXISTS `weapons` ( `id` INT NOT NULL AUTO_INCREMENT , `auth` VARCHAR(128) NOT NULL , `name` VARCHAR(128) NOT NULL , `t_primary` INT NOT NULL DEFAULT 0, `ct_primary` INT NOT NULL DEFAULT 0, `t_secondary` INT NOT NULL DEFAULT 0, `ct_secondary` INT NOT NULL DEFAULT 0, `give_awp` INT NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE (`auth`)) ENGINE = InnoDB;"; 44 | 45 | Query(SQL_CheckForErrors, query); 46 | } 47 | 48 | public static string EscapeString(string buffer) 49 | { 50 | return MySqlHelper.EscapeString(buffer); 51 | } 52 | 53 | public static void Query(QueryCallback callback, string query, dynamic data = null!) 54 | { 55 | try 56 | { 57 | if (string.IsNullOrEmpty(query)) 58 | { 59 | ThrowError("Query cannot be null or empty."); 60 | } 61 | 62 | using var connection = new MySqlConnection(_connectionString); 63 | connection.Open(); 64 | 65 | using(var command = new MySqlCommand(query, connection)) 66 | { 67 | using(var reader = command.ExecuteReader()) 68 | { 69 | callback(reader, null!, data); 70 | } 71 | } 72 | 73 | connection.Close(); 74 | } 75 | catch (Exception e) 76 | { 77 | callback(null!, e, data); 78 | } 79 | } 80 | 81 | public static void SQL_CheckForErrors(MySqlDataReader reader, Exception exception, dynamic data) 82 | { 83 | if (exception != null!) 84 | { 85 | ThrowError($"Database error, {exception.Message}"); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Modules/Handlers/Commands.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Modules.Commands; 3 | using CounterStrikeSharp.API.Modules.Admin; 4 | 5 | using static RetakesAllocator.Modules.Core; 6 | using static RetakesAllocator.Modules.Utils; 7 | using static RetakesAllocator.Modules.Weapons.Menu; 8 | using static RetakesAllocator.Modules.Votes.Votes; 9 | using RetakesAllocator.Modules.Votes; 10 | 11 | namespace RetakesAllocator.Modules.Handlers; 12 | 13 | internal static class Commands 14 | { 15 | public static void RegisterCommands() 16 | { 17 | Plugin.AddCommand("css_guns", "Opens the guns menu", GunsCommand); 18 | 19 | Plugin.AddCommand("css_ct_guns", "Opens the CT guns menu", CTGunsCommand); 20 | Plugin.AddCommand("css_t_guns", "Opens the T guns menu", TGunsCommand); 21 | Plugin.AddCommand("css_t_pistols", "Opens the pistols menu", PistolsTCommand); 22 | Plugin.AddCommand("css_ct_pistols", "Opens the pistols menu", PistolsCTCommand); 23 | Plugin.AddCommand("css_awp", "Opens the awps menu", AwpCommand); 24 | 25 | Plugin.AddCommand("css_weapons_reload", "Reloads the weapons allocator's weapons configs", ReloadCommand); 26 | Plugin.AddCommand("css_skip_pistol", "Skips the pistol round", SkipPistolRoundCommand); 27 | } 28 | 29 | public static void UnRegisterCommands() 30 | { 31 | Plugin.RemoveCommand("css_guns", GunsCommand); 32 | 33 | Plugin.RemoveCommand("css_ct_guns", CTGunsCommand); 34 | Plugin.RemoveCommand("css_t_guns", TGunsCommand); 35 | Plugin.RemoveCommand("css_t_pistols", PistolsTCommand); 36 | Plugin.RemoveCommand("css_ct_pistols", PistolsCTCommand); 37 | Plugin.RemoveCommand("css_awp", AwpCommand); 38 | 39 | Plugin.RemoveCommand("css_weapons_reload", ReloadCommand); 40 | } 41 | 42 | private static void GunsCommand(CCSPlayerController? player, CommandInfo commandInfo) 43 | { 44 | if (player == null) 45 | { 46 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 47 | return; 48 | } 49 | 50 | if (!player.IsValid) 51 | { 52 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 53 | return; 54 | } 55 | 56 | var playerObj = FindPlayer(player); 57 | 58 | if (playerObj == null!) 59 | { 60 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 61 | return; 62 | } 63 | 64 | OpenTPrimaryMenu(player); 65 | } 66 | 67 | private static void CTGunsCommand(CCSPlayerController? player, CommandInfo commandInfo) 68 | { 69 | if (player == null) 70 | { 71 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 72 | return; 73 | } 74 | 75 | if (!player.IsValid) 76 | { 77 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 78 | return; 79 | } 80 | 81 | var playerObj = FindPlayer(player); 82 | 83 | if (playerObj == null!) 84 | { 85 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 86 | return; 87 | } 88 | 89 | OpenCTPrimaryMenu(player, false); 90 | } 91 | 92 | private static void TGunsCommand(CCSPlayerController? player, CommandInfo commandInfo) 93 | { 94 | if (player == null) 95 | { 96 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 97 | return; 98 | } 99 | 100 | if (!player.IsValid) 101 | { 102 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 103 | return; 104 | } 105 | 106 | var playerObj = FindPlayer(player); 107 | 108 | if (playerObj == null!) 109 | { 110 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 111 | return; 112 | } 113 | 114 | OpenTPrimaryMenu(player, false); 115 | } 116 | 117 | private static void PistolsTCommand(CCSPlayerController? player, CommandInfo commandInfo) 118 | { 119 | if (player == null) 120 | { 121 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 122 | return; 123 | } 124 | 125 | if (!player.IsValid) 126 | { 127 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 128 | return; 129 | } 130 | 131 | var playerObj = FindPlayer(player); 132 | 133 | if (playerObj == null!) 134 | { 135 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 136 | return; 137 | } 138 | 139 | OpenSecondaryTMenu(player, false); 140 | } 141 | 142 | private static void PistolsCTCommand(CCSPlayerController? player, CommandInfo commandInfo) 143 | { 144 | if (player == null) 145 | { 146 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 147 | return; 148 | } 149 | 150 | if (!player.IsValid) 151 | { 152 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 153 | return; 154 | } 155 | 156 | var playerObj = FindPlayer(player); 157 | 158 | if (playerObj == null!) 159 | { 160 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 161 | return; 162 | } 163 | 164 | OpenSecondaryCTMenu(player, false); 165 | } 166 | 167 | private static void AwpCommand(CCSPlayerController? player, CommandInfo commandInfo) 168 | { 169 | if (player == null) 170 | { 171 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 172 | return; 173 | } 174 | 175 | if (!player.IsValid) 176 | { 177 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 178 | return; 179 | } 180 | 181 | var playerObj = FindPlayer(player); 182 | 183 | if (playerObj == null!) 184 | { 185 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 186 | return; 187 | } 188 | 189 | OpenGiveAWPMenu(player); 190 | } 191 | 192 | [RequiresPermissions(new string[] { "@css/root" })] 193 | private static void ReloadCommand(CCSPlayerController? player, CommandInfo commandInfo) 194 | { 195 | if (player == null) 196 | { 197 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 198 | return; 199 | } 200 | 201 | if (!player.IsValid) 202 | { 203 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 204 | return; 205 | } 206 | 207 | LoadConfigs(false); 208 | PrintToChat(player, $"{PREFIX} Configs reloaded."); 209 | } 210 | 211 | public static void OnVoteCommand(CCSPlayerController? player, CommandInfo commandInfo) 212 | { 213 | if (player == null) 214 | { 215 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 216 | return; 217 | } 218 | 219 | if (!player.IsValid) 220 | { 221 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 222 | return; 223 | } 224 | 225 | if(RoundsCounter < Core.Config.PistolRound.RoundAmount) 226 | { 227 | ReplyToCommand(commandInfo, $"{PREFIX} You can't vote during the pistol rounds."); 228 | return; 229 | } 230 | 231 | int userId = player.UserId!.Value; 232 | 233 | string command = commandInfo.GetArg(0); 234 | AsyncVoteManager voteManager = GetVote(command); 235 | 236 | if (voteManager == null!) 237 | { 238 | ReplyToCommand(commandInfo, $"{PREFIX} Invalid vote command."); 239 | return; 240 | } 241 | 242 | switch(voteManager.AddVote(userId)) 243 | { 244 | case VoteResultEnum.Added: 245 | PrintToChatAll($"{PREFIX} Player \x03{player.PlayerName}\x01 wants to {(voteManager.IsRunningVote() ? "cancel" : "")} {voteManager.vote.Description} rounds ({voteManager.VoteCount} voted, {voteManager.RequiredVotes} needed)."); 246 | break; 247 | case VoteResultEnum.AlreadyAddedBefore: 248 | voteManager.RemoveVote(userId); 249 | PrintToChatAll($"{PREFIX} Player \x03{player.PlayerName}\x01 dont wants {(voteManager.IsRunningVote() ? "to cancel" : "")} {voteManager.vote.Description} rounds anymore ({voteManager.VoteCount} voted, {voteManager.RequiredVotes} needed)."); 250 | break; 251 | default: 252 | break; 253 | } 254 | 255 | if (voteManager.CheckVotes()) 256 | { 257 | Votes_OnVoteReached(voteManager); 258 | } 259 | } 260 | 261 | [RequiresPermissions(new string[] { "@css/root" })] 262 | public static void OnForceVoteCommand(CCSPlayerController? player, CommandInfo commandInfo) 263 | { 264 | if (player == null) 265 | { 266 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 267 | return; 268 | } 269 | 270 | if (!player.IsValid) 271 | { 272 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 273 | return; 274 | } 275 | 276 | if(RoundsCounter < Core.Config.PistolRound.RoundAmount) 277 | { 278 | ReplyToCommand(commandInfo, $"{PREFIX} You can't vote during the pistol rounds."); 279 | return; 280 | } 281 | 282 | string command = commandInfo.GetArg(0); 283 | AsyncVoteManager voteManager = GetVote(command); 284 | 285 | if (voteManager == null!) 286 | { 287 | ReplyToCommand(commandInfo, $"{PREFIX} Invalid vote command."); 288 | return; 289 | } 290 | 291 | PrintToChatAll($"{PREFIX} ADMIN: Forced {voteManager.vote.Description} rounds."); 292 | Votes_OnVoteReached(voteManager); 293 | } 294 | 295 | [RequiresPermissions(new string[] { "@css/root" })] 296 | private static void SkipPistolRoundCommand(CCSPlayerController? player, CommandInfo commandInfo) 297 | { 298 | if (player == null) 299 | { 300 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a player."); 301 | return; 302 | } 303 | 304 | if (!player.IsValid) 305 | { 306 | ReplyToCommand(commandInfo, $"{PREFIX} This command can only be executed by a valid player."); 307 | return; 308 | } 309 | 310 | if(RoundsCounter >= Core.Config.PistolRound.RoundAmount) 311 | { 312 | ReplyToCommand(commandInfo, $"{PREFIX} You can't skip the pistol rounds when there is no pistol rounds."); 313 | return; 314 | } 315 | 316 | PrintToChatAll($"{PREFIX} ADMIN: Skipped the pistol rounds."); 317 | RoundsCounter = Core.Config.PistolRound.RoundAmount + 1; 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /Modules/Handlers/Events.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | 3 | using static RetakesAllocator.Modules.Core; 4 | using static RetakesAllocator.Modules.Utils; 5 | using static RetakesAllocator.Modules.Models.Player; 6 | using static RetakesAllocator.Modules.Weapons.Allocator; 7 | 8 | namespace RetakesAllocator.Modules.Handlers; 9 | 10 | internal static class Events 11 | { 12 | static bool IgnoreRoundEnd = false; 13 | 14 | public static void RegisterEvents() 15 | { 16 | Plugin.RegisterEventHandler(OnRoundPreStart); 17 | Plugin.RegisterEventHandler(OnRoundEnd); 18 | Plugin.RegisterEventHandler(OnPlayerSpawn); 19 | } 20 | 21 | private static HookResult OnRoundPreStart(EventRoundPrestart @event, GameEventInfo info) 22 | { 23 | if (GetGameRules().WarmupPeriod) 24 | { 25 | IgnoreRoundEnd = true; 26 | return HookResult.Continue; 27 | } 28 | 29 | SetupPlayers(Players); 30 | ResetNades(); 31 | 32 | if(currentVote != null && currentVote.vote.OnlyHeadshots) 33 | { 34 | mp_damage_headshot_only.SetValue(true); 35 | } 36 | 37 | return HookResult.Continue; 38 | } 39 | 40 | private static HookResult OnRoundEnd(EventRoundEnd @event, GameEventInfo info) 41 | { 42 | if (GetGameRules().WarmupPeriod || IgnoreRoundEnd) 43 | { 44 | IgnoreRoundEnd = false; 45 | return HookResult.Continue; 46 | } 47 | 48 | RoundsCounter++; 49 | mp_damage_headshot_only.SetValue(false); 50 | 51 | return HookResult.Continue; 52 | } 53 | 54 | private static HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) 55 | { 56 | if(GetGameRules().WarmupPeriod) 57 | { 58 | return HookResult.Continue; 59 | } 60 | 61 | var playerController = @event.Userid; 62 | 63 | if (playerController == null! || !playerController.IsValid) 64 | { 65 | PrintToServer("OnPlayerSpawn: playerController is null or invalid"); 66 | return HookResult.Continue; 67 | } 68 | 69 | var player = FindPlayer(playerController); 70 | 71 | if (player == null! || !player.IsValid()) 72 | { 73 | PrintToServer("OnPlayerSpawn: player is null or invalid"); 74 | return HookResult.Continue; 75 | } 76 | 77 | PrintToServer("OnPlayerSpawn"); 78 | 79 | player.CreateSpawnDelay(); 80 | 81 | return HookResult.Continue; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Modules/Handlers/Listeners.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Commands; 4 | using CounterStrikeSharp.API.Modules.Entities; 5 | 6 | using static RetakesAllocator.Modules.Core; 7 | using static RetakesAllocator.Modules.Utils; 8 | using static RetakesAllocator.Modules.Weapons.Menu; 9 | using static RetakesAllocator.Modules.Votes.Votes; 10 | using RetakesAllocator.Modules.Votes; 11 | 12 | namespace RetakesAllocator.Modules.Handlers; 13 | 14 | internal static class Listeners 15 | { 16 | public static void RegisterListeners() 17 | { 18 | Plugin.RegisterListener(OnMapStart); 19 | Plugin.RegisterListener(OnClientAuthorized); 20 | Plugin.RegisterListener(OnClientDisconnect); 21 | 22 | Plugin.AddCommandListener("say", OnSay); 23 | Plugin.AddCommandListener("say_team", OnSay); 24 | } 25 | 26 | private static void OnMapStart(string map_name) 27 | { 28 | RoundsCounter = 0; 29 | Players.Clear(); 30 | Utilities.GetPlayers().ForEach(AddPlayerToList); 31 | Votes_OnMapStart(); 32 | } 33 | 34 | private static void OnClientAuthorized(int playerSlot, SteamID steamID) 35 | { 36 | CCSPlayerController player = Utilities.GetPlayerFromSlot(playerSlot)!; 37 | AddPlayerToList(player); 38 | } 39 | 40 | private static void OnClientDisconnect(int playerSlot) 41 | { 42 | var player = Utilities.GetPlayerFromSlot(playerSlot)!; 43 | RemovePlayerFromList(player); 44 | Votes_OnPlayerDisconnect(player); 45 | } 46 | 47 | private static HookResult OnSay(CCSPlayerController? player, CommandInfo command) 48 | { 49 | if (player == null || !player.IsValid) 50 | return HookResult.Continue; 51 | 52 | if(command.ArgCount < 2) 53 | return HookResult.Continue; 54 | 55 | var message = command.GetArg(1); 56 | 57 | if(!Core.Config.triggerWords.Any(word => word.Equals(message))) 58 | return HookResult.Continue; 59 | 60 | var playerObj = FindPlayer(player); 61 | 62 | if (playerObj == null!) 63 | { 64 | ReplyToCommand(command, $"{PREFIX} This command can only be executed by a valid player."); 65 | return HookResult.Continue; 66 | } 67 | 68 | OpenTPrimaryMenu(player); 69 | 70 | return HookResult.Continue; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Modules/Models/Player.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Utils; 4 | using RetakesAllocator.Modules.Votes; 5 | using RetakesAllocator.Modules.Weapons; 6 | 7 | using static RetakesAllocator.Modules.Core; 8 | 9 | namespace RetakesAllocator.Modules.Models; 10 | 11 | public class Player 12 | { 13 | public int playerIndex; 14 | public CCSPlayerController player => Utilities.GetPlayerFromIndex(playerIndex)!; 15 | 16 | public readonly Allocator WeaponsAllocator; 17 | 18 | public Player(CCSPlayerController player) 19 | { 20 | playerIndex = (int)player.Index; 21 | WeaponsAllocator = new Allocator(this); 22 | } 23 | 24 | public static void SetupPlayers(List players) 25 | { 26 | List players_t = new(); 27 | List players_ct = new(); 28 | 29 | foreach(var player in players) 30 | { 31 | var team = player.GetTeam(); 32 | var giveAwp = player.WeaponsAllocator.SetupGiveAwp(); 33 | 34 | player.WeaponsAllocator.ShouldGiveAwp = false; 35 | 36 | if (giveAwp) 37 | { 38 | if (team == CsTeam.Terrorist ) 39 | { 40 | players_t.Add(player); 41 | } 42 | 43 | if (team == CsTeam.CounterTerrorist) 44 | { 45 | players_ct.Add(player); 46 | } 47 | } 48 | } 49 | 50 | if(0 < players_t.Count) 51 | { 52 | Player player_t = Utils.GetRandomFromList(players_t); 53 | player_t.WeaponsAllocator.ShouldGiveAwp = true; 54 | } 55 | 56 | if(0 < players_ct.Count) 57 | { 58 | Player player_ct = Utils.GetRandomFromList(players_ct); 59 | player_ct.WeaponsAllocator.ShouldGiveAwp = true; 60 | } 61 | } 62 | 63 | private CsTeam GetTeam() 64 | { 65 | CsTeam team; 66 | 67 | try 68 | { 69 | team = player.Team; 70 | } 71 | catch 72 | { 73 | team = CsTeam.None; 74 | } 75 | 76 | return team; 77 | } 78 | 79 | public string GetSteamId2() 80 | { 81 | return player.AuthorizedSteamID!.SteamId2; 82 | } 83 | 84 | public string GetName() 85 | { 86 | if (player == null! || !player.IsValid) 87 | { 88 | return string.Empty; 89 | } 90 | 91 | return player.PlayerName; 92 | } 93 | 94 | public bool IsValid() 95 | { 96 | return !(player == null! || !player.IsValid); 97 | } 98 | 99 | public void CreateSpawnDelay() 100 | { 101 | Plugin.AddTimer(.1f, Timer_GiveWeapons); 102 | } 103 | 104 | private void Timer_GiveWeapons() 105 | { 106 | if(RoundsCounter < Core.Config.PistolRound.RoundAmount) 107 | { 108 | WeaponsAllocator.AllocatePistolRound(); 109 | WeaponsAllocator.AllocateArmor(false); 110 | return; 111 | } 112 | 113 | if(currentVote == null!) 114 | { 115 | WeaponsAllocator.Allocate(); 116 | WeaponsAllocator.AllocateNades(); 117 | WeaponsAllocator.AllocateArmor(); 118 | return; 119 | } 120 | 121 | Vote vote = currentVote.vote; 122 | 123 | if(vote.GiveArmor) 124 | { 125 | WeaponsAllocator.AllocateArmor(vote.GiveHelmet); 126 | } 127 | 128 | if(vote.GiveNades) 129 | { 130 | WeaponsAllocator.AllocateNades(); 131 | } 132 | 133 | if(vote.GiveWeapons) 134 | { 135 | WeaponsAllocator.AllocateVote(vote); 136 | return; 137 | } 138 | 139 | WeaponsAllocator.Allocate(); 140 | } 141 | } -------------------------------------------------------------------------------- /Modules/RetakeCapability.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core.Capabilities; 2 | 3 | using RetakesPluginShared; 4 | using RetakesPluginShared.Events; 5 | 6 | using static RetakesAllocator.Modules.Core; 7 | using static RetakesAllocator.Modules.Utils; 8 | 9 | namespace RetakesAllocator.Modules; 10 | 11 | public class RetakeCapability 12 | { 13 | 14 | private static IRetakesPluginEventSender? RetakesPluginEventSender { get; set; } 15 | 16 | public static void RetakeCapability_OnLoad() 17 | { 18 | Plugin.AddTimer(0.1f, () => { GetRetakesPluginEventSender().RetakesPluginEventHandlers += RetakesEventHandler; }); 19 | } 20 | 21 | public static void RetakeCapability_OnUnload() 22 | { 23 | GetRetakesPluginEventSender().RetakesPluginEventHandlers -= RetakesEventHandler; 24 | } 25 | 26 | private static IRetakesPluginEventSender GetRetakesPluginEventSender() 27 | { 28 | if (RetakesPluginEventSender is not null) 29 | { 30 | return RetakesPluginEventSender; 31 | } 32 | 33 | var sender = new PluginCapability("retakes_plugin:event_sender").Get(); 34 | if (sender is null) 35 | { 36 | throw new Exception("Couldn't load retakes plugin event sender capability"); 37 | } 38 | 39 | RetakesPluginEventSender = sender; 40 | return sender; 41 | } 42 | 43 | private static void RetakesEventHandler(object? _, IRetakesPluginEvent @event) 44 | { 45 | Action? handler = @event switch 46 | { 47 | AnnounceBombsiteEvent => HandleAnnounceBombsiteEvent, 48 | _ => null 49 | }; 50 | handler?.Invoke(); 51 | } 52 | 53 | private static void HandleAnnounceBombsiteEvent() 54 | { 55 | if(GetGameRules().WarmupPeriod) 56 | { 57 | return; 58 | } 59 | 60 | string mode = "normal mode"; 61 | 62 | if(currentVote != null!) 63 | { 64 | mode = currentVote.vote.Description + " mode"; 65 | } 66 | 67 | if(RoundsCounter < Core.Config.PistolRound.RoundAmount) 68 | { 69 | mode = $"pistol rounds, {Core.Config.PistolRound.RoundAmount - RoundsCounter} rounds left"; 70 | } 71 | 72 | PrintToChatAll($"{PREFIX} Retake {mode}."); 73 | } 74 | } -------------------------------------------------------------------------------- /Modules/Utils.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | using CounterStrikeSharp.API.Core; 3 | using CounterStrikeSharp.API.Modules.Commands; 4 | using static RetakesAllocator.Modules.Core; 5 | using static RetakesAllocator.Modules.Database; 6 | using Player = RetakesAllocator.Modules.Models.Player; 7 | 8 | namespace RetakesAllocator.Modules; 9 | 10 | internal static class Utils 11 | { 12 | public static string PREFIX { get; set; } = Core.Config.Prefix.Prefix; 13 | public static string PREFIX_CON { get; set; } = Core.Config.Prefix.PrefixCon; 14 | 15 | public static void PrintToChat(CCSPlayerController controller, string msg) 16 | { 17 | controller.PrintToChat(msg); 18 | } 19 | 20 | public static void PrintToChatAll(string msg) 21 | { 22 | Server.PrintToChatAll(msg); 23 | } 24 | 25 | public static void PrintToServer(string msg, ConsoleColor color = ConsoleColor.Cyan) 26 | { 27 | Console.ForegroundColor = color; 28 | 29 | msg = $"{PREFIX_CON} {msg}"; 30 | Console.WriteLine(msg); 31 | Console.ResetColor(); 32 | } 33 | 34 | public static void ThrowError(string msg) 35 | { 36 | throw new Exception(msg); 37 | } 38 | 39 | public static void ReplyToCommand(CommandInfo commandInfo, string msg) 40 | { 41 | commandInfo.ReplyToCommand(msg); 42 | } 43 | 44 | public static Player FindPlayer(CCSPlayerController cCSPlayerController) 45 | { 46 | return Players.Find(player => player.playerIndex == cCSPlayerController.Index)!; 47 | } 48 | 49 | public static void ServerCommand(string command, params object[] args) 50 | { 51 | Server.ExecuteCommand(string.Format(command, args)); 52 | } 53 | 54 | public static void AddPlayerToList(CCSPlayerController player) 55 | { 56 | if (player == null || !player.IsValid || player.IsBot) 57 | { 58 | return; 59 | } 60 | 61 | if(FindPlayer(player) != null!) 62 | { 63 | return; 64 | } 65 | 66 | var playerObj = new Player(player); 67 | 68 | Players.Add(playerObj); 69 | 70 | var index = Players.IndexOf(playerObj); 71 | 72 | Query(SQL_FetchUser_CB, $"SELECT * FROM `weapons` WHERE `auth` = '{playerObj.GetSteamId2()}'", index); 73 | } 74 | 75 | public static void RemovePlayerFromList(CCSPlayerController player) 76 | { 77 | if (player == null || !player.IsValid || player.IsBot) 78 | { 79 | return; 80 | } 81 | 82 | var playerObj = FindPlayer(player); 83 | 84 | if (playerObj == null!) 85 | { 86 | return; 87 | } 88 | 89 | Query(SQL_CheckForErrors, $"UPDATE `weapons` SET `t_primary` = '{playerObj.WeaponsAllocator.PrimaryWeaponT}', `ct_primary` = '{playerObj.WeaponsAllocator.PrimaryWeaponCt}', `t_secondary` = '{playerObj.WeaponsAllocator.SecondaryWeaponT}', `ct_secondary` = '{playerObj.WeaponsAllocator.SecondaryWeaponCt}' ,`give_awp` = '{(int)playerObj.WeaponsAllocator.GiveAwp}' WHERE `auth` = '{playerObj.GetSteamId2()}'"); 90 | 91 | Players.Remove(playerObj); 92 | } 93 | 94 | public static int GetRoundsAmount() 95 | { 96 | IEnumerable team = Utilities.FindAllEntitiesByDesignerName("cs_team"); 97 | 98 | if (team.Count() == 0) 99 | { 100 | return 0; 101 | } 102 | 103 | int rounds = 0; 104 | 105 | foreach (var t in team) 106 | { 107 | rounds = t.Score; 108 | } 109 | 110 | return rounds; 111 | } 112 | 113 | public static CCSPlayerController[] ValidPlayers(bool considerBots = false) 114 | { 115 | //considerBots = true; 116 | return Utilities.GetPlayers() 117 | .Where(x => x.ReallyValid(considerBots)) 118 | .Where(x => !x.IsHLTV) 119 | .Where(x => considerBots || !x.IsBot) 120 | .ToArray(); 121 | } 122 | 123 | public static bool ReallyValid(this CCSPlayerController? player, bool considerBots = false) 124 | { 125 | return player is not null && player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && 126 | (considerBots || (!player.IsBot && !player.IsHLTV)); 127 | } 128 | 129 | public static int ValidPlayerCount(bool considerBots = false) 130 | { 131 | return ValidPlayers(considerBots).Length; 132 | } 133 | 134 | public static T GetRandomFromList(this List list) 135 | { 136 | int index = new Random().Next(list.Count); 137 | return list[index]; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Modules/Votes/AsyncVoteManager.cs: -------------------------------------------------------------------------------- 1 | using static RetakesAllocator.Modules.Core; 2 | 3 | namespace RetakesAllocator.Modules.Votes; 4 | 5 | public class AsyncVoteManager 6 | { 7 | private List votes = new(); 8 | public int VoteCount => votes.Count; 9 | public int RequiredVotes => _voteValidator.RequiredVotes; 10 | private readonly AsyncVoteValidator _voteValidator; 11 | public string Command => vote.Command; 12 | public Vote vote { get; set; } = null!; 13 | 14 | public AsyncVoteManager(Vote vote) 15 | { 16 | _voteValidator = new AsyncVoteValidator(); 17 | this.vote = vote; 18 | } 19 | 20 | public void OnMapStart() 21 | { 22 | votes.Clear(); 23 | } 24 | 25 | public VoteResultEnum AddVote(int userId) 26 | { 27 | VoteResultEnum? result = null; 28 | if (votes.IndexOf(userId) != -1) 29 | result = VoteResultEnum.AlreadyAddedBefore; 30 | else 31 | { 32 | votes.Add(userId); 33 | result = VoteResultEnum.Added; 34 | } 35 | 36 | return result.Value; 37 | } 38 | 39 | public bool CheckVotes() 40 | { 41 | if (_voteValidator.CheckVotes(votes.Count)) 42 | { 43 | votes.Clear(); 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | public bool IsRunningVote() 51 | { 52 | return currentVote != null! && Command == currentVote.Command; 53 | } 54 | 55 | public void RemoveVote(int userId) 56 | { 57 | var index = votes.IndexOf(userId); 58 | if (index > -1) 59 | votes.RemoveAt(index); 60 | } 61 | 62 | public void ClearVotes() 63 | { 64 | votes.Clear(); 65 | } 66 | } -------------------------------------------------------------------------------- /Modules/Votes/AsyncVoteValidator.cs: -------------------------------------------------------------------------------- 1 | using static RetakesAllocator.Modules.Utils; 2 | using static RetakesAllocator.Modules.Votes.Votes; 3 | 4 | namespace RetakesAllocator.Modules.Votes; 5 | 6 | public class AsyncVoteValidator 7 | { 8 | private float VotePercentage = 0F; 9 | public int RequiredVotes { get => (int)Math.Round(ValidPlayerCount() * VotePercentage); } 10 | 11 | public AsyncVoteValidator() 12 | { 13 | VotePercentage = RequiredPrecentage / 100F; 14 | } 15 | 16 | public bool CheckVotes(int numberOfVotes) 17 | { 18 | return numberOfVotes > 0 && numberOfVotes >= RequiredVotes; 19 | } 20 | } -------------------------------------------------------------------------------- /Modules/Votes/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | using static RetakesAllocator.Modules.Core; 4 | using static RetakesAllocator.Modules.Configs; 5 | using static RetakesAllocator.Modules.Votes.Votes; 6 | 7 | namespace RetakesAllocator.Modules.Votes; 8 | 9 | public class Config 10 | { 11 | private const string votes_path = "votes.json"; 12 | 13 | public static void LoadConfig() 14 | { 15 | LoadVotes(); 16 | } 17 | 18 | private static void LoadVotes() 19 | { 20 | string configPath = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, votes_path); 21 | 22 | if (!File.Exists(configPath)) 23 | { 24 | CreateConfig(configPath, WeaponVotes); 25 | return; 26 | } 27 | 28 | var config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; 29 | 30 | if(config.Votes.Count == 0) 31 | { 32 | CreateConfig(configPath, WeaponVotes); 33 | return; 34 | } 35 | 36 | WeaponVotes.Clear(); 37 | 38 | foreach (var vote in config.Votes) 39 | { 40 | WeaponVotes.Add(vote); 41 | } 42 | 43 | Votes_OnConfigParsed(config.WeaponSelectionTime, config.RequiredPrecentage); 44 | } 45 | 46 | private static void CreateConfig(string configPath, List votes) 47 | { 48 | var config = new VotesConfig(votes); 49 | 50 | File.WriteAllText(configPath, JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); 51 | 52 | Votes_OnConfigParsed(config.WeaponSelectionTime, config.RequiredPrecentage); 53 | } 54 | } 55 | 56 | class VotesConfig 57 | { 58 | public int RequiredPrecentage { get; set; } = 60; 59 | public int WeaponSelectionTime { get; set; } = 5; 60 | public List Votes { get; set; } = new(); 61 | 62 | public VotesConfig(List votes) 63 | { 64 | foreach (Vote vote in votes) 65 | { 66 | Votes.Add(vote); 67 | } 68 | RequiredPrecentage = 60; 69 | WeaponSelectionTime = 5; 70 | } 71 | } -------------------------------------------------------------------------------- /Modules/Votes/Votes.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using static RetakesAllocator.Modules.Core; 3 | using static RetakesAllocator.Modules.Handlers.Commands; 4 | using static RetakesAllocator.Modules.Utils; 5 | 6 | namespace RetakesAllocator.Modules.Votes; 7 | 8 | public enum VoteResultEnum 9 | { 10 | Added, 11 | AlreadyAddedBefore, 12 | VotesAlreadyReached 13 | } 14 | 15 | 16 | public class Vote 17 | { 18 | public string Command { get; set; } = string.Empty; 19 | public string Description { get; set; } = string.Empty; 20 | public List weapons_t { get; set; } = new(); 21 | public List weapons_ct { get; set; } = new(); 22 | public bool OnlyHeadshots { get; set; } = false; 23 | public bool GiveWeapons { get; set; } = true; 24 | public bool GiveNades { get; set; } = true; 25 | public bool GiveKnife { get; set; } = true; 26 | public bool GiveArmor { get; set; } = true; 27 | public bool GiveHelmet { get; set; } = true; 28 | 29 | public Vote(string Command, string Description, List weapons_t, List weapons_ct, bool OnlyHeadshots, bool GiveWeapons, bool GiveKnife = true, bool GiveArmor = true, bool GiveHelmet = true) 30 | { 31 | this.Command = Command; 32 | this.Description = Description; 33 | this.weapons_t = weapons_t; 34 | this.weapons_ct = weapons_ct; 35 | this.OnlyHeadshots = OnlyHeadshots; 36 | this.GiveWeapons = GiveWeapons; 37 | this.GiveKnife = GiveKnife; 38 | this.GiveArmor = GiveArmor; 39 | this.GiveHelmet = GiveHelmet; 40 | } 41 | } 42 | 43 | public class Votes 44 | { 45 | public static List WeaponVotes = new() 46 | { 47 | new("vp", "pistol only", new() { "glock" }, new() { "usp_silencer" }, false, true, true, true, false), 48 | new("vph", "pistol only with headshots only", new() { "glock" }, new() { "usp_silencer" }, true, true), 49 | new("vhs", "headshots only", new(), new(), true, false), 50 | new("vawp", "awp only", new() { "awp" }, new() { "awp" }, false, true), 51 | new("vrifles", "rifle only", new() { "ak47", "galilar" }, new() { "m4a1", "m4a1_silencer" }, false, true), 52 | }; 53 | 54 | public static List VoteManagers = new(); 55 | public static int RequiredPrecentage = 60; 56 | public static int WeaponSelectionTime = 5; 57 | 58 | public static AsyncVoteManager GetVote(string command) 59 | { 60 | if(VoteManagers.Count == 0) 61 | return null!; 62 | 63 | return VoteManagers.FirstOrDefault(x => command.Replace("css_", "").Replace("force", "") == x.vote.Command)!; 64 | } 65 | 66 | public static void Votes_OnConfigParsed(int weaponSelectionTime, int requiredPrecentage) 67 | { 68 | WeaponSelectionTime = weaponSelectionTime; 69 | RequiredPrecentage = requiredPrecentage; 70 | 71 | foreach (var vote in WeaponVotes) 72 | { 73 | Plugin.AddCommand($"css_{vote.Command}", vote.Description, OnVoteCommand); 74 | Plugin.AddCommand($"css_force{vote.Command}", $"force {vote.Description}", OnForceVoteCommand); 75 | AsyncVoteManager voteManager = new(vote); 76 | 77 | VoteManagers.Add(voteManager); 78 | } 79 | } 80 | 81 | public static void Votes_OnMapStart() 82 | { 83 | currentVote = null!; 84 | 85 | foreach (var vote in WeaponVotes) 86 | { 87 | foreach (var voteManager in VoteManagers) 88 | { 89 | voteManager.OnMapStart(); 90 | } 91 | } 92 | } 93 | 94 | public static void Votes_OnPluginUnload() 95 | { 96 | foreach (var vote in WeaponVotes) 97 | { 98 | Plugin.RemoveCommand($"css_{vote.Command}", OnVoteCommand); 99 | Plugin.RemoveCommand($"css_force{vote.Command}", OnForceVoteCommand); 100 | } 101 | } 102 | 103 | public static void Votes_OnVoteReached(AsyncVoteManager voteManager) 104 | { 105 | string description = voteManager.vote.Description; 106 | description = description.Substring(0, 1).ToUpper() + description.Substring(1); 107 | voteManager.ClearVotes(); 108 | 109 | 110 | if(currentVote != null! && voteManager.IsRunningVote()) 111 | { 112 | currentVote = null!; 113 | 114 | PrintToChatAll($"{PREFIX} {description} rounds will be canceled next round."); 115 | return; 116 | } 117 | 118 | currentVote = voteManager; 119 | PrintToChatAll($"{PREFIX} {description} rounds will start next round!"); 120 | } 121 | 122 | public static void Votes_OnPlayerDisconnect(CCSPlayerController player) 123 | { 124 | var userId = player.UserId!.Value; 125 | 126 | foreach (AsyncVoteManager voteManager in VoteManagers) 127 | { 128 | voteManager.RemoveVote(userId); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /Modules/Weapons/Allocator.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Modules.Utils; 3 | using CounterStrikeSharp.API.Modules.Entities.Constants; 4 | using RetakesAllocator.Modules.Models; 5 | using RetakesAllocator.Modules.Votes; 6 | 7 | using static RetakesAllocator.Modules.Weapons.Menu; 8 | using static RetakesAllocator.Modules.Votes.Votes; 9 | 10 | namespace RetakesAllocator.Modules.Weapons; 11 | 12 | public enum GiveAwp 13 | { 14 | Never, 15 | Sometimes, 16 | Always 17 | } 18 | 19 | public class Weapon 20 | { 21 | public string Item { get; set; } = string.Empty; 22 | public string DisplayName { get; set; } = string.Empty; 23 | 24 | public Weapon(string item, string displayName) 25 | { 26 | Item = item; 27 | DisplayName = displayName; 28 | } 29 | } 30 | 31 | public class Allocator 32 | { 33 | public enum WeaponType 34 | { 35 | PrimaryT, 36 | PrimaryCt, 37 | SecondaryT, 38 | SecondaryCt 39 | }; 40 | 41 | public static List PrimaryT = new() 42 | { 43 | new("weapon_ak47", "AK-47"), 44 | new("weapon_sg556", "SG 553") 45 | }; 46 | 47 | public static List PrimaryCt = new() 48 | { 49 | new("weapon_m4a1", "M4A4"), 50 | new("weapon_m4a1_silencer", "M4A1-S"), 51 | new("weapon_aug", "AUG") 52 | }; 53 | 54 | public static List PistolsT = new() 55 | { 56 | new("weapon_glock", "Glock-18"), 57 | new("weapon_p250", "P250"), 58 | }; 59 | 60 | public static List PistolsCT = new() 61 | { 62 | new("weapon_usp_silencer", "USP-S"), 63 | new("weapon_p250", "P250"), 64 | new("weapon_hkp2000", "P2000") 65 | }; 66 | 67 | public static Nades CTNades = new(); 68 | public static Nades TNades = new(); 69 | 70 | private readonly Player _player; 71 | 72 | private CCSPlayerController cCSPlayerController => _player.player; 73 | 74 | public int PrimaryWeaponT = 0; 75 | public int PrimaryWeaponCt = 0; 76 | public int SecondaryWeaponT = 0; 77 | public int SecondaryWeaponCt = 0; 78 | 79 | public GiveAwp GiveAwp = GiveAwp.Never; 80 | public bool ShouldGiveAwp = false; 81 | 82 | public Allocator(Player player) 83 | { 84 | _player = player; 85 | } 86 | 87 | public static void ResetNades() 88 | { 89 | CTNades = new Nades(Core.NadesConfig.CTNades); 90 | TNades = new Nades(Core.NadesConfig.TNades); 91 | } 92 | 93 | public static int GetWeaponIndex(string weapon, WeaponType type) 94 | { 95 | switch(type) 96 | { 97 | case WeaponType.PrimaryT: 98 | return PrimaryT.FindIndex(w => w.DisplayName == weapon); 99 | case WeaponType.PrimaryCt: 100 | return PrimaryCt.FindIndex(w => w.DisplayName == weapon); 101 | case WeaponType.SecondaryT: 102 | return PistolsT.FindIndex(w => w.DisplayName == weapon); 103 | case WeaponType.SecondaryCt: 104 | return PistolsCT.FindIndex(w => w.DisplayName == weapon); 105 | } 106 | 107 | return -1; 108 | } 109 | 110 | public bool SetupGiveAwp() 111 | { 112 | var giveAwp = GiveAwp switch 113 | { 114 | GiveAwp.Always => true, 115 | GiveAwp.Never => false, 116 | _ => new Random().Next(0, 2) == 1 117 | }; 118 | 119 | return giveAwp; 120 | } 121 | 122 | public void AllocateNades() 123 | { 124 | if (_player == null || cCSPlayerController == null || !_player.player.IsValid) 125 | { 126 | return; 127 | } 128 | 129 | if (!cCSPlayerController.PawnIsAlive) 130 | { 131 | return; 132 | } 133 | 134 | if (cCSPlayerController.Team < CsTeam.Terrorist || cCSPlayerController.Team > CsTeam.CounterTerrorist) 135 | { 136 | return; 137 | } 138 | 139 | if((cCSPlayerController.Team == CsTeam.Terrorist && !TNades.HasNades()) || (cCSPlayerController.Team == CsTeam.CounterTerrorist && !CTNades.HasNades())) 140 | { 141 | return; 142 | } 143 | 144 | Nades nades = cCSPlayerController.Team == CsTeam.Terrorist ? TNades : CTNades; 145 | 146 | CsItem grenade; 147 | do{ 148 | grenade = SelectGrenade(); 149 | } 150 | while(!CheckIfNadeAvailable(nades, grenade)); 151 | 152 | nades.RemoveNade(grenade); 153 | 154 | cCSPlayerController.GiveNamedItem(grenade); 155 | } 156 | 157 | public bool CheckIfNadeAvailable(Nades nades, CsItem nade) 158 | { 159 | if (_player == null || cCSPlayerController == null || !_player.player.IsValid) 160 | { 161 | return false; 162 | } 163 | 164 | if (!cCSPlayerController.PawnIsAlive) 165 | { 166 | return false; 167 | } 168 | 169 | if (cCSPlayerController.Team < CsTeam.Terrorist || cCSPlayerController.Team > CsTeam.CounterTerrorist) 170 | { 171 | return false; 172 | } 173 | 174 | switch(nade) 175 | { 176 | case CsItem.Flashbang: 177 | { 178 | return nades.HasFlashbangs(); 179 | } 180 | case CsItem.Molotov or CsItem.Incendiary: 181 | { 182 | return nades.HasMolotovs(); 183 | } 184 | case CsItem.HEGrenade: 185 | { 186 | return nades.HasHeGrenades(); 187 | } 188 | case CsItem.SmokeGrenade: 189 | { 190 | return nades.HasSmokes(); 191 | } 192 | } 193 | 194 | return false; 195 | } 196 | 197 | public void AllocateArmor(bool give_full = true) 198 | { 199 | if (_player == null || cCSPlayerController == null || !_player.player.IsValid) 200 | { 201 | return; 202 | } 203 | 204 | if (!cCSPlayerController.PawnIsAlive) 205 | { 206 | return; 207 | } 208 | 209 | if (cCSPlayerController.Team < CsTeam.Terrorist || cCSPlayerController.Team > CsTeam.CounterTerrorist) 210 | { 211 | return; 212 | } 213 | 214 | cCSPlayerController.GiveNamedItem(give_full ? CsItem.KevlarHelmet : CsItem.Kevlar); 215 | } 216 | 217 | public void Allocate() 218 | { 219 | if (_player == null || cCSPlayerController == null || !_player.player.IsValid) 220 | { 221 | return; 222 | } 223 | 224 | if (!cCSPlayerController.PawnIsAlive) 225 | { 226 | return; 227 | } 228 | 229 | if (cCSPlayerController.Team < CsTeam.Terrorist || cCSPlayerController.Team > CsTeam.CounterTerrorist) 230 | { 231 | return; 232 | } 233 | 234 | string primary; 235 | if (ShouldGiveAwp) 236 | { 237 | primary = "weapon_awp"; 238 | } 239 | else 240 | { 241 | if (cCSPlayerController.Team == CsTeam.Terrorist) 242 | { 243 | primary = PrimaryT[PrimaryWeaponT].Item; 244 | } 245 | else 246 | { 247 | primary = PrimaryCt[PrimaryWeaponCt].Item; 248 | } 249 | } 250 | 251 | string secondary; 252 | 253 | if (ShouldGiveAwp) 254 | { 255 | secondary = "weapon_deagle"; 256 | } 257 | else 258 | { 259 | if (cCSPlayerController.Team == CsTeam.Terrorist) 260 | { 261 | secondary = PistolsT[SecondaryWeaponT].Item; 262 | } 263 | else 264 | { 265 | secondary = PistolsCT[SecondaryWeaponCt].Item; 266 | } 267 | } 268 | 269 | cCSPlayerController.GiveNamedItem(primary); 270 | cCSPlayerController.GiveNamedItem(secondary); 271 | cCSPlayerController.GiveNamedItem(CsItem.Knife); 272 | 273 | if (cCSPlayerController.Team == CsTeam.CounterTerrorist) 274 | { 275 | GiveCtEquipment(); 276 | } 277 | } 278 | 279 | public void AllocatePistolRound() 280 | { 281 | if (_player == null || cCSPlayerController == null || !_player.player.IsValid) 282 | { 283 | return; 284 | } 285 | 286 | if (!cCSPlayerController.PawnIsAlive) 287 | { 288 | return; 289 | } 290 | 291 | if (cCSPlayerController.Team < CsTeam.Terrorist || cCSPlayerController.Team > CsTeam.CounterTerrorist) 292 | { 293 | return; 294 | } 295 | 296 | string secondary = Core.Config.PistolRound.GetWeaponByTeam(cCSPlayerController.Team); 297 | 298 | cCSPlayerController.GiveNamedItem(secondary); 299 | cCSPlayerController.GiveNamedItem(CsItem.Knife); 300 | 301 | if (cCSPlayerController.Team == CsTeam.CounterTerrorist) 302 | { 303 | GiveCtEquipment(); 304 | } 305 | } 306 | 307 | public void AllocateVote(Vote vote) 308 | { 309 | if (_player == null || cCSPlayerController == null || !_player.player.IsValid) 310 | { 311 | return; 312 | } 313 | 314 | if (!cCSPlayerController.PawnIsAlive) 315 | { 316 | return; 317 | } 318 | 319 | if (cCSPlayerController.Team < CsTeam.Terrorist || cCSPlayerController.Team > CsTeam.CounterTerrorist) 320 | { 321 | return; 322 | } 323 | 324 | List weapons = cCSPlayerController.Team == CsTeam.Terrorist ? vote.weapons_t : vote.weapons_ct; 325 | 326 | if(weapons.Count > 1) 327 | { 328 | ShowWeaponSelectionMenu(cCSPlayerController, weapons, WeaponSelectionTime); 329 | } else { 330 | cCSPlayerController.GiveNamedItem("weapon_" + weapons.First()); 331 | } 332 | 333 | if(vote.GiveKnife) 334 | cCSPlayerController.GiveNamedItem(CsItem.Knife); 335 | 336 | if (cCSPlayerController.Team == CsTeam.CounterTerrorist) 337 | { 338 | GiveCtEquipment(); 339 | } 340 | } 341 | 342 | private CsItem SelectGrenade() 343 | { 344 | var grenade = CsItem.HEGrenade; 345 | 346 | var rand = new Random().Next(0,4); 347 | 348 | switch(rand) 349 | { 350 | case 0: 351 | grenade = CsItem.HEGrenade; 352 | break; 353 | case 1: 354 | grenade = CsItem.Flashbang; 355 | break; 356 | case 2: 357 | grenade = CsItem.SmokeGrenade; 358 | break; 359 | case 3: 360 | grenade = cCSPlayerController.Team == CsTeam.Terrorist ? CsItem.Molotov : CsItem.Incendiary; 361 | break; 362 | } 363 | 364 | return grenade; 365 | } 366 | 367 | private void GiveCtEquipment() 368 | { 369 | if ( 370 | cCSPlayerController.Team == CsTeam.CounterTerrorist 371 | && cCSPlayerController.PlayerPawn.IsValid 372 | && cCSPlayerController.PlayerPawn.Value != null 373 | && cCSPlayerController.PlayerPawn.Value.IsValid 374 | && cCSPlayerController.PlayerPawn.Value.ItemServices != null 375 | ) { 376 | var itemServices = new CCSPlayer_ItemServices(cCSPlayerController.PlayerPawn.Value.ItemServices.Handle) 377 | { 378 | HasDefuser = true 379 | }; 380 | } 381 | } 382 | } -------------------------------------------------------------------------------- /Modules/Weapons/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | using static RetakesAllocator.Modules.Core; 4 | using static RetakesAllocator.Modules.Configs; 5 | using static RetakesAllocator.Modules.Weapons.Allocator; 6 | using CounterStrikeSharp.API.Modules.Entities.Constants; 7 | 8 | namespace RetakesAllocator.Modules.Weapons; 9 | 10 | public class Config 11 | { 12 | private const string weapons_directory = "weapons"; 13 | private const string tprimary_path = "primary_t.json"; 14 | private const string ctprimary_path = "primary_ct.json"; 15 | private const string tpistols_path = "pistols_t.json"; 16 | private const string ctpistols_path = "pistols_ct.json"; 17 | private const string nades_path = "nades.json"; 18 | 19 | public static void LoadConfig() 20 | { 21 | CreateWeaponsDirectory(); 22 | 23 | LoadTPrimary(); 24 | LoadCTPrimary(); 25 | LoadTPistols(); 26 | LoadCTPistols(); 27 | LoadNades(); 28 | } 29 | 30 | private static void CreateWeaponsDirectory() 31 | { 32 | var weaponsDirectory = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, weapons_directory); 33 | 34 | if (!Directory.Exists(weaponsDirectory)) 35 | { 36 | Directory.CreateDirectory(weaponsDirectory); 37 | } 38 | } 39 | 40 | private static void LoadTPrimary() 41 | { 42 | string configPath = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, weapons_directory, tprimary_path); 43 | 44 | if (!File.Exists(configPath)) 45 | { 46 | CreateConfig(configPath, PrimaryT); 47 | return; 48 | } 49 | 50 | PrimaryT.Clear(); 51 | 52 | var config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; 53 | 54 | foreach (var weapon in config.Weapons) 55 | { 56 | PrimaryT.Add(weapon); 57 | } 58 | 59 | } 60 | 61 | private static void LoadCTPrimary() 62 | { 63 | string configPath = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, weapons_directory, ctprimary_path); 64 | 65 | if (!File.Exists(configPath)) 66 | { 67 | CreateConfig(configPath, PrimaryCt); 68 | return; 69 | } 70 | 71 | PrimaryCt.Clear(); 72 | 73 | var config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; 74 | 75 | foreach (var weapon in config.Weapons) 76 | { 77 | PrimaryCt.Add(weapon); 78 | } 79 | } 80 | 81 | private static void LoadTPistols() 82 | { 83 | string configPath = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, weapons_directory, tpistols_path); 84 | 85 | if (!File.Exists(configPath)) 86 | { 87 | CreateConfig(configPath, PistolsT); 88 | return; 89 | } 90 | 91 | PistolsT.Clear(); 92 | 93 | var config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; 94 | 95 | foreach (var weapon in config.Weapons) 96 | { 97 | PistolsT.Add(weapon); 98 | } 99 | } 100 | 101 | private static void LoadCTPistols() 102 | { 103 | string configPath = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, weapons_directory, ctpistols_path); 104 | 105 | if (!File.Exists(configPath)) 106 | { 107 | CreateConfig(configPath, PistolsCT); 108 | return; 109 | } 110 | 111 | PistolsCT.Clear(); 112 | 113 | var config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; 114 | 115 | foreach (var weapon in config.Weapons) 116 | { 117 | PistolsCT.Add(weapon); 118 | } 119 | } 120 | 121 | private static void LoadNades() 122 | { 123 | string configPath = Path.Combine(Plugin.ModuleDirectory, ConfigDirectory, weapons_directory, nades_path); 124 | 125 | if (!File.Exists(configPath)) 126 | { 127 | CreateNadesConfig(configPath); 128 | return; 129 | } 130 | 131 | var config = JsonSerializer.Deserialize(File.ReadAllText(configPath))!; 132 | 133 | Core.NadesConfig = config; 134 | } 135 | 136 | private static void CreateConfig(string configPath, List weapons) 137 | { 138 | var config = new WeaponsConfig(weapons); 139 | 140 | File.WriteAllText(configPath, JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); 141 | } 142 | 143 | private static void CreateNadesConfig(string configPath) 144 | { 145 | var config = new NadesConfig(); 146 | 147 | File.WriteAllText(configPath, JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true })); 148 | } 149 | } 150 | 151 | class WeaponsConfig 152 | { 153 | public List Weapons { get; set; } = new(); 154 | 155 | public WeaponsConfig(List weapons) 156 | { 157 | foreach (Weapon weapon in weapons) 158 | { 159 | Weapons.Add(weapon); 160 | } 161 | } 162 | } 163 | 164 | public class NadesConfig 165 | { 166 | public Nades CTNades { get; set; } = new(); 167 | public Nades TNades { get; set; } = new(); 168 | 169 | public NadesConfig(Nades ctNades, Nades tnNades) 170 | { 171 | CTNades = ctNades; 172 | TNades = tnNades; 173 | } 174 | 175 | public NadesConfig() 176 | { 177 | CTNades = new Nades() 178 | { 179 | Flashbangs = 2, 180 | Smokes = 1, 181 | Molotovs = 1, 182 | HeGrenades = 1 183 | }; 184 | 185 | TNades = new Nades() 186 | { 187 | Flashbangs = 1, 188 | Smokes = 1, 189 | Molotovs = 1, 190 | HeGrenades = 1 191 | }; 192 | } 193 | } 194 | 195 | public class Nades 196 | { 197 | public int Flashbangs { get; set; } = 0; 198 | public int Smokes { get; set; } = 0; 199 | public int Molotovs { get; set; } = 0; 200 | public int HeGrenades { get; set; } = 0; 201 | 202 | public Nades() 203 | { 204 | } 205 | 206 | public Nades(Nades nades) 207 | { 208 | Flashbangs = nades.Flashbangs; 209 | Smokes = nades.Smokes; 210 | Molotovs = nades.Molotovs; 211 | HeGrenades = nades.HeGrenades; 212 | } 213 | 214 | public bool HasNades() 215 | { 216 | return Flashbangs > 0 || Smokes > 0 || Molotovs > 0 || HeGrenades > 0; 217 | } 218 | 219 | public bool HasFlashbangs() 220 | { 221 | return Flashbangs > 0; 222 | } 223 | 224 | public bool HasSmokes() 225 | { 226 | return Smokes > 0; 227 | } 228 | 229 | public bool HasMolotovs() 230 | { 231 | return Molotovs > 0; 232 | } 233 | 234 | public bool HasHeGrenades() 235 | { 236 | return HeGrenades > 0; 237 | } 238 | 239 | public void RemoveNade(CsItem nade) 240 | { 241 | switch (nade) 242 | { 243 | case CsItem.Flashbang: 244 | Flashbangs--; 245 | break; 246 | case CsItem.Smoke: 247 | Smokes--; 248 | break; 249 | case CsItem.Molotov or CsItem.Incendiary: 250 | Molotovs--; 251 | break; 252 | case CsItem.HEGrenade: 253 | HeGrenades--; 254 | break; 255 | } 256 | } 257 | } -------------------------------------------------------------------------------- /Modules/Weapons/Menu.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Modules.Menu; 3 | using RetakesAllocator.Modules.Models; 4 | using Timer = CounterStrikeSharp.API.Modules.Timers.Timer; 5 | 6 | using static RetakesAllocator.Modules.Core; 7 | using static RetakesAllocator.Modules.Utils; 8 | using static RetakesAllocator.Modules.Weapons.Allocator; 9 | 10 | namespace RetakesAllocator.Modules.Weapons; 11 | 12 | public class Menu 13 | { 14 | private static Dictionary timers = new(); 15 | 16 | private static Dictionary c2Weapons = new Dictionary() 17 | { 18 | {"deagle", "Desert Eagle"}, 19 | {"elite", "Dual Berettas"}, 20 | {"fiveseven", "Five-SeveN"}, 21 | {"glock", "Glock-18"}, 22 | {"tec9", "Tec-9"}, 23 | {"hkp2000", "P2000"}, 24 | {"p250", "P250"}, 25 | {"usp_silencer", "USP-S"}, 26 | {"cz75a", "CZ75-Auto"}, 27 | {"revolver", "R8 Revolver"}, 28 | {"xm1014", "XM1014"}, 29 | {"mag7", "MAG-7"}, 30 | {"sawedoff", "Sawed-Off"}, 31 | {"nova", "Nova"}, 32 | {"mac10", "MAC-10"}, 33 | {"mp5sd", "MP5-SD"}, 34 | {"p90", "P90"}, 35 | {"ump45", "UMP-45"}, 36 | {"bizon", "PP-Bizon"}, 37 | {"mp7", "MP7"}, 38 | {"mp9", "MP9"}, 39 | {"ak47", "AK-47"}, 40 | {"aug", "AUG"}, 41 | {"famas", "FAMAS"}, 42 | {"galilar", "Galil AR"}, 43 | {"m4a4", "M4A4"}, 44 | {"sg556", "SG 553"}, 45 | {"m4a1_silencer", "M4A1-S"}, 46 | {"m4a1", "M4A1"}, // Added M4A1 47 | {"m249", "M249"}, 48 | {"negev", "Negev"}, 49 | {"awp", "AWP"}, 50 | {"scar20", "SCAR-20"}, 51 | {"g3sg1", "G3SG1"}, 52 | {"ssg08", "SSG 08"} 53 | }; 54 | 55 | public static void OpenTPrimaryMenu(CCSPlayerController player, bool showNext = true) 56 | { 57 | CenterHtmlMenu centerHtmlMenu = new CenterHtmlMenu($"{PREFIX} Select a T Primary Weapon", Plugin); 58 | 59 | if(Core.Config.AddSkipOption && showNext) 60 | { 61 | centerHtmlMenu.AddMenuOption("SKIP", (p, _) => OpenCTPrimaryMenu(p)); 62 | } 63 | 64 | foreach (Weapon weapon in PrimaryT) 65 | { 66 | centerHtmlMenu.AddMenuOption(weapon.DisplayName, (CCSPlayerController player, ChatMenuOption option) => OnTPrimarySelect(player, option, showNext)); 67 | } 68 | 69 | MenuManager.OpenCenterHtmlMenu(Plugin, player, centerHtmlMenu); 70 | } 71 | 72 | private static void OnTPrimarySelect(CCSPlayerController player, ChatMenuOption option, bool showNext) 73 | { 74 | if (option == null) 75 | { 76 | PrintToChat(player, $"{PREFIX} You did not select a weapon!"); 77 | return; 78 | } 79 | 80 | Player player_obj = FindPlayer(player); 81 | 82 | if (player_obj == null!) 83 | { 84 | return; 85 | } 86 | 87 | PrintToChat(player, $"{PREFIX} You selected {option.Text} as T Primary!"); 88 | 89 | player_obj.WeaponsAllocator.PrimaryWeaponT = GetWeaponIndex(option.Text, WeaponType.PrimaryT); 90 | 91 | if(showNext) 92 | { 93 | OpenCTPrimaryMenu(player); 94 | return; 95 | } 96 | 97 | MenuManager.CloseActiveMenu(player); 98 | } 99 | 100 | public static void OpenCTPrimaryMenu(CCSPlayerController player, bool showNext = true) 101 | { 102 | CenterHtmlMenu centerHtmlMenu = new CenterHtmlMenu($"{PREFIX} Select a CT Primary Weapon", Plugin); 103 | 104 | if(Core.Config.AddSkipOption && showNext) 105 | { 106 | centerHtmlMenu.AddMenuOption("SKIP", (p, _) => OpenSecondaryTMenu(p)); 107 | } 108 | 109 | foreach (Weapon weapon in PrimaryCt) 110 | { 111 | centerHtmlMenu.AddMenuOption(weapon.DisplayName, (CCSPlayerController player, ChatMenuOption option) => OnCTPrimarySelect(player, option, showNext)); 112 | } 113 | 114 | MenuManager.OpenCenterHtmlMenu(Plugin, player, centerHtmlMenu); 115 | } 116 | 117 | private static void OnCTPrimarySelect(CCSPlayerController player, ChatMenuOption option, bool showNext) 118 | { 119 | if (option == null) 120 | { 121 | PrintToChat(player, $"{PREFIX} You did not select a weapon!"); 122 | return; 123 | } 124 | 125 | var player_obj = FindPlayer(player); 126 | 127 | if (player_obj == null!) 128 | { 129 | return; 130 | } 131 | 132 | PrintToChat(player, $"{PREFIX} You selected {option.Text} as CT Primary!"); 133 | 134 | player_obj.WeaponsAllocator.PrimaryWeaponCt = GetWeaponIndex(option.Text, WeaponType.PrimaryCt); 135 | 136 | if(showNext) 137 | { 138 | OpenSecondaryTMenu(player); 139 | return; 140 | } 141 | 142 | MenuManager.CloseActiveMenu(player); 143 | } 144 | 145 | public static void OpenSecondaryTMenu(CCSPlayerController player, bool showNext = true) 146 | { 147 | CenterHtmlMenu centerHtmlMenu = new CenterHtmlMenu($"{PREFIX} Select a T Secondary Weapon", Plugin); 148 | 149 | if(Core.Config.AddSkipOption && showNext) 150 | { 151 | centerHtmlMenu.AddMenuOption("SKIP", (p, _) => OpenSecondaryCTMenu(p)); 152 | } 153 | 154 | foreach (Weapon weapon in PistolsT) 155 | { 156 | centerHtmlMenu.AddMenuOption(weapon.DisplayName, (CCSPlayerController player, ChatMenuOption option) => OnSecondaryTSelect(player, option, showNext)); 157 | } 158 | 159 | MenuManager.OpenCenterHtmlMenu(Plugin, player, centerHtmlMenu); 160 | } 161 | 162 | private static void OnSecondaryTSelect(CCSPlayerController player, ChatMenuOption option, bool showNext = true) 163 | { 164 | if (option == null) 165 | { 166 | PrintToChat(player, $"{PREFIX} You did not select a weapon!"); 167 | return; 168 | } 169 | 170 | Player player_obj = FindPlayer(player); 171 | 172 | if (player_obj == null!) 173 | { 174 | return; 175 | } 176 | 177 | PrintToChat(player, $"{PREFIX} You selected {option.Text} as T Secondary!"); 178 | 179 | player_obj.WeaponsAllocator.SecondaryWeaponT = GetWeaponIndex(option.Text, WeaponType.SecondaryT); 180 | 181 | if(showNext) 182 | { 183 | OpenSecondaryCTMenu(player); 184 | return; 185 | } 186 | 187 | MenuManager.CloseActiveMenu(player); 188 | } 189 | 190 | public static void OpenSecondaryCTMenu(CCSPlayerController player, bool showNext = true) 191 | { 192 | CenterHtmlMenu centerHtmlMenu = new CenterHtmlMenu($"{PREFIX} Select a CT Secondary Weapon", Plugin); 193 | 194 | if(Core.Config.AddSkipOption && showNext) 195 | { 196 | centerHtmlMenu.AddMenuOption("SKIP", (p, _) => OpenGiveAWPMenu(p)); 197 | } 198 | 199 | foreach (Weapon weapon in PistolsCT) 200 | { 201 | centerHtmlMenu.AddMenuOption(weapon.DisplayName, (CCSPlayerController player, ChatMenuOption option) => OnSecondaryCTSelect(player, option, showNext)); 202 | } 203 | 204 | MenuManager.OpenCenterHtmlMenu(Plugin, player, centerHtmlMenu); 205 | } 206 | 207 | private static void OnSecondaryCTSelect(CCSPlayerController player, ChatMenuOption option, bool showNext = true) 208 | { 209 | if (option == null) 210 | { 211 | PrintToChat(player, $"{PREFIX} You did not select a weapon!"); 212 | return; 213 | } 214 | 215 | Player player_obj = FindPlayer(player); 216 | 217 | if (player_obj == null!) 218 | { 219 | return; 220 | } 221 | 222 | PrintToChat(player, $"{PREFIX} You selected {option.Text} as CT Secondary!"); 223 | 224 | player_obj.WeaponsAllocator.SecondaryWeaponCt = GetWeaponIndex(option.Text, WeaponType.SecondaryCt); 225 | 226 | if(showNext) 227 | { 228 | OpenGiveAWPMenu(player); 229 | return; 230 | } 231 | 232 | MenuManager.CloseActiveMenu(player); 233 | } 234 | 235 | public static void OpenGiveAWPMenu(CCSPlayerController player) 236 | { 237 | CenterHtmlMenu centerHtmlMenu = new CenterHtmlMenu($"{PREFIX} Select when to give the AWP", Plugin); 238 | 239 | centerHtmlMenu.AddMenuOption("Never", OnGiveAWPSelect); 240 | centerHtmlMenu.AddMenuOption("Sometimes", OnGiveAWPSelect); 241 | centerHtmlMenu.AddMenuOption("Always", OnGiveAWPSelect); 242 | 243 | MenuManager.OpenCenterHtmlMenu(Plugin, player, centerHtmlMenu); 244 | } 245 | 246 | private static void OnGiveAWPSelect(CCSPlayerController player, ChatMenuOption option) 247 | { 248 | if (option == null) 249 | { 250 | PrintToChat(player, $"{PREFIX} You did not select an option!"); 251 | return; 252 | } 253 | 254 | var player_obj = FindPlayer(player); 255 | 256 | if (player_obj == null!) 257 | { 258 | return; 259 | } 260 | 261 | PrintToChat(player, $"{PREFIX} You selected {option.Text} as when to give the AWP!"); 262 | 263 | switch (option.Text) 264 | { 265 | case "Never": 266 | player_obj.WeaponsAllocator.GiveAwp = GiveAwp.Never; 267 | break; 268 | case "Sometimes": 269 | player_obj.WeaponsAllocator.GiveAwp = GiveAwp.Sometimes; 270 | break; 271 | case "Always": 272 | player_obj.WeaponsAllocator.GiveAwp = GiveAwp.Always; 273 | break; 274 | } 275 | 276 | MenuManager.CloseActiveMenu(player); 277 | } 278 | 279 | public static void ShowWeaponSelectionMenu(CCSPlayerController player, List weapons, int time) 280 | { 281 | CenterHtmlMenu centerHtmlMenu = new CenterHtmlMenu($"Select a weapon [{time} Seconds Left]", Plugin); 282 | 283 | foreach (string weapon in weapons) 284 | { 285 | centerHtmlMenu.AddMenuOption(WeaponToDisplayName(weapon), (p, c) => OnWeaponSelect(p, c, weapon)); 286 | } 287 | 288 | MenuManager.OpenCenterHtmlMenu(Plugin, player, centerHtmlMenu); 289 | 290 | timers.Clear(); 291 | 292 | Timer timer = Plugin.AddTimer(1f, () => Countdown(centerHtmlMenu, player, weapons, time)); 293 | timers.Add(player, timer); 294 | } 295 | 296 | private static void OnWeaponSelect(CCSPlayerController player, ChatMenuOption option, string weapon) 297 | { 298 | if (option == null) 299 | { 300 | PrintToChat(player, $"{PREFIX} You did not select a weapon!"); 301 | return; 302 | } 303 | 304 | PrintToChat(player, $"{PREFIX} You selected {WeaponToDisplayName(weapon)} as your weapon!"); 305 | player.GiveNamedItem("weapon_" + weapon); 306 | 307 | if (timers.ContainsKey(player)) 308 | { 309 | Timer timer = timers[player]; 310 | timer.Kill(); 311 | timers.Remove(player); 312 | } 313 | 314 | MenuManager.CloseActiveMenu(player); 315 | } 316 | 317 | public static void Countdown(CenterHtmlMenu menu, CCSPlayerController player, List weapons, int seconds) 318 | { 319 | menu.Title = $"Select a weapon [{--seconds} Seconds Left]"; 320 | 321 | if (seconds == 0) 322 | { 323 | GiveRandomWeapon(player, weapons); 324 | return; 325 | } 326 | 327 | MenuManager.OpenCenterHtmlMenu(Plugin, player, menu); 328 | 329 | Timer timer = Plugin.AddTimer(1f, () => Countdown(menu, player, weapons, seconds)); 330 | timers[player] = timer; 331 | } 332 | 333 | public static void GiveRandomWeapon(CCSPlayerController player, List weapons) 334 | { 335 | string weapon = weapons[new Random().Next(0, weapons.Count)]; 336 | 337 | PrintToChat(player, $"{PREFIX} You have'nt selected a weapon, giving you a random weapon!"); 338 | PrintToChat(player, $"{PREFIX} You received a {WeaponToDisplayName(weapon)}!"); 339 | player.GiveNamedItem("weapon_" + weapon); 340 | 341 | MenuManager.CloseActiveMenu(player); 342 | } 343 | 344 | private static string WeaponToDisplayName(string weapon) 345 | { 346 | if (c2Weapons.ContainsKey(weapon)) 347 | { 348 | return c2Weapons[weapon]; 349 | } 350 | 351 | return weapon; 352 | } 353 | } -------------------------------------------------------------------------------- /MySqlConnector.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ravid-A/cs2-retakes-weapon-allocator/7237feb35c5aebaeead42ff8c0ed8b44cebd1f64/MySqlConnector.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## cs2-retakes-weapon-allocator 2 | 3 | WeaponsAllocator plugin for retakes written in C# (.Net) for CounterStrikeSharp 4 | 5 | ## Retakes 6 | 7 | This plugin is made to run alongside B3none's retakes implementation: https://github.com/b3none/cs2-retakes 8 | 9 | ## Config 10 | 11 | The config file will be generated automaticly by the plugin and will be located where the plugin is and inside a directory named `configs`. 12 | 13 | The DBConnection will be generated empty and the plugin will raise an exception, make sure to update it or update the one provided with the release. 14 | 15 | ### Example Config 16 | 17 | ``` 18 | { 19 | "DBConnection": { 20 | "Host": "", 21 | "Database": "", 22 | "User": "", 23 | "Password": "", 24 | "Port": 3306 25 | }, 26 | "PREFIX": { 27 | "PREFIX": " \u0004[Retakes]\u0001", 28 | "PREFIX_CON": "[Retakes]", 29 | }, 30 | "GiveArmor": true, 31 | "triggerWords": [ 32 | "guns", 33 | "gun", 34 | "weapon", 35 | "weapons" 36 | ] 37 | } 38 | 39 | ``` 40 | 41 | ## Weapons Config 42 | 43 | The weapons can be changed to your prefrence and the configs will be generated to the defaults 44 | and will be located where the plugin is and inside a directory named `configs/weapons`, each for each type of weapon. 45 | 46 | ## Setup for development 47 | 48 | Use cmd in the current directory and run the command `dotnet restore` to install the CounterStrikeSharp API 49 | -------------------------------------------------------------------------------- /RetakesAllocator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | RetakesAllocator 8 | 9 | 10 | 11 | 12 | MySqlConnector.dll 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------