Merge pull request #741 from shavitush/very_good_yes

v2.4.1
This commit is contained in:
shavit 2019-03-08 12:56:52 +02:00 committed by GitHub
commit a0d205247a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1012 additions and 312 deletions

View File

@ -60,6 +60,7 @@
"special" "0" // For third-party modules. The core plugins will not need this setting. "special" "0" // For third-party modules. The core plugins will not need this setting.
"specialstring" "" // For modularity. Separated with semicolon. Built-in flags: "segments". "specialstring" "" // For modularity. Separated with semicolon. Built-in flags: "segments".
"permission" "" // Permission required. Syntax: "flag;override". For example "p;style_tas" to require the 'p' flag or the "style_tas" override. "permission" "" // Permission required. Syntax: "flag;override". For example "p;style_tas" to require the 'p' flag or the "style_tas" override.
"ordering" "0" // Ordering in menus where styles appear. If this value is not present, style ID will be used instead.
} }
"1" "1"

View File

@ -23,7 +23,7 @@
#endif #endif
#define _shavit_included #define _shavit_included
#define SHAVIT_VERSION "2.4.0" #define SHAVIT_VERSION "2.4.1"
#define STYLE_LIMIT 256 #define STYLE_LIMIT 256
#define MAX_ZONES 64 #define MAX_ZONES 64
#define MAX_NAME_LENGTH_SQL 32 #define MAX_NAME_LENGTH_SQL 32
@ -156,6 +156,7 @@ enum struct stylesettings_t
bool bStrafeCountD; bool bStrafeCountD;
float fRankingMultiplier; float fRankingMultiplier;
int iSpecial; int iSpecial;
int iOrdering;
} }
enum struct chatstrings_t enum struct chatstrings_t
@ -546,6 +547,18 @@ forward void Shavit_OnWorstRecord(int client, int style, float time, int jumps,
*/ */
forward void Shavit_OnTierAssigned(const char[] map, int tier); forward void Shavit_OnTierAssigned(const char[] map, int tier);
/**
* Gets called when the server acknowledges the client's ranking status.
* It is called after OnClientPostAdminCheck and at forced rank recalculations.
*
* @param client Client index.
* @param rank Client's rank. (0 if unranked or unassigned)
* @param points Client's points. (0.0 if unranked or unassigned)
* @param first True if the forward is called after the initial connection, false if it is caused by recalculation.
* @noreturn
*/
forward void Shavit_OnRankAssigned(int client, int rank, float points, bool first);
/** /**
* Called when replay playback starts. * Called when replay playback starts.
* *
@ -652,6 +665,18 @@ native void Shavit_Replay_DeleteMap(const char[] map);
*/ */
native void Shavit_Rankings_DeleteMap(const char[] map); native void Shavit_Rankings_DeleteMap(const char[] map);
/**
* Changes a player's bhop style.
*
* @param client Client index.
* @param style Style.
* @param force Ignore style permissions.
* @param manual Is it a manual style change? (Was it caused by user interaction?)
* @param noforward Bypasses the call to `Shavit_OnStyleChanged`.
* @return False if failed due to lack of access, true otherwise.
*/
native bool Shavit_ChangeClientStyle(int client, int style, bool force = false, bool manual = false, bool noforward = false);
/** /**
* Finishes the map for a player, with their current timer stats. * Finishes the map for a player, with their current timer stats.
* Will not teleport the player to anywhere, it's handled inside the mapzones plugin. * Will not teleport the player to anywhere, it's handled inside the mapzones plugin.
@ -762,6 +787,13 @@ native void Shavit_GetWRTime(int style, float &time, int track);
*/ */
native float Shavit_GetWorldRecord(int style, int track); native float Shavit_GetWorldRecord(int style, int track);
/**
* Reloads WR leaderboards cache for the current map.
*
* @noreturn
*/
native void Shavit_ReloadLeaderboards();
/** /**
* Saves the WR's record ID for the current map on a variable. * Saves the WR's record ID for the current map on a variable.
* Unused in base plugins, as of pre-1.4b. * Unused in base plugins, as of pre-1.4b.
@ -878,6 +910,17 @@ native void Shavit_PauseTimer(int client);
*/ */
native void Shavit_ResumeTimer(int client); native void Shavit_ResumeTimer(int client);
/**
* Deletes the specified replay file.
* Replay data will be unloaded if necessary.
*
* @param map Map display name.
* @param style Bhop style.
* @param track Timer track.
* @return true if replay existed, false otherwise.
*/
native bool Shavit_DeleteReplay(const char[] map, int style, int track);
/** /**
* Retrieves the engine time of the replay bot's first frame. * Retrieves the engine time of the replay bot's first frame.
* *
@ -1063,6 +1106,15 @@ native int Shavit_GetStyleStrings(int style, int stringtype, char[] StyleStrings
*/ */
native int Shavit_GetStyleCount(); native int Shavit_GetStyleCount();
/**
* Gets an array with style IDs in their configured menu ordering as specified in the styles config.
*
* @param arr Reference to array to fill with style IDs.
* @param size Array size.
* @noreturn
*/
native void Shavit_GetOrderedStyles(int[] arr, int size);
/** /**
* Saves chat related strings on string references. * Saves chat related strings on string references.
* *
@ -1212,6 +1264,14 @@ native StringMap Shavit_GetMapTiers();
*/ */
native bool Shavit_HasStyleAccess(int client, int style); native bool Shavit_HasStyleAccess(int client, int style);
/**
* Determines whether a client's timer is paused or not.
*
* @param client Client index.
* @return Boolean value.
*/
native bool Shavit_IsPaused(int client);
/** /**
* Use this native when printing anything in chat if it's related to the timer. * Use this native when printing anything in chat if it's related to the timer.
* This native will auto-assign colors and a chat prefix. * This native will auto-assign colors and a chat prefix.
@ -1265,6 +1325,8 @@ public SharedPlugin __pl_shavit =
#if !defined REQUIRE_PLUGIN #if !defined REQUIRE_PLUGIN
public void __pl_shavit_SetNTVOptional() public void __pl_shavit_SetNTVOptional()
{ {
MarkNativeAsOptional("Shavit_ChangeClientStyle");
MarkNativeAsOptional("Shavit_DeleteReplay");
MarkNativeAsOptional("Shavit_FinishMap"); MarkNativeAsOptional("Shavit_FinishMap");
MarkNativeAsOptional("Shavit_ForceHUDUpdate"); MarkNativeAsOptional("Shavit_ForceHUDUpdate");
MarkNativeAsOptional("Shavit_FormatChat"); MarkNativeAsOptional("Shavit_FormatChat");
@ -1279,6 +1341,7 @@ public void __pl_shavit_SetNTVOptional()
MarkNativeAsOptional("Shavit_GetHUDSettings"); MarkNativeAsOptional("Shavit_GetHUDSettings");
MarkNativeAsOptional("Shavit_GetMapTier"); MarkNativeAsOptional("Shavit_GetMapTier");
MarkNativeAsOptional("Shavit_GetMapTiers"); MarkNativeAsOptional("Shavit_GetMapTiers");
MarkNativeAsOptional("Shavit_GetOrderedStyles");
MarkNativeAsOptional("Shavit_GetPerfectJumps"); MarkNativeAsOptional("Shavit_GetPerfectJumps");
MarkNativeAsOptional("Shavit_GetPlayerPB"); MarkNativeAsOptional("Shavit_GetPlayerPB");
MarkNativeAsOptional("Shavit_GetPoints"); MarkNativeAsOptional("Shavit_GetPoints");
@ -1315,6 +1378,7 @@ public void __pl_shavit_SetNTVOptional()
MarkNativeAsOptional("Shavit_InsideZone"); MarkNativeAsOptional("Shavit_InsideZone");
MarkNativeAsOptional("Shavit_IsClientCreatingZone"); MarkNativeAsOptional("Shavit_IsClientCreatingZone");
MarkNativeAsOptional("Shavit_IsKZMap"); MarkNativeAsOptional("Shavit_IsKZMap");
MarkNativeAsOptional("Shavit_IsPaused");
MarkNativeAsOptional("Shavit_IsPracticeMode"); MarkNativeAsOptional("Shavit_IsPracticeMode");
MarkNativeAsOptional("Shavit_IsReplayDataLoaded"); MarkNativeAsOptional("Shavit_IsReplayDataLoaded");
MarkNativeAsOptional("Shavit_LoadSnapshot"); MarkNativeAsOptional("Shavit_LoadSnapshot");
@ -1323,6 +1387,7 @@ public void __pl_shavit_SetNTVOptional()
MarkNativeAsOptional("Shavit_PauseTimer"); MarkNativeAsOptional("Shavit_PauseTimer");
MarkNativeAsOptional("Shavit_PrintToChat"); MarkNativeAsOptional("Shavit_PrintToChat");
MarkNativeAsOptional("Shavit_Rankings_DeleteMap"); MarkNativeAsOptional("Shavit_Rankings_DeleteMap");
MarkNativeAsOptional("Shavit_ReloadLeaderboards");
MarkNativeAsOptional("Shavit_ReloadReplay"); MarkNativeAsOptional("Shavit_ReloadReplay");
MarkNativeAsOptional("Shavit_ReloadReplays"); MarkNativeAsOptional("Shavit_ReloadReplays");
MarkNativeAsOptional("Shavit_Replay_DeleteMap"); MarkNativeAsOptional("Shavit_Replay_DeleteMap");

View File

@ -131,6 +131,7 @@ ConVar sv_enablebunnyhopping = null;
// timer settings // timer settings
bool gB_Registered = false; bool gB_Registered = false;
int gI_Styles = 0; int gI_Styles = 0;
int gI_OrderedStyles[STYLE_LIMIT];
stylestrings_t gS_StyleStrings[STYLE_LIMIT]; stylestrings_t gS_StyleStrings[STYLE_LIMIT];
stylesettings_t gA_StyleSettings[STYLE_LIMIT]; stylesettings_t gA_StyleSettings[STYLE_LIMIT];
@ -142,6 +143,8 @@ bool gB_StopChatSound = false;
bool gB_HookedJump = false; bool gB_HookedJump = false;
char gS_LogPath[PLATFORM_MAX_PATH]; char gS_LogPath[PLATFORM_MAX_PATH];
char gS_DeleteMap[MAXPLAYERS+1][160]; char gS_DeleteMap[MAXPLAYERS+1][160];
char gS_WipePlayerID[MAXPLAYERS+1][32];
char gS_Verification[MAXPLAYERS+1][16];
// flags // flags
int gI_StyleFlag[STYLE_LIMIT]; int gI_StyleFlag[STYLE_LIMIT];
@ -161,6 +164,7 @@ public Plugin myinfo =
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{ {
CreateNative("Shavit_ChangeClientStyle", Native_ChangeClientStyle);
CreateNative("Shavit_FinishMap", Native_FinishMap); CreateNative("Shavit_FinishMap", Native_FinishMap);
CreateNative("Shavit_GetBhopStyle", Native_GetBhopStyle); CreateNative("Shavit_GetBhopStyle", Native_GetBhopStyle);
CreateNative("Shavit_GetChatStrings", Native_GetChatStrings); CreateNative("Shavit_GetChatStrings", Native_GetChatStrings);
@ -170,6 +174,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
CreateNative("Shavit_GetDatabase", Native_GetDatabase); CreateNative("Shavit_GetDatabase", Native_GetDatabase);
CreateNative("Shavit_GetDB", Native_GetDB); CreateNative("Shavit_GetDB", Native_GetDB);
CreateNative("Shavit_GetGameType", Native_GetGameType); CreateNative("Shavit_GetGameType", Native_GetGameType);
CreateNative("Shavit_GetOrderedStyles", Native_GetOrderedStyles);
CreateNative("Shavit_GetPerfectJumps", Native_GetPerfectJumps); CreateNative("Shavit_GetPerfectJumps", Native_GetPerfectJumps);
CreateNative("Shavit_GetStrafeCount", Native_GetStrafeCount); CreateNative("Shavit_GetStrafeCount", Native_GetStrafeCount);
CreateNative("Shavit_GetStyleCount", Native_GetStyleCount); CreateNative("Shavit_GetStyleCount", Native_GetStyleCount);
@ -180,6 +185,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
CreateNative("Shavit_GetTimerStatus", Native_GetTimerStatus); CreateNative("Shavit_GetTimerStatus", Native_GetTimerStatus);
CreateNative("Shavit_HasStyleAccess", Native_HasStyleAccess); CreateNative("Shavit_HasStyleAccess", Native_HasStyleAccess);
CreateNative("Shavit_IsKZMap", Native_IsKZMap); CreateNative("Shavit_IsKZMap", Native_IsKZMap);
CreateNative("Shavit_IsPaused", Native_IsPaused);
CreateNative("Shavit_IsPracticeMode", Native_IsPracticeMode); CreateNative("Shavit_IsPracticeMode", Native_IsPracticeMode);
CreateNative("Shavit_LoadSnapshot", Native_LoadSnapshot); CreateNative("Shavit_LoadSnapshot", Native_LoadSnapshot);
CreateNative("Shavit_LogMessage", Native_LogMessage); CreateNative("Shavit_LogMessage", Native_LogMessage);
@ -222,6 +228,7 @@ public void OnPluginStart()
gH_Forwards_OnTimerIncrementPost = CreateGlobalForward("Shavit_OnTimeIncrementPost", ET_Event, Param_Cell, Param_Cell, Param_Array); gH_Forwards_OnTimerIncrementPost = CreateGlobalForward("Shavit_OnTimeIncrementPost", ET_Event, Param_Cell, Param_Cell, Param_Array);
LoadTranslations("shavit-core.phrases"); LoadTranslations("shavit-core.phrases");
LoadTranslations("shavit-common.phrases");
// game types // game types
gEV_Type = GetEngineVersion(); gEV_Type = GetEngineVersion();
@ -297,6 +304,7 @@ public void OnPluginStart()
// admin // admin
RegAdminCmd("sm_deletemap", Command_DeleteMap, ADMFLAG_ROOT, "Deletes all map data. Usage: sm_deletemap <map>"); RegAdminCmd("sm_deletemap", Command_DeleteMap, ADMFLAG_ROOT, "Deletes all map data. Usage: sm_deletemap <map>");
RegAdminCmd("sm_wipeplayer", Command_WipePlayer, ADMFLAG_BAN, "Wipes all bhoptimer data for specified player. Usage: sm_wipeplayer <steamid3>");
// commands END // commands END
// logs // logs
@ -470,7 +478,10 @@ public Action Command_StartTimer(int client, int args)
else else
{ {
Shavit_PrintToChat(client, "%T", "StartZoneUndefined", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText); char sTrack[32];
GetTrackName(client, track, sTrack, 32);
Shavit_PrintToChat(client, "%T", "StartZoneUndefined", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTrack, gS_ChatStrings.sText);
} }
return Plugin_Handled; return Plugin_Handled;
@ -636,6 +647,214 @@ public Action Command_DeleteMap(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
public Action Command_WipePlayer(int client, int args)
{
if(args == 0)
{
ReplyToCommand(client, "Usage: sm_wipeplayer <steamid3>\nAfter entering a SteamID, you will be prompted with a verification captcha.");
return Plugin_Handled;
}
char sArgString[32];
GetCmdArgString(sArgString, 32);
if(strlen(gS_Verification[client]) == 0 || !StrEqual(sArgString, gS_Verification[client]))
{
char sAlphabet[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#";
for(int i = 0; i < GetRandomInt(5, 7); i++)
{
gS_Verification[client][i] = sAlphabet[GetRandomInt(0, sizeof(sAlphabet))];
}
strcopy(gS_WipePlayerID[client], 32, sArgString);
Shavit_PrintToChat(client, "Preparing to delete all user data for SteamID %s%s%s. To confirm, enter %s!wipeplayer %s",
gS_ChatStrings.sVariable, sArgString, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, gS_Verification[client]);
}
else
{
int iLength = ((strlen(gS_WipePlayerID[client]) * 2) + 1);
char[] sEscapedAuthID = new char[iLength];
gH_SQL.Escape(gS_WipePlayerID[client], sEscapedAuthID, iLength);
Shavit_PrintToChat(client, "Deleting data for SteamID %s%s%s...",
gS_ChatStrings.sVariable, sEscapedAuthID, gS_ChatStrings.sText);
DeleteUserData(client, sEscapedAuthID);
strcopy(gS_Verification[client], 32, "");
strcopy(gS_WipePlayerID[client], 32, "");
}
return Plugin_Handled;
}
void DeleteUserData(int client, const char[] sAuthID3)
{
if(gB_Replay)
{
char sQueryGetWorldRecords[256];
FormatEx(sQueryGetWorldRecords, 256,
"SELECT map, id, style, track FROM %splayertimes WHERE auth = '%s' GROUP BY map, style, track;",
gS_MySQLPrefix, sAuthID3);
DataPack pack = new DataPack();
pack.WriteCell(client);
pack.WriteString(sAuthID3);
gH_SQL.Query(SQL_DeleteUserData_GetRecords_Callback, sQueryGetWorldRecords, pack, DBPrio_High);
}
else
{
char sQueryDeleteUserTimes[256];
FormatEx(sQueryDeleteUserTimes, 256,
"DELETE FROM %splayertimes WHERE auth = '%s';",
gS_MySQLPrefix, sAuthID3);
DataPack steamPack = new DataPack();
steamPack.WriteString(sAuthID3);
steamPack.WriteCell(client);
gH_SQL.Query(SQL_DeleteUserTimes_Callback, sQueryDeleteUserTimes, steamPack, DBPrio_High);
}
}
public void SQL_DeleteUserData_GetRecords_Callback(Database db, DBResultSet results, const char[] error, any data)
{
DataPack pack = view_as<DataPack>(data);
pack.Reset();
int client = pack.ReadCell();
char sAuthID3[32];
pack.ReadString(sAuthID3, 32);
delete pack;
if(results == null)
{
LogError("Timer error! Failed to wipe user data (wipe | get player records). Reason: %s", error);
return;
}
Transaction trans = new Transaction();
while(results.FetchRow())
{
char map[160];
results.FetchString(0, map, 160);
int id = results.FetchInt(1);
int style = results.FetchInt(2);
int track = results.FetchInt(3);
char sQueryGetWorldRecordID[256];
FormatEx(sQueryGetWorldRecordID, 256,
"SELECT id FROM %splayertimes WHERE map = '%s' AND style = %d AND track = %d ORDER BY time LIMIT 1;",
gS_MySQLPrefix, map, style, track);
DataPack transPack = new DataPack();
transPack.WriteString(map);
transPack.WriteCell(id);
transPack.WriteCell(style);
transPack.WriteCell(track);
trans.AddQuery(sQueryGetWorldRecordID, transPack);
}
DataPack steamPack = new DataPack();
steamPack.WriteString(sAuthID3);
steamPack.WriteCell(client);
gH_SQL.Execute(trans, Trans_OnRecordCompare, INVALID_FUNCTION, steamPack, DBPrio_High);
}
public void Trans_OnRecordCompare(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
{
DataPack pack = view_as<DataPack>(data);
pack.Reset();
char sAuthID3[32];
pack.ReadString(sAuthID3, 32);
for(int i = 0; i < numQueries; i++)
{
DataPack hQueryPack = view_as<DataPack>(queryData[i]);
hQueryPack.Reset();
char sMap[32];
hQueryPack.ReadString(sMap, 32);
int iRecordID = hQueryPack.ReadCell();
int iStyle = hQueryPack.ReadCell();
int iTrack = hQueryPack.ReadCell();
delete hQueryPack;
if(results[i] != null && results[i].FetchRow())
{
int iWR = results[i].FetchInt(0);
if(iWR == iRecordID)
{
Shavit_DeleteReplay(sMap, iStyle, iTrack);
}
}
}
char sQueryDeleteUserTimes[256];
FormatEx(sQueryDeleteUserTimes, 256,
"DELETE FROM %splayertimes WHERE auth = '%s';",
gS_MySQLPrefix, sAuthID3);
gH_SQL.Query(SQL_DeleteUserTimes_Callback, sQueryDeleteUserTimes, pack, DBPrio_High);
}
public void SQL_DeleteUserTimes_Callback(Database db, DBResultSet results, const char[] error, any data)
{
DataPack pack = view_as<DataPack>(data);
pack.Reset();
char sAuthID3[32];
pack.ReadString(sAuthID3, 32);
if(results == null)
{
LogError("Timer error! Failed to wipe user data (wipe | delete user times). Reason: %s", error);
delete pack;
return;
}
char sQueryDeleteUsers[256];
FormatEx(sQueryDeleteUsers, 256, "DELETE FROM %susers WHERE auth = '%s';",
gS_MySQLPrefix, sAuthID3);
gH_SQL.Query(SQL_DeleteUserData_Callback, sQueryDeleteUsers, pack, DBPrio_High);
}
public void SQL_DeleteUserData_Callback(Database db, DBResultSet results, const char[] error, any data)
{
DataPack pack = view_as<DataPack>(data);
pack.Reset();
char sAuthID3[32];
pack.ReadString(sAuthID3, 32);
int client = pack.ReadCell();
delete pack;
if(results == null)
{
LogError("Timer error! Failed to wipe user data (wipe | delete user data, id %s). Reason: %s", error, sAuthID3);
return;
}
Shavit_ReloadLeaderboards();
Shavit_PrintToChat(client, "Finished wiping timer data for user %s%s%s.",
gS_ChatStrings.sVariable, sAuthID3, gS_ChatStrings.sText);
}
public Action Command_AutoBhop(int client, int args) public Action Command_AutoBhop(int client, int args)
{ {
if(!IsValidClient(client)) if(!IsValidClient(client))
@ -683,14 +902,16 @@ public Action Command_Style(int client, int args)
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
int iStyle = gI_OrderedStyles[i];
char sInfo[8]; char sInfo[8];
IntToString(i, sInfo, 8); IntToString(iStyle, sInfo, 8);
char sDisplay[64]; char sDisplay[64];
if(gA_StyleSettings[i].bUnranked) if(gA_StyleSettings[iStyle].bUnranked)
{ {
FormatEx(sDisplay, 64, "%T %s", "StyleUnranked", client, gS_StyleStrings[i].sStyleName); FormatEx(sDisplay, 64, "%T %s", "StyleUnranked", client, gS_StyleStrings[iStyle].sStyleName);
} }
else else
@ -699,7 +920,7 @@ public Action Command_Style(int client, int args)
if(gB_WR) if(gB_WR)
{ {
time = Shavit_GetWorldRecord(i, Track_Main); time = Shavit_GetWorldRecord(iStyle, Track_Main);
} }
if(time > 0.0) if(time > 0.0)
@ -707,16 +928,16 @@ public Action Command_Style(int client, int args)
char sTime[32]; char sTime[32];
FormatSeconds(time, sTime, 32, false); FormatSeconds(time, sTime, 32, false);
FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[i].sStyleName, sTime); FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[iStyle].sStyleName, sTime);
} }
else else
{ {
strcopy(sDisplay, 64, gS_StyleStrings[i].sStyleName); strcopy(sDisplay, 64, gS_StyleStrings[iStyle].sStyleName);
} }
} }
menu.AddItem(sInfo, sDisplay, (gA_Timers[client].iStyle == i || !Shavit_HasStyleAccess(client, i))? ITEMDRAW_DISABLED:ITEMDRAW_DEFAULT); menu.AddItem(sInfo, sDisplay, (gA_Timers[client].iStyle == iStyle || !Shavit_HasStyleAccess(client, iStyle))? ITEMDRAW_DISABLED:ITEMDRAW_DEFAULT);
} }
// should NEVER happen // should NEVER happen
@ -933,6 +1154,11 @@ public int Native_GetGameType(Handle handler, int numParams)
return view_as<int>(gEV_Type); return view_as<int>(gEV_Type);
} }
public int Native_GetOrderedStyles(Handle handler, int numParams)
{
return SetNativeArray(1, gI_OrderedStyles, GetNativeCell(2));
}
public int Native_GetDatabase(Handle handler, int numParams) public int Native_GetDatabase(Handle handler, int numParams)
{ {
return view_as<int>(CloneHandle(gH_SQL, handler)); return view_as<int>(CloneHandle(gH_SQL, handler));
@ -1009,6 +1235,34 @@ public int Native_StopTimer(Handle handler, int numParams)
Call_Finish(); Call_Finish();
} }
public int Native_ChangeClientStyle(Handle handler, int numParams)
{
int client = GetNativeCell(1);
int style = GetNativeCell(2);
bool force = view_as<bool>(GetNativeCell(3));
bool manual = view_as<bool>(GetNativeCell(4));
bool noforward = view_as<bool>(GetNativeCell(5));
if(force || Shavit_HasStyleAccess(client, style))
{
if(noforward)
{
gA_Timers[client].iStyle = style;
UpdateStyleSettings(client);
}
else
{
CallOnStyleChanged(client, gA_Timers[client].iStyle, style, manual);
}
return true;
}
return false;
}
public int Native_FinishMap(Handle handler, int numParams) public int Native_FinishMap(Handle handler, int numParams)
{ {
int client = GetNativeCell(1); int client = GetNativeCell(1);
@ -1101,19 +1355,26 @@ public int Native_PrintToChat(Handle handler, int numParams)
{ {
int client = GetNativeCell(1); int client = GetNativeCell(1);
if(!IsClientInGame(client))
{
gB_StopChatSound = false;
return;
}
static int iWritten = 0; // useless? static int iWritten = 0; // useless?
char sBuffer[300]; char sBuffer[300];
FormatNativeString(0, 2, 3, 300, iWritten, sBuffer); FormatNativeString(0, 2, 3, 300, iWritten, sBuffer);
Format(sBuffer, 300, "%s %s%s", gS_ChatStrings.sPrefix, gS_ChatStrings.sText, sBuffer); Format(sBuffer, 300, "%s %s%s", gS_ChatStrings.sPrefix, gS_ChatStrings.sText, sBuffer);
if(client == 0)
{
PrintToServer("%s", sBuffer);
return false;
}
if(!IsClientInGame(client))
{
gB_StopChatSound = false;
return false;
}
Handle hSayText2 = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS); Handle hSayText2 = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS);
if(gB_Protobuf) if(gB_Protobuf)
@ -1144,6 +1405,8 @@ public int Native_PrintToChat(Handle handler, int numParams)
EndMessage(); EndMessage();
gB_StopChatSound = false; gB_StopChatSound = false;
return true;
} }
public int Native_RestartTimer(Handle handler, int numParams) public int Native_RestartTimer(Handle handler, int numParams)
@ -1240,6 +1503,11 @@ public int Native_SetPracticeMode(Handle handler, int numParams)
gA_Timers[client].bPracticeMode = practice; gA_Timers[client].bPracticeMode = practice;
} }
public int Native_IsPaused(Handle handler, int numParams)
{
return view_as<int>(gA_Timers[GetNativeCell(1)].bPaused);
}
public int Native_IsPracticeMode(Handle handler, int numParams) public int Native_IsPracticeMode(Handle handler, int numParams)
{ {
return view_as<int>(gA_Timers[GetNativeCell(1)].bPracticeMode); return view_as<int>(gA_Timers[GetNativeCell(1)].bPracticeMode);
@ -1618,6 +1886,7 @@ bool LoadStyles()
gA_StyleSettings[i].bStrafeCountD = view_as<bool>(kv.GetNum("strafe_count_d", true)); gA_StyleSettings[i].bStrafeCountD = view_as<bool>(kv.GetNum("strafe_count_d", true));
gA_StyleSettings[i].fRankingMultiplier = kv.GetFloat("rankingmultiplier", 1.00); gA_StyleSettings[i].fRankingMultiplier = kv.GetFloat("rankingmultiplier", 1.00);
gA_StyleSettings[i].iSpecial = kv.GetNum("special", 0); gA_StyleSettings[i].iSpecial = kv.GetNum("special", 0);
gA_StyleSettings[i].iOrdering = kv.GetNum("ordering", i);
if(!gB_Registered && strlen(gS_StyleStrings[i].sChangeCommand) > 0) if(!gB_Registered && strlen(gS_StyleStrings[i].sChangeCommand) > 0)
{ {
@ -1656,7 +1925,7 @@ bool LoadStyles()
strcopy(gS_StyleOverride[i], 32, (iCount >= 2)? sText[1]:""); strcopy(gS_StyleOverride[i], 32, (iCount >= 2)? sText[1]:"");
} }
i++; gI_OrderedStyles[i] = i++;
} }
while(kv.GotoNextKey()); while(kv.GotoNextKey());
@ -1666,6 +1935,8 @@ bool LoadStyles()
gI_Styles = i; gI_Styles = i;
gB_Registered = true; gB_Registered = true;
SortCustom1D(gI_OrderedStyles, gI_Styles, SortAscending_StyleOrder);
Call_StartForward(gH_Forwards_OnStyleConfigLoaded); Call_StartForward(gH_Forwards_OnStyleConfigLoaded);
Call_PushCell(gI_Styles); Call_PushCell(gI_Styles);
Call_Finish(); Call_Finish();
@ -1673,6 +1944,27 @@ bool LoadStyles()
return true; return true;
} }
public int SortAscending_StyleOrder(int index1, int index2, const int[] array, any hndl)
{
int order1 = gA_StyleSettings[index1].iOrdering;
int order2 = gA_StyleSettings[index2].iOrdering;
if(order1 < order2)
{
return -1;
}
else if(order1 == order2)
{
return 0;
}
else
{
return 1;
}
}
public Action Command_StyleChange(int client, int args) public Action Command_StyleChange(int client, int args)
{ {
char sCommand[128]; char sCommand[128];
@ -2397,3 +2689,17 @@ void UpdateStyleSettings(int client)
SetEntityGravity(client, view_as<float>(gA_StyleSettings[gA_Timers[client].iStyle].fGravityMultiplier)); SetEntityGravity(client, view_as<float>(gA_StyleSettings[gA_Timers[client].iStyle].fGravityMultiplier));
} }
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);
}

View File

@ -39,6 +39,8 @@
#define HUD2_STYLE (1 << 5) #define HUD2_STYLE (1 << 5)
#define HUD2_RANK (1 << 6) #define HUD2_RANK (1 << 6)
#define HUD2_TRACK (1 << 7) #define HUD2_TRACK (1 << 7)
#define HUD2_SPLITPB (1 << 8)
#define HUD2_MAPTIER (1 << 9)
#define HUD_DEFAULT (HUD_MASTER|HUD_CENTER|HUD_ZONEHUD|HUD_OBSERVE|HUD_TOPLEFT|HUD_SYNC|HUD_TIMELEFT|HUD_2DVEL|HUD_SPECTATORS) #define HUD_DEFAULT (HUD_MASTER|HUD_CENTER|HUD_ZONEHUD|HUD_OBSERVE|HUD_TOPLEFT|HUD_SYNC|HUD_TIMELEFT|HUD_2DVEL|HUD_SPECTATORS)
#define HUD_DEFAULT2 0 #define HUD_DEFAULT2 0
@ -83,6 +85,7 @@ EngineVersion gEV_Type = Engine_Unknown;
bool gB_Replay = false; bool gB_Replay = false;
bool gB_Zones = false; bool gB_Zones = false;
bool gB_Sounds = false; bool gB_Sounds = false;
bool gB_Rankings = false;
bool gB_BhopStats = false; bool gB_BhopStats = false;
// cache // cache
@ -90,6 +93,7 @@ int gI_Cycle = 0;
color_t gI_Gradient; color_t gI_Gradient;
int gI_GradientDirection = -1; int gI_GradientDirection = -1;
int gI_Styles = 0; int gI_Styles = 0;
char gS_Map[160];
Handle gH_HUDCookie = null; Handle gH_HUDCookie = null;
Handle gH_HUDCookieMain = null; Handle gH_HUDCookieMain = null;
@ -171,6 +175,7 @@ public void OnPluginStart()
gB_Replay = LibraryExists("shavit-replay"); gB_Replay = LibraryExists("shavit-replay");
gB_Zones = LibraryExists("shavit-zones"); gB_Zones = LibraryExists("shavit-zones");
gB_Sounds = LibraryExists("shavit-sounds"); gB_Sounds = LibraryExists("shavit-sounds");
gB_Rankings = LibraryExists("shavit-rankings");
gB_BhopStats = LibraryExists("bhopstats"); gB_BhopStats = LibraryExists("bhopstats");
// HUD handle // HUD handle
@ -231,6 +236,9 @@ public void OnPluginStart()
public void OnMapStart() public void OnMapStart()
{ {
GetCurrentMap(gS_Map, 160);
GetMapDisplayName(gS_Map, gS_Map, 160);
if(gB_Late) if(gB_Late)
{ {
Shavit_OnStyleConfigLoaded(-1); Shavit_OnStyleConfigLoaded(-1);
@ -255,6 +263,11 @@ public void OnLibraryAdded(const char[] name)
gB_Sounds = true; gB_Sounds = true;
} }
else if(StrEqual(name, "shavit-rankings"))
{
gB_Rankings = true;
}
else if(StrEqual(name, "bhopstats")) else if(StrEqual(name, "bhopstats"))
{ {
gB_BhopStats = true; gB_BhopStats = true;
@ -278,6 +291,11 @@ public void OnLibraryRemoved(const char[] name)
gB_Sounds = false; gB_Sounds = false;
} }
else if(StrEqual(name, "shavit-rankings"))
{
gB_Rankings = false;
}
else if(StrEqual(name, "bhopstats")) else if(StrEqual(name, "bhopstats"))
{ {
gB_BhopStats = false; gB_BhopStats = false;
@ -645,6 +663,17 @@ Action ShowHUDMenu(int client, int item)
FormatEx(sHudItem, 64, "%T", "HudTrackText", client); FormatEx(sHudItem, 64, "%T", "HudTrackText", client);
menu.AddItem(sInfo, sHudItem); menu.AddItem(sInfo, sHudItem);
FormatEx(sInfo, 16, "@%d", HUD2_SPLITPB);
FormatEx(sHudItem, 64, "%T", "HudSplitPbText", client);
menu.AddItem(sInfo, sHudItem);
if(gB_Rankings)
{
FormatEx(sInfo, 16, "@%d", HUD2_MAPTIER);
FormatEx(sHudItem, 64, "%T", "HudMapTierText", client);
menu.AddItem(sInfo, sHudItem);
}
menu.ExitButton = true; menu.ExitButton = true;
menu.DisplayAt(client, item, 60); menu.DisplayAt(client, item, 60);
@ -966,7 +995,23 @@ int AddHUDToBuffer_Source2013(int client, huddata_t data, char[] buffer, int max
if((gI_HUDSettings[client] & HUD_ZONEHUD) > 0 && data.iZoneHUD != ZoneHUD_None) if((gI_HUDSettings[client] & HUD_ZONEHUD) > 0 && data.iZoneHUD != ZoneHUD_None)
{ {
FormatEx(sLine, 128, "%T ", (data.iZoneHUD == ZoneHUD_Start)? "HudInStartZone":"HudInEndZone", client, data.iSpeed); if(gB_Rankings && (gI_HUD2Settings[client] & HUD2_MAPTIER) == 0)
{
FormatEx(sLine, 128, "%T", "HudZoneTier", client, Shavit_GetMapTier(gS_Map));
AddHUDLine(buffer, maxlen, sLine, iLines);
iLines++;
}
if(data.iZoneHUD == ZoneHUD_Start)
{
FormatEx(sLine, 128, "%T ", "HudInStartZone", client, data.iSpeed);
}
else
{
FormatEx(sLine, 128, "%T ", "HudInEndZone", client, data.iSpeed);
}
AddHUDLine(buffer, maxlen, sLine, iLines); AddHUDLine(buffer, maxlen, sLine, iLines);
return ++iLines; return ++iLines;
@ -1095,6 +1140,7 @@ int AddHUDToBuffer_CSGO(int client, huddata_t data, char[] buffer, int maxlen)
{ {
FormatEx(sLine, 128, "%d u/s", data.iSpeed); FormatEx(sLine, 128, "%d u/s", data.iSpeed);
AddHUDLine(buffer, maxlen, sLine, iLines); AddHUDLine(buffer, maxlen, sLine, iLines);
iLines++;
} }
} }
@ -1115,8 +1161,24 @@ int AddHUDToBuffer_CSGO(int client, huddata_t data, char[] buffer, int maxlen)
char sZoneHUD[64]; char sZoneHUD[64];
FormatEx(sZoneHUD, 64, "<span class='fontSize-xxl' color='#%06X'>", ((gI_Gradient.r << 16) + (gI_Gradient.g << 8) + (gI_Gradient.b))); FormatEx(sZoneHUD, 64, "<span class='fontSize-xxl' color='#%06X'>", ((gI_Gradient.r << 16) + (gI_Gradient.g << 8) + (gI_Gradient.b)));
StrCat(buffer, maxlen, sZoneHUD); StrCat(buffer, maxlen, sZoneHUD);
if(data.iZoneHUD == ZoneHUD_Start)
{
if(gB_Rankings && (gI_HUD2Settings[client] & HUD2_MAPTIER) == 0)
{
FormatEx(sZoneHUD, 32, "%T\n\n", "HudZoneTier", client, Shavit_GetMapTier(gS_Map));
AddHUDLine(buffer, maxlen, sZoneHUD, iLines);
iLines++;
}
FormatEx(sZoneHUD, 64, "%T</span>", "HudInStartZoneCSGO", client, data.iSpeed);
}
else
{
FormatEx(sZoneHUD, 64, "%T</span>", "HudInEndZoneCSGO", client, data.iSpeed);
}
FormatEx(sZoneHUD, 64, "%T</span>", (data.iZoneHUD == ZoneHUD_Start)? "HudInStartZoneCSGO":"HudInEndZoneCSGO", client, data.iSpeed);
StrCat(buffer, maxlen, sZoneHUD); StrCat(buffer, maxlen, sZoneHUD);
return ++iLines; return ++iLines;
@ -1219,7 +1281,7 @@ int AddHUDToBuffer_CSGO(int client, huddata_t data, char[] buffer, int maxlen)
StrCat(buffer, maxlen, "</span>"); StrCat(buffer, maxlen, "</span>");
return iLines; return iLines;
} }
void UpdateMainHUD(int client) void UpdateMainHUD(int client)
{ {
@ -1262,7 +1324,7 @@ void UpdateMainHUD(int client)
if(iReplayStyle != -1) if(iReplayStyle != -1)
{ {
fReplayTime = (Shavit_GetReplayTime(iReplayStyle, iReplayTrack) * gA_StyleSettings[iReplayStyle].fTimescale); fReplayTime = Shavit_GetReplayTime(iReplayStyle, iReplayTrack);
fReplayLength = Shavit_GetReplayLength(iReplayStyle, iReplayTrack); fReplayLength = Shavit_GetReplayLength(iReplayStyle, iReplayTrack);
} }
} }
@ -1492,21 +1554,35 @@ void UpdateTopLeftHUD(int client, bool wait)
char sWRName[MAX_NAME_LENGTH]; char sWRName[MAX_NAME_LENGTH];
Shavit_GetWRName(style, sWRName, MAX_NAME_LENGTH, track); Shavit_GetWRName(style, sWRName, MAX_NAME_LENGTH, track);
float fPBTime = Shavit_GetClientPB(target, style, track);
char sPBTime[16];
FormatSeconds(fPBTime, sPBTime, MAX_NAME_LENGTH);
char sTopLeft[128]; char sTopLeft[128];
FormatEx(sTopLeft, 128, "WR: %s (%s)", sWRTime, sWRName);
if(fPBTime != 0.0) float fTargetPB = Shavit_GetClientPB(target, style, track);
char sTargetPB[64];
FormatSeconds(fTargetPB, sTargetPB, 64);
Format(sTargetPB, 64, "%T: %s", "HudBestText", client, sTargetPB);
float fSelfPB = Shavit_GetClientPB(client, style, track);
char sSelfPB[64];
FormatSeconds(fSelfPB, sSelfPB, 64);
Format(sSelfPB, 64, "%T: %s", "HudBestText", client, sSelfPB);
if((gI_HUD2Settings[client] & HUD2_SPLITPB) == 0 && target != client)
{ {
FormatEx(sTopLeft, 128, "WR: %s (%s)\n%T: %s (#%d)", sWRTime, sWRName, "HudBestText", client, sPBTime, Shavit_GetRankForTime(style, fPBTime, track)); if(fTargetPB != 0.0)
{
Format(sTopLeft, 128, "%s\n%s (%N)", sTopLeft, sTargetPB, target);
}
if(fSelfPB != 0.0)
{
Format(sTopLeft, 128, "%s\n%s (%N)", sTopLeft, sSelfPB, client);
}
} }
else else if(fSelfPB != 0.0)
{ {
FormatEx(sTopLeft, 128, "WR: %s (%s)", sWRTime, sWRName); Format(sTopLeft, 128, "%s\n%s (#%d)", sTopLeft, sSelfPB, Shavit_GetRankForTime(style, fSelfPB, track));
} }
SetHudTextParams(0.01, 0.01, 2.5, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0); SetHudTextParams(0.01, 0.01, 2.5, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0);

View File

@ -286,7 +286,7 @@ public void OnPluginStart()
gCV_AdvertisementInterval = CreateConVar("shavit_misc_advertisementinterval", "600.0", "Interval between each chat advertisement.\nConfiguration file for those is configs/shavit-advertisements.cfg.\nSet to 0.0 to disable.\nRequires server restart for changes to take effect.", 0, true, 0.0); gCV_AdvertisementInterval = CreateConVar("shavit_misc_advertisementinterval", "600.0", "Interval between each chat advertisement.\nConfiguration file for those is configs/shavit-advertisements.cfg.\nSet to 0.0 to disable.\nRequires server restart for changes to take effect.", 0, true, 0.0);
gCV_Checkpoints = CreateConVar("shavit_misc_checkpoints", "1", "Allow players to save and teleport to checkpoints.", 0, true, 0.0, true, 1.0); gCV_Checkpoints = CreateConVar("shavit_misc_checkpoints", "1", "Allow players to save and teleport to checkpoints.", 0, true, 0.0, true, 1.0);
gCV_RemoveRagdolls = CreateConVar("shavit_misc_removeragdolls", "1", "Remove ragdolls after death?\n0 - Disabled\n1 - Only remove replay bot ragdolls.\n2 - Remove all ragdolls.", 0, true, 0.0, true, 2.0); gCV_RemoveRagdolls = CreateConVar("shavit_misc_removeragdolls", "1", "Remove ragdolls after death?\n0 - Disabled\n1 - Only remove replay bot ragdolls.\n2 - Remove all ragdolls.", 0, true, 0.0, true, 2.0);
gCV_ClanTag = CreateConVar("shavit_misc_clantag", "{tr}{styletag} :: {time}", "Custom clantag for players.\n0 - Disabled\n{styletag} - style settings from shavit-styles.cfg.\n{style} - style name.\n{time} - formatted time.\n{tr} - first letter of track, if not default.", 0); gCV_ClanTag = CreateConVar("shavit_misc_clantag", "{tr}{styletag} :: {time}", "Custom clantag for players.\n0 - Disabled\n{styletag} - style tag.\n{style} - style name.\n{time} - formatted time.\n{tr} - first letter of track.\n{rank} - player rank.", 0);
gCV_DropAll = CreateConVar("shavit_misc_dropall", "1", "Allow all weapons to be dropped?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_DropAll = CreateConVar("shavit_misc_dropall", "1", "Allow all weapons to be dropped?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0);
gCV_ResetTargetname = CreateConVar("shavit_misc_resettargetname", "0", "Reset the player's targetname upon timer start?\nRecommended to leave disabled. Enable via per-map configs when necessary.\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_ResetTargetname = CreateConVar("shavit_misc_resettargetname", "0", "Reset the player's targetname upon timer start?\nRecommended to leave disabled. Enable via per-map configs when necessary.\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0);
gCV_RestoreStates = CreateConVar("shavit_misc_restorestates", "0", "Save the players' timer/position etc.. when they die/change teams,\nand load the data when they spawn?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); gCV_RestoreStates = CreateConVar("shavit_misc_restorestates", "0", "Save the players' timer/position etc.. when they die/change teams,\nand load the data when they spawn?\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0);
@ -873,12 +873,20 @@ void UpdateClanTag(int client)
GetTrackName(client, track, sTrack, 3); GetTrackName(client, track, sTrack, 3);
} }
char sRank[8];
if(gB_Rankings)
{
IntToString(Shavit_GetRank(client), sRank, 8);
}
char sCustomTag[32]; char sCustomTag[32];
strcopy(sCustomTag, 32, sTag); strcopy(sCustomTag, 32, sTag);
ReplaceString(sCustomTag, 32, "{style}", gS_StyleStrings[gI_Style[client]].sStyleName); ReplaceString(sCustomTag, 32, "{style}", gS_StyleStrings[gI_Style[client]].sStyleName);
ReplaceString(sCustomTag, 32, "{styletag}", gS_StyleStrings[gI_Style[client]].sClanTag); ReplaceString(sCustomTag, 32, "{styletag}", gS_StyleStrings[gI_Style[client]].sClanTag);
ReplaceString(sCustomTag, 32, "{time}", sTime); ReplaceString(sCustomTag, 32, "{time}", sTime);
ReplaceString(sCustomTag, 32, "{tr}", sTrack); ReplaceString(sCustomTag, 32, "{tr}", sTrack);
ReplaceString(sCustomTag, 32, "{rank}", sRank);
CS_SetClientClanTag(client, sCustomTag); CS_SetClientClanTag(client, sCustomTag);
} }
@ -1454,13 +1462,10 @@ public Action Command_Save(int client, int args)
} }
} }
else else if(SaveCheckpoint(client, index, bOverflow))
{ {
if(SaveCheckpoint(client, index, bOverflow)) gA_CheckpointsCache[client].iCurrentCheckpoint = (bOverflow)? iMaxCPs:++gA_CheckpointsCache[client].iCheckpoints;
{ Shavit_PrintToChat(client, "%T", "MiscCheckpointsSaved", client, gA_CheckpointsCache[client].iCurrentCheckpoint, gS_ChatStrings.sVariable, gS_ChatStrings.sText);
gA_CheckpointsCache[client].iCurrentCheckpoint = (bOverflow)? iMaxCPs:++gA_CheckpointsCache[client].iCheckpoints;
Shavit_PrintToChat(client, "%T", "MiscCheckpointsSaved", client, gA_CheckpointsCache[client].iCurrentCheckpoint, gS_ChatStrings.sVariable, gS_ChatStrings.sText);
}
} }
return Plugin_Handled; return Plugin_Handled;
@ -1591,8 +1596,10 @@ public int MenuHandler_Checkpoints(Menu menu, MenuAction action, int param1, int
return 0; return 0;
} }
SaveCheckpoint(param1, ++gA_CheckpointsCache[param1].iCheckpoints); if(SaveCheckpoint(param1, gA_CheckpointsCache[param1].iCheckpoints + 1))
gA_CheckpointsCache[param1].iCurrentCheckpoint = gA_CheckpointsCache[param1].iCheckpoints; {
gA_CheckpointsCache[param1].iCurrentCheckpoint = ++gA_CheckpointsCache[param1].iCheckpoints;
}
} }
else else
@ -1692,6 +1699,13 @@ bool SaveCheckpoint(int client, int index, bool overflow = false)
return false; return false;
} }
else if(Shavit_IsPaused(client) || Shavit_IsPaused(target))
{
Shavit_PrintToChat(client, "%T", "CommandNoPause", client, gS_ChatStrings.sVariable, gS_ChatStrings.sText);
return false;
}
char sKey[32]; char sKey[32];
int iSerial = GetClientSerial(client); int iSerial = GetClientSerial(client);
FormatEx(sKey, 32, "%d_%d", iSerial, index); FormatEx(sKey, 32, "%d_%d", iSerial, index);
@ -1893,6 +1907,13 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage)
return; return;
} }
else if(Shavit_IsPaused(client))
{
Shavit_PrintToChat(client, "%T", "CommandNoPause", client, gS_ChatStrings.sVariable, gS_ChatStrings.sText);
return;
}
float pos[3]; float pos[3];
CopyArray(cpcache.fPosition, pos, 3); CopyArray(cpcache.fPosition, pos, 3);
@ -1916,6 +1937,7 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage)
timer_snapshot_t snapshot; timer_snapshot_t snapshot;
CopyArray(cpcache.aSnapshot, snapshot, sizeof(timer_snapshot_t)); CopyArray(cpcache.aSnapshot, snapshot, sizeof(timer_snapshot_t));
Shavit_LoadSnapshot(client, snapshot); Shavit_LoadSnapshot(client, snapshot);
Shavit_ResumeTimer(client);
float ang[3]; float ang[3];
CopyArray(cpcache.fAngles, ang, 3); CopyArray(cpcache.fAngles, ang, 3);
@ -1936,6 +1958,22 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage)
vel = NULL_VECTOR; vel = NULL_VECTOR;
} }
if(cpcache.iTargetname != -1)
{
char sTargetname[64];
gA_Targetnames.GetString(cpcache.iTargetname, sTargetname, 64);
SetEntPropString(client, Prop_Data, "m_iName", sTargetname);
}
if(cpcache.iClassname != -1)
{
char sClassname[64];
gA_Classnames.GetString(cpcache.iClassname, sClassname, 64);
SetEntPropString(client, Prop_Data, "m_iClassname", sClassname);
}
TeleportEntity(client, pos, TeleportEntity(client, pos,
((gI_CheckpointsSettings[client] & CP_ANGLES) > 0 || cpcache.bSegmented)? ang:NULL_VECTOR, ((gI_CheckpointsSettings[client] & CP_ANGLES) > 0 || cpcache.bSegmented)? ang:NULL_VECTOR,
vel); vel);
@ -1958,26 +1996,6 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage)
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", cpcache.fSpeed); SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", cpcache.fSpeed);
SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", cpcache.iGroundEntity); SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", cpcache.iGroundEntity);
int iTargetname = gA_Targetnames.FindValue(cpcache.iTargetname);
if(iTargetname != -1)
{
char sTargetname[64];
gA_Targetnames.GetString(iTargetname, sTargetname, 64);
SetEntPropString(client, Prop_Data, "m_iName", sTargetname);
}
int iClassname = gA_Classnames.FindValue(cpcache.iClassname);
if(iClassname != -1)
{
char sClassname[64];
gA_Classnames.GetString(iClassname, sClassname, 64);
SetEntPropString(client, Prop_Data, "m_iClassname", sClassname);
}
if(gEV_Type != Engine_TF2) if(gEV_Type != Engine_TF2)
{ {
SetEntPropFloat(client, Prop_Send, "m_flStamina", cpcache.fStamina); SetEntPropFloat(client, Prop_Send, "m_flStamina", cpcache.fStamina);
@ -2703,6 +2721,7 @@ void LoadState(int client)
Shavit_LoadSnapshot(client, gA_SaveStates[client]); Shavit_LoadSnapshot(client, gA_SaveStates[client]);
Shavit_SetPracticeMode(client, gB_SaveStatesSegmented[client], false); Shavit_SetPracticeMode(client, gB_SaveStatesSegmented[client], false);
Shavit_ResumeTimer(client);
if(gB_Replay && gA_SaveFrames[client] != null) if(gB_Replay && gA_SaveFrames[client] != null)
{ {

View File

@ -73,6 +73,7 @@ int gI_RankedPlayers = 0;
Menu gH_Top100Menu = null; Menu gH_Top100Menu = null;
Handle gH_Forwards_OnTierAssigned = null; Handle gH_Forwards_OnTierAssigned = null;
Handle gH_Forwards_OnRankAssigned = null;
// Timer settings. // Timer settings.
chatstrings_t gS_ChatStrings; chatstrings_t gS_ChatStrings;
@ -127,6 +128,7 @@ public void OnAllPluginsLoaded()
public void OnPluginStart() public void OnPluginStart()
{ {
gH_Forwards_OnTierAssigned = CreateGlobalForward("Shavit_OnTierAssigned", ET_Event, Param_String, Param_Cell); gH_Forwards_OnTierAssigned = CreateGlobalForward("Shavit_OnTierAssigned", ET_Event, Param_String, Param_Cell);
gH_Forwards_OnRankAssigned = CreateGlobalForward("Shavit_OnRankAssigned", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
RegConsoleCmd("sm_tier", Command_Tier, "Prints the map's tier to chat."); RegConsoleCmd("sm_tier", Command_Tier, "Prints the map's tier to chat.");
RegConsoleCmd("sm_maptier", Command_Tier, "Prints the map's tier to chat. (sm_tier alias)"); RegConsoleCmd("sm_maptier", Command_Tier, "Prints the map's tier to chat. (sm_tier alias)");
@ -387,7 +389,7 @@ public void OnClientPostAdminCheck(int client)
{ {
if(!IsFakeClient(client)) if(!IsFakeClient(client))
{ {
UpdatePlayerRank(client); UpdatePlayerRank(client, true);
} }
} }
@ -705,7 +707,7 @@ public void Trans_OnRecalcSuccess(Database db, any data, int numQueries, DBResul
{ {
if(IsClientInGame(i) && IsClientAuthorized(i)) if(IsClientInGame(i) && IsClientAuthorized(i))
{ {
UpdatePlayerRank(i); UpdatePlayerRank(i, false);
} }
} }
@ -780,7 +782,9 @@ void UpdateAllPoints()
#endif #endif
char sQuery[128]; char sQuery[128];
FormatEx(sQuery, 128, "UPDATE %susers SET points = GetWeightedPoints(auth);", gS_MySQLPrefix); FormatEx(sQuery, 128, "UPDATE %susers SET points = GetWeightedPoints(auth);",
gS_MySQLPrefix);
gH_SQL.Query(SQL_UpdateAllPoints_Callback, sQuery); gH_SQL.Query(SQL_UpdateAllPoints_Callback, sQuery);
} }
@ -794,7 +798,7 @@ public void SQL_UpdateAllPoints_Callback(Database db, DBResultSet results, const
} }
} }
void UpdatePlayerRank(int client) void UpdatePlayerRank(int client, bool first)
{ {
gI_Rank[client] = 0; gI_Rank[client] = 0;
gF_Points[client] = 0.0; gF_Points[client] = 0.0;
@ -809,12 +813,23 @@ void UpdatePlayerRank(int client)
FormatEx(sQuery, 512, "SELECT p.points, COUNT(*) rank FROM %susers u JOIN (SELECT points FROM %susers WHERE auth = '%s' LIMIT 1) p WHERE u.points >= p.points LIMIT 1;", FormatEx(sQuery, 512, "SELECT p.points, COUNT(*) rank FROM %susers u JOIN (SELECT points FROM %susers WHERE auth = '%s' LIMIT 1) p WHERE u.points >= p.points LIMIT 1;",
gS_MySQLPrefix, gS_MySQLPrefix, sAuthID); gS_MySQLPrefix, gS_MySQLPrefix, sAuthID);
gH_SQL.Query(SQL_UpdatePlayerRank_Callback, sQuery, GetClientSerial(client), DBPrio_Low); DataPack hPack = new DataPack();
hPack.WriteCell(GetClientSerial(client));
hPack.WriteCell(first);
gH_SQL.Query(SQL_UpdatePlayerRank_Callback, sQuery, hPack, DBPrio_Low);
} }
} }
public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, const char[] error, any data) public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, const char[] error, any data)
{ {
DataPack hPack = view_as<DataPack>(data);
hPack.Reset();
int iSerial = hPack.ReadCell();
bool bFirst = view_as<bool>(hPack.ReadCell());
delete hPack;
if(results == null) if(results == null)
{ {
LogError("Timer (rankings, update player rank) error! Reason: %s", error); LogError("Timer (rankings, update player rank) error! Reason: %s", error);
@ -822,7 +837,7 @@ public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, cons
return; return;
} }
int client = GetClientFromSerial(data); int client = GetClientFromSerial(iSerial);
if(client == 0) if(client == 0)
{ {
@ -833,13 +848,22 @@ public void SQL_UpdatePlayerRank_Callback(Database db, DBResultSet results, cons
{ {
gF_Points[client] = results.FetchFloat(0); gF_Points[client] = results.FetchFloat(0);
gI_Rank[client] = (gF_Points[client] > 0.0)? results.FetchInt(1):0; gI_Rank[client] = (gF_Points[client] > 0.0)? results.FetchInt(1):0;
Call_StartForward(gH_Forwards_OnRankAssigned);
Call_PushCell(client);
Call_PushCell(gI_Rank[client]);
Call_PushCell(gF_Points[client]);
Call_PushCell(bFirst);
Call_Finish();
} }
} }
void UpdateRankedPlayers() void UpdateRankedPlayers()
{ {
char sQuery[512]; char sQuery[512];
FormatEx(sQuery, 512, "SELECT COUNT(*) count FROM %susers WHERE points > 0.0;", gS_MySQLPrefix); FormatEx(sQuery, 512, "SELECT COUNT(*) count FROM %susers WHERE points > 0.0;",
gS_MySQLPrefix);
gH_SQL.Query(SQL_UpdateRankedPlayers_Callback, sQuery, 0, DBPrio_High); gH_SQL.Query(SQL_UpdateRankedPlayers_Callback, sQuery, 0, DBPrio_High);
} }

View File

@ -29,6 +29,7 @@
#undef REQUIRE_EXTENSIONS #undef REQUIRE_EXTENSIONS
#include <cstrike> #include <cstrike>
#include <tf2> #include <tf2>
#include <tf2_stocks>
#define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}" #define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}"
#define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}" #define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}"
@ -48,6 +49,7 @@ enum struct centralbot_cache_t
int iStyle; int iStyle;
ReplayStatus iReplayStatus; ReplayStatus iReplayStatus;
int iTrack; int iTrack;
int iPlaybackSerial;
} }
enum struct replaystrings_t enum struct replaystrings_t
@ -87,12 +89,15 @@ ArrayList gA_Frames[STYLE_LIMIT][TRACKS_SIZE];
float gF_StartTick[STYLE_LIMIT]; float gF_StartTick[STYLE_LIMIT];
ReplayStatus gRS_ReplayStatus[STYLE_LIMIT]; ReplayStatus gRS_ReplayStatus[STYLE_LIMIT];
framecache_t gA_FrameCache[STYLE_LIMIT][TRACKS_SIZE]; framecache_t gA_FrameCache[STYLE_LIMIT][TRACKS_SIZE];
bool gB_ForciblyStopped = false; bool gB_ForciblyStopped = false;
Handle gH_ReplayTimers[STYLE_LIMIT];
bool gB_Button[MAXPLAYERS+1]; bool gB_Button[MAXPLAYERS+1];
int gI_PlayerFrames[MAXPLAYERS+1]; int gI_PlayerFrames[MAXPLAYERS+1];
ArrayList gA_PlayerFrames[MAXPLAYERS+1]; ArrayList gA_PlayerFrames[MAXPLAYERS+1];
int gI_Track[MAXPLAYERS+1]; int gI_Track[MAXPLAYERS+1];
float gF_LastInteraction[MAXPLAYERS+1];
bool gB_Late = false; bool gB_Late = false;
@ -122,6 +127,8 @@ ConVar gCV_CentralBot = null;
ConVar gCV_BotShooting = null; ConVar gCV_BotShooting = null;
ConVar gCV_BotPlusUse = null; ConVar gCV_BotPlusUse = null;
ConVar gCV_BotWeapon = null; ConVar gCV_BotWeapon = null;
ConVar gCV_PlaybackCanStop = null;
ConVar gCV_PlaybackCooldown = null;
// timer settings // timer settings
int gI_Styles = 0; int gI_Styles = 0;
@ -153,6 +160,7 @@ public Plugin myinfo =
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{ {
CreateNative("Shavit_DeleteReplay", Native_DeleteReplay);
CreateNative("Shavit_GetReplayBotCurrentFrame", Native_GetReplayBotIndex); CreateNative("Shavit_GetReplayBotCurrentFrame", Native_GetReplayBotIndex);
CreateNative("Shavit_GetReplayBotFirstFrame", Native_GetReplayBotFirstFrame); CreateNative("Shavit_GetReplayBotFirstFrame", Native_GetReplayBotFirstFrame);
CreateNative("Shavit_GetReplayBotIndex", Native_GetReplayBotIndex); CreateNative("Shavit_GetReplayBotIndex", Native_GetReplayBotIndex);
@ -223,6 +231,8 @@ public void OnPluginStart()
gCV_BotShooting = CreateConVar("shavit_replay_botshooting", "3", "Attacking buttons to allow for bots.\n0 - none\n1 - +attack\n2 - +attack2\n3 - both", 0, true, 0.0, true, 3.0); gCV_BotShooting = CreateConVar("shavit_replay_botshooting", "3", "Attacking buttons to allow for bots.\n0 - none\n1 - +attack\n2 - +attack2\n3 - both", 0, true, 0.0, true, 3.0);
gCV_BotPlusUse = CreateConVar("shavit_replay_botplususe", "1", "Allow bots to use +use?", 0, true, 0.0, true, 1.0); gCV_BotPlusUse = CreateConVar("shavit_replay_botplususe", "1", "Allow bots to use +use?", 0, true, 0.0, true, 1.0);
gCV_BotWeapon = CreateConVar("shavit_replay_botweapon", "", "Choose which weapon the bot will hold.\nLeave empty to use the default.\nSet to \"none\" to have none.\nExample: weapon_usp"); gCV_BotWeapon = CreateConVar("shavit_replay_botweapon", "", "Choose which weapon the bot will hold.\nLeave empty to use the default.\nSet to \"none\" to have none.\nExample: weapon_usp");
gCV_PlaybackCanStop = CreateConVar("shavit_replay_pbcanstop", "1", "Allow players to stop playback if they requested it?", 0, true, 0.0, true, 1.0);
gCV_PlaybackCooldown = CreateConVar("shavit_replay_pbcooldown", "10.0", "Cooldown in seconds to apply for players between each playback they request/stop.\nDoes not apply to RCON admins.", 0, true, 0.0);
gCV_CentralBot.AddChangeHook(OnConVarChanged); gCV_CentralBot.AddChangeHook(OnConVarChanged);
@ -311,6 +321,53 @@ public void AdminMenu_DeleteReplay(Handle topmenu, TopMenuAction action, TopMenu
} }
} }
void UnloadReplay(int style, int track)
{
if(gCV_CentralBot.BoolValue && gA_CentralCache.iStyle == style && gA_CentralCache.iTrack == track)
{
StopCentralReplay(0);
}
gA_Frames[style][track].Clear();
gA_FrameCache[style][track].iFrameCount = 0;
gA_FrameCache[style][track].fTime = 0.0;
gA_FrameCache[style][track].bNewFormat = true;
strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, "invalid");
gI_ReplayTick[style] = -1;
if(gI_ReplayBotClient[style] != 0)
{
UpdateReplayInfo(gI_ReplayBotClient[style], style, 0.0, track);
}
}
public int Native_DeleteReplay(Handle handler, int numParams)
{
char sMap[160];
GetNativeString(1, sMap, 160);
int iStyle = GetNativeCell(2);
int iTrack = GetNativeCell(3);
char sTrack[4];
FormatEx(sTrack, 4, "_%d", iTrack);
char sPath[PLATFORM_MAX_PATH];
FormatEx(sPath, PLATFORM_MAX_PATH, "%s/%d/%s%s.replay", gS_ReplayFolder, iStyle, gS_Map, (iTrack > 0)? sTrack:"");
if(!FileExists(sPath) || !DeleteFile(sPath))
{
return false;
}
if(StrEqual(sMap, gS_Map))
{
UnloadReplay(iStyle, iTrack);
}
return true;
}
public int Native_GetReplayBotFirstFrame(Handle handler, int numParams) public int Native_GetReplayBotFirstFrame(Handle handler, int numParams)
{ {
SetNativeCellRef(2, gF_StartTick[GetNativeCell(1)]); SetNativeCellRef(2, gF_StartTick[GetNativeCell(1)]);
@ -362,7 +419,7 @@ public int Native_ReloadReplay(Handle handler, int numParams)
gA_Frames[style][track] = new ArrayList(CELLS_PER_FRAME); gA_Frames[style][track] = new ArrayList(CELLS_PER_FRAME);
gA_FrameCache[style][track].iFrameCount = 0; gA_FrameCache[style][track].iFrameCount = 0;
gA_FrameCache[style][track].fTime = 0.0; gA_FrameCache[style][track].fTime = 0.0;
gA_FrameCache[style][track].bNewFormat = false; gA_FrameCache[style][track].bNewFormat = true;
strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, "invalid"); strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, "invalid");
bool loaded = false; bool loaded = false;
@ -397,7 +454,9 @@ public int Native_ReloadReplay(Handle handler, int numParams)
{ {
gI_ReplayTick[style] = 0; gI_ReplayTick[style] = 0;
gRS_ReplayStatus[style] = Replay_Start; gRS_ReplayStatus[style] = Replay_Start;
CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE);
delete gH_ReplayTimers[style];
gH_ReplayTimers[style] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE);
} }
} }
@ -494,7 +553,7 @@ public int Native_GetReplayTime(Handle handler, int numParams)
return view_as<int>(GetReplayLength(Track_Main, track)); return view_as<int>(GetReplayLength(Track_Main, track));
} }
return view_as<int>(float(gI_ReplayTick[style]) / gF_Tickrate); return view_as<int>(float(gI_ReplayTick[style]) / gF_Tickrate * gA_StyleSettings[style].fTimescale);
} }
public int Native_HijackAngles(Handle handler, int numParams) public int Native_HijackAngles(Handle handler, int numParams)
@ -693,6 +752,13 @@ public void OnMapStart()
SetFailState("Could not load the replay bots' configuration file. Make sure it exists (addons/sourcemod/configs/shavit-replay.cfg) and follows the proper syntax!"); SetFailState("Could not load the replay bots' configuration file. Make sure it exists (addons/sourcemod/configs/shavit-replay.cfg) and follows the proper syntax!");
} }
// VERY RARE CASE
// this is required to not cause replays to break if we change map before playback starts, after it is requested
for(int i = 0; i < STYLE_LIMIT; i++)
{
gH_ReplayTimers[i] = null;
}
if(gB_Late) if(gB_Late)
{ {
Shavit_OnStyleConfigLoaded(-1); Shavit_OnStyleConfigLoaded(-1);
@ -703,6 +769,7 @@ public void OnMapStart()
gA_CentralCache.iStyle = -1; gA_CentralCache.iStyle = -1;
gA_CentralCache.iReplayStatus = Replay_Idle; gA_CentralCache.iReplayStatus = Replay_Idle;
gA_CentralCache.iTrack = Track_Main; gA_CentralCache.iTrack = Track_Main;
gA_CentralCache.iPlaybackSerial = 0;
gB_ForciblyStopped = false; gB_ForciblyStopped = false;
@ -714,13 +781,6 @@ public void OnMapStart()
return; return;
} }
bot_quota = FindConVar((gEV_Type != Engine_TF2)? "bot_quota":"tf_bot_quota");
if(bot_quota != null)
{
bot_quota.Flags &= ~FCVAR_NOTIFY;
}
char sTempMap[PLATFORM_MAX_PATH]; char sTempMap[PLATFORM_MAX_PATH];
FormatEx(sTempMap, PLATFORM_MAX_PATH, "maps/%s.nav", gS_Map); FormatEx(sTempMap, PLATFORM_MAX_PATH, "maps/%s.nav", gS_Map);
@ -743,60 +803,18 @@ public void OnMapStart()
if(bot_controllable != null) if(bot_controllable != null)
{ {
bot_controllable.BoolValue = false; bot_controllable.BoolValue = false;
delete bot_controllable;
} }
ConVar bot_stop = FindConVar("bot_stop"); FindConVar((gEV_Type != Engine_TF2)? "bot_quota":"tf_bot_quota").Flags &= ~FCVAR_NOTIFY;
FindConVar("bot_stop").Flags &= ~FCVAR_CHEAT;
if(bot_stop != null) FindConVar("bot_stop").BoolValue = true;
{ FindConVar((gEV_Type != Engine_TF2)? "bot_quota_mode":"tf_bot_quota_mode").SetString("normal");
bot_stop.BoolValue = true; FindConVar("mp_limitteams").IntValue = 0;
delete bot_stop; FindConVar((gEV_Type != Engine_TF2)? "bot_join_after_player":"tf_bot_join_after_player").BoolValue = false;
} FindConVar("bot_chatter").SetString("off");
FindConVar("bot_flipout").BoolValue = true;
ConVar bot_quota_mode = FindConVar((gEV_Type != Engine_TF2)? "bot_quota_mode":"tf_bot_quota_mode"); FindConVar("bot_zombie").BoolValue = true;
FindConVar("mp_autoteambalance").BoolValue = false;
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"); ServerCommand((gEV_Type != Engine_TF2)? "bot_kick":"tf_bot_kick all");
@ -834,7 +852,7 @@ public void OnMapStart()
gA_Frames[i][j] = new ArrayList(CELLS_PER_FRAME); gA_Frames[i][j] = new ArrayList(CELLS_PER_FRAME);
gA_FrameCache[i][j].iFrameCount = 0; gA_FrameCache[i][j].iFrameCount = 0;
gA_FrameCache[i][j].fTime = 0.0; gA_FrameCache[i][j].fTime = 0.0;
gA_FrameCache[i][j].bNewFormat = false; gA_FrameCache[i][j].bNewFormat = true;
strcopy(gA_FrameCache[i][j].sReplayName, MAX_NAME_LENGTH, "invalid"); strcopy(gA_FrameCache[i][j].sReplayName, MAX_NAME_LENGTH, "invalid");
loaded = DefaultLoadReplay(i, j); loaded = DefaultLoadReplay(i, j);
@ -849,7 +867,9 @@ public void OnMapStart()
{ {
gI_ReplayTick[i] = 0; gI_ReplayTick[i] = 0;
gRS_ReplayStatus[i] = Replay_Start; gRS_ReplayStatus[i] = Replay_Start;
CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, i, TIMER_FLAG_NO_MAPCHANGE);
delete gH_ReplayTimers[i];
gH_ReplayTimers[i] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, i, TIMER_FLAG_NO_MAPCHANGE);
} }
} }
} }
@ -1111,22 +1131,7 @@ bool DeleteReplay(int style, int track)
return false; return false;
} }
if(gCV_CentralBot.BoolValue && gA_CentralCache.iStyle == style && gA_CentralCache.iTrack == track) UnloadReplay(style, track);
{
StopCentralReplay(0);
}
gA_Frames[style][track].Clear();
gA_FrameCache[style][track].iFrameCount = 0;
gA_FrameCache[style][track].fTime = 0.0;
gA_FrameCache[style][track].bNewFormat = false;
strcopy(gA_FrameCache[style][track].sReplayName, MAX_NAME_LENGTH, "invalid");
gI_ReplayTick[style] = -1;
if(gI_ReplayBotClient[style] != 0)
{
UpdateReplayInfo(gI_ReplayBotClient[style], style, 0.0, track);
}
return true; return true;
} }
@ -1616,7 +1621,7 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
float vecVelocity[3]; float vecVelocity[3];
MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity); MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity);
ScaleVector(vecVelocity, gF_Tickrate); ScaleVector(vecVelocity, gF_Tickrate);
TeleportEntity(client, NULL_VECTOR, vecAngles, vecVelocity); TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, vecVelocity);
} }
return Plugin_Changed; return Plugin_Changed;
@ -1627,7 +1632,9 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
gI_ReplayTick[style] = 0; gI_ReplayTick[style] = 0;
gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_End; gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_End;
CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_EndReplay, style, TIMER_FLAG_NO_MAPCHANGE); delete gH_ReplayTimers[style];
gH_ReplayTimers[style] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_EndReplay, style, TIMER_FLAG_NO_MAPCHANGE);
gA_CentralCache.iPlaybackSerial = 0;
return Plugin_Changed; return Plugin_Changed;
} }
@ -1677,9 +1684,9 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
MoveType movetype = gA_Frames[style][track].Get(gI_ReplayTick[style], 7); MoveType movetype = gA_Frames[style][track].Get(gI_ReplayTick[style], 7);
if(movetype == MOVETYPE_LADDER) if(movetype == MOVETYPE_LADDER || (movetype == MOVETYPE_WALK && (iReplayFlags & FL_ONGROUND) > 0))
{ {
mt = MOVETYPE_LADDER; mt = movetype;
} }
} }
@ -1689,33 +1696,16 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity); MakeVectorFromPoints(vecCurrentPosition, vecPosition, vecVelocity);
ScaleVector(vecVelocity, gF_Tickrate); ScaleVector(vecVelocity, gF_Tickrate);
if(gI_ReplayTick[style] > 1) if(gI_ReplayTick[style] > 1 &&
// replay is going above 50k speed, just teleport at this point
(GetVectorLength(vecVelocity) > 50000.0 ||
// bot is on ground.. if the distance between the previous position is much bigger (1.5x) than the expected according
// to the bot's velocity, teleport to avoid sync issues
(mt == MOVETYPE_WALK && GetVectorDistance(vecCurrentPosition, vecPosition) > GetVectorLength(vecVelocity) / gF_Tickrate * 1.5)))
{ {
float vecLastPosition[3]; TeleportEntity(client, vecPosition, vecAngles, NULL_VECTOR);
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);
// fix for replay not syncing return Plugin_Changed;
if(GetVectorDistance(vecLastPosition, vecCurrentPosition) >= 100.0 && IsWallBetween(vecLastPosition, vecCurrentPosition, client))
{
TeleportEntity(client, vecPosition, NULL_VECTOR, NULL_VECTOR);
return Plugin_Handled;
}
#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); TeleportEntity(client, NULL_VECTOR, vecAngles, vecVelocity);
@ -1770,20 +1760,19 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3
return Plugin_Continue; return Plugin_Continue;
} }
public bool Filter_Clients(int entity, int contentsMask, any data)
{
return (1 <= entity <= MaxClients && entity != data);
}
bool IsWallBetween(float pos1[3], float pos2[3], int bot)
{
TR_TraceRayFilter(pos1, pos2, MASK_SOLID, RayType_EndPoint, Filter_Clients, bot);
return !TR_DidHit();
}
public Action Timer_EndReplay(Handle Timer, any data) public Action Timer_EndReplay(Handle Timer, any data)
{ {
gH_ReplayTimers[data] = null;
int client = GetClientFromSerial(gA_CentralCache.iPlaybackSerial);
if(client != 0)
{
gF_LastInteraction[client] = GetEngineTime();
}
gA_CentralCache.iPlaybackSerial = 0;
if(gCV_CentralBot.BoolValue && gB_ForciblyStopped) if(gCV_CentralBot.BoolValue && gB_ForciblyStopped)
{ {
gB_ForciblyStopped = false; gB_ForciblyStopped = false;
@ -1801,7 +1790,8 @@ public Action Timer_EndReplay(Handle Timer, any data)
{ {
gRS_ReplayStatus[data] = Replay_Start; gRS_ReplayStatus[data] = Replay_Start;
CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, data, TIMER_FLAG_NO_MAPCHANGE); delete gH_ReplayTimers[data];
gH_ReplayTimers[data] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, data, TIMER_FLAG_NO_MAPCHANGE);
} }
else else
@ -1815,6 +1805,8 @@ public Action Timer_EndReplay(Handle Timer, any data)
public Action Timer_StartReplay(Handle Timer, any data) public Action Timer_StartReplay(Handle Timer, any data)
{ {
gH_ReplayTimers[data] = null;
if(gRS_ReplayStatus[data] == Replay_Running || (gCV_CentralBot.BoolValue && gB_ForciblyStopped)) if(gRS_ReplayStatus[data] == Replay_Running || (gCV_CentralBot.BoolValue && gB_ForciblyStopped))
{ {
return Plugin_Stop; return Plugin_Stop;
@ -1964,24 +1956,29 @@ public Action Command_DeleteReplay(int client, int args)
Menu menu = new Menu(DeleteReplay_Callback); Menu menu = new Menu(DeleteReplay_Callback);
menu.SetTitle("%T", "DeleteReplayMenuTitle", client); menu.SetTitle("%T", "DeleteReplayMenuTitle", client);
int[] styles = new int[gI_Styles];
Shavit_GetOrderedStyles(styles, gI_Styles);
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
if(!ReplayEnabled(i)) int iStyle = styles[i];
if(!ReplayEnabled(iStyle))
{ {
continue; continue;
} }
for(int j = 0; j < TRACKS_SIZE; j++) for(int j = 0; j < TRACKS_SIZE; j++)
{ {
if(gA_FrameCache[i][j].iFrameCount == 0) if(gA_FrameCache[iStyle][j].iFrameCount == 0)
{ {
continue; continue;
} }
char sInfo[8]; char sInfo[8];
FormatEx(sInfo, 8, "%d;%d", i, j); FormatEx(sInfo, 8, "%d;%d", iStyle, j);
float time = GetReplayLength(i, j); float time = GetReplayLength(iStyle, j);
char sTrack[32]; char sTrack[32];
GetTrackName(client, j, sTrack, 32); GetTrackName(client, j, sTrack, 32);
@ -1993,12 +1990,12 @@ public Action Command_DeleteReplay(int client, int args)
char sTime[32]; char sTime[32];
FormatSeconds(time, sTime, 32, false); FormatSeconds(time, sTime, 32, false);
FormatEx(sDisplay, 64, "%s (%s) - %s", gS_StyleStrings[i].sStyleName, sTrack, sTime); FormatEx(sDisplay, 64, "%s (%s) - %s", gS_StyleStrings[iStyle].sStyleName, sTrack, sTime);
} }
else else
{ {
FormatEx(sDisplay, 64, "%s (%s)", gS_StyleStrings[i].sStyleName, sTrack); FormatEx(sDisplay, 64, "%s (%s)", gS_StyleStrings[iStyle].sStyleName, sTrack);
} }
menu.AddItem(sInfo, sDisplay); menu.AddItem(sInfo, sDisplay);
@ -2108,14 +2105,29 @@ public Action Command_Replay(int client, int args)
return Plugin_Handled; return Plugin_Handled;
} }
if(GetClientTeam(client) != 1 || GetSpectatorTarget(client) != gA_CentralCache.iClient) if(GetClientTeam(client) > 1)
{
if(gEV_Type == Engine_TF2)
{
TF2_ChangeClientTeam(client, TFTeam_Spectator);
}
else
{
ChangeClientTeam(client, CS_TEAM_SPECTATOR);
}
SetEntPropEnt(client, Prop_Send, "m_hObserverTarget", gA_CentralCache.iClient);
}
else if(GetSpectatorTarget(client) != gA_CentralCache.iClient)
{ {
Shavit_PrintToChat(client, "%T", "CentralReplaySpectator", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText, gS_ChatStrings.sVariable, gS_ChatStrings.sText); Shavit_PrintToChat(client, "%T", "CentralReplaySpectator", client, gS_ChatStrings.sWarning, gS_ChatStrings.sText, gS_ChatStrings.sVariable, gS_ChatStrings.sText);
return Plugin_Handled; return Plugin_Handled;
} }
if(CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON)) if(CanStopCentral(client))
{ {
char arg[8]; char arg[8];
GetCmdArg(1, arg, 8); GetCmdArg(1, arg, 8);
@ -2194,7 +2206,7 @@ void OpenReplaySubMenu(int client, int track)
Menu menu = new Menu(MenuHandler_ReplaySubmenu); Menu menu = new Menu(MenuHandler_ReplaySubmenu);
menu.SetTitle("%T (%s)\n ", "CentralReplayTitle", client, sTrack); menu.SetTitle("%T (%s)\n ", "CentralReplayTitle", client, sTrack);
if(CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON)) if(CanStopCentral(client))
{ {
char sDisplay[64]; char sDisplay[64];
FormatEx(sDisplay, 64, "%T", "CentralReplayStop", client); FormatEx(sDisplay, 64, "%T", "CentralReplayStop", client);
@ -2202,17 +2214,22 @@ void OpenReplaySubMenu(int client, int track)
menu.AddItem("stop", sDisplay, (gA_CentralCache.iReplayStatus != Replay_Idle)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); menu.AddItem("stop", sDisplay, (gA_CentralCache.iReplayStatus != Replay_Idle)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
} }
int[] styles = new int[gI_Styles];
Shavit_GetOrderedStyles(styles, gI_Styles);
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
if(!ReplayEnabled(i)) int iStyle = styles[i];
if(!ReplayEnabled(iStyle))
{ {
continue; continue;
} }
char sInfo[8]; char sInfo[8];
IntToString(i, sInfo, 8); IntToString(iStyle, sInfo, 8);
float time = GetReplayLength(i, track); float time = GetReplayLength(iStyle, track);
char sDisplay[64]; char sDisplay[64];
@ -2221,15 +2238,15 @@ void OpenReplaySubMenu(int client, int track)
char sTime[32]; char sTime[32];
FormatSeconds(time, sTime, 32, false); FormatSeconds(time, sTime, 32, false);
FormatEx(sDisplay, 64, "%s - %s", gS_StyleStrings[i].sStyleName, sTime); FormatEx(sDisplay, 64, "%s - %s", gS_StyleStrings[iStyle].sStyleName, sTime);
} }
else else
{ {
strcopy(sDisplay, 64, gS_StyleStrings[i].sStyleName); strcopy(sDisplay, 64, gS_StyleStrings[iStyle].sStyleName);
} }
menu.AddItem(sInfo, sDisplay, (gA_FrameCache[i][track].iFrameCount > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); menu.AddItem(sInfo, sDisplay, (gA_FrameCache[iStyle][track].iFrameCount > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
} }
if(menu.ItemCount == 0) if(menu.ItemCount == 0)
@ -2253,7 +2270,7 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i
char info[16]; char info[16];
menu.GetItem(param2, info, 16); menu.GetItem(param2, info, 16);
if(StrEqual(info, "stop")) if(StrEqual(info, "stop") && CanStopCentral(param1))
{ {
StopCentralReplay(param1); StopCentralReplay(param1);
OpenReplaySubMenu(param1, gI_Track[param1]); OpenReplaySubMenu(param1, gI_Track[param1]);
@ -2280,6 +2297,8 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i
gI_ReplayTick[style] = 0; gI_ReplayTick[style] = 0;
gA_CentralCache.iStyle = style; gA_CentralCache.iStyle = style;
gA_CentralCache.iTrack = gI_Track[param1]; gA_CentralCache.iTrack = gI_Track[param1];
gA_CentralCache.iPlaybackSerial = GetClientSerial(param1);
gF_LastInteraction[param1] = GetEngineTime();
gI_ReplayBotClient[style] = gA_CentralCache.iClient; gI_ReplayBotClient[style] = gA_CentralCache.iClient;
gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_Start; gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_Start;
TeleportToStart(gA_CentralCache.iClient, style, gI_Track[param1]); TeleportToStart(gA_CentralCache.iClient, style, gI_Track[param1]);
@ -2289,7 +2308,8 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i
UpdateReplayInfo(gA_CentralCache.iClient, style, time, gI_Track[param1]); UpdateReplayInfo(gA_CentralCache.iClient, style, time, gI_Track[param1]);
CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE); delete gH_ReplayTimers[style];
gH_ReplayTimers[style] = CreateTimer((gCV_ReplayDelay.FloatValue / 2.0), Timer_StartReplay, style, TIMER_FLAG_NO_MAPCHANGE);
} }
} }
@ -2306,6 +2326,14 @@ public int MenuHandler_ReplaySubmenu(Menu menu, MenuAction action, int param1, i
return 0; return 0;
} }
bool CanStopCentral(int client)
{
return (CheckCommandAccess(client, "sm_deletereplay", ADMFLAG_RCON) ||
(gCV_PlaybackCanStop.BoolValue &&
GetClientSerial(client) == gA_CentralCache.iPlaybackSerial &&
GetEngineTime() - gF_LastInteraction[client] > gCV_PlaybackCooldown.FloatValue));
}
void TeleportToStart(int client, int style, int track) void TeleportToStart(int client, int style, int track)
{ {
if(gA_FrameCache[style][track].iFrameCount == 0) if(gA_FrameCache[style][track].iFrameCount == 0)
@ -2333,6 +2361,12 @@ void StopCentralReplay(int client)
} }
int style = gA_CentralCache.iStyle; int style = gA_CentralCache.iStyle;
int player = GetClientFromSerial(gA_CentralCache.iPlaybackSerial);
if(player != 0)
{
gF_LastInteraction[player] = GetEngineTime();
}
gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_Idle; gRS_ReplayStatus[style] = gA_CentralCache.iReplayStatus = Replay_Idle;
gI_ReplayTick[style] = 0; gI_ReplayTick[style] = 0;
@ -2340,6 +2374,7 @@ void StopCentralReplay(int client)
gF_StartTick[style] = -65535.0; gF_StartTick[style] = -65535.0;
gA_CentralCache.iStyle = 0; gA_CentralCache.iStyle = 0;
gB_ForciblyStopped = true; gB_ForciblyStopped = true;
gA_CentralCache.iPlaybackSerial = 0;
if(gA_CentralCache.iClient != -1) if(gA_CentralCache.iClient != -1)
{ {
@ -2425,14 +2460,17 @@ void GetTrackName(int client, int track, char[] output, int size)
float GetReplayLength(int style, int track) float GetReplayLength(int style, int track)
{ {
if(gA_FrameCache[style][track].iFrameCount == 0)
{
return 0.0;
}
if(gA_FrameCache[style][track].bNewFormat) if(gA_FrameCache[style][track].bNewFormat)
{ {
return gA_FrameCache[style][track].fTime; return gA_FrameCache[style][track].fTime;
} }
float fWRTime = Shavit_GetWorldRecord(style, track); return Shavit_GetWorldRecord(style, track) * gA_StyleSettings[style].fTimescale;
return fWRTime;
} }
void GetReplayName(int style, int track, char[] buffer, int length) void GetReplayName(int style, int track, char[] buffer, int length)

View File

@ -462,17 +462,22 @@ public void OpenStatsMenuCallback(Database db, DBResultSet results, const char[]
gS_TargetName[client], "Profile", client, gS_TargetAuth[client], "Country", client, sCountry, sLastLogin, sClearString, gS_TargetName[client], "Profile", client, gS_TargetAuth[client], "Country", client, sCountry, sLastLogin, sClearString,
gS_StyleStrings[0].sStyleName, "WorldRecords", client, iWRs, sRankingString); gS_StyleStrings[0].sStyleName, "WorldRecords", client, iWRs, sRankingString);
int[] styles = new int[gI_Styles];
Shavit_GetOrderedStyles(styles, gI_Styles);
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
if(gA_StyleSettings[i].bUnranked) int iStyle = styles[i];
if(gA_StyleSettings[iStyle].bUnranked)
{ {
continue; continue;
} }
char sInfo[4]; char sInfo[4];
IntToString(i, sInfo, 4); IntToString(iStyle, sInfo, 4);
menu.AddItem(sInfo, gS_StyleStrings[i].sStyleName); menu.AddItem(sInfo, gS_StyleStrings[iStyle].sStyleName);
} }
// should NEVER happen // should NEVER happen

View File

@ -107,6 +107,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max
CreateNative("Shavit_GetWRName", Native_GetWRName); CreateNative("Shavit_GetWRName", Native_GetWRName);
CreateNative("Shavit_GetWRRecordID", Native_GetWRRecordID); CreateNative("Shavit_GetWRRecordID", Native_GetWRRecordID);
CreateNative("Shavit_GetWRTime", Native_GetWRTime); CreateNative("Shavit_GetWRTime", Native_GetWRTime);
CreateNative("Shavit_ReloadLeaderboards", Native_ReloadLeaderboards);
CreateNative("Shavit_WR_DeleteMap", Native_WR_DeleteMap); CreateNative("Shavit_WR_DeleteMap", Native_WR_DeleteMap);
// registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins // registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins
@ -507,13 +508,6 @@ public void SQL_UpdateWRCache_Callback(Database db, DBResultSet results, const c
return; return;
} }
// resultset structure
// FIELD 0: style
// FIELD 1: id
// FIELD 2: time - sorted
// FIELD 3: name
// FIELD 4: track
// reset cache // reset cache
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
@ -556,6 +550,12 @@ public int Native_GetWRTime(Handle handler, int numParams)
SetNativeCellRef(2, gF_WRTime[GetNativeCell(1)][GetNativeCell(3)]); SetNativeCellRef(2, gF_WRTime[GetNativeCell(1)][GetNativeCell(3)]);
} }
public int Native_ReloadLeaderboards(Handle handler, int numParams)
{
UpdateLeaderboards();
UpdateWRCache();
}
public int Native_GetWRRecordID(Handle handler, int numParams) public int Native_GetWRRecordID(Handle handler, int numParams)
{ {
SetNativeCellRef(2, gI_WRRecordID[GetNativeCell(1)][GetNativeCell(3)]); SetNativeCellRef(2, gI_WRRecordID[GetNativeCell(1)][GetNativeCell(3)]);
@ -745,15 +745,20 @@ void DeleteSubmenu(int client)
Menu menu = new Menu(MenuHandler_Delete); Menu menu = new Menu(MenuHandler_Delete);
menu.SetTitle("%T\n ", "DeleteMenuTitle", client); menu.SetTitle("%T\n ", "DeleteMenuTitle", client);
int[] styles = new int[gI_Styles];
Shavit_GetOrderedStyles(styles, gI_Styles);
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
int iStyle = styles[i];
char sInfo[8]; char sInfo[8];
IntToString(i, sInfo, 8); IntToString(iStyle, sInfo, 8);
char sDisplay[64]; char sDisplay[64];
FormatEx(sDisplay, 64, "%s (%T: %d)", gS_StyleStrings[i].sStyleName, "WRRecord", client, gI_RecordAmount[i][gA_WRCache[client].iLastTrack]); FormatEx(sDisplay, 64, "%s (%T: %d)", gS_StyleStrings[iStyle].sStyleName, "WRRecord", client, gI_RecordAmount[iStyle][gA_WRCache[client].iLastTrack]);
menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[i][gA_WRCache[client].iLastTrack] > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[iStyle][gA_WRCache[client].iLastTrack] > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
} }
menu.ExitButton = true; menu.ExitButton = true;
@ -775,17 +780,17 @@ public Action Command_DeleteAll(int client, int args)
char sInfo[8]; char sInfo[8];
IntToString(i, sInfo, 8); IntToString(i, sInfo, 8);
int records = GetTrackRecordCount(i); int iRecords = GetTrackRecordCount(i);
char sTrack[64]; char sTrack[64];
GetTrackName(client, i, sTrack, 64); GetTrackName(client, i, sTrack, 64);
if(records > 0) if(iRecords > 0)
{ {
Format(sTrack, 64, "%s (%T: %d)", sTrack, "WRRecord", client, records); Format(sTrack, 64, "%s (%T: %d)", sTrack, "WRRecord", client, iRecords);
} }
menu.AddItem(sInfo, sTrack, (records > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); menu.AddItem(sInfo, sTrack, (iRecords > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
} }
menu.ExitButton = true; menu.ExitButton = true;
@ -798,9 +803,57 @@ public int MenuHandler_DeleteAll_First(Menu menu, MenuAction action, int param1,
{ {
if(action == MenuAction_Select) if(action == MenuAction_Select)
{ {
char info[16]; char sInfo[8];
menu.GetItem(param2, info, 16); menu.GetItem(param2, sInfo, 8);
gA_WRCache[param1].iLastTrack = StringToInt(info); int iTrack = gA_WRCache[param1].iLastTrack = StringToInt(sInfo);
char sTrack[64];
GetTrackName(param1, iTrack, sTrack, 64);
Menu subMenu = new Menu(MenuHandler_DeleteAll_Second);
subMenu.SetTitle("%T\n ", "DeleteTrackAllStyle", param1, sTrack);
int[] styles = new int[gI_Styles];
Shavit_GetOrderedStyles(styles, gI_Styles);
for(int i = 0; i < gI_Styles; i++)
{
int iStyle = styles[i];
char sStyle[64];
strcopy(sStyle, 64, gS_StyleStrings[iStyle].sStyleName);
IntToString(iStyle, sInfo, 8);
int iRecords = gI_RecordAmount[iStyle][iTrack];
if(iRecords > 0)
{
Format(sStyle, 64, "%s (%T: %d)", sStyle, "WRRecord", param1, iRecords);
}
subMenu.AddItem(sInfo, sStyle, (iRecords > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
}
subMenu.ExitButton = true;
subMenu.Display(param1, 20);
}
else if(action == MenuAction_End)
{
delete menu;
}
return 0;
}
public int MenuHandler_DeleteAll_Second(Menu menu, MenuAction action, int param1, int param2)
{
if(action == MenuAction_Select)
{
char sInfo[8];
menu.GetItem(param2, sInfo, 8);
gA_WRCache[param1].iLastStyle = StringToInt(sInfo);
DeleteAllSubmenu(param1); DeleteAllSubmenu(param1);
} }
@ -819,7 +872,7 @@ void DeleteAllSubmenu(int client)
GetTrackName(client, gA_WRCache[client].iLastTrack, sTrack, 32); GetTrackName(client, gA_WRCache[client].iLastTrack, sTrack, 32);
Menu menu = new Menu(MenuHandler_DeleteAll); Menu menu = new Menu(MenuHandler_DeleteAll);
menu.SetTitle("%T\n ", "DeleteAllRecordsMenuTitle", client, gS_Map, sTrack); menu.SetTitle("%T\n ", "DeleteAllRecordsMenuTitle", client, gS_Map, sTrack, gS_StyleStrings[gA_WRCache[client].iLastStyle].sStyleName);
char sMenuItem[64]; char sMenuItem[64];
@ -859,10 +912,12 @@ public int MenuHandler_DeleteAll(Menu menu, MenuAction action, int param1, int p
char sTrack[32]; char sTrack[32];
GetTrackName(LANG_SERVER, gA_WRCache[param1].iLastTrack, sTrack, 32); GetTrackName(LANG_SERVER, gA_WRCache[param1].iLastTrack, sTrack, 32);
Shavit_LogMessage("%L - deleted all %s track records from map `%s`.", param1, sTrack, gS_Map); Shavit_LogMessage("%L - deleted all %s track and %s style records from map `%s`.",
param1, sTrack, gS_StyleStrings[gA_WRCache[param1].iLastStyle].sStyleName, gS_Map);
char sQuery[256]; char sQuery[256];
FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE map = '%s' AND track = %d;", gS_MySQLPrefix, gS_Map, gA_WRCache[param1].iLastTrack); FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE map = '%s' AND style = %d AND track = %d;",
gS_MySQLPrefix, gS_Map, gA_WRCache[param1].iLastStyle, gA_WRCache[param1].iLastTrack);
gH_SQL.Query(DeleteAll_Callback, sQuery, GetClientSerial(param1), DBPrio_High); gH_SQL.Query(DeleteAll_Callback, sQuery, GetClientSerial(param1), DBPrio_High);
} }
@ -885,24 +940,29 @@ public Action Command_DeleteStyleRecords(int client, int args)
Menu menu = new Menu(MenuHandler_DeleteStyleRecords); Menu menu = new Menu(MenuHandler_DeleteStyleRecords);
menu.SetTitle("%T\n ", "DeleteStyleRecordsRecordsMenuTitle", client, gS_Map); menu.SetTitle("%T\n ", "DeleteStyleRecordsRecordsMenuTitle", client, gS_Map);
int[] styles = new int[gI_Styles];
Shavit_GetOrderedStyles(styles, gI_Styles);
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
if(gA_StyleSettings[i].bUnranked) int iStyle = styles[i];
if(gA_StyleSettings[iStyle].bUnranked)
{ {
continue; continue;
} }
char sInfo[8]; char sInfo[8];
IntToString(i, sInfo, 8); IntToString(iStyle, sInfo, 8);
char sDisplay[64]; char sDisplay[64];
FormatEx(sDisplay, 64, "%s (%d %T)", gS_StyleStrings[i].sStyleName, gI_RecordAmount[i], "WRRecord", client); FormatEx(sDisplay, 64, "%s (%d %T)", gS_StyleStrings[iStyle].sStyleName, gI_RecordAmount[iStyle], "WRRecord", client);
int iTotalAmount = 0; int iTotalAmount = 0;
for(int j = 0; j < TRACKS_SIZE; j++) for(int j = 0; j < TRACKS_SIZE; j++)
{ {
iTotalAmount += gI_RecordAmount[i][j]; iTotalAmount += gI_RecordAmount[iStyle][j];
} }
menu.AddItem(sInfo, sDisplay, (iTotalAmount > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); menu.AddItem(sInfo, sDisplay, (iTotalAmount > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
@ -1048,8 +1108,9 @@ public int MenuHandler_Delete(Menu menu, MenuAction action, int param1, int para
{ {
char info[16]; char info[16];
menu.GetItem(param2, info, 16); menu.GetItem(param2, info, 16);
gA_WRCache[param1].iLastStyle = StringToInt(info);
OpenDelete(param1, StringToInt(info)); OpenDelete(param1);
UpdateLeaderboards(); UpdateLeaderboards();
} }
@ -1062,25 +1123,17 @@ public int MenuHandler_Delete(Menu menu, MenuAction action, int param1, int para
return 0; return 0;
} }
void OpenDelete(int client, int style) void OpenDelete(int client)
{ {
char sQuery[512]; char sQuery[512];
FormatEx(sQuery, 512, "SELECT p.id, u.name, p.time, p.jumps FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE map = '%s' AND style = %d AND track = %d ORDER BY time ASC, date ASC LIMIT 1000;",
gS_MySQLPrefix, gS_MySQLPrefix, gS_Map, gA_WRCache[client].iLastStyle, gA_WRCache[client].iLastTrack);
FormatEx(sQuery, 512, "SELECT p.id, u.name, p.time, p.jumps FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE map = '%s' AND style = %d AND track = %d ORDER BY time ASC, date ASC LIMIT 1000;", gS_MySQLPrefix, gS_MySQLPrefix, gS_Map, style, gA_WRCache[client].iLastTrack); gH_SQL.Query(SQL_OpenDelete_Callback, sQuery, GetClientSerial(client), DBPrio_High);
DataPack datapack = new DataPack();
datapack.WriteCell(GetClientSerial(client));
datapack.WriteCell(style);
gH_SQL.Query(SQL_OpenDelete_Callback, sQuery, datapack, DBPrio_High);
} }
public void SQL_OpenDelete_Callback(Database db, DBResultSet results, const char[] error, DataPack data) public void SQL_OpenDelete_Callback(Database db, DBResultSet results, const char[] error, any data)
{ {
data.Reset();
int client = GetClientFromSerial(data.ReadCell());
int style = data.ReadCell();
delete data;
if(results == null) if(results == null)
{ {
LogError("Timer (WR OpenDelete) SQL query failed. Reason: %s", error); LogError("Timer (WR OpenDelete) SQL query failed. Reason: %s", error);
@ -1088,13 +1141,17 @@ public void SQL_OpenDelete_Callback(Database db, DBResultSet results, const char
return; return;
} }
int client = GetClientFromSerial(data);
if(client == 0) if(client == 0)
{ {
return; return;
} }
int iStyle = gA_WRCache[client].iLastStyle;
Menu menu = new Menu(OpenDelete_Handler); Menu menu = new Menu(OpenDelete_Handler);
menu.SetTitle("%t", "ListClientRecords", gS_Map, gS_StyleStrings[style].sStyleName); menu.SetTitle("%t", "ListClientRecords", gS_Map, gS_StyleStrings[iStyle].sStyleName);
int iCount = 0; int iCount = 0;
@ -1140,10 +1197,10 @@ public int OpenDelete_Handler(Menu menu, MenuAction action, int param1, int para
{ {
if(action == MenuAction_Select) if(action == MenuAction_Select)
{ {
char info[16]; char sInfo[16];
menu.GetItem(param2, info, 16); menu.GetItem(param2, sInfo, 16);
int id = StringToInt(info); int id = StringToInt(sInfo);
if(id != -1) if(id != -1)
{ {
@ -1174,9 +1231,9 @@ void OpenDeleteMenu(int client, int id)
FormatEx(sMenuItem, 64, "%T", "MenuResponseYesSingle", client); FormatEx(sMenuItem, 64, "%T", "MenuResponseYesSingle", client);
char info[16]; char sInfo[16];
IntToString(id, info, 16); IntToString(id, sInfo, 16);
menu.AddItem(info, sMenuItem); menu.AddItem(sInfo, sMenuItem);
for(int i = 1; i <= GetRandomInt(1, 3); i++) for(int i = 1; i <= GetRandomInt(1, 3); i++)
{ {
@ -1192,9 +1249,10 @@ public int DeleteConfirm_Handler(Menu menu, MenuAction action, int param1, int p
{ {
if(action == MenuAction_Select) if(action == MenuAction_Select)
{ {
char info[16]; char sInfo[16];
menu.GetItem(param2, info, 16); menu.GetItem(param2, sInfo, 16);
int iRecordID = StringToInt(info);
int iRecordID = StringToInt(sInfo);
if(iRecordID == -1) if(iRecordID == -1)
{ {
@ -1203,35 +1261,11 @@ public int DeleteConfirm_Handler(Menu menu, MenuAction action, int param1, int p
return 0; return 0;
} }
for(int i = 0; i < gI_Styles; i++)
{
if(gA_StyleSettings[i].bUnranked)
{
continue;
}
for(int j = 0; j < TRACKS_SIZE; j++)
{
if(gI_WRRecordID[i][j] != iRecordID)
{
continue;
}
Call_StartForward(gH_OnWRDeleted);
Call_PushCell(i);
Call_PushCell(iRecordID);
Call_PushCell(j);
Call_Finish();
}
}
// TODO: display record details here (map name, player name/authid, time, rank on map) without dropping threaded queries
Shavit_LogMessage("%L - deleted record id %d.", param1, iRecordID);
char sQuery[256]; char sQuery[256];
FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE id = %d;", gS_MySQLPrefix, iRecordID); FormatEx(sQuery, 256, "SELECT u.auth, u.name, p.map, p.time, p.sync, p.perfs, p.jumps, p.strafes, p.id, p.date FROM %susers u LEFT JOIN %splayertimes p ON u.auth = p.auth WHERE p.id = %d;",
gS_MySQLPrefix, gS_MySQLPrefix, iRecordID);
gH_SQL.Query(DeleteConfirm_Callback, sQuery, GetClientSerial(param1), DBPrio_High); gH_SQL.Query(GetRecordDetails_Callback, sQuery, GetClientSerial(param1), DBPrio_High);
} }
else if(action == MenuAction_End) else if(action == MenuAction_End)
@ -1242,8 +1276,101 @@ public int DeleteConfirm_Handler(Menu menu, MenuAction action, int param1, int p
return 0; return 0;
} }
public void GetRecordDetails_Callback(Database db, DBResultSet results, const char[] error, any data)
{
if(results == null)
{
LogError("Timer (WR GetRecordDetails) SQL query failed. Reason: %s", error);
return;
}
int client = GetClientFromSerial(data);
if(client == 0)
{
return;
}
if(results.FetchRow())
{
char sAuthID[32];
results.FetchString(0, sAuthID, 32);
char sName[MAX_NAME_LENGTH];
results.FetchString(1, sName, MAX_NAME_LENGTH);
char sMap[160];
results.FetchString(2, sMap, 160);
float fTime = results.FetchFloat(3);
float fSync = results.FetchFloat(4);
float fPerfectJumps = results.FetchFloat(5);
int iJumps = results.FetchInt(6);
int iStrafes = results.FetchInt(7);
int iRecordID = results.FetchInt(8);
int iTimestamp = results.FetchInt(9);
int iStyle = gA_WRCache[client].iLastStyle;
int iTrack = gA_WRCache[client].iLastTrack;
bool bWRDeleted = (gI_WRRecordID[iStyle][iTrack] == iRecordID);
// that's a big datapack ya yeet
DataPack hPack = new DataPack();
hPack.WriteCell(GetClientSerial(client));
hPack.WriteString(sAuthID);
hPack.WriteString(sName);
hPack.WriteString(sMap);
hPack.WriteCell(fTime);
hPack.WriteCell(fSync);
hPack.WriteCell(fPerfectJumps);
hPack.WriteCell(iJumps);
hPack.WriteCell(iStrafes);
hPack.WriteCell(iRecordID);
hPack.WriteCell(iTimestamp);
hPack.WriteCell(iStyle);
hPack.WriteCell(iTrack);
hPack.WriteCell(bWRDeleted);
char sQuery[256];
FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE id = %d;",
gS_MySQLPrefix, iRecordID);
gH_SQL.Query(DeleteConfirm_Callback, sQuery, hPack, DBPrio_High);
}
}
public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[] error, any data) public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[] error, any data)
{ {
DataPack hPack = view_as<DataPack>(data);
hPack.Reset();
int iSerial = hPack.ReadCell();
char sAuthID[32];
hPack.ReadString(sAuthID, 32);
char sName[MAX_NAME_LENGTH];
hPack.ReadString(sName, MAX_NAME_LENGTH);
char sMap[160];
hPack.ReadString(sMap, 160);
float fTime = view_as<float>(hPack.ReadCell());
float fSync = view_as<float>(hPack.ReadCell());
float fPerfectJumps = view_as<float>(hPack.ReadCell());
int iJumps = hPack.ReadCell();
int iStrafes = hPack.ReadCell();
int iRecordID = hPack.ReadCell();
int iTimestamp = hPack.ReadCell();
int iStyle = hPack.ReadCell();
int iTrack = hPack.ReadCell();
bool bWRDeleted = view_as<bool>(hPack.ReadCell());
delete hPack;
if(results == null) if(results == null)
{ {
LogError("Timer (WR DeleteConfirm) SQL query failed. Reason: %s", error); LogError("Timer (WR DeleteConfirm) SQL query failed. Reason: %s", error);
@ -1251,6 +1378,15 @@ public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[
return; return;
} }
if(bWRDeleted)
{
Call_StartForward(gH_OnWRDeleted);
Call_PushCell(iStyle);
Call_PushCell(iRecordID);
Call_PushCell(iTrack);
Call_Finish();
}
UpdateWRCache(); UpdateWRCache();
for(int i = 1; i <= MaxClients; i++) for(int i = 1; i <= MaxClients; i++)
@ -1258,7 +1394,17 @@ public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[
OnClientPutInServer(i); OnClientPutInServer(i);
} }
int client = GetClientFromSerial(data); int client = GetClientFromSerial(iSerial);
char sTrack[32];
GetTrackName(LANG_SERVER, iTrack, sTrack, 32);
char sDate[32];
FormatTime(sDate, 32, "%Y-%m-%d %H:%M:%S", iTimestamp);
// above the client == 0 so log doesn't get lost if admin disconnects between deleting record and query execution
Shavit_LogMessage("%L - deleted record. Runner: %s (%s) | Map: %s | Style: %s | Track: %s | Time: %.2f (%s) | Strafes: %d (%.1f%%) | Jumps: %d (%.1f%%) | Run date: %s | Record ID: %d",
client, sName, sAuthID, sMap, gS_StyleStrings[iStyle].sStyleName, sTrack, fTime, (bWRDeleted)? "WR":"not WR", iStrafes, fSync, iJumps, fPerfectJumps, sDate, iRecordID);
if(client == 0) if(client == 0)
{ {
@ -1291,19 +1437,11 @@ public void DeleteAll_Callback(Database db, DBResultSet results, const char[] er
return; return;
} }
for(int i = 0; i < gI_Styles; i++) Call_StartForward(gH_OnWRDeleted);
{ Call_PushCell(gA_WRCache[client].iLastStyle);
if(gA_StyleSettings[i].bUnranked) Call_PushCell(-1);
{ Call_PushCell(gA_WRCache[client].iLastTrack);
continue; Call_Finish();
}
Call_StartForward(gH_OnWRDeleted);
Call_PushCell(i);
Call_PushCell(-1);
Call_PushCell(gA_WRCache[client].iLastTrack);
Call_Finish();
}
Shavit_PrintToChat(client, "%T", "DeletedRecordsMap", client, gS_ChatStrings.sVariable, gS_Map, gS_ChatStrings.sText); Shavit_PrintToChat(client, "%T", "DeletedRecordsMap", client, gS_ChatStrings.sVariable, gS_Map, gS_ChatStrings.sText);
} }
@ -1357,32 +1495,37 @@ Action ShowWRStyleMenu(int client, int track)
Menu menu = new Menu(MenuHandler_StyleChooser); Menu menu = new Menu(MenuHandler_StyleChooser);
menu.SetTitle("%T", "WRMenuTitle", client); menu.SetTitle("%T", "WRMenuTitle", client);
int[] styles = new int[gI_Styles];
Shavit_GetOrderedStyles(styles, gI_Styles);
for(int i = 0; i < gI_Styles; i++) for(int i = 0; i < gI_Styles; i++)
{ {
if(gA_StyleSettings[i].bUnranked) int iStyle = styles[i];
if(gA_StyleSettings[iStyle].bUnranked)
{ {
continue; continue;
} }
char sInfo[8]; char sInfo[8];
IntToString(i, sInfo, 8); IntToString(iStyle, sInfo, 8);
char sDisplay[64]; char sDisplay[64];
if(StrEqual(gA_WRCache[client].sClientMap, gS_Map) && gF_WRTime[i][track] > 0.0) if(StrEqual(gA_WRCache[client].sClientMap, gS_Map) && gF_WRTime[iStyle][track] > 0.0)
{ {
char sTime[32]; char sTime[32];
FormatSeconds(gF_WRTime[i][track], sTime, 32, false); FormatSeconds(gF_WRTime[iStyle][track], sTime, 32, false);
FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[i].sStyleName, sTime); FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[iStyle].sStyleName, sTime);
} }
else else
{ {
strcopy(sDisplay, 64, gS_StyleStrings[i].sStyleName); strcopy(sDisplay, 64, gS_StyleStrings[iStyle].sStyleName);
} }
menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[i][track] > 0 || !StrEqual(gA_WRCache[client].sClientMap, gS_Map))? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[iStyle][track] > 0 || !StrEqual(gA_WRCache[client].sClientMap, gS_Map))? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
} }
// should NEVER happen // should NEVER happen
@ -2309,7 +2452,7 @@ public void SQL_OnFinish_Callback(Database db, DBResultSet results, const char[]
void UpdateLeaderboards() void UpdateLeaderboards()
{ {
char sQuery[192]; char sQuery[192];
FormatEx(sQuery, 192, "SELECT style, time, track FROM %splayertimes WHERE map = '%s' ORDER BY time ASC, date ASC;", gS_MySQLPrefix, gS_Map); FormatEx(sQuery, 192, "SELECT style, time, track FROM %splayertimes WHERE map = '%s' ORDER BY time ASC, date ASC LIMIT 1000;", gS_MySQLPrefix, gS_Map);
gH_SQL.Query(SQL_UpdateLeaderboards_Callback, sQuery, 0); gH_SQL.Query(SQL_UpdateLeaderboards_Callback, sQuery, 0);
} }

View File

@ -113,8 +113,8 @@
// ---------- Zones ---------- // // ---------- Zones ---------- //
"StartZoneUndefined" "StartZoneUndefined"
{ {
"#format" "{1:s},{2:s}" "#format" "{1:s},{2:s},{3:s},{4:s},{5:s}"
"en" "Your timer {1}will not{2} start as a start zone for the map is not defined." "en" "Your timer {1}will not{2} start because the {3}{4}{5} start zone is not set."
} }
"EndZoneUndefined" "EndZoneUndefined"
{ {

View File

@ -6,6 +6,11 @@
{ {
"en" "Start Zone" "en" "Start Zone"
} }
"HudZoneTier"
{
"#format" "{1:d}"
"en" "Tier {1}"
}
"HudInStartZone" "HudInStartZone"
{ {
"#format" "{1:d}" "#format" "{1:d}"
@ -131,6 +136,14 @@
{ {
"en" "Timer track" "en" "Timer track"
} }
"HudSplitPbText"
{
"en" "Split PB"
}
"HudMapTierText"
{
"en" "Map tier"
}
// ---------- Record Bots ---------- // // ---------- Record Bots ---------- //
"ReplayText" "ReplayText"
{ {

View File

@ -11,6 +11,11 @@
"#format" "{1:s},{2:s},{3:s},{4:s}" "#format" "{1:s},{2:s},{3:s},{4:s}"
"en" "You have to be {1}alive{2} or {3}spectate a player{4} to use this command." "en" "You have to be {1}alive{2} or {3}spectate a player{4} to use this command."
} }
"CommandNoPause"
{
"#format" "{1:s},{2:s}"
"en" "Your timer has to be {1}resumed{2} to use this command."
}
"CommandDisabled" "CommandDisabled"
{ {
"#format" "{1:s},{2:s}" "#format" "{1:s},{2:s}"

View File

@ -53,8 +53,8 @@
} }
"DeleteAllRecordsMenuTitle" "DeleteAllRecordsMenuTitle"
{ {
"#format" "{1:s},{2:s}" "#format" "{1:s},{2:s},{3:s}"
"en" "Delete ALL the records for '{1}'? (Track: {2})" "en" "Delete ALL the records for '{1}'? (Track: {2} | Style: {3})"
} }
"DeleteConfirm" "DeleteConfirm"
{ {
@ -107,6 +107,11 @@
{ {
"en" "Choose a track to delete a ALL records from:" "en" "Choose a track to delete a ALL records from:"
} }
"DeleteTrackAllStyle"
{
"#format" "{1:s}"
"en" "Choose a style to delete all the records for ({1}):"
}
// ---------- Errors ---------- // // ---------- Errors ---------- //
"DatabaseError" "DatabaseError"
{ {