summaryrefslogtreecommitdiff
path: root/source/sourcemod/scripting/movementapi/hooks.sp
diff options
context:
space:
mode:
authoraura <nw@moneybot.cc>2026-04-25 22:46:52 +0200
committeraura <nw@moneybot.cc>2026-04-25 22:46:52 +0200
commit216c729c8762f4d0388d8750905a91fe1de64ccf (patch)
tree72f0f2f201f7156f220a46a1837c2768abdbb278 /source/sourcemod/scripting/movementapi/hooks.sp
parent6018b8161bdc2bc7eee15500fbd9658d8ad0fc3f (diff)
wip on removing gokz gamemode features
Diffstat (limited to 'source/sourcemod/scripting/movementapi/hooks.sp')
-rw-r--r--source/sourcemod/scripting/movementapi/hooks.sp580
1 files changed, 580 insertions, 0 deletions
diff --git a/source/sourcemod/scripting/movementapi/hooks.sp b/source/sourcemod/scripting/movementapi/hooks.sp
new file mode 100644
index 0000000..7b97b62
--- /dev/null
+++ b/source/sourcemod/scripting/movementapi/hooks.sp
@@ -0,0 +1,580 @@
+static DynamicDetour H_OnPlayerMove;
+static DynamicDetour H_OnDuck;
+static DynamicDetour H_OnLadderMove;
+static DynamicDetour H_OnFullLadderMove;
+static DynamicDetour H_OnJump;
+static DynamicDetour H_OnAirAccelerate;
+static DynamicDetour H_OnWalkMove;
+static DynamicDetour H_OnCategorizePosition;
+
+float gF_Origin[MAXPLAYERS + 1][3];
+float gF_Velocity[MAXPLAYERS + 1][3];
+
+bool gB_ProcessingLadderMove[MAXPLAYERS + 1];
+float gF_PreLadderMoveVelocity[MAXPLAYERS + 1][3];
+bool gB_TakeoffFromLadder[MAXPLAYERS + 1];
+float gF_PostLadderMoveOrigin[MAXPLAYERS + 1][3];
+float gF_PostLadderMoveVelocity[MAXPLAYERS + 1][3];
+
+bool gB_ProcessingDuck[MAXPLAYERS + 1];
+bool gB_Ducking[MAXPLAYERS + 1];
+bool gB_PrevOnGround[MAXPLAYERS + 1];
+bool gB_Duckbugged[MAXPLAYERS + 1];
+float gF_PostDuckOrigin[MAXPLAYERS + 1][3];
+
+bool gB_Jumpbugged[MAXPLAYERS + 1];
+
+bool gB_WalkMoved[MAXPLAYERS + 1];
+float gF_PostWalkMoveVelocity[MAXPLAYERS + 1][3];
+float gF_PostAAOrigin[MAXPLAYERS + 1][3];
+float gF_PostAAVelocity[MAXPLAYERS + 1][3];
+
+bool gB_OldWalkMoved[MAXPLAYERS + 1];
+
+void HookGameMovementFunctions()
+{
+ HookGameMovementFunction(H_OnDuck, "CCSGameMovement::Duck", DHooks_OnDuck_Pre, DHooks_OnDuck_Post);
+ HookGameMovementFunction(H_OnLadderMove, "CGameMovement::LadderMove", DHooks_OnLadderMove_Pre, DHooks_OnLadderMove_Post);
+ HookGameMovementFunction(H_OnFullLadderMove, "CGameMovement::FullLadderMove", DHooks_OnFullLadderMove_Pre, DHooks_OnFullLadderMove_Post);
+ HookGameMovementFunction(H_OnAirAccelerate, "CGameMovement::AirAccelerate", DHooks_OnAirAccelerate_Pre, DHooks_OnAirAccelerate_Post);
+ HookGameMovementFunction(H_OnWalkMove, "CGameMovement::WalkMove", DHooks_OnWalkMove_Pre, DHooks_OnWalkMove_Post);
+ HookGameMovementFunction(H_OnJump, "CCSGameMovement::OnJump", DHooks_OnJump_Pre, DHooks_OnJump_Post);
+ HookGameMovementFunction(H_OnPlayerMove, "CCSGameMovement::PlayerMove", DHooks_OnPlayerMove_Pre, DHooks_OnPlayerMove_Post);
+ HookGameMovementFunction(H_OnCategorizePosition, "CGameMovement::CategorizePosition", DHooks_OnCategorizePosition_Pre, DHooks_OnCategorizePosition_Post);
+}
+
+Action UpdateMoveData(Address pThis, int client, Function func)
+{
+ GameMove_GetOrigin(pThis, gF_Origin[client]);
+ GameMove_GetVelocity(pThis, gF_Velocity[client]);
+ Action result;
+ Call_StartFunction(INVALID_HANDLE, func);
+ Call_PushCell(client);
+ Call_PushArrayEx(gF_Origin[client], 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(gF_Velocity[client], 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ if (result != Plugin_Continue)
+ {
+ GameMove_SetOrigin(pThis, gF_Origin[client]);
+ GameMove_SetVelocity(pThis, gF_Velocity[client]);
+ }
+ return result;
+}
+
+public MRESReturn DHooks_OnDuck_Pre(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client) || Movement_GetMovetype(client) == MOVETYPE_NOCLIP)
+ {
+ return MRES_Ignored;
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnDuckPre);
+
+ gB_Ducking[client] = Movement_GetDucking(client);
+ gB_ProcessingDuck[client] = true;
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnDuck_Post(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client) || Movement_GetMovetype(client) == MOVETYPE_NOCLIP)
+ {
+ return MRES_Ignored;
+ }
+
+ if (gB_Ducking[client] && !gB_OldDucking[client])
+ {
+ Call_OnStartDucking(client);
+ }
+ else if (!gB_Ducking[client] && gB_OldDucking[client])
+ {
+ Call_OnStopDucking(client);
+ }
+ gB_ProcessingDuck[client] = false;
+ GameMove_GetOrigin(pThis, gF_PostDuckOrigin[client]);
+
+ Action result = UpdateMoveData(pThis, client, Call_OnDuckPost);
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnLadderMove_Pre(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client) || Movement_GetMovetype(client) == MOVETYPE_NOCLIP)
+ {
+ return MRES_Ignored;
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnLadderMovePre);
+
+ gB_ProcessingLadderMove[client] = true;
+ GameMove_GetVelocity(pThis, gF_PreLadderMoveVelocity[client]);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnLadderMove_Post(Address pThis, DHookReturn hReturn)
+{
+ // While the movetype changed here, the vertical velocity is not yet updated.
+ // gF_PostLadderMoveVelocity can be incorrect here.
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client) || Movement_GetMovetype(client) == MOVETYPE_NOCLIP)
+ {
+ return MRES_Ignored;
+ }
+
+ GameMove_GetOrigin(pThis, gF_PostLadderMoveOrigin[client]);
+ GameMove_GetVelocity(pThis, gF_PostLadderMoveVelocity[client]);
+ gB_ProcessingLadderMove[client] = false;
+ bool returnValue = DHookGetReturn(hReturn);
+ // If this returns false, and the movetype was originally MOVETYPE_LADDER, that means the player will change movetype and takeoff (LAJ)
+ // If this returns true, the movetype can still change in FullLadderMove by jumping (LAH)
+ // The current movetype here is still ladder, but it will change right after this function call.
+ if (!returnValue && Movement_GetMovetype(client) == MOVETYPE_LADDER)
+ {
+ gF_TakeoffVelocity[client] = gF_PostLadderMoveVelocity[client];
+ gF_TakeoffOrigin[client] = gF_PostLadderMoveOrigin[client];
+ gI_TakeoffTick[client] = gI_TickCount[client];
+ gI_TakeoffCmdNum[client] = gI_Cmdnum[client];
+ gB_Jumped[client] = false;
+ gB_HitPerf[client] = false;
+ Call_OnChangeMovetype(client, MOVETYPE_LADDER, MOVETYPE_WALK);
+ }
+ else if (returnValue && gMT_OldMovetype[client] != MOVETYPE_LADDER)
+ {
+ if (Movement_GetMovetype(client) == MOVETYPE_LADDER)
+ {
+ gF_LandingOrigin[client] = gF_PostLadderMoveOrigin[client];
+ // We don't really care about nobug origin when player lands on ladder.
+ gF_NobugLandingOrigin[client] = gF_LandingOrigin[client];
+ gF_LandingVelocity[client] = gF_PreLadderMoveVelocity[client];
+ gI_LandingCmdNum[client] = gI_Cmdnum[client];
+ gI_LandingTick[client] = gI_TickCount[client];
+ Call_OnChangeMovetype(client, MOVETYPE_WALK, MOVETYPE_LADDER);
+ }
+ }
+ else if (returnValue && gMT_OldMovetype[client] == MOVETYPE_LADDER)
+ {
+ // Player is on the ladder and in the air, pressing jump pushes them away from the ladder.
+ float curtime = GetGameTime();
+ int buttons = GetClientButtons(client);
+ float ignoreLadderJumpTime = GetEntPropFloat(client, Prop_Data, "m_ignoreLadderJumpTime");
+ if (buttons & IN_JUMP && ignoreLadderJumpTime <= curtime)
+ {
+ gF_TakeoffVelocity[client] = gF_PostLadderMoveVelocity[client];
+ gF_TakeoffOrigin[client] = gF_PostLadderMoveOrigin[client];
+ gI_TakeoffTick[client] = gI_TickCount[client];
+ gI_TakeoffCmdNum[client] = gI_Cmdnum[client];
+ gB_Jumped[client] = false;
+ gB_HitPerf[client] = false;
+ gB_TakeoffFromLadder[client] = true;
+ Call_OnChangeMovetype(client, MOVETYPE_LADDER, MOVETYPE_WALK);
+ }
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnLadderMovePost);
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnFullLadderMove_Pre(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnFullLadderMovePre);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnJump_Pre(Address pThis, DHookParam hParams)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+
+ gB_Jumped[client] = true;
+ if (gB_Duckbugged[client])
+ {
+ gB_Jumpbugged[client] = true;
+ }
+
+ // HitPerf must be modified here so plugins can know if player hits a perf or not.
+ // Not a perf if last movetype was ladder, because jumping works differently on ladders.
+ if (gMT_OldMovetype[client] != MOVETYPE_LADDER)
+ {
+ // If you walked on the last tick then clearly it's not going to be a perf.
+ // Can't perf if you don't jump.
+ gB_HitPerf[client] = !gB_OldWalkMoved[client];
+ }
+ else
+ {
+ gB_HitPerf[client] = false;
+ }
+
+ Action result = UpdateMoveData(pThis, client, Call_OnJumpPre);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnJump_Post(Address pThis, DHookParam hParams)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+ // We need to update LadderMove velocity again in case of jumping.
+ GameMove_GetVelocity(pThis, gF_PostLadderMoveVelocity[client]);
+
+ // Current origin because the player hasn't moved yet.
+ gF_TakeoffOrigin[client] = gF_Origin[client];
+ gF_TakeoffVelocity[client] = gF_Velocity[client];
+ gI_TakeoffCmdNum[client] = gI_Cmdnum[client];
+ gI_TakeoffTick[client] = gI_TickCount[client];
+
+ // OnJump will only be called if the client previously touched some sort of ground, so Call_OnStopTouchGround should always be called.
+ Call_OnStopTouchGround(client, true, gB_TakeoffFromLadder[client], gB_Jumpbugged[client]);
+
+ Action result = UpdateMoveData(pThis, client, Call_OnJumpPost);
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnFullLadderMove_Post(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client) || Movement_GetMovetype(client) == MOVETYPE_NOCLIP)
+ {
+ return MRES_Ignored;
+ }
+
+ Action result = UpdateMoveData(pThis, client, Call_OnFullLadderMovePost);
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+// We hook AirAccelerate because TryPlayerMove in AirMove can change velocity
+// AirAccelerate velocity is required for nobug landing origin.
+public MRESReturn DHooks_OnAirAccelerate_Pre(Address pThis, DHookParam hParams)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnAirAcceleratePre);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnAirAccelerate_Post(Address pThis, DHookParam hParams)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+
+ GameMove_GetOrigin(pThis, gF_PostAAOrigin[client]);
+ GameMove_GetVelocity(pThis, gF_PostAAVelocity[client]);
+
+ Action result = UpdateMoveData(pThis, client, Call_OnAirAcceleratePost);
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+// WalkMove is called too early to detect if the player is still on ground or not.
+public MRESReturn DHooks_OnWalkMove_Pre(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnWalkMovePre);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnWalkMove_Post(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+
+ GameMove_GetVelocity(pThis, gF_PostWalkMoveVelocity[client]);
+ gB_WalkMoved[client] = true;
+
+ Action result = UpdateMoveData(pThis, client, Call_OnWalkMovePost);
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnPlayerMove_Pre(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+
+ gB_Duckbugged[client] = false;
+ gB_WalkMoved[client] = false;
+ gB_Jumpbugged[client] = false;
+ gB_Jumped[client] = false;
+ gB_TakeoffFromLadder[client] = false;
+
+ Action result = UpdateMoveData(pThis, client, Call_OnPlayerMovePre);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnPlayerMove_Post(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnPlayerMovePost);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnCategorizePosition_Pre(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+ Action result = UpdateMoveData(pThis, client, Call_OnCategorizePositionPre);
+
+ gB_PrevOnGround[client] = Movement_GetOnGround(client);
+
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+public MRESReturn DHooks_OnCategorizePosition_Post(Address pThis)
+{
+ int client = GetClientFromGameMovementAddress(pThis);
+ if (!IsPlayerAlive(client) || IsFakeClient(client))
+ {
+ return MRES_Ignored;
+ }
+ bool ground = Movement_GetOnGround(client);
+ // Ground state changed!
+ if (gB_PrevOnGround[client] != ground)
+ {
+ if (ground) // Landing
+ {
+ NobugLandingOrigin(client, gF_NobugLandingOrigin[client]);
+
+ gF_LandingOrigin[client] = gF_Origin[client];
+ gI_LandingCmdNum[client] = gI_Cmdnum[client];
+ gI_LandingTick[client] = gI_TickCount[client];
+ Call_OnStartTouchGround(client);
+ }
+ else // Takeoff
+ {
+ gF_TakeoffOrigin[client] = gF_OldOrigin[client];
+ // Note: Jumping isn't detected here.
+ if (gB_WalkMoved[client])
+ {
+ gF_TakeoffVelocity[client] = gF_PostWalkMoveVelocity[client];
+ }
+ else
+ {
+ gF_TakeoffVelocity[client] = gF_PostLadderMoveVelocity[client];
+ }
+ gI_TakeoffTick[client] = gI_TickCount[client];
+ gI_TakeoffCmdNum[client] = gI_Cmdnum[client];
+ gB_Jumped[client] = false;
+ gB_HitPerf[client] = false;
+ bool hadLadderMoveType = Movement_GetMovetype(client) == MOVETYPE_LADDER || gMT_OldMovetype[client] == MOVETYPE_LADDER;
+ Call_OnStopTouchGround(client, false, hadLadderMoveType && !gB_WalkMoved[client], false);
+ }
+ }
+
+ Action result = UpdateMoveData(pThis, client, Call_OnCategorizePositionPost);
+ if (result != Plugin_Continue)
+ {
+ return MRES_Handled;
+ }
+ else
+ {
+ return MRES_Ignored;
+ }
+}
+
+static void NobugLandingOrigin(int client, float landingOrigin[3])
+{
+ // NOTE: Get ground position and distance to ground.
+ float groundEndPoint[3];
+ groundEndPoint = gF_Origin[client];
+ groundEndPoint[2] -= 2.0;
+ float mins[3] = {-16.0, -16.0, 0.0};
+ float maxs[3] = {16.0, 16.0, 0.0};
+ TR_TraceHullFilter(gF_Origin[client], groundEndPoint, mins, maxs, MASK_PLAYERSOLID, TraceEntityFilterPlayers, client);
+
+ float groundPos[3];
+ TR_GetEndPosition(groundPos);
+
+ // NOTE: This is almost guaranteed to hit because CategorizePosition does
+ // the exact same trace to determine if the player is on the ground or not.
+ if (!TR_DidHit())
+ {
+ // Use groundEndPoint if trace fails, because this MIGHT
+ // give less distance in this extremely rare case.
+ groundPos = groundEndPoint;
+ }
+
+ gB_Duckbugged[client] = gB_ProcessingDuck[client];
+ float distanceToGround = gF_Origin[client][2] - groundPos[2];
+ float velocity[3], origin[3];
+ // If there's any distance to the ground, then we'll trace it with this one.
+
+ // It seems like sometimes the player can end up ever so slighly above this "ground" value,
+ // likely due to floating point precision error. Treat it as a bugged jump as well.
+ if (distanceToGround > 0.001 || gB_ProcessingDuck[client])
+ {
+ // Use the current origin and velocity if we're not touching the ground
+ gF_LandingVelocity[client] = gF_Velocity[client];
+ velocity = gF_Velocity[client];
+ origin = gF_Origin[client];
+ }
+ else
+ {
+ // NOTE: Use gF_OldVelocity and gF_OldOrigin if jump is potentially bugged.
+ gF_LandingVelocity[client] = gF_PostAAVelocity[client];
+ velocity = gF_PostAAVelocity[client];
+ origin = gF_PostAAOrigin[client];
+ }
+
+ float firstTraceEndpoint[3], scaledVelocity[3];
+ scaledVelocity = velocity;
+ ScaleVector(scaledVelocity, GetTickInterval());
+ AddVectors(origin, scaledVelocity, firstTraceEndpoint);
+
+ TR_TraceHullFilter(origin, firstTraceEndpoint, mins, maxs, MASK_PLAYERSOLID, TraceEntityFilterPlayers, client);
+ if (!TR_DidHit())
+ {
+ // It is possible to not hit the trace, if your vertical velocity is low enough.
+ // In an extreme case, you would need 10 more traces for this to hit.
+ // It is also possible to miss the trace on a flat jump, by hitting the very edge of a block.
+
+ // Use groundPos, because this will give no distance advantage to the player, but
+ // it will let the player not have his jump invalidated.
+ landingOrigin = groundPos;
+ }
+ else
+ {
+ TR_GetEndPosition(landingOrigin);
+ }
+}