#include "sourcemod" #include "sdktools" #include "dhooks" #include "glib/memutils" #define SNAME "[NoclipSpeed] " public Plugin myinfo = { name = "NoclipSpeed", author = "GAMMA CASE", description = "Let's you change noclip speed.", version = "1.0.0", url = "http://steamcommunity.com/id/_GAMMACASE_/" }; #define FLT_EPSILON 1.0e-7 // https://github.com/perilouswithadollarsign/cstrike15_src/blob/29e4c1fda9698d5cebcdaf1a0de4b829fa149bf8/public/const.h#L90 #define MAX_EDICT_BITS 11 #define MAX_EDICTS (1 << MAX_EDICT_BITS) #define NUM_ENT_ENTRY_BITS (MAX_EDICT_BITS + 1) #define NUM_ENT_ENTRIES (1 << NUM_ENT_ENTRY_BITS) #define INVALID_EHANDLE_INDEX 0xFFFFFFFF #define NUM_SERIAL_NUM_BITS 16 // (32 - NUM_ENT_ENTRY_BITS) #define ENT_ENTRY_MASK_CSGO (( 1 << NUM_SERIAL_NUM_BITS) - 1) #define ENT_ENTRY_MASK_CSS (NUM_ENT_ENTRIES - 1) enum OSType { OSUnknown = -1, OSWindows = 1, OSLinux = 2 } OSType gOSType; int gCGameMovement_player_offs; int CBaseEntity_m_RefEHandle_offs; float gPlayerNoclipSpeed[MAXPLAYERS]; ConVar gMaxAllowedNoclipFactor; ConVar sv_maxspeed; ConVar sv_friction; ConVar sv_noclipspeed; EngineVersion gEVType; public void OnPluginStart() { gEVType = GetEngineVersion(); RegConsoleCmd("sm_ns", SM_NoclipSpeed, "Sets noclip speed."); RegConsoleCmd("sm_noclipspeed", SM_NoclipSpeed, "Sets noclip speed."); gMaxAllowedNoclipFactor = CreateConVar("noclipspeed_max_factor", "35", "Max allowed factor for noclip (factor * 300 = speed)", .hasMin = true); AutoExecConfig(); GameData gd = new GameData("noclipspeed.games"); gOSType = view_as(gd.GetOffset("OSType")); ASSERT_MSG(gOSType != OSUnknown, "Failed to get OS type, only windows and linux are supported."); SetupOffs(gd); SetupDhooks(gd); delete gd; sv_noclipspeed = FindConVar("sv_noclipspeed"); ASSERT_MSG(sv_noclipspeed, "Failed to find \"sv_noclipspeed\" cvar."); sv_maxspeed = FindConVar("sv_maxspeed"); ASSERT_MSG(sv_maxspeed, "Failed to find \"sv_maxspeed\" convar."); sv_friction = FindConVar("sv_friction"); ASSERT_MSG(sv_friction, "Failed to find \"sv_friction\" convar."); } public void OnPluginEnd() { } void SetupOffs(GameData gd) { //CGameMovement char buff[32]; ASSERT_MSG(gd.GetKeyValue("CGameMovement::player", buff, sizeof(buff)), "Failed to find \"CGameMovement::player\" offset."); gCGameMovement_player_offs = StringToInt(buff); //CBaseEntity int ibuff = gd.GetOffset("m_angRotation"); ASSERT_MSG(ibuff != -1, "Failed to find \"CBaseEntity::m_angRotation\" offset."); ASSERT_MSG(gd.GetKeyValue("CBaseEntity::m_RefEHandle", buff, sizeof(buff)), "Failed to find \"CBaseEntity::m_RefEHandle\" offset."); CBaseEntity_m_RefEHandle_offs = ibuff + StringToInt(buff); } void SetupDhooks(GameData gd) { Handle dhook; //CGameMovement::FullNoClipMove if(gOSType == OSWindows) { dhook = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Ignore); ASSERT_MSG(DHookSetFromConf(dhook, gd, SDKConf_Signature, "CGameMovement::FullNoClipMove"), "Failed to find \"CGameMovement::FullNoClipMove\" signature."); DHookAddParam(dhook, HookParamType_Int, .custom_register = DHookRegister_ECX); if(gEVType == Engine_CSGO) { DHookAddParam(dhook, HookParamType_Float, .custom_register = DHookRegister_XMM1); DHookAddParam(dhook, HookParamType_Float, .custom_register = DHookRegister_XMM2); } else { DHookAddParam(dhook, HookParamType_Float); DHookAddParam(dhook, HookParamType_Float); } ASSERT_MSG(DHookEnableDetour(dhook, false, FullNoClipMove_Dhook), "Failed to enable \"CGameMovement::FullNoClipMove\" detour."); } else if(gOSType == OSLinux) { dhook = DHookCreateDetour(Address_Null, CallConv_CDECL, ReturnType_Void, ThisPointer_Ignore); ASSERT_MSG(DHookSetFromConf(dhook, gd, SDKConf_Signature, "CGameMovement::FullNoClipMove"), "Failed to find \"CGameMovement::FullNoClipMove\" signature."); DHookAddParam(dhook, HookParamType_Int); DHookAddParam(dhook, HookParamType_Float); DHookAddParam(dhook, HookParamType_Float); ASSERT_MSG(DHookEnableDetour(dhook, false, FullNoClipMove_Dhook), "Failed to enable \"CGameMovement::FullNoClipMove\" detour."); } } public void OnClientPutInServer(int client) { if(IsFakeClient(client)) return; gPlayerNoclipSpeed[client] = sv_noclipspeed.FloatValue; } public void OnClientDisconnect(int client) { if(IsFakeClient(client)) return; gPlayerNoclipSpeed[client] = sv_noclipspeed.FloatValue; char buff[32]; Format(buff, sizeof(buff), "%f", sv_noclipspeed.FloatValue); sv_noclipspeed.ReplicateToClient(client, buff); } public MRESReturn FullNoClipMove_Dhook(Handle hParams) { Address player = view_as
(LoadFromAddress(DHookGetParam(hParams, 1) + gCGameMovement_player_offs, NumberType_Int32)); int client = EntityToBCompatRef(player); if(client <= 0 || client > MaxClients || !IsClientInGame(client) || IsFakeClient(client)) return MRES_Ignored; float factor = DHookGetParam(hParams, 2); if(CloseEnough(factor, gPlayerNoclipSpeed[client])) return MRES_Ignored; DHookSetParam(hParams, 2, gPlayerNoclipSpeed[client]); return MRES_ChangedHandled; } // https://github.com/alliedmodders/sourcemod/blob/c5619f887d6d13643ad8281e8e7479668226c342/core/HalfLife2.cpp#L1082 int EntityToBCompatRef(Address player) { if(player == Address_Null) return INVALID_EHANDLE_INDEX; int m_RefEHandle = LoadFromAddress(player + CBaseEntity_m_RefEHandle_offs, NumberType_Int32) if(m_RefEHandle == INVALID_EHANDLE_INDEX) return INVALID_EHANDLE_INDEX; // https://github.com/perilouswithadollarsign/cstrike15_src/blob/29e4c1fda9698d5cebcdaf1a0de4b829fa149bf8/public/basehandle.h#L137 int entry_idx = gEVType == Engine_CSGO ? m_RefEHandle & ENT_ENTRY_MASK_CSGO : m_RefEHandle & ENT_ENTRY_MASK_CSS; if(entry_idx >= MAX_EDICTS) return m_RefEHandle | (1 << 31); return entry_idx; } public Action SM_NoclipSpeed(int client, int args) { if(!client) return Plugin_Handled; if(args < 1) { Menu menu = new Menu(NoclipSpeed_Menu, MENU_ACTIONS_DEFAULT | MenuAction_DrawItem | MenuAction_Display); menu.AddItem("def", "Reset to default\n "); char buff[128]; Format(buff, sizeof(buff), "Increase by %.2f", GetCurrentSpeedForFactor()); menu.AddItem("inc", buff); Format(buff, sizeof(buff), "Decrease by %.2f", GetCurrentSpeedForFactor()); menu.AddItem("dec", buff); menu.Display(client, MENU_TIME_FOREVER); } else { char buff[32]; GetCmdArg(1, buff, sizeof(buff)); float spd = StringToFloat(buff); gPlayerNoclipSpeed[client] = Clamp(NoclipUPSToFactor(spd), 0.0, gMaxAllowedNoclipFactor.FloatValue); Format(buff, sizeof(buff), "%f", gPlayerNoclipSpeed[client]); sv_noclipspeed.ReplicateToClient(client, buff); PrintToChat(client, SNAME..."Changed noclip speed to: %.2f u/s", NoclipFactorToUPS(gPlayerNoclipSpeed[client])); } return Plugin_Handled; } public int NoclipSpeed_Menu(Menu menu, MenuAction action, int param1, int param2) { switch(action) { case MenuAction_Display: { menu.SetTitle("Noclip speed\n \nCurrent speed: %.2f\n ", NoclipFactorToUPS(gPlayerNoclipSpeed[param1])); } case MenuAction_DrawItem: { char buff[32]; int style; menu.GetItem(param2, buff, sizeof(buff), style); if(StrEqual(buff, "inc") && gPlayerNoclipSpeed[param1] + 1.0 > gMaxAllowedNoclipFactor.FloatValue) return ITEMDRAW_DISABLED; else if(StrEqual(buff, "dec") && gPlayerNoclipSpeed[param1] - 1.0 < 0.0) return ITEMDRAW_DISABLED; return style; } case MenuAction_Select: { char buff[32]; menu.GetItem(param2, buff, sizeof(buff)); if(StrEqual(buff, "inc")) gPlayerNoclipSpeed[param1] += 1.0 + FLT_EPSILON; else if(StrEqual(buff, "dec")) gPlayerNoclipSpeed[param1] -= 1.0 - FLT_EPSILON; else if(StrEqual(buff, "def")) gPlayerNoclipSpeed[param1] = sv_noclipspeed.FloatValue; Format(buff, sizeof(buff), "%f", gPlayerNoclipSpeed[param1]); sv_noclipspeed.ReplicateToClient(param1, buff); menu.Display(param1, MENU_TIME_FOREVER); } case MenuAction_End: if(param2 != MenuEnd_Selected) delete menu; } return 0; } float NoclipUPSToFactor(float spd) { return spd / GetCurrentSpeedForFactor(); } float NoclipFactorToUPS(float factor) { return factor * GetCurrentSpeedForFactor(); } float GetCurrentSpeedForFactor() { return sv_maxspeed.FloatValue - (sv_maxspeed.FloatValue * GetTickInterval() * sv_friction.FloatValue); } float Clamp(float val, float min, float max) { return (val < min) ? min : (max < val) ? max : val; } bool CloseEnough(float a, float b, float eps = FLT_EPSILON) { return FloatAbs(a - b) <= eps; }