diff --git a/addons/sourcemod/gamedata/movementapi.games.txt b/addons/sourcemod/gamedata/movementapi.games.txt index f2dbffb..a9da35d 100644 --- a/addons/sourcemod/gamedata/movementapi.games.txt +++ b/addons/sourcemod/gamedata/movementapi.games.txt @@ -2,6 +2,24 @@ { "csgo" { + "Addresses" + { + "sm_pSingleton" + { + "windows" + { + "signature" "CGameMovement::TryPlayerMove" + "read" "718" + } + "linux" + { + "signature" "CGameMovement::TryPlayerMove" + "read" "483" + } + + "read" "0" + } + } "Keys" { "CGameMovement::player" "4" @@ -90,6 +108,24 @@ "this" "address" "return" "void" } + "CGameMovement::TryPlayerMove" + { + "signature" "CGameMovement::TryPlayerMove" + "callconv" "thiscall" + "this" "address" + "return" "int" + "arguments" + { + "pFirstDest" + { + "type" "vectorptr" + } + "pFirstTrace" + { + "type" "objectptr" + } + } + } } "Offsets" { @@ -150,6 +186,12 @@ "windows" "\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\xF9\xC7\x45\x2A\x00\x00\x00\x00\x8B\x47\x2A\xC7\x80" "linux" "\x55\x89\xE5\x57\x56\x53\x81\xEC\xA8\x00\x00\x00\x8B\x7D\x08" } + "CGameMovement::TryPlayerMove" + { + "library" "server" + "windows" "\x55\x8B\xEC\x83\xE4\xF8\x81\xEC\x38\x01\x00\x00\xF3\x0F\x10\x35\x2A\x2A\x2A\x2A" + "linux" "\x55\x66\x0F\xEF\xDB\x89\xE5\x57\x56\x53\x81\xEC\x2A\x2A\x2A\x2A\x8B\x7D\x08" + } } } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/movementapi.inc b/addons/sourcemod/scripting/include/movementapi.inc index 45ee263..0cb1525 100644 --- a/addons/sourcemod/scripting/include/movementapi.inc +++ b/addons/sourcemod/scripting/include/movementapi.inc @@ -107,6 +107,16 @@ forward void Movement_OnStopDucking(int client); */ forward void Movement_OnPlayerJump(int client, bool jumpbug); +/** + * Called when a player edgebugs + * Setting velocity when this is called may not be effective. + * + * @param client Client index. + * @param origin Player origin before edgebug. + * @param velocity Player velocity before edgebug. + */ +forward void Movement_OnPlayerEdgebug(int client, float origin[3], float velocity[3]); + /** * Called before PlayerMove movement function is called. * Modifying origin or velocity parameters will change player's origin and velocity accordingly. @@ -283,6 +293,28 @@ forward Action Movement_OnCategorizePositionPre(int client, float origin[3], flo */ forward Action Movement_OnCategorizePositionPost(int client, float origin[3], float velocity[3]); +/** + * Called before TryPlayerMove movement function is called. + * Modifying origin or velocity parameters will change player's origin and velocity accordingly. + * + * @param client Client index. + * @param origin Player origin. + * @param velocity Player velocity. + * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise. + */ +forward Action Movement_OnTryPlayerMovePre(int client, float origin[3], float velocity[3]); + +/** + * Called after TryPlayerMove movement function is called. + * Modifying origin or velocity parameters will change player's origin and velocity accordingly. + * + * @param client Client index. + * @param origin Player origin. + * @param velocity Player velocity. + * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise. + */ +forward Action Movement_OnTryPlayerMovePost(int client, float origin[3], float velocity[3]); + // =====[ NATIVES ]===== /** @@ -484,13 +516,42 @@ native void Movement_SetTakeoffVelocity(int client, float velocity[3]); */ native void Movement_SetLandingOrigin(int client, float origin[3]); +/** + * Get the player's number of collisions during movement processing. + * This function should ideally be called after inside Movement_OnTryPlayerMovePost. + * + * @param client Client index. + * @param origin Desired velocity. + */ +native int Movement_GetCollisionCount(int client); + +/** + * Set the player's landing velocity. + * This function should ideally be called after inside Movement_OnTryPlayerMovePost. + * + * @param client Client index. + * @param num Collision number, must not exceed Movement_GetCollisionCount's value. + * @param result Resultant vector. + */ +native void Movement_GetCollisionStartOrigin(int client, int num, float result[3]); + /** * Set the player's landing velocity. + * This function should ideally be called after inside Movement_OnTryPlayerMovePost. * * @param client Client index. * @param origin Desired velocity. */ -native void Movement_SetLandingVelocity(int client, float velocity[3]); +native void Movement_GetCollisionEndOrigin(int client, int num, float result[3]); + +/** + * Set the player's landing velocity. + * This function should ideally be called after inside Movement_OnTryPlayerMovePost. + * + * @param client Client index. + * @param origin Desired velocity. + */ +native void Movement_GetCollisionNormal(int client, int num, float result[3]); // =====[ METHODMAP ]===== @@ -608,6 +669,26 @@ methodmap MovementAPIPlayer < MovementPlayer { } } + property int CollisionCount { + public get() { + return Movement_GetCollisionCount(this.ID); + } + } + public void GetCollisionStartOrigin(int num, float buffer[3]) + { + Movement_GetCollisionStartOrigin(this.ID, num, buffer); + } + + public void GetCollisionEndOrigin(int num, float buffer[3]) + { + Movement_GetCollisionEndOrigin(this.ID, num, buffer); + } + + public void GetCollisionNormal(int num, float buffer[3]) + { + Movement_GetCollisionNormal(this.ID, num, buffer); + } + public void GetProcessingVelocity(float buffer[3]) { Movement_GetProcessingVelocity(this.ID, buffer); @@ -659,5 +740,9 @@ public void __pl_movementapi_SetNTVOptional() MarkNativeAsOptional("Movement_SetTakeoffVelocity"); MarkNativeAsOptional("Movement_SetLandingOrigin"); MarkNativeAsOptional("Movement_SetLandingVelocity"); + MarkNativeAsOptional("Movement_GetCollisionCount"); + MarkNativeAsOptional("Movement_GetCollisionStartOrigin"); + MarkNativeAsOptional("Movement_GetCollisionEndOrigin"); + MarkNativeAsOptional("Movement_GetCollisionNormal"); } #endif diff --git a/addons/sourcemod/scripting/movementapi/forwards.sp b/addons/sourcemod/scripting/movementapi/forwards.sp index 9cf0b72..bc96b00 100644 --- a/addons/sourcemod/scripting/movementapi/forwards.sp +++ b/addons/sourcemod/scripting/movementapi/forwards.sp @@ -4,6 +4,7 @@ static Handle H_OnStartTouchGround; static Handle H_OnStopTouchGround; static Handle H_OnChangeMovetype; static Handle H_OnPlayerJump; +static Handle H_OnPlayerEdgebug; static Handle H_OnPlayerMovePre; static Handle H_OnPlayerMovePost; @@ -21,6 +22,8 @@ static Handle H_OnWalkMovePre; static Handle H_OnWalkMovePost; static Handle H_OnCategorizePositionPre; static Handle H_OnCategorizePositionPost; +static Handle H_OnTryPlayerMovePre; +static Handle H_OnTryPlayerMovePost; void CreateGlobalForwards() { @@ -30,6 +33,7 @@ void CreateGlobalForwards() 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_OnPlayerEdgebug = CreateGlobalForward("Movement_OnPlayerEdgebug", ET_Ignore, Param_Cell, Param_Array, Param_Array); 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); @@ -54,6 +58,9 @@ void CreateGlobalForwards() 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); + + H_OnTryPlayerMovePre = CreateGlobalForward("Movement_OnTryPlayerMovePre", ET_Event, Param_Cell, Param_Array, Param_Array); + H_OnTryPlayerMovePost = CreateGlobalForward("Movement_OnTryPlayerMovePost", ET_Event, Param_Cell, Param_Array, Param_Array); } void Call_OnStartDucking(int client) @@ -85,8 +92,6 @@ void Call_OnStopTouchGround(int client, bool jumped, bool ladderJump, bool jumpb 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; } @@ -107,6 +112,15 @@ void Call_OnPlayerJump(int client, bool jumpbug) Call_Finish(); } +void Call_OnPlayerEdgebug(int client, float origin[3], float velocity[3]) +{ + Call_StartForward(H_OnPlayerEdgebug); + Call_PushCell(client); + Call_PushArray(origin, 3); + Call_PushArray(velocity, 3); + Call_Finish(); +} + Action Call_OnPlayerMovePre(int client, float origin[3], float velocity[3], Action &result) { Call_StartForward(H_OnPlayerMovePre); @@ -265,4 +279,24 @@ Action Call_OnCategorizePositionPost(int client, float origin[3], float velocity Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK); Call_Finish(result); return result; +} + +Action Call_OnTryPlayerMovePre(int client, float origin[3], float velocity[3], Action &result) +{ + Call_StartForward(H_OnTryPlayerMovePre); + Call_PushCell(client); + Call_PushArrayEx(origin, 3, SM_PARAM_COPYBACK); + Call_PushArrayEx(velocity, 3, SM_PARAM_COPYBACK); + Call_Finish(result); + return result; +} + +Action Call_OnTryPlayerMovePost(int client, float origin[3], float velocity[3], Action &result) +{ + Call_StartForward(H_OnTryPlayerMovePost); + 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/addons/sourcemod/scripting/movementapi/hooks.sp b/addons/sourcemod/scripting/movementapi/hooks.sp index 7b97b62..3b670ea 100644 --- a/addons/sourcemod/scripting/movementapi/hooks.sp +++ b/addons/sourcemod/scripting/movementapi/hooks.sp @@ -6,6 +6,9 @@ static DynamicDetour H_OnJump; static DynamicDetour H_OnAirAccelerate; static DynamicDetour H_OnWalkMove; static DynamicDetour H_OnCategorizePosition; +static DynamicDetour H_OnTryPlayerMove; +static Address moveHelperAddr; +static bool tryPlayerMoveThisTick; float gF_Origin[MAXPLAYERS + 1][3]; float gF_Velocity[MAXPLAYERS + 1][3]; @@ -31,6 +34,12 @@ float gF_PostAAVelocity[MAXPLAYERS + 1][3]; bool gB_OldWalkMoved[MAXPLAYERS + 1]; +int gI_CollisionCount[MAXPLAYERS + 1]; + +float gF_TraceStartOrigin[MAXPLAYERS + 1][MAX_BUMPS][3]; +float gF_TraceEndOrigin[MAXPLAYERS + 1][MAX_BUMPS][3]; +float gF_TraceNormal[MAXPLAYERS + 1][MAX_BUMPS][3]; + void HookGameMovementFunctions() { HookGameMovementFunction(H_OnDuck, "CCSGameMovement::Duck", DHooks_OnDuck_Pre, DHooks_OnDuck_Post); @@ -41,6 +50,13 @@ void HookGameMovementFunctions() 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); + HookGameMovementFunction(H_OnTryPlayerMove, "CGameMovement::TryPlayerMove", DHooks_OnTryPlayerMove_Pre, DHooks_OnTryPlayerMove_Post); + + moveHelperAddr = GameConfGetAddress(gH_GameData, "sm_pSingleton"); + if (!moveHelperAddr) + { + SetFailState("Failed to find IMoveHelper::sm_pSingleton."); + } } Action UpdateMoveData(Address pThis, int client, Function func) @@ -429,7 +445,7 @@ public MRESReturn DHooks_OnPlayerMove_Post(Address pThis) return MRES_Ignored; } Action result = UpdateMoveData(pThis, client, Call_OnPlayerMovePost); - + tryPlayerMoveThisTick = false; if (result != Plugin_Continue) { return MRES_Handled; @@ -513,8 +529,107 @@ public MRESReturn DHooks_OnCategorizePosition_Post(Address pThis) } } +public MRESReturn DHooks_OnTryPlayerMove_Pre(Address pThis, DHookReturn hReturn, DHookParam hParams) +{ + int client = GetClientFromGameMovementAddress(pThis); + if (!IsPlayerAlive(client) || IsFakeClient(client)) + { + return MRES_Ignored; + } + Action result = UpdateMoveData(pThis, client, Call_OnTryPlayerMovePre); + + for (int i = 0; i < MAX_BUMPS; i++) + { + gF_TraceStartOrigin[client][i] = NULL_VECTOR; + gF_TraceEndOrigin[client][i] = NULL_VECTOR; + gF_TraceNormal[client][i] = NULL_VECTOR; + } + + if (result != Plugin_Continue) + { + return MRES_Handled; + } + else + { + return MRES_Ignored; + } +} + +public MRESReturn DHooks_OnTryPlayerMove_Post(Address pThis, DHookReturn hReturn, DHookParam hParams) +{ + int client = GetClientFromGameMovementAddress(pThis); + if (!IsPlayerAlive(client) || IsFakeClient(client)) + { + return MRES_Ignored; + } + + tryPlayerMoveThisTick = true; + gI_CollisionCount[client] = LoadFromAddress(moveHelperAddr + view_as
(8) + view_as
(12), NumberType_Int32); + + Address m_TouchList_m_pElements = LoadFromAddress(moveHelperAddr + view_as
(8) + view_as
(16), NumberType_Int32); + + bool hitStandableSurface = false; + static ConVar sv_standable_normal; + if (sv_standable_normal == INVALID_HANDLE) + { + sv_standable_normal = FindConVar("sv_standable_normal"); + } + for (int i = 0; i < gI_CollisionCount[client]; i++) + { + Trace trace = Trace(m_TouchList_m_pElements + view_as
(i*96) + view_as
(12)); + trace.startpos.ToArray(gF_TraceStartOrigin[client][i]); + trace.endpos.ToArray(gF_TraceEndOrigin[client][i]); + trace.plane.normal.ToArray(gF_TraceNormal[client][i]); + if (trace.plane.normal.z >= sv_standable_normal.FloatValue) + { + hitStandableSurface = true; + } + } + + // Edgebug detection + + if (hitStandableSurface) + { + float currentOrigin[3], groundEndPoint[3]; + + GameMove_GetOrigin(pThis, currentOrigin); + groundEndPoint = currentOrigin; + groundEndPoint[2] -= 2.0; + float mins[3] = {-16.0, -16.0, 0.0}; + float maxs[3] = {16.0, 16.0, 0.0}; + TR_TraceHullFilter(currentOrigin, groundEndPoint, mins, maxs, MASK_PLAYERSOLID, TraceEntityFilterPlayers, client); + + float groundPos[3]; + TR_GetEndPosition(groundPos); + + // Note: Origin and velocity are not updated yet. + if (!TR_DidHit()) + { + Call_OnPlayerEdgebug(client, gF_Origin[client], gF_Velocity[client]); + } + } + + Action result = UpdateMoveData(pThis, client, Call_OnTryPlayerMovePost); + if (result != Plugin_Continue) + { + return MRES_Handled; + } + else + { + return MRES_Ignored; + } +} + static void NobugLandingOrigin(int client, float landingOrigin[3]) { + // Jump is bugged, try to use the trace result of TryPlayerMove if possible. + if (tryPlayerMoveThisTick && gI_CollisionCount[client] > 0) + { + landingOrigin = gF_TraceEndOrigin[client][0]; + return; + } + // Fallback when no collision happened during TryPlayerMove, or that function was not called. + // NOTE: Get ground position and distance to ground. float groundEndPoint[3]; groundEndPoint = gF_Origin[client]; diff --git a/addons/sourcemod/scripting/movementapi/natives.sp b/addons/sourcemod/scripting/movementapi/natives.sp index f8cd7a5..0e5d45a 100644 --- a/addons/sourcemod/scripting/movementapi/natives.sp +++ b/addons/sourcemod/scripting/movementapi/natives.sp @@ -25,6 +25,11 @@ void CreateNatives() CreateNative("Movement_SetTakeoffVelocity", Native_SetTakeoffVelocity); CreateNative("Movement_SetLandingOrigin", Native_SetLandingOrigin); CreateNative("Movement_SetLandingVelocity", Native_SetLandingVelocity); + + CreateNative("Movement_GetCollisionCount", Native_GetCollisionCount); + CreateNative("Movement_GetCollisionStartOrigin", Native_GetCollisionStartOrigin); + CreateNative("Movement_GetCollisionEndOrigin", Native_GetCollisionEndOrigin); + CreateNative("Movement_GetCollisionNormal", Native_GetCollisionNormal); } public int Native_GetJumped(Handle plugin, int numParams) @@ -180,4 +185,27 @@ public int Native_SetLandingVelocity(Handle plugin, int numParams) } return 0; +} + +public int Native_GetCollisionCount(Handle plugin, int numParams) +{ + return gI_CollisionCount[GetNativeCell(1)]; +} + +public int Native_GetCollisionStartOrigin(Handle plugin, int numParams) +{ + SetNativeArray(3, gF_TraceStartOrigin[GetNativeCell(1)][GetNativeCell(2)], sizeof(gF_TraceStartOrigin[][])); + return 0; +} + +public int Native_GetCollisionEndOrigin(Handle plugin, int numParams) +{ + SetNativeArray(3, gF_TraceEndOrigin[GetNativeCell(1)][GetNativeCell(2)], sizeof(gF_TraceEndOrigin[][])); + return 0; +} + +public int Native_GetCollisionNormal(Handle plugin, int numParams) +{ + SetNativeArray(3, gF_TraceNormal[GetNativeCell(1)][GetNativeCell(2)], sizeof(gF_TraceNormal[][])); + return 0; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/movementapi/stocks.sp b/addons/sourcemod/scripting/movementapi/stocks.sp index e080efc..fde78fd 100644 --- a/addons/sourcemod/scripting/movementapi/stocks.sp +++ b/addons/sourcemod/scripting/movementapi/stocks.sp @@ -1,3 +1,140 @@ +#define MAX_BUMPS 4 + +methodmap Vector +{ + public Vector(Address address) + { + return view_as(address); + } + + property float x + { + public get() + { + return view_as( LoadFromAddress(view_as
(this) + view_as
(0), NumberType_Int32) ); + } + public set(float x) + { + StoreToAddress(view_as
(this) + view_as
(0), view_as(x), NumberType_Int32, false); + } + } + + property float y + { + public get() + { + return view_as( LoadFromAddress(view_as
(this) + view_as
(4), NumberType_Int32) ); + } + public set(float y) + { + StoreToAddress(view_as
(this) + view_as
(4), view_as(y), NumberType_Int32, false); + } + } + + property float z + { + public get() + { + return view_as( LoadFromAddress(view_as
(this) + view_as
(8), NumberType_Int32) ); + } + public set(float z) + { + StoreToAddress(view_as
(this) + view_as
(8), view_as(z), NumberType_Int32, false); + } + } + + public void ToArray(float vec[3]) + { + vec[0] = this.x; + vec[1] = this.y; + vec[2] = this.z; + } +}; + +methodmap Plane +{ + public Plane(Address address) + { + return view_as(address); + } + + property Vector normal + { + public get() + { + return view_as( view_as
(this) + view_as
(0) ); + } + } + + property float dist + { + public get() + { + return view_as( LoadFromAddress(view_as
(this) + view_as
(12), NumberType_Int32) ); + } + public set(float dist) + { + StoreToAddress(view_as
(this) + view_as
(12), view_as(dist), NumberType_Int32, false); + } + } + + property int type + { + public get() + { + return view_as( LoadFromAddress(view_as
(this) + view_as
(16), NumberType_Int8) ); + } + public set(int type) + { + StoreToAddress(view_as
(this) + view_as
(16), view_as(type), NumberType_Int8, false); + } + } + + property int signbits + { + public get() + { + return view_as( LoadFromAddress(view_as
(this) + view_as
(17), NumberType_Int8) ); + } + public set(int signbits) + { + StoreToAddress(view_as
(this) + view_as
(17), view_as(signbits), NumberType_Int8, false); + } + } +}; + +methodmap Trace +{ + public Trace(Address address) + { + return view_as(address); + } + + property Vector startpos + { + public get() + { + return view_as( view_as
(this) + view_as
(0) ); + } + } + + property Vector endpos + { + public get() + { + return view_as( view_as
(this) + view_as
(12) ); + } + } + + property Plane plane + { + public get() + { + return view_as( view_as
(this) + view_as
(24) ); + } + } +}; + stock void GameMove_SetVelocity(Address addr, float velocity[3]) { if (velocity[0] != velocity[0] || velocity[1] != velocity[1] || velocity[2] != velocity[2])