├── .gitignore ├── LICENSE ├── Main.cs ├── README.md ├── WeaponRestrict.csproj └── WeaponRestrict.sln /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Main.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API; 2 | 3 | using CounterStrikeSharp.API.Core; 4 | using CounterStrikeSharp.API.Core.Attributes.Registration; 5 | using CounterStrikeSharp.API.Core.Attributes; 6 | 7 | using CounterStrikeSharp.API.Modules.Config; 8 | using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; 9 | using CounterStrikeSharp.API.Modules.Admin; 10 | using CounterStrikeSharp.API.Modules.Utils; 11 | using CounterStrikeSharp.API.Modules.Commands; 12 | using CounterStrikeSharp.API.Modules.Memory; 13 | 14 | using Microsoft.Extensions.Logging; 15 | 16 | using System.Text.Json.Serialization; 17 | using System.Text.RegularExpressions; 18 | using System.Data; 19 | using System.Reflection; 20 | 21 | namespace WeaponRestrict 22 | { 23 | public class WeaponRestrictConfig : BasePluginConfig 24 | { 25 | [JsonIgnore] 26 | public const int CONFIG_VERSION = 4; 27 | 28 | [JsonIgnore] 29 | public const string WEAPON_QUOTAS = "WeaponQuotas"; 30 | 31 | [JsonIgnore] 32 | public const string WEAPON_LIMITS = "WeaponLimits"; 33 | 34 | [JsonPropertyName("MessagePrefix")] public string MessagePrefix { get; set; } = "{Color.Orange}[WeaponRestrict] "; 35 | [JsonPropertyName("RestrictMessage")] public string RestrictMessage { get; set; } = "{Color.LightPurple}{0}{Color.Default} is currently restricted to {Color.LightRed}{1}{Color.Default} per team."; 36 | [JsonPropertyName("DisabledMessage")] public string DisabledMessage { get; set; } = "{Color.LightPurple}{0}{Color.Default} is currently {Color.LightRed}disabled{Color.Default}."; 37 | 38 | [JsonPropertyName("DefaultQuotas")] 39 | public Dictionary DefaultQuotas { get; set; } = new Dictionary() 40 | { 41 | ["weapon_awp"] = 0.2f 42 | }; 43 | 44 | [JsonPropertyName("DefaultLimits")] 45 | public Dictionary DefaultLimits { get; set; } = new Dictionary() 46 | { 47 | ["weapon_awp"] = 1 48 | }; 49 | 50 | [JsonPropertyName("DoTeamCheck")] public bool DoTeamCheck { get; set; } = true; 51 | 52 | [JsonPropertyName("AllowPickup")] public bool AllowPickup { get; set; } = false; 53 | 54 | [JsonPropertyName("RestrictWarmup")] public bool RestrictWarmup { get; set; } = true; 55 | 56 | [JsonPropertyName("VIPFlag")] public string VIPFlag { get; set; } = "@css/vip"; 57 | 58 | [JsonPropertyName("MapConfigs")] 59 | public Dictionary>> MapConfigs { get; set; } = new Dictionary>>() 60 | { 61 | ["de_dust2"] = new Dictionary>() 62 | { 63 | [WEAPON_QUOTAS] = new Dictionary() 64 | { 65 | ["weapon_awp"] = 0.2f 66 | }, 67 | [WEAPON_LIMITS] = new Dictionary() 68 | { 69 | ["weapon_awp"] = 1 70 | }, 71 | ["awp.*"] = new Dictionary() 72 | } 73 | }; 74 | 75 | [JsonPropertyName("ConfigVersion")] public new int Version { get; set; } = CONFIG_VERSION; 76 | } 77 | 78 | [MinimumApiVersion(284)] 79 | public class WeaponRestrictPlugin : BasePlugin, IPluginConfig 80 | { 81 | public override string ModuleName => "WeaponRestrict"; 82 | 83 | public override string ModuleVersion => "1.4.0"; 84 | 85 | public override string ModuleAuthor => "jon, sapphyrus, FireBird & stefanx111"; 86 | 87 | public override string ModuleDescription => "Restricts player weapons based on total player or teammate count."; 88 | 89 | public required WeaponRestrictConfig Config { get; set; } 90 | 91 | public readonly Dictionary WeaponQuotas = new(); 92 | public readonly Dictionary WeaponLimits = new(); 93 | 94 | public bool InWarmup = false; 95 | 96 | public CCSGameRules? gameRules; 97 | 98 | public override void Load(bool hotReload) 99 | { 100 | VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Hook(OnWeaponCanAcquire, HookMode.Pre); 101 | 102 | RegisterListener((mapName) => 103 | { 104 | Server.NextWorldUpdate(() => 105 | { 106 | gameRules = GetGameRules(); 107 | LoadMapConfig(); 108 | }); 109 | }); 110 | 111 | RegisterEventHandler((@event, info) => 112 | { 113 | InWarmup = true; 114 | 115 | return HookResult.Continue; 116 | }); 117 | 118 | RegisterEventHandler((@event, info) => 119 | { 120 | InWarmup = false; 121 | 122 | return HookResult.Continue; 123 | }); 124 | 125 | if (hotReload) 126 | { 127 | Server.NextWorldUpdate(() => 128 | { 129 | gameRules = GetGameRules(); 130 | LoadMapConfig(); 131 | }); 132 | } 133 | } 134 | 135 | public override void Unload(bool hotReload) 136 | { 137 | VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnWeaponCanAcquire, HookMode.Pre); 138 | } 139 | 140 | private static CCSGameRules GetGameRules() 141 | { 142 | foreach (CBaseEntity entity in Utilities.FindAllEntitiesByDesignerName("cs_gamerules")) 143 | { 144 | return new CCSGameRules(entity.Handle); 145 | } 146 | 147 | throw new Exception("No CCSGameRules found!"); 148 | } 149 | 150 | [ConsoleCommand("css_restrictweapon", "Restricts or unrestricts the specified weapon until mapchange or plugin reload")] 151 | [RequiresPermissions("@css/generic")] 152 | [CommandHelper(minArgs: 1, usage: "weapon_name [[default/none] | [quota/limit value]]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)] 153 | public void OnRestrictCommand(CCSPlayerController? player, CommandInfo commandInfo) 154 | { 155 | string weapon = commandInfo.GetArg(1).ToLower(); 156 | 157 | string commandType = commandInfo.GetArg(2).ToLower(); 158 | 159 | if (weapon == "") 160 | { 161 | commandInfo.ReplyToCommand("WeaponRestrict: Please specify a weapon name"); 162 | return; 163 | } 164 | 165 | if (commandType == "none") 166 | { 167 | WeaponQuotas.Remove(weapon); 168 | WeaponLimits.Remove(weapon); 169 | 170 | commandInfo.ReplyToCommand($"WeaponRestrict: \"{weapon}\" is now unrestricted."); 171 | return; 172 | } 173 | 174 | if (!float.TryParse(commandInfo.GetArg(3), out float limit)) 175 | { 176 | limit = -1f; 177 | } 178 | 179 | switch (commandType) 180 | { 181 | case "quota": 182 | if (limit >= 0f) 183 | { 184 | WeaponQuotas[weapon] = limit; 185 | 186 | commandInfo.ReplyToCommand($"WeaponRestrict: Restricted \"{weapon}\" to \"{limit}\" per player(s) on team"); 187 | } 188 | else 189 | { 190 | WeaponQuotas.Remove(weapon); 191 | 192 | commandInfo.ReplyToCommand($"WeaponRestrict: Removed quota for \"{weapon}\""); 193 | } 194 | 195 | break; 196 | case "limit": 197 | if (limit >= 0f) 198 | { 199 | int roundedLimit = (int)Math.Round(limit); 200 | WeaponLimits[weapon] = roundedLimit; 201 | 202 | commandInfo.ReplyToCommand($"WeaponRestrict: Restricted \"{weapon}\" to \"{roundedLimit}\" per team"); 203 | } 204 | else 205 | { 206 | WeaponLimits.Remove(weapon); 207 | 208 | commandInfo.ReplyToCommand($"WeaponRestrict: Removed limit for \"{weapon}\""); 209 | } 210 | 211 | break; 212 | case "default": 213 | // TODO: Grab the value from the config and do not reload the entire map config. 214 | LoadMapConfig(); 215 | 216 | commandInfo.ReplyToCommand($"WeaponRestrict: Reset to default weapon restrictions."); 217 | break; 218 | default: 219 | commandInfo.ReplyToCommand("WeaponRestrict: Unknown restrict method specified. Please use \"quota\", \"limit\", \"default\", or \"none\""); 220 | break; 221 | } 222 | } 223 | 224 | private void LoadMapConfig() 225 | { 226 | // Load map config if exists 227 | if (Server.MapName == null) return; // Null check on server boot 228 | 229 | // Key: Restriction type, Value: Restriction values 230 | Dictionary>? currentMapConfig = null; 231 | 232 | if (!Config.MapConfigs.TryGetValue(Server.MapName, out currentMapConfig)) 233 | { 234 | currentMapConfig = null; 235 | var cfgEnum = Config.MapConfigs.Where(x => Regex.IsMatch(Server.MapName, $"^{x.Key}$")).Select(x => x.Value); 236 | 237 | if (cfgEnum.Any()) 238 | { 239 | if (cfgEnum.Count() > 1) 240 | { 241 | Logger.LogInformation("Ambiguous wildcard search for {Mapname} in configs.", Server.MapName); 242 | } 243 | 244 | // Load the found wildcard config 245 | currentMapConfig = cfgEnum.First(); 246 | } 247 | } 248 | 249 | WeaponQuotas.Clear(); 250 | WeaponLimits.Clear(); 251 | 252 | if (currentMapConfig == null) 253 | { 254 | // Load the default config 255 | foreach (var (key, value) in Config.DefaultQuotas) 256 | { 257 | WeaponQuotas[key] = value; 258 | } 259 | 260 | foreach (var (key, value) in Config.DefaultLimits) 261 | { 262 | WeaponLimits[key] = value; 263 | } 264 | } 265 | else 266 | { 267 | // Load the found config 268 | if (currentMapConfig.TryGetValue(WeaponRestrictConfig.WEAPON_QUOTAS, out Dictionary? newQuotas)) 269 | { 270 | foreach (var (key, value) in newQuotas) 271 | { 272 | WeaponQuotas[key] = value; 273 | } 274 | } 275 | 276 | if (currentMapConfig.TryGetValue(WeaponRestrictConfig.WEAPON_LIMITS, out Dictionary? newLimits)) 277 | { 278 | foreach (var (key, value) in newLimits) 279 | { 280 | WeaponLimits[key] = (int)value; 281 | } 282 | } 283 | } 284 | 285 | Logger.LogInformation("Loaded {DefaultPrefix}config for {MapName} (Limits: {Limits}, Quotas: {Quotas})", 286 | currentMapConfig == null ? "default " : "", 287 | Server.MapName, 288 | string.Join(",", WeaponLimits), 289 | string.Join(",", WeaponQuotas)); 290 | } 291 | 292 | public static string FormatChatColors(string s) 293 | { 294 | const string FieldPrefix = "Color."; 295 | 296 | if (string.IsNullOrEmpty(s)) 297 | { 298 | return s; 299 | } 300 | 301 | foreach (FieldInfo field in typeof(ChatColors).GetFields().Where(f => (f.FieldType == typeof(char) || f.FieldType == typeof(string)) && f.IsStatic && !string.IsNullOrEmpty(f.Name))) 302 | { 303 | s = s.Replace($"{{{FieldPrefix}{field.Name}}}", field.GetValue(null)!.ToString()); 304 | } 305 | return s; 306 | } 307 | 308 | public void OnConfigParsed(WeaponRestrictConfig loadedConfig) 309 | { 310 | loadedConfig = ConfigManager.Load("WeaponRestrict"); 311 | 312 | if (loadedConfig.Version < WeaponRestrictConfig.CONFIG_VERSION) 313 | { 314 | Logger.LogInformation("Outdated config version. Please review the latest changes and update the config version to {NewCfgVersion}", WeaponRestrictConfig.CONFIG_VERSION); 315 | } 316 | 317 | // TODO: Somehow check for default values? 318 | 319 | // Format chat colors 320 | loadedConfig.MessagePrefix = "\u1010" + FormatChatColors(loadedConfig.MessagePrefix); 321 | loadedConfig.DisabledMessage = FormatChatColors(loadedConfig.DisabledMessage); 322 | loadedConfig.RestrictMessage = FormatChatColors(loadedConfig.RestrictMessage); 323 | 324 | Config = loadedConfig; 325 | 326 | LoadMapConfig(); 327 | } 328 | 329 | private int CountWeaponsOnTeam(string designerName, IEnumerable players) 330 | { 331 | int count = 0; 332 | 333 | foreach (CCSPlayerController player in players) 334 | { 335 | // TODO: Null check can be simplified due to new API changes 336 | // VIP, null and alive check 337 | if ((Config.VIPFlag != "" && AdminManager.PlayerHasPermissions(player, Config.VIPFlag)) 338 | || player.PlayerPawn == null 339 | || !player.PawnIsAlive 340 | || player.PlayerPawn.Value == null 341 | || !player.PlayerPawn.Value.IsValid 342 | || player.PlayerPawn.Value.WeaponServices == null 343 | || player.PlayerPawn.Value.WeaponServices.MyWeapons == null) 344 | continue; 345 | 346 | // Iterate over player weapons 347 | foreach (CHandle weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons) 348 | { 349 | //Get the item DesignerName and compare it to the counted name 350 | if (weapon.Value == null || weapon.Value.DesignerName != designerName) continue; 351 | // Increment count if weapon is found 352 | count++; 353 | } 354 | } 355 | 356 | return count; 357 | } 358 | 359 | public HookResult OnWeaponCanAcquire(DynamicHook hook) 360 | { 361 | // Warmup check 362 | if (!Config.RestrictWarmup && InWarmup) 363 | return HookResult.Continue; 364 | 365 | if (gameRules != null) 366 | { 367 | if (Config.AllowPickup && gameRules.BuyTimeEnded && hook.GetParam(2) == AcquireMethod.PickUp) 368 | return HookResult.Continue; 369 | } 370 | 371 | CCSWeaponBaseVData vdata = VirtualFunctions.GetCSWeaponDataFromKeyFunc.Invoke(-1, hook.GetParam(1).ItemDefinitionIndex.ToString()) ?? throw new Exception("Failed to get CCSWeaponBaseVData"); 372 | 373 | // Weapon is not restricted 374 | if (!WeaponQuotas.ContainsKey(vdata.Name) && !WeaponLimits.ContainsKey(vdata.Name)) 375 | return HookResult.Continue; 376 | 377 | CCSPlayerController client = hook.GetParam(0).Pawn.Value!.Controller.Value!.As(); 378 | 379 | if (client == null || !client.IsValid || !client.PawnIsAlive) 380 | return HookResult.Continue; 381 | 382 | /* 383 | Logger.LogInformation("{Player} is trying to acquire {Weapon} ({Method}) on {Map} (WMUP: {Warmup}, BUYT: {BuyTime})", 384 | client.PlayerName, 385 | vdata.Name, 386 | hook.GetParam(2), 387 | Server.MapName, 388 | InWarmup, 389 | gameRules.BuyTimeEnded 390 | ); 391 | */ 392 | 393 | // Player is VIP proof 394 | if (Config.VIPFlag != "" && AdminManager.PlayerHasPermissions(client, Config.VIPFlag)) 395 | return HookResult.Continue; 396 | 397 | // Get every valid player that is currently connected 398 | IEnumerable players = Utilities.GetPlayers().Where(player => 399 | player.IsValid // Unneccessary? 400 | && player.Connected == PlayerConnectedState.PlayerConnected 401 | && (!Config.DoTeamCheck || player.Team == client.Team) 402 | ); 403 | 404 | int limit = int.MaxValue; 405 | bool disabled = false; 406 | if (WeaponQuotas.TryGetValue(vdata.Name, out float cfgQuota)) 407 | { 408 | limit = Math.Min(limit, cfgQuota > 0f ? (int)(players.Count() * cfgQuota) : 0); 409 | disabled |= cfgQuota == 0f; 410 | } 411 | 412 | if (WeaponLimits.TryGetValue(vdata.Name, out int cfgLimit)) 413 | { 414 | limit = Math.Min(limit, cfgLimit); 415 | disabled |= cfgLimit == 0; 416 | } 417 | 418 | if (!disabled) 419 | { 420 | int count = CountWeaponsOnTeam(vdata.Name, players); 421 | if (count < limit) 422 | return HookResult.Continue; 423 | } 424 | 425 | // Print chat message if we attempted to do anything except pick up this weapon. This is to prevent chat spam. 426 | if (hook.GetParam(2) != AcquireMethod.PickUp) 427 | { 428 | hook.SetReturn(AcquireResult.AlreadyOwned); 429 | 430 | string msg = ""; 431 | if (disabled && Config.DisabledMessage != "") 432 | msg = string.Format(Config.MessagePrefix + Config.DisabledMessage, vdata.Name); 433 | else if (Config.RestrictMessage != "") 434 | msg = string.Format(Config.MessagePrefix + Config.RestrictMessage, vdata.Name, limit.ToString()); 435 | 436 | if (msg != "") 437 | Server.NextFrame(() => client.PrintToChat(msg)); 438 | } 439 | else 440 | { 441 | hook.SetReturn(AcquireResult.InvalidItem); 442 | } 443 | 444 | return HookResult.Stop; 445 | } 446 | } 447 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | Allows you to restrict weapon based on a percentage quota and a hard cap. 3 | 4 | # Installation 5 | 1. Download the `.dll` file in the releases page 6 | 2. Create a folder named `WeaponRestrict` in `/game/csgo/addons/counterstrikesharp/plugins` and move the `WeaponRestrict.dll` into the folder you just created 7 | 3. Start the server 8 | 5. Configure the auto-generated config in `/game/csgo/addons/counterstrikesharp/configs/plugins/WeaponRestrict/WeaponRestrict.json` to your liking using the guide below 9 | 6. *(Optional) Reload the plugin using `css_plugins reload [plugin index]` to update the loaded config (you can get the index by running `css_plugins list`)* 10 | 11 | # Config 12 | You can customize the `MessagePrefix`, `RestrictMessage` and `DisabledMessage`. To not use the `DisabledMessage` leave it as an empty string (""). 13 | 14 | For `RestrictMessage` use `{0}` for the weapon name and `{1}` for the limit. 15 | 16 | For `DisabledMessage` use `{0}` for the weapon name. 17 | 18 | When formatting the messages with colors, use the escaped character code or `{Color.*}` where `*` is the exact name of the field from [this class](https://github.com/roflmuffin/CounterStrikeSharp/blob/5c9d38b2b006e7edf544bb8f185acb4bd5fb6722/managed/CounterStrikeSharp.API/Modules/Utils/ChatColors.cs#L21). 19 | 20 | For example, to use the light purple color use `{Color.LightPurple}` and NOT `{Color.lightPurple}` or any other case variant. 21 | 22 | To completely disable a weapon, set the `WeaponQuotas` or `WeaponLimits` for the desired weapon to 0. 23 | 24 | `WeaponQuotas` is a decimal number used for a percentage of players (0.2 = 20%, for every 5th person a new weapon is allowed), either on the total players or for the team (depending on `DoTeamCheck`). 25 | 26 | `WeaponLimits` is a hard cap per weapon (1 = 1 AWP total/per team, also caps out the `WeaponQuotas` value). 27 | 28 | You can use one, both, or none of `WeaponQuotas` and `WeaponLimits`. 29 | 30 | `AllowPickup` will allow anyone to pick up the restricted weapons after buytime is over, but not allow the purchase if the limit is met on their team. Do note that this is easily bypassable by holders dropping the weapons until other players purchase and picking up when buytime is over. 31 | 32 | `RestrictWarmup` will restrict weapon usage in warmup if set to true. If set to false, restrictions are not applied on warmup period. 33 | 34 | You can use the `VIPFlag` to enable VIP bypass for a permission group, or use an empty string ("") to disable the check. 35 | 36 | The example config will allow 20% of the players on either team to have an AWP, with a hard limit of 1 per team. Meaning if you have at least 5 players on a team, they get one AWP. 37 | VIPs are completely ignored and can always buy, drop and pick up the AWP. 38 | 39 | `MapConfigs` is an **optional** dictionary of maps which contains the same structure of `WeaponQuotas` and `WeaponLimits`. Both are optional. 40 | If you specify a map name and do not specify any quotas or limits, the map will load unrestricted. See `awp.*` in the example config. 41 | You can use a [regular expression](https://regex101.com/) for the map name. Do note that the pattern is already wrapped in `^` and `$` for simplicity. 42 | 43 | If you do not want to add any overrides to maps, make `MapConfigs` and empty array as such: 44 | `"MapConfigs": {}` 45 | 46 | Example config generated by the plugin: 47 | ```json 48 | { 49 | "MessagePrefix": "{Color.Orange}[WeaponRestrict] ", 50 | "RestrictMessage": "{Color.LightPurple}{0}{Color.Default} is currently restricted to {Color.LightRed}{1}{Color.Default} per team.", 51 | "DisabledMessage": "{Color.LightPurple}{0}{Color.Default} is currently {Color.LightRed}disabled{Color.Default}.", 52 | "DefaultQuotas": { 53 | "weapon_awp": 0.2 54 | }, 55 | "DefaultLimits": { 56 | "weapon_awp": 1 57 | }, 58 | "DoTeamCheck": true, 59 | "AllowPickup": false, 60 | "RestrictWarmup": true, 61 | "VIPFlag": "@css/vip", 62 | "MapConfigs": { 63 | "de_dust2": { 64 | "WeaponQuotas": { 65 | "weapon_awp": 0.2 66 | }, 67 | "WeaponLimits": { 68 | "weapon_awp": 1 69 | }, 70 | "awp.*": {} 71 | } 72 | }, 73 | "ConfigVersion": 4 74 | } 75 | ``` 76 | 77 | # Commands 78 | `css_restrictweapon (!restrictweapon)` lets you restrict, unrestrict or reset any weapon until next map change or plugin reload. Usage: `css_restrictweapon weapon_name [[default/none] | [quota/limit value]]`. 79 | -------------------------------------------------------------------------------- /WeaponRestrict.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=X:\ 11 | 12 | 13 | 14 | embedded 15 | 16 | 17 | 18 | embedded 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /WeaponRestrict.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34511.84 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WeaponRestrict", "WeaponRestrict.csproj", "{F65A237E-1776-4DFA-84CB-158A71DB3739}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {F65A237E-1776-4DFA-84CB-158A71DB3739}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {F65A237E-1776-4DFA-84CB-158A71DB3739}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(SolutionProperties) = preSolution 17 | HideSolutionNode = FALSE 18 | EndGlobalSection 19 | GlobalSection(ExtensibilityGlobals) = postSolution 20 | SolutionGuid = {2A9119B3-4737-4B22-B80C-C69D3989D6B3} 21 | EndGlobalSection 22 | EndGlobal 23 | --------------------------------------------------------------------------------