├── LICENSE.md ├── README.md └── crouchboostfix.sp /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 mat 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crouchboostfix 2 | 3 | A [SourceMod](https://www.sourcemod.net/about.php) plugin that prevents crouchboosting. 4 | 5 | "Crouchboosting" is the act of crouching/uncrouching while touching a trigger in order to exit and then immediately enter it again. In the case of push triggers, or triggers with `AddOutput basevelocity`, this results in unintentional speed boosts. Crouchboosting is easier on higher tickrates, and can be done pretty consistently on *surf_cookiejar*'s start for example. 6 | 7 | ## Usage 8 | 9 | `crouchboostfix_enabled <0/1>` (default 1) - Enables/disables the plugin 10 | 11 | ## Installation 12 | 13 | **[EndTouchFix](https://github.com/rumourA/End-Touch-Fix)** is required. 14 | 15 | ## Notes 16 | 17 | Use **[PushFixDE](https://github.com/GAMMACASE/PushFixDE)** to fix client prediction errors in push triggers. This plugin is incompatible with any other [pushfix](https://forums.alliedmods.net/showthread.php?t=267131) implementation. 18 | 19 | ## Technical Overview 20 | 21 | When a player starts touching a trigger too soon after their last EndTouch, and either: 22 | 23 | - their last EndTouch was caused by a mid-air duck, or 24 | - this StartTouch was caused by a mid-air unduck 25 | 26 | ... the player is considered to have crouchboosted. 27 | 28 | In this case, 29 | 30 | - For `trigger_multiple/trigger_push/trigger_gravity`, outputs are prevented from being queued (`OnStartTouch/OnEndTouch`) until after the next EndTouch 31 | - For `trigger_push`, pushing is prevented until after the next EndTouch 32 | -------------------------------------------------------------------------------- /crouchboostfix.sp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #pragma semicolon 1 6 | #pragma newdecls required 7 | 8 | public Plugin myinfo = { 9 | name = "crouchboostfix", 10 | version = "2.0.2", 11 | author = "https://github.com/t5mat", 12 | description = "Prevents crouchboosting", 13 | url = "https://github.com/t5mat/crouchboostfix", 14 | }; 15 | 16 | #define CROUCHBOOST_TIME (0.25) 17 | 18 | #define MAX_ENTITIES (4096) 19 | 20 | enum struct Engine 21 | { 22 | int m_vecMins; 23 | int m_vecMaxs; 24 | int m_flLaggedMovementValue; 25 | 26 | int m_vecAbsOrigin; 27 | int m_vecAbsVelocity; 28 | 29 | void Initialize(int entity = -1, const char[] classname = "") 30 | { 31 | static bool start = false; 32 | if (!start) { 33 | start = true; 34 | (this.m_vecMins = FindSendPropInfo("CBaseEntity", "m_vecMins")) == -1 && SetFailState("CBaseEntity::m_vecMins"); 35 | (this.m_vecMaxs = FindSendPropInfo("CBaseEntity", "m_vecMaxs")) == -1 && SetFailState("CBaseEntity::m_vecMaxs"); 36 | (this.m_flLaggedMovementValue = FindSendPropInfo("CBasePlayer", "m_flLaggedMovementValue")) == -1 && SetFailState("CBasePlayer::m_flLaggedMovementValue"); 37 | } 38 | 39 | static bool CBaseEntity = false; 40 | if (!CBaseEntity && entity != -1) { 41 | CBaseEntity = true; 42 | (this.m_vecAbsOrigin = FindDataMapInfo(entity, "m_vecAbsOrigin")) == -1 && SetFailState("CBaseEntity::m_vecAbsOrigin"); 43 | (this.m_vecAbsVelocity = FindDataMapInfo(entity, "m_vecAbsVelocity")) == -1 && SetFailState("CBaseEntity::m_vecAbsVelocity"); 44 | } 45 | } 46 | } 47 | 48 | enum struct Client 49 | { 50 | float origin[3]; 51 | float velocity[3]; 52 | float mins[3]; 53 | float maxs[3]; 54 | int flags; 55 | float frame; 56 | bool validTouch[MAX_ENTITIES]; 57 | float endTouchFrame[MAX_ENTITIES]; 58 | bool endTouchDuck[MAX_ENTITIES]; 59 | } 60 | 61 | bool g_late; 62 | ConVar g_crouchboostfix_enabled; 63 | Engine g_engine; 64 | Client g_clients[MAXPLAYERS + 1]; 65 | 66 | public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) 67 | { 68 | RegPluginLibrary("crouchboostfix"); 69 | g_late = late; 70 | return APLRes_Success; 71 | } 72 | 73 | public void OnPluginStart() 74 | { 75 | g_engine.Initialize(); 76 | 77 | g_crouchboostfix_enabled = CreateConVar("crouchboostfix_enabled", "1", "Enable crouchboost prevention", FCVAR_NOTIFY, true, 0.0, true, 1.0); 78 | 79 | AutoExecConfig(); 80 | 81 | HookEntityOutput("trigger_multiple", "OnStartTouch", Hook_EntityOutput); 82 | HookEntityOutput("trigger_multiple", "OnEndTouch", Hook_EntityOutput); 83 | HookEntityOutput("trigger_push", "OnStartTouch", Hook_EntityOutput); 84 | HookEntityOutput("trigger_push", "OnEndTouch", Hook_EntityOutput); 85 | HookEntityOutput("trigger_gravity", "OnStartTouch", Hook_EntityOutput); 86 | HookEntityOutput("trigger_gravity", "OnEndTouch", Hook_EntityOutput); 87 | 88 | if (g_late) { 89 | for (int e = 0; e < sizeof(Client::validTouch); ++e) { 90 | if (IsValidEntity(e)) { 91 | char classname[64]; 92 | GetEntityClassname(e, classname, sizeof(classname)); 93 | OnEntityCreated(e, classname); 94 | } 95 | } 96 | 97 | for (int c = 1; c <= MaxClients; ++c) { 98 | if (IsClientInGame(c)) { 99 | OnClientPutInServer(c); 100 | } 101 | } 102 | } 103 | } 104 | 105 | public void OnEntityCreated(int entity, const char[] classname) 106 | { 107 | g_engine.Initialize(entity, classname); 108 | 109 | bool push = StrEqual(classname, "trigger_push"); 110 | if (StrEqual(classname, "trigger_multiple") || push || StrEqual(classname, "trigger_gravity")) { 111 | for (int i = 0; i < sizeof(g_clients); ++i) { 112 | g_clients[i].validTouch[entity] = false; 113 | g_clients[i].endTouchFrame[entity] = -1.0; 114 | } 115 | 116 | SDKHook(entity, SDKHook_StartTouch, Hook_TriggerStartTouch); 117 | SDKHook(entity, SDKHook_EndTouchPost, Hook_TriggerEndTouchPost); 118 | if (push) { 119 | SDKHook(entity, SDKHook_Touch, Hook_TriggerPushTouch); 120 | } 121 | } 122 | } 123 | 124 | public void OnClientPutInServer(int client) 125 | { 126 | g_clients[client].frame = 0.0; 127 | for (int i = 0; i < sizeof(Client::endTouchFrame); ++i) { 128 | g_clients[client].endTouchFrame[i] = -1.0; 129 | } 130 | 131 | SDKHook(client, SDKHook_PreThinkPost, Hook_ClientPreThinkPost); 132 | } 133 | 134 | Action Hook_EntityOutput(const char[] output, int caller, int activator, float delay) 135 | { 136 | caller = EntRefToEntIndex(caller); 137 | if (caller == -1 || caller > sizeof(Client::validTouch) - 1) { 138 | return Plugin_Continue; 139 | } 140 | 141 | activator = EntRefToEntIndex(activator); 142 | if (activator < 1 || activator > sizeof(g_clients) - 1) { 143 | return Plugin_Continue; 144 | } 145 | 146 | if (g_crouchboostfix_enabled.BoolValue && !g_clients[activator].validTouch[caller]) { 147 | return Plugin_Handled; 148 | } 149 | 150 | return Plugin_Continue; 151 | } 152 | 153 | void Hook_ClientPreThinkPost(int client) 154 | { 155 | GetEntDataVector(client, g_engine.m_vecAbsOrigin, g_clients[client].origin); 156 | GetEntDataVector(client, g_engine.m_vecAbsVelocity, g_clients[client].velocity); 157 | GetEntDataVector(client, g_engine.m_vecMins, g_clients[client].mins); 158 | GetEntDataVector(client, g_engine.m_vecMaxs, g_clients[client].maxs); 159 | g_clients[client].flags = GetEntityFlags(client); 160 | 161 | g_clients[client].frame += GetEntDataFloat(client, g_engine.m_flLaggedMovementValue); 162 | } 163 | 164 | Action Hook_TriggerStartTouch(int entity, int other) 165 | { 166 | if (other > sizeof(g_clients) - 1) { 167 | return Plugin_Continue; 168 | } 169 | 170 | if (g_clients[other].endTouchFrame[entity] != -1.0 && g_clients[other].frame - g_clients[other].endTouchFrame[entity] < CROUCHBOOST_TIME / GetTickInterval()) { 171 | bool startTouchUnduck = false; 172 | 173 | if (!g_clients[other].endTouchDuck[entity]) { 174 | // Were we mid-air last tick? 175 | if (!(g_clients[other].flags & FL_ONGROUND)) { 176 | // Did we unduck? 177 | if ((g_clients[other].flags & FL_DUCKING) && !(GetEntityFlags(other) & FL_DUCKING)) { 178 | // Had we not unducked, would we still be not touching the trigger? 179 | float origin[3]; 180 | origin = g_clients[other].velocity; 181 | ScaleVector(origin, GetTickInterval() * GetEntDataFloat(other, g_engine.m_flLaggedMovementValue)); 182 | AddVectors(origin, g_clients[other].origin, origin); 183 | if (!DoHullEntityIntersect(origin, g_clients[other].mins, g_clients[other].maxs, entity)) { 184 | // This StartTouch was caused by a mid-air unduck 185 | startTouchUnduck = true; 186 | } 187 | } 188 | } 189 | } 190 | 191 | // If this StartTouch happened too soon after the last EndTouch, and either: 192 | // - the last EndTouch was caused by a mid-air duck, or 193 | // - this StartTouch was caused by a mid-air unduck 194 | // then this StartTouch is considered "invalid", disable pushing so we don't get boosted again 195 | g_clients[other].validTouch[entity] = !(g_clients[other].endTouchDuck[entity] || startTouchUnduck); 196 | } else { 197 | g_clients[other].validTouch[entity] = true; 198 | } 199 | 200 | return Plugin_Continue; 201 | } 202 | 203 | void Hook_TriggerEndTouchPost(int entity, int other) 204 | { 205 | if (other > sizeof(g_clients) - 1) { 206 | return; 207 | } 208 | 209 | g_clients[other].validTouch[entity] = false; 210 | g_clients[other].endTouchFrame[entity] = g_clients[other].frame; 211 | g_clients[other].endTouchDuck[entity] = false; 212 | 213 | // Were we mid-air last tick? 214 | if (!(g_clients[other].flags & FL_ONGROUND)) { 215 | // Did we duck? 216 | if (!(g_clients[other].flags & FL_DUCKING) && (GetEntityFlags(other) & FL_DUCKING)) { 217 | // Had we not ducked, would we still be touching the trigger? 218 | float origin[3]; 219 | origin = g_clients[other].velocity; 220 | ScaleVector(origin, GetTickInterval() * GetEntDataFloat(other, g_engine.m_flLaggedMovementValue)); 221 | AddVectors(origin, g_clients[other].origin, origin); 222 | if (DoHullEntityIntersect(origin, g_clients[other].mins, g_clients[other].maxs, entity)) { 223 | // This EndTouch was caused by a mid-air duck 224 | g_clients[other].endTouchDuck[entity] = true; 225 | } 226 | } 227 | } 228 | } 229 | 230 | Action Hook_TriggerPushTouch(int entity, int other) 231 | { 232 | if (other > sizeof(g_clients) - 1) { 233 | return Plugin_Continue; 234 | } 235 | 236 | if (g_crouchboostfix_enabled.BoolValue && !g_clients[other].validTouch[entity]) { 237 | return Plugin_Handled; 238 | } 239 | 240 | return Plugin_Continue; 241 | } 242 | 243 | bool g_DoHullEntityIntersect_hit; 244 | 245 | bool DoHullEntityIntersect(const float origin[3], const float mins[3], const float maxs[3], int entity, int mask = PARTITION_TRIGGER_EDICTS) 246 | { 247 | g_DoHullEntityIntersect_hit = false; 248 | TR_EnumerateEntitiesHull(origin, origin, mins, maxs, mask, Trace_DoHullEntityIntersect, entity); 249 | return g_DoHullEntityIntersect_hit; 250 | } 251 | 252 | bool Trace_DoHullEntityIntersect(int entity, any data) 253 | { 254 | if (entity == data) { 255 | TR_ClipCurrentRayToEntity(MASK_ALL, entity); 256 | g_DoHullEntityIntersect_hit = TR_DidHit(); 257 | return false; 258 | } 259 | return true; 260 | } 261 | --------------------------------------------------------------------------------