summaryrefslogtreecommitdiff
path: root/source/sourcemod/scripting/movementapi
diff options
context:
space:
mode:
Diffstat (limited to 'source/sourcemod/scripting/movementapi')
-rw-r--r--source/sourcemod/scripting/movementapi/forwards.sp268
-rw-r--r--source/sourcemod/scripting/movementapi/hooks.sp580
-rw-r--r--source/sourcemod/scripting/movementapi/natives.sp183
-rw-r--r--source/sourcemod/scripting/movementapi/stocks.sp200
4 files changed, 1231 insertions, 0 deletions
diff --git a/source/sourcemod/scripting/movementapi/forwards.sp b/source/sourcemod/scripting/movementapi/forwards.sp
new file mode 100644
index 0000000..9cf0b72
--- /dev/null
+++ b/source/sourcemod/scripting/movementapi/forwards.sp
@@ -0,0 +1,268 @@
+static Handle H_OnStartDucking;
+static Handle H_OnStopDucking;
+static Handle H_OnStartTouchGround;
+static Handle H_OnStopTouchGround;
+static Handle H_OnChangeMovetype;
+static Handle H_OnPlayerJump;
+
+static Handle H_OnPlayerMovePre;
+static Handle H_OnPlayerMovePost;
+static Handle H_OnDuckPre;
+static Handle H_OnDuckPost;
+static Handle H_OnLadderMovePre;
+static Handle H_OnLadderMovePost;
+static Handle H_OnFullLadderMovePre;
+static Handle H_OnFullLadderMovePost;
+static Handle H_OnJumpPre;
+static Handle H_OnJumpPost;
+static Handle H_OnAirAcceleratePre;
+static Handle H_OnAirAcceleratePost;
+static Handle H_OnWalkMovePre;
+static Handle H_OnWalkMovePost;
+static Handle H_OnCategorizePositionPre;
+static Handle H_OnCategorizePositionPost;
+
+void CreateGlobalForwards()
+{
+ H_OnStartDucking = CreateGlobalForward("Movement_OnStartDucking", ET_Ignore, Param_Cell);
+ H_OnStopDucking = CreateGlobalForward("Movement_OnStopDucking", ET_Ignore, Param_Cell);
+ H_OnStartTouchGround = CreateGlobalForward("Movement_OnStartTouchGround", ET_Ignore, Param_Cell);
+ H_OnStopTouchGround = CreateGlobalForward("Movement_OnStopTouchGround", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
+ H_OnChangeMovetype = CreateGlobalForward("Movement_OnChangeMovetype", ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
+ H_OnPlayerJump = CreateGlobalForward("Movement_OnPlayerJump", ET_Ignore, Param_Cell, Param_Cell);
+
+ H_OnPlayerMovePre = CreateGlobalForward("Movement_OnPlayerMovePre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnPlayerMovePost = CreateGlobalForward("Movement_OnPlayerMovePost", ET_Event, Param_Cell, Param_Array, Param_Array);
+
+ H_OnDuckPre = CreateGlobalForward("Movement_OnDuckPre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnDuckPost = CreateGlobalForward("Movement_OnDuckPost", ET_Event, Param_Cell, Param_Array, Param_Array);
+
+ H_OnLadderMovePre = CreateGlobalForward("Movement_OnLadderMovePre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnLadderMovePost = CreateGlobalForward("Movement_OnLadderMovePost", ET_Event, Param_Cell, Param_Array, Param_Array);
+
+ H_OnFullLadderMovePre = CreateGlobalForward("Movement_OnFullLadderMovePre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnFullLadderMovePost = CreateGlobalForward("Movement_OnFullLadderMovePost", ET_Event, Param_Cell, Param_Array, Param_Array);
+
+ H_OnJumpPre = CreateGlobalForward("Movement_OnJumpPre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnJumpPost = CreateGlobalForward("Movement_OnJumpPost", ET_Event, Param_Cell, Param_Array, Param_Array);
+
+ H_OnAirAcceleratePre = CreateGlobalForward("Movement_OnAirAcceleratePre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnAirAcceleratePost = CreateGlobalForward("Movement_OnAirAcceleratePost", ET_Event, Param_Cell, Param_Array, Param_Array);
+
+ H_OnWalkMovePre = CreateGlobalForward("Movement_OnWalkMovePre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnWalkMovePost = CreateGlobalForward("Movement_OnWalkMovePost", ET_Event, Param_Cell, Param_Array, Param_Array);
+
+ H_OnCategorizePositionPre = CreateGlobalForward("Movement_OnCategorizePositionPre", ET_Event, Param_Cell, Param_Array, Param_Array);
+ H_OnCategorizePositionPost = CreateGlobalForward("Movement_OnCategorizePositionPost", ET_Event, Param_Cell, Param_Array, Param_Array);
+}
+
+void Call_OnStartDucking(int client)
+{
+ Call_StartForward(H_OnStartDucking);
+ Call_PushCell(client);
+ Call_Finish();
+}
+
+void Call_OnStopDucking(int client)
+{
+ Call_StartForward(H_OnStopDucking);
+ Call_PushCell(client);
+ Call_Finish();
+}
+
+void Call_OnStartTouchGround(int client)
+{
+ Call_StartForward(H_OnStartTouchGround);
+ Call_PushCell(client);
+ Call_Finish();
+}
+
+void Call_OnStopTouchGround(int client, bool jumped, bool ladderJump, bool jumpbug)
+{
+ Call_StartForward(H_OnStopTouchGround);
+ Call_PushCell(client);
+ Call_PushCell(jumped);
+ Call_PushCell(ladderJump);
+ Call_PushCell(jumpbug);
+ Call_Finish();
+ // Immediately update OldOnGround state, so we can catch takeoffs that happen outside movement processing.
+ gB_OldOnGround[client] = false;
+}
+
+
+void Call_OnChangeMovetype(int client, MoveType oldMovetype, MoveType newMovetype)
+{
+ Call_StartForward(H_OnChangeMovetype);
+ Call_PushCell(client);
+ Call_PushCell(oldMovetype);
+ Call_PushCell(newMovetype);
+ Call_Finish();
+}
+
+void Call_OnPlayerJump(int client, bool jumpbug)
+{
+ Call_StartForward(H_OnPlayerJump);
+ Call_PushCell(client);
+ Call_PushCell(jumpbug);
+ Call_Finish();
+}
+
+Action Call_OnPlayerMovePre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnPlayerMovePre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnPlayerMovePost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnPlayerMovePost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnDuckPre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnDuckPre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnDuckPost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnDuckPost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnLadderMovePre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnLadderMovePre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnLadderMovePost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnLadderMovePost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnFullLadderMovePre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnFullLadderMovePre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnFullLadderMovePost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnFullLadderMovePost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnJumpPre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnJumpPre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnJumpPost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnJumpPost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnAirAcceleratePre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnAirAcceleratePre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnAirAcceleratePost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnAirAcceleratePost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnWalkMovePre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnWalkMovePre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnWalkMovePost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnWalkMovePost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnCategorizePositionPre(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnCategorizePositionPre);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+}
+
+Action Call_OnCategorizePositionPost(int client, float origin[3], float velocity[3], Action &result)
+{
+ Call_StartForward(H_OnCategorizePositionPost);
+ Call_PushCell(client);
+ Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK);
+ Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK);
+ Call_Finish(result);
+ return result;
+} \ No newline at end of file
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);
+ }
+}
diff --git a/source/sourcemod/scripting/movementapi/natives.sp b/source/sourcemod/scripting/movementapi/natives.sp
new file mode 100644
index 0000000..f8cd7a5
--- /dev/null
+++ b/source/sourcemod/scripting/movementapi/natives.sp
@@ -0,0 +1,183 @@
+void CreateNatives()
+{
+ CreateNative("Movement_GetJumped", Native_GetJumped);
+ CreateNative("Movement_GetHitPerf", Native_GetHitPerf);
+ CreateNative("Movement_GetTakeoffOrigin", Native_GetTakeoffOrigin);
+ CreateNative("Movement_GetTakeoffVelocity", Native_GetTakeoffVelocity);
+ CreateNative("Movement_GetTakeoffSpeed", Native_GetTakeoffSpeed);
+ CreateNative("Movement_GetTakeoffTick", Native_GetTakeoffTick);
+ CreateNative("Movement_GetTakeoffCmdNum", Native_GetTakeoffCmdNum);
+ CreateNative("Movement_GetNobugLandingOrigin", Native_GetNobugLandingOrigin);
+ CreateNative("Movement_GetLandingOrigin", Native_GetLandingOrigin);
+ CreateNative("Movement_GetLandingVelocity", Native_GetLandingVelocity);
+ CreateNative("Movement_GetLandingSpeed", Native_GetLandingSpeed);
+ CreateNative("Movement_GetLandingTick", Native_GetLandingTick);
+ CreateNative("Movement_GetLandingCmdNum", Native_GetLandingCmdNum);
+ CreateNative("Movement_GetTurning", Native_GetTurning);
+ CreateNative("Movement_GetTurningLeft", Native_GetTurningLeft);
+ CreateNative("Movement_GetTurningRight", Native_GetTurningRight);
+ CreateNative("Movement_GetMaxSpeed", Native_GetMaxSpeed);
+ CreateNative("Movement_GetDuckbugged", Native_GetDuckbugged);
+ CreateNative("Movement_GetJumpbugged", Native_GetJumpbugged);
+ CreateNative("Movement_GetProcessingOrigin", Native_GetProcessingOrigin);
+ CreateNative("Movement_GetProcessingVelocity", Native_GetProcessingVelocity);
+ CreateNative("Movement_SetTakeoffOrigin", Native_SetTakeoffOrigin);
+ CreateNative("Movement_SetTakeoffVelocity", Native_SetTakeoffVelocity);
+ CreateNative("Movement_SetLandingOrigin", Native_SetLandingOrigin);
+ CreateNative("Movement_SetLandingVelocity", Native_SetLandingVelocity);
+}
+
+public int Native_GetJumped(Handle plugin, int numParams)
+{
+ return gB_Jumped[GetNativeCell(1)];
+}
+
+public int Native_GetHitPerf(Handle plugin, int numParams)
+{
+ return gB_HitPerf[GetNativeCell(1)];
+}
+
+public int Native_GetTakeoffOrigin(Handle plugin, int numParams)
+{
+ return SetNativeArray(2, gF_TakeoffOrigin[GetNativeCell(1)], 3);
+}
+
+public int Native_GetTakeoffVelocity(Handle plugin, int numParams)
+{
+ return SetNativeArray(2, gF_TakeoffVelocity[GetNativeCell(1)], 3);
+}
+
+public int Native_GetTakeoffSpeed(Handle plugin, int numParams)
+{
+ return view_as<int>(GetVectorHorizontalLength(gF_TakeoffVelocity[GetNativeCell(1)]));
+}
+
+public int Native_GetTakeoffTick(Handle plugin, int numParams)
+{
+ return gI_TakeoffTick[GetNativeCell(1)];
+}
+
+public int Native_GetTakeoffCmdNum(Handle plugin, int numParams)
+{
+ return gI_TakeoffCmdNum[GetNativeCell(1)];
+}
+
+public int Native_GetNobugLandingOrigin(Handle plugin, int numParams)
+{
+ return SetNativeArray(2, gF_NobugLandingOrigin[GetNativeCell(1)], 3);
+}
+
+public int Native_GetLandingOrigin(Handle plugin, int numParams)
+{
+ return SetNativeArray(2, gF_LandingOrigin[GetNativeCell(1)], 3);
+}
+
+public int Native_GetLandingVelocity(Handle plugin, int numParams)
+{
+ return SetNativeArray(2, gF_LandingVelocity[GetNativeCell(1)], 3);
+}
+
+public int Native_GetLandingSpeed(Handle plugin, int numParams)
+{
+ return view_as<int>(GetVectorHorizontalLength(gF_LandingVelocity[GetNativeCell(1)]));
+}
+
+public int Native_GetLandingTick(Handle plugin, int numParams)
+{
+ return gI_LandingTick[GetNativeCell(1)];
+}
+
+public int Native_GetLandingCmdNum(Handle plugin, int numParams)
+{
+ return gI_LandingCmdNum[GetNativeCell(1)];
+}
+
+public int Native_GetTurning(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_Turning[GetNativeCell(1)]);
+}
+
+public int Native_GetTurningLeft(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_TurningLeft[GetNativeCell(1)]);
+}
+
+public int Native_GetTurningRight(Handle plugin, int numParams)
+{
+ int client = GetNativeCell(1);
+ return view_as<int>(gB_Turning[client] && !gB_TurningLeft[client]);
+}
+
+public int Native_GetMaxSpeed(Handle plugin, int numParams)
+{
+ int client = GetNativeCell(1);
+ return view_as<int>(GetMaxSpeed(client));
+}
+
+public int Native_GetDuckbugged(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_Duckbugged[GetNativeCell(1)]);
+}
+
+public int Native_GetJumpbugged(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_Jumpbugged[GetNativeCell(1)]);
+}
+
+public int Native_GetProcessingOrigin(Handle plugin, int numParams)
+{
+ return SetNativeArray(2, gF_Origin[GetNativeCell(1)], 3);
+}
+
+public int Native_GetProcessingVelocity(Handle plugin, int numParams)
+{
+ return SetNativeArray(2, gF_Velocity[GetNativeCell(1)], 3);
+}
+
+public int Native_SetTakeoffOrigin(Handle plugin, int numParams)
+{
+ float array[3];
+ GetNativeArray(2, array, sizeof(array));
+ for (int i = 0; i < 3; i++)
+ {
+ gF_TakeoffOrigin[GetNativeCell(1)][i] = array[i];
+ }
+
+ return 0;
+}
+
+public int Native_SetTakeoffVelocity(Handle plugin, int numParams)
+{
+ float array[3];
+ GetNativeArray(2, array, sizeof(array));
+ for (int i = 0; i < 3; i++)
+ {
+ gF_TakeoffVelocity[GetNativeCell(1)][i] = array[i];
+ }
+
+ return 0;
+}
+
+public int Native_SetLandingOrigin(Handle plugin, int numParams)
+{
+ float array[3];
+ GetNativeArray(2, array, sizeof(array));
+ for (int i = 0; i < 3; i++)
+ {
+ gF_LandingOrigin[GetNativeCell(1)][i] = array[i];
+ }
+
+ return 0;
+}
+
+public int Native_SetLandingVelocity(Handle plugin, int numParams)
+{
+ float array[3];
+ GetNativeArray(2, array, sizeof(array));
+ for (int i = 0; i < 3; i++)
+ {
+ gF_LandingVelocity[GetNativeCell(1)][i] = array[i];
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/source/sourcemod/scripting/movementapi/stocks.sp b/source/sourcemod/scripting/movementapi/stocks.sp
new file mode 100644
index 0000000..e080efc
--- /dev/null
+++ b/source/sourcemod/scripting/movementapi/stocks.sp
@@ -0,0 +1,200 @@
+stock void GameMove_SetVelocity(Address addr, float velocity[3])
+{
+ if (velocity[0] != velocity[0] || velocity[1] != velocity[1] || velocity[2] != velocity[2])
+ {
+ return;
+ }
+ static int mvOffset;
+ static int velocityOffset;
+ if (!mvOffset)
+ {
+ char buffer[8];
+ if (!gH_GameData.GetKeyValue("CGameMovement::mv", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CGameMovement::mv offset.");
+ return;
+ }
+ mvOffset = StringToInt(buffer);
+ if (!gH_GameData.GetKeyValue("CMoveData::m_vecVelocity", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CMoveData::m_vecVelocity offset.");
+ return;
+ }
+ velocityOffset = StringToInt(buffer);
+ }
+
+ Address mvAddress = view_as<Address>(LoadFromAddress(view_as<Address>(view_as<int>(addr) + mvOffset), NumberType_Int32));
+ // TODO: idk if raw cast works here or not
+ StoreToAddress(view_as<Address>(view_as<int>(mvAddress) + velocityOffset), view_as<int>(velocity[0]), NumberType_Int32);
+ StoreToAddress(view_as<Address>(view_as<int>(mvAddress) + velocityOffset + 4), view_as<int>(velocity[1]), NumberType_Int32);
+ StoreToAddress(view_as<Address>(view_as<int>(mvAddress) + velocityOffset + 8), view_as<int>(velocity[2]), NumberType_Int32);
+}
+
+stock void GameMove_SetOrigin(Address addr, float origin[3])
+{
+ if (origin[0] != origin[0] || origin[1] != origin[1] || origin[2] != origin[2])
+ {
+ return;
+ }
+ static int mvOffset;
+ static int originOffset;
+ if (!mvOffset)
+ {
+ char buffer[8];
+ if (!gH_GameData.GetKeyValue("CGameMovement::mv", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CGameMovement::mv offset.");
+ return;
+ }
+ mvOffset = StringToInt(buffer);
+ if (!gH_GameData.GetKeyValue("CMoveData::m_vecAbsOrigin", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CMoveData::m_vecAbsOrigin offset.");
+ return;
+ }
+ originOffset = StringToInt(buffer);
+ }
+
+ Address mvAddress = view_as<Address>(LoadFromAddress(view_as<Address>(view_as<int>(addr) + mvOffset), NumberType_Int32));
+ // TODO: idk if raw cast works here or not
+ StoreToAddress(view_as<Address>(view_as<int>(mvAddress) + originOffset), view_as<int>(origin[0]), NumberType_Int32);
+ StoreToAddress(view_as<Address>(view_as<int>(mvAddress) + originOffset + 4), view_as<int>(origin[1]), NumberType_Int32);
+ StoreToAddress(view_as<Address>(view_as<int>(mvAddress) + originOffset + 8), view_as<int>(origin[2]), NumberType_Int32);
+}
+
+stock void GameMove_GetVelocity(Address addr, float result[3])
+{
+ static int mvOffset;
+ static int velocityOffset;
+ if (!mvOffset)
+ {
+ char buffer[8];
+ if (!gH_GameData.GetKeyValue("CGameMovement::mv", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CGameMovement::mv offset.");
+ return;
+ }
+ mvOffset = StringToInt(buffer);
+ if (!gH_GameData.GetKeyValue("CMoveData::m_vecVelocity", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CMoveData::m_vecVelocity offset.");
+ return;
+ }
+ velocityOffset = StringToInt(buffer);
+ }
+
+ Address mvAddress = view_as<Address>(LoadFromAddress(view_as<Address>(view_as<int>(addr) + mvOffset), NumberType_Int32));
+ result[0] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + velocityOffset), NumberType_Int32));
+ result[1] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + velocityOffset + 4), NumberType_Int32));
+ result[2] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + velocityOffset + 8), NumberType_Int32));
+}
+
+stock void GameMove_GetOrigin(Address addr, float result[3])
+{
+ static int mvOffset;
+ static int originOffset;
+ if (!mvOffset)
+ {
+ char buffer[8];
+ if (!gH_GameData.GetKeyValue("CGameMovement::mv", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CGameMovement::mv offset.");
+ return;
+ }
+ mvOffset = StringToInt(buffer);
+ if (!gH_GameData.GetKeyValue("CMoveData::m_vecAbsOrigin", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CMoveData::m_vecAbsOrigin offset.");
+ return;
+ }
+ originOffset = StringToInt(buffer);
+ }
+
+ Address mvAddress = view_as<Address>(LoadFromAddress(view_as<Address>(view_as<int>(addr) + mvOffset), NumberType_Int32));
+ result[0] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + originOffset), NumberType_Int32));
+ result[1] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + originOffset + 4), NumberType_Int32));
+ result[2] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + originOffset + 8), NumberType_Int32));
+}
+
+stock void GameMove_GetEyeAngles(Address addr, float result[3])
+{
+ static int mvOffset;
+ static int viewAngleOffset;
+ if (!mvOffset)
+ {
+ char buffer[8];
+ if (!gH_GameData.GetKeyValue("CGameMovement::mv", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CGameMovement::mv offset.");
+ return;
+ }
+ mvOffset = StringToInt(buffer);
+ if (!gH_GameData.GetKeyValue("CMoveData::m_viewAngleOffset", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CMoveData::m_viewAngleOffset offset.");
+ return;
+ }
+ viewAngleOffset = StringToInt(buffer);
+ }
+
+ Address mvAddress = view_as<Address>(LoadFromAddress(view_as<Address>(view_as<int>(addr) + mvOffset), NumberType_Int32));
+ result[0] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + viewAngleOffset), NumberType_Int32));
+ result[1] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + viewAngleOffset + 4), NumberType_Int32));
+ result[2] = view_as<float>(LoadFromAddress(view_as<Address>(view_as<int>(mvAddress) + viewAngleOffset + 8), NumberType_Int32));
+}
+
+stock int GetEntityFromAddress(Address pEntity) {
+ static int offs_RefEHandle;
+ if (offs_RefEHandle) {
+ return EntRefToEntIndex(LoadFromAddress(pEntity + view_as<Address>(offs_RefEHandle), NumberType_Int32) | (1 << 31));
+ }
+
+ // if we don't have it already, attempt to lookup offset based on SDK information
+ // CWorld is derived from CBaseEntity so it should have both offsets
+ int offs_angRotation = FindDataMapInfo(0, "m_angRotation"),
+ offs_vecViewOffset = FindDataMapInfo(0, "m_vecViewOffset");
+ if (offs_angRotation == -1) {
+ ThrowError("Could not find offset for ((CBaseEntity) CWorld)::m_angRotation");
+ } else if (offs_vecViewOffset == -1) {
+ ThrowError("Could not find offset for ((CBaseEntity) CWorld)::m_vecViewOffset");
+ } else if ((offs_angRotation + 0x0C) != (offs_vecViewOffset - 0x04)) {
+ char game[32];
+ GetGameFolderName(game, sizeof(game));
+ ThrowError("Could not confirm offset of CBaseEntity::m_RefEHandle "
+ ... "(incorrect assumption for game '%s'?)", game);
+ }
+
+ // offset seems right, cache it for the next call
+ offs_RefEHandle = offs_angRotation + 0x0C;
+ return GetEntityFromAddress(pEntity);
+}
+
+stock int GetClientFromGameMovementAddress(Address addr)
+{
+ char buffer[8];
+ if (!gH_GameData.GetKeyValue("CGameMovement::player", buffer, sizeof(buffer)))
+ {
+ ThrowError("Failed to get CGameMovement::player offset.");
+ return -1;
+ }
+ int offset = StringToInt(buffer);
+ Address playerAddr = view_as<Address>(LoadFromAddress(view_as<Address>(view_as<int>(addr) + offset), NumberType_Int32));
+ return GetEntityFromAddress(playerAddr);
+}
+
+stock void HookGameMovementFunction(DynamicDetour handle, char[] fName, DHookCallback preCallback, DHookCallback postCallback)
+{
+ handle = DynamicDetour.FromConf(gH_GameData, fName);
+ if (!handle)
+ {
+ SetFailState("Failed to find %s config", fName);
+ }
+ if (!handle.Enable(Hook_Pre, preCallback))
+ {
+ SetFailState("Failed to enable detour on %s", fName);
+ }
+ if (!handle.Enable(Hook_Post, postCallback))
+ {
+ SetFailState("Failed to enable detour on %s", fName);
+ }
+}