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/scripting/shavit-misc.sp b/addons/sourcemod/scripting/shavit-misc.sp index 4120a10e..effaa2bf 100644 --- a/addons/sourcemod/scripting/shavit-misc.sp +++ b/addons/sourcemod/scripting/shavit-misc.sp @@ -50,19 +50,22 @@ enum CheckpointsCache iCPFlags, any:aCPSnapshot[TIMERSNAPSHOT_SIZE], String:sCPTargetname[32], + ArrayList:aCPFrames, + bool:bCPSegmented, 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 25 // game specific EngineVersion gEV_Type = Engine_Unknown; @@ -84,7 +87,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 { @@ -425,7 +428,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 +443,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 +462,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) @@ -503,6 +514,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 +563,14 @@ public void OnMapStart() } } +public void OnMapEnd() +{ + for(int i = 1; i <= MaxClients; i++) + { + ResetCheckpoints(i); + } +} + bool LoadAdvertisementsConfig() { gA_Advertisements.Clear(); @@ -727,7 +751,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 +912,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 +943,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 +964,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 +1009,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; } @@ -1015,6 +1039,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 +1131,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])); } } @@ -1501,7 +1533,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 +1543,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)); @@ -1556,30 +1599,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 +1644,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 +1691,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; @@ -1726,6 +1781,56 @@ bool SaveCheckpoint(int client, int index) } CopyArray(snapshot, cpcache[aCPSnapshot], TIMERSNAPSHOT_SIZE); + + if(CanSegment(client)) + { + if(gB_Replay) + { + cpcache[aCPFrames] = Shavit_GetReplayData(client); + } + + cpcache[bCPSegmented] = true; + } + + else + { + cpcache[aCPFrames] = null; + cpcache[bCPSegmented] = false; + } + + if(overflow) + { + int iSerial = GetClientSerial(client); + int iMaxCPs = GetMaxCPs(client); + + char[] sKey = new char[32]; + + 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 +1838,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 +1874,10 @@ void TeleportToCheckpoint(int client, int index, bool suppressMessage) Shavit_StopTimer(client); } - Shavit_SetPracticeMode(client, true, !bInStart); + if(!cpcache[bCPSegmented]) + { + Shavit_SetPracticeMode(client, true, !bInStart); + } any snapshot[TIMERSNAPSHOT_SIZE]; CopyArray(cpcache[aCPSnapshot], snapshot, TIMERSNAPSHOT_SIZE); @@ -1815,6 +1923,16 @@ 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) + { + Shavit_SetReplayData(client, cpcache[aCPFrames]); + + if((gI_CheckpointsSettings[client] & CP_ANGLES) > 0) + { + Shavit_HijackAngles(client, ang[0], ang[1]); + } + } if(!suppressMessage) { @@ -1977,7 +2095,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 +2617,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 +2669,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/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!"