From fee9898651bf30ad1d21360e330233faf07e6923 Mon Sep 17 00:00:00 2001 From: Mat Date: Fri, 3 Sep 2021 10:52:42 +0000 Subject: [PATCH] Initial commit --- LICENSE.md | 19 +++ boostfix.sp | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 395 insertions(+) create mode 100644 LICENSE.md create mode 100644 boostfix.sp diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..389df78 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2021 mat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/boostfix.sp b/boostfix.sp new file mode 100644 index 0000000..d304831 --- /dev/null +++ b/boostfix.sp @@ -0,0 +1,376 @@ +#include +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +public Plugin myinfo = { + name = "boostfix", + version = "1.0.0", + author = "https://github.com/t5mat", + description = "Fixes buggy push triggers; prevents crouchboosting", + url = "https://github.com/t5mat/boostfix", +}; + +#define SOLID_NONE (0) +#define FSOLID_NOT_SOLID (0x0004) + +#define SF_TRIG_PUSH_ONCE (0x80) +#define SF_TRIG_PUSH_AFFECT_PLAYER_ON_LADDER (0x100) + +#define VEC_HULL_MIN (view_as({-16.0, -16.0, 0.0})) +#define VEC_HULL_MAX (view_as({16.0, 16.0, 72.0})) +#define VEC_DUCK_HULL_MIN (view_as({-16.0, -16.0, 0.0})) +#define VEC_DUCK_HULL_MAX (view_as({16.0, 16.0, 54.0})) + +#define CROUCHBOOST_TIME (0.25) + +enum struct Engine +{ + int m_nSolidType; + int m_usSolidFlags; + int m_spawnflags; + int m_vecBaseVelocity; + int m_flLaggedMovementValue; + int m_hGroundEntity; + Handle PassesTriggerFilters_; + int m_hMoveParent; + int m_vecAbsOrigin; + int m_angAbsRotation; + int m_vecAbsVelocity; + int m_flSpeed; + int m_vecPushDir; + + void Initialize() + { + (this.m_nSolidType = FindSendPropInfo("CBaseEntity", "m_nSolidType")) == -1 && SetFailState("CBaseEntity::m_nSolidType"); + (this.m_usSolidFlags = FindSendPropInfo("CBaseEntity", "m_usSolidFlags")) == -1 && SetFailState("CBaseEntity::m_usSolidFlags"); + (this.m_spawnflags = FindSendPropInfo("CBaseTrigger", "m_spawnflags")) == -1 && SetFailState("CBaseTrigger::m_spawnflags"); + (this.m_vecBaseVelocity = FindSendPropInfo("CBasePlayer", "m_vecBaseVelocity")) == -1 && SetFailState("CBasePlayer::m_vecBaseVelocity"); + (this.m_flLaggedMovementValue = FindSendPropInfo("CBasePlayer", "m_flLaggedMovementValue")) == -1 && SetFailState("CBasePlayer::m_flLaggedMovementValue"); + (this.m_hGroundEntity = FindSendPropInfo("CBasePlayer", "m_hGroundEntity")) == -1 && SetFailState("CBasePlayer::m_hGroundEntity"); + + GameData gd; + (gd = new GameData("rngfix.games")) == null && SetFailState("rngfix.games"); + + StartPrepSDKCall(SDKCall_Entity); + PrepSDKCall_SetFromConf(gd, SDKConf_Virtual, "CBaseTrigger::PassesTriggerFilters"); + PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); + (this.PassesTriggerFilters_ = EndPrepSDKCall()) == null && SetFailState("CBaseTrigger::PassesTriggerFilters"); + + delete gd; + + this.m_hMoveParent = -1; + this.m_vecPushDir = -1; + } + + void InitializeCBaseEntityOffsets(int entity) + { + if (this.m_hMoveParent != -1) { + return; + } + + (this.m_hMoveParent = FindDataMapInfo(entity, "m_hMoveParent")) == -1 && SetFailState("CBaseEntity::m_hMoveParent"); + (this.m_vecAbsOrigin = FindDataMapInfo(entity, "m_vecAbsOrigin")) == -1 && SetFailState("CBaseEntity::m_vecAbsOrigin"); + (this.m_angAbsRotation = FindDataMapInfo(entity, "m_angAbsRotation")) == -1 && SetFailState("CBaseEntity::m_angAbsRotation"); + (this.m_vecAbsVelocity = FindDataMapInfo(entity, "m_vecAbsVelocity")) == -1 && SetFailState("CBaseEntity::m_vecAbsVelocity"); + (this.m_flSpeed = FindDataMapInfo(entity, "m_flSpeed")) == -1 && SetFailState("CBaseEntity::m_flSpeed"); + } + + void InitializeCTriggerPushOffsets(int entity) + { + if (this.m_vecPushDir != -1) { + return; + } + + (this.m_vecPushDir = FindDataMapInfo(entity, "m_vecPushDir")) == -1 && SetFailState("CTriggerPush::m_vecPushDir"); + } + + bool PassesTriggerFilters(int entity, int other) + { + return SDKCall(this.PassesTriggerFilters_, entity, other); + } +} + +enum struct Client +{ + float origin[3]; + float velocity[3]; + int flags; + float frame; + bool touching[4096]; + float endTouchFrame[4096]; + bool endTouchDuck[4096]; +} + +bool g_late; +ConVar g_boostfix_pushfix; +ConVar g_boostfix_crouchboostfix; +Engine g_engine; +Client g_clients[MAXPLAYERS + 1]; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + if (GetEngineVersion() != Engine_CSGO) { + FormatEx(error, err_max, "Not supported"); + return APLRes_Failure; + } + + g_late = late; + return APLRes_Success; +} + +public void OnPluginStart() +{ + g_engine.Initialize(); + + g_boostfix_pushfix = CreateConVar("boostfix_pushfix", "1", "Enable trigger_push fix", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_boostfix_crouchboostfix = CreateConVar("boostfix_crouchboostfix", "1", "Enable crouchboost prevention", FCVAR_NOTIFY, true, 0.0, true, 1.0); + + if (g_late) { + for (int e = 0; e < sizeof(Client::touching); ++e) { + if ((e < 1 || e > sizeof(g_clients) - 1) && IsValidEntity(e)) { + char classname[64]; + GetEntityClassname(e, classname, sizeof(classname)); + OnEntityCreated(e, classname); + } + } + + for (int c = 1; c <= MaxClients; ++c) { + if (IsClientInGame(c)) { + OnClientPutInServer(c); + } + } + } +} + +public void OnEntityCreated(int entity, const char[] classname) +{ + g_engine.InitializeCBaseEntityOffsets(entity); + + if (StrEqual(classname, "trigger_push")) { + g_engine.InitializeCTriggerPushOffsets(entity); + + for (int i = 0; i < sizeof(g_clients); ++i) { + g_clients[i].touching[entity] = false; + } + + SDKHook(entity, SDKHook_StartTouch, Hook_PushStartTouch); + SDKHook(entity, SDKHook_EndTouch, Hook_PushEndTouch); + SDKHook(entity, SDKHook_Touch, Hook_PushTouch); + } +} + +public void OnClientPutInServer(int client) +{ + g_clients[client].frame = 0.0; + for (int i = 0; i < sizeof(Client::endTouchFrame); ++i) { + g_clients[client].endTouchFrame[i] = -1.0; + } + + SDKHook(client, SDKHook_PreThinkPost, Hook_ClientPreThinkPost); +} + +void Hook_ClientPreThinkPost(int client) +{ + GetEntDataVector(client, g_engine.m_vecAbsOrigin, g_clients[client].origin); + GetEntDataVector(client, g_engine.m_vecAbsVelocity, g_clients[client].velocity); + g_clients[client].flags = GetEntityFlags(client); + + g_clients[client].frame += GetEntDataFloat(client, g_engine.m_flLaggedMovementValue); +} + +Action Hook_PushStartTouch(int entity, int other) +{ + if (other > sizeof(g_clients) - 1) { + return Plugin_Continue; + } + + if (g_boostfix_crouchboostfix.BoolValue && g_clients[other].endTouchFrame[entity] != -1.0 && g_clients[other].frame - g_clients[other].endTouchFrame[entity] < CROUCHBOOST_TIME / GetTickInterval()) { + bool startTouchUnduck = false; + + if (!g_clients[other].endTouchDuck[entity]) { + // Were we mid-air last tick? + if (!(g_clients[other].flags & FL_ONGROUND)) { + // Did we unduck? + if ((g_clients[other].flags & FL_DUCKING) && !(GetEntityFlags(other) & FL_DUCKING)) { + // Had we not unducked, would we still be not touching the trigger? + float origin[3]; + origin = g_clients[other].velocity; + ScaleVector(origin, GetTickInterval() * GetEntDataFloat(other, g_engine.m_flLaggedMovementValue)); + AddVectors(origin, g_clients[other].origin, origin); + if (!DoesHullEntityIntersect(origin, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, entity)) { + // This StartTouch was caused by a mid-air unduck + startTouchUnduck = true; + } + + } + } + } + + // If this StartTouch happened too soon after the last EndTouch, and either: + // - the last EndTouch was caused by a mid-air duck, or + // - this StartTouch was caused by a mid-air unduck + // then this StartTouch is considered "invalid", disable pushing so we don't get boosted again + g_clients[other].touching[entity] = !(g_clients[other].endTouchDuck[entity] || startTouchUnduck); + } else { + g_clients[other].touching[entity] = true; + } + + if (!g_clients[other].touching[entity]) { + // Prevent outputs from being queued + return Plugin_Handled; + } + + return Plugin_Continue; +} + +Action Hook_PushEndTouch(int entity, int other) +{ + if (other > sizeof(g_clients) - 1) { + return Plugin_Continue; + } + + g_clients[other].touching[entity] = false; + g_clients[other].endTouchFrame[entity] = g_clients[other].frame; + g_clients[other].endTouchDuck[entity] = false; + + // Were we mid-air last tick? + if (!(g_clients[other].flags & FL_ONGROUND)) { + // Did we duck? + if (!(g_clients[other].flags & FL_DUCKING) && (GetEntityFlags(other) & FL_DUCKING)) { + // Had we not ducked, would we still be touching the trigger? + float origin[3]; + origin = g_clients[other].velocity; + ScaleVector(origin, GetTickInterval() * GetEntDataFloat(other, g_engine.m_flLaggedMovementValue)); + AddVectors(origin, g_clients[other].origin, origin); + if (DoesHullEntityIntersect(origin, VEC_HULL_MIN, VEC_HULL_MAX, entity)) { + // This EndTouch was caused by a mid-air duck + g_clients[other].endTouchDuck[entity] = true; + } + } + } + + return Plugin_Continue; +} + +Action Hook_PushTouch(int entity, int other) +{ + if (other > sizeof(g_clients) - 1) { + return Plugin_Continue; + } + + if (!g_clients[other].touching[entity]) { + return Plugin_Handled; + } + + MoveType moveType = GetEntityMoveType(other); + + if (moveType == MOVETYPE_VPHYSICS) { + return Plugin_Continue; + } + + if (!g_boostfix_pushfix.BoolValue) { + return Plugin_Continue; + } + + // https://github.com/perilouswithadollarsign/cstrike15_src/blob/29e4c1fda9698d5cebcdaf1a0de4b829fa149bf8/game/server/triggers.cpp#L2545 + + if (moveType == MOVETYPE_NONE || moveType == MOVETYPE_PUSH) { + return Plugin_Handled; + } + + if ((GetEntData(other, g_engine.m_nSolidType, 1) == SOLID_NONE) || (GetEntData(other, g_engine.m_usSolidFlags, 2) & FSOLID_NOT_SOLID)) { + return Plugin_Handled; + } + + if (GetEntDataEnt2(other, g_engine.m_hMoveParent) != -1) { + return Plugin_Handled; + } + + if (!g_engine.PassesTriggerFilters(entity, other)) { + return Plugin_Handled; + } + + float direction[3]; + { + float rotation[3]; + GetEntDataVector(entity, g_engine.m_angAbsRotation, rotation); + + float local[3]; + GetEntDataVector(entity, g_engine.m_vecPushDir, local); + + float sy = Sine(DegToRad(rotation[1])); + float cy = Cosine(DegToRad(rotation[1])); + float sp = Sine(DegToRad(rotation[0])); + float cp = Cosine(DegToRad(rotation[0])); + float sr = Sine(DegToRad(rotation[2])); + float cr = Cosine(DegToRad(rotation[2])); + + direction[0] = local[0] * (cp * cy) + local[1] * (sp * sr * cy - cr * sy) + local[2] * (sp * cr * cy + sr * sy); + direction[1] = local[0] * (cp * sy) + local[1] * (sp * sr * sy + cr * cy) + local[2] * (sp * cr * sy - sr * cy); + direction[2] = local[0] * (-sp) + local[1] * (sr * cp) + local[2] * (cr * cp); + } + + float speed = GetEntDataFloat(entity, g_engine.m_flSpeed); + + float push[3]; + push = direction; + ScaleVector(push, speed); + + if (GetEntData(entity, g_engine.m_spawnflags) & SF_TRIG_PUSH_ONCE) { + float velocity[3]; + GetEntDataVector(other, g_engine.m_vecAbsVelocity, velocity); + AddVectors(velocity, push, velocity); + + TeleportEntity(other, NULL_VECTOR, NULL_VECTOR, velocity); + if (direction[2] > 0.0) { + SetEntDataEnt2(other, g_engine.m_hGroundEntity, -1); + } + + RemoveEdict(entity); + return Plugin_Handled; + } + + int flags = GetEntityFlags(other); + + if (flags & FL_BASEVELOCITY) { + float base[3]; + GetEntDataVector(other, g_engine.m_vecBaseVelocity, base); + AddVectors(push, base, push); + } + + // https://forums.alliedmods.net/showpost.php?p=2561673&postcount=17 + + float velocity[3]; + GetEntDataVector(other, g_engine.m_vecAbsVelocity, velocity); + velocity[2] += push[2] * GetTickInterval() * GetEntDataFloat(other, g_engine.m_flLaggedMovementValue); + push[2] = 0.0; + + TeleportEntity(other, NULL_VECTOR, NULL_VECTOR, velocity); + SetEntDataVector(other, g_engine.m_vecBaseVelocity, push); + SetEntityFlags(other, flags | FL_BASEVELOCITY); + + return Plugin_Handled; +} + +bool g_DoesHullEntityIntersect_hit; + +bool DoesHullEntityIntersect(const float origin[3], const float mins[3], const float maxs[3], int entity, int mask = PARTITION_TRIGGER_EDICTS) +{ + g_DoesHullEntityIntersect_hit = false; + TR_EnumerateEntitiesHull(origin, origin, mins, maxs, mask, Trace_DoesHullEntityIntersect, entity); + return g_DoesHullEntityIntersect_hit; +} + +bool Trace_DoesHullEntityIntersect(int entity, any data) +{ + if (entity == data) { + TR_ClipCurrentRayToEntity(MASK_ALL, entity); + g_DoesHullEntityIntersect_hit = TR_DidHit(); + return false; + } + return true; +}