diff --git a/FEATURES.md b/FEATURES.md index 026bd14b..71374fde 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -36,7 +36,7 @@ Some features are: Per-player settings (!hud), truevel and gradient-like display #### shavit-misc This plugin handles miscellaneous things used in bunnyhop servers. -Such as: Team handling (respawning/spectating too), spectators list (!specs), smart player hiding that works for spectating too, teleportation to other players, weapon commands (!knife/!usp/!glock) and ammo management, segmented checkpoints, noclipping (can be set to work for VIPs/admins only), drop-all, godmode, prespeed blocking, prespeed limitation, chat tidying, radar hiding, weapon drop cleaning, player collision removal, auto-respawning, spawn points generator, radio removal, scoreboard manipulation, model opacity changes, fixed runspeed, automatic and configurable chat advertisements, player ragdoll removal and WR messages. +Such as: Segmented runs, team handling (respawning/spectating too), spectators list (!specs), smart player hiding that works for spectating too, teleportation to other players, weapon commands (!knife/!usp/!glock) and ammo management, segmented checkpoints, noclipping (can be set to work for VIPs/admins only), drop-all, godmode, prespeed blocking, prespeed limitation, chat tidying, radar hiding, weapon drop cleaning, player collision removal, auto-respawning, spawn points generator, radio removal, scoreboard manipulation, model opacity changes, fixed runspeed, automatic and configurable chat advertisements, player ragdoll removal and WR messages. #### shavit-rankings Enables !rank, !top and introduces map tiers (!settier). diff --git a/README.md b/README.md index 24ab2e9f..2e98046f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This is (nearly) an all-in-one server plugin for Counter-Strike: Source, Counter-Strike: Global Offensive and Team Fortress 2 that adds a timer system and many other utilities, so you can install it and have a proper bunnyhop server running. -Including a records system, map zones (start/end marks etc), bonuses, HUD with useful information, chat processor, miscellaneous such as weapon commands/spawn point generator, bots that replay the best records of the map, sounds, statistics, a fair & competitive rankings system and more! +Including a records system, map zones (start/end marks etc), bonuses, HUD with useful information, chat processor, miscellaneous such as weapon commands/spawn point generator, bots that replay the best records of the map, sounds, statistics, segmented running, a fair & competitive rankings system and more! [Mapzones' setup demonstration](https://youtu.be/OXFMGm40F6c) diff --git a/addons/sourcemod/configs/shavit-styles.cfg b/addons/sourcemod/configs/shavit-styles.cfg index e5d09de6..0a6e10db 100644 --- a/addons/sourcemod/configs/shavit-styles.cfg +++ b/addons/sourcemod/configs/shavit-styles.cfg @@ -58,7 +58,7 @@ // Special flags "special" "0" // For third-party modules. The core plugins will not need this setting. - "specialstring" "" // For third-party modules. The core plugins will not need this setting. + "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. } @@ -187,6 +187,18 @@ } "7" + { + "name" "Segmented" + "shortname" "SEG" + "htmlcolor" "FFFFFF" + "command" "sr; seg; segment; segmented" + "clantag" "SEG" + + "rankingmultiplier" "0.0" + "specialstring" "segments" + } + + "8" { "name" "Low Gravity" "shortname" "LG" @@ -199,7 +211,7 @@ "unranked" "1" } - "8" + "9" { "name" "Slow Motion" "shortname" "SLOW" diff --git a/addons/sourcemod/scripting/include/shavit.inc b/addons/sourcemod/scripting/include/shavit.inc index 15cb03ed..feab9833 100644 --- a/addons/sourcemod/scripting/include/shavit.inc +++ b/addons/sourcemod/scripting/include/shavit.inc @@ -23,7 +23,7 @@ #endif #define _shavit_included -#define SHAVIT_VERSION "2.0.3" +#define SHAVIT_VERSION "2.1.0" #define STYLE_LIMIT 256 #define MAX_ZONES 64 @@ -145,6 +145,8 @@ enum fServerTime, iSHSWCombination, iTimerTrack, + iMeasuredJumps, + iPerfectJumps, TIMERSNAPSHOT_SIZE }; @@ -386,9 +388,10 @@ forward Action Shavit_OnFinishPre(int client, any snapshot[TIMERSNAPSHOT_SIZE]); * @param sync Sync percentage (0.0 to 100.0) or -1.0 when not measured. * @param track Timer track. * @param oldtime The player's best time on the map before this finish. + * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @noreturn */ -forward void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime); +forward void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs); /** * Like Shavit_OnFinish, but after the insertion query was called. @@ -403,9 +406,11 @@ forward void Shavit_OnFinish(int client, int style, float time, int jumps, int s * @param rank Rank on map. * @param overwrite 1 - brand new record. 2 - update. * @param track Timer track. + * @param oldtime The player's best time on the map before this finish. + * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @noreturn */ -forward void Shavit_OnFinish_Post(int client, int style, float time, int jumps, int strafes, float sync, int rank, int overwrite, int track); +forward void Shavit_OnFinish_Post(int client, int style, float time, int jumps, int strafes, float sync, int rank, int overwrite, int track, float oldtime, float perfs); /** * Called when there's a new WR on the map. @@ -418,9 +423,11 @@ forward void Shavit_OnFinish_Post(int client, int style, float time, int jumps, * @param sync Sync percentage (0.0 to 100.0) or -1.0 when not measured. * @param track Timer track. * @param oldwr Time of the old WR. 0.0 if there's none. + * @param oldtime The player's best time on the map before this finish. + * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @noreturn */ -forward void Shavit_OnWorldRecord(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldwr); +forward void Shavit_OnWorldRecord(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldwr, float oldtime, float perfs); /** * Called when an admin deletes a WR. @@ -520,9 +527,11 @@ forward void Shavit_OnLeaveZone(int client, int type, int track, int id, int ent * @param strafes Amount of strafes. * @param sync Sync percentage (0.0 to 100.0) or -1.0 when not measured. * @param track Timer track. + * @param oldtime The player's best time on the map before this finish. + * @param perfs Perfect jump percentage (0.0 to 100.0) or 100.0 when not measured. * @noreturn */ -forward void Shavit_OnWorstRecord(int client, int style, float time, int jumps, int strafes, float sync, int track); +forward void Shavit_OnWorstRecord(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs); /** * Gets called when a map's tier is assigned. @@ -661,6 +670,15 @@ native TimerStatus Shavit_GetTimerStatus(int client); */ native int Shavit_GetStrafeCount(int client); +/** + * Retrieve the perfect jumps percentage for the player. + * Will return 100.0 if no jumps were measured. + * + * @param client Client index. + * @return Perfect jump percentage. + */ +native float Shavit_GetPerfectJumps(int client); + /** * Retrieve strafe sync since timer start. * Will return 0.0 if timer isn't running or -1.0 when not measured. @@ -871,6 +889,17 @@ native float Shavit_GetReplayTime(int style, int track); */ native void Shavit_GetReplayName(int style, int track, char[] buffer, int length); +/** + * Hijack the replay data so that this view angle will be used for the next tick. + * Use case is to make segmented runs look smoother. + * + * @param client Client index. + * @param pitch Vertical view angle. + * @param yaw Horizontal view angle. + * @noreturn + */ +native void Shavit_HijackAngles(int client, float pitch, float yaw); + /** * Checks if there's loaded replay data for a bhop style or not. * @@ -1172,6 +1201,7 @@ public void __pl_shavit_SetNTVOptional() MarkNativeAsOptional("Shavit_GetHUDSettings"); MarkNativeAsOptional("Shavit_GetMapTier"); MarkNativeAsOptional("Shavit_GetMapTiers"); + MarkNativeAsOptional("Shavit_GetPerfectJumps"); MarkNativeAsOptional("Shavit_GetPlayerPB"); MarkNativeAsOptional("Shavit_GetPoints"); MarkNativeAsOptional("Shavit_GetRank"); @@ -1202,6 +1232,7 @@ public void __pl_shavit_SetNTVOptional() MarkNativeAsOptional("Shavit_GetWRRecordID"); MarkNativeAsOptional("Shavit_GetWRTime"); MarkNativeAsOptional("Shavit_HasStyleAccess"); + MarkNativeAsOptional("Shavit_HijackAngles"); MarkNativeAsOptional("Shavit_InsideZone"); MarkNativeAsOptional("Shavit_IsClientCreatingZone"); MarkNativeAsOptional("Shavit_IsKZMap"); diff --git a/addons/sourcemod/scripting/shavit-core.sp b/addons/sourcemod/scripting/shavit-core.sp index aa542088..2df0744c 100644 --- a/addons/sourcemod/scripting/shavit-core.sp +++ b/addons/sourcemod/scripting/shavit-core.sp @@ -25,7 +25,6 @@ #include #undef REQUIRE_PLUGIN -#include #include #define USES_CHAT_COLORS @@ -79,6 +78,8 @@ float gF_StrafeWarning[MAXPLAYERS+1]; bool gB_PracticeMode[MAXPLAYERS+1]; int gI_SHSW_FirstCombination[MAXPLAYERS+1]; int gI_Track[MAXPLAYERS+1]; +int gI_MeasuredJumps[MAXPLAYERS+1]; +int gI_PerfectJumps[MAXPLAYERS+1]; StringMap gSM_StyleCommands = null; @@ -133,6 +134,7 @@ char gS_ChatStrings[CHATSETTINGS_SIZE][128]; bool gB_StopChatSound = false; bool gB_HookedJump = false; char gS_LogPath[PLATFORM_MAX_PATH]; +int gI_GroundTicks[MAXPLAYERS+1]; // flags int gI_StyleFlag[STYLE_LIMIT]; @@ -161,6 +163,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("Shavit_GetDatabase", Native_GetDatabase); CreateNative("Shavit_GetDB", Native_GetDB); CreateNative("Shavit_GetGameType", Native_GetGameType); + CreateNative("Shavit_GetPerfectJumps", Native_GetPerfectJumps); CreateNative("Shavit_GetStrafeCount", Native_GetStrafeCount); CreateNative("Shavit_GetStyleCount", Native_GetStyleCount); CreateNative("Shavit_GetStyleSettings", Native_GetStyleSettings); @@ -198,7 +201,7 @@ public void OnPluginStart() gH_Forwards_Start = CreateGlobalForward("Shavit_OnStart", ET_Event, Param_Cell, Param_Cell); gH_Forwards_Stop = CreateGlobalForward("Shavit_OnStop", ET_Event, Param_Cell, Param_Cell); gH_Forwards_FinishPre = CreateGlobalForward("Shavit_OnFinishPre", ET_Event, Param_Cell, Param_Array); - gH_Forwards_Finish = CreateGlobalForward("Shavit_OnFinish", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_Forwards_Finish = CreateGlobalForward("Shavit_OnFinish", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); gH_Forwards_OnRestart = CreateGlobalForward("Shavit_OnRestart", ET_Event, Param_Cell, Param_Cell); gH_Forwards_OnEnd = CreateGlobalForward("Shavit_OnEnd", ET_Event, Param_Cell, Param_Cell); gH_Forwards_OnPause = CreateGlobalForward("Shavit_OnPause", ET_Event, Param_Cell, Param_Cell); @@ -328,8 +331,6 @@ public void OnPluginStart() // late if(gB_Late) { - OnAdminMenuReady(null); - for(int i = 1; i <= MaxClients; i++) { if(IsValidClient(i)) @@ -385,29 +386,6 @@ public void OnLibraryRemoved(const char[] name) } } -public void OnAdminMenuReady(Handle topmenu) -{ - Handle hTopMenu = INVALID_HANDLE; - - if(LibraryExists("adminmenu") && ((hTopMenu = GetAdminTopMenu()) != INVALID_HANDLE)) - { - AddToTopMenu(hTopMenu, "Timer Commands", TopMenuObject_Category, CategoryHandler, INVALID_TOPMENUOBJECT); - } -} - -public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) -{ - if(action == TopMenuAction_DisplayTitle) - { - strcopy(buffer, maxlength, "Timer Commands:"); - } - - else if(action == TopMenuAction_DisplayOption) - { - strcopy(buffer, maxlength, "Timer Commands"); - } -} - public void OnMapStart() { // styles @@ -981,6 +959,8 @@ public int Native_FinishMap(Handle handler, int numParams) snapshot[fCurrentTime] = gF_PlayerTimer[client]; snapshot[iSHSWCombination] = gI_SHSW_FirstCombination[client]; snapshot[iTimerTrack] = gI_Track[client]; + snapshot[iMeasuredJumps] = gI_MeasuredJumps[client]; + snapshot[iPerfectJumps] = gI_PerfectJumps[client]; Action result = Plugin_Continue; Call_StartForward(gH_Forwards_FinishPre); @@ -998,6 +978,7 @@ public int Native_FinishMap(Handle handler, int numParams) int style = 0; int track = Track_Main; + float perfs = 100.0; if(result == Plugin_Continue) { @@ -1007,6 +988,7 @@ public int Native_FinishMap(Handle handler, int numParams) Call_PushCell(gI_Strafes[client]); Call_PushCell((gA_StyleSettings[gI_Style[client]][bSync])? (gI_GoodGains[client] == 0)? 0.0:(gI_GoodGains[client] / float(gI_TotalMeasures[client]) * 100.0):-1.0); Call_PushCell(track = gI_Track[client]); + perfs = (gI_MeasuredJumps[client] == 0)? 100.0:(gI_PerfectJumps[client] / float(gI_MeasuredJumps[client]) * 100.0); } else @@ -1017,6 +999,7 @@ public int Native_FinishMap(Handle handler, int numParams) Call_PushCell(snapshot[iStrafes]); Call_PushCell((gA_StyleSettings[snapshot[bsStyle]][bSync])? (snapshot[iGoodGains] == 0)? 0.0:(snapshot[iGoodGains] / float(snapshot[iTotalMeasures]) * 100.0):-1.0); Call_PushCell(track = snapshot[iTimerTrack]); + perfs = (snapshot[iMeasuredJumps] == 0)? 100.0:(snapshot[iPerfectJumps] / float(snapshot[iMeasuredJumps]) * 100.0); } float oldtime = 0.0; @@ -1027,6 +1010,7 @@ public int Native_FinishMap(Handle handler, int numParams) } Call_PushCell(oldtime); + Call_PushCell(perfs); Call_Finish(); StopTimer(client); @@ -1091,6 +1075,13 @@ public int Native_RestartTimer(Handle handler, int numParams) StartTimer(client, track); } +public int Native_GetPerfectJumps(Handle handler, int numParams) +{ + int client = GetNativeCell(1); + + return view_as((gI_MeasuredJumps[client] == 0)? 100.0:(gI_PerfectJumps[client] / float(gI_MeasuredJumps[client]) * 100.0)); +} + public int Native_GetStrafeCount(Handle handler, int numParams) { return gI_Strafes[GetNativeCell(1)]; @@ -1158,6 +1149,8 @@ public int Native_SaveSnapshot(Handle handler, int numParams) snapshot[fCurrentTime] = gF_PlayerTimer[client]; snapshot[iSHSWCombination] = gI_SHSW_FirstCombination[client]; snapshot[iTimerTrack] = gI_Track[client]; + snapshot[iMeasuredJumps] = gI_MeasuredJumps[client]; + snapshot[iPerfectJumps] = gI_PerfectJumps[client]; return SetNativeArray(2, snapshot, TIMERSNAPSHOT_SIZE); } @@ -1185,6 +1178,8 @@ public int Native_LoadSnapshot(Handle handler, int numParams) gI_GoodGains[client] = view_as(snapshot[iGoodGains]); gF_PlayerTimer[client] = snapshot[fCurrentTime]; gI_SHSW_FirstCombination[client] = view_as(snapshot[iSHSWCombination]); + gI_MeasuredJumps[client] = view_as(snapshot[iMeasuredJumps]); + gI_PerfectJumps[client] = view_as(snapshot[iPerfectJumps]); } public int Native_LogMessage(Handle plugin, int numParams) @@ -1255,6 +1250,8 @@ void StartTimer(int client, int track) gI_SHSW_FirstCombination[client] = -1; gF_PlayerTimer[client] = 0.0; gB_PracticeMode[client] = false; + gI_MeasuredJumps[client] = 0; + gI_PerfectJumps[client] = 0; SetEntityGravity(client, view_as(gA_StyleSettings[gI_Style[client]][fGravityMultiplier])); SetEntPropFloat(client, Prop_Data, "m_flLaggedMovementValue", view_as(gA_StyleSettings[gI_Style[client]][fSpeedMultiplier])); @@ -2112,15 +2109,40 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 SetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", fSpeed); } + int iPButtons = buttons; + if(gA_StyleSettings[gI_Style[client]][bAutobhop] && gB_Auto[client] && (buttons & IN_JUMP) > 0 && mtMoveType == MOVETYPE_WALK && !bInWater) { int iOldButtons = GetEntProp(client, Prop_Data, "m_nOldButtons"); - SetEntProp(client, Prop_Data, "m_nOldButtons", iOldButtons & ~IN_JUMP); + SetEntProp(client, Prop_Data, "m_nOldButtons", (iOldButtons & ~IN_JUMP)); + iPButtons &= ~IN_JUMP; } else if(gB_DoubleSteps[client] && (buttons & IN_JUMP) == 0) { - buttons |= IN_JUMP; + iPButtons = buttons |= IN_JUMP; + } + + // perf jump measuring + if(!bInWater && mtMoveType == MOVETYPE_WALK && iGroundEntity != -1) + { + gI_GroundTicks[client]++; + + if((((gI_ButtonCache[client] & IN_JUMP) == 0 && (iPButtons & IN_JUMP) > 0) || iPButtons != buttons) && + 1 <= gI_GroundTicks[client] <= 8) + { + gI_MeasuredJumps[client]++; + + if(gI_GroundTicks[client] == 1) + { + gI_PerfectJumps[client]++; + } + } + } + + else + { + gI_GroundTicks[client] = 0; } if(bInStart && gB_BlockPreJump && !gA_StyleSettings[gI_Style[client]][bPrespeed] && (vel[2] > 0 || (buttons & IN_JUMP) > 0)) @@ -2208,7 +2230,7 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 } } - gI_ButtonCache[client] = buttons; + gI_ButtonCache[client] = iPButtons; gF_AngleCache[client] = angles[1]; return Plugin_Continue; diff --git a/addons/sourcemod/scripting/shavit-hud.sp b/addons/sourcemod/scripting/shavit-hud.sp index 8ae26f98..95774044 100644 --- a/addons/sourcemod/scripting/shavit-hud.sp +++ b/addons/sourcemod/scripting/shavit-hud.sp @@ -761,7 +761,7 @@ void UpdateHUD(int client) { if(gA_StyleSettings[style][bSync]) { - Format(sHintText, 512, "%s%s\t%T: %d (%.02f%%)", sHintText, (iSpeed < 1000)? "\t":"", "HudStrafeText", client, strafes, Shavit_GetSync(target)); + Format(sHintText, 512, "%s%s\t%T: %d (%.01f%%)", sHintText, (iSpeed < 1000)? "\t":"", "HudStrafeText", client, strafes, Shavit_GetSync(target)); } else @@ -1075,9 +1075,16 @@ void UpdateKeyHint(int client) if(IsValidClient(target) && (target == client || (gI_HUDSettings[client] & HUD_OBSERVE) > 0)) { - if((gI_HUDSettings[client] & HUD_SYNC) > 0 && Shavit_GetTimerStatus(target) == Timer_Running && gA_StyleSettings[Shavit_GetBhopStyle(target)][bSync] && !IsFakeClient(target) && (!gB_Zones || !Shavit_InsideZone(target, Zone_Start, -1))) + int style = Shavit_GetBhopStyle(target); + + if((gI_HUDSettings[client] & HUD_SYNC) > 0 && Shavit_GetTimerStatus(target) == Timer_Running && gA_StyleSettings[style][bSync] && !IsFakeClient(target) && (!gB_Zones || !Shavit_InsideZone(target, Zone_Start, -1))) { - Format(sMessage, 256, "%s%s%T: %.02f", sMessage, (strlen(sMessage) > 0)? "\n\n":"", "HudSync", client, Shavit_GetSync(target)); + Format(sMessage, 256, "%s%s%T: %.01f", sMessage, (strlen(sMessage) > 0)? "\n\n":"", "HudSync", client, Shavit_GetSync(target)); + + if(!gA_StyleSettings[style][bAutobhop]) + { + Format(sMessage, 256, "%s\n%T: %.1f", sMessage, "HudPerfs", client, Shavit_GetPerfectJumps(target)); + } } if((gI_HUDSettings[client] & HUD_SPECTATORS) > 0) diff --git a/addons/sourcemod/scripting/shavit-misc.sp b/addons/sourcemod/scripting/shavit-misc.sp index 4120a10e..81a01a0e 100644 --- a/addons/sourcemod/scripting/shavit-misc.sp +++ b/addons/sourcemod/scripting/shavit-misc.sp @@ -50,19 +50,23 @@ enum CheckpointsCache iCPFlags, any:aCPSnapshot[TIMERSNAPSHOT_SIZE], String:sCPTargetname[32], + ArrayList:aCPFrames, + bool:bCPSegmented, + bool:bCPSpectated, PCPCACHE_SIZE } #pragma newdecls required #pragma semicolon 1 -#pragma dynamic 131072 +#pragma dynamic 524288 #define CP_ANGLES (1 << 0) #define CP_VELOCITY (1 << 1) #define CP_DEFAULT (CP_ANGLES|CP_VELOCITY) -#define CP_MAX 1000 // segmented runs shouldn't even reach 1k jumps on any map anyways +#define CP_MAX 1000 // shouldn't even reach 1k jumps on any map when routing anyways +#define CP_MAX_SEGMENTED 10 // game specific EngineVersion gEV_Type = Engine_Unknown; @@ -74,6 +78,7 @@ char gS_RadioCommands[][] = {"coverme", "takepoint", "holdpos", "regroup", "foll // cache ConVar sv_disable_immunity_alpha = null; +ConVar mp_humanteam = null; ConVar hostname = null; ConVar hostport = null; @@ -84,7 +89,7 @@ int gI_LastShot[MAXPLAYERS+1]; ArrayList gA_Advertisements = null; int gI_AdvertisementsCycle = 0; char gS_CurrentMap[192]; -int gBS_Style[MAXPLAYERS+1]; +int gI_Style[MAXPLAYERS+1]; enum { @@ -167,6 +172,7 @@ bool gB_DropAll = true; bool gB_ResetTargetname = false; bool gB_RestoreStates = false; bool gB_JointeamHook = true; +int gI_HumanTeam = 0; // dhooks Handle gH_GetPlayerMaxSpeed = null; @@ -277,6 +283,12 @@ public void OnPluginStart() gA_Advertisements = new ArrayList(300); hostname = FindConVar("hostname"); hostport = FindConVar("hostport"); + mp_humanteam = FindConVar("mp_humanteam"); + + if(mp_humanteam == null) + { + mp_humanteam = FindConVar("mp_humans_must_join_team"); + } // cvars and stuff gCV_GodMode = CreateConVar("shavit_misc_godmode", "3", "Enable godmode for players?\n0 - Disabled\n1 - Only prevent fall/world damage.\n2 - Only prevent damage from other players.\n3 - Full godmode.", 0, true, 0.0, true, 3.0); @@ -336,6 +348,7 @@ public void OnPluginStart() gCV_ResetTargetname.AddChangeHook(OnConVarChanged); gCV_RestoreStates.AddChangeHook(OnConVarChanged); gCV_JointeamHook.AddChangeHook(OnConVarChanged); + mp_humanteam.AddChangeHook(OnConVarChanged); AutoExecConfig(); @@ -425,7 +438,7 @@ public void OnClientCookiesCached(int client) gI_CheckpointsSettings[client] = StringToInt(sSetting); } - gBS_Style[client] = Shavit_GetBhopStyle(client); + gI_Style[client] = Shavit_GetBhopStyle(client); } public void Shavit_OnStyleConfigLoaded(int styles) @@ -440,6 +453,7 @@ public void Shavit_OnStyleConfigLoaded(int styles) Shavit_GetStyleSettings(i, gA_StyleSettings[i]); Shavit_GetStyleStrings(i, sStyleName, gS_StyleStrings[i][sStyleName], 128); Shavit_GetStyleStrings(i, sClanTag, gS_StyleStrings[i][sClanTag], 128); + Shavit_GetStyleStrings(i, sSpecialString, gS_StyleStrings[i][sSpecialString], 128); } } @@ -458,7 +472,14 @@ public void Shavit_OnChatConfigLoaded() public void Shavit_OnStyleChanged(int client, int oldstyle, int newstyle, int track, bool manual) { - gBS_Style[client] = newstyle; + gI_Style[client] = newstyle; + + if(StrContains(gS_StyleStrings[oldstyle][sSpecialString], "segments") == -1 && + StrContains(gS_StyleStrings[newstyle][sSpecialString], "segments") != -1) + { + OpenCheckpointsMenu(client, 0); + Shavit_PrintToChat(client, "%T", "MiscSegmentedCommand", client, gS_ChatStrings[sMessageVariable], gS_ChatStrings[sMessageText]); + } } public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue) @@ -491,6 +512,24 @@ public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] n gB_ResetTargetname = gCV_ResetTargetname.BoolValue; gB_RestoreStates = gCV_RestoreStates.BoolValue; gB_JointeamHook = gCV_JointeamHook.BoolValue; + + if(convar == mp_humanteam) + { + if(StrEqual(newValue, "t", false) || StrEqual(newValue, "red", false)) + { + gI_HumanTeam = 2; + } + + else if(StrEqual(newValue, "ct", false) || StrEqual(newValue, "blue", false)) + { + gI_HumanTeam = 3; + } + + else + { + gI_HumanTeam = 0; + } + } } public void OnConfigsExecuted() @@ -503,6 +542,11 @@ public void OnConfigsExecuted() public void OnMapStart() { + for(int i = 1; i <= MaxClients; i++) + { + ResetCheckpoints(i); + } + gSM_Checkpoints.Clear(); GetCurrentMap(gS_CurrentMap, 192); @@ -547,6 +591,14 @@ public void OnMapStart() } } +public void OnMapEnd() +{ + for(int i = 1; i <= MaxClients; i++) + { + ResetCheckpoints(i); + } +} + bool LoadAdvertisementsConfig() { gA_Advertisements.Clear(); @@ -637,6 +689,11 @@ public Action Command_Jointeam(int client, const char[] command, int args) int iTeam = StringToInt(arg1); + if(gI_HumanTeam == 0 && !(0 <= iTeam <= 1)) + { + iTeam = gI_HumanTeam; + } + bool bRespawn = false; switch(iTeam) @@ -727,7 +784,7 @@ public MRESReturn DHook_GetPlayerMaxSpeed(int pThis, Handle hReturn) return MRES_Ignored; } - DHookSetReturn(hReturn, view_as(gA_StyleSettings[gBS_Style[pThis]][fRunspeed])); + DHookSetReturn(hReturn, view_as(gA_StyleSettings[gI_Style[pThis]][fRunspeed])); return MRES_Override; } @@ -888,8 +945,8 @@ void UpdateClanTag(int client) char[] sCustomTag = new char[32]; strcopy(sCustomTag, 32, gS_ClanTag); - ReplaceString(sCustomTag, 32, "{style}", gS_StyleStrings[gBS_Style[client]][sStyleName]); - ReplaceString(sCustomTag, 32, "{styletag}", gS_StyleStrings[gBS_Style[client]][sClanTag]); + ReplaceString(sCustomTag, 32, "{style}", gS_StyleStrings[gI_Style[client]][sStyleName]); + ReplaceString(sCustomTag, 32, "{styletag}", gS_StyleStrings[gI_Style[client]][sClanTag]); ReplaceString(sCustomTag, 32, "{time}", sTime); ReplaceString(sCustomTag, 32, "{tr}", sTrack); @@ -919,7 +976,7 @@ public Action Shavit_OnUserCmdPre(int client, int &buttons, int &impulse, float int iGroundEntity = GetEntPropEnt(client, Prop_Send, "m_hGroundEntity"); // prespeed - if(!bNoclip && !gA_StyleSettings[gBS_Style[client]][bPrespeed] && Shavit_InsideZone(client, Zone_Start, track)) + if(!bNoclip && !gA_StyleSettings[gI_Style[client]][bPrespeed] && Shavit_InsideZone(client, Zone_Start, track)) { if((gI_PreSpeed == 2 || gI_PreSpeed == 3) && gI_GroundEntity[client] == -1 && iGroundEntity != -1 && (buttons & IN_JUMP) > 0) { @@ -940,7 +997,7 @@ public Action Shavit_OnUserCmdPre(int client, int &buttons, int &impulse, float if(gI_PreSpeed < 4) { - fLimit = view_as(gA_StyleSettings[gBS_Style[client]][fRunspeed]) + gF_PrestrafeLimit; + fLimit = view_as(gA_StyleSettings[gI_Style[client]][fRunspeed]) + gF_PrestrafeLimit; // if trying to jump, add a very low limit to stop prespeeding in an elegant way // otherwise, make sure nothing weird is happening (such as sliding at ridiculous speeds, at zone enter) @@ -985,7 +1042,7 @@ public void OnClientPutInServer(int client) if(!AreClientCookiesCached(client)) { - gBS_Style[client] = Shavit_GetBhopStyle(client); + gI_Style[client] = Shavit_GetBhopStyle(client); gB_Hide[client] = false; gI_CheckpointsSettings[client] = CP_DEFAULT; } @@ -1004,9 +1061,30 @@ public void OnClientPutInServer(int client) public void OnClientDisconnect(int client) { + if(gB_NoWeaponDrops) + { + int entity = -1; + + while((entity = FindEntityByClassname(entity, "weapon_*")) != -1) + { + if(GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity") == client) + { + RequestFrame(RemoveWeapon, EntIndexToEntRef(entity)); + } + } + } + ResetCheckpoints(client); } +void RemoveWeapon(any data) +{ + if(IsValidEntity(data)) + { + AcceptEntityInput(data, "Kill"); + } +} + void ResetCheckpoints(int client) { int serial = GetClientSerial(client); @@ -1015,6 +1093,14 @@ void ResetCheckpoints(int client) for(int i = 0; i < gI_CheckpointsCache[client][iCheckpoints]; i++) { FormatEx(key, 32, "%d_%d", serial, i); + + CheckpointsCache cpcache[PCPCACHE_SIZE]; + + if(gSM_Checkpoints.GetArray(key, cpcache[0], view_as(PCPCACHE_SIZE))) + { + delete cpcache[aCPFrames]; // free up replay frames if there are any + } + gSM_Checkpoints.Remove(key); } @@ -1099,7 +1185,7 @@ public void OnPreThink(int client) if(IsPlayerAlive(client)) { // not the best method, but only one i found for tf2 - SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", view_as(gA_StyleSettings[gBS_Style[client]][fRunspeed])); + SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", view_as(gA_StyleSettings[gI_Style[client]][fRunspeed])); } } @@ -1409,12 +1495,16 @@ public Action Command_Save(int client, int args) return Plugin_Handled; } - if(!gB_Checkpoints) + bool bSegmented = CanSegment(client); + + if(!gB_Checkpoints && !bSegmented) { Shavit_PrintToChat(client, "%T", "FeatureDisabled", client, gS_ChatStrings[sMessageWarning], gS_ChatStrings[sMessageText]); return Plugin_Handled; } + + int iMaxCPs = GetMaxCPs(client); if(args > 0) { @@ -1425,7 +1515,7 @@ public Action Command_Save(int client, int args) int parsed = StringToInt(arg); - if(parsed > 0 && parsed <= CP_MAX) + if(parsed > 0 && parsed <= iMaxCPs) { index = (parsed - 1); } @@ -1440,7 +1530,7 @@ public Action Command_Save(int client, int args) { bool bSaved = false; - if(gI_CheckpointsCache[client][iCheckpoints] < CP_MAX) + if(gI_CheckpointsCache[client][iCheckpoints] < iMaxCPs) { if((bSaved = SaveCheckpoint(client, gI_CheckpointsCache[client][iCheckpoints]))) { @@ -1448,9 +1538,9 @@ public Action Command_Save(int client, int args) } } - else if((bSaved = SaveCheckpoint(client, (CP_MAX - 1)))) + else if((bSaved = SaveCheckpoint(client, (iMaxCPs - 1), true))) { - gI_CheckpointsCache[client][iCurrentCheckpoint] = CP_MAX; + gI_CheckpointsCache[client][iCurrentCheckpoint] = iMaxCPs; } if(bSaved) @@ -1501,7 +1591,9 @@ public Action Command_Tele(int client, int args) public Action OpenCheckpointsMenu(int client, int item) { - if(!gB_Checkpoints) + bool bSegmented = CanSegment(client); + + if(!gB_Checkpoints && !bSegmented) { Shavit_PrintToChat(client, "%T", "FeatureDisabled", client, gS_ChatStrings[sMessageWarning], gS_ChatStrings[sMessageText]); @@ -1509,7 +1601,16 @@ public Action OpenCheckpointsMenu(int client, int item) } Menu menu = new Menu(MenuHandler_Checkpoints, MENU_ACTIONS_DEFAULT|MenuAction_DisplayItem); - menu.SetTitle("%T\n%T\n ", "MiscCheckpointMenu", client, "MiscCheckpointWarning", client); + + if(!bSegmented) + { + menu.SetTitle("%T\n%T\n ", "MiscCheckpointMenu", client, "MiscCheckpointWarning", client); + } + + else + { + menu.SetTitle("%T\n ", "MiscCheckpointMenuSegmented", client); + } char[] sDisplay = new char[64]; FormatEx(sDisplay, 64, "%T", "MiscCheckpointSave", client, (gI_CheckpointsCache[client][iCheckpoints] + 1)); @@ -1536,14 +1637,17 @@ public Action OpenCheckpointsMenu(int client, int item) FormatEx(sDisplay, 64, "%T", "MiscCheckpointReset", client); menu.AddItem("reset", sDisplay); - char[] sInfo = new char[16]; - IntToString(CP_ANGLES, sInfo, 16); - FormatEx(sDisplay, 64, "%T", "MiscCheckpointUseAngles", client); - menu.AddItem(sInfo, sDisplay); + if(!bSegmented) + { + char[] sInfo = new char[16]; + IntToString(CP_ANGLES, sInfo, 16); + FormatEx(sDisplay, 64, "%T", "MiscCheckpointUseAngles", client); + menu.AddItem(sInfo, sDisplay); - IntToString(CP_VELOCITY, sInfo, 16); - FormatEx(sDisplay, 64, "%T", "MiscCheckpointUseVelocity", client); - menu.AddItem(sInfo, sDisplay); + IntToString(CP_VELOCITY, sInfo, 16); + FormatEx(sDisplay, 64, "%T", "MiscCheckpointUseVelocity", client); + menu.AddItem(sInfo, sDisplay); + } menu.Pagination = MENU_NO_PAGINATION; menu.ExitButton = true; @@ -1556,30 +1660,42 @@ public int MenuHandler_Checkpoints(Menu menu, MenuAction action, int param1, int { if(action == MenuAction_Select) { - int current = gI_CheckpointsCache[param1][iCurrentCheckpoint]; + bool bSegmenting = CanSegment(param1); + int iMaxCPs = GetMaxCPs(param1); + int iCurrent = gI_CheckpointsCache[param1][iCurrentCheckpoint]; switch(param2) { case 0: { - // fight an exploit - if(gI_CheckpointsCache[param1][iCheckpoints] >= CP_MAX) + if(!bSegmenting) { - return 0; - } + // fight an exploit + if(gI_CheckpointsCache[param1][iCheckpoints] >= iMaxCPs) + { + return 0; + } - SaveCheckpoint(param1, gI_CheckpointsCache[param1][iCheckpoints]); - gI_CheckpointsCache[param1][iCurrentCheckpoint] = ++gI_CheckpointsCache[param1][iCheckpoints]; + SaveCheckpoint(param1, gI_CheckpointsCache[param1][iCheckpoints]); + gI_CheckpointsCache[param1][iCurrentCheckpoint] = ++gI_CheckpointsCache[param1][iCheckpoints]; + } + + else + { + bool bOverflow = gI_CheckpointsCache[param1][iCheckpoints] >= iMaxCPs; + SaveCheckpoint(param1, gI_CheckpointsCache[param1][iCheckpoints], bOverflow); + gI_CheckpointsCache[param1][iCurrentCheckpoint] = (bOverflow)? iMaxCPs:++gI_CheckpointsCache[param1][iCheckpoints]; + } } case 1: { - TeleportToCheckpoint(param1, current - 1, true); + TeleportToCheckpoint(param1, iCurrent - 1, true); } case 2: { - if(current > 1) + if(iCurrent > 1) { gI_CheckpointsCache[param1][iCurrentCheckpoint]--; } @@ -1589,7 +1705,7 @@ public int MenuHandler_Checkpoints(Menu menu, MenuAction action, int param1, int { CheckpointsCache cpcache[PCPCACHE_SIZE]; - if(current < CP_MAX && GetCheckpoint(param1, current, cpcache)) + if(iCurrent < iMaxCPs && GetCheckpoint(param1, iCurrent, cpcache)) { gI_CheckpointsCache[param1][iCurrentCheckpoint]++; } @@ -1636,7 +1752,7 @@ public int MenuHandler_Checkpoints(Menu menu, MenuAction action, int param1, int return 0; } -bool SaveCheckpoint(int client, int index) +bool SaveCheckpoint(int client, int index, bool overflow = false) { int target = client; @@ -1655,6 +1771,18 @@ bool SaveCheckpoint(int client, int index) return false; } + char[] sKey = new char[32]; + int iSerial = GetClientSerial(client); + FormatEx(sKey, 32, "%d_%d", iSerial, index); + + CheckpointsCache cpcacheprev[PCPCACHE_SIZE]; + + if(gSM_Checkpoints.GetArray(sKey, cpcacheprev[0], view_as(PCPCACHE_SIZE))) + { + delete cpcacheprev[aCPFrames]; + gSM_Checkpoints.SetArray(sKey, cpcacheprev[0], view_as(PCPCACHE_SIZE)); + } + CheckpointsCache cpcache[PCPCACHE_SIZE]; float temp[3]; @@ -1726,6 +1854,55 @@ bool SaveCheckpoint(int client, int index) } CopyArray(snapshot, cpcache[aCPSnapshot], TIMERSNAPSHOT_SIZE); + + if(CanSegment(target)) + { + if(gB_Replay) + { + cpcache[aCPFrames] = Shavit_GetReplayData(target); + } + + cpcache[bCPSegmented] = true; + } + + else + { + cpcache[aCPFrames] = null; + cpcache[bCPSegmented] = false; + } + + cpcache[bCPSpectated] = (client != target); + + if(overflow) + { + int iMaxCPs = GetMaxCPs(client); + + for(int i = 0; i < iMaxCPs; i++) + { + CheckpointsCache cpcacheold[PCPCACHE_SIZE]; + FormatEx(sKey, 32, "%d_%d", iSerial, i); + + if(!gSM_Checkpoints.GetArray(sKey, cpcacheold[0], view_as(PCPCACHE_SIZE))) + { + continue; // ??? + } + + if(i == 0) + { + delete cpcacheold[aCPFrames]; + gSM_Checkpoints.Remove(sKey); + + continue; + } + + gSM_Checkpoints.Remove(sKey); + FormatEx(sKey, 32, "%d_%d", iSerial, (i - 1)); // set cp index to one less + gSM_Checkpoints.SetArray(sKey, cpcacheold[0], view_as(PCPCACHE_SIZE)); + } + + index = (iMaxCPs - 1); + } + SetCheckpoint(client, index, cpcache); return true; @@ -1733,7 +1910,7 @@ bool SaveCheckpoint(int client, int index) void TeleportToCheckpoint(int client, int index, bool suppressMessage) { - if(index < 0 || index >= CP_MAX) + if(index < 0 || index >= CP_MAX || (!gB_Checkpoints && !CanSegment(client))) { return; } @@ -1769,7 +1946,10 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) Shavit_StopTimer(client); } - Shavit_SetPracticeMode(client, true, !bInStart); + if(!cpcache[bCPSegmented] || cpcache[bCPSpectated]) + { + Shavit_SetPracticeMode(client, true, !bInStart); + } any snapshot[TIMERSNAPSHOT_SIZE]; CopyArray(cpcache[aCPSnapshot], snapshot, TIMERSNAPSHOT_SIZE); @@ -1782,8 +1962,8 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) CopyArray(cpcache[fCPVelocity], vel, 3); TeleportEntity(client, pos, - ((gI_CheckpointsSettings[client] & CP_ANGLES) > 0)? ang:NULL_VECTOR, - ((gI_CheckpointsSettings[client] & CP_VELOCITY) > 0)? vel:NULL_VECTOR); + ((gI_CheckpointsSettings[client] & CP_ANGLES) > 0 || cpcache[bCPSegmented])? ang:NULL_VECTOR, + ((gI_CheckpointsSettings[client] & CP_VELOCITY) > 0 || cpcache[bCPSegmented])? vel:NULL_VECTOR); MoveType mt = cpcache[mtCPMoveType]; @@ -1815,6 +1995,24 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) SetEntPropFloat(client, Prop_Send, "m_flDuckAmount", cpcache[fCPDucktime]); SetEntPropFloat(client, Prop_Send, "m_flDuckSpeed", cpcache[fCPDuckSpeed]); } + + if(cpcache[bCPSegmented] && gB_Replay) + { + if(cpcache[aCPFrames] == null) + { + LogError("SetReplayData for %L failed, recorded frames are null.", client); + } + + else + { + Shavit_SetReplayData(client, cpcache[aCPFrames]); + + if((gI_CheckpointsSettings[client] & CP_ANGLES) > 0) + { + Shavit_HijackAngles(client, ang[0], ang[1]); + } + } + } if(!suppressMessage) { @@ -1977,7 +2175,7 @@ public Action Command_Specs(int client, int args) public Action Shavit_OnStart(int client) { - if(!gA_StyleSettings[gBS_Style[client]][bPrespeed] && GetEntityMoveType(client) == MOVETYPE_NOCLIP) + if(!gA_StyleSettings[gI_Style[client]][bPrespeed] && GetEntityMoveType(client) == MOVETYPE_NOCLIP) { return Plugin_Stop; } @@ -2499,6 +2697,7 @@ void LoadState(int client) if(gB_Replay && gA_SaveFrames[client] != null) { Shavit_SetReplayData(client, gA_SaveFrames[client]); + Shavit_HijackAngles(client, gF_SaveStateData[client][1][0], gF_SaveStateData[client][1][1]); } delete gA_SaveFrames[client]; @@ -2550,3 +2749,13 @@ void CopyArray(const any[] from, any[] to, int size) to[i] = from[i]; } } + +bool CanSegment(int client) +{ + return StrContains(gS_StyleStrings[gI_Style[client]][sSpecialString], "segments") != -1; +} + +int GetMaxCPs(int client) +{ + return CanSegment(client)? CP_MAX_SEGMENTED:CP_MAX; +} diff --git a/addons/sourcemod/scripting/shavit-replay.sp b/addons/sourcemod/scripting/shavit-replay.sp index fdc096e8..33a3c479 100644 --- a/addons/sourcemod/scripting/shavit-replay.sp +++ b/addons/sourcemod/scripting/shavit-replay.sp @@ -24,6 +24,7 @@ #undef REQUIRE_PLUGIN #include +#include #undef REQUIRE_EXTENSIONS #include @@ -60,6 +61,12 @@ enum REPLAYSTRINGS_SIZE }; +enum +{ + iBotShooting_Attack1 = (1 << 0), + iBotShooting_Attack2 = (1 << 1) +} + // game type EngineVersion gEV_Type = Engine_Unknown; @@ -82,7 +89,6 @@ bool gB_Record[MAXPLAYERS+1]; int gI_Track[MAXPLAYERS+1]; bool gB_Late = false; -int gI_DefaultTeamSlots = 0; // server specific float gF_Tickrate = 0.0; @@ -94,6 +100,8 @@ any gA_CentralCache[CENTRALBOTCACHE_SIZE]; // how do i call this bool gB_HideNameChange = false; bool gB_DontCallTimer = false; +bool gB_HijackFrame[MAXPLAYERS+1]; +float gF_HijackedAngles[MAXPLAYERS+1][2]; // plugin cvars ConVar gCV_Enabled = null; @@ -101,6 +109,7 @@ ConVar gCV_ReplayDelay = null; ConVar gCV_TimeLimit = null; ConVar gCV_DefaultTeam = null; ConVar gCV_CentralBot = null; +ConVar gCV_BotShooting = null; // cached cvars bool gB_Enabled = true; @@ -108,6 +117,7 @@ float gF_ReplayDelay = 5.0; float gF_TimeLimit = 5400.0; int gI_DefaultTeam = 3; bool gB_CentralBot = true; +int gI_BotShooting = 3; // timer settings int gI_Styles = 0; @@ -120,6 +130,10 @@ char gS_ChatStrings[CHATSETTINGS_SIZE][128]; // replay settings char gS_ReplayStrings[REPLAYSTRINGS_SIZE][MAX_NAME_LENGTH]; +// admin menu +TopMenu gH_AdminMenu = null; +TopMenuObject gH_TimerCommands = INVALID_TOPMENUOBJECT; + // database related things Database gH_SQL = null; char gS_MySQLPrefix[32]; @@ -146,6 +160,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("Shavit_GetReplayLength", Native_GetReplayLength); CreateNative("Shavit_GetReplayName", Native_GetReplayName); CreateNative("Shavit_GetReplayTime", Native_GetReplayTime); + CreateNative("Shavit_HijackAngles", Native_HijackAngles); CreateNative("Shavit_IsReplayDataLoaded", Native_IsReplayDataLoaded); CreateNative("Shavit_ReloadReplay", Native_ReloadReplay); CreateNative("Shavit_ReloadReplays", Native_ReloadReplays); @@ -196,22 +211,29 @@ public void OnPluginStart() gCV_TimeLimit = CreateConVar("shavit_replay_timelimit", "5400.0", "Maximum amount of time (in seconds) to allow saving to disk.\nDefault is 5400.0 (1:30 hours)\n0 - Disabled"); gCV_DefaultTeam = CreateConVar("shavit_replay_defaultteam", "3", "Default team to make the bots join, if possible.\n2 - Terrorists/RED\n3 - Counter Terrorists/BLU", 0, true, 2.0, true, 3.0); gCV_CentralBot = CreateConVar("shavit_replay_centralbot", "1", "Have one central bot instead of one bot per replay.\nTriggered with !replay.\nRestart the map for changes to take effect.\nThe disabled setting is not supported - use at your own risk.\n0 - Disabled\n1 - Enabled", 0, true, 0.0, true, 1.0); + gCV_BotShooting = CreateConVar("shavit_replay_botshooting", "3", "Attacking buttons to allow for bots.\n0 - none\1 - +attack\n2 - +attack2\n3 - both", 0, true, 0.0, true, 3.0); gCV_Enabled.AddChangeHook(OnConVarChanged); gCV_ReplayDelay.AddChangeHook(OnConVarChanged); gCV_TimeLimit.AddChangeHook(OnConVarChanged); gCV_DefaultTeam.AddChangeHook(OnConVarChanged); gCV_CentralBot.AddChangeHook(OnConVarChanged); + gCV_BotShooting.AddChangeHook(OnConVarChanged); AutoExecConfig(); + // admin menu + if(LibraryExists("adminmenu") && ((gH_AdminMenu = GetAdminTopMenu()) != null)) + { + OnAdminMenuReady(gH_AdminMenu); + } + // hooks HookEvent("player_spawn", Player_Event, EventHookMode_Pre); HookEvent("player_death", Player_Event, EventHookMode_Pre); HookEvent("player_connect", BotEvents, EventHookMode_Pre); HookEvent("player_disconnect", BotEvents, EventHookMode_Pre); HookEventEx("player_connect_client", BotEvents, EventHookMode_Pre); - HookEvent("round_start", Round_Start); // name change suppression HookUserMessage(GetUserMessageId("SayText2"), Hook_SayText2, true); @@ -231,6 +253,7 @@ public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] n gF_TimeLimit = gCV_TimeLimit.FloatValue; gI_DefaultTeam = gCV_DefaultTeam.IntValue; gB_CentralBot = gCV_CentralBot.BoolValue; + gI_BotShooting = gCV_BotShooting.IntValue; if(convar == gCV_CentralBot) { @@ -238,6 +261,60 @@ public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] n } } +public void OnAdminMenuCreated(Handle topmenu) +{ + if(gH_AdminMenu == null || (topmenu == gH_AdminMenu && gH_TimerCommands != INVALID_TOPMENUOBJECT)) + { + return; + } + + gH_TimerCommands = gH_AdminMenu.AddCategory("Timer Commands", CategoryHandler, "shavit_admin", ADMFLAG_RCON); +} + +public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if(action == TopMenuAction_DisplayTitle) + { + FormatEx(buffer, maxlength, "%T:", "TimerCommands", param); + } + + else if(action == TopMenuAction_DisplayOption) + { + FormatEx(buffer, maxlength, "%T", "TimerCommands", param); + } +} + +public void OnAdminMenuReady(Handle topmenu) +{ + if((gH_AdminMenu = GetAdminTopMenu()) != null) + { + if(gH_TimerCommands == INVALID_TOPMENUOBJECT) + { + gH_TimerCommands = gH_AdminMenu.FindCategory("Timer Commands"); + + if(gH_TimerCommands == INVALID_TOPMENUOBJECT) + { + OnAdminMenuCreated(topmenu); + } + } + + gH_AdminMenu.AddItem("sm_deletereplay", AdminMenu_DeleteReplay, gH_TimerCommands, "sm_deletereplay", ADMFLAG_RCON); + } +} + +public void AdminMenu_DeleteReplay(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if(action == TopMenuAction_DisplayOption) + { + FormatEx(buffer, maxlength, "%t", "DeleteReplayAdminMenu"); + } + + else if(action == TopMenuAction_SelectOption) + { + Command_DeleteReplay(param, 0); + } +} + public int Native_GetReplayBotFirstFrame(Handle handler, int numParams) { SetNativeCellRef(2, gF_StartTick[GetNativeCell(1)]); @@ -360,10 +437,16 @@ public int Native_SetReplayData(Handle handler, int numParams) int client = GetNativeCell(1); ArrayList frames = view_as(CloneHandle(GetNativeCell(2), handler)); + delete gA_PlayerFrames[client]; gA_PlayerFrames[client] = frames.Clone(); delete frames; gI_PlayerFrames[client] = gA_PlayerFrames[client].Length; + + if(gI_PlayerFrames[client] > 0) + { + gB_Record[client] = true; + } } public int Native_GetReplayData(Handle handler, int numParams) @@ -422,6 +505,15 @@ public int Native_GetReplayTime(Handle handler, int numParams) return view_as(float(gI_ReplayTick[style]) / gF_Tickrate); } +public int Native_HijackAngles(Handle handler, int numParams) +{ + int client = GetNativeCell(1); + + gB_HijackFrame[client] = true; + gF_HijackedAngles[client][0] = view_as(GetNativeCell(2)); + gF_HijackedAngles[client][1] = view_as(GetNativeCell(3)); +} + public int Native_GetReplayBotStyle(Handle handler, int numParams) { return (gB_CentralBot && gA_CentralCache[iCentralReplayStatus] == Replay_Idle)? -1:GetReplayStyle(GetNativeCell(1)); @@ -1185,7 +1277,7 @@ void UpdateReplayInfo(int client, int style, float time, int track) } } - if(gI_DefaultTeamSlots >= gI_Styles && GetClientTeam(client) != gI_DefaultTeam) + if(GetClientTeam(client) != gI_DefaultTeam) { if(gEV_Type == Engine_TF2) { @@ -1461,6 +1553,16 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 buttons = gA_Frames[style][track].Get(gI_ReplayTick[style], 5); + if((gI_BotShooting & iBotShooting_Attack1) == 0) + { + buttons &= ~IN_ATTACK; + } + + if((gI_BotShooting & iBotShooting_Attack2) == 0) + { + buttons &= ~IN_ATTACK2; + } + MoveType mt = MOVETYPE_NOCLIP; if(gA_FrameCache[style][track][3] >= 0x02) @@ -1524,8 +1626,19 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 gA_PlayerFrames[client].Set(gI_PlayerFrames[client], vecCurrentPosition[1], 1); gA_PlayerFrames[client].Set(gI_PlayerFrames[client], vecCurrentPosition[2], 2); - gA_PlayerFrames[client].Set(gI_PlayerFrames[client], angles[0], 3); - gA_PlayerFrames[client].Set(gI_PlayerFrames[client], angles[1], 4); + if(!gB_HijackFrame[client]) + { + gA_PlayerFrames[client].Set(gI_PlayerFrames[client], angles[0], 3); + gA_PlayerFrames[client].Set(gI_PlayerFrames[client], angles[1], 4); + } + + else + { + gA_PlayerFrames[client].Set(gI_PlayerFrames[client], gF_HijackedAngles[client][0], 3); + gA_PlayerFrames[client].Set(gI_PlayerFrames[client], gF_HijackedAngles[client][1], 4); + + gB_HijackFrame[client] = false; + } gA_PlayerFrames[client].Set(gI_PlayerFrames[client], buttons, 5); gA_PlayerFrames[client].Set(gI_PlayerFrames[client], GetEntityFlags(client), 6); @@ -1642,30 +1755,6 @@ public void BotEvents(Event event, const char[] name, bool dontBroadcast) } } -public void Round_Start(Event event, const char[] name, bool dontBroadcast) -{ - gI_DefaultTeamSlots = 0; - - char[] sEntity = new char[32]; - - if(gEV_Type == Engine_TF2) - { - strcopy(sEntity, 32, "info_player_teamspawn"); - } - - else - { - strcopy(sEntity, 32, (gI_DefaultTeam == 2)? "info_player_terrorist":"info_player_counterterrorist"); - } - - int iEntity = -1; - - while((iEntity = FindEntityByClassname(iEntity, sEntity)) != INVALID_ENT_REFERENCE) - { - gI_DefaultTeamSlots++; - } -} - public Action Hook_SayText2(UserMsg msg_id, any msg, const int[] players, int playersNum, bool reliable, bool init) { if(!gB_HideNameChange || !gB_Enabled) diff --git a/addons/sourcemod/scripting/shavit-wr.sp b/addons/sourcemod/scripting/shavit-wr.sp index e649ef09..d00893fe 100644 --- a/addons/sourcemod/scripting/shavit-wr.sp +++ b/addons/sourcemod/scripting/shavit-wr.sp @@ -62,7 +62,8 @@ ArrayList gA_Leaderboard[STYLE_LIMIT][TRACKS_SIZE]; float gF_PlayerRecord[MAXPLAYERS+1][STYLE_LIMIT][TRACKS_SIZE]; // admin menu -Handle gH_AdminMenu = null; +TopMenu gH_AdminMenu = null; +TopMenuObject gH_TimerCommands = INVALID_TOPMENUOBJECT; // table prefix char gS_MySQLPrefix[32]; @@ -130,10 +131,10 @@ public void OnPluginStart() #endif // forwards - gH_OnWorldRecord = CreateGlobalForward("Shavit_OnWorldRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); - gH_OnFinish_Post = CreateGlobalForward("Shavit_OnFinish_Post", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnWorldRecord = CreateGlobalForward("Shavit_OnWorldRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnFinish_Post = CreateGlobalForward("Shavit_OnFinish_Post", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); gH_OnWRDeleted = CreateGlobalForward("Shavit_OnWRDeleted", ET_Event, Param_Cell, Param_Cell, Param_Cell); - gH_OnWorstRecord = CreateGlobalForward("Shavit_OnWorstRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + gH_OnWorstRecord = CreateGlobalForward("Shavit_OnWorstRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell); // player commands RegConsoleCmd("sm_wr", Command_WorldRecord, "View the leaderboard of a map. Usage: sm_wr [map]"); @@ -164,7 +165,10 @@ public void OnPluginStart() AutoExecConfig(); // admin menu - OnAdminMenuReady(null); + if(LibraryExists("adminmenu") && ((gH_AdminMenu = GetAdminTopMenu()) != null)) + { + OnAdminMenuReady(gH_AdminMenu); + } // modules gB_Rankings = LibraryExists("shavit-rankings"); @@ -182,18 +186,46 @@ public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] n gI_RecentLimit = gCV_RecentLimit.IntValue; } +public void OnAdminMenuCreated(Handle topmenu) +{ + if(gH_AdminMenu == null || (topmenu == gH_AdminMenu && gH_TimerCommands != INVALID_TOPMENUOBJECT)) + { + return; + } + + gH_TimerCommands = gH_AdminMenu.AddCategory("Timer Commands", CategoryHandler, "shavit_admin", ADMFLAG_RCON); +} + +public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +{ + if(action == TopMenuAction_DisplayTitle) + { + FormatEx(buffer, maxlength, "%T:", "TimerCommands", param); + } + + else if(action == TopMenuAction_DisplayOption) + { + FormatEx(buffer, maxlength, "%T", "TimerCommands", param); + } +} + public void OnAdminMenuReady(Handle topmenu) { - if(LibraryExists("adminmenu") && ((gH_AdminMenu = GetAdminTopMenu()) != null)) + if((gH_AdminMenu = GetAdminTopMenu()) != null) { - TopMenuObject tmoTimer = FindTopMenuCategory(gH_AdminMenu, "Timer Commands"); - - if(tmoTimer != INVALID_TOPMENUOBJECT) + if(gH_TimerCommands == INVALID_TOPMENUOBJECT) { - AddToTopMenu(gH_AdminMenu, "sm_deleteall", TopMenuObject_Item, AdminMenu_DeleteAll, tmoTimer, "sm_deleteall", ADMFLAG_RCON); - AddToTopMenu(gH_AdminMenu, "sm_delete", TopMenuObject_Item, AdminMenu_Delete, tmoTimer, "sm_delete", ADMFLAG_RCON); - AddToTopMenu(gH_AdminMenu, "sm_deletestylerecords", TopMenuObject_Item, AdminMenu_DeleteStyleRecords, tmoTimer, "sm_deletestylerecords", ADMFLAG_RCON); + gH_TimerCommands = gH_AdminMenu.FindCategory("Timer Commands"); + + if(gH_TimerCommands == INVALID_TOPMENUOBJECT) + { + OnAdminMenuCreated(topmenu); + } } + + gH_AdminMenu.AddItem("sm_deleteall", AdminMenu_DeleteAll, gH_TimerCommands, "sm_deleteall", ADMFLAG_RCON); + gH_AdminMenu.AddItem("sm_delete", AdminMenu_Delete, gH_TimerCommands, "sm_delete", ADMFLAG_RCON); + gH_AdminMenu.AddItem("sm_deletestylerecords", AdminMenu_DeleteStyleRecords, gH_TimerCommands, "sm_deletestylerecords", ADMFLAG_RCON); } } @@ -263,7 +295,7 @@ public void OnLibraryRemoved(const char[] name) else if(StrEqual(name, "adminmenu")) { - gH_AdminMenu = null; + delete gH_AdminMenu; } } @@ -668,7 +700,7 @@ void DeleteSubmenu(int client) char[] sDisplay = new char[64]; FormatEx(sDisplay, 64, "%s (%T: %d)", gS_StyleStrings[i][sStyleName], "WRRecord", client, gI_RecordAmount[i][gI_LastTrack[client]]); - menu.AddItem(sInfo, gS_StyleStrings[i][sStyleName]); + menu.AddItem(sInfo, sDisplay, (gI_RecordAmount[i][gI_LastTrack[client]] > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED); } menu.ExitButton = true; @@ -1639,7 +1671,7 @@ public int RRMenu_Handler(Menu m, MenuAction action, int param1, int param2) void OpenSubMenu(int client, int id) { char[] sQuery = new char[512]; - FormatEx(sQuery, 512, "SELECT u.name, p.time, p.jumps, p.style, u.auth, p.date, p.map, p.strafes, p.sync, p.points, p.track FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE p.id = %d LIMIT 1;", gS_MySQLPrefix, gS_MySQLPrefix, id); + FormatEx(sQuery, 512, "SELECT u.name, p.time, p.jumps, p.style, u.auth, p.date, p.map, p.strafes, p.sync, p.perfs, p.points, p.track FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE p.id = %d LIMIT 1;", gS_MySQLPrefix, gS_MySQLPrefix, id); DataPack datapack = new DataPack(); datapack.WriteCell(GetClientSerial(client)); @@ -1690,19 +1722,30 @@ public void SQL_SubMenu_Callback(Database db, DBResultSet results, const char[] menu.AddItem("-1", sDisplay); // 2 - jumps + int style = results.FetchInt(3); int jumps = results.FetchInt(2); - FormatEx(sDisplay, 128, "%T: %d", "WRJumps", client, jumps); + + if(gA_StyleSettings[style][bAutobhop]) + { + FormatEx(sDisplay, 128, "%T: %d", "WRJumps", client, jumps); + } + + else + { + float perfs = results.FetchFloat(9); + FormatEx(sDisplay, 128, "%T: %d (%.2f%%)", "WRJumps", client, jumps, perfs); + } + menu.AddItem("-1", sDisplay); // 3 - style - int style = results.FetchInt(3); FormatEx(sDisplay, 128, "%T: %s", "WRStyle", client, gS_StyleStrings[style][sStyleName]); menu.AddItem("-1", sDisplay); // 6 - map results.FetchString(6, sMap, 192); - float fPoints = results.FetchFloat(9); + float fPoints = results.FetchFloat(10); if(gB_Rankings && fPoints > 0.0) { @@ -1752,7 +1795,7 @@ public void SQL_SubMenu_Callback(Database db, DBResultSet results, const char[] menu.AddItem(sInfo, sMenuItem); } - GetTrackName(client, results.FetchInt(10), sTrack, 32); + GetTrackName(client, results.FetchInt(11), sTrack, 32); } else @@ -1889,12 +1932,12 @@ void SQL_DBConnect() if(gB_MySQL) { - FormatEx(sQuery, 512, "CREATE TABLE IF NOT EXISTS `%splayertimes` (`id` INT NOT NULL AUTO_INCREMENT, `auth` CHAR(32), `map` CHAR(128), `time` FLOAT, `jumps` INT, `style` TINYINT, `date` CHAR(16), `strafes` INT, `sync` FLOAT, `points` FLOAT NOT NULL DEFAULT 0, `track` TINYINT NOT NULL DEFAULT 0, PRIMARY KEY (`id`), INDEX `map` (`map`, `style`, `track`), INDEX `auth` (`auth`, `date`, `points`), INDEX `time` (`time`)) ENGINE=INNODB;", gS_MySQLPrefix); + FormatEx(sQuery, 512, "CREATE TABLE IF NOT EXISTS `%splayertimes` (`id` INT NOT NULL AUTO_INCREMENT, `auth` CHAR(32), `map` CHAR(128), `time` FLOAT, `jumps` INT, `style` TINYINT, `date` CHAR(16), `strafes` INT, `sync` FLOAT, `points` FLOAT NOT NULL DEFAULT 0, `track` TINYINT NOT NULL DEFAULT 0, `perfs` FLOAT DEFAULT 0, PRIMARY KEY (`id`), INDEX `map` (`map`, `style`, `track`), INDEX `auth` (`auth`, `date`, `points`), INDEX `time` (`time`)) ENGINE=INNODB;", gS_MySQLPrefix); } else { - FormatEx(sQuery, 512, "CREATE TABLE IF NOT EXISTS `%splayertimes` (`id` INTEGER PRIMARY KEY, `auth` CHAR(32), `map` CHAR(128), `time` FLOAT, `jumps` INT, `style` TINYINT, `date` CHAR(16), `strafes` INT, `sync` FLOAT, `points` FLOAT NOT NULL DEFAULT 0, `track` TINYINT NOT NULL DEFAULT 0);", gS_MySQLPrefix); + FormatEx(sQuery, 512, "CREATE TABLE IF NOT EXISTS `%splayertimes` (`id` INTEGER PRIMARY KEY, `auth` CHAR(32), `map` CHAR(128), `time` FLOAT, `jumps` INT, `style` TINYINT, `date` CHAR(16), `strafes` INT, `sync` FLOAT, `points` FLOAT NOT NULL DEFAULT 0, `track` TINYINT NOT NULL DEFAULT 0, `perfs` FLOAT DEFAULT 0);", gS_MySQLPrefix); } gH_SQL.Query(SQL_CreateTable_Callback, sQuery, 0, DBPrio_High); @@ -1934,6 +1977,9 @@ public void SQL_CreateTable_Callback(Database db, DBResultSet results, const cha FormatEx(sQuery, 64, "SELECT track FROM %splayertimes LIMIT 1;", gS_MySQLPrefix); gH_SQL.Query(SQL_TableMigration4_Callback, sQuery); + + FormatEx(sQuery, 64, "SELECT perfs FROM %splayertimes LIMIT 1;", gS_MySQLPrefix); + gH_SQL.Query(SQL_TableMigration5_Callback, sQuery); } public void SQL_TableMigration1_Callback(Database db, DBResultSet results, const char[] error, any data) @@ -1964,8 +2010,6 @@ public void SQL_AlterTable1_Callback(Database db, DBResultSet results, const cha if(results == null) { LogError("Timer (WR module) error! Times' table migration (1) failed. Reason: %s", error); - - return; } } @@ -1974,8 +2018,6 @@ public void SQL_AlterTable2_Callback(Database db, DBResultSet results, const cha if(results == null) { LogError("Timer (WR module) error! Times' table migration (2) failed. Reason: %s", error); - - return; } } @@ -1994,8 +2036,6 @@ public void SQL_AlterTable3_Callback(Database db, DBResultSet results, const cha if(results == null) { LogError("Timer (WR module) error! Times' table migration (3) failed. Reason: %s", error); - - return; } } @@ -2006,11 +2046,7 @@ public void SQL_TableMigration4_Callback(Database db, DBResultSet results, const char[] sQuery = new char[256]; FormatEx(sQuery, 256, "ALTER TABLE `%splayertimes` ADD %s;", gS_MySQLPrefix, (gB_MySQL)? "(`track` INT NOT NULL DEFAULT 0)":"COLUMN `track` INT NOT NULL DEFAULT 0"); gH_SQL.Query(SQL_AlterTable4_Callback, sQuery); - - return; } - - OnMapStart(); } public void SQL_AlterTable4_Callback(Database db, DBResultSet results, const char[] error, any data) @@ -2018,6 +2054,26 @@ public void SQL_AlterTable4_Callback(Database db, DBResultSet results, const cha if(results == null) { LogError("Timer (WR module) error! Times' table migration (4) failed. Reason: %s", error); + } +} + +public void SQL_TableMigration5_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + if(results == null) + { + char[] sQuery = new char[256]; + + if(gB_MySQL) + { + FormatEx(sQuery, 256, "ALTER TABLE `%splayertimes` ADD (`perfs` FLOAT DEFAULT 0);", gS_MySQLPrefix); + } + + else + { + FormatEx(sQuery, 256, "ALTER TABLE `%splayertimes` ADD COLUMN `perfs` FLOAT DEFAULT 0;", gS_MySQLPrefix); + } + + gH_SQL.Query(SQL_AlterTable5_Callback, sQuery); return; } @@ -2025,7 +2081,19 @@ public void SQL_AlterTable4_Callback(Database db, DBResultSet results, const cha OnMapStart(); } -public void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track) +public void SQL_AlterTable5_Callback(Database db, DBResultSet results, const char[] error, any data) +{ + if(results == null) + { + LogError("Timer (WR module) error! Times' table migration (5) failed. Reason: %s", error); + + return; + } + + OnMapStart(); +} + +public void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs) { char[] sTime = new char[32]; FormatSeconds(time, sTime, 32); @@ -2067,6 +2135,8 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st Call_PushCell(sync); Call_PushCell(track); Call_PushCell(oldwr); + Call_PushCell(oldtime); + Call_PushCell(perfs); Call_Finish(); #if defined DEBUG @@ -2086,6 +2156,8 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st Call_PushCell(strafes); Call_PushCell(sync); Call_PushCell(track); + Call_PushCell(oldtime); + Call_PushCell(perfs); Call_Finish(); } @@ -2118,14 +2190,14 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st return; } - FormatEx(sQuery, 512, "INSERT INTO %splayertimes (auth, map, time, jumps, date, style, strafes, sync, points, track) VALUES ('%s', '%s', %.03f, %d, %d, %d, %d, %.2f, 0.0, %d);", gS_MySQLPrefix, sAuthID, gS_Map, time, jumps, GetTime(), style, strafes, sync, track); + FormatEx(sQuery, 512, "INSERT INTO %splayertimes (auth, map, time, jumps, date, style, strafes, sync, points, track, perfs) VALUES ('%s', '%s', %.03f, %d, %d, %d, %d, %.2f, 0.0, %d, %.2f);", gS_MySQLPrefix, sAuthID, gS_Map, time, jumps, GetTime(), style, strafes, sync, track, perfs); } else // update { Shavit_PrintToChatAll("%s[%s]%s %T", gS_ChatStrings[sMessageVariable], sTrack, gS_ChatStrings[sMessageText], "NotFirstCompletion", LANG_SERVER, gS_ChatStrings[sMessageVariable2], client, gS_ChatStrings[sMessageText], gS_ChatStrings[sMessageStyle], gS_StyleStrings[style][sStyleName], gS_ChatStrings[sMessageText], gS_ChatStrings[sMessageVariable2], sTime, gS_ChatStrings[sMessageText], gS_ChatStrings[sMessageVariable], iRank, gS_ChatStrings[sMessageText], jumps, strafes, sSync, gS_ChatStrings[sMessageText], gS_ChatStrings[sMessageWarning], sDifference); - FormatEx(sQuery, 512, "UPDATE %splayertimes SET time = %.03f, jumps = %d, date = %d, strafes = %d, sync = %.02f, points = 0.0 WHERE map = '%s' AND auth = '%s' AND style = %d AND track = %d;", gS_MySQLPrefix, time, jumps, GetTime(), strafes, sync, gS_Map, sAuthID, style, track); + FormatEx(sQuery, 512, "UPDATE %splayertimes SET time = %.03f, jumps = %d, date = %d, strafes = %d, sync = %.02f, points = 0.0, perfs = %.2f WHERE map = '%s' AND auth = '%s' AND style = %d AND track = %d;", gS_MySQLPrefix, time, jumps, GetTime(), strafes, sync, perfs, gS_Map, sAuthID, style, track); } gH_SQL.Query(SQL_OnFinish_Callback, sQuery, GetClientSerial(client), DBPrio_High); @@ -2140,6 +2212,8 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st Call_PushCell(iRank); Call_PushCell(overwrite); Call_PushCell(track); + Call_PushCell(oldtime); + Call_PushCell(perfs); Call_Finish(); gF_PlayerRecord[client][style][track] = time; diff --git a/addons/sourcemod/scripting/shavit-zones.sp b/addons/sourcemod/scripting/shavit-zones.sp index 143364b8..0046ac91 100644 --- a/addons/sourcemod/scripting/shavit-zones.sp +++ b/addons/sourcemod/scripting/shavit-zones.sp @@ -108,7 +108,8 @@ int gI_BeamSprite = -1; int gI_HaloSprite = -1; // admin menu -Handle gH_AdminMenu = INVALID_HANDLE; +TopMenu gH_AdminMenu = null; +TopMenuObject gH_TimerCommands = INVALID_TOPMENUOBJECT; // misc cache bool gB_Late = false; @@ -177,11 +178,6 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max public void OnAllPluginsLoaded() { - if(gB_Late) - { - OnAdminMenuReady(null); - } - if(gH_SQL == null) { Shavit_OnDatabaseLoaded(); @@ -252,6 +248,12 @@ public void OnPluginStart() AutoExecConfig(); + // admin menu + if(LibraryExists("adminmenu") && ((gH_AdminMenu = GetAdminTopMenu()) != null)) + { + OnAdminMenuReady(gH_AdminMenu); + } + // misc cvars sv_gravity = FindConVar("sv_gravity"); @@ -322,20 +324,14 @@ public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] n } } -public void OnAdminMenuReady(Handle topmenu) +public void OnAdminMenuCreated(Handle topmenu) { - if(LibraryExists("adminmenu") && ((gH_AdminMenu = GetAdminTopMenu()) != null)) + if(gH_AdminMenu == null || (topmenu == gH_AdminMenu && gH_TimerCommands != INVALID_TOPMENUOBJECT)) { - TopMenuObject tmoTimer = FindTopMenuCategory(gH_AdminMenu, "Timer Commands"); - - if(tmoTimer != INVALID_TOPMENUOBJECT) - { - AddToTopMenu(gH_AdminMenu, "sm_zones", TopMenuObject_Item, AdminMenu_Zones, tmoTimer, "sm_zones", ADMFLAG_RCON); - AddToTopMenu(gH_AdminMenu, "sm_deletezone", TopMenuObject_Item, AdminMenu_DeleteZone, tmoTimer, "sm_deletezone", ADMFLAG_RCON); - AddToTopMenu(gH_AdminMenu, "sm_deleteallzones", TopMenuObject_Item, AdminMenu_DeleteAllZones, tmoTimer, "sm_deleteallzones", ADMFLAG_RCON); - AddToTopMenu(gH_AdminMenu, "sm_zoneedit", TopMenuObject_Item, AdminMenu_ZoneEdit, tmoTimer, "sm_zoneedit", ADMFLAG_RCON); - } + return; } + + gH_TimerCommands = gH_AdminMenu.AddCategory("Timer Commands", CategoryHandler, "shavit_admin", ADMFLAG_RCON); } public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) @@ -351,7 +347,28 @@ public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject } } -public void AdminMenu_Zones(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +public void OnAdminMenuReady(Handle topmenu) +{ + if((gH_AdminMenu = GetAdminTopMenu()) != null) + { + if(gH_TimerCommands == INVALID_TOPMENUOBJECT) + { + gH_TimerCommands = gH_AdminMenu.FindCategory("Timer Commands"); + + if(gH_TimerCommands == INVALID_TOPMENUOBJECT) + { + OnAdminMenuCreated(topmenu); + } + } + + gH_AdminMenu.AddItem("sm_zones", AdminMenu_Zones, gH_TimerCommands, "sm_zones", ADMFLAG_RCON); + gH_AdminMenu.AddItem("sm_deletezone", AdminMenu_DeleteZone, gH_TimerCommands, "sm_deletezone", ADMFLAG_RCON); + gH_AdminMenu.AddItem("sm_deleteallzones", AdminMenu_DeleteAllZones, gH_TimerCommands, "sm_deleteallzones", ADMFLAG_RCON); + gH_AdminMenu.AddItem("sm_zoneedit", AdminMenu_ZoneEdit, gH_TimerCommands, "sm_zoneedit", ADMFLAG_RCON); + } +} + +public void AdminMenu_Zones(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayOption) { @@ -364,7 +381,7 @@ public void AdminMenu_Zones(Handle topmenu, TopMenuAction action, TopMenuObject } } -public void AdminMenu_DeleteZone(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +public void AdminMenu_DeleteZone(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayOption) { @@ -390,7 +407,7 @@ public void AdminMenu_DeleteAllZones(Handle topmenu, TopMenuAction action, TopM } } -public void AdminMenu_ZoneEdit(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) +public void AdminMenu_ZoneEdit(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { if(action == TopMenuAction_DisplayOption) { diff --git a/addons/sourcemod/translations/shavit-common.phrases.txt b/addons/sourcemod/translations/shavit-common.phrases.txt index d4126158..99849345 100644 --- a/addons/sourcemod/translations/shavit-common.phrases.txt +++ b/addons/sourcemod/translations/shavit-common.phrases.txt @@ -31,6 +31,10 @@ "#format" "{1:s}" "en" "Usage: {1}" } + "TimerCommands" + { + "en" "Timer Commands" + } // ---------- Timelimit ---------- // "Minutes" { diff --git a/addons/sourcemod/translations/shavit-hud.phrases.txt b/addons/sourcemod/translations/shavit-hud.phrases.txt index 95789df2..fcaeaa16 100644 --- a/addons/sourcemod/translations/shavit-hud.phrases.txt +++ b/addons/sourcemod/translations/shavit-hud.phrases.txt @@ -56,6 +56,10 @@ { "en" "Sync" } + "HudPerfs" + { + "en" "Perfect jumps" + } "HudPracticeMode" { "en" "[PRACTICE]" diff --git a/addons/sourcemod/translations/shavit-misc.phrases.txt b/addons/sourcemod/translations/shavit-misc.phrases.txt index 0e431da2..69064b7d 100644 --- a/addons/sourcemod/translations/shavit-misc.phrases.txt +++ b/addons/sourcemod/translations/shavit-misc.phrases.txt @@ -83,6 +83,11 @@ "#format" "{1:d},{2:s},{3:s}" "en" "Checkpoint {1} is {2}empty{3}." } + "MiscSegmentedCommand" + { + "#format" "{1:s},{2:s}" + "en" "Use {1}!cp{2} to re-open the segmented checkpoints menu." + } // ---------- Menus ---------- // "TeleportMenuTitle" { @@ -97,6 +102,10 @@ { "en" "Checkpoints" } + "MiscCheckpointMenuSegmented" + { + "en" "Segmented Checkpoints" + } "MiscCheckpointWarning" { "en" "WARNING: Teleporting will stop your timer!" diff --git a/addons/sourcemod/translations/shavit-replay.phrases.txt b/addons/sourcemod/translations/shavit-replay.phrases.txt index 8e3e8309..616c8bab 100644 --- a/addons/sourcemod/translations/shavit-replay.phrases.txt +++ b/addons/sourcemod/translations/shavit-replay.phrases.txt @@ -1,6 +1,10 @@ "Phrases" { // ---------- Menus ---------- // + "DeleteReplayAdminMenu" + { + "en" "Delete replay" + } "DeleteReplayMenuTitle" { "en" "Delete a replay:" diff --git a/addons/sourcemod/translations/shavit-zones.phrases.txt b/addons/sourcemod/translations/shavit-zones.phrases.txt index 59ee98ee..86034225 100644 --- a/addons/sourcemod/translations/shavit-zones.phrases.txt +++ b/addons/sourcemod/translations/shavit-zones.phrases.txt @@ -13,10 +13,6 @@ { "en" "Delete map zone" } - "TimerCommands" - { - "en" "Timer Commands" - } // ---------- Commands ---------- // "ModifierCommandNoArgs" {