New timer method, tick fractions & StartReplay native (#931)

* Add Convar to restrict noclip in start zone

* remove unused translation file

* Require the enum struct size to be passed to natives

This should hopefully prevent Invalid memory access from plugins compiled on older version of the timer. If an enum struct is changed then plugins compiled on older version of the timer will be forced to be recompiled to match the version they are using.

* Fix save states not tracking preframes

Misc. name changes for natives. And a somehow possible exception fixed when getting a record.

* Add hitbox/center mass cvar (#930)

* Add optional cvar for player center of mass/player hitbox for touching zones

* 2.5.7a Final Changes

fix spelling mistake
add missing optional native
tabify spaces

* Tick offsets, new timing method

Added the tick fraction system from Momentum Mod. Timer is now run within ProcessMovement

* Add StartReplay Native

* Remove debug code

* remove extra gamedata

* Change Hook To Post.

* Increment version

Co-authored-by: KiD Fearless <kidfearless1@gmail.com>
Co-authored-by: Nairda <38843773+Nairdaa@users.noreply.github.com>
This commit is contained in:
Herman Simensen 2020-07-09 02:00:28 +02:00 committed by GitHub
parent 48f051f78c
commit dec640f2b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 355 additions and 52 deletions

View File

@ -1,14 +1,28 @@
"Games"
{
"cstrike"
"#default"
{
"Keys"
{
"IGameMovement" "GameMovement001"
}
"Signatures"
{
"CreateInterface"
{
"library" "server"
"windows" "@CreateInterface"
"linux" "@CreateInterface"
}
}
"Offsets"
{
"CCSPlayer::GetPlayerMaxSpeed"
"ProcessMovement"
{
"windows" "438"
"linux" "439"
"mac" "439"
"windows" "1"
"linux" "2"
}
}
}
@ -25,4 +39,17 @@
}
}
}
"cstrike"
{
"Offsets"
{
"CCSPlayer::GetPlayerMaxSpeed"
{
"windows" "438"
"linux" "439"
"mac" "439"
}
}
}
}

View File

@ -23,7 +23,7 @@
#endif
#define _shavit_included
#define SHAVIT_VERSION "2.5.7a"
#define SHAVIT_VERSION "2.6.0"
#define STYLE_LIMIT 256
#define MAX_ZONES 64
#define MAX_NAME_LENGTH_SQL 32
@ -1500,6 +1500,15 @@ native void Shavit_SetReplayData(int client, ArrayList data);
*/
native ArrayList Shavit_GetReplayData(int client);
/**
* Starts a replay given a style and track.
* @param client Client index.
* @param style Bhop style.
* @param track Timer track.
* @noreturn
*/
native void Shavit_StartReplay(int style, int track, float delay, int client = 1);
/**
* Reloads a specific replay into the replay bot cache.
* Note: Not guaranteed to work with legacy replay bots.
@ -1509,7 +1518,7 @@ native ArrayList Shavit_GetReplayData(int client);
* @param restart Restart the playback of the replay bot if it's playing?
* @param path Path to the replay file. Use `BuildPath(Path_SM, ...)` to generate one. Leave as empty to use default.
* @return Was the replay loaded?
*/
*/
native bool Shavit_ReloadReplay(int style, int track, bool restart, char[] path = "");
/**
@ -1888,6 +1897,7 @@ public void __pl_shavit_SetNTVOptional()
MarkNativeAsOptional("Shavit_SaveSnapshot");
MarkNativeAsOptional("Shavit_SetPracticeMode");
MarkNativeAsOptional("Shavit_SetReplayData");
MarkNativeAsOptional("Shavit_StartReplay");
MarkNativeAsOptional("Shavit_StartTimer");
MarkNativeAsOptional("Shavit_StopChatSound");
MarkNativeAsOptional("Shavit_StopTimer");

View File

@ -24,6 +24,7 @@
#include <geoip>
#include <clientprefs>
#include <convar_class>
#include <dhooks>
#undef REQUIRE_PLUGIN
#define USES_CHAT_COLORS
@ -100,6 +101,10 @@ float gF_PauseOrigin[MAXPLAYERS+1][3];
float gF_PauseAngles[MAXPLAYERS+1][3];
float gF_PauseVelocity[MAXPLAYERS+1][3];
// used for offsets
float gF_PreviousOrigin[MAXPLAYERS + 1][2][3];
float gF_SmallestDist[MAXPLAYERS + 1];
// cookies
Handle gH_StyleCookie = null;
Handle gH_AutoBhopCookie = null;
@ -124,6 +129,8 @@ Convar gCV_VelocityTeleport = null;
Convar gCV_DefaultStyle = null;
Convar gCV_NoChatSound = null;
Convar gCV_SimplerLadders = null;
Convar gCV_UseOffsets = null;
Convar gCV_DebugOffsets = null;
// cached cvars
int gI_DefaultStyle = 0;
@ -225,6 +232,8 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
public void OnPluginStart()
{
LoadDHooks();
// forwards
gH_Forwards_Start = CreateGlobalForward("Shavit_OnStart", ET_Event, Param_Cell, Param_Cell);
gH_Forwards_Stop = CreateGlobalForward("Shavit_OnStop", ET_Event, Param_Cell, Param_Cell);
@ -244,10 +253,10 @@ public void OnPluginStart()
gH_Forwards_OnTimerIncrement = CreateGlobalForward("Shavit_OnTimeIncrement", ET_Event, Param_Cell, Param_Array, Param_CellByRef, Param_Array);
gH_Forwards_OnTimerIncrementPost = CreateGlobalForward("Shavit_OnTimeIncrementPost", ET_Event, Param_Cell, Param_Cell, Param_Array);
gH_Forwards_OnTimescaleChanged = CreateGlobalForward("Shavit_OnTimescaleChanged", ET_Event, Param_Cell, Param_Cell, Param_Cell);
LoadTranslations("shavit-core.phrases");
LoadTranslations("shavit-common.phrases");
// game types
gEV_Type = GetEngineVersion();
gB_Protobuf = (GetUserMessageType() == UM_Protobuf);
@ -336,7 +345,8 @@ public void OnPluginStart()
gCV_DefaultStyle = new Convar("shavit_core_defaultstyle", "0", "Default style ID.\nAdd the '!' prefix to disable style cookies - i.e. \"!3\" to *force* scroll to be the default style.", 0, true, 0.0);
gCV_NoChatSound = new Convar("shavit_core_nochatsound", "0", "Disables click sound for chat messages.", 0, true, 0.0, true, 1.0);
gCV_SimplerLadders = new Convar("shavit_core_simplerladders", "1", "Allows using all keys on limited styles (such as sideways) after touching ladders\nTouching the ground enables the restriction again.", 0, true, 0.0, true, 1.0);
gCV_UseOffsets = new Convar("shavit_core_useoffsets", "1", "Calculates more accurate times by subtracting/adding tick offsets from the time the server uses to register that a player has left or entered a trigger", 0, true, 0.0, true, 1.0);
gCV_DebugOffsets = new Convar("shavit_core_debugoffsets", "0", "Print offset upon leaving or entering a zone?", 0, true, 0.0, true, 1.0);
gCV_DefaultStyle.AddChangeHook(OnConVarChanged);
Convar.AutoExecConfig();
@ -373,6 +383,60 @@ public void OnPluginStart()
}
}
void LoadDHooks()
{
Handle gamedataConf = LoadGameConfigFile("shavit.games");
if(gamedataConf == null)
{
SetFailState("Failed to load shavit gamedata");
}
StartPrepSDKCall(SDKCall_Static);
if(!PrepSDKCall_SetFromConf(gamedataConf, SDKConf_Signature, "CreateInterface"))
{
SetFailState("Failed to get CreateInterface");
}
PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer, VDECODE_FLAG_ALLOWNULL);
PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
Handle CreateInterface = EndPrepSDKCall();
if(CreateInterface == null)
{
SetFailState("Unable to prepare SDKCall for CreateInterface");
}
char interfaceName[64];
// ProcessMovement
if(!GameConfGetKeyValue(gamedataConf, "IGameMovement", interfaceName, sizeof(interfaceName)))
{
SetFailState("Failed to get IGameMovement interface name");
}
Address IGameMovement = SDKCall(CreateInterface, interfaceName, 0);
if(!IGameMovement)
{
SetFailState("Failed to get IGameMovement pointer");
}
int offset = GameConfGetOffset(gamedataConf, "ProcessMovement");
if(offset == -1)
{
SetFailState("Failed to get ProcessMovement offset");
}
Handle processMovement = DHookCreate(offset, HookType_Raw, ReturnType_Void, ThisPointer_Ignore, DHook_ProcessMovementPost);
DHookAddParam(processMovement, HookParamType_CBaseEntity);
DHookAddParam(processMovement, HookParamType_ObjectPtr);
DHookRaw(processMovement, true, IGameMovement);
delete CreateInterface;
delete gamedataConf;
}
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
gB_StyleCookies = (newValue[0] != '!');
@ -1464,6 +1528,19 @@ public int Native_FinishMap(Handle handler, int numParams)
{
int client = GetNativeCell(1);
if(gCV_UseOffsets.BoolValue)
{
float offset = CalculateTickIntervalOffset(client, Zone_End);
gA_Timers[client].fTimer -= offset;
if(gCV_DebugOffsets.BoolValue)
{
char sOffsetMessage[64];
FormatEx(sOffsetMessage, 64, "%T", "DebugOffsets", client, offset);
Shavit_PrintToChat(client, "%s", sOffsetMessage);
}
}
timer_snapshot_t snapshot;
snapshot.bTimerEnabled = gA_Timers[client].bEnabled;
snapshot.bClientPaused = gA_Timers[client].bPaused;
@ -1478,7 +1555,7 @@ public int Native_FinishMap(Handle handler, int numParams)
snapshot.iTimerTrack = gA_Timers[client].iTrack;
snapshot.iMeasuredJumps = gA_Timers[client].iMeasuredJumps;
snapshot.iPerfectJumps = gA_Timers[client].iPerfectJumps;
Action result = Plugin_Continue;
Call_StartForward(gH_Forwards_FinishPre);
Call_PushCell(client);
@ -2032,6 +2109,7 @@ public void OnClientPutInServer(int client)
}
SDKHook(client, SDKHook_PreThinkPost, PreThinkPost);
SDKHook(client, SDKHook_PostThink, PostThink);
int iSteamID = GetSteamAccountID(client);
@ -2712,6 +2790,22 @@ public void Shavit_OnLeaveZone(int client, int type, int track, int id, int enti
{
UpdateAiraccelerate(client, view_as<float>(gA_StyleSettings[gA_Timers[client].iStyle].fAiraccelerate));
}
if(type == Zone_Start && track == gA_Timers[client].iTrack)
{
if(gCV_UseOffsets.BoolValue)
{
float offset = CalculateTickIntervalOffset(client, type);
gA_Timers[client].fTimer += offset;
if(gCV_DebugOffsets.BoolValue)
{
char sOffsetMessage[64];
FormatEx(sOffsetMessage, 64, "%T", "DebugOffsets", client, offset);
Shavit_PrintToChat(client, "%s", sOffsetMessage);
}
}
}
}
public void PreThinkPost(int client)
@ -2746,56 +2840,189 @@ public void PreThinkPost(int client)
}
}
public void OnGameFrame()
public void PostThink(int client)
{
//we need to add height to the players origin since the zones are not on the ground
float height = ((IsSource2013(GetEngineVersion()))? 62.0:72.0) / 2;
gF_PreviousOrigin[client][1] = gF_PreviousOrigin[client][0];
GetEntPropVector(client, Prop_Data, "m_vecAbsOrigin", gF_PreviousOrigin[client][0]);
gF_PreviousOrigin[client][0][2] += height;
}
public MRESReturn DHook_ProcessMovementPost(Handle hParams)
{
int client = DHookGetParam(hParams, 1);
float frametime = GetGameFrameTime();
for(int i = 1; i <= MaxClients; i++)
if(gA_Timers[client].bPaused || !gA_Timers[client].bEnabled)
{
if(gA_Timers[i].bPaused || !gA_Timers[i].bEnabled)
{
continue;
}
return MRES_Ignored;
}
float time;
if(gA_Timers[client].fTimescale != -1.0)
{
time = frametime * gA_Timers[client].fTimescale;
}
else
{
time = frametime * view_as<float>(gA_StyleSettings[gA_Timers[client].iStyle].fTimescale);
}
timer_snapshot_t snapshot;
snapshot.bTimerEnabled = gA_Timers[client].bEnabled;
snapshot.bClientPaused = gA_Timers[client].bPaused;
snapshot.iJumps = gA_Timers[client].iJumps;
snapshot.bsStyle = gA_Timers[client].iStyle;
snapshot.iStrafes = gA_Timers[client].iStrafes;
snapshot.iTotalMeasures = gA_Timers[client].iTotalMeasures;
snapshot.iGoodGains = gA_Timers[client].iGoodGains;
snapshot.fServerTime = GetEngineTime();
snapshot.fCurrentTime = gA_Timers[client].fTimer;
snapshot.iSHSWCombination = gA_Timers[client].iSHSWCombination;
snapshot.iTimerTrack = gA_Timers[client].iTrack;
Call_StartForward(gH_Forwards_OnTimerIncrement);
Call_PushCell(client);
Call_PushArray(snapshot, sizeof(timer_snapshot_t));
Call_PushCellRef(time);
Call_PushArray(gA_StyleSettings[gA_Timers[client].iStyle], sizeof(stylesettings_t));
Call_Finish();
float time;
if(gA_Timers[i].fTimescale != -1.0)
{
time = frametime * gA_Timers[i].fTimescale;
}
gA_Timers[client].fTimer += time;
Call_StartForward(gH_Forwards_OnTimerIncrementPost);
Call_PushCell(client);
Call_PushCell(time);
Call_PushArray(gA_StyleSettings[gA_Timers[client].iStyle], sizeof(stylesettings_t));
Call_Finish();
return MRES_Ignored;
}
// reference: https://github.com/momentum-mod/game/blob/5e2d1995ca7c599907980ee5b5da04d7b5474c61/mp/src/game/server/momentum/mom_timer.cpp#L388
float CalculateTickIntervalOffset(int client, int zonetype)
{
float newPos[3];
float tracepoint[3];
float maxs[3];
float mins[3];
float vel[3];
GetEntPropVector(client, Prop_Send, "m_vecMins", mins);
GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs);
GetEntPropVector(client, Prop_Data, "m_vecVelocity", vel);
for (int i = 0; i < 8; i++)
{
switch (i)
{
case 0:
{
AddVectors(gF_PreviousOrigin[client][0], mins, tracepoint);
}
case 1:
{
newPos[0] = mins[0];
newPos[1] = maxs[1];
newPos[2] = mins[2];
AddVectors(gF_PreviousOrigin[client][0], newPos, tracepoint);
}
case 2:
{
newPos[0] = mins[0];
newPos[1] = mins[1];
newPos[2] = maxs[2];
AddVectors(gF_PreviousOrigin[client][0], newPos, tracepoint);
}
case 3:
{
newPos[0] = mins[0];
newPos[1] = maxs[1];
newPos[2] = maxs[2];
AddVectors(gF_PreviousOrigin[client][0], newPos, tracepoint);
}
case 4:
{
newPos[0] = maxs[0];
newPos[1] = mins[1];
newPos[2] = maxs[2];
AddVectors(gF_PreviousOrigin[client][0], newPos, tracepoint);
}
case 5:
{
newPos[0] = maxs[0];
newPos[1] = mins[1];
newPos[2] = mins[2];
AddVectors(gF_PreviousOrigin[client][0], newPos, tracepoint);
}
case 6:
{
newPos[0] = maxs[0];
newPos[1] = maxs[1];
newPos[2] = mins[2];
AddVectors(gF_PreviousOrigin[client][0], newPos, tracepoint);
}
case 7:
{
AddVectors(gF_PreviousOrigin[client][0], maxs, tracepoint);
}
}
if (zonetype == Zone_Start)
{
gF_SmallestDist[client] = -1.0;
TR_EnumerateEntities(tracepoint, gF_PreviousOrigin[client][1], PARTITION_TRIGGER_EDICTS, RayType_EndPoint, TREnumTrigger, client);
}
else
{
time = frametime * view_as<float>(gA_StyleSettings[gA_Timers[i].iStyle].fTimescale);
gF_SmallestDist[client] = -1.0;
TR_EnumerateEntities(gF_PreviousOrigin[client][0], tracepoint, PARTITION_TRIGGER_EDICTS, RayType_EndPoint, TREnumTrigger, client);
}
timer_snapshot_t snapshot;
snapshot.bTimerEnabled = gA_Timers[i].bEnabled;
snapshot.bClientPaused = gA_Timers[i].bPaused;
snapshot.iJumps = gA_Timers[i].iJumps;
snapshot.bsStyle = gA_Timers[i].iStyle;
snapshot.iStrafes = gA_Timers[i].iStrafes;
snapshot.iTotalMeasures = gA_Timers[i].iTotalMeasures;
snapshot.iGoodGains = gA_Timers[i].iGoodGains;
snapshot.fServerTime = GetEngineTime();
snapshot.fCurrentTime = gA_Timers[i].fTimer;
snapshot.iSHSWCombination = gA_Timers[i].iSHSWCombination;
snapshot.iTimerTrack = gA_Timers[i].iTrack;
Call_StartForward(gH_Forwards_OnTimerIncrement);
Call_PushCell(i);
Call_PushArray(snapshot, sizeof(timer_snapshot_t));
Call_PushCellRef(time);
Call_PushArray(gA_StyleSettings[gA_Timers[i].iStyle], sizeof(stylesettings_t));
Call_Finish();
gA_Timers[i].fTimer += time;
Call_StartForward(gH_Forwards_OnTimerIncrementPost);
Call_PushCell(i);
Call_PushCell(time);
Call_PushArray(gA_StyleSettings[gA_Timers[i].iStyle], sizeof(stylesettings_t));
Call_Finish();
}
float offset = gF_SmallestDist[client] / GetVectorLength(vel);
// This should never happen under normal circumstances, but you can glitch the offset by getting affected by the jump penalty from zones
// We will return 0 if this happens as its an invalid offset.
if(FloatAbs(offset) > GetTickInterval())
{
offset = 0.0;
}
return FloatAbs(offset);
}
bool TREnumTrigger(int entity, int client) {
if (entity <= MaxClients) {
return true;
}
char classname[32];
GetEntityClassname(entity, classname, sizeof(classname));
//the entity is a zone
if(StrContains(classname, "trigger_multiple") > -1)
{
TR_ClipCurrentRayToEntity(PARTITION_TRIGGER_EDICTS, entity);
float start[3];
TR_GetStartPosition(INVALID_HANDLE, start);
float end[3];
TR_GetEndPosition(end);
float distance = GetVectorDistance(start, end);
if(gF_SmallestDist[client] > distance)
{
gF_SmallestDist[client] = distance;
}
return false;
}
return true;
}
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2])

View File

@ -205,6 +205,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
CreateNative("Shavit_GetReplayTime", Native_GetReplayTime);
CreateNative("Shavit_HijackAngles", Native_HijackAngles);
CreateNative("Shavit_IsReplayDataLoaded", Native_IsReplayDataLoaded);
CreateNative("Shavit_StartReplay", Native_StartReplay);
CreateNative("Shavit_ReloadReplay", Native_ReloadReplay);
CreateNative("Shavit_ReloadReplays", Native_ReloadReplays);
CreateNative("Shavit_Replay_DeleteMap", Native_Replay_DeleteMap);
@ -516,6 +517,39 @@ public int Native_IsReplayDataLoaded(Handle handler, int numParams)
return view_as<int>(ReplayEnabled(style) && gA_FrameCache[style][Track_Main].iFrameCount > 0);
}
public int Native_StartReplay(Handle handler, int numParams)
{
int style = GetNativeCell(1);
int track = GetNativeCell(2);
float delay = GetNativeCell(3);
int client = GetNativeCell(4);
if(gA_FrameCache[style][track].iFrameCount == 0)
{
return false;
}
gI_ReplayTick[style] = 0;
gI_TimerTick[style] = 0;
gA_CentralCache.iStyle = style;
gA_CentralCache.iTrack = track;
gA_CentralCache.iPlaybackSerial = GetClientSerial(client);
gF_LastInteraction[client] = GetEngineTime();
gI_ReplayBotClient[style] = gA_CentralCache.iClient;
gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_Start;
TeleportToStart(gA_CentralCache.iClient, style, track);
gB_ForciblyStopped = false;
float time = GetReplayLength(gA_CentralCache.iStyle, track);
UpdateReplayInfo(gA_CentralCache.iClient, style, time, track);
delete gH_ReplayTimers[style];
gH_ReplayTimers[style] = CreateTimer((delay / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE);
return true;
}
public int Native_ReloadReplay(Handle handler, int numParams)
{
int style = GetNativeCell(1);

View File

@ -121,4 +121,9 @@
"#format" "{1:s},{2:s}"
"en" "You {1}can't{2} teleport as an end zone for the map is not defined."
}
"DebugOffsets"
{
"#format" "{1:f}"
"en" "Your tick offset was {1}."
}
}