diff options
Diffstat (limited to 'sourcemod/scripting/gokz-replays')
| -rw-r--r-- | sourcemod/scripting/gokz-replays/api.sp | 78 | ||||
| -rw-r--r-- | sourcemod/scripting/gokz-replays/commands.sp | 55 | ||||
| -rw-r--r-- | sourcemod/scripting/gokz-replays/controls.sp | 224 | ||||
| -rw-r--r-- | sourcemod/scripting/gokz-replays/nav.sp | 97 | ||||
| -rw-r--r-- | sourcemod/scripting/gokz-replays/playback.sp | 1501 | ||||
| -rw-r--r-- | sourcemod/scripting/gokz-replays/recording.sp | 990 | ||||
| -rw-r--r-- | sourcemod/scripting/gokz-replays/replay_cache.sp | 176 | ||||
| -rw-r--r-- | sourcemod/scripting/gokz-replays/replay_menu.sp | 139 |
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 |
