├── .gitignore ├── banner.png ├── .vscode └── tasks.json ├── scripting ├── include │ └── RSP.inc └── SpawnProtection.sp ├── translations └── spawnprotection.phrases.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | SpawnProtection.smx -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/Spawn-Protection/HEAD/banner.png -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "options": 4 | { 5 | "env": 6 | { 7 | "sm_path": "/home/dev/sourcemod/scripting" 8 | } 9 | }, 10 | "tasks": 11 | [ 12 | { 13 | "label": "sourcepawn", 14 | "type": "shell", 15 | "command": "$sm_path/spcomp64 ${file} -D ${workspaceFolder} -i ${workspaceFolder}/scripting/include -i $sm_path/include" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /scripting/include/RSP.inc: -------------------------------------------------------------------------------- 1 | /* Natives */ 2 | /** 3 | * Checks whether the client is protected or not. 4 | * 5 | * @param iClient Client's index. 6 | * 7 | * @return boolean 8 | */ 9 | native bool IsSpawnProtected(int iClient); 10 | 11 | /** 12 | * Notifies the attacker that the victim is AFK. 13 | * 14 | * @param iAttacker The attacker's client index. 15 | * @param iVictim The victim's client index. 16 | * 17 | * @return boolean Returns true on success and false otherwise. 18 | */ 19 | native bool NotifyAttacker(int iAttacker, int iVictim); -------------------------------------------------------------------------------- /translations/spawnprotection.phrases.txt: -------------------------------------------------------------------------------- 1 | "Phrases" 2 | { 3 | // The tag infront of each message. 4 | "Tag" 5 | { 6 | "en" "{darkred}[SP]" 7 | } 8 | 9 | // Announced when you are protected. 10 | "Protected" 11 | { 12 | "en" "{default}You are now protected!" 13 | } 14 | 15 | // Announced when you are not protected anymore. 16 | "NotProtected" 17 | { 18 | "en" "{default}You are no longer protected!" 19 | } 20 | 21 | // Sent when the victim is AFK. 22 | "IsAFK" 23 | { 24 | // 1: The client's username. 25 | "#format" "{1:N}" 26 | "en" "{lightgreen}{1} {default}is AFK!" 27 | } 28 | 29 | // Sent when the victim is protected. 30 | "IsProtected" 31 | { 32 | // 1: The client's username. 33 | "#format" "{1:N}" 34 | "en" "{lightgreen}{1} {default}is protected!" 35 | } 36 | 37 | // When the client disables notifications. 38 | "NotifyDisabled" 39 | { 40 | "en" "{default}Notifications are {lightred}disabled{default}!" 41 | } 42 | 43 | // When the client enables notifications. 44 | "NotifyEnabled" 45 | { 46 | "en" "{default}Notifications are {lightgreen}enabled{default}!" 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Banner](banner.png)](https://bestmods.io/view/cs-spawnprotection/) 2 | 3 | ## Description 4 | A simple SourceMod Spawn Protection plugin that was developed for Counter-Strike: Source and Counter-Strike: Global Offensive, but should work in other games as well. What's neat about this plugin is it uses SDKHooks to block damage and includes an option to stop spawn protection when the victim fires their weapon. 5 | 6 | ## ConVars 7 | * **sm_sp_announce** - When enabled, the attacker will receive chat messages (if enabled per client) that the victim is protected (Default 1). 8 | * **sm_sp_time** - The amount of time to protect each player on spawn (Default 5.0). 9 | * **sm_sp_on_fire** - When enabled, if the victim fires their weapon before the spawn protection time runs out, it will remove their protection (Default 1). 10 | * **sm_sp_exclude_afk** - If `1`, users who are AFK will not be spawn protected. 11 | 12 | ## Commands 13 | * **sm_rspnotify** - This toggles protected notifications per attacker/client via client cookies. 14 | 15 | ## Natives 16 | For developers, the following natives are supported. 17 | 18 | ```SourcePawn 19 | /** 20 | * Checks whether the client is protected or not. 21 | * 22 | * @param iClient Client's index. 23 | * 24 | * @return boolean 25 | */ 26 | native bool IsSpawnProtected(int iClient); 27 | 28 | /** 29 | * Notifies the attacker that the victim is AFK. 30 | * 31 | * @param iAttacker The attacker's client index. 32 | * @param iVictim The victim's client index. 33 | * 34 | * @return boolean Returns true on success and false otherwise. 35 | */ 36 | native bool NotifyAttacker(int iAttacker, int iVictim); 37 | ``` 38 | 39 | You may include the plugin with the following assuming `scripting/include/RSP.inc` exists. 40 | 41 | ```SourcePawn 42 | #include 43 | ``` 44 | 45 | ## Installation 46 | You'll want to compile the source code (`scripting/SpawnProtection.sp`). Afterwards, copy/move the compiled SpawnProtection.smx file into the server's addons/sourcemod/plugins directory. 47 | 48 | ## Credits 49 | * [Christian Deacon](https://github.com/gamemann) -------------------------------------------------------------------------------- /scripting/SpawnProtection.sp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define MAX_BUTTONS 25 9 | 10 | public Plugin myinfo = 11 | { 12 | name = "Spawn Protection", 13 | author = "Roy (Christian Deacon)", 14 | description = "Advanced spawn protection.", 15 | version = "1.1", 16 | url = "GFLClan.com & AlliedMods.net" 17 | }; 18 | 19 | /* Bools for the client */ 20 | bool g_bProtected[MAXPLAYERS+1]; 21 | bool g_bAFK[MAXPLAYERS+1]; 22 | bool g_bFTimer[MAXPLAYERS+1]; 23 | bool g_bNotify[MAXPLAYERS+1]; 24 | 25 | /* ConVars */ 26 | ConVar g_hAnnounce = null; 27 | ConVar g_hTime = null; 28 | ConVar g_hWeaponShoot = null; 29 | ConVar g_hExcludeAFK = null; 30 | 31 | /* ConVar Values */ 32 | bool g_bAnnounce; 33 | float g_fTime 34 | bool g_bWeaponShoot; 35 | bool g_bExcludeAFK; 36 | 37 | /* Client Cookies */ 38 | Handle g_hClientCookie = null; 39 | 40 | public APLRes AskPluginLoad2(Handle hMySelf, bool bLate, char[] sErr, int iErrMax) 41 | { 42 | // Register the plugin's library. 43 | RegPluginLibrary("RSP"); 44 | 45 | // Register the isSpawnProtected native. 46 | CreateNative("IsSpawnProtected", Native_IsSpawnProtected); 47 | 48 | // Register the notifyAttacker native. 49 | CreateNative("NotifyAttacker", Native_NotifyAttacker); 50 | 51 | return APLRes_Success; 52 | } 53 | 54 | public void OnPluginStart() 55 | { 56 | /* Events */ 57 | HookEvent("player_spawn", Event_PlayerSpawn); 58 | HookEvent("weapon_fire", Event_WeaponFire); 59 | 60 | /* ConVars */ 61 | g_hAnnounce = CreateConVar("sm_sp_announce", "1", "Announce when the user is and is not protected?"); 62 | HookConVarChange(g_hAnnounce, CVarChanged); 63 | 64 | g_hTime = CreateConVar("sm_sp_time", "5.0", "Amount of time protection lasts for if the user isn't AFK"); 65 | HookConVarChange(g_hTime, CVarChanged); 66 | 67 | g_hWeaponShoot = CreateConVar("sm_sp_on_fire", "1", "If 1, even if the user is in the \"sm_roy_sp_time\" immunity, if they fire, it will remove spawn protection"); 68 | HookConVarChange(g_hWeaponShoot, CVarChanged); 69 | 70 | g_hExcludeAFK = CreateConVar("sm_sp_exclude_afk", "0", "If 1, users who are AFK will not be spawn protected."); 71 | HookConVarChange(g_hExcludeAFK, CVarChanged); 72 | 73 | /* Commands. */ 74 | RegConsoleCmd("sm_rspnotify", Command_Notify); 75 | 76 | /* Client cookie. */ 77 | g_hClientCookie = RegClientCookie("rsp_notify", "RSP Notify", CookieAccess_Protected); 78 | 79 | /* Translations */ 80 | LoadTranslations("spawnprotection.phrases.txt"); 81 | 82 | /* Config */ 83 | AutoExecConfig(true, "plugin.spawnprotection"); 84 | 85 | /* Late loading. */ 86 | for (int i = MaxClients; i > 0; --i) 87 | { 88 | if (!AreClientCookiesCached(i)) 89 | { 90 | continue; 91 | } 92 | 93 | OnClientCookiesCached(i); 94 | } 95 | } 96 | 97 | public void OnClientCookiesCached(int iClient) 98 | { 99 | char sValue[8]; 100 | GetClientCookie(iClient, g_hClientCookie, sValue, sizeof(sValue)); 101 | 102 | g_bNotify[iClient] = ((sValue[0] == '\0') || (sValue[0] != '\0' && StringToInt(sValue) > 0)); 103 | } 104 | 105 | public void OnConfigsExecuted() 106 | { 107 | // Receive the ConVar values. 108 | g_bAnnounce = GetConVarBool(g_hAnnounce); 109 | g_fTime = GetConVarFloat(g_hTime); 110 | g_bWeaponShoot = GetConVarBool(g_hWeaponShoot); 111 | g_bExcludeAFK = GetConVarBool(g_hExcludeAFK); 112 | } 113 | 114 | public void CVarChanged(Handle hCVar, const char[] sOldV, const char[] sNewV) 115 | { 116 | OnConfigsExecuted(); 117 | } 118 | 119 | /* Commands. */ 120 | public Action Command_Notify(int iClient, int iArgs) 121 | { 122 | if (g_bNotify[iClient]) 123 | { 124 | g_bNotify[iClient] = false; 125 | SetClientCookie(iClient, g_hClientCookie, "0"); 126 | CReplyToCommand(iClient, "%t %t", "Tag", "NotifyDisabled"); 127 | } 128 | else 129 | { 130 | g_bNotify[iClient] = true; 131 | SetClientCookie(iClient, g_hClientCookie, "1"); 132 | CReplyToCommand(iClient, "%t %t", "Tag", "NotifyEnabled"); 133 | } 134 | 135 | return Plugin_Handled; 136 | } 137 | 138 | /* Events */ 139 | // Event: Player Spawn 140 | public Action Event_PlayerSpawn(Event eEvent, const char[] sName, bool bDontBroadcast) 141 | { 142 | // Get the client. 143 | int iClient = GetClientOfUserId(GetEventInt(eEvent, "userid")); 144 | 145 | // Set everything to true. 146 | g_bProtected[iClient] = true; 147 | g_bAFK[iClient] = false; 148 | g_bFTimer[iClient] = true; 149 | 150 | // Announce they are protected (if the ConVar is enabled) 151 | if (g_bAnnounce) 152 | { 153 | // Make sure the client is valid. 154 | if (iClient > 0 && iClient <= MaxClients && IsClientInGame(iClient) && g_bNotify[iClient]) 155 | { 156 | // Print to chat! 157 | CPrintToChat(iClient, "%t %t", "Tag", "Protected"); 158 | } 159 | } 160 | 161 | // Create the timer to disable protection. 162 | CreateTimer(g_fTime, DisableProtection, iClient, TIMER_FLAG_NO_MAPCHANGE); 163 | 164 | return Plugin_Continue; 165 | } 166 | 167 | // Event: Weapon Fire. 168 | public Action Event_WeaponFire(Event eEvent, const char[] sName, bool bDontBroadcast) 169 | { 170 | // Get the client index. 171 | int iClient = GetClientOfUserId(GetEventInt(eEvent, "userid")); 172 | 173 | // Check if the client is valid. 174 | if (iClient > 0 && iClient <= MaxClients && IsClientInGame(iClient) && GetClientTeam(iClient) > 1) 175 | { 176 | // Check if the client has protection. 177 | if (g_bProtected[iClient] || g_bFTimer[iClient]) 178 | { 179 | // If the weapon shoot ConVar is enabled, we must disable the "ftimer" protection as well. 180 | if (g_bWeaponShoot && g_bFTimer[iClient]) 181 | { 182 | // Set "ftimer" to false. 183 | g_bFTimer[iClient] = false; 184 | } 185 | 186 | // Check if the client is "protected". 187 | if (g_bProtected[iClient]) 188 | { 189 | // Set "g_bProtected" to false. 190 | g_bProtected[iClient] = false; 191 | } 192 | 193 | // Announce they are no longer protected (if the ConVar is enabled and they are actually no longer protected) 194 | if (g_bAnnounce && !g_bProtected[iClient] && !g_bFTimer[iClient] && g_bNotify[iClient]) 195 | { 196 | // Print to chat! 197 | CPrintToChat(iClient, "%t %t", "Tag", "NotProtected"); 198 | } 199 | } 200 | 201 | // Check if "g_bAFK" is true. 202 | if (g_bAFK[iClient]) 203 | { 204 | // Set "g_bAFK" to false (since they fired). 205 | g_bAFK[iClient] = false; 206 | } 207 | } 208 | 209 | return Plugin_Continue; 210 | } 211 | 212 | /* Timers */ 213 | // Timer: Player Spawn timer to disable protection! 214 | public Action DisableProtection(Handle hTimer, any iClient) 215 | { 216 | // Check if they are still protected and if the client is valid. 217 | if (g_bFTimer[iClient] && iClient > 0 && iClient <= MaxClients && IsClientInGame(iClient) && IsPlayerAlive(iClient) && GetClientTeam(iClient) > 1) 218 | { 219 | // Set the "ftimer" protection to false. 220 | g_bFTimer[iClient] = false; 221 | 222 | // If they aren't protected (non-AFK), which means they are able to get damaged, announce they are no longer protected (if the ConVar is enabled) 223 | if (!g_bProtected[iClient] || g_bExcludeAFK) 224 | { 225 | // We shouldn't be protected. 226 | if (g_bProtected[iClient]) 227 | { 228 | g_bProtected[iClient] = false; 229 | } 230 | 231 | // Check whether the ConVar is enabled or not. 232 | if (g_bAnnounce && g_bNotify[iClient]) 233 | { 234 | // Print to the chat! 235 | CPrintToChat(iClient, "%t %t", "Tag", "NotProtected"); 236 | } 237 | } 238 | else 239 | { 240 | // The user must be AFK, set "g_bAFK" to true. 241 | g_bAFK[iClient] = true; 242 | } 243 | } 244 | 245 | return Plugin_Continue; 246 | } 247 | 248 | // On Client Put In Server (once they connect) 249 | public void OnClientPutInServer(int iClient) 250 | { 251 | // Hook the "OnTakeDamage" event. 252 | SDKHook(iClient, SDKHook_OnTakeDamage, OnTakeDamage); 253 | } 254 | 255 | // On Player Run Command action (if a user hits a key) 256 | public Action OnPlayerRunCmd(int iClient, int &iButtons, int &iImpulse, float fVel[3], float fAngles[3], int &iWeapon) 257 | { 258 | // Get the maximum buttons. 259 | for (int i = 0; i < MAX_BUTTONS; i++) 260 | { 261 | // Get the button 262 | int button = (1 << i); 263 | 264 | // Check whether the user is hitting a key. 265 | if ((iButtons & button)) 266 | { 267 | // Check if the client is protected and if the client is valid. 268 | if (g_bProtected[iClient] && GetClientTeam(iClient) > 1 && iClient > 0 && iClient <= MaxClients && IsClientInGame(iClient)) 269 | { 270 | // Set "g_bProtected" to false. 271 | g_bProtected[iClient] = false; 272 | 273 | // Set "g_bAFK" to false. 274 | g_bAFK[iClient] = false; 275 | 276 | // Announce they are no longer protected (if the ConVar is enabled and "ftimer" is false) 277 | if (g_bAnnounce && !g_bFTimer[iClient] && g_bNotify[iClient]) 278 | { 279 | // Print to chat! 280 | CPrintToChat(iClient, "%t %t", "Tag", "NotProtected"); 281 | } 282 | } 283 | } 284 | } 285 | 286 | return Plugin_Continue; 287 | } 288 | 289 | // The "OnTakeDamage" SDKHook. 290 | public Action OnTakeDamage(int iVictim, int &iAttacker, int &iInflictor, float &fDamage, int &iDamageType) 291 | { 292 | // Check if "g_bProtected" or "g_bFTimer" is true for the victim. 293 | if (g_bProtected[iVictim] || g_bFTimer[iVictim]) 294 | { 295 | // If it is, they are protected and we need to set fDamage to 0.0. 296 | fDamage = 0.0; 297 | 298 | // Check if the attacker is valid. 299 | if (iAttacker > 0 && iAttacker <= MaxClients && IsClientInGame(iAttacker)) 300 | { 301 | NotifyAttacker(iAttacker, iVictim); 302 | } 303 | 304 | // Return the plugin changed values. 305 | return Plugin_Changed; 306 | } 307 | 308 | // Allow other plugins to execute "OnTakeDamage". 309 | return Plugin_Continue; 310 | } 311 | 312 | /* Natives */ 313 | // Native: IsSpawnProtected (returns true if the player is spawn protected). 314 | public int Native_IsSpawnProtected(Handle hPlugin, int iNumParams) 315 | { 316 | int iClient = GetNativeCell(1); 317 | 318 | if (IsClientInGame(iClient) && (g_bProtected[iClient] || g_bFTimer[iClient])) 319 | { 320 | return true; 321 | } 322 | 323 | return false; 324 | } 325 | 326 | // Native: NotifyUser (Sends a message to the attacker saying the victim is AFK). 327 | public int Native_NotifyAttacker(Handle hPlugin, int iNumParams) 328 | { 329 | int iAttacker = GetNativeCell(1); 330 | int iVictim = GetNativeCell(2); 331 | 332 | if (!IsClientInGame(iAttacker) || !IsClientInGame(iVictim)) 333 | { 334 | return false; 335 | } 336 | 337 | // Check if the attacker has notifications enabled. 338 | if (!g_bNotify[iAttacker]) 339 | { 340 | return false; 341 | } 342 | 343 | // Check if "afk" is true for the victim. 344 | if (g_bAFK[iVictim]) 345 | { 346 | // Print to chat! (victim is AFK) 347 | CPrintToChat(iAttacker, "%t %t", "Tag", "IsAFK", iVictim); 348 | } 349 | else 350 | { 351 | // Print to chat! (victim is protected but not AFK) 352 | CPrintToChat(iAttacker, "%t %t", "Tag", "IsProtected", iVictim); 353 | } 354 | 355 | return true; 356 | } 357 | --------------------------------------------------------------------------------