summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/gokz-replays
diff options
context:
space:
mode:
Diffstat (limited to 'sourcemod/scripting/gokz-replays')
-rw-r--r--sourcemod/scripting/gokz-replays/api.sp78
-rw-r--r--sourcemod/scripting/gokz-replays/commands.sp55
-rw-r--r--sourcemod/scripting/gokz-replays/controls.sp224
-rw-r--r--sourcemod/scripting/gokz-replays/nav.sp97
-rw-r--r--sourcemod/scripting/gokz-replays/playback.sp1501
-rw-r--r--sourcemod/scripting/gokz-replays/recording.sp990
-rw-r--r--sourcemod/scripting/gokz-replays/replay_cache.sp176
-rw-r--r--sourcemod/scripting/gokz-replays/replay_menu.sp139
8 files changed, 0 insertions, 3260 deletions
diff --git a/sourcemod/scripting/gokz-replays/api.sp b/sourcemod/scripting/gokz-replays/api.sp
deleted file mode 100644
index 3c115e1..0000000
--- a/sourcemod/scripting/gokz-replays/api.sp
+++ /dev/null
@@ -1,78 +0,0 @@
-static GlobalForward H_OnReplaySaved;
-static GlobalForward H_OnReplayDiscarded;
-static GlobalForward H_OnTimerEnd_Post;
-
-// =====[ NATIVES ]=====
-
-void CreateNatives()
-{
- CreateNative("GOKZ_RP_GetPlaybackInfo", Native_RP_GetPlaybackInfo);
- CreateNative("GOKZ_RP_LoadJumpReplay", Native_RP_LoadJumpReplay);
- CreateNative("GOKZ_RP_UpdateReplayControlMenu", Native_RP_UpdateReplayControlMenu);
-}
-
-public int Native_RP_GetPlaybackInfo(Handle plugin, int numParams)
-{
- HUDInfo info;
- GetPlaybackState(GetNativeCell(1), info);
- SetNativeArray(2, info, sizeof(HUDInfo));
- return 1;
-}
-
-public int Native_RP_LoadJumpReplay(Handle plugin, int numParams)
-{
- int len;
- GetNativeStringLength(2, len);
- char[] path = new char[len + 1];
- GetNativeString(2, path, len + 1);
- int botClient = LoadReplayBot(GetNativeCell(1), path);
- return botClient;
-}
-
-public int Native_RP_UpdateReplayControlMenu(Handle plugin, int numParams)
-{
- return view_as<int>(UpdateReplayControlMenu(GetNativeCell(1)));
-}
-
-// =====[ FORWARDS ]=====
-
-void CreateGlobalForwards()
-{
- H_OnReplaySaved = new GlobalForward("GOKZ_RP_OnReplaySaved", ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell, Param_Cell, Param_Float, Param_String, Param_Cell);
- H_OnReplayDiscarded = new GlobalForward("GOKZ_RP_OnReplayDiscarded", ET_Ignore, Param_Cell);
- H_OnTimerEnd_Post = new GlobalForward("GOKZ_RP_OnTimerEnd_Post", ET_Ignore, Param_Cell, Param_String, Param_Cell, Param_Float, Param_Cell);
-}
-
-Action Call_OnReplaySaved(int client, int replayType, const char[] map, int course, int timeType, float time, const char[] filePath, bool tempReplay)
-{
- Action result;
- Call_StartForward(H_OnReplaySaved);
- Call_PushCell(client);
- Call_PushCell(replayType);
- Call_PushString(map);
- Call_PushCell(course);
- Call_PushCell(timeType);
- Call_PushFloat(time);
- Call_PushString(filePath);
- Call_PushCell(tempReplay);
- Call_Finish(result);
- return result;
-}
-
-void Call_OnReplayDiscarded(int client)
-{
- Call_StartForward(H_OnReplayDiscarded);
- Call_PushCell(client);
- Call_Finish();
-}
-
-void Call_OnTimerEnd_Post(int client, const char[] filePath, int course, float time, int teleportsUsed)
-{
- Call_StartForward(H_OnTimerEnd_Post);
- Call_PushCell(client);
- Call_PushString(filePath);
- Call_PushCell(course);
- Call_PushFloat(time);
- Call_PushCell(teleportsUsed);
- Call_Finish();
-}
diff --git a/sourcemod/scripting/gokz-replays/commands.sp b/sourcemod/scripting/gokz-replays/commands.sp
deleted file mode 100644
index 43251f6..0000000
--- a/sourcemod/scripting/gokz-replays/commands.sp
+++ /dev/null
@@ -1,55 +0,0 @@
-void RegisterCommands()
-{
- RegConsoleCmd("sm_replay", CommandReplay, "[KZ] Open the replay loading menu.");
- RegConsoleCmd("sm_replaycontrols", CommandReplayControls, "[KZ] Toggle the replay control menu.");
- RegConsoleCmd("sm_rpcontrols", CommandReplayControls, "[KZ] Toggle the replay control menu.");
- RegConsoleCmd("sm_replaygoto", CommandReplayGoto, "[KZ] Skip to a specific time in the replay (hh:mm:ss).");
- RegConsoleCmd("sm_rpgoto", CommandReplayGoto, "[KZ] Skip to a specific time in the replay (hh:mm:ss).");
-}
-
-public Action CommandReplay(int client, int args)
-{
- DisplayReplayModeMenu(client);
- return Plugin_Handled;
-}
-
-public Action CommandReplayControls(int client, int args)
-{
- ToggleReplayControls(client);
- return Plugin_Handled;
-}
-
-public Action CommandReplayGoto(int client, int args)
-{
- int seconds;
- char timeString[32], split[3][32];
-
- GetCmdArgString(timeString, sizeof(timeString));
- int res = ExplodeString(timeString, ":", split, 3, 32, false);
- switch (res)
- {
- case 1:
- {
- seconds = StringToInt(split[0]);
- }
-
- case 2:
- {
- seconds = StringToInt(split[0]) * 60 + StringToInt(split[1]);
- }
-
- case 3:
- {
- seconds = StringToInt(split[0]) * 3600 + StringToInt(split[1]) * 60 + StringToInt(split[2]);
- }
-
- default:
- {
- GOKZ_PrintToChat(client, true, "%t", "Replay Controls - Invalid Time");
- return Plugin_Handled;
- }
- }
-
- TrySkipToTime(client, seconds);
- return Plugin_Handled;
-}
diff --git a/sourcemod/scripting/gokz-replays/controls.sp b/sourcemod/scripting/gokz-replays/controls.sp
deleted file mode 100644
index cda7f07..0000000
--- a/sourcemod/scripting/gokz-replays/controls.sp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- Lets player control the replay bot.
-*/
-
-#define ITEM_INFO_PAUSE "pause"
-#define ITEM_INFO_SKIP "skip"
-#define ITEM_INFO_REWIND "rewind"
-#define ITEM_INFO_FREECAM "freecam"
-
-static int controllingPlayer[RP_MAX_BOTS];
-static int botTeleports[RP_MAX_BOTS];
-static bool showReplayControls[MAXPLAYERS + 1];
-
-
-
-// =====[ PUBLIC ]=====
-
-void OnPlayerRunCmdPost_ReplayControls(int client, int cmdnum)
-{
- // Let the HUD plugin takes care of this if possible.
- if (cmdnum % 6 == 3 && !gB_GOKZHUD)
- {
- UpdateReplayControlMenu(client);
- }
-}
-
-bool UpdateReplayControlMenu(int client)
-{
- if (!IsValidClient(client) || IsFakeClient(client))
- {
- return false;
- }
-
- int botClient = GetObserverTarget(client);
- int bot = GetBotFromClient(botClient);
- if (bot == -1)
- {
- return false;
- }
-
- if (!IsReplayBotControlled(bot, botClient) && !InBreather(bot))
- {
- CancelReplayControlsForBot(bot);
- controllingPlayer[bot] = client;
- }
- else if (controllingPlayer[bot] != client)
- {
- return false;
- }
-
- if (showReplayControls[client] &&
- GOKZ_HUD_GetOption(client, HUDOption_ShowControls) == ReplayControls_Enabled)
- {
- // We have to update this often if bot uses teleports.
- if (GetClientMenu(client) == MenuSource_None ||
- GOKZ_HUD_GetMenuShowing(client) && GetClientAvgLoss(client, NetFlow_Both) > EPSILON ||
- GOKZ_HUD_GetMenuShowing(client) && GOKZ_HUD_GetOption(client, HUDOption_TimerText) == TimerText_TPMenu ||
- GOKZ_HUD_GetMenuShowing(client) && PlaybackGetTeleports(bot) > 0)
- {
- botTeleports[bot] = PlaybackGetTeleports(bot);
- ShowReplayControlMenu(client, bot);
- }
- return true;
- }
- return false;
-}
-
-void ShowReplayControlMenu(int client, int bot)
-{
- char text[256];
-
- Menu menu = new Menu(MenuHandler_ReplayControls);
- menu.OptionFlags = MENUFLAG_NO_SOUND;
- menu.Pagination = MENU_NO_PAGINATION;
- menu.ExitButton = true;
- if (gB_GOKZHUD)
- {
- if (GOKZ_HUD_GetOption(client, HUDOption_ShowSpectators) != ShowSpecs_Disabled &&
- GOKZ_HUD_GetOption(client, HUDOption_SpecListPosition) == SpecListPosition_TPMenu)
- {
- HUDInfo info;
- GetPlaybackState(client, info);
- GOKZ_HUD_GetMenuSpectatorText(client, info, text, sizeof(text));
- }
- if (GOKZ_HUD_GetOption(client, HUDOption_TimerText) == TimerText_TPMenu)
- {
- Format(text, sizeof(text), "%s\n%T - %s", text, "Replay Controls - Title", client,
- GOKZ_FormatTime(GetPlaybackTime(bot), GOKZ_HUD_GetOption(client, HUDOption_TimerStyle) == TimerStyle_Precise));
- }
- else
- {
- Format(text, sizeof(text), "%s%T", text, "Replay Controls - Title", client);
- }
- }
- else
- {
- Format(text, sizeof(text), "%s%T", text, "Replay Controls - Title", client);
- }
-
-
- if (botTeleports[bot] > 0)
- {
- Format(text, sizeof(text), "%s\n%T", text, "Replay Controls - Teleports", client, botTeleports[bot]);
- }
-
- menu.SetTitle(text);
-
- if (PlaybackPaused(bot))
- {
- FormatEx(text, sizeof(text), "%T", "Replay Controls - Resume", client);
- menu.AddItem(ITEM_INFO_PAUSE, text);
- }
- else
- {
- FormatEx(text, sizeof(text), "%T", "Replay Controls - Pause", client);
- menu.AddItem(ITEM_INFO_PAUSE, text);
- }
-
- FormatEx(text, sizeof(text), "%T", "Replay Controls - Skip", client);
- menu.AddItem(ITEM_INFO_SKIP, text);
-
- FormatEx(text, sizeof(text), "%T\n ", "Replay Controls - Rewind", client);
- menu.AddItem(ITEM_INFO_REWIND, text);
-
- FormatEx(text, sizeof(text), "%T", "Replay Controls - Freecam", client);
- menu.AddItem(ITEM_INFO_FREECAM, text);
-
- menu.Display(client, MENU_TIME_FOREVER);
-
- if (gB_GOKZHUD)
- {
- GOKZ_HUD_SetMenuShowing(client, true);
- }
-}
-
-void ToggleReplayControls(int client)
-{
- if (showReplayControls[client])
- {
- CancelReplayControls(client);
- }
- else
- {
- showReplayControls[client] = true;
- }
-}
-
-void EnableReplayControls(int client)
-{
- showReplayControls[client] = true;
-}
-
-bool IsReplayBotControlled(int bot, int botClient)
-{
- return IsValidClient(controllingPlayer[bot]) &&
- (GetObserverTarget(controllingPlayer[bot]) == botClient ||
- GetEntProp(controllingPlayer[bot], Prop_Send, "m_iObserverMode") == 6);
-}
-
-int MenuHandler_ReplayControls(Menu menu, MenuAction action, int param1, int param2)
-{
- switch (action)
- {
- case MenuAction_Select:
- {
- if (!IsValidClient(param1))
- {
- return;
- }
-
- int bot = GetBotFromClient(GetObserverTarget(param1));
- if (bot == -1 || controllingPlayer[bot] != param1)
- {
- return;
- }
-
- char info[16];
- menu.GetItem(param2, info, sizeof(info));
- if (StrEqual(info, ITEM_INFO_PAUSE, false))
- {
- PlaybackTogglePause(bot);
- }
- else if (StrEqual(info, ITEM_INFO_SKIP, false))
- {
- PlaybackSkipForward(bot);
- }
- else if (StrEqual(info, ITEM_INFO_REWIND, false))
- {
- PlaybackSkipBack(bot);
- }
- else if (StrEqual(info, ITEM_INFO_FREECAM, false))
- {
- SetEntProp(param1, Prop_Send, "m_iObserverMode", 6);
- }
- GOKZ_HUD_SetMenuShowing(param1, false);
- }
- case MenuAction_Cancel:
- {
- GOKZ_HUD_SetMenuShowing(param1, false);
- if (param2 == MenuCancel_Exit)
- {
- CancelReplayControls(param1);
- }
- }
- case MenuAction_End:
- {
- delete menu;
- }
- }
-}
-
-void CancelReplayControls(int client)
-{
- if (IsValidClient(client) && showReplayControls[client])
- {
- CancelClientMenu(client);
- showReplayControls[client] = false;
- }
-}
-
-void CancelReplayControlsForBot(int bot)
-{
- CancelReplayControls(controllingPlayer[bot]);
-}
diff --git a/sourcemod/scripting/gokz-replays/nav.sp b/sourcemod/scripting/gokz-replays/nav.sp
deleted file mode 100644
index 4e73c2f..0000000
--- a/sourcemod/scripting/gokz-replays/nav.sp
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- Ensures that there is .nav file for the map so the server
- does not to auto-generating one.
-*/
-
-
-
-// =====[ EVENTS ]=====
-
-void OnMapStart_Nav()
-{
- if (!CheckForNavFile())
- {
- GenerateNavFile();
- }
-}
-
-
-
-// =====[ PRIVATE ]=====
-
-static bool CheckForNavFile()
-{
- // Make sure there's a nav file
- // Credit to shavit's simple bhop timer - https://github.com/shavitush/bhoptimer
-
- char mapPath[PLATFORM_MAX_PATH];
- GetCurrentMap(mapPath, sizeof(mapPath));
-
- char navFilePath[PLATFORM_MAX_PATH];
- FormatEx(navFilePath, PLATFORM_MAX_PATH, "maps/%s.nav", mapPath);
-
- return FileExists(navFilePath);
-}
-
-static void GenerateNavFile()
-{
- // Generate (copy a) .nav file for the map
- // Credit to shavit's simple bhop timer - https://github.com/shavitush/bhoptimer
-
- char mapPath[PLATFORM_MAX_PATH];
- GetCurrentMap(mapPath, sizeof(mapPath));
-
- char[] navFilePath = new char[PLATFORM_MAX_PATH];
- FormatEx(navFilePath, PLATFORM_MAX_PATH, "maps/%s.nav", mapPath);
-
- if (!FileExists(RP_NAV_FILE))
- {
- SetFailState("Failed to load file: \"%s\". Check that it exists.", RP_NAV_FILE);
- }
- File_Copy(RP_NAV_FILE, navFilePath);
- ForceChangeLevel(gC_CurrentMap, "[gokz-replays] Generate .nav file.");
-}
-
-/*
- * Copies file source to destination
- * Based on code of javalia:
- * http://forums.alliedmods.net/showthread.php?t=159895
- *
- * Credit to shavit's simple bhop timer - https://github.com/shavitush/bhoptimer
- *
- * @param source Input file
- * @param destination Output file
- */
-static bool File_Copy(const char[] source, const char[] destination)
-{
- File file_source = OpenFile(source, "rb");
-
- if (file_source == null)
- {
- return false;
- }
-
- File file_destination = OpenFile(destination, "wb");
-
- if (file_destination == null)
- {
- delete file_source;
-
- return false;
- }
-
- int[] buffer = new int[32];
- int cache = 0;
-
- while (!IsEndOfFile(file_source))
- {
- cache = ReadFile(file_source, buffer, 32, 1);
-
- file_destination.Write(buffer, cache, 1);
- }
-
- delete file_source;
- delete file_destination;
-
- return true;
-} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-replays/playback.sp b/sourcemod/scripting/gokz-replays/playback.sp
deleted file mode 100644
index b3f6865..0000000
--- a/sourcemod/scripting/gokz-replays/playback.sp
+++ /dev/null
@@ -1,1501 +0,0 @@
-/*
- Bot replay playback logic and processes.
-
- The recorded files are read and their information and tick data
- stored into variables. A bot is then used to playback the recorded
- data by setting it's origin, velocity, etc. in OnPlayerRunCmd.
-*/
-
-
-
-static int preAndPostRunTickCount;
-
-static int playbackTick[RP_MAX_BOTS];
-static ArrayList playbackTickData[RP_MAX_BOTS];
-static bool inBreather[RP_MAX_BOTS];
-static float breatherStartTime[RP_MAX_BOTS];
-
-// Original bot caller, needed for OnClientPutInServer callback
-static int botCaller[RP_MAX_BOTS];
-// Original bot name after creation by bot_add, needed for bot removal
-static char botName[RP_MAX_BOTS][MAX_NAME_LENGTH];
-static bool botInGame[RP_MAX_BOTS];
-static int botClient[RP_MAX_BOTS];
-static bool botDataLoaded[RP_MAX_BOTS];
-static int botReplayType[RP_MAX_BOTS];
-static int botReplayVersion[RP_MAX_BOTS];
-static int botSteamAccountID[RP_MAX_BOTS];
-static int botCourse[RP_MAX_BOTS];
-static int botMode[RP_MAX_BOTS];
-static int botStyle[RP_MAX_BOTS];
-static float botTime[RP_MAX_BOTS];
-static int botTimeTicks[RP_MAX_BOTS];
-static char botAlias[RP_MAX_BOTS][MAX_NAME_LENGTH];
-static bool botPaused[RP_MAX_BOTS];
-static bool botPlaybackPaused[RP_MAX_BOTS];
-static int botKnife[RP_MAX_BOTS];
-static int botWeapon[RP_MAX_BOTS];
-static int botJumpType[RP_MAX_BOTS];
-static float botJumpDistance[RP_MAX_BOTS];
-static int botJumpBlockDistance[RP_MAX_BOTS];
-
-static int timeOnGround[RP_MAX_BOTS];
-static int timeInAir[RP_MAX_BOTS];
-static int botTeleportsUsed[RP_MAX_BOTS];
-static int botCurrentTeleport[RP_MAX_BOTS];
-static int botButtons[RP_MAX_BOTS];
-static MoveType botMoveType[RP_MAX_BOTS];
-static float botTakeoffSpeed[RP_MAX_BOTS];
-static float botSpeed[RP_MAX_BOTS];
-static float botLastOrigin[RP_MAX_BOTS][3];
-static bool hitBhop[RP_MAX_BOTS];
-static bool hitPerf[RP_MAX_BOTS];
-static bool botJumped[RP_MAX_BOTS];
-static bool botIsTakeoff[RP_MAX_BOTS];
-static bool botJustTeleported[RP_MAX_BOTS];
-static float botLandingSpeed[RP_MAX_BOTS];
-
-
-
-// =====[ PUBLIC ]=====
-
-// Returns the client index of the replay bot, or -1 otherwise
-int LoadReplayBot(int client, char[] path)
-{
- // Safeguard Check
- if (GOKZ_GetCoreOption(client, Option_Safeguard) > Safeguard_Disabled && GOKZ_GetTimerRunning(client) && GOKZ_GetValidTimer(client))
- {
- if (!GOKZ_GetPaused(client) && !GOKZ_GetCanPause(client))
- {
- GOKZ_PrintToChat(client, true, "%t", "Safeguard - Blocked");
- GOKZ_PlayErrorSound(client);
- return -1;
- }
- }
- int bot;
- if (GetBotsInUse() < RP_MAX_BOTS)
- {
- bot = GetUnusedBot();
- }
- else
- {
- GOKZ_PrintToChat(client, true, "%t", "No Bots Available");
- GOKZ_PlayErrorSound(client);
- return -1;
- }
-
- if (bot == -1)
- {
- LogError("Unused bot could not be found even though only %d out of %d are known to be in use.",
- GetBotsInUse(), RP_MAX_BOTS);
- GOKZ_PlayErrorSound(client);
- return -1;
- }
-
- if (!LoadPlayback(client, bot, path))
- {
- GOKZ_PlayErrorSound(client);
- return -1;
- }
-
- ServerCommand("bot_add");
- botCaller[bot] = client;
- return botClient[bot];
-}
-
-// Passes the current state of the replay into the HUDInfo struct
-void GetPlaybackState(int client, HUDInfo info)
-{
- int bot, i;
- for(i = 0; i < RP_MAX_BOTS; i++)
- {
- bot = botClient[i] == client ? i : bot;
- }
- if (i == RP_MAX_BOTS + 1) return;
-
- if (playbackTickData[bot] == INVALID_HANDLE)
- {
- return;
- }
-
- info.TimerRunning = botReplayType[bot] == ReplayType_Jump ? false : true;
- if (botReplayVersion[bot] == 1)
- {
- info.Time = playbackTick[bot] * GetTickInterval();
- }
- else if (botReplayVersion[bot] == 2)
- {
- if (playbackTick[bot] < preAndPostRunTickCount)
- {
- info.Time = 0.0;
- }
- else if (playbackTick[bot] >= playbackTickData[bot].Length - preAndPostRunTickCount)
- {
- info.Time = botTime[bot];
- }
- else if (playbackTick[bot] >= preAndPostRunTickCount)
- {
- info.Time = (playbackTick[bot] - preAndPostRunTickCount) * GetTickInterval();
- }
- }
- info.TimerRunning = true;
- info.TimeType = botTeleportsUsed[bot] > 0 ? TimeType_Nub : TimeType_Pro;
- info.Speed = botSpeed[bot];
- info.Paused = false;
- info.OnLadder = (botMoveType[bot] == MOVETYPE_LADDER);
- info.Noclipping = false;
- info.OnGround = Movement_GetOnGround(client);
- info.Ducking = botButtons[bot] & IN_DUCK > 0;
- info.ID = botClient[bot];
- info.Jumped = botJumped[bot];
- info.HitBhop = hitBhop[bot];
- info.HitPerf = hitPerf[bot];
- info.Buttons = botButtons[bot];
- info.TakeoffSpeed = botTakeoffSpeed[bot];
- info.IsTakeoff = botIsTakeoff[bot] && !Movement_GetOnGround(client);
- info.CurrentTeleport = botCurrentTeleport[bot];
-}
-
-int GetBotFromClient(int client)
-{
- for (int bot = 0; bot < RP_MAX_BOTS; bot++)
- {
- if (botClient[bot] == client)
- {
- return bot;
- }
- }
- return -1;
-}
-
-bool InBreather(int bot)
-{
- return inBreather[bot];
-}
-
-bool PlaybackPaused(int bot)
-{
- return botPlaybackPaused[bot];
-}
-
-void PlaybackTogglePause(int bot)
-{
- if(botPlaybackPaused[bot])
- {
- botPlaybackPaused[bot] = false;
- }
- else
- {
- botPlaybackPaused[bot] = true;
- }
-}
-
-void PlaybackSkipForward(int bot)
-{
- if (playbackTick[bot] + RoundToZero(RP_SKIP_TIME / GetTickInterval()) < playbackTickData[bot].Length)
- {
- PlaybackSkipToTick(bot, playbackTick[bot] + RoundToZero(RP_SKIP_TIME / GetTickInterval()));
- }
-}
-
-void PlaybackSkipBack(int bot)
-{
- if (playbackTick[bot] < RoundToZero(RP_SKIP_TIME / GetTickInterval()))
- {
- PlaybackSkipToTick(bot, 0);
- }
- else
- {
- PlaybackSkipToTick(bot, playbackTick[bot] - RoundToZero(RP_SKIP_TIME / GetTickInterval()));
- }
-}
-
-int PlaybackGetTeleports(int bot)
-{
- return botCurrentTeleport[bot];
-}
-
-void TrySkipToTime(int client, int seconds)
-{
- if (!IsValidClient(client))
- {
- return;
- }
-
- int tick = seconds * 128 + preAndPostRunTickCount;
- int bot = GetBotFromClient(GetObserverTarget(client));
-
- if (tick >= 0 && tick < playbackTickData[bot].Length)
- {
- PlaybackSkipToTick(bot, tick);
- }
- else
- {
- GOKZ_PrintToChat(client, true, "%t", "Replay Controls - Invalid Time");
- }
-}
-
-float GetPlaybackTime(int bot)
-{
- if (playbackTick[bot] < preAndPostRunTickCount)
- {
- return 0.0;
- }
- if (playbackTick[bot] >= playbackTickData[bot].Length - preAndPostRunTickCount)
- {
- return botTime[bot];
- }
- if (playbackTick[bot] >= preAndPostRunTickCount)
- {
- return (playbackTick[bot] - preAndPostRunTickCount) * GetTickInterval();
- }
-
- return 0.0;
-}
-
-
-
-// =====[ EVENTS ]=====
-
-void OnClientPutInServer_Playback(int client)
-{
- if (!IsFakeClient(client) || IsClientSourceTV(client))
- {
- return;
- }
-
- // Check if an unassigned bot has joined, and assign it
- for (int bot; bot < RP_MAX_BOTS; bot++)
- {
- // Also check if the bot was created by us.
- if (!botInGame[bot] && botCaller[bot] != 0)
- {
- botInGame[bot] = true;
- botClient[bot] = client;
- GetClientName(client, botName[bot], sizeof(botName[]));
- // The bot won't receive its weapons properly if we don't wait a frame
- RequestFrame(SetBotStuff, bot);
- if (IsValidClient(botCaller[bot]))
- {
- MakePlayerSpectate(botCaller[bot], botClient[bot]);
- botCaller[bot] = 0;
- }
- break;
- }
- }
-}
-
-void OnClientDisconnect_Playback(int client)
-{
- for (int bot; bot < RP_MAX_BOTS; bot++)
- {
- if (botClient[bot] != client)
- {
- continue;
- }
-
- botInGame[bot] = false;
- if (playbackTickData[bot] != null)
- {
- playbackTickData[bot].Clear(); // Clear it all out
- botDataLoaded[bot] = false;
- }
- }
-}
-
-void OnPlayerRunCmd_Playback(int client, int &buttons, float vel[3], float angles[3])
-{
- if (!IsFakeClient(client))
- {
- return;
- }
-
- for (int bot; bot < RP_MAX_BOTS; bot++)
- {
- // Check if not the bot we're looking for
- if (!botInGame[bot] || botClient[bot] != client || !botDataLoaded[bot])
- {
- continue;
- }
-
- switch (botReplayVersion[bot])
- {
- case 1: PlaybackVersion1(client, bot, buttons);
- case 2: PlaybackVersion2(client, bot, buttons, vel, angles);
- }
- break;
- }
-}
-
-void OnPlayerRunCmdPost_Playback(int client)
-{
- for (int bot; bot < RP_MAX_BOTS; bot++)
- {
- // Check if not the bot we're looking for
- if (!botInGame[bot] || botClient[bot] != client || !botDataLoaded[bot])
- {
- continue;
- }
- if (botReplayVersion[bot] == 2)
- {
- PlaybackVersion2Post(client, bot);
- }
- break;
- }
-}
-
-void GOKZ_OnOptionsLoaded_Playback(int client)
-{
- for (int bot = 0; bot < RP_MAX_BOTS; bot++)
- {
- if (botClient[bot] == client)
- {
- // Reset its movement options as it might be wrongfully changed
- GOKZ_SetCoreOption(client, Option_Mode, botMode[bot]);
- GOKZ_SetCoreOption(client, Option_Style, botStyle[bot]);
- }
- }
-}
-// =====[ PRIVATE ]=====
-
-// Returns false if there was a problem loading the playback e.g. doesn't exist
-static bool LoadPlayback(int client, int bot, char[] path)
-{
- if (!FileExists(path))
- {
- GOKZ_PrintToChat(client, true, "%t", "No Replay Found");
- return false;
- }
-
- File file = OpenFile(path, "rb");
-
- // Check magic number in header
- int magicNumber;
- file.ReadInt32(magicNumber);
- if (magicNumber != RP_MAGIC_NUMBER)
- {
- LogError("Failed to load invalid replay file: \"%s\".", path);
- delete file;
- return false;
- }
-
- // Check replay format version
- int formatVersion;
- file.ReadInt8(formatVersion);
- switch(formatVersion)
- {
- case 1:
- {
- botReplayVersion[bot] = 1;
- if (!LoadFormatVersion1Replay(file, bot))
- {
- return false;
- }
- }
- case 2:
- {
- botReplayVersion[bot] = 2;
- if (!LoadFormatVersion2Replay(file, client, bot))
- {
- return false;
- }
- }
-
- default:
- {
- LogError("Failed to load replay file with unsupported format version: \"%s\".", path);
- delete file;
- return false;
- }
- }
-
- return true;
-}
-
-static bool LoadFormatVersion1Replay(File file, int bot)
-{
- // Old replays only support runs, not jumps
- botReplayType[bot] = ReplayType_Run;
-
- int length;
-
- // GOKZ version
- file.ReadInt8(length);
- char[] gokzVersion = new char[length + 1];
- file.ReadString(gokzVersion, length, length);
- gokzVersion[length] = '\0';
-
- // Map name
- file.ReadInt8(length);
- char[] mapName = new char[length + 1];
- file.ReadString(mapName, length, length);
- mapName[length] = '\0';
-
- // Some integers...
- file.ReadInt32(botCourse[bot]);
- file.ReadInt32(botMode[bot]);
- file.ReadInt32(botStyle[bot]);
-
- // Old replays don't store the weapon information
- botKnife[bot] = CS_WeaponIDToItemDefIndex(CSWeapon_KNIFE);
- botWeapon[bot] = (botMode[bot] == Mode_Vanilla) ? -1 : CS_WeaponIDToItemDefIndex(CSWeapon_USP_SILENCER);
-
- // Time
- int timeAsInt;
- file.ReadInt32(timeAsInt);
- botTime[bot] = view_as<float>(timeAsInt);
-
- // Some integers...
- file.ReadInt32(botTeleportsUsed[bot]);
- file.ReadInt32(botSteamAccountID[bot]);
-
- // SteamID2
- file.ReadInt8(length);
- char[] steamID2 = new char[length + 1];
- file.ReadString(steamID2, length, length);
- steamID2[length] = '\0';
-
- // IP
- file.ReadInt8(length);
- char[] IP = new char[length + 1];
- file.ReadString(IP, length, length);
- IP[length] = '\0';
-
- // Alias
- file.ReadInt8(length);
- file.ReadString(botAlias[bot], sizeof(botAlias[]), length);
- botAlias[bot][length] = '\0';
-
- // Read tick data
- file.ReadInt32(length);
-
- // Setup playback tick data array list
- if (playbackTickData[bot] == null)
- {
- playbackTickData[bot] = new ArrayList(IntMax(RP_V1_TICK_DATA_BLOCKSIZE, sizeof(ReplayTickData)), length);
- }
- else
- { // Make sure it's all clear and the correct size
- playbackTickData[bot].Clear();
- playbackTickData[bot].Resize(length);
- }
-
- // The replay has no replay data, this shouldn't happen normally,
- // but this would cause issues in other code, so we don't even try to load this.
- if (length == 0)
- {
- delete file;
- return false;
- }
-
- any tickData[RP_V1_TICK_DATA_BLOCKSIZE];
- for (int i = 0; i < length; i++)
- {
- file.Read(tickData, RP_V1_TICK_DATA_BLOCKSIZE, 4);
- playbackTickData[bot].Set(i, view_as<float>(tickData[0]), 0); // origin[0]
- playbackTickData[bot].Set(i, view_as<float>(tickData[1]), 1); // origin[1]
- playbackTickData[bot].Set(i, view_as<float>(tickData[2]), 2); // origin[2]
- playbackTickData[bot].Set(i, view_as<float>(tickData[3]), 3); // angles[0]
- playbackTickData[bot].Set(i, view_as<float>(tickData[4]), 4); // angles[1]
- playbackTickData[bot].Set(i, view_as<int>(tickData[5]), 5); // buttons
- playbackTickData[bot].Set(i, view_as<int>(tickData[6]), 6); // flags
- }
-
- playbackTick[bot] = 0;
- botDataLoaded[bot] = true;
-
- delete file;
- return true;
-}
-
-static bool LoadFormatVersion2Replay(File file, int client, int bot)
-{
- int length;
-
- // Replay type
- int replayType;
- file.ReadInt8(replayType);
-
- // GOKZ version
- file.ReadInt8(length);
- char[] gokzVersion = new char[length + 1];
- file.ReadString(gokzVersion, length, length);
- gokzVersion[length] = '\0';
-
- // Map name
- file.ReadInt8(length);
- char[] mapName = new char[length + 1];
- file.ReadString(mapName, length, length);
- mapName[length] = '\0';
- if (!StrEqual(mapName, gC_CurrentMap))
- {
- GOKZ_PrintToChat(client, true, "%t", "Replay Menu - Wrong Map", mapName);
- delete file;
- return false;
- }
-
- // Map filesize
- int mapFileSize;
- file.ReadInt32(mapFileSize);
-
- // Server IP
- int serverIP;
- file.ReadInt32(serverIP);
-
- // Timestamp
- int timestamp;
- file.ReadInt32(timestamp);
-
- // Player Alias
- file.ReadInt8(length);
- file.ReadString(botAlias[bot], sizeof(botAlias[]), length);
- botAlias[bot][length] = '\0';
-
- // Player Steam ID
- int steamID;
- file.ReadInt32(steamID);
-
- // Mode
- file.ReadInt8(botMode[bot]);
-
- // Style
- file.ReadInt8(botStyle[bot]);
-
- // Player Sensitivity
- int intPlayerSensitivity;
- file.ReadInt32(intPlayerSensitivity);
- float playerSensitivity = view_as<float>(intPlayerSensitivity);
-
- // Player MYAW
- int intPlayerMYaw;
- file.ReadInt32(intPlayerMYaw);
- float playerMYaw = view_as<float>(intPlayerMYaw);
-
- // Tickrate
- int tickrateAsInt;
- file.ReadInt32(tickrateAsInt);
- float tickrate = view_as<float>(tickrateAsInt);
- if (tickrate != RoundToZero(1 / GetTickInterval()))
- {
- GOKZ_PrintToChat(client, true, "%t", "Replay Menu - Wrong Tickrate", tickrate, (RoundToZero(1 / GetTickInterval())));
- delete file;
- return false;
- }
-
- // Tick Count
- int tickCount;
- file.ReadInt32(tickCount);
-
- // The replay has no replay data, this shouldn't happen normally,
- // but this would cause issues in other code, so we don't even try to load this.
- if (tickCount == 0)
- {
- delete file;
- return false;
- }
-
- // Equipped Weapon
- file.ReadInt32(botWeapon[bot]);
-
- // Equipped Knife
- file.ReadInt32(botKnife[bot]);
-
- // Big spit to console
- PrintToConsole(client, "Replay Type: %d\nGOKZ Version: %s\nMap Name: %s\nMap Filesize: %d\nServer IP: %d\nTimestamp: %d\nPlayer Alias: %s\nPlayer Steam ID: %d\nMode: %d\nStyle: %d\nPlayer Sensitivity: %f\nPlayer m_yaw: %f\nTickrate: %f\nTick Count: %d\nWeapon: %d\nKnife: %d", replayType, gokzVersion, mapName, mapFileSize, serverIP, timestamp, botAlias[bot], steamID, botMode[bot], botStyle[bot], playerSensitivity, playerMYaw, tickrate, tickCount, botWeapon[bot], botKnife[bot]);
-
- switch(replayType)
- {
- case ReplayType_Run:
- {
- // Time
- int timeAsInt;
- file.ReadInt32(timeAsInt);
- botTime[bot] = view_as<float>(timeAsInt);
- botTimeTicks[bot] = RoundToNearest(botTime[bot] * tickrate);
-
- // Course
- file.ReadInt8(botCourse[bot]);
-
- // Teleports Used
- file.ReadInt32(botTeleportsUsed[bot]);
-
- // Type
- botReplayType[bot] = ReplayType_Run;
-
- // Finish spit to console
- PrintToConsole(client, "Time: %f\nCourse: %d\nTeleports Used: %d", botTime[bot], botCourse[bot], botTeleportsUsed[bot]);
- }
- case ReplayType_Cheater:
- {
- // Reason
- int reason;
- file.ReadInt8(reason);
-
- // Type
- botReplayType[bot] = ReplayType_Cheater;
-
- // Finish spit to console
- PrintToConsole(client, "AC Reason: %s", gC_ACReasons[reason]);
- }
- case ReplayType_Jump:
- {
- // Jump Type
- file.ReadInt8(botJumpType[bot]);
-
- // Distance
- file.ReadInt32(view_as<int>(botJumpDistance[bot]));
-
- // Block Distance
- file.ReadInt32(botJumpBlockDistance[bot]);
-
- // Strafe Count
- int strafeCount;
- file.ReadInt8(strafeCount);
-
- // Sync
- float sync;
- file.ReadInt32(view_as<int>(sync));
-
- // Pre
- float pre;
- file.ReadInt32(view_as<int>(pre));
-
- // Max
- float max;
- file.ReadInt32(view_as<int>(max));
-
- // Airtime
- int airtime;
- file.ReadInt32(airtime);
-
- // Type
- botReplayType[bot] = ReplayType_Jump;
-
- // Finish spit to console
- PrintToConsole(client, "Jump Type: %s\nJump Distance: %f\nBlock Distance: %d\nStrafe Count: %d\nSync: %f\n Pre: %f\nMax: %f\nAirtime: %d",
- gC_JumpTypes[botJumpType[bot]], botJumpDistance[bot], botJumpBlockDistance[bot], strafeCount, sync, pre, max, airtime);
- }
- }
-
- // Tick Data
- // Setup playback tick data array list
- if (playbackTickData[bot] == null)
- {
- playbackTickData[bot] = new ArrayList(IntMax(RP_V1_TICK_DATA_BLOCKSIZE, sizeof(ReplayTickData)));
- }
- else
- {
- playbackTickData[bot].Clear();
- }
-
- // Read tick data
- preAndPostRunTickCount = RoundToZero(RP_PLAYBACK_BREATHER_TIME / GetTickInterval());
- any tickDataArray[RP_V2_TICK_DATA_BLOCKSIZE];
- for (int i = 0; i < tickCount; i++)
- {
- file.ReadInt32(tickDataArray[RPDELTA_DELTAFLAGS]);
-
- for (int index = 1; index < sizeof(tickDataArray); index++)
- {
- int currentFlag = (1 << index);
- if (tickDataArray[RPDELTA_DELTAFLAGS] & currentFlag)
- {
- file.ReadInt32(tickDataArray[index]);
- }
- }
-
- ReplayTickData tickData;
- TickDataFromArray(tickDataArray, tickData);
- // HACK: Jump replays don't record proper length sometimes. I don't know why.
- // This leads to oversized replays full of 0s at the end.
- // So, we do this horrible check to dodge that issue.
- if (tickData.origin[0] == 0 && tickData.origin[1] == 0 && tickData.origin[2] == 0 && tickData.angles[0] == 0 && tickData.angles[1] == 0)
- {
- break;
- }
- playbackTickData[bot].PushArray(tickData);
- }
-
- playbackTick[bot] = 0;
- botDataLoaded[bot] = true;
-
- delete file;
-
- return true;
-}
-
-static void PlaybackVersion1(int client, int bot, int &buttons)
-{
- int size = playbackTickData[bot].Length;
- float repOrigin[3], repAngles[3];
- int repButtons, repFlags;
-
- // If first or last frame of the playback
- if (playbackTick[bot] == 0 || playbackTick[bot] == (size - 1))
- {
- // Move the bot and pause them at that tick
- repOrigin[0] = playbackTickData[bot].Get(playbackTick[bot], 0);
- repOrigin[1] = playbackTickData[bot].Get(playbackTick[bot], 1);
- repOrigin[2] = playbackTickData[bot].Get(playbackTick[bot], 2);
- repAngles[0] = playbackTickData[bot].Get(playbackTick[bot], 3);
- repAngles[1] = playbackTickData[bot].Get(playbackTick[bot], 4);
- TeleportEntity(client, repOrigin, repAngles, view_as<float>( { 0.0, 0.0, 0.0 } ));
-
- if (!inBreather[bot])
- {
- // Start the breather period
- inBreather[bot] = true;
- breatherStartTime[bot] = GetEngineTime();
- if (playbackTick[bot] == (size - 1))
- {
- GOKZ_EmitSoundToClientSpectators(client, gC_ModeEndSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer End");
- }
- }
- else if (GetEngineTime() > breatherStartTime[bot] + RP_PLAYBACK_BREATHER_TIME)
- {
- // End the breather period
- inBreather[bot] = false;
- botPlaybackPaused[bot] = false;
- if (playbackTick[bot] == 0)
- {
- GOKZ_EmitSoundToClientSpectators(client, gC_ModeStartSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer Start");
- }
- // Start the bot if first tick. Clear bot if last tick.
- playbackTick[bot]++;
- if (playbackTick[bot] == size)
- {
- playbackTickData[bot].Clear(); // Clear it all out
- botDataLoaded[bot] = false;
- CancelReplayControlsForBot(bot);
- ServerCommand("bot_kick %s", botName[bot]);
- }
- }
- }
- else
- {
- // Check whether somebody is actually spectating the bot
- int spec;
- for (spec = 1; spec < MAXPLAYERS + 1; spec++)
- {
- if (IsValidClient(spec) && GetObserverTarget(spec) == botClient[bot])
- {
- break;
- }
- }
- if (spec == MAXPLAYERS + 1 && !IsReplayBotControlled(bot, botClient[bot]))
- {
- playbackTickData[bot].Clear();
- botDataLoaded[bot] = false;
- CancelReplayControlsForBot(bot);
- ServerCommand("bot_kick %s", botName[bot]);
- return;
- }
-
- // Load in the next tick
- repOrigin[0] = playbackTickData[bot].Get(playbackTick[bot], 0);
- repOrigin[1] = playbackTickData[bot].Get(playbackTick[bot], 1);
- repOrigin[2] = playbackTickData[bot].Get(playbackTick[bot], 2);
- repAngles[0] = playbackTickData[bot].Get(playbackTick[bot], 3);
- repAngles[1] = playbackTickData[bot].Get(playbackTick[bot], 4);
- repButtons = playbackTickData[bot].Get(playbackTick[bot], 5);
- repFlags = playbackTickData[bot].Get(playbackTick[bot], 6);
-
- // Check if the replay is paused
- if (botPlaybackPaused[bot])
- {
- TeleportEntity(client, repOrigin, repAngles, view_as<float>( { 0.0, 0.0, 0.0 } ));
- return;
- }
-
- // Set velocity to travel from current origin to recorded origin
- float currentOrigin[3], velocity[3];
- Movement_GetOrigin(client, currentOrigin);
- MakeVectorFromPoints(currentOrigin, repOrigin, velocity);
- ScaleVector(velocity, 128.0); // Hard-coded 128 tickrate
- TeleportEntity(client, NULL_VECTOR, repAngles, velocity);
-
- // We need the velocity directly from the replay to calculate the speeds
- // for the HUD.
- MakeVectorFromPoints(botLastOrigin[bot], repOrigin, velocity);
- ScaleVector(velocity, 128.0); // Hard-coded 128 tickrate
- CopyVector(repOrigin, botLastOrigin[bot]);
-
- botSpeed[bot] = GetVectorHorizontalLength(velocity);
- buttons = repButtons;
- botButtons[bot] = repButtons;
-
- // Should the bot be ducking?!
- if (repButtons & IN_DUCK || repFlags & FL_DUCKING)
- {
- buttons |= IN_DUCK;
- }
-
- // If the replay file says the bot's on the ground, then fine! Unless you're going too fast...
- // Note that we don't mind if replay file says bot isn't on ground but the bot is.
- if (repFlags & FL_ONGROUND && Movement_GetSpeed(client) < SPEED_NORMAL * 2)
- {
- if (timeInAir[bot] > 0)
- {
- botLandingSpeed[bot] = botSpeed[bot];
- timeInAir[bot] = 0;
- botIsTakeoff[bot] = false;
- botJumped[bot] = false;
- hitBhop[bot] = false;
- hitPerf[bot] = false;
- if (!Movement_GetOnGround(client))
- {
- timeOnGround[bot] = 0;
- }
- }
-
- SetEntityFlags(client, GetEntityFlags(client) | FL_ONGROUND);
- Movement_SetMovetype(client, MOVETYPE_WALK);
-
- timeOnGround[bot]++;
- botTakeoffSpeed[bot] = botSpeed[bot];
- }
- else
- {
- if (timeInAir[bot] == 0)
- {
- botIsTakeoff[bot] = true;
- botJumped[bot] = botButtons[bot] & IN_JUMP > 0;
- hitBhop[bot] = (timeOnGround[bot] <= RP_MAX_BHOP_GROUND_TICKS) && botJumped[bot];
-
- if (botMode[bot] == Mode_SimpleKZ)
- {
- hitPerf[bot] = timeOnGround[bot] < 3 && botJumped[bot];
- }
- else
- {
- hitPerf[bot] = timeOnGround[bot] < 2 && botJumped[bot];
- }
-
- if (hitPerf[bot])
- {
- if (botMode[bot] == Mode_SimpleKZ)
- {
- botTakeoffSpeed[bot] = FloatMin(botLandingSpeed[bot], (0.2 * botLandingSpeed[bot] + 200));
- }
- else if (botMode[bot] == Mode_KZTimer)
- {
- botTakeoffSpeed[bot] = FloatMin(botLandingSpeed[bot], 380.0);
- }
- else
- {
- botTakeoffSpeed[bot] = FloatMin(botLandingSpeed[bot], 286.0);
- }
- }
- }
- else
- {
- botJumped[bot] = false;
- botIsTakeoff[bot] = false;
- }
-
- timeInAir[bot]++;
- Movement_SetMovetype(client, MOVETYPE_NOCLIP);
- }
-
- playbackTick[bot]++;
- }
-}
-void PlaybackVersion2(int client, int bot, int &buttons, float vel[3], float angles[3])
-{
- int size = playbackTickData[bot].Length;
- ReplayTickData prevTickData;
- ReplayTickData currentTickData;
-
- // If first or last frame of the playback
- if (playbackTick[bot] == 0 || playbackTick[bot] == (size - 1))
- {
- // Move the bot and pause them at that tick
- playbackTickData[bot].GetArray(playbackTick[bot], currentTickData);
- playbackTickData[bot].GetArray(IntMax(playbackTick[bot] - 1, 0), prevTickData);
- TeleportEntity(client, currentTickData.origin, currentTickData.angles, view_as<float>( { 0.0, 0.0, 0.0 } ));
-
- if (!inBreather[bot])
- {
- // Start the breather period
- inBreather[bot] = true;
- breatherStartTime[bot] = GetEngineTime();
- }
- else if (GetEngineTime() > breatherStartTime[bot] + RP_PLAYBACK_BREATHER_TIME)
- {
- // End the breather period
- inBreather[bot] = false;
- botPlaybackPaused[bot] = false;
-
- // Start the bot if first tick. Clear bot if last tick.
- playbackTick[bot]++;
- if (playbackTick[bot] == size)
- {
- playbackTickData[bot].Clear(); // Clear it all out
- botDataLoaded[bot] = false;
- CancelReplayControlsForBot(bot);
- ServerCommand("bot_kick %s", botName[bot]);
- }
- }
- }
- else
- {
- // Check whether somebody is actually spectating the bot
- int spec;
- for (spec = 1; spec < MAXPLAYERS + 1; spec++)
- {
- if (IsValidClient(spec) && GetObserverTarget(spec) == botClient[bot])
- {
- break;
- }
- }
- if (spec == MAXPLAYERS + 1 && !IsReplayBotControlled(bot, botClient[bot]))
- {
- playbackTickData[bot].Clear();
- botDataLoaded[bot] = false;
- CancelReplayControlsForBot(bot);
- ServerCommand("bot_kick %s", botName[bot]);
- return;
- }
-
- // Load in the next tick
- playbackTickData[bot].GetArray(playbackTick[bot], currentTickData);
- playbackTickData[bot].GetArray(IntMax(playbackTick[bot] - 1, 0), prevTickData);
-
- // Check if the replay is paused
- if (botPlaybackPaused[bot])
- {
- TeleportEntity(client, currentTickData.origin, currentTickData.angles, view_as<float>( { 0.0, 0.0, 0.0 } ));
- return;
- }
-
- // Play timer start/end sound, if necessary. Reset teleports
- if (playbackTick[bot] == preAndPostRunTickCount && botReplayType[bot] == ReplayType_Run)
- {
- GOKZ_EmitSoundToClientSpectators(client, gC_ModeStartSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer Start");
- botCurrentTeleport[bot] = 0;
- }
- if (playbackTick[bot] == botTimeTicks[bot] + preAndPostRunTickCount && botReplayType[bot] == ReplayType_Run)
- {
- GOKZ_EmitSoundToClientSpectators(client, gC_ModeEndSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer End");
- }
- // We use the previous position/velocity data to recreate sounds accurately.
- // This might not be necessary as we already did do this in OnPlayerRunCmdPost of last tick,
- // but we do it again just in case the values don't match up somehow (eg. collision with moving objects?)
- TeleportEntity(client, NULL_VECTOR, prevTickData.angles, prevTickData.velocity);
- // TeleportEntity does not set the absolute origin and velocity so we need to do it
- // to prevent inaccurate eye position interpolation.
- SetEntPropVector(client, Prop_Data, "m_vecVelocity", prevTickData.velocity);
- SetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", prevTickData.velocity);
-
- SetEntPropVector(client, Prop_Data, "m_vecAbsOrigin", prevTickData.origin);
- SetEntPropVector(client, Prop_Data, "m_vecOrigin", prevTickData.origin);
-
-
- // Set buttons and potential inputs.
- int newButtons;
- if (currentTickData.flags & RP_IN_ATTACK)
- {
- newButtons |= IN_ATTACK;
- }
- if (currentTickData.flags & RP_IN_ATTACK2)
- {
- newButtons |= IN_ATTACK2;
- }
- if (currentTickData.flags & RP_IN_JUMP)
- {
- newButtons |= IN_JUMP;
- }
- if (currentTickData.flags & RP_IN_DUCK || currentTickData.flags & RP_FL_DUCKING)
- {
- newButtons |= IN_DUCK;
- }
- // Few assumptions here because the replay doesn't track them: Player doesn't use +klook or +strafe.
- // If the assumptions are wrong we will just end up with wrong sound prediction, no big deal.
- if (currentTickData.flags & RP_IN_FORWARD)
- {
- newButtons |= IN_FORWARD;
- vel[0] += RP_PLAYER_ACCELSPEED;
- }
- if (currentTickData.flags & RP_IN_BACK)
- {
- newButtons |= IN_BACK;
- vel[0] -= RP_PLAYER_ACCELSPEED;
- }
- if (currentTickData.flags & RP_IN_MOVELEFT)
- {
- newButtons |= IN_MOVELEFT;
- vel[1] -= RP_PLAYER_ACCELSPEED;
- }
- if (currentTickData.flags & RP_IN_MOVERIGHT)
- {
- newButtons |= IN_MOVERIGHT;
- vel[1] += RP_PLAYER_ACCELSPEED;
- }
- if (currentTickData.flags & RP_IN_LEFT)
- {
- newButtons |= IN_LEFT;
- }
- if (currentTickData.flags & RP_IN_RIGHT)
- {
- newButtons |= IN_RIGHT;
- }
- if (currentTickData.flags & RP_IN_RELOAD)
- {
- newButtons |= IN_RELOAD;
- }
- if (currentTickData.flags & RP_IN_SPEED)
- {
- newButtons |= IN_SPEED;
- }
- buttons = newButtons;
- botButtons[bot] = buttons;
- // The angles might be wrong if the player teleports, but this should only affect sound prediction.
- angles = currentTickData.angles;
-
- // Set the bot's MoveType
- MoveType replayMoveType = view_as<MoveType>(prevTickData.flags & RP_MOVETYPE_MASK);
- botMoveType[bot] = replayMoveType;
- if (replayMoveType == MOVETYPE_WALK)
- {
- Movement_SetMovetype(client, MOVETYPE_WALK);
- }
- else if (replayMoveType == MOVETYPE_LADDER)
- {
- botPaused[bot] = false;
- Movement_SetMovetype(client, MOVETYPE_LADDER);
- }
- else
- {
- Movement_SetMovetype(client, MOVETYPE_NOCLIP);
- }
- // Set some variables
- if (currentTickData.flags & RP_TELEPORT_TICK)
- {
- botJustTeleported[bot] = true;
- botCurrentTeleport[bot]++;
- }
-
- if (currentTickData.flags & RP_TAKEOFF_TICK)
- {
- hitPerf[bot] = currentTickData.flags & RP_HIT_PERF > 0;
- botIsTakeoff[bot] = true;
- botTakeoffSpeed[bot] = GetVectorHorizontalLength(currentTickData.velocity);
- }
-
- if ((currentTickData.flags & RP_SECONDARY_EQUIPPED) && !IsCurrentWeaponSecondary(client))
- {
- int item = GetPlayerWeaponSlot(client, CS_SLOT_SECONDARY);
- if (item != -1)
- {
- char name[64];
- GetEntityClassname(item, name, sizeof(name));
- FakeClientCommand(client, "use %s", name);
- }
- }
- else if (!(currentTickData.flags & RP_SECONDARY_EQUIPPED) && IsCurrentWeaponSecondary(client))
- {
- int item = GetPlayerWeaponSlot(client, CS_SLOT_KNIFE);
- if (item != -1)
- {
- char name[64];
- GetEntityClassname(item, name, sizeof(name));
- FakeClientCommand(client, "use %s", name);
- }
- }
-
- #if defined DEBUG
- if(!botPlaybackPaused[bot])
- {
- PrintToServer("Tick: %d", playbackTick[bot]);
- PrintToServer("X %f \nY %f \nZ %f\nPitch %f\nYaw %f", currentTickData.origin[0], currentTickData.origin[1], currentTickData.origin[2], currentTickData.angles[0], currentTickData.angles[1]);
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_WALK)) PrintToServer("MOVETYPE_WALK");
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_LADDER)) PrintToServer("MOVETYPE_LADDER");
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_NOCLIP)) PrintToServer("MOVETYPE_NOCLIP");
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_NOCLIP)) PrintToServer("MOVETYPE_NONE");
-
- if(currentTickData.flags & RP_IN_ATTACK) PrintToServer("IN_ATTACK");
- if(currentTickData.flags & RP_IN_ATTACK2) PrintToServer("IN_ATTACK2");
- if(currentTickData.flags & RP_IN_JUMP) PrintToServer("IN_JUMP");
- if(currentTickData.flags & RP_IN_DUCK) PrintToServer("IN_DUCK");
- if(currentTickData.flags & RP_IN_FORWARD) PrintToServer("IN_FORWARD");
- if(currentTickData.flags & RP_IN_BACK) PrintToServer("IN_BACK");
- if(currentTickData.flags & RP_IN_LEFT) PrintToServer("IN_LEFT");
- if(currentTickData.flags & RP_IN_RIGHT) PrintToServer("IN_RIGHT");
- if(currentTickData.flags & RP_IN_MOVELEFT) PrintToServer("IN_MOVELEFT");
- if(currentTickData.flags & RP_IN_MOVERIGHT) PrintToServer("IN_MOVERIGHT");
- if(currentTickData.flags & RP_IN_RELOAD) PrintToServer("IN_RELOAD");
- if(currentTickData.flags & RP_IN_SPEED) PrintToServer("IN_SPEED");
- if(currentTickData.flags & RP_IN_USE) PrintToServer("IN_USE");
- if(currentTickData.flags & RP_IN_BULLRUSH) PrintToServer("IN_BULLRUSH");
-
- if(currentTickData.flags & RP_FL_ONGROUND) PrintToServer("FL_ONGROUND");
- if(currentTickData.flags & RP_FL_DUCKING ) PrintToServer("FL_DUCKING");
- if(currentTickData.flags & RP_FL_SWIM) PrintToServer("FL_SWIM");
- if(currentTickData.flags & RP_UNDER_WATER) PrintToServer("WATERLEVEL!=0");
- if(currentTickData.flags & RP_TELEPORT_TICK) PrintToServer("TELEPORT");
- if(currentTickData.flags & RP_TAKEOFF_TICK) PrintToServer("TAKEOFF");
- if(currentTickData.flags & RP_HIT_PERF) PrintToServer("PERF");
- if(currentTickData.flags & RP_SECONDARY_EQUIPPED) PrintToServer("SECONDARY_WEAPON_EQUIPPED");
- PrintToServer("==============================================================");
- }
- #endif
- }
-}
-
-void PlaybackVersion2Post(int client, int bot)
-{
- if (botPlaybackPaused[bot])
- {
- return;
- }
- int size = playbackTickData[bot].Length;
- if (playbackTick[bot] != 0 && playbackTick[bot] != (size - 1))
- {
- ReplayTickData currentTickData;
- ReplayTickData prevTickData;
- playbackTickData[bot].GetArray(playbackTick[bot], currentTickData);
- playbackTickData[bot].GetArray(IntMax(playbackTick[bot] - 1, 0), prevTickData);
-
- // TeleportEntity does not set the absolute origin and velocity so we need to do it
- // to prevent inaccurate eye position interpolation.
- SetEntPropVector(client, Prop_Data, "m_vecVelocity", currentTickData.velocity);
- SetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", currentTickData.velocity);
-
- SetEntPropVector(client, Prop_Data, "m_vecAbsOrigin", currentTickData.origin);
- SetEntPropVector(client, Prop_Data, "m_vecOrigin", currentTickData.origin);
-
- SetEntPropFloat(client, Prop_Send, "m_angEyeAngles[0]", currentTickData.angles[0]);
- SetEntPropFloat(client, Prop_Send, "m_angEyeAngles[1]", currentTickData.angles[1]);
-
- MoveType replayMoveType = view_as<MoveType>(currentTickData.flags & RP_MOVETYPE_MASK);
- botMoveType[bot] = replayMoveType;
- int entityFlags = GetEntityFlags(client);
- if (replayMoveType == MOVETYPE_WALK)
- {
- if (currentTickData.flags & RP_FL_ONGROUND)
- {
- SetEntityFlags(client, entityFlags | FL_ONGROUND);
- botPaused[bot] = false;
- // The bot is on the ground, so there must be a ground entity attributed to the bot.
- int groundEnt = GetEntPropEnt(client, Prop_Send, "m_hGroundEntity");
- if (groundEnt == -1 && botJustTeleported[bot])
- {
- SetEntPropFloat(client, Prop_Send, "m_flFallVelocity", 0.0);
- float endPosition[3], mins[3], maxs[3];
- GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs);
- GetEntPropVector(client, Prop_Send, "m_vecMins", mins);
- endPosition = currentTickData.origin;
- endPosition[2] -= 2.0;
- TR_TraceHullFilter(currentTickData.origin, endPosition, mins, maxs, MASK_PLAYERSOLID, TraceEntityFilterPlayers);
-
- // This should always hit.
- if (TR_DidHit())
- {
- groundEnt = TR_GetEntityIndex();
- SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", groundEnt);
- }
- }
- }
- else
- {
- botJustTeleported[bot] = false;
- }
- }
-
- if (currentTickData.flags & RP_UNDER_WATER)
- {
- SetEntityFlags(client, entityFlags | FL_INWATER);
- }
-
- botSpeed[bot] = GetVectorHorizontalLength(currentTickData.velocity);
- playbackTick[bot]++;
- }
-}
-
-// Set the bot client's GOKZ options, clan tag and name based on the loaded replay data
-static void SetBotStuff(int bot)
-{
- if (!botInGame[bot] || !botDataLoaded[bot])
- {
- return;
- }
-
- int client = botClient[bot];
-
- // Set its movement options just in case it could negatively affect the playback
- GOKZ_SetCoreOption(client, Option_Mode, botMode[bot]);
- GOKZ_SetCoreOption(client, Option_Style, botStyle[bot]);
-
- // Clan tag and name
- SetBotClanTag(bot);
- SetBotName(bot);
-
- // Bot takes one tick after being put in server to be able to respawn.
- RequestFrame(RequestFrame_SetBotStuff, GetClientUserId(client));
-}
-
-public void RequestFrame_SetBotStuff(int userid)
-{
- int client = GetClientOfUserId(userid);
- if (!client)
- {
- return;
- }
- int bot;
- for (bot = 0; bot <= RP_MAX_BOTS; bot++)
- {
- if (botClient[bot] == client)
- {
- break;
- }
- else if (bot == RP_MAX_BOTS)
- {
- return;
- }
- }
- // Set the bot's team based on if it's NUB or PRO
- if (botReplayType[bot] == ReplayType_Run
- && GOKZ_GetTimeTypeEx(botTeleportsUsed[bot]) == TimeType_Pro)
- {
- GOKZ_JoinTeam(client, CS_TEAM_CT, .forceBroadcast = true);
- }
- else
- {
- GOKZ_JoinTeam(client, CS_TEAM_CT, .forceBroadcast = true);
- }
- // Set bot weapons
- // Always start by removing the pistol and knife
- int currentPistol = GetPlayerWeaponSlot(client, CS_SLOT_SECONDARY);
- if (currentPistol != -1)
- {
- RemovePlayerItem(client, currentPistol);
- }
-
- int currentKnife = GetPlayerWeaponSlot(client, CS_SLOT_KNIFE);
- if (currentKnife != -1)
- {
- RemovePlayerItem(client, currentKnife);
- }
-
- char weaponName[128];
- // Give the bot the knife stored in the replay
- /*
- if (botKnife[bot] != 0)
- {
- CS_WeaponIDToAlias(CS_ItemDefIndexToID(botKnife[bot]), weaponName, sizeof(weaponName));
- Format(weaponName, sizeof(weaponName), "weapon_%s", weaponName);
- GivePlayerItem(client, weaponName);
- }
- else
- {
- GivePlayerItem(client, "weapon_knife");
- }
- */
- // We are currently not doing that, as it would require us to disable the
- // FollowCSGOServerGuidelines failsafe if the bot has a non-standard knife.
- GivePlayerItem(client, "weapon_knife");
-
- // Give the bot the pistol stored in the replay
- if (botWeapon[bot] != -1)
- {
- CS_WeaponIDToAlias(CS_ItemDefIndexToID(botWeapon[bot]), weaponName, sizeof(weaponName));
- Format(weaponName, sizeof(weaponName), "weapon_%s", weaponName);
- GivePlayerItem(client, weaponName);
- }
-
- botCurrentTeleport[bot] = 0;
-}
-
-static void SetBotClanTag(int bot)
-{
- char tag[MAX_NAME_LENGTH];
-
- if (botReplayType[bot] == ReplayType_Run)
- {
- if (botCourse[bot] == 0)
- {
- // KZT PRO
- FormatEx(tag, sizeof(tag), "%s %s",
- gC_ModeNamesShort[botMode[bot]], gC_TimeTypeNames[GOKZ_GetTimeTypeEx(botTeleportsUsed[bot])]);
- }
- else
- {
- // KZT B2 PRO
- FormatEx(tag, sizeof(tag), "%s B%d %s",
- gC_ModeNamesShort[botMode[bot]], botCourse[bot], gC_TimeTypeNames[GOKZ_GetTimeTypeEx(botTeleportsUsed[bot])]);
- }
- }
- else if (botReplayType[bot] == ReplayType_Jump)
- {
- // KZT LJ
- FormatEx(tag, sizeof(tag), "%s %s",
- gC_ModeNamesShort[botMode[bot]], gC_JumpTypesShort[botJumpType[bot]]);
- }
- else
- {
- // KZT
- FormatEx(tag, sizeof(tag), "%s",
- gC_ModeNamesShort[botMode[bot]]);
- }
-
- CS_SetClientClanTag(botClient[bot], tag);
-}
-
-static void SetBotName(int bot)
-{
- char name[MAX_NAME_LENGTH];
-
- if (botReplayType[bot] == ReplayType_Run)
- {
- // DanZay (01:23.45)
- FormatEx(name, sizeof(name), "%s (%s)",
- botAlias[bot], GOKZ_FormatTime(botTime[bot]));
- }
- else if (botReplayType[bot] == ReplayType_Jump)
- {
- if (botJumpBlockDistance[bot] == 0)
- {
- // DanZay (291.44)
- FormatEx(name, sizeof(name), "%s (%.2f)",
- botAlias[bot], botJumpDistance[bot]);
- }
- else
- {
- // DanZay (291.44 on 289 block)
- FormatEx(name, sizeof(name), "%s (%.2f on %d block)",
- botAlias[bot], botJumpDistance[bot], botJumpBlockDistance[bot]);
- }
- }
- else
- {
- // DanZay
- FormatEx(name, sizeof(name), "%s",
- botAlias[bot]);
- }
-
- gB_HideNameChange = true;
- SetClientName(botClient[bot], name);
-}
-
-// Returns the number of bots that are currently replaying
-static int GetBotsInUse()
-{
- int botsInUse = 0;
- for (int bot; bot < RP_MAX_BOTS; bot++)
- {
- if (botInGame[bot] && botDataLoaded[bot])
- {
- botsInUse++;
- }
- }
- return botsInUse;
-}
-
-// Returns a bot that isn't currently replaying, or -1 if no unused bots found
-static int GetUnusedBot()
-{
- for (int bot = 0; bot < RP_MAX_BOTS; bot++)
- {
- if (!botInGame[bot])
- {
- return bot;
- }
- }
- return -1;
-}
-
-static void PlaybackSkipToTick(int bot, int tick)
-{
- if (botReplayVersion[bot] == 1)
- {
- // Load in the next tick
- float repOrigin[3], repAngles[3];
- repOrigin[0] = playbackTickData[bot].Get(tick, 0);
- repOrigin[1] = playbackTickData[bot].Get(tick, 1);
- repOrigin[2] = playbackTickData[bot].Get(tick, 2);
- repAngles[0] = playbackTickData[bot].Get(tick, 3);
- repAngles[1] = playbackTickData[bot].Get(tick, 4);
-
- TeleportEntity(botClient[bot], repOrigin, repAngles, view_as<float>( { 0.0, 0.0, 0.0 } ));
- }
- else if (botReplayVersion[bot] == 2)
- {
- // Load in the next tick
- ReplayTickData currentTickData;
- playbackTickData[bot].GetArray(tick, currentTickData);
-
- TeleportEntity(botClient[bot], currentTickData.origin, currentTickData.angles, view_as<float>( { 0.0, 0.0, 0.0 } ));
-
- int direction = tick < playbackTick[bot] ? -1 : 1;
- for (int i = playbackTick[bot]; i != tick; i += direction)
- {
- playbackTickData[bot].GetArray(i, currentTickData);
- if (currentTickData.flags & RP_TELEPORT_TICK)
- {
- botCurrentTeleport[bot] += direction;
- }
- }
-
- #if defined DEBUG
- PrintToServer("X %f \nY %f \nZ %f\nPitch %f\nYaw %f", currentTickData.origin[0], currentTickData.origin[1], currentTickData.origin[2], currentTickData.angles[0], currentTickData.angles[1]);
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_WALK)) PrintToServer("MOVETYPE_WALK");
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_LADDER)) PrintToServer("MOVETYPE_LADDER");
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_NOCLIP)) PrintToServer("MOVETYPE_NOCLIP");
- if(currentTickData.flags & RP_MOVETYPE_MASK == view_as<int>(MOVETYPE_NONE)) PrintToServer("MOVETYPE_NONE");
-
- if(currentTickData.flags & RP_IN_ATTACK) PrintToServer("IN_ATTACK");
- if(currentTickData.flags & RP_IN_ATTACK2) PrintToServer("IN_ATTACK2");
- if(currentTickData.flags & RP_IN_JUMP) PrintToServer("IN_JUMP");
- if(currentTickData.flags & RP_IN_DUCK) PrintToServer("IN_DUCK");
- if(currentTickData.flags & RP_IN_FORWARD) PrintToServer("IN_FORWARD");
- if(currentTickData.flags & RP_IN_BACK) PrintToServer("IN_BACK");
- if(currentTickData.flags & RP_IN_LEFT) PrintToServer("IN_LEFT");
- if(currentTickData.flags & RP_IN_RIGHT) PrintToServer("IN_RIGHT");
- if(currentTickData.flags & RP_IN_MOVELEFT) PrintToServer("IN_MOVELEFT");
- if(currentTickData.flags & RP_IN_MOVERIGHT) PrintToServer("IN_MOVERIGHT");
- if(currentTickData.flags & RP_IN_RELOAD) PrintToServer("IN_RELOAD");
- if(currentTickData.flags & RP_IN_SPEED) PrintToServer("IN_SPEED");
- if(currentTickData.flags & RP_FL_ONGROUND) PrintToServer("FL_ONGROUND");
- if(currentTickData.flags & RP_FL_DUCKING ) PrintToServer("FL_DUCKING");
- if(currentTickData.flags & RP_FL_SWIM) PrintToServer("FL_SWIM");
- if(currentTickData.flags & RP_UNDER_WATER) PrintToServer("WATERLEVEL!=0");
- if(currentTickData.flags & RP_TELEPORT_TICK) PrintToServer("TELEPORT");
- if(currentTickData.flags & RP_TAKEOFF_TICK) PrintToServer("TAKEOFF");
- if(currentTickData.flags & RP_HIT_PERF) PrintToServer("PERF");
- if(currentTickData.flags & RP_SECONDARY_EQUIPPED) PrintToServer("SECONDARY_WEAPON_EQUIPPED");
- PrintToServer("==============================================================");
- #endif
- }
-
- Movement_SetMovetype(botClient[bot], MOVETYPE_NOCLIP);
- playbackTick[bot] = tick;
-}
-
-static bool IsCurrentWeaponSecondary(int client)
-{
- int activeWeaponEnt = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
- int secondaryEnt = GetPlayerWeaponSlot(client, CS_SLOT_SECONDARY);
- return activeWeaponEnt == secondaryEnt;
-}
-
-static void MakePlayerSpectate(int client, int bot)
-{
- GOKZ_JoinTeam(client, CS_TEAM_SPECTATOR);
- SetEntProp(client, Prop_Send, "m_iObserverMode", 4);
- SetEntPropEnt(client, Prop_Send, "m_hObserverTarget", bot);
-
- int clientUserID = GetClientUserId(client);
- DataPack data = new DataPack();
- data.WriteCell(clientUserID);
- data.WriteCell(GetClientUserId(bot));
- CreateTimer(0.1, Timer_UpdateBotName, GetClientUserId(bot));
- EnableReplayControls(client);
-}
-
-public Action Timer_UpdateBotName(Handle timer, int botUID)
-{
- Event e = CreateEvent("spec_target_updated");
- e.SetInt("userid", botUID);
- e.Fire();
- return Plugin_Continue;
-} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-replays/recording.sp b/sourcemod/scripting/gokz-replays/recording.sp
deleted file mode 100644
index babbd5e..0000000
--- a/sourcemod/scripting/gokz-replays/recording.sp
+++ /dev/null
@@ -1,990 +0,0 @@
-/*
- Bot replay recording logic and processes.
-
- Records data every time OnPlayerRunCmdPost is called.
- If the player doesn't have their timer running, it keeps track
- of the last 2 minutes of their actions. If a player is banned
- while their timer isn't running, those 2 minutes are saved.
- If the player has their timer running, the recording is done from
- the beginning of the run. If the player can no longer beat their PB,
- then the recording goes back to only keeping track of the last
- two minutes. Upon beating their PB, a temporary binary file will be
- written with a 'header' containing information about the run,
- followed by the recorded tick data from OnPlayerRunCmdPost.
- The binary file will be permanently locally saved on the server
- if the run beats the server record.
-*/
-
-static float tickrate;
-static int preAndPostRunTickCount;
-static int maxCheaterReplayTicks;
-static int recordingIndex[MAXPLAYERS + 1];
-static float playerSensitivity[MAXPLAYERS + 1];
-static float playerMYaw[MAXPLAYERS + 1];
-static bool isTeleportTick[MAXPLAYERS + 1];
-static ReplaySaveState replaySaveState[MAXPLAYERS + 1];
-static bool recordingPaused[MAXPLAYERS + 1];
-static bool postRunRecording[MAXPLAYERS + 1];
-static ArrayList recordedRecentData[MAXPLAYERS + 1];
-static ArrayList recordedRunData[MAXPLAYERS + 1];
-static ArrayList recordedPostRunData[MAXPLAYERS + 1];
-static Handle runningRunBreatherTimer[MAXPLAYERS + 1];
-static ArrayList runningJumpstatTimers[MAXPLAYERS + 1];
-
-// =====[ EVENTS ]=====
-
-void OnMapStart_Recording()
-{
- CreateReplaysDirectory(gC_CurrentMap);
- tickrate = 1/GetTickInterval();
- preAndPostRunTickCount = RoundToZero(RP_PLAYBACK_BREATHER_TIME * tickrate);
- maxCheaterReplayTicks = RoundToCeil(RP_MAX_CHEATER_REPLAY_LENGTH * tickrate);
-}
-
-void OnClientPutInServer_Recording(int client)
-{
- ClearClientRecordingState(client);
-}
-
-void OnClientAuthorized_Recording(int client)
-{
- // Apparently the client isn't valid yet here, so we can't check for that!
- if(!IsFakeClient(client))
- {
- // Create directory path for player if not exists
- char replayPath[PLATFORM_MAX_PATH];
- BuildPath(Path_SM, replayPath, sizeof(replayPath), "%s/%d", RP_DIRECTORY_JUMPS, GetSteamAccountID(client));
- if (!DirExists(replayPath))
- {
- CreateDirectory(replayPath, 511);
- }
- BuildPath(Path_SM, replayPath, sizeof(replayPath), "%s/%d/%s", RP_DIRECTORY_JUMPS, GetSteamAccountID(client), RP_DIRECTORY_BLOCKJUMPS);
- if (!DirExists(replayPath))
- {
- CreateDirectory(replayPath, 511);
- }
- }
-}
-
-void OnClientDisconnect_Recording(int client)
-{
- // Stop exceptions if OnClientPutInServer was never ran for this client id.
- // As long as the arrays aren't null we'll be fine.
- if (runningJumpstatTimers[client] == null)
- {
- return;
- }
-
- // Trigger all timers early
- if(!IsFakeClient(client))
- {
- if (runningRunBreatherTimer[client] != INVALID_HANDLE)
- {
- TriggerTimer(runningRunBreatherTimer[client], false);
- }
-
- // We have to clone the array because the timer callback removes the timer
- // from the array we're running over, and doing weird tricks is scary.
- ArrayList timers = runningJumpstatTimers[client].Clone();
- for (int i = 0; i < timers.Length; i++)
- {
- Handle timer = timers.Get(i);
- TriggerTimer(timer, false);
- }
- delete timers;
- }
-
- ClearClientRecordingState(client);
-}
-
-void OnPlayerRunCmdPost_Recording(int client, int buttons, int tickCount, const float vel[3], const int mouse[2])
-{
- if (!IsValidClient(client) || IsFakeClient(client) || !IsPlayerAlive(client) || recordingPaused[client])
- {
- return;
- }
-
- ReplayTickData tickData;
-
- Movement_GetOrigin(client, tickData.origin);
-
- tickData.mouse = mouse;
- tickData.vel = vel;
- Movement_GetVelocity(client, tickData.velocity);
- Movement_GetEyeAngles(client, tickData.angles);
- tickData.flags = EncodePlayerFlags(client, buttons, tickCount);
- tickData.packetsPerSecond = GetClientAvgPackets(client, NetFlow_Incoming);
- tickData.laggedMovementValue = GetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue");
- tickData.buttonsForced = GetEntProp(client, Prop_Data, "m_afButtonForced");
-
- // HACK: Reset teleport tick marker. Too bad!
- if (isTeleportTick[client])
- {
- isTeleportTick[client] = false;
- }
-
- if (replaySaveState[client] != ReplaySave_Disabled)
- {
- int runTick = GetArraySize(recordedRunData[client]);
- if (runTick < RP_MAX_DURATION)
- {
- // Resize might fail if the timer exceed the max duration,
- // as it is not guaranteed to allocate more than 1GB of contiguous memory,
- // causing mass lag spikes that kick everyone out of the server.
- // We can still attempt to save the rest of the recording though.
- recordedRunData[client].Resize(runTick + 1);
- recordedRunData[client].SetArray(runTick, tickData);
- }
- }
- if (postRunRecording[client])
- {
- int tick = GetArraySize(recordedPostRunData[client]);
- if (tick < RP_MAX_DURATION)
- {
- recordedPostRunData[client].Resize(tick + 1);
- recordedPostRunData[client].SetArray(tick, tickData);
- }
- }
-
- int tick = recordingIndex[client];
- if (recordedRecentData[client].Length < maxCheaterReplayTicks)
- {
- recordedRecentData[client].Resize(recordedRecentData[client].Length + 1);
- recordingIndex[client] = recordingIndex[client] + 1 == maxCheaterReplayTicks ? 0 : recordingIndex[client] + 1;
- }
- else
- {
- recordingIndex[client] = RecordingIndexAdd(client, 1);
- }
-
- recordedRecentData[client].SetArray(tick, tickData);
-}
-
-Action GOKZ_OnTimerStart_Recording(int client)
-{
- // Hack to fix an exception when starting the timer on the very
- // first tick after loading the plugin.
- if (recordedRecentData[client].Length == 0)
- {
- return Plugin_Handled;
- }
-
- return Plugin_Continue;
-}
-
-void GOKZ_OnTimerStart_Post_Recording(int client)
-{
- replaySaveState[client] = ReplaySave_Local;
- StartRunRecording(client);
-}
-
-void GOKZ_OnTimerEnd_Recording(int client, int course, float time, int teleportsUsed)
-{
- if (replaySaveState[client] == ReplaySave_Disabled)
- {
- return;
- }
-
- DataPack data = new DataPack();
- data.WriteCell(GetClientUserId(client));
- data.WriteCell(course);
- data.WriteFloat(time);
- data.WriteCell(teleportsUsed);
- data.WriteCell(replaySaveState[client]);
- // The previous run breather still did not finish, end it now or
- // we will start overwriting the data.
- if (runningRunBreatherTimer[client] != INVALID_HANDLE)
- {
- TriggerTimer(runningRunBreatherTimer[client], false);
- }
-
- replaySaveState[client] = ReplaySave_Disabled;
- postRunRecording[client] = true;
-
- // Swap recordedRunData and recordedPostRunData.
- // This lets new runs start immediately, before the post-run breather is
- // finished recording.
- ArrayList tmp = recordedPostRunData[client];
- recordedPostRunData[client] = recordedRunData[client];
- recordedRunData[client] = tmp;
- recordedRunData[client].Clear();
-
- runningRunBreatherTimer[client] = CreateTimer(RP_PLAYBACK_BREATHER_TIME, Timer_EndRecording, data);
- if (runningRunBreatherTimer[client] == INVALID_HANDLE)
- {
- LogError("Could not create a timer so can't end the run replay recording");
- }
-}
-
-public Action Timer_EndRecording(Handle timer, DataPack data)
-{
- data.Reset();
- int client = GetClientOfUserId(data.ReadCell());
- int course = data.ReadCell();
- float time = data.ReadFloat();
- int teleportsUsed = data.ReadCell();
- ReplaySaveState saveState = data.ReadCell();
- delete data;
-
- // The client left after the run was done but before the post-run
- // breather had the chance to finish. This should not happen, as we
- // trigger all running timers on disconnect.
- if (!IsValidClient(client))
- {
- return Plugin_Stop;
- }
-
- runningRunBreatherTimer[client] = INVALID_HANDLE;
- postRunRecording[client] = false;
-
- if (gB_GOKZLocalDB && GOKZ_DB_IsCheater(client))
- {
- // Replay might be submitted globally, but will not be saved locally.
- saveState = ReplaySave_Temp;
- }
-
- char path[PLATFORM_MAX_PATH];
- if (SaveRecordingOfRun(path, client, course, time, teleportsUsed, saveState == ReplaySave_Temp))
- {
- Call_OnTimerEnd_Post(client, path, course, time, teleportsUsed);
- }
- else
- {
- Call_OnTimerEnd_Post(client, "", course, time, teleportsUsed);
- }
-
- return Plugin_Stop;
-}
-
-void GOKZ_OnPause_Recording(int client)
-{
- PauseRecording(client);
-}
-
-void GOKZ_OnResume_Recording(int client)
-{
- ResumeRecording(client);
-}
-
-void GOKZ_OnTimerStopped_Recording(int client)
-{
- replaySaveState[client] = ReplaySave_Disabled;
-}
-
-void GOKZ_OnCountedTeleport_Recording(int client)
-{
- if (gB_NubRecordMissed[client])
- {
- replaySaveState[client] = ReplaySave_Disabled;
- }
-
- isTeleportTick[client] = true;
-}
-
-void GOKZ_LR_OnRecordMissed_Recording(int client, int recordType)
-{
- if (replaySaveState[client] == ReplaySave_Disabled)
- {
- return;
- }
- // If missed PRO record or both records, then can no longer beat a server record
- if (recordType == RecordType_NubAndPro || recordType == RecordType_Pro)
- {
- replaySaveState[client] = ReplaySave_Temp;
- }
-
- // If on a NUB run and missed NUB record, then can no longer beat a server record
- // Otherwise wait to see if they teleport before stopping the recording
- if (recordType == RecordType_Nub)
- {
- if (GOKZ_GetTeleportCount(client) > 0)
- {
- replaySaveState[client] = ReplaySave_Temp;
- }
- }
-}
-
-public void GOKZ_LR_OnPBMissed(int client, float pbTime, int course, int mode, int style, int recordType)
-{
- if (replaySaveState[client] == ReplaySave_Disabled)
- {
- return;
- }
- // If missed PRO record or both records, then can no longer beat PB
- if (recordType == RecordType_NubAndPro || recordType == RecordType_Pro)
- {
- replaySaveState[client] = ReplaySave_Disabled;
- }
-
- // If on a NUB run and missed NUB record, then can no longer beat PB
- // Otherwise wait to see if they teleport before stopping the recording
- if (recordType == RecordType_Nub)
- {
- if (GOKZ_GetTeleportCount(client) > 0)
- {
- replaySaveState[client] = ReplaySave_Disabled;
- }
- }
-}
-
-void GOKZ_AC_OnPlayerSuspected_Recording(int client, ACReason reason)
-{
- SaveRecordingOfCheater(client, reason);
-}
-
-void GOKZ_DB_OnJumpstatPB_Recording(int client, int jumptype, float distance, int block, int strafes, float sync, float pre, float max, int airtime)
-{
- DataPack data = new DataPack();
- data.WriteCell(GetClientUserId(client));
- data.WriteCell(jumptype);
- data.WriteFloat(distance);
- data.WriteCell(block);
- data.WriteCell(strafes);
- data.WriteFloat(sync);
- data.WriteFloat(pre);
- data.WriteFloat(max);
- data.WriteCell(airtime);
-
- Handle timer = CreateTimer(RP_PLAYBACK_BREATHER_TIME, SaveJump, data);
- if (timer != INVALID_HANDLE)
- {
- runningJumpstatTimers[client].Push(timer);
- }
- else
- {
- LogError("Could not create a timer so can't save jumpstat pb replay");
- }
-}
-
-public Action SaveJump(Handle timer, DataPack data)
-{
- data.Reset();
- int client = GetClientOfUserId(data.ReadCell());
- int jumptype = data.ReadCell();
- float distance = data.ReadFloat();
- int block = data.ReadCell();
- int strafes = data.ReadCell();
- float sync = data.ReadFloat();
- float pre = data.ReadFloat();
- float max = data.ReadFloat();
- int airtime = data.ReadCell();
- delete data;
-
- // The client left after the jump was done but before the post-jump
- // breather had the chance to finish. This should not happen, as we
- // trigger all running timers on disconnect.
- if (!IsValidClient(client))
- {
- return Plugin_Stop;
- }
-
- RemoveFromRunningTimers(client, timer);
-
- SaveRecordingOfJump(client, jumptype, distance, block, strafes, sync, pre, max, airtime);
- return Plugin_Stop;
-}
-
-
-
-// =====[ PRIVATE ]=====
-
-static void ClearClientRecordingState(int client)
-{
- recordingIndex[client] = 0;
- playerSensitivity[client] = -1.0;
- playerMYaw[client] = -1.0;
- isTeleportTick[client] = false;
- replaySaveState[client] = ReplaySave_Disabled;
- recordingPaused[client] = false;
- postRunRecording[client] = false;
- runningRunBreatherTimer[client] = INVALID_HANDLE;
-
- if (recordedRecentData[client] == null)
- recordedRecentData[client] = new ArrayList(sizeof(ReplayTickData));
-
- if (recordedRunData[client] == null)
- recordedRunData[client] = new ArrayList(sizeof(ReplayTickData));
-
- if (recordedPostRunData[client] == null)
- recordedPostRunData[client] = new ArrayList(sizeof(ReplayTickData));
-
- if (runningJumpstatTimers[client] == null)
- runningJumpstatTimers[client] = new ArrayList();
-
- recordedRecentData[client].Clear();
- recordedRunData[client].Clear();
- recordedPostRunData[client].Clear();
- runningJumpstatTimers[client].Clear();
-}
-
-static void StartRunRecording(int client)
-{
- if (IsFakeClient(client))
- {
- return;
- }
-
- QueryClientConVar(client, "sensitivity", SensitivityCheck, client);
- QueryClientConVar(client, "m_yaw", MYAWCheck, client);
-
- DiscardRecording(client);
- ResumeRecording(client);
-
- // Copy pre data
- int index;
- recordedRunData[client].Resize(preAndPostRunTickCount);
- if (recordedRecentData[client].Length < preAndPostRunTickCount)
- {
- index = recordingIndex[client] - preAndPostRunTickCount;
- }
- else
- {
- index = RecordingIndexAdd(client, -preAndPostRunTickCount);
- }
- for (int i = 0; i < preAndPostRunTickCount; i++)
- {
- ReplayTickData tickData;
- if (index < 0)
- {
- recordedRecentData[client].GetArray(0, tickData);
- recordedRunData[client].SetArray(i, tickData);
- index += 1;
- }
- else
- {
- recordedRecentData[client].GetArray(index, tickData);
- recordedRunData[client].SetArray(i, tickData);
- index = RecordingIndexAdd(client, -preAndPostRunTickCount + i + 1);
- }
- }
-}
-
-static void DiscardRecording(int client)
-{
- recordedRunData[client].Clear();
- Call_OnReplayDiscarded(client);
-}
-
-static void PauseRecording(int client)
-{
- recordingPaused[client] = true;
-}
-
-static void ResumeRecording(int client)
-{
- recordingPaused[client] = false;
-}
-
-static bool SaveRecordingOfRun(char replayPath[PLATFORM_MAX_PATH], int client, int course, float time, int teleportsUsed, bool temp)
-{
- // Prepare data
- int timeType = GOKZ_GetTimeTypeEx(teleportsUsed);
-
- // Create and fill General Header
- GeneralReplayHeader generalHeader;
- FillGeneralHeader(generalHeader, client, ReplayType_Run, recordedPostRunData[client].Length);
-
- // Create and fill Run Header
- RunReplayHeader runHeader;
- runHeader.time = time;
- runHeader.course = course;
- runHeader.teleportsUsed = teleportsUsed;
-
- // Build path and create/overwrite associated file
- FormatRunReplayPath(replayPath, sizeof(replayPath), course, generalHeader.mode, generalHeader.style, timeType, temp);
- if (FileExists(replayPath))
- {
- DeleteFile(replayPath);
- }
- else if (!temp)
- {
- AddToReplayInfoCache(course, generalHeader.mode, generalHeader.style, timeType);
- SortReplayInfoCache();
- }
-
- File file = OpenFile(replayPath, "wb");
- if (file == null)
- {
- LogError("Failed to create/open replay file to write to: \"%s\".", replayPath);
- return false;
- }
-
- WriteGeneralHeader(file, generalHeader);
-
- // Write run header
- file.WriteInt32(view_as<int>(runHeader.time));
- file.WriteInt8(runHeader.course);
- file.WriteInt32(runHeader.teleportsUsed);
-
- WriteTickData(file, client, ReplayType_Run);
-
- delete file;
- // If there is no plugin that wants to take over the replay file, we will delete it ourselves.
- if (Call_OnReplaySaved(client, ReplayType_Run, gC_CurrentMap, course, timeType, time, replayPath, temp) == Plugin_Continue && temp)
- {
- DeleteFile(replayPath);
- }
-
- return true;
-}
-
-static bool SaveRecordingOfCheater(int client, ACReason reason)
-{
- // Create and fill general header
- GeneralReplayHeader generalHeader;
- FillGeneralHeader(generalHeader, client, ReplayType_Cheater, recordedRecentData[client].Length);
-
- // Create and fill cheater header
- CheaterReplayHeader cheaterHeader;
- cheaterHeader.ACReason = reason;
-
- //Build path and create/overwrite associated file
- char replayPath[PLATFORM_MAX_PATH];
- FormatCheaterReplayPath(replayPath, sizeof(replayPath), client, generalHeader.mode, generalHeader.style);
-
- File file = OpenFile(replayPath, "wb");
- if (file == null)
- {
- LogError("Failed to create/open replay file to write to: \"%s\".", replayPath);
- return false;
- }
-
- WriteGeneralHeader(file, generalHeader);
- file.WriteInt8(view_as<int>(cheaterHeader.ACReason));
- WriteTickData(file, client, ReplayType_Cheater);
-
- delete file;
-
- return true;
-}
-
-static bool SaveRecordingOfJump(int client, int jumptype, float distance, int block, int strafes, float sync, float pre, float max, int airtime)
-{
- // Just cause I know how buggy jumpstats can be
- int airtimeTicks = RoundToNearest((float(airtime) / GOKZ_DB_JS_AIRTIME_PRECISION) * tickrate);
- if (airtimeTicks + 2 * preAndPostRunTickCount >= maxCheaterReplayTicks)
- {
- LogError("WARNING: Invalid airtime (this is probably a bugged jump, please report it!).");
- return false;
- }
-
- // Create and fill general header
- GeneralReplayHeader generalHeader;
- FillGeneralHeader(generalHeader, client, ReplayType_Jump, 2 * preAndPostRunTickCount + airtimeTicks);
-
- // Create and fill jump header
- JumpReplayHeader jumpHeader;
- FillJumpHeader(jumpHeader, jumptype, distance, block, strafes, sync, pre, max, airtime);
-
- // Make sure the client is authenticated
- if (GetSteamAccountID(client) == 0)
- {
- LogError("Failed to save jump, client is not authenticated.");
- return false;
- }
-
- // Build path and create/overwrite associated file
- char replayPath[PLATFORM_MAX_PATH];
- if (block > 0)
- {
- FormatBlockJumpReplayPath(replayPath, sizeof(replayPath), client, block, jumpHeader.jumpType, generalHeader.mode, generalHeader.style);
- }
- else
- {
- FormatJumpReplayPath(replayPath, sizeof(replayPath), client, jumpHeader.jumpType, generalHeader.mode, generalHeader.style);
- }
-
- File file = OpenFile(replayPath, "wb");
- if (file == null)
- {
- LogError("Failed to create/open replay file to write to: \"%s\".", replayPath);
- delete file;
- return false;
- }
-
- WriteGeneralHeader(file, generalHeader);
- WriteJumpHeader(file, jumpHeader);
- WriteTickData(file, client, ReplayType_Jump, airtimeTicks);
-
- delete file;
-
- return true;
-}
-
-static void FillGeneralHeader(GeneralReplayHeader generalHeader, int client, int replayType, int tickCount)
-{
- // Prepare data
- int mode = GOKZ_GetCoreOption(client, Option_Mode);
- int style = GOKZ_GetCoreOption(client, Option_Style);
-
- // Fill general header
- generalHeader.magicNumber = RP_MAGIC_NUMBER;
- generalHeader.formatVersion = RP_FORMAT_VERSION;
- generalHeader.replayType = replayType;
- generalHeader.gokzVersion = GOKZ_VERSION;
- generalHeader.mapName = gC_CurrentMap;
- generalHeader.mapFileSize = gI_CurrentMapFileSize;
- generalHeader.serverIP = FindConVar("hostip").IntValue;
- generalHeader.timestamp = GetTime();
- GetClientName(client, generalHeader.playerAlias, sizeof(GeneralReplayHeader::playerAlias));
- generalHeader.playerSteamID = GetSteamAccountID(client);
- generalHeader.mode = mode;
- generalHeader.style = style;
- generalHeader.playerSensitivity = playerSensitivity[client];
- generalHeader.playerMYaw = playerMYaw[client];
- generalHeader.tickrate = tickrate;
- generalHeader.tickCount = tickCount;
- generalHeader.equippedWeapon = GetPlayerWeaponSlotDefIndex(client, CS_SLOT_SECONDARY);
- generalHeader.equippedKnife = GetPlayerWeaponSlotDefIndex(client, CS_SLOT_KNIFE);
-}
-
-static void FillJumpHeader(JumpReplayHeader jumpHeader, int jumptype, float distance, int block, int strafes, float sync, float pre, float max, int airtime)
-{
- jumpHeader.jumpType = jumptype;
- jumpHeader.distance = distance;
- jumpHeader.blockDistance = block;
- jumpHeader.strafeCount = strafes;
- jumpHeader.sync = sync;
- jumpHeader.pre = pre;
- jumpHeader.max = max;
- jumpHeader.airtime = airtime;
-}
-
-static void WriteGeneralHeader(File file, GeneralReplayHeader generalHeader)
-{
- file.WriteInt32(generalHeader.magicNumber);
- file.WriteInt8(generalHeader.formatVersion);
- file.WriteInt8(generalHeader.replayType);
- file.WriteInt8(strlen(generalHeader.gokzVersion));
- file.WriteString(generalHeader.gokzVersion, false);
- file.WriteInt8(strlen(generalHeader.mapName));
- file.WriteString(generalHeader.mapName, false);
- file.WriteInt32(generalHeader.mapFileSize);
- file.WriteInt32(generalHeader.serverIP);
- file.WriteInt32(generalHeader.timestamp);
- file.WriteInt8(strlen(generalHeader.playerAlias));
- file.WriteString(generalHeader.playerAlias, false);
- file.WriteInt32(generalHeader.playerSteamID);
- file.WriteInt8(generalHeader.mode);
- file.WriteInt8(generalHeader.style);
- file.WriteInt32(view_as<int>(generalHeader.playerSensitivity));
- file.WriteInt32(view_as<int>(generalHeader.playerMYaw));
- file.WriteInt32(view_as<int>(generalHeader.tickrate));
- file.WriteInt32(generalHeader.tickCount);
- file.WriteInt32(generalHeader.equippedWeapon);
- file.WriteInt32(generalHeader.equippedKnife);
-}
-
-static void WriteJumpHeader(File file, JumpReplayHeader jumpHeader)
-{
- file.WriteInt8(jumpHeader.jumpType);
- file.WriteInt32(view_as<int>(jumpHeader.distance));
- file.WriteInt32(jumpHeader.blockDistance);
- file.WriteInt8(jumpHeader.strafeCount);
- file.WriteInt32(view_as<int>(jumpHeader.sync));
- file.WriteInt32(view_as<int>(jumpHeader.pre));
- file.WriteInt32(view_as<int>(jumpHeader.max));
- file.WriteInt32((jumpHeader.airtime));
-}
-
-static void WriteTickData(File file, int client, int replayType, int airtime = 0)
-{
- ReplayTickData tickData;
- ReplayTickData prevTickData;
- bool isFirstTick = true;
- switch(replayType)
- {
- case ReplayType_Run:
- {
- for (int i = 0; i < recordedPostRunData[client].Length; i++)
- {
- recordedPostRunData[client].GetArray(i, tickData);
- recordedPostRunData[client].GetArray(IntMax(0, i-1), prevTickData);
- WriteTickDataToFile(file, isFirstTick, tickData, prevTickData);
- isFirstTick = false;
- }
- }
- case ReplayType_Cheater:
- {
- for (int i = 0; i < recordedRecentData[client].Length; i++)
- {
- int rollingI = RecordingIndexAdd(client, i);
- recordedRecentData[client].GetArray(rollingI, tickData);
- recordedRecentData[client].GetArray(IntMax(0, i-1), prevTickData);
- WriteTickDataToFile(file, isFirstTick, tickData, prevTickData);
- isFirstTick = false;
- }
-
- }
- case ReplayType_Jump:
- {
- int replayLength = 2 * preAndPostRunTickCount + airtime;
- for (int i = 0; i < replayLength; i++)
- {
- int rollingI = RecordingIndexAdd(client, i - replayLength);
- recordedRecentData[client].GetArray(rollingI, tickData);
- recordedRecentData[client].GetArray(IntMax(0, i-1), prevTickData);
- WriteTickDataToFile(file, isFirstTick, tickData, prevTickData);
- isFirstTick = false;
- }
- }
- }
-}
-
-static void WriteTickDataToFile(File file, bool isFirstTick, ReplayTickData tickDataStruct, ReplayTickData prevTickDataStruct)
-{
- any tickData[RP_V2_TICK_DATA_BLOCKSIZE];
- any prevTickData[RP_V2_TICK_DATA_BLOCKSIZE];
- TickDataToArray(tickDataStruct, tickData);
- TickDataToArray(prevTickDataStruct, prevTickData);
-
- int deltaFlags = (1 << RPDELTA_DELTAFLAGS);
- if (isFirstTick)
- {
- // NOTE: Set every bit to 1 until RP_V2_TICK_DATA_BLOCKSIZE.
- deltaFlags = (1 << (RP_V2_TICK_DATA_BLOCKSIZE)) - 1;
- }
- else
- {
- // NOTE: Test tickData against prevTickData for differences.
- for (int i = 1; i < sizeof(tickData); i++)
- {
- // If the bits in tickData[i] are different to prevTickData[i], then
- // set the corresponding bitflag.
- if (tickData[i] ^ prevTickData[i])
- {
- deltaFlags |= (1 << i);
- }
- }
- }
-
- file.WriteInt32(deltaFlags);
- // NOTE: write only data that has changed since the previous tick.
- for (int i = 1; i < sizeof(tickData); i++)
- {
- int currentFlag = (1 << i);
- if (deltaFlags & currentFlag)
- {
- file.WriteInt32(tickData[i]);
- }
- }
-}
-
-static void FormatRunReplayPath(char[] buffer, int maxlength, int course, int mode, int style, int timeType, bool tempPath)
-{
- // Use GetEngineTime to prevent accidental replay overrides.
- // Technically it would still be possible to override this file by accident,
- // if somehow the server restarts to this exact map and course,
- // and this function is run at the exact same time, but that is extremely unlikely.
- // Also by then this file should have already been deleted.
- char tempTimeString[32];
- Format(tempTimeString, sizeof(tempTimeString), "%f_", GetEngineTime());
- BuildPath(Path_SM, buffer, maxlength,
- "%s/%s/%s%d_%s_%s_%s.%s",
- tempPath ? RP_DIRECTORY_RUNS_TEMP : RP_DIRECTORY_RUNS,
- gC_CurrentMap,
- tempPath ? tempTimeString : "",
- course,
- gC_ModeNamesShort[mode],
- gC_StyleNamesShort[style],
- gC_TimeTypeNames[timeType],
- RP_FILE_EXTENSION);
-}
-
-static void FormatCheaterReplayPath(char[] buffer, int maxlength, int client, int mode, int style)
-{
- BuildPath(Path_SM, buffer, maxlength,
- "%s/%d_%s_%d_%s_%s.%s",
- RP_DIRECTORY_CHEATERS,
- GetSteamAccountID(client),
- gC_CurrentMap,
- GetTime(),
- gC_ModeNamesShort[mode],
- gC_StyleNamesShort[style],
- RP_FILE_EXTENSION);
-}
-
-static void FormatJumpReplayPath(char[] buffer, int maxlength, int client, int jumpType, int mode, int style)
-{
- BuildPath(Path_SM, buffer, maxlength,
- "%s/%d/%d_%s_%s.%s",
- RP_DIRECTORY_JUMPS,
- GetSteamAccountID(client),
- jumpType,
- gC_ModeNamesShort[mode],
- gC_StyleNamesShort[style],
- RP_FILE_EXTENSION);
-}
-
-static void FormatBlockJumpReplayPath(char[] buffer, int maxlength, int client, int block, int jumpType, int mode, int style)
-{
- BuildPath(Path_SM, buffer, maxlength,
- "%s/%d/%s/%d_%d_%s_%s.%s",
- RP_DIRECTORY_JUMPS,
- GetSteamAccountID(client),
- RP_DIRECTORY_BLOCKJUMPS,
- jumpType,
- block,
- gC_ModeNamesShort[mode],
- gC_StyleNamesShort[style],
- RP_FILE_EXTENSION);
-}
-
-static int EncodePlayerFlags(int client, int buttons, int tickCount)
-{
- int flags = 0;
- MoveType movetype = Movement_GetMovetype(client);
- int clientFlags = GetEntityFlags(client);
-
- flags = view_as<int>(movetype) & RP_MOVETYPE_MASK;
-
- SetKthBit(flags, 4, IsBitSet(buttons, IN_ATTACK));
- SetKthBit(flags, 5, IsBitSet(buttons, IN_ATTACK2));
- SetKthBit(flags, 6, IsBitSet(buttons, IN_JUMP));
- SetKthBit(flags, 7, IsBitSet(buttons, IN_DUCK));
- SetKthBit(flags, 8, IsBitSet(buttons, IN_FORWARD));
- SetKthBit(flags, 9, IsBitSet(buttons, IN_BACK));
- SetKthBit(flags, 10, IsBitSet(buttons, IN_LEFT));
- SetKthBit(flags, 11, IsBitSet(buttons, IN_RIGHT));
- SetKthBit(flags, 12, IsBitSet(buttons, IN_MOVELEFT));
- SetKthBit(flags, 13, IsBitSet(buttons, IN_MOVERIGHT));
- SetKthBit(flags, 14, IsBitSet(buttons, IN_RELOAD));
- SetKthBit(flags, 15, IsBitSet(buttons, IN_SPEED));
- SetKthBit(flags, 16, IsBitSet(buttons, IN_USE));
- SetKthBit(flags, 17, IsBitSet(buttons, IN_BULLRUSH));
- SetKthBit(flags, 18, IsBitSet(clientFlags, FL_ONGROUND));
- SetKthBit(flags, 19, IsBitSet(clientFlags, FL_DUCKING));
- SetKthBit(flags, 20, IsBitSet(clientFlags, FL_SWIM));
-
- SetKthBit(flags, 21, GetEntProp(client, Prop_Data, "m_nWaterLevel") != 0);
-
- SetKthBit(flags, 22, isTeleportTick[client]);
- SetKthBit(flags, 23, Movement_GetTakeoffTick(client) == tickCount);
- SetKthBit(flags, 24, GOKZ_GetHitPerf(client));
- SetKthBit(flags, 25, IsCurrentWeaponSecondary(client));
-
- return flags;
-}
-
-// Function to set the bitNum bit in integer to value
-static void SetKthBit(int &number, int offset, bool value)
-{
- int intValue = value ? 1 : 0;
- number |= intValue << offset;
-}
-
-static bool IsBitSet(int number, int checkBit)
-{
- return (number & checkBit) ? true : false;
-}
-
-static int GetPlayerWeaponSlotDefIndex(int client, int slot)
-{
- int ent = GetPlayerWeaponSlot(client, slot);
-
- // Nothing equipped in the slot
- if (ent == -1)
- {
- return -1;
- }
-
- return GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex");
-}
-
-static bool IsCurrentWeaponSecondary(int client)
-{
- int activeWeaponEnt = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
- int secondaryEnt = GetPlayerWeaponSlot(client, CS_SLOT_SECONDARY);
- return activeWeaponEnt == secondaryEnt;
-}
-
-static void CreateReplaysDirectory(const char[] map)
-{
- char path[PLATFORM_MAX_PATH];
-
- // Create parent replay directory
- BuildPath(Path_SM, path, sizeof(path), RP_DIRECTORY);
- if (!DirExists(path))
- {
- CreateDirectory(path, 511);
- }
-
- // Create maps parent replay directory
- BuildPath(Path_SM, path, sizeof(path), "%s", RP_DIRECTORY_RUNS);
- if (!DirExists(path))
- {
- CreateDirectory(path, 511);
- }
-
-
- // Create maps replay directory
- BuildPath(Path_SM, path, sizeof(path), "%s/%s", RP_DIRECTORY_RUNS, map);
- if (!DirExists(path))
- {
- CreateDirectory(path, 511);
- }
-
- // Create maps parent replay directory
- BuildPath(Path_SM, path, sizeof(path), "%s", RP_DIRECTORY_RUNS_TEMP);
- if (!DirExists(path))
- {
- CreateDirectory(path, 511);
- }
-
-
- // Create maps replay directory
- BuildPath(Path_SM, path, sizeof(path), "%s/%s", RP_DIRECTORY_RUNS_TEMP, map);
- if (!DirExists(path))
- {
- CreateDirectory(path, 511);
- }
-
- // Create cheaters replay directory
- BuildPath(Path_SM, path, sizeof(path), "%s", RP_DIRECTORY_CHEATERS);
- if (!DirExists(path))
- {
- CreateDirectory(path, 511);
- }
-
- // Create jumps parent replay directory
- BuildPath(Path_SM, path, sizeof(path), "%s", RP_DIRECTORY_JUMPS);
- if (!DirExists(path))
- {
- CreateDirectory(path, 511);
- }
-}
-
-public void MYAWCheck(QueryCookie cookie, int client, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue, any value)
-{
- if (IsValidClient(client) && !IsFakeClient(client))
- {
- playerMYaw[client] = StringToFloat(cvarValue);
- }
-}
-
-public void SensitivityCheck(QueryCookie cookie, int client, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue, any value)
-{
- if (IsValidClient(client) && !IsFakeClient(client))
- {
- playerSensitivity[client] = StringToFloat(cvarValue);
- }
-}
-
-static int RecordingIndexAdd(int client, int offset)
-{
- int index = recordingIndex[client] + offset;
- if (index < 0)
- {
- index += recordedRecentData[client].Length;
- }
- return index % recordedRecentData[client].Length;
-}
-
-static void RemoveFromRunningTimers(int client, Handle timerToRemove)
-{
- int index = runningJumpstatTimers[client].FindValue(timerToRemove);
- if (index != -1)
- {
- runningJumpstatTimers[client].Erase(index);
- }
-}
diff --git a/sourcemod/scripting/gokz-replays/replay_cache.sp b/sourcemod/scripting/gokz-replays/replay_cache.sp
deleted file mode 100644
index 83f36d0..0000000
--- a/sourcemod/scripting/gokz-replays/replay_cache.sp
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- Cached info about the map's available replay bots stored in an ArrayList.
-*/
-
-
-
-// =====[ PUBLIC ]=====
-
-// Adds a replay to the cache
-void AddToReplayInfoCache(int course, int mode, int style, int timeType)
-{
- int index = g_ReplayInfoCache.Length;
- g_ReplayInfoCache.Resize(index + 1);
- g_ReplayInfoCache.Set(index, course, 0);
- g_ReplayInfoCache.Set(index, mode, 1);
- g_ReplayInfoCache.Set(index, style, 2);
- g_ReplayInfoCache.Set(index, timeType, 3);
-}
-
-// Use this to sort the cache after finished adding to it
-void SortReplayInfoCache()
-{
- g_ReplayInfoCache.SortCustom(SortFunc_ReplayInfoCache);
-}
-
-public int SortFunc_ReplayInfoCache(int index1, int index2, Handle array, Handle hndl)
-{
- // Do not expect any indexes to be 'equal'
- int replayInfo1[RP_CACHE_BLOCKSIZE], replayInfo2[RP_CACHE_BLOCKSIZE];
- g_ReplayInfoCache.GetArray(index1, replayInfo1);
- g_ReplayInfoCache.GetArray(index2, replayInfo2);
-
- // Compare courses - lower course number goes first
- if (replayInfo1[0] < replayInfo2[0])
- {
- return -1;
- }
- else if (replayInfo1[0] > replayInfo2[0])
- {
- return 1;
- }
- // Same course, so compare mode
- else if (replayInfo1[1] < replayInfo2[1])
- {
- return -1;
- }
- else if (replayInfo1[1] > replayInfo2[1])
- {
- return 1;
- }
- // Same course and mode, so compare style
- else if (replayInfo1[2] < replayInfo2[2])
- {
- return -1;
- }
- else if (replayInfo1[2] > replayInfo2[2])
- {
- return 1;
- }
- // Same course, mode and style so compare time type, assuming can't be identical
- else if (replayInfo1[3] == TimeType_Pro)
- {
- return 1;
- }
- return -1;
-}
-
-
-
-// =====[ EVENTS ]=====
-
-void OnMapStart_ReplayCache()
-{
- if (g_ReplayInfoCache == null)
- {
- g_ReplayInfoCache = new ArrayList(RP_CACHE_BLOCKSIZE, 0);
- }
- else
- {
- g_ReplayInfoCache.Clear();
- }
-
- char path[PLATFORM_MAX_PATH];
- BuildPath(Path_SM, path, sizeof(path), "%s/%s", RP_DIRECTORY_RUNS, gC_CurrentMap);
- DirectoryListing dir = OpenDirectory(path);
-
- // We want to find files that look like "0_KZT_NRM_PRO.rec"
- char file[PLATFORM_MAX_PATH], pieces[4][16];
- int length, dotpos, course, mode, style, timeType;
-
- while (dir.GetNext(file, sizeof(file)))
- {
- // Some credit to Influx Timer - https://github.com/TotallyMehis/Influx-Timer
-
- // Check file extension
- length = strlen(file);
- dotpos = 0;
- for (int i = 0; i < length; i++)
- {
- if (file[i] == '.')
- {
- dotpos = i;
- }
- }
- if (!StrEqual(file[dotpos + 1], RP_FILE_EXTENSION, false))
- {
- continue;
- }
-
- // Remove file extension
- Format(file, dotpos + 1, file);
-
- // Break down file name into pieces
- if (ExplodeString(file, "_", pieces, sizeof(pieces), sizeof(pieces[])) != sizeof(pieces))
- {
- continue;
- }
-
- // Extract info from the pieces
- course = StringToInt(pieces[0]);
- mode = GetModeIDFromString(pieces[1]);
- style = GetStyleIDFromString(pieces[2]);
- timeType = GetTimeTypeIDFromString(pieces[3]);
- if (!GOKZ_IsValidCourse(course) || mode == -1 || style == -1 || timeType == -1)
- {
- continue;
- }
-
- // Add it to the cache
- AddToReplayInfoCache(course, mode, style, timeType);
- }
-
- SortReplayInfoCache();
-
- delete dir;
-}
-
-
-
-// =====[ PRIVATE ]=====
-
-static int GetModeIDFromString(const char[] mode)
-{
- for (int modeID = 0; modeID < MODE_COUNT; modeID++)
- {
- if (StrEqual(mode, gC_ModeNamesShort[modeID], false))
- {
- return modeID;
- }
- }
- return -1;
-}
-
-static int GetStyleIDFromString(const char[] style)
-{
- for (int styleID = 0; styleID < STYLE_COUNT; styleID++)
- {
- if (StrEqual(style, gC_StyleNamesShort[styleID], false))
- {
- return styleID;
- }
- }
- return -1;
-}
-
-static int GetTimeTypeIDFromString(const char[] timeType)
-{
- for (int timeTypeID = 0; timeTypeID < TIMETYPE_COUNT; timeTypeID++)
- {
- if (StrEqual(timeType, gC_TimeTypeNames[timeTypeID], false))
- {
- return timeTypeID;
- }
- }
- return -1;
-} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-replays/replay_menu.sp b/sourcemod/scripting/gokz-replays/replay_menu.sp
deleted file mode 100644
index 94acd66..0000000
--- a/sourcemod/scripting/gokz-replays/replay_menu.sp
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- Lets player select a replay bot to play back.
-*/
-
-
-
-static int selectedReplayMode[MAXPLAYERS + 1];
-
-
-
-// =====[ PUBLIC ]=====
-
-void DisplayReplayModeMenu(int client)
-{
- if (g_ReplayInfoCache.Length == 0)
- {
- GOKZ_PrintToChat(client, true, "%t", "No Replays Found (Map)");
- GOKZ_PlayErrorSound(client);
- return;
- }
-
- Menu menu = new Menu(MenuHandler_ReplayMode);
- menu.SetTitle("%T", "Replay Menu (Mode) - Title", client, gC_CurrentMap);
- GOKZ_MenuAddModeItems(client, menu, false);
- menu.Display(client, MENU_TIME_FOREVER);
-}
-
-
-
-// =====[ EVENTS ]=====
-
-public int MenuHandler_ReplayMode(Menu menu, MenuAction action, int param1, int param2)
-{
- if (action == MenuAction_Select)
- {
- selectedReplayMode[param1] = param2;
- DisplayReplayMenu(param1);
- }
- else if (action == MenuAction_End)
- {
- delete menu;
- }
- return 0;
-}
-
-public int MenuHandler_Replay(Menu menu, MenuAction action, int param1, int param2)
-{
- if (action == MenuAction_Select)
- {
- char info[4];
- menu.GetItem(param2, info, sizeof(info));
- int replayIndex = StringToInt(info);
- int replayInfo[RP_CACHE_BLOCKSIZE];
- g_ReplayInfoCache.GetArray(replayIndex, replayInfo);
-
- char path[PLATFORM_MAX_PATH];
- BuildPath(Path_SM, path, sizeof(path),
- "%s/%s/%d_%s_%s_%s.%s",
- RP_DIRECTORY_RUNS, gC_CurrentMap, replayInfo[0], gC_ModeNamesShort[replayInfo[1]], gC_StyleNamesShort[replayInfo[2]], gC_TimeTypeNames[replayInfo[3]], RP_FILE_EXTENSION);
- if (!FileExists(path))
- {
- BuildPath(Path_SM, path, sizeof(path),
- "%s/%d_%s_%s_%s.%s",
- RP_DIRECTORY, gC_CurrentMap, replayInfo[0], gC_ModeNamesShort[replayInfo[1]], gC_StyleNamesShort[replayInfo[2]], gC_TimeTypeNames[replayInfo[3]], RP_FILE_EXTENSION);
- if (!FileExists(path))
- {
- LogError("Failed to load file: \"%s\".", path);
- GOKZ_PrintToChat(param1, true, "%t", "Replay Menu - No File");
- return 0;
- }
- }
-
- LoadReplayBot(param1, path);
- }
- else if (action == MenuAction_Cancel)
- {
- DisplayReplayModeMenu(param1);
- }
- else if (action == MenuAction_End)
- {
- delete menu;
- }
- return 0;
-}
-
-
-
-// =====[ PRIVATE ]=====
-
-static void DisplayReplayMenu(int client)
-{
- Menu menu = new Menu(MenuHandler_Replay);
- menu.SetTitle("%T", "Replay Menu - Title", client, gC_CurrentMap, gC_ModeNames[selectedReplayMode[client]]);
- if (ReplayMenuAddItems(client, menu) > 0)
- {
- menu.Display(client, MENU_TIME_FOREVER);
- }
- else
- {
- GOKZ_PrintToChat(client, true, "%t", "No Replays Found (Mode)", gC_ModeNames[selectedReplayMode[client]]);
- GOKZ_PlayErrorSound(client);
- DisplayReplayModeMenu(client);
- }
-}
-
-// Returns the number of replay menu items added
-static int ReplayMenuAddItems(int client, Menu menu)
-{
- int replaysAdded = 0;
- int replayCount = g_ReplayInfoCache.Length;
- int replayInfo[RP_CACHE_BLOCKSIZE];
- char temp[32], indexString[4];
-
- menu.RemoveAllItems();
-
- for (int i = 0; i < replayCount; i++)
- {
- IntToString(i, indexString, sizeof(indexString));
- g_ReplayInfoCache.GetArray(i, replayInfo);
- if (replayInfo[1] != selectedReplayMode[client]) // Wrong mode!
- {
- continue;
- }
-
- if (replayInfo[0] == 0)
- {
- FormatEx(temp, sizeof(temp), "Main %s", gC_TimeTypeNames[replayInfo[3]]);
- }
- else
- {
- FormatEx(temp, sizeof(temp), "Bonus %d %s", replayInfo[0], gC_TimeTypeNames[replayInfo[3]]);
- }
- menu.AddItem(indexString, temp, ITEMDRAW_DEFAULT);
-
- replaysAdded++;
- }
-
- return replaysAdded;
-} \ No newline at end of file