bhoptimer/addons/sourcemod/scripting/shavit-replay.sp
2018-05-02 10:30:44 +03:00

2349 lines
55 KiB
SourcePawn

/*
* shavit's Timer - Replay Bot
* by: shavit
*
* This file is part of shavit's Timer.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#undef REQUIRE_PLUGIN
#include <shavit>
#include <adminmenu>
#undef REQUIRE_EXTENSIONS
#include <cstrike>
#include <tf2>
#define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}"
#define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}"
#define REPLAY_FORMAT_SUBVERSION 0x02
#define CELLS_PER_FRAME 8 // origin[3], angles[2], buttons, flags, movetype
// #define DEBUG
#pragma newdecls required
#pragma semicolon 1
#pragma dynamic 131072
enum
{
iCentralClient,
iCentralStyle,
iCentralReplayStatus,
iCentralTrack,
CENTRALBOTCACHE_SIZE
};
enum
{
sReplayClanTag,
sReplayNameStyle,
sReplayCentralName,
sReplayCentralStyle,
sReplayCentralStyleTag,
sReplayUnloaded,
REPLAYSTRINGS_SIZE
};
enum
{
iBotShooting_Attack1 = (1 << 0),
iBotShooting_Attack2 = (1 << 1)
}
// game type
EngineVersion gEV_Type = Engine_Unknown;
// cache
char gS_ReplayFolder[PLATFORM_MAX_PATH];
int gI_ReplayTick[STYLE_LIMIT];
int gI_ReplayBotClient[STYLE_LIMIT];
ArrayList gA_Frames[STYLE_LIMIT][TRACKS_SIZE];
float gF_StartTick[STYLE_LIMIT];
ReplayStatus gRS_ReplayStatus[STYLE_LIMIT];
any gA_FrameCache[STYLE_LIMIT][TRACKS_SIZE][4]; // int frame_count, float time, bool new_format, int replay_version
char gS_ReplayNames[STYLE_LIMIT][TRACKS_SIZE][MAX_NAME_LENGTH];
bool gB_ForciblyStopped = false;
bool gB_Button[MAXPLAYERS+1];
int gI_PlayerFrames[MAXPLAYERS+1];
ArrayList gA_PlayerFrames[MAXPLAYERS+1];
bool gB_Record[MAXPLAYERS+1];
int gI_Track[MAXPLAYERS+1];
bool gB_Late = false;
// server specific
float gF_Tickrate = 0.0;
char gS_Map[160];
int gI_ExpectedBots = 0;
ConVar bot_quota = null;
any gA_CentralCache[CENTRALBOTCACHE_SIZE];
// how do i call this
bool gB_HideNameChange = false;
bool gB_DontCallTimer = false;
bool gB_HijackFrame[MAXPLAYERS+1];
float gF_HijackedAngles[MAXPLAYERS+1][2];
// plugin cvars
ConVar gCV_Enabled = null;
ConVar gCV_ReplayDelay = null;
ConVar gCV_TimeLimit = null;
ConVar gCV_DefaultTeam = null;
ConVar gCV_CentralBot = null;
ConVar gCV_BotShooting = null;
// cached cvars
bool gB_Enabled = true;
float gF_ReplayDelay = 5.0;
float gF_TimeLimit = 5400.0;
int gI_DefaultTeam = 3;
bool gB_CentralBot = true;
int gI_BotShooting = 3;
// timer settings
int gI_Styles = 0;
char gS_StyleStrings[STYLE_LIMIT][STYLESTRINGS_SIZE][128];
any gA_StyleSettings[STYLE_LIMIT][STYLESETTINGS_SIZE];
// chat settings
char gS_ChatStrings[CHATSETTINGS_SIZE][128];
// replay settings
char gS_ReplayStrings[REPLAYSTRINGS_SIZE][MAX_NAME_LENGTH];
// admin menu
TopMenu gH_AdminMenu = null;
TopMenuObject gH_TimerCommands = INVALID_TOPMENUOBJECT;
// database related things
Database gH_SQL = null;
char gS_MySQLPrefix[32];
public Plugin myinfo =
{
name = "[shavit] Replay Bot",
author = "shavit",
description = "A replay bot for shavit's bhop timer.",
version = SHAVIT_VERSION,
url = "https://github.com/shavitush/bhoptimer"
}
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
CreateNative("Shavit_GetReplayBotCurrentFrame", Native_GetReplayBotIndex);
CreateNative("Shavit_GetReplayBotFirstFrame", Native_GetReplayBotFirstFrame);
CreateNative("Shavit_GetReplayBotIndex", Native_GetReplayBotIndex);
CreateNative("Shavit_GetReplayBotStyle", Native_GetReplayBotStyle);
CreateNative("Shavit_GetReplayBotTrack", Native_GetReplayBotTrack);
CreateNative("Shavit_GetReplayBotType", Native_GetReplayBotType);
CreateNative("Shavit_GetReplayData", Native_GetReplayData);
CreateNative("Shavit_GetReplayFrameCount", Native_GetReplayFrameCount);
CreateNative("Shavit_GetReplayLength", Native_GetReplayLength);
CreateNative("Shavit_GetReplayName", Native_GetReplayName);
CreateNative("Shavit_GetReplayTime", Native_GetReplayTime);
CreateNative("Shavit_HijackAngles", Native_HijackAngles);
CreateNative("Shavit_IsReplayDataLoaded", Native_IsReplayDataLoaded);
CreateNative("Shavit_ReloadReplay", Native_ReloadReplay);
CreateNative("Shavit_ReloadReplays", Native_ReloadReplays);
CreateNative("Shavit_SetReplayData", Native_SetReplayData);
// registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins
RegPluginLibrary("shavit-replay");
gB_Late = late;
return APLRes_Success;
}
public void OnAllPluginsLoaded()
{
if(!LibraryExists("shavit-wr"))
{
SetFailState("shavit-wr is required for the plugin to work.");
}
if(gH_SQL == null)
{
Shavit_OnDatabaseLoaded();
}
}
public void OnPluginStart()
{
LoadTranslations("shavit-common.phrases");
LoadTranslations("shavit-replay.phrases");
// game specific
gEV_Type = GetEngineVersion();
gF_Tickrate = (1.0 / GetTickInterval());
// late load
for(int i = 1; i <= MaxClients; i++)
{
if(IsValidClient(i) && !IsFakeClient(i))
{
OnClientPutInServer(i);
}
}
// plugin convars
gCV_Enabled = CreateConVar("shavit_replay_enabled", "1", "Enable replay bot functionality?", 0, true, 0.0, true, 1.0);
gCV_ReplayDelay = CreateConVar("shavit_replay_delay", "5.0", "Time to wait before restarting the replay after it finishes playing.", 0, true, 0.0, true, 10.0);
gCV_TimeLimit = CreateConVar("shavit_replay_timelimit", "5400.0", "Maximum amount of time (in seconds) to allow saving to disk.\nDefault is 5400.0 (1:30 hours)\n0 - Disabled");
gCV_DefaultTeam = CreateConVar("shavit_replay_defaultteam", "3", "Default team to make the bots join, if possible.\n2 - Terrorists/RED\n3 - Counter Terrorists/BLU", 0, true, 2.0, true, 3.0);
gCV_CentralBot = CreateConVar("shavit_replay_centralbot", "1", "Have one central bot instead of one bot per replay.\nTriggered with !replay.\nRestart the map for changes to take effect.\nThe disabled setting is not supported - use at your own risk.\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0);
gCV_BotShooting = CreateConVar("shavit_replay_botshooting", "3", "Attacking buttons to allow for bots.\n0 - none\1 - +attack\n2 - +attack2\n3 - both", 0, true, 0.0, true, 3.0);
gCV_Enabled.AddChangeHook(OnConVarChanged);
gCV_ReplayDelay.AddChangeHook(OnConVarChanged);
gCV_TimeLimit.AddChangeHook(OnConVarChanged);
gCV_DefaultTeam.AddChangeHook(OnConVarChanged);
gCV_CentralBot.AddChangeHook(OnConVarChanged);
gCV_BotShooting.AddChangeHook(OnConVarChanged);
AutoExecConfig();
// admin menu
if(LibraryExists("adminmenu") && ((gH_AdminMenu = GetAdminTopMenu()) != null))
{
OnAdminMenuReady(gH_AdminMenu);
}
// hooks
HookEvent("player_spawn", Player_Event, EventHookMode_Pre);
HookEvent("player_death", Player_Event, EventHookMode_Pre);
HookEvent("player_connect", BotEvents, EventHookMode_Pre);
HookEvent("player_disconnect", BotEvents, EventHookMode_Pre);
HookEventEx("player_connect_client", BotEvents, EventHookMode_Pre);
// name change suppression
HookUserMessage(GetUserMessageId("SayText2"), Hook_SayText2, true);
// commands
RegAdminCmd("sm_deletereplay", Command_DeleteReplay, ADMFLAG_RCON, "Open replay deletion menu.");
RegConsoleCmd("sm_replay", Command_Replay, "Opens the central bot menu. For admins: 'sm_replay stop' to stop the playback.");
// database
SQL_SetPrefix();
}
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
gB_Enabled = gCV_Enabled.BoolValue;
gF_ReplayDelay = gCV_ReplayDelay.FloatValue;
gF_TimeLimit = gCV_TimeLimit.FloatValue;
gI_DefaultTeam = gCV_DefaultTeam.IntValue;
gB_CentralBot = gCV_CentralBot.BoolValue;
gI_BotShooting = gCV_BotShooting.IntValue;
if(convar == gCV_CentralBot)
{
OnMapStart();
}
}
public void OnAdminMenuCreated(Handle topmenu)
{
if(gH_AdminMenu == null || (topmenu == gH_AdminMenu && gH_TimerCommands != INVALID_TOPMENUOBJECT))
{
return;
}
gH_TimerCommands = gH_AdminMenu.AddCategory("Timer Commands", CategoryHandler, "shavit_admin", ADMFLAG_RCON);
}
public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength)
{
if(action == TopMenuAction_DisplayTitle)
{
FormatEx(buffer, maxlength, "%T:", "TimerCommands", param);
}
else if(action == TopMenuAction_DisplayOption)
{
FormatEx(buffer, maxlength, "%T", "TimerCommands", param);
}
}
public void OnAdminMenuReady(Handle topmenu)
{
if((gH_AdminMenu = GetAdminTopMenu()) != null)
{
if(gH_TimerCommands == INVALID_TOPMENUOBJECT)
{
gH_TimerCommands = gH_AdminMenu.FindCategory("Timer Commands");
if(gH_TimerCommands == INVALID_TOPMENUOBJECT)
{
OnAdminMenuCreated(topmenu);
}
}
gH_AdminMenu.AddItem("sm_deletereplay", AdminMenu_DeleteReplay, gH_TimerCommands, "sm_deletereplay", ADMFLAG_RCON);
}
}
public void AdminMenu_DeleteReplay(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength)
{
if(action == TopMenuAction_DisplayOption)
{
FormatEx(buffer, maxlength, "%t", "DeleteReplayAdminMenu");
}
else if(action == TopMenuAction_SelectOption)
{
Command_DeleteReplay(param, 0);
}
}
public int Native_GetReplayBotFirstFrame(Handle handler, int numParams)
{
SetNativeCellRef(2, gF_StartTick[GetNativeCell(1)]);
}
public int Native_GetReplayBotCurrentFrame(Handle handler, int numParams)
{
return gI_ReplayTick[GetNativeCell(1)];
}
public int Native_GetReplayBotIndex(Handle handler, int numParams)
{
if(gB_CentralBot)
{
return gA_CentralCache[iCentralClient];
}
return gI_ReplayBotClient[GetNativeCell(1)];
}
public int Native_IsReplayDataLoaded(Handle handler, int numParams)
{
int style = GetNativeCell(1);
int track = GetNativeCell(2);
if(gB_CentralBot)
{
return (gA_CentralCache[iCentralClient] != -1 && gA_CentralCache[iCentralClient] != Replay_Idle && view_as<int>(gA_FrameCache[style][track][0]) > 0);
}
return view_as<int>(ReplayEnabled(style) && gA_FrameCache[style][Track_Main][0] > 0);
}
public int Native_ReloadReplay(Handle handler, int numParams)
{
int style = GetNativeCell(1);
gI_ReplayTick[style] = -1;
gF_StartTick[style] = -65535.0;
gRS_ReplayStatus[style] = Replay_Idle;
int track = GetNativeCell(2);
bool restart = view_as<bool>(GetNativeCell(3));
char[] path = new char[PLATFORM_MAX_PATH];
GetNativeString(4, path, PLATFORM_MAX_PATH);
delete gA_Frames[style][track];
gA_Frames[style][track] = new ArrayList(CELLS_PER_FRAME);
gA_FrameCache[style][track][0] = 0;
gA_FrameCache[style][track][1] = 0.0;
gA_FrameCache[style][track][2] = false;
strcopy(gS_ReplayNames[style][track], MAX_NAME_LENGTH, "invalid");
bool loaded = false;
if(strlen(path) > 0)
{
loaded = LoadReplay(style, track, path);
}
else
{
loaded = DefaultLoadReplay(style, track);
}
if(gB_CentralBot)
{
if(gA_CentralCache[iCentralStyle] == style && gA_CentralCache[iCentralTrack] == track)
{
StopCentralReplay(0);
}
}
else
{
if(gI_ReplayBotClient[style] == 0)
{
ServerCommand((gEV_Type != Engine_TF2)? "bot_add":"tf_bot_add");
gI_ExpectedBots++;
}
if(loaded && restart)
{
gI_ReplayTick[style] = 0;
gRS_ReplayStatus[style] = Replay_Start;
CreateTimer((gF_ReplayDelay / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE);
}
}
return loaded;
}
public int Native_ReloadReplays(Handle handler, int numParams)
{
bool restart = view_as<bool>(GetNativeCell(1));
int loaded = 0;
for(int i = 0; i < gI_Styles; i++)
{
if(!ReplayEnabled(i))
{
continue;
}
for(int j = 0; j < ((gB_CentralBot)? TRACKS_SIZE:1); j++)
{
if(Shavit_ReloadReplay(i, j, restart))
{
loaded++;
}
}
}
return loaded;
}
public int Native_SetReplayData(Handle handler, int numParams)
{
int client = GetNativeCell(1);
ArrayList frames = view_as<ArrayList>(CloneHandle(GetNativeCell(2), handler));
delete gA_PlayerFrames[client];
gA_PlayerFrames[client] = frames.Clone();
delete frames;
gI_PlayerFrames[client] = gA_PlayerFrames[client].Length;
if(gI_PlayerFrames[client] > 0)
{
gB_Record[client] = true;
}
}
public int Native_GetReplayData(Handle handler, int numParams)
{
int client = GetNativeCell(1);
ArrayList frames = null;
if(gA_PlayerFrames[client] != null)
{
ArrayList temp = gA_PlayerFrames[client].Clone();
frames = view_as<ArrayList>(CloneHandle(temp, handler));
delete temp;
}
return view_as<int>(frames);
}
public int Native_GetReplayFrameCount(Handle handler, int numParams)
{
return view_as<int>(gA_FrameCache[GetNativeCell(1)][GetNativeCell(2)][0]);
}
public int Native_GetReplayLength(Handle handler, int numParams)
{
return view_as<int>(GetReplayLength(GetNativeCell(1), GetNativeCell(2)));
}
public int Native_GetReplayName(Handle handler, int numParams)
{
return SetNativeString(3, gS_ReplayNames[GetNativeCell(1)][GetNativeCell(2)], GetNativeCell(4));
}
public int Native_GetReplayTime(Handle handler, int numParams)
{
int style = GetNativeCell(1);
int track = GetNativeCell(2);
if(style < 0 || track < 0)
{
return view_as<int>(0.0);
}
if(gB_CentralBot)
{
if(gA_CentralCache[iCentralReplayStatus] == Replay_End)
{
return view_as<int>(GetReplayLength(style, track));
}
}
else if(gRS_ReplayStatus[style] == Replay_End)
{
return view_as<int>(GetReplayLength(Track_Main, track));
}
return view_as<int>(float(gI_ReplayTick[style]) / gF_Tickrate);
}
public int Native_HijackAngles(Handle handler, int numParams)
{
int client = GetNativeCell(1);
gB_HijackFrame[client] = true;
gF_HijackedAngles[client][0] = view_as<float>(GetNativeCell(2));
gF_HijackedAngles[client][1] = view_as<float>(GetNativeCell(3));
}
public int Native_GetReplayBotStyle(Handle handler, int numParams)
{
return (gB_CentralBot && gA_CentralCache[iCentralReplayStatus] == Replay_Idle)? -1:GetReplayStyle(GetNativeCell(1));
}
public int Native_GetReplayBotTrack(Handle handler, int numParams)
{
return GetReplayTrack(GetNativeCell(1));
}
public int Native_GetReplayBotType(Handle handler, int numParams)
{
return view_as<int>((gB_CentralBot)? Replay_Central:Replay_Legacy);
}
public void Shavit_OnDatabaseLoaded()
{
gH_SQL = Shavit_GetDatabase();
SetSQLInfo();
}
public Action CheckForSQLInfo(Handle Timer)
{
return SetSQLInfo();
}
Action SetSQLInfo()
{
if(gH_SQL == null)
{
gH_SQL = Shavit_GetDatabase();
CreateTimer(0.5, CheckForSQLInfo);
}
else
{
return Plugin_Stop;
}
return Plugin_Continue;
}
void SQL_SetPrefix()
{
char[] sFile = new char[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sFile, PLATFORM_MAX_PATH, "configs/shavit-prefix.txt");
File fFile = OpenFile(sFile, "r");
if(fFile == null)
{
SetFailState("Cannot open \"configs/shavit-prefix.txt\". Make sure this file exists and that the server has read permissions to it.");
}
char[] sLine = new char[PLATFORM_MAX_PATH*2];
while(fFile.ReadLine(sLine, PLATFORM_MAX_PATH*2))
{
TrimString(sLine);
strcopy(gS_MySQLPrefix, 32, sLine);
break;
}
delete fFile;
}
public Action Cron(Handle Timer)
{
if(!gB_Enabled)
{
bot_quota.IntValue = 0;
return Plugin_Continue;
}
// make sure there are enough bots
else if(bot_quota != null && bot_quota.IntValue != gI_ExpectedBots)
{
bot_quota.IntValue = gI_ExpectedBots;
}
for(int i = 0; i < gI_Styles; i++)
{
for(int j = 0; j < ((gB_CentralBot)? TRACKS_SIZE:1); j++)
{
if(!gB_CentralBot && gI_ReplayBotClient[i] != 0)
{
UpdateReplayInfo(gI_ReplayBotClient[i], i, GetReplayLength(i, j), j);
}
}
}
if(gB_CentralBot && gA_CentralCache[iCentralClient] != -1)
{
if(gA_CentralCache[iCentralStyle] != -1)
{
UpdateReplayInfo(gA_CentralCache[iCentralClient], gA_CentralCache[iCentralStyle], -1.0, gA_CentralCache[iCentralTrack]);
}
else
{
UpdateReplayInfo(gA_CentralCache[iCentralClient], 0, 0.0, 0);
}
}
return Plugin_Continue;
}
bool LoadStyling()
{
char[] sPath = new char[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sPath, PLATFORM_MAX_PATH, "configs/shavit-replay.cfg");
KeyValues kv = new KeyValues("shavit-replay");
if(!kv.ImportFromFile(sPath))
{
delete kv;
return false;
}
kv.GetString("clantag", gS_ReplayStrings[sReplayClanTag], MAX_NAME_LENGTH, "<EMPTY CLANTAG>");
kv.GetString("namestyle", gS_ReplayStrings[sReplayNameStyle], MAX_NAME_LENGTH, "<EMPTY NAMESTYLE>");
kv.GetString("centralname", gS_ReplayStrings[sReplayCentralName], MAX_NAME_LENGTH, "<EMPTY CENTRALNAME>");
kv.GetString("centralstyle", gS_ReplayStrings[sReplayCentralStyle], MAX_NAME_LENGTH, "<EMPTY CENTRALSTYLE>");
kv.GetString("centralstyletag", gS_ReplayStrings[sReplayCentralStyleTag], MAX_NAME_LENGTH, "<EMPTY CENTRALSTYLETAG>");
kv.GetString("unloaded", gS_ReplayStrings[sReplayUnloaded], MAX_NAME_LENGTH, "<EMPTY UNLOADED>");
char[] sFolder = new char[PLATFORM_MAX_PATH];
kv.GetString("replayfolder", sFolder, PLATFORM_MAX_PATH, "{SM}/data/replaybot");
delete kv;
if(StrContains(sFolder, "{SM}") != -1)
{
ReplaceString(sFolder, PLATFORM_MAX_PATH, "{SM}/", "");
BuildPath(Path_SM, sFolder, PLATFORM_MAX_PATH, "%s", sFolder);
}
strcopy(gS_ReplayFolder, PLATFORM_MAX_PATH, sFolder);
return true;
}
public void OnMapStart()
{
if(!LoadStyling())
{
SetFailState("Could not load the replay bots' configuration file. Make sure it exists (addons/sourcemod/configs/shavit-replay.cfg) and follows the proper syntax!");
}
if(gB_Late)
{
Shavit_OnStyleConfigLoaded(-1);
Shavit_OnChatConfigLoaded();
}
gA_CentralCache[iCentralClient] = -1;
gA_CentralCache[iCentralStyle] = -1;
gA_CentralCache[iCentralReplayStatus] = Replay_Idle;
gA_CentralCache[iCentralTrack] = Track_Main;
gB_ForciblyStopped = false;
GetCurrentMap(gS_Map, 160);
GetMapDisplayName(gS_Map, gS_Map, 160);
if(!gB_Enabled)
{
return;
}
bot_quota = FindConVar((gEV_Type != Engine_TF2)? "bot_quota":"tf_bot_quota");
if(bot_quota != null)
{
bot_quota.Flags &= ~FCVAR_NOTIFY;
}
char[] sTempMap = new char[PLATFORM_MAX_PATH];
FormatEx(sTempMap, PLATFORM_MAX_PATH, "maps/%s.nav", gS_Map);
if(!FileExists(sTempMap))
{
if(!FileExists("maps/base.nav"))
{
SetFailState("Plugin startup FAILED: \"maps/base.nav\" does not exist.");
}
File_Copy("maps/base.nav", sTempMap);
ForceChangeLevel(gS_Map, ".nav file generate");
return;
}
ConVar bot_controllable = FindConVar("bot_controllable");
if(bot_controllable != null)
{
bot_controllable.BoolValue = false;
delete bot_controllable;
}
ConVar bot_stop = FindConVar("bot_stop");
if(bot_stop != null)
{
bot_stop.BoolValue = true;
delete bot_stop;
}
ConVar bot_quota_mode = FindConVar((gEV_Type != Engine_TF2)? "bot_quota_mode":"tf_bot_quota_mode");
if(bot_quota_mode != null)
{
bot_quota_mode.SetString("normal");
delete bot_quota_mode;
}
ConVar mp_limitteams = FindConVar("mp_limitteams");
if(mp_limitteams != null)
{
mp_limitteams.IntValue = 0;
delete mp_limitteams;
}
ConVar bot_join_after_player = FindConVar((gEV_Type != Engine_TF2)? "bot_join_after_player":"tf_bot_join_after_player");
if(bot_join_after_player != null)
{
bot_join_after_player.BoolValue = false;
delete bot_join_after_player;
}
ConVar bot_chatter = FindConVar("bot_chatter");
if(bot_chatter != null)
{
bot_chatter.SetString("off");
delete bot_chatter;
}
ConVar bot_zombie = FindConVar("bot_zombie");
if(bot_zombie != null)
{
bot_zombie.BoolValue = true;
delete bot_zombie;
}
ConVar mp_autoteambalance = FindConVar("mp_autoteambalance");
mp_autoteambalance.BoolValue = false;
delete mp_autoteambalance;
ServerCommand((gEV_Type != Engine_TF2)? "bot_kick":"tf_bot_kick all");
gI_ExpectedBots = 0;
if(!DirExists(gS_ReplayFolder))
{
CreateDirectory(gS_ReplayFolder, 511);
}
for(int i = 0; i < gI_Styles; i++)
{
gI_ReplayTick[i] = -1;
gF_StartTick[i] = -65535.0;
gRS_ReplayStatus[i] = Replay_Idle;
if(!ReplayEnabled(i))
{
continue;
}
char[] sPath = new char[PLATFORM_MAX_PATH];
FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d", gS_ReplayFolder, i);
if(!DirExists(sPath))
{
CreateDirectory(sPath, 511);
}
bool loaded = false;
for(int j = 0; j < ((gB_CentralBot)? TRACKS_SIZE:1); j++)
{
delete gA_Frames[i][j];
gA_Frames[i][j] = new ArrayList(CELLS_PER_FRAME);
gA_FrameCache[i][j][0] = 0;
gA_FrameCache[i][j][1] = 0.0;
gA_FrameCache[i][j][2] = false;
strcopy(gS_ReplayNames[i][j], MAX_NAME_LENGTH, "invalid");
loaded = DefaultLoadReplay(i, j);
}
if(!gB_CentralBot)
{
ServerCommand((gEV_Type != Engine_TF2)? "bot_add":"tf_bot_add");
gI_ExpectedBots++;
if(loaded)
{
gI_ReplayTick[i] = 0;
gRS_ReplayStatus[i] = Replay_Start;
CreateTimer((gF_ReplayDelay / 2.0), Timer_StartReplay, i, TIMER_FLAG_NO_MAPCHANGE);
}
}
}
if(gB_CentralBot)
{
gI_ExpectedBots = 1;
ServerCommand((gEV_Type != Engine_TF2)? "bot_add":"tf_bot_add");
}
CreateTimer(3.0, Cron, INVALID_HANDLE, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
}
public void Shavit_OnStyleConfigLoaded(int styles)
{
if(styles == -1)
{
styles = Shavit_GetStyleCount();
}
for(int i = 0; i < styles; i++)
{
Shavit_GetStyleSettings(i, gA_StyleSettings[i]);
Shavit_GetStyleStrings(i, sStyleName, gS_StyleStrings[i][sStyleName], 128);
Shavit_GetStyleStrings(i, sShortName, gS_StyleStrings[i][sShortName], 128);
}
gI_Styles = styles;
}
public void Shavit_OnChatConfigLoaded()
{
for(int i = 0; i < CHATSETTINGS_SIZE; i++)
{
Shavit_GetChatStrings(i, gS_ChatStrings[i], 128);
}
}
bool DefaultLoadReplay(int style, int track)
{
char[] sTrack = new char[4];
FormatEx(sTrack, 4, "_%d", track);
char[] sPath = new char[PLATFORM_MAX_PATH];
FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d/%s%s.replay", gS_ReplayFolder, style, gS_Map, (track > 0)? sTrack:"");
return LoadReplay(style, track, sPath);
}
bool LoadReplay(int style, int track, const char[] path)
{
if(FileExists(path))
{
File fFile = OpenFile(path, "rb");
char[] sHeader = new char[64];
if(!fFile.ReadLine(sHeader, 64))
{
return false;
}
TrimString(sHeader);
char[][] sExplodedHeader = new char[2][64];
ExplodeString(sHeader, ":", sExplodedHeader, 2, 64);
if(StrEqual(sExplodedHeader[1], REPLAY_FORMAT_FINAL)) // hopefully, the last of them
{
gA_FrameCache[style][track][3] = StringToInt(sExplodedHeader[0]);
int iTemp = 0;
fFile.ReadInt32(iTemp);
gA_FrameCache[style][track][0] = iTemp;
if(gA_Frames[style][track] == null)
{
gA_Frames[style][track] = new ArrayList(CELLS_PER_FRAME);
}
gA_Frames[style][track].Resize(iTemp);
fFile.ReadInt32(iTemp);
gA_FrameCache[style][track][1] = iTemp;
char[] sAuthID = new char[32];
fFile.ReadString(sAuthID, 32);
if(gH_SQL != null)
{
char[] sQuery = new char[192];
FormatEx(sQuery, 192, "SELECT name FROM %susers WHERE auth = '%s';", gS_MySQLPrefix, sAuthID);
DataPack pack = new DataPack();
pack.WriteCell(style);
pack.WriteCell(track);
gH_SQL.Query(SQL_GetUserName_Callback, sQuery, pack, DBPrio_High);
}
int cells = 8;
// backwards compatibility
if(gA_FrameCache[style][track][3] == 0x01)
{
cells = 6;
}
any[] aReplayData = new any[cells];
for(int i = 0; i < gA_FrameCache[style][track][0]; i++)
{
if(fFile.Read(aReplayData, cells, 4) >= 0)
{
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[0]), 0);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[1]), 1);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[2]), 2);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[3]), 3);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[4]), 4);
gA_Frames[style][track].Set(i, view_as<int>(aReplayData[5]), 5);
if(gA_FrameCache[style][track][3] >= 0x02)
{
gA_Frames[style][track].Set(i, view_as<int>(aReplayData[6]), 6);
gA_Frames[style][track].Set(i, view_as<int>(aReplayData[7]), 7);
}
}
}
gA_FrameCache[style][track][2] = true; // not wr-based
}
else if(StrEqual(sExplodedHeader[1], REPLAY_FORMAT_V2))
{
int iReplaySize = gA_FrameCache[style][track][0] = StringToInt(sExplodedHeader[0]);
gA_Frames[style][track].Resize(iReplaySize);
gA_FrameCache[style][track][1] = 0.0; // N/A at this version
any[] aReplayData = new any[6];
for(int i = 0; i < iReplaySize; i++)
{
if(fFile.Read(aReplayData, 6, 4) >= 0)
{
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[0]), 0);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[1]), 1);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[2]), 2);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[3]), 3);
gA_Frames[style][track].Set(i, view_as<float>(aReplayData[4]), 4);
gA_Frames[style][track].Set(i, view_as<int>(aReplayData[5]), 5);
}
}
gA_FrameCache[style][track][2] = false;
strcopy(gS_ReplayNames[style][track], MAX_NAME_LENGTH, "invalid");
}
else // old, outdated and slow - only used for ancient replays
{
char[] sLine = new char[320];
char[][] sExplodedLine = new char[6][64];
for(int i = 0; !fFile.EndOfFile(); i++)
{
fFile.ReadLine(sLine, 320);
int iStrings = ExplodeString(sLine, "|", sExplodedLine, 6, 64);
gA_Frames[style][track].Resize(i + 1);
gA_Frames[style][track].Set(i, StringToFloat(sExplodedLine[0]), 0);
gA_Frames[style][track].Set(i, StringToFloat(sExplodedLine[1]), 1);
gA_Frames[style][track].Set(i, StringToFloat(sExplodedLine[2]), 2);
gA_Frames[style][track].Set(i, StringToFloat(sExplodedLine[3]), 3);
gA_Frames[style][track].Set(i, StringToFloat(sExplodedLine[4]), 4);
gA_Frames[style][track].Set(i, (iStrings == 6)? StringToInt(sExplodedLine[5]):0, 5);
}
gA_FrameCache[style][track][0] = gA_Frames[style][track].Length;
gA_FrameCache[style][track][1] = 0.0; // N/A at this version
gA_FrameCache[style][track][2] = false; // wr-based
strcopy(gS_ReplayNames[style][track], MAX_NAME_LENGTH, "invalid");
}
delete fFile;
return true;
}
return false;
}
bool SaveReplay(int style, int track, float time, char[] authid, char[] name)
{
char[] sTrack = new char[4];
FormatEx(sTrack, 4, "_%d", track);
char[] sPath = new char[PLATFORM_MAX_PATH];
FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d/%s%s.replay", gS_ReplayFolder, style, gS_Map, (track > 0)? sTrack:"");
if(FileExists(sPath))
{
DeleteFile(sPath);
}
File fFile = OpenFile(sPath, "wb");
fFile.WriteLine("%d:" ... REPLAY_FORMAT_FINAL, REPLAY_FORMAT_SUBVERSION);
int iSize = gA_Frames[style][track].Length;
fFile.WriteInt32(iSize);
fFile.WriteInt32(view_as<int>(time));
fFile.WriteString(authid, true);
// if REPLAY_FORMAT_SUBVERSION is over 0x01 i'll add variables here
any[] aFrameData = new any[CELLS_PER_FRAME];
for(int i = 0; i < iSize; i++)
{
gA_Frames[style][track].GetArray(i, aFrameData, CELLS_PER_FRAME);
fFile.Write(aFrameData, CELLS_PER_FRAME, 4);
}
delete fFile;
gA_FrameCache[style][track][0] = iSize;
gA_FrameCache[style][track][1] = time;
gA_FrameCache[style][track][2] = true;
strcopy(gS_ReplayNames[style][track], MAX_NAME_LENGTH, name);
return true;
}
bool DeleteReplay(int style, int track)
{
char[] sTrack = new char[4];
FormatEx(sTrack, 4, "_%d", track);
char[] sPath = new char[PLATFORM_MAX_PATH];
FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d/%s%s.replay", gS_ReplayFolder, style, gS_Map, (track > 0)? sTrack:"");
if(!FileExists(sPath) || !DeleteFile(sPath))
{
return false;
}
if(gB_CentralBot && gA_CentralCache[iCentralStyle] == style && gA_CentralCache[iCentralTrack] == track)
{
StopCentralReplay(0);
}
gA_Frames[style][track].Clear();
gA_FrameCache[style][track][0] = 0;
gA_FrameCache[style][track][1] = 0.0;
gA_FrameCache[style][track][2] = false;
strcopy(gS_ReplayNames[style][track], MAX_NAME_LENGTH, "invalid");
gI_ReplayTick[style] = -1;
if(gI_ReplayBotClient[style] != 0)
{
UpdateReplayInfo(gI_ReplayBotClient[style], style, 0.0, track);
}
return true;
}
public void SQL_GetUserName_Callback(Database db, DBResultSet results, const char[] error, DataPack data)
{
data.Reset();
int style = data.ReadCell();
int track = data.ReadCell();
delete data;
if(results == null)
{
LogError("Timer error! Get user name (replay) failed. Reason: %s", error);
return;
}
if(results.FetchRow())
{
results.FetchString(0, gS_ReplayNames[style][track], MAX_NAME_LENGTH);
}
}
public void OnClientPutInServer(int client)
{
if(IsClientSourceTV(client))
{
return;
}
if(!IsFakeClient(client))
{
delete gA_PlayerFrames[client];
gA_PlayerFrames[client] = new ArrayList(CELLS_PER_FRAME);
}
else
{
if(!gB_CentralBot)
{
for(int i = 0; i < gI_Styles; i++)
{
if(ReplayEnabled(i) && gI_ReplayBotClient[i] == 0)
{
gI_ReplayBotClient[i] = client;
UpdateReplayInfo(client, i, -1.0, Track_Main);
break;
}
}
}
else if(gA_CentralCache[iCentralClient] == -1)
{
UpdateReplayInfo(client, 0, 0.0, Track_Main);
gA_CentralCache[iCentralClient] = client;
}
}
}
void FormatStyle(const char[] source, int style, bool central, float time, int track, char[] dest, int size)
{
float fWRTime = GetReplayLength(style, track);
char[] sTime = new char[16];
FormatSeconds((time == -1.0)? fWRTime:time, sTime, 16);
char[] sName = new char[MAX_NAME_LENGTH];
GetReplayName(style, track, sName, MAX_NAME_LENGTH);
char[] temp = new char[size];
strcopy(temp, size, source);
ReplaceString(temp, size, "{map}", gS_Map);
if(central && gA_CentralCache[iCentralReplayStatus] == Replay_Idle)
{
ReplaceString(temp, size, "{style}", gS_ReplayStrings[sReplayCentralStyle]);
ReplaceString(temp, size, "{styletag}", gS_ReplayStrings[sReplayCentralStyleTag]);
}
else
{
ReplaceString(temp, size, "{style}", gS_StyleStrings[style][sStyleName]);
ReplaceString(temp, size, "{styletag}", gS_StyleStrings[style][sClanTag]);
}
ReplaceString(temp, size, "{time}", sTime);
ReplaceString(temp, size, "{player}", sName);
char[] sTrack = new char[32];
GetTrackName(LANG_SERVER, track, sTrack, 32);
ReplaceString(temp, size, "{track}", sTrack);
strcopy(dest, size, temp);
}
void UpdateReplayInfo(int client, int style, float time, int track)
{
if(!gB_Enabled || !IsValidClient(client) || !IsFakeClient(client))
{
return;
}
SetEntProp(client, Prop_Data, "m_CollisionGroup", 2);
SetEntityMoveType(client, MOVETYPE_NOCLIP);
bool central = (gA_CentralCache[iCentralClient] == client);
bool idle = (central && gA_CentralCache[iCentralReplayStatus] == Replay_Idle);
if(gEV_Type != Engine_TF2)
{
char[] sTag = new char[MAX_NAME_LENGTH];
FormatStyle(gS_ReplayStrings[sReplayClanTag], style, central, time, track, sTag, MAX_NAME_LENGTH);
CS_SetClientClanTag(client, sTag);
}
char[] sName = new char[MAX_NAME_LENGTH];
int iFrameCount = view_as<int>(gA_FrameCache[style][track][0]);
if(central || iFrameCount > 0)
{
FormatStyle(gS_ReplayStrings[(idle)? sReplayCentralName:sReplayNameStyle], style, central, time, track, sName, MAX_NAME_LENGTH);
}
else
{
FormatStyle(gS_ReplayStrings[sReplayUnloaded], style, central, time, track, sName, MAX_NAME_LENGTH);
}
gB_HideNameChange = true;
SetClientName(client, sName);
int iScore = (iFrameCount > 0 || client == gA_CentralCache[iCentralClient])? 2000:-2000;
if(gEV_Type == Engine_CSGO)
{
CS_SetClientContributionScore(client, iScore);
}
else if(gEV_Type == Engine_CSS)
{
SetEntProp(client, Prop_Data, "m_iFrags", iScore);
}
SetEntProp(client, Prop_Data, "m_iDeaths", 0);
gB_DontCallTimer = true;
if(!gB_CentralBot && iFrameCount == 0)
{
if(IsPlayerAlive(client))
{
ForcePlayerSuicide(client);
}
}
else
{
if(!IsPlayerAlive(client))
{
if(gEV_Type == Engine_TF2)
{
TF2_RespawnPlayer(client);
}
else
{
CS_RespawnPlayer(client);
}
}
else
{
int iFlags = GetEntityFlags(client);
if((iFlags & FL_ATCONTROLS) == 0)
{
SetEntityFlags(client, (iFlags|FL_ATCONTROLS));
}
}
// Spectating is laggy if the player has no weapons
if(gEV_Type != Engine_TF2 && GetPlayerWeaponSlot(client, CS_SLOT_KNIFE) == -1)
{
GivePlayerItem(client, "weapon_knife");
}
}
if(GetClientTeam(client) != gI_DefaultTeam)
{
if(gEV_Type == Engine_TF2)
{
ChangeClientTeam(client, gI_DefaultTeam);
}
else
{
CS_SwitchTeam(client, gI_DefaultTeam);
}
}
}
public void OnClientDisconnect(int client)
{
if(!IsFakeClient(client))
{
if(gA_PlayerFrames[client] != null)
{
delete gA_PlayerFrames[client];
}
return;
}
if(gA_CentralCache[iCentralClient] == client)
{
gA_CentralCache[iCentralClient] = -1;
return;
}
for(int i = 0; i < gI_Styles; i++)
{
if(client == gI_ReplayBotClient[i])
{
gI_ReplayBotClient[i] = 0;
break;
}
}
}
public Action Shavit_OnStart(int client)
{
ClearFrames(client);
gB_Record[client] = true;
return Plugin_Continue;
}
public void Shavit_OnStop(int client)
{
ClearFrames(client);
}
public void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track)
{
if(Shavit_IsPracticeMode(client))
{
return;
}
if(!gB_Enabled || (gF_TimeLimit > 0.0 && time > gF_TimeLimit))
{
ClearFrames(client);
return;
}
gB_Record[client] = false;
bool newformat = view_as<bool>(gA_FrameCache[style][track][2]);
float length = GetReplayLength(style, track);
if(newformat)
{
if(length > 0.0 && time > length)
{
return;
}
}
else
{
float wrtime = 0.0;
Shavit_GetWRTime(style, wrtime, track);
if(wrtime != 0.0 && time > wrtime)
{
return;
}
}
if(gI_PlayerFrames[client] == 0)
{
return;
}
gA_Frames[style][track] = gA_PlayerFrames[client].Clone();
char[] sAuthID = new char[32];
GetClientAuthId(client, AuthId_Steam3, sAuthID, 32);
char[] sName = new char[MAX_NAME_LENGTH];
GetClientName(client, sName, MAX_NAME_LENGTH);
ReplaceString(sName, MAX_NAME_LENGTH, "#", "?");
SaveReplay(style, track, time, sAuthID, sName);
if(ReplayEnabled(style))
{
if(gB_CentralBot && gA_CentralCache[iCentralStyle] == style && gA_CentralCache[iCentralTrack] == track)
{
StopCentralReplay(0);
}
else if(!gB_CentralBot && gI_ReplayBotClient[style] != 0)
{
UpdateReplayInfo(gI_ReplayBotClient[style], style, time, track);
if(gEV_Type == Engine_TF2)
{
TF2_RespawnPlayer(gI_ReplayBotClient[style]);
}
else
{
CS_RespawnPlayer(gI_ReplayBotClient[style]);
}
gRS_ReplayStatus[style] = Replay_Running;
gI_ReplayTick[style] = 0;
float vecPosition[3];
vecPosition[0] = gA_Frames[style][track].Get(0, 0);
vecPosition[1] = gA_Frames[style][track].Get(0, 1);
vecPosition[2] = gA_Frames[style][track].Get(0, 2);
TeleportEntity(gI_ReplayBotClient[style], vecPosition, NULL_VECTOR, NULL_VECTOR);
}
}
ClearFrames(client);
}
public void Shavit_OnPause(int client)
{
gB_Record[client] = false;
}
public void Shavit_OnResume(int client)
{
gB_Record[client] = true;
}
void ModifyFlags(int &flags, int flag, bool add)
{
if(add)
{
flags |= flag;
}
else
{
flags &= flag;
}
}
// OnPlayerRunCmd instead of Shavit_OnUserCmdPre because bots are also used here.
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3])
{
if(!gB_Enabled)
{
return Plugin_Continue;
}
if(!IsPlayerAlive(client))
{
if((buttons & IN_USE) > 0)
{
if(!gB_Button[client] && GetSpectatorTarget(client) == gA_CentralCache[iCentralClient])
{
OpenReplayMenu(client);
}
gB_Button[client] = true;
}
else
{
gB_Button[client] = false;
}
return Plugin_Continue;
}
float vecCurrentPosition[3];
GetClientAbsOrigin(client, vecCurrentPosition);
int style = GetReplayStyle(client);
int track = GetReplayTrack(client);
if(style != -1 && ReplayEnabled(style))
{
buttons = 0;
vel[0] = 0.0;
vel[1] = 0.0;
int iFrameCount = view_as<int>(gA_FrameCache[style][track][0]);
if(gA_Frames[style][track] == null || iFrameCount <= 0) // if no replay is loaded
{
return Plugin_Changed;
}
if(gI_ReplayTick[style] != -1 && iFrameCount >= 1)
{
float vecPosition[3];
float vecAngles[3];
if(gRS_ReplayStatus[style] != Replay_Running)
{
bool bStart = (gRS_ReplayStatus[style] == Replay_Start);
int iFrame = (bStart)? 0:(iFrameCount - 1);
vecPosition[0] = gA_Frames[style][track].Get(iFrame, 0);
vecPosition[1] = gA_Frames[style][track].Get(iFrame, 1);
vecPosition[2] = gA_Frames[style][track].Get(iFrame, 2);
vecAngles[0] = gA_Frames[style][track].Get(iFrame, 3);
vecAngles[1] = gA_Frames[style][track].Get(iFrame, 4);
if(bStart)
{
TeleportEntity(client, vecPosition, vecAngles, view_as<float>({0.0, 0.0, 0.0}));
}
else
{
float vecVelocity[3];
MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity);
ScaleVector(vecVelocity, gF_Tickrate);
TeleportEntity(client, NULL_VECTOR, vecAngles, vecVelocity);
}
return Plugin_Changed;
}
if(++gI_ReplayTick[style] >= iFrameCount)
{
gI_ReplayTick[style] = 0;
gRS_ReplayStatus[style] = gA_CentralCache[iCentralReplayStatus] = Replay_End;
CreateTimer((gF_ReplayDelay / 2.0), Timer_EndReplay, style, TIMER_FLAG_NO_MAPCHANGE);
return Plugin_Changed;
}
if(gI_ReplayTick[style] == 1)
{
gF_StartTick[style] = GetEngineTime();
}
vecPosition[0] = gA_Frames[style][track].Get(gI_ReplayTick[style], 0);
vecPosition[1] = gA_Frames[style][track].Get(gI_ReplayTick[style], 1);
vecPosition[2] = gA_Frames[style][track].Get(gI_ReplayTick[style], 2);
vecAngles[0] = gA_Frames[style][track].Get(gI_ReplayTick[style], 3);
vecAngles[1] = gA_Frames[style][track].Get(gI_ReplayTick[style], 4);
buttons = gA_Frames[style][track].Get(gI_ReplayTick[style], 5);
if((gI_BotShooting & iBotShooting_Attack1) == 0)
{
buttons &= ~IN_ATTACK;
}
if((gI_BotShooting & iBotShooting_Attack2) == 0)
{
buttons &= ~IN_ATTACK2;
}
MoveType mt = MOVETYPE_NOCLIP;
if(gA_FrameCache[style][track][3] >= 0x02)
{
int iReplayFlags = gA_Frames[style][track].Get(gI_ReplayTick[style], 6);
int iEntityFlags = GetEntityFlags(client);
ModifyFlags(iEntityFlags, FL_ONGROUND, (iReplayFlags & FL_ONGROUND) > 0);
ModifyFlags(iEntityFlags, FL_PARTIALGROUND, (iReplayFlags & FL_PARTIALGROUND) > 0);
ModifyFlags(iEntityFlags, FL_INWATER, (iReplayFlags & FL_INWATER) > 0);
ModifyFlags(iEntityFlags, FL_SWIM, (iReplayFlags & FL_SWIM) > 0);
SetEntityFlags(client, iEntityFlags);
MoveType movetype = gA_Frames[style][track].Get(gI_ReplayTick[style], 7);
if(movetype == MOVETYPE_LADDER)
{
mt = MOVETYPE_LADDER;
}
}
SetEntityMoveType(client, mt);
float vecVelocity[3];
MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity);
ScaleVector(vecVelocity, gF_Tickrate);
if((gI_ReplayTick[style] % RoundToFloor(gF_Tickrate * 1.5)) == 0)
{
float vecLastPosition[3];
vecLastPosition[0] = gA_Frames[style][track].Get(gI_ReplayTick[style] - 1, 0);
vecLastPosition[1] = gA_Frames[style][track].Get(gI_ReplayTick[style] - 1, 1);
vecLastPosition[2] = gA_Frames[style][track].Get(gI_ReplayTick[style] - 1, 2);
#if defined DEBUG
PrintToChatAll("vecVelocity: %.02f | dist %.02f", GetVectorLength(vecVelocity), GetVectorDistance(vecLastPosition, vecPosition) * gF_Tickrate);
#endif
if(GetVectorLength(vecVelocity) / (GetVectorDistance(vecLastPosition, vecPosition) * gF_Tickrate) > 2.0)
{
MakeVectorFromPoints(vecLastPosition, vecPosition, vecVelocity);
ScaleVector(vecVelocity, gF_Tickrate);
TeleportEntity(client, vecLastPosition, vecAngles, vecVelocity);
return Plugin_Changed;
}
}
TeleportEntity(client, NULL_VECTOR, vecAngles, vecVelocity);
return Plugin_Changed;
}
}
else if(gB_Record[client] && ReplayEnabled(Shavit_GetBhopStyle(client)) && Shavit_GetTimerStatus(client) == Timer_Running)
{
gA_PlayerFrames[client].Resize(gI_PlayerFrames[client] + 1);
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], vecCurrentPosition[0], 0);
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], vecCurrentPosition[1], 1);
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], vecCurrentPosition[2], 2);
if(!gB_HijackFrame[client])
{
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], angles[0], 3);
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], angles[1], 4);
}
else
{
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], gF_HijackedAngles[client][0], 3);
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], gF_HijackedAngles[client][1], 4);
gB_HijackFrame[client] = false;
}
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], buttons, 5);
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], GetEntityFlags(client), 6);
gA_PlayerFrames[client].Set(gI_PlayerFrames[client], GetEntityMoveType(client), 7);
gI_PlayerFrames[client]++;
}
return Plugin_Continue;
}
public Action Timer_EndReplay(Handle Timer, any data)
{
if(gB_CentralBot && gB_ForciblyStopped)
{
gB_ForciblyStopped = false;
return Plugin_Stop;
}
gI_ReplayTick[data] = 0;
if(gI_ReplayBotClient[data] != gA_CentralCache[iCentralClient])
{
gRS_ReplayStatus[data] = Replay_Start;
CreateTimer((gF_ReplayDelay / 2.0), Timer_StartReplay, data, TIMER_FLAG_NO_MAPCHANGE);
}
else
{
gRS_ReplayStatus[data] = gA_CentralCache[iCentralReplayStatus] = Replay_Idle;
gI_ReplayBotClient[data] = 0;
}
return Plugin_Stop;
}
public Action Timer_StartReplay(Handle Timer, any data)
{
if(gRS_ReplayStatus[data] == Replay_Running || (gB_CentralBot && gB_ForciblyStopped))
{
return Plugin_Stop;
}
gRS_ReplayStatus[data] = gA_CentralCache[iCentralReplayStatus] = Replay_Running;
return Plugin_Stop;
}
bool ReplayEnabled(any style)
{
return (!gA_StyleSettings[style][bUnranked] && !gA_StyleSettings[style][bNoReplay]);
}
public void Player_Event(Event event, const char[] name, bool dontBroadcast)
{
if(!gB_Enabled)
{
return;
}
int client = GetClientOfUserId(event.GetInt("userid"));
if(IsFakeClient(client))
{
event.BroadcastDisabled = true;
if(!gB_DontCallTimer)
{
CreateTimer(0.10, DelayedUpdate, GetClientSerial(client), TIMER_FLAG_NO_MAPCHANGE);
}
gB_DontCallTimer = false;
}
}
public Action DelayedUpdate(Handle Timer, any data)
{
int client = GetClientFromSerial(data);
if(client == 0)
{
return Plugin_Stop;
}
UpdateReplayInfo(client, GetReplayStyle(client), -1.0, GetReplayTrack(client));
return Plugin_Stop;
}
public void BotEvents(Event event, const char[] name, bool dontBroadcast)
{
if(!gB_Enabled)
{
return;
}
if(event.GetBool("bot"))
{
event.BroadcastDisabled = true;
int client = GetClientOfUserId(event.GetInt("userid"));
if(IsValidClient(client))
{
int style = GetReplayStyle(client);
if(style != -1)
{
UpdateReplayInfo(client, style, -1.0, GetReplayTrack(client));
}
}
}
}
public Action Hook_SayText2(UserMsg msg_id, any msg, const int[] players, int playersNum, bool reliable, bool init)
{
if(!gB_HideNameChange || !gB_Enabled)
{
return Plugin_Continue;
}
// caching usermessage type rather than call it every time
static UserMessageType um = view_as<UserMessageType>(-1);
if(um == view_as<UserMessageType>(-1))
{
um = GetUserMessageType();
}
char[] sMessage = new char[24];
if(um == UM_Protobuf)
{
Protobuf pbmsg = msg;
pbmsg.ReadString("msg_name", sMessage, 24);
delete pbmsg;
}
else
{
BfRead bfmsg = msg;
bfmsg.ReadByte();
bfmsg.ReadByte();
bfmsg.ReadString(sMessage, 24, false);
delete bfmsg;
}
if(StrEqual(sMessage, "#Cstrike_Name_Change") || StrEqual(sMessage, "#TF_Name_Change"))
{
gB_HideNameChange = false;
return Plugin_Handled;
}
return Plugin_Continue;
}
void ClearFrames(int client)
{
gA_PlayerFrames[client].Clear();
gI_PlayerFrames[client] = 0;
}
public void Shavit_OnWRDeleted(int style, int id, int track)
{
float time = 0.0;
Shavit_GetWRTime(style, time, track);
if(view_as<int>(gA_FrameCache[style][track][0]) > 0 && GetReplayLength(style, track) - gF_Tickrate <= time) // -0.1 to fix rounding issues
{
DeleteReplay(style, track);
}
}
public Action Command_DeleteReplay(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
Menu menu = new Menu(DeleteReplay_Callback);
menu.SetTitle("%T", "DeleteReplayMenuTitle", client);
for(int i = 0; i < gI_Styles; i++)
{
if(!ReplayEnabled(i))
{
continue;
}
for(int j = 0; j < TRACKS_SIZE; j++)
{
if(view_as<int>(gA_FrameCache[i][j][0]) == 0)
{
continue;
}
char[] sInfo = new char[8];
FormatEx(sInfo, 8, "%d;%d", i, j);
float time = GetReplayLength(i, j);
char[] sTrack = new char[32];
GetTrackName(client, j, sTrack, 32);
char[] sDisplay = new char[64];
if(time > 0.0)
{
char[] sTime = new char[32];
FormatSeconds(time, sTime, 32, false);
FormatEx(sDisplay, 64, "%s (%s) - %s", gS_StyleStrings[i][sStyleName], sTrack, sTime);
}
else
{
FormatEx(sDisplay, 64, "%s (%s)", gS_StyleStrings[i][sStyleName], sTrack);
}
menu.AddItem(sInfo, sDisplay);
}
}
if(menu.ItemCount == 0)
{
char[] sMenuItem = new char[64];
FormatEx(sMenuItem, 64, "%T", "ReplaysUnavailable", client);
menu.AddItem("-1", sMenuItem);
}
menu.ExitButton = true;
menu.Display(client, 20);
return Plugin_Handled;
}
public int DeleteReplay_Callback(Menu menu, MenuAction action, int param1, int param2)
{
if(action == MenuAction_Select)
{
char[] sInfo = new char[8];
menu.GetItem(param2, sInfo, 8);
char[][] sExploded = new char[2][4];
ExplodeString(sInfo, ";", sExploded, 2, 4);
int style = StringToInt(sExploded[0]);
if(style == -1)
{
return 0;
}
gI_Track[param1] = StringToInt(sExploded[1]);
Menu submenu = new Menu(DeleteConfirmation_Callback);
submenu.SetTitle("%T", "ReplayDeletionConfirmation", param1, gS_StyleStrings[style][sStyleName]);
char[] sMenuItem = new char[64];
for(int i = 1; i <= GetRandomInt(2, 4); i++)
{
FormatEx(sMenuItem, 64, "%T", "MenuResponseNo", param1);
submenu.AddItem("-1", sMenuItem);
}
FormatEx(sMenuItem, 64, "%T", "MenuResponseYes", param1);
submenu.AddItem(sInfo, sMenuItem);
for(int i = 1; i <= GetRandomInt(2, 4); i++)
{
FormatEx(sMenuItem, 64, "%T", "MenuResponseNo", param1);
submenu.AddItem("-1", sMenuItem);
}
submenu.ExitButton = true;
submenu.Display(param1, 20);
}
else if(action == MenuAction_End)
{
delete menu;
}
return 0;
}
public int DeleteConfirmation_Callback(Menu menu, MenuAction action, int param1, int param2)
{
if(action == MenuAction_Select)
{
char[] sInfo = new char[4];
menu.GetItem(param2, sInfo, 4);
int style = StringToInt(sInfo);
if(DeleteReplay(style, gI_Track[param1]))
{
char[] sTrack = new char[32];
GetTrackName(param1, gI_Track[param1], sTrack, 32);
LogAction(param1, param1, "Deleted replay for %s on map %s. (Track: %s)", gS_StyleStrings[style][sStyleName], gS_Map, sTrack);
Shavit_PrintToChat(param1, "%T (%s%s%s)", "ReplayDeleted", param1, gS_ChatStrings[sMessageStyle], gS_StyleStrings[style][sStyleName], gS_ChatStrings[sMessageText], gS_ChatStrings[sMessageVariable], sTrack, gS_ChatStrings[sMessageText]);
}
else
{
Shavit_PrintToChat(param1, "%T", "ReplayDeleteFailure", param1, gS_ChatStrings[sMessageStyle], gS_StyleStrings[style][sStyleName], gS_ChatStrings[sMessageText]);
}
}
else if(action == MenuAction_End)
{
delete menu;
}
return 0;
}
public Action Command_Replay(int client, int args)
{
if(!IsValidClient(client) || !gB_CentralBot || gA_CentralCache[iCentralClient] == -1)
{
return Plugin_Handled;
}
if(GetClientTeam(client) != 1 || GetSpectatorTarget(client) != gA_CentralCache[iCentralClient])
{
Shavit_PrintToChat(client, "%T", "CentralReplaySpectator", client, gS_ChatStrings[sMessageWarning], gS_ChatStrings[sMessageText], gS_ChatStrings[sMessageVariable], gS_ChatStrings[sMessageText]);
return Plugin_Handled;
}
if(CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON))
{
char[] arg = new char[8];
GetCmdArg(1, arg, 8);
if(StrEqual(arg, "stop"))
{
StopCentralReplay(client);
return Plugin_Handled;
}
}
return OpenReplayMenu(client);
}
Action OpenReplayMenu(int client)
{
Menu menu = new Menu(MenuHandler_Replay);
menu.SetTitle("%T\n ", "CentralReplayTrack", client);
for(int i = 0; i < TRACKS_SIZE; i++)
{
bool records = false;
for(int j = 0; j < gI_Styles; j++)
{
if(view_as<int>(gA_FrameCache[j][i][0]) > 0)
{
records = true;
continue;
}
}
char[] sInfo = new char[8];
IntToString(i, sInfo, 8);
char[] sTrack = new char[32];
GetTrackName(client, i, sTrack, 32);
menu.AddItem(sInfo, sTrack, (records)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
}
menu.ExitButton = true;
menu.Display(client, 60);
return Plugin_Handled;
}
public int MenuHandler_Replay(Menu menu, MenuAction action, int param1, int param2)
{
if(action == MenuAction_Select)
{
// avoid an exploit
if(param2 >= 0 && param2 < TRACKS_SIZE)
{
OpenReplaySubMenu(param1, param2);
}
}
else if(action == MenuAction_End)
{
delete menu;
}
return 0;
}
void OpenReplaySubMenu(int client, int track)
{
gI_Track[client] = track;
char[] sTrack = new char[32];
GetTrackName(client, track, sTrack, 32);
Menu menu = new Menu(MenuHandler_ReplaySubmenu);
menu.SetTitle("%T (%s)\n ", "CentralReplayTitle", client, sTrack);
if(CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON))
{
char[] sDisplay = new char[64];
FormatEx(sDisplay, 64, "%T", "CentralReplayStop", client);
menu.AddItem("stop", sDisplay, (gA_CentralCache[iCentralReplayStatus] != Replay_Idle)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
}
for(int i = 0; i < gI_Styles; i++)
{
if(!ReplayEnabled(i))
{
continue;
}
char[] sInfo = new char[8];
IntToString(i, sInfo, 8);
float time = GetReplayLength(i, track);
char[] sDisplay = new char[64];
if(time > 0.0)
{
char[] sTime = new char[32];
FormatSeconds(time, sTime, 32, false);
FormatEx(sDisplay, 64, "%s - %s", gS_StyleStrings[i][sStyleName], sTime);
}
else
{
strcopy(sDisplay, 64, gS_StyleStrings[i][sStyleName]);
}
menu.AddItem(sInfo, sDisplay, (view_as<int>(gA_FrameCache[i][track][0]) > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
}
if(menu.ItemCount == 0)
{
menu.AddItem("-1", "ERROR");
}
else if(menu.ItemCount <= ((gEV_Type == Engine_CSS)? 8:7))
{
menu.Pagination = MENU_NO_PAGINATION;
}
menu.ExitBackButton = true;
menu.Display(client, 60);
}
public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, int param2)
{
if(action == MenuAction_Select)
{
char[] info = new char[16];
menu.GetItem(param2, info, 16);
if(StrEqual(info, "stop"))
{
StopCentralReplay(param1);
OpenReplaySubMenu(param1, gI_Track[param1]);
return 0;
}
int style = StringToInt(info);
if(style == -1 || !ReplayEnabled(style) || view_as<int>(gA_FrameCache[style][gI_Track[param1]][0]) == 0 || gA_CentralCache[iCentralClient] <= 0)
{
return 0;
}
if(gA_CentralCache[iCentralReplayStatus] != Replay_Idle)
{
Shavit_PrintToChat(param1, "%T", "CentralReplayPlaying", param1);
OpenReplaySubMenu(param1, gI_Track[param1]);
}
else
{
gI_ReplayTick[style] = 0;
gA_CentralCache[iCentralStyle] = style;
gA_CentralCache[iCentralTrack] = gI_Track[param1];
gI_ReplayBotClient[style] = gA_CentralCache[iCentralClient];
gRS_ReplayStatus[style] = gA_CentralCache[iCentralReplayStatus] = Replay_Start;
TeleportToStart(gA_CentralCache[iCentralClient], style, gI_Track[param1]);
gB_ForciblyStopped = false;
float time = GetReplayLength(gA_CentralCache[iCentralStyle], gI_Track[param1]);
UpdateReplayInfo(gA_CentralCache[iCentralClient], style, time, gI_Track[param1]);
CreateTimer((gF_ReplayDelay / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE);
}
}
else if(action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
{
OpenReplayMenu(param1);
}
else if(action == MenuAction_End)
{
delete menu;
}
return 0;
}
void TeleportToStart(int client, int style, int track)
{
if(view_as<int>(gA_FrameCache[style][track][0]) == 0)
{
return;
}
float vecPosition[3];
vecPosition[0] = gA_Frames[style][track].Get(0, 0);
vecPosition[1] = gA_Frames[style][track].Get(0, 1);
vecPosition[2] = gA_Frames[style][track].Get(0, 2);
float vecAngles[3];
vecAngles[0] = gA_Frames[style][track].Get(0, 3);
vecAngles[1] = gA_Frames[style][track].Get(0, 4);
TeleportEntity(client, vecPosition, vecAngles, view_as<float>({0.0, 0.0, 0.0}));
}
void StopCentralReplay(int client)
{
if(client > 0)
{
Shavit_PrintToChat(client, "%T", "CentralReplayStopped", client);
}
int style = gA_CentralCache[iCentralStyle];
gRS_ReplayStatus[style] = gA_CentralCache[iCentralReplayStatus] = Replay_Idle;
gI_ReplayTick[style] = 0;
gI_ReplayBotClient[style] = 0;
gF_StartTick[style] = -65535.0;
TeleportToStart(gA_CentralCache[iCentralClient], style, GetReplayTrack(gA_CentralCache[iCentralClient]));
gA_CentralCache[iCentralStyle] = 0;
gB_ForciblyStopped = true;
UpdateReplayInfo(client, 0, 0.0, gA_CentralCache[iCentralTrack]);
}
int GetReplayStyle(int client)
{
if(!IsFakeClient(client))
{
return -1;
}
if(gB_CentralBot)
{
if(gA_CentralCache[iCentralStyle] == -1)
{
return 0;
}
return gA_CentralCache[iCentralStyle];
}
for(int i = 0; i < gI_Styles; i++)
{
if(gI_ReplayBotClient[i] == client)
{
return i;
}
}
return -1;
}
int GetReplayTrack(int client)
{
if(!IsFakeClient(client))
{
return -1;
}
return (gB_CentralBot)? gA_CentralCache[iCentralTrack]:Track_Main;
}
int GetSpectatorTarget(int client)
{
int target = -1;
if(IsClientObserver(client))
{
int iObserverMode = GetEntProp(client, Prop_Send, "m_iObserverMode");
if(iObserverMode >= 3 && iObserverMode <= 5)
{
int iTarget = GetEntPropEnt(client, Prop_Send, "m_hObserverTarget");
if(IsValidClient(iTarget, true))
{
target = iTarget;
}
}
}
return target;
}
void GetTrackName(int client, int track, char[] output, int size)
{
if(track < 0 || track >= TRACKS_SIZE)
{
FormatEx(output, size, "%T", "Track_Unknown", client);
return;
}
static char sTrack[16];
FormatEx(sTrack, 16, "Track_%d", track);
FormatEx(output, size, "%T", sTrack, client);
}
float GetReplayLength(int style, int track)
{
if(view_as<bool>(gA_FrameCache[style][track][2]))
{
return view_as<float>(gA_FrameCache[style][track][1]);
}
float fWRTime = 0.0;
Shavit_GetWRTime(style, fWRTime, track);
return fWRTime;
}
void GetReplayName(int style, int track, char[] buffer, int length)
{
if(view_as<bool>(gA_FrameCache[style][track][2]))
{
strcopy(buffer, length, gS_ReplayNames[style][track]);
return;
}
Shavit_GetWRName(style, buffer, length, track);
}
/*
* Copies file source to destination
* Based on code of javalia:
* http://forums.alliedmods.net/showthread.php?t=159895
*
* @param source Input file
* @param destination Output file
*/
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;
}