├── CS2DropKnife.cs ├── CS2DropKnife.csproj ├── CS2DropKnifeConf.cs ├── LICENSE ├── README.md └── settings.json.example /CS2DropKnife.cs: -------------------------------------------------------------------------------- 1 | using CounterStrikeSharp.API.Core; 2 | using CounterStrikeSharp.API.Modules.Commands; 3 | using CounterStrikeSharp.API.Core.Attributes.Registration; 4 | using CounterStrikeSharp.API; 5 | using System.Numerics; 6 | using CounterStrikeSharp.API.Modules.Entities; 7 | using CounterStrikeSharp.API.Modules.Menu; 8 | using CounterStrikeSharp.API.Modules.Utils; 9 | using Microsoft.VisualBasic; 10 | using Microsoft.Extensions.Logging.Abstractions; 11 | using System.Runtime.InteropServices; 12 | using CounterStrikeSharp.API.Modules.Events; 13 | 14 | namespace CS2DropKnife; 15 | 16 | public class CS2DropKnife : BasePlugin 17 | { 18 | public override string ModuleName => "CS2 Drop Knife"; 19 | 20 | public override string ModuleVersion => "4.0.0"; 21 | 22 | private List player_slot_ids = new List(); 23 | 24 | private List ct_players = new List(); 25 | private List t_players = new List(); 26 | 27 | private DropRules ?_settings; 28 | 29 | public override void Load(bool hotReload) 30 | { 31 | base.Load(hotReload); 32 | 33 | Console.WriteLine("[CS2DropKnife] Registering listeners."); 34 | RegisterListener(OnMapStartHandler); 35 | 36 | _settings = new DropRules(ModuleDirectory); 37 | _settings.LoadSettings(); 38 | 39 | Server.ExecuteCommand("mp_drop_knife_enable 1"); 40 | } 41 | 42 | 43 | [GameEventHandler] 44 | public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) 45 | { 46 | player_slot_ids.Clear(); 47 | 48 | foreach(var player in Utilities.GetPlayers()) 49 | { 50 | if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) 51 | { 52 | continue; 53 | } 54 | 55 | player_slot_ids.Add(player.Slot); 56 | 57 | if (player.Team == CsTeam.CounterTerrorist) 58 | { 59 | ct_players.Add(player.Slot); 60 | } 61 | if (player.Team == CsTeam.Terrorist) 62 | { 63 | t_players.Add(player.Slot); 64 | } 65 | 66 | } 67 | 68 | return HookResult.Continue; 69 | } 70 | 71 | 72 | public void OnMapStartHandler(string map) 73 | { 74 | Server.ExecuteCommand("mp_drop_knife_enable 1"); 75 | } 76 | 77 | [ConsoleCommand("css_drop", "Drop 5 copies of player's knife on the ground.")] 78 | [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] 79 | public void OnDropCommand(CCSPlayerController player, CommandInfo commandInfo) 80 | { 81 | DropKnife(player); 82 | } 83 | 84 | [ConsoleCommand("css_takeknife", "Drop 5 copies of player's knife on the ground.")] 85 | [CommandHelper(whoCanExecute: CommandUsage.CLIENT_ONLY)] 86 | public void OnTakeKnifeCommand(CCSPlayerController player, CommandInfo commandInfo) 87 | { 88 | DropKnife(player); 89 | } 90 | 91 | 92 | public void DropKnife(CCSPlayerController player) 93 | { 94 | // Player might not be alive. 95 | if (player == null || !player.IsValid || player.IsBot || player.IsHLTV || !player.PawnIsAlive || player.Pawn?.Value == null) // || player.PlayerPawn?.Value.WeaponServices == null || player.PlayerPawn?.Value.ItemServices == null) 96 | { 97 | return; 98 | } 99 | 100 | // It is not allowed for a single player to drop knives multiple times in a round 101 | if (!player_slot_ids.Contains(player.Slot)) 102 | { 103 | return; 104 | } 105 | 106 | // Only allow dropping knife at freeze time 107 | if (_settings == null || _settings.FreezeTimeOnly) 108 | { 109 | var gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules")?.FirstOrDefault()?.GameRules; 110 | if (gameRules != null && !gameRules.FreezePeriod) 111 | { 112 | return; 113 | } 114 | } 115 | 116 | // Find all teammates 117 | List teammates = new List(); 118 | int self_slot = player.Slot; 119 | if (player.Team == CsTeam.CounterTerrorist) 120 | { 121 | foreach (int teammate in ct_players) 122 | { 123 | if (teammate != self_slot) 124 | { 125 | teammates.Add(teammate); 126 | } 127 | } 128 | } 129 | if (player.Team == CsTeam.Terrorist) 130 | { 131 | foreach (int teammate in t_players) 132 | { 133 | if (teammate != self_slot) 134 | { 135 | teammates.Add(teammate); 136 | } 137 | } 138 | } 139 | 140 | // First find the held knife 141 | string knife_designer_name = ""; 142 | int held_knife_index = -1; 143 | 144 | var weapons = player.PlayerPawn.Value?.WeaponServices?.MyWeapons!; 145 | if (weapons == null) 146 | { 147 | return; 148 | } 149 | foreach (var weapon in weapons) 150 | { 151 | if (weapon != null && weapon.IsValid && weapon.Value != null) 152 | { 153 | if (weapon.Value.DesignerName.Contains("knife") || weapon.Value.DesignerName.Contains("bayonet")) 154 | { 155 | knife_designer_name = weapon.Value.DesignerName; 156 | held_knife_index = (int)weapon.Value.Index; 157 | // Server.PrintToChatAll($"[CS2DropKnife] DEBUG knife_designer_name = {knife_designer_name}, index = {held_knife_index}"); // DEBUG 158 | break; 159 | } 160 | } 161 | } 162 | 163 | if (held_knife_index != -1) 164 | { 165 | // Drop knives 166 | List knife_pointers = new List(); 167 | for (int i = 0; i < teammates.Count; i++) 168 | { 169 | nint pointer = player.GiveNamedItem(knife_designer_name); 170 | knife_pointers.Add(pointer); 171 | } 172 | 173 | // Then find dropped knives and teleport 174 | if (_settings == null || _settings.DirectSend) 175 | { 176 | if (knife_pointers.Count >= teammates.Count) 177 | { 178 | for (int i = 0; i < teammates.Count; i++) 179 | { 180 | CBasePlayerWeapon? knife = new(knife_pointers[i]); 181 | if (knife == null || !knife.IsValid) 182 | { 183 | // Server.PrintToChatAll($"[CS2DropKnife] DEBUG failed to find the knife {(int)knife_pointers[i]}"); // DEBUG 184 | continue; 185 | } 186 | // Server.PrintToChatAll($"[CS2DropKnife] DEBUG successfully found the knife with pointer {(int)knife_pointers[i]}, the index is {knife.Index}"); // DEBUG 187 | var teammate = Utilities.GetPlayerFromSlot(teammates[i]); 188 | if (teammate != null && teammate.IsValid && !teammate.IsBot && !teammate.IsHLTV 189 | && teammate.PawnIsAlive && teammate.Pawn != null && teammate.Pawn.IsValid && teammate.Pawn.Value != null) 190 | { 191 | knife.Teleport(teammate.Pawn.Value.AbsOrigin); 192 | } 193 | } 194 | } 195 | 196 | // List knife_indices = new List(); 197 | 198 | // // Find dropped knives (possible bug: what if multiple players drop knife at the same time?) 199 | // var entities = Utilities.FindAllEntitiesByDesignerName(knife_designer_name); 200 | // foreach (var entity in entities) 201 | // { 202 | // if (!entity.IsValid) 203 | // { 204 | // continue; 205 | // } 206 | 207 | // if (entity.State != CSWeaponState_t.WEAPON_NOT_CARRIED) 208 | // { 209 | // continue; 210 | // } 211 | 212 | // if (entity.DesignerName.StartsWith("weapon_") == false) 213 | // { 214 | // continue; 215 | // } 216 | 217 | // Server.PrintToChatAll($"[CS2DropKnife] DEBUG knife on the ground index = {(int)entity.Index}"); // DEBUG 218 | 219 | // if ((int)entity.Index == held_knife_index) 220 | // { 221 | // continue; 222 | // } 223 | 224 | // knife_indices.Add((int)entity.Index); 225 | // } 226 | 227 | // Teleport 228 | // if (knife_indices.Count >= teammates.Count) 229 | // { 230 | // for (int i = 0; i < teammates.Count; i++) 231 | // { 232 | // var knife = Utilities.GetEntityFromIndex(knife_indices[i]); 233 | // if (knife != null && knife.IsValid) 234 | // { 235 | // var teammate = Utilities.GetPlayerFromSlot(teammates[i]); 236 | // if (teammate != null && teammate.IsValid // DEBUG && !teammate.IsBot && !teammate.IsHLTV 237 | // && teammate.PawnIsAlive && teammate.Pawn != null && teammate.Pawn.IsValid && teammate.Pawn.Value != null) 238 | // { 239 | // knife.Teleport(teammate.Pawn.Value.AbsOrigin); 240 | // } 241 | // } 242 | // } 243 | } 244 | } 245 | 246 | // No more chance to drop in this round 247 | if (_settings == null || _settings!.OncePerRound) 248 | { 249 | player_slot_ids.Remove(player.Slot); 250 | } 251 | 252 | return; 253 | } 254 | 255 | 256 | // Enable this for chat filtering (might cause performance issues) 257 | [GameEventHandler] 258 | public HookResult OnPlayerChat(EventPlayerChat @event, GameEventInfo info) 259 | { 260 | if (_settings != null && _settings.ChatFiltering) 261 | { 262 | int player_slot = @event.Userid; 263 | 264 | try 265 | { 266 | CCSPlayerController player = Utilities.GetPlayerFromSlot(player_slot)!; 267 | if (player == null || !player.IsValid || player.IsBot || player.IsHLTV) 268 | { 269 | return HookResult.Continue; 270 | } 271 | 272 | string chat_message = @event.Text; 273 | 274 | if (chat_message.StartsWith("!drop") 275 | || chat_message.StartsWith("/drop") 276 | || chat_message.StartsWith(".drop") 277 | || chat_message.Equals("!d") 278 | || chat_message.Equals("/d") 279 | || chat_message.Equals(".d") 280 | || chat_message.StartsWith("!takeknife") 281 | || chat_message.StartsWith("/takeknife") 282 | || chat_message.StartsWith(".takeknife")) 283 | { 284 | DropKnife(player); 285 | } 286 | } 287 | catch (System.Exception) 288 | { 289 | return HookResult.Continue; 290 | } 291 | } 292 | 293 | return HookResult.Continue; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /CS2DropKnife.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /CS2DropKnifeConf.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace CS2DropKnife; 4 | 5 | public class DropRules 6 | { 7 | public bool DirectSend {get; set; } = true; 8 | 9 | public bool OncePerRound {get; set; } = true; 10 | 11 | public bool FreezeTimeOnly {get; set; } = true; 12 | 13 | public bool ChatFiltering {get; set; } = false; 14 | 15 | private string _moduleDirectory = ""; 16 | 17 | public DropRules() 18 | { 19 | // DeserializeConstructor 20 | } 21 | 22 | public DropRules(string moduleDirectory) 23 | { 24 | _moduleDirectory = moduleDirectory; 25 | } 26 | 27 | public void LoadSettings() 28 | { 29 | string path = $"{_moduleDirectory}/settings.json"; 30 | 31 | if (!File.Exists(path)) 32 | { 33 | // Use default settings 34 | Console.WriteLine("[CS2DropKnife] No custom settings provided. Will use default settings."); 35 | } 36 | else 37 | { 38 | // Load settings from settings.json 39 | JsonSerializerOptions options = new JsonSerializerOptions 40 | { 41 | ReadCommentHandling = JsonCommentHandling.Skip, 42 | AllowTrailingCommas = true, 43 | 44 | }; 45 | 46 | string jsonString = File.ReadAllText(path); 47 | 48 | try 49 | { 50 | DropRules jsonConfig = JsonSerializer.Deserialize(jsonString, options)!; 51 | 52 | DirectSend = jsonConfig.DirectSend; 53 | OncePerRound = jsonConfig.OncePerRound; 54 | FreezeTimeOnly = jsonConfig.FreezeTimeOnly; 55 | ChatFiltering = jsonConfig.ChatFiltering; 56 | 57 | Console.WriteLine($"[CS2DropKnife] Settings are loaded successfully."); 58 | } 59 | catch (System.Exception) 60 | { 61 | Console.WriteLine("[CS2DropKnife] Failed to load settings.json. Will use the default settings."); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 lengran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS2DropKnife 2 | A server-side CS2 plugin powered by CounterStrikeSharp that allows player to share their knife with others. 3 | 4 | ## How to install 5 | 6 | Install CounterStrikeSharp first ([Installation Guide](https://docs.cssharp.dev/docs/guides/getting-started.html)). Extract the files downloaded from [Release](https://github.com/lengran/CS2DropKnife/releases) to the **game/csgo/addons/counterstrikesharp/plugins/CS2DropKnife** folder. 7 | 8 | ## How to use 9 | 10 | As long as you have a knife equipped, type "!drop" or "!takeknife" in the chat box and you should see 5 of your knives dropped on the ground. 11 | 12 | Note: Every player is allowed to drop knives only once in each round. 13 | 14 | ## Which version should I use? 15 | 16 | ## Customize plugin behaviors 17 | 18 | For most of the cases, I would recommand to use the plugin as it is. But if you do wish it to behave differently, feel free to tweat the settings in **settings.json**. A template file *settings.json.example* is provided, and you can simply **rename it to settings.json to enable it**. 19 | 20 | ### Description of options 21 | - If you want the knives to be sent directly to the teammates, set **DirectSend** to true. 22 | 23 | - **OncePerRound** restricts players to drop knife only once in every round. 24 | 25 | - **FreezeTimeOnly** decides whether players are only allowed to drop knife in the freeze time before rounds start. 26 | 27 | - **ChatFiltering** is disabled defaultly. If players have bound "say !drop", this can be enabled to allow such shortcuts. But it might damage server performance. It is strongly suggested to bind "css_drop" instead of "say" or "teamsay" commands. -------------------------------------------------------------------------------- /settings.json.example: -------------------------------------------------------------------------------- 1 | { 2 | // If you want the knives to be sent directly to the teammates, set this to true. 3 | "DirectSend":true, 4 | // This restricts players to drop knife only once in every round. 5 | "OncePerRound":true, 6 | // Players are only allowed to drop knife in the freeze time before rounds start. 7 | "FreezeTimeOnly":true, 8 | // If players have bound "say !drop", this can be enabled to allow such shortcuts. But it might damage server performance. It is strongly suggested to bind "css_drop" instead of "say" or "teamsay" commands. 9 | "ChatFiltering":false 10 | } --------------------------------------------------------------------------------