From aef0d1c1268ab7d4bc18996c9c6b4da16a40aadc Mon Sep 17 00:00:00 2001 From: navewindre Date: Mon, 4 Dec 2023 18:06:10 +0100 Subject: bbbbbbbbwaaaaaaaaaaa --- sourcemod/scripting/gokz-saveloc.sp | 822 ++++++++++++++++++++++++++++++++++++ 1 file changed, 822 insertions(+) create mode 100644 sourcemod/scripting/gokz-saveloc.sp (limited to 'sourcemod/scripting/gokz-saveloc.sp') diff --git a/sourcemod/scripting/gokz-saveloc.sp b/sourcemod/scripting/gokz-saveloc.sp new file mode 100644 index 0000000..98d64c0 --- /dev/null +++ b/sourcemod/scripting/gokz-saveloc.sp @@ -0,0 +1,822 @@ +#include + +#include +#include + +#include +#include + +#undef REQUIRE_EXTENSIONS +#undef REQUIRE_PLUGIN +#include +#include +#pragma newdecls required +#pragma semicolon 1 + +public Plugin myinfo = +{ + name = "GOKZ SaveLoc", + author = "JWL", + description = "Allows players to save/load locations that preserve position, angles, and velocity", + version = GOKZ_VERSION, + url = GOKZ_SOURCE_URL +}; + +#define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-saveloc.txt" +#define LOADLOC_INVALIDATE_DURATION 0.12 +#define MAX_LOCATION_NAME_LENGTH 32 + +enum struct Location { + // Location name must be first for FindString to work. + char locationName[MAX_LOCATION_NAME_LENGTH]; + char locationCreator[MAX_NAME_LENGTH]; + + // GOKZ related states + int mode; + int course; + float currentTime; + ArrayList checkpointData; + int checkpointCount; + int teleportCount; + ArrayList undoTeleportData; + + // Movement related states + int groundEnt; + int flags; + float position[3]; + float angles[3]; + float velocity[3]; + float duckAmount; + bool ducking; + bool ducked; + float lastDuckTime; + float duckSpeed; + float stamina; + MoveType movetype; + float ladderNormal[3]; + int collisionGroup; + float waterJumpTime; + bool hasWalkMovedSinceLastJump; + float ignoreLadderJumpTimeOffset; + float lastPositionAtFullCrouchSpeed[2]; + + void Create(int client, int target) + { + GetClientName(client, this.locationCreator, sizeof(Location::locationCreator)); + this.groundEnt = GetEntPropEnt(target, Prop_Data, "m_hGroundEntity"); + this.flags = GetEntityFlags(target); + this.mode = GOKZ_GetCoreOption(target, Option_Mode); + this.course = GOKZ_GetCourse(target); + GetClientAbsOrigin(target, this.position); + GetClientEyeAngles(target, this.angles); + GetEntPropVector(target, Prop_Data, "m_vecVelocity", this.velocity); + this.duckAmount = GetEntPropFloat(target, Prop_Send, "m_flDuckAmount"); + this.ducking = !!GetEntProp(target, Prop_Send, "m_bDucking"); + this.ducked = !!GetEntProp(target, Prop_Send, "m_bDucked"); + this.lastDuckTime = GetEntPropFloat(target, Prop_Send, "m_flLastDuckTime"); + this.duckSpeed = Movement_GetDuckSpeed(target); + this.stamina = GetEntPropFloat(target, Prop_Send, "m_flStamina"); + this.movetype = Movement_GetMovetype(target); + GetEntPropVector(target, Prop_Send, "m_vecLadderNormal", this.ladderNormal); + this.collisionGroup = GetEntProp(target, Prop_Send, "m_CollisionGroup"); + this.waterJumpTime = GetEntPropFloat(target, Prop_Data, "m_flWaterJumpTime"); + this.hasWalkMovedSinceLastJump = !!GetEntProp(target, Prop_Data, "m_bHasWalkMovedSinceLastJump"); + this.ignoreLadderJumpTimeOffset = GetEntPropFloat(target, Prop_Data, "m_ignoreLadderJumpTime") - GetGameTime(); + GetLastPositionAtFullCrouchSpeed(target, this.lastPositionAtFullCrouchSpeed); + + if (GOKZ_GetTimerRunning(target)) + { + this.currentTime = GOKZ_GetTime(target); + } + else + { + this.currentTime = -1.0; + } + this.checkpointData = GOKZ_GetCheckpointData(target); + this.checkpointCount = GOKZ_GetCheckpointCount(target); + this.teleportCount = GOKZ_GetTeleportCount(target); + this.undoTeleportData = GOKZ_GetUndoTeleportData(target); + } + + bool Load(int client) + { + // Safeguard Check + if (GOKZ_GetCoreOption(client, Option_Safeguard) > Safeguard_Disabled && GOKZ_GetTimerRunning(client) && GOKZ_GetValidTimer(client)) + { + GOKZ_PrintToChat(client, true, "%t", "Safeguard - Blocked"); + GOKZ_PlayErrorSound(client); + return false; + } + if (!GOKZ_SetMode(client, this.mode)) + { + GOKZ_PrintToChat(client, true, "%t", "LoadLoc - Mode Not Available"); + } + GOKZ_SetCourse(client, this.course); + if (this.currentTime >= 0.0) + { + GOKZ_SetTime(client, this.currentTime); + } + GOKZ_SetCheckpointData(client, this.checkpointData, GOKZ_CHECKPOINT_VERSION); + GOKZ_SetCheckpointCount(client, this.checkpointCount); + GOKZ_SetTeleportCount(client, this.teleportCount); + GOKZ_SetUndoTeleportData(client, this.undoTeleportData, GOKZ_CHECKPOINT_VERSION); + + SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", this.groundEnt); + SetEntityFlags(client, this.flags); + TeleportEntity(client, this.position, this.angles, this.velocity); + SetEntPropFloat(client, Prop_Send, "m_flDuckAmount", this.duckAmount); + SetEntProp(client, Prop_Send, "m_bDucking", this.ducking); + SetEntProp(client, Prop_Send, "m_bDucked", this.ducked); + SetEntPropFloat(client, Prop_Send, "m_flLastDuckTime", this.lastDuckTime); + Movement_SetDuckSpeed(client, this.duckSpeed); + SetEntPropFloat(client, Prop_Send, "m_flStamina", this.stamina); + Movement_SetMovetype(client, this.movetype); + SetEntPropVector(client, Prop_Send, "m_vecLadderNormal", this.ladderNormal); + SetEntProp(client, Prop_Send, "m_CollisionGroup", this.collisionGroup); + SetEntPropFloat(client, Prop_Data, "m_flWaterJumpTime", this.waterJumpTime); + SetEntProp(client, Prop_Data, "m_bHasWalkMovedSinceLastJump", this.hasWalkMovedSinceLastJump); + SetEntPropFloat(client, Prop_Data, "m_ignoreLadderJumpTime", this.ignoreLadderJumpTimeOffset + GetGameTime()); + SetLastPositionAtFullCrouchSpeed(client, this.lastPositionAtFullCrouchSpeed); + + GOKZ_InvalidateRun(client); + return true; + } +} + +ArrayList gA_Locations; +bool gB_LocMenuOpen[MAXPLAYERS + 1]; +bool gB_UsedLoc[MAXPLAYERS + 1]; +int gI_MostRecentLocation[MAXPLAYERS + 1]; +float gF_LastLoadlocTime[MAXPLAYERS + 1]; + +bool gB_GOKZHUD; + +// =====[ PLUGIN EVENTS ]===== + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + RegPluginLibrary("gokz-saveloc"); + return APLRes_Success; +} + +public void OnPluginStart() +{ + LoadTranslations("gokz-common.phrases"); + LoadTranslations("gokz-saveloc.phrases"); + + HookEvents(); + RegisterCommands(); + CreateArrays(); +} + +public void OnAllPluginsLoaded() +{ + if (LibraryExists("updater")) + { + Updater_AddPlugin(UPDATER_URL); + } + gB_GOKZHUD = LibraryExists("gokz-hud"); +} + +public void OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, "updater")) + { + Updater_AddPlugin(UPDATER_URL); + } + gB_GOKZHUD = gB_GOKZHUD || StrEqual(name, "gokz-hud"); +} + +public void OnLibraryRemoved(const char[] name) +{ + gB_GOKZHUD = gB_GOKZHUD && !StrEqual(name, "gokz-hud"); +} + +public void OnMapStart() +{ + ClearLocations(); +} + + + +// =====[ CLIENT EVENTS ]===== + +public void OnClientPutInServer(int client) +{ + gF_LastLoadlocTime[client] = 0.0; +} + +public void OnPlayerDeath(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + + CloseLocMenu(client); +} + +public void OnPlayerJoinTeam(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + int team = event.GetInt("team"); + + if (team == CS_TEAM_SPECTATOR) + { + CloseLocMenu(client); + } +} + +public Action GOKZ_OnTimerStart(int client, int course) +{ + CloseLocMenu(client); + gB_UsedLoc[client] = false; + if (GetGameTime() < gF_LastLoadlocTime[client] + LOADLOC_INVALIDATE_DURATION) + { + return Plugin_Stop; + } + return Plugin_Continue; +} + +public Action GOKZ_OnTimerEnd(int client, int course, float time) +{ + if (gB_UsedLoc[client]) + { + PrintEndTimeString_SaveLoc(client, course, time); + } + return Plugin_Continue; +} + +// =====[ GENERAL ]===== + +void HookEvents() +{ + HookEvent("player_death", OnPlayerDeath); + HookEvent("player_team", OnPlayerJoinTeam); +} + + + +// =====[ COMMANDS ]===== + +void RegisterCommands() +{ + RegConsoleCmd("sm_saveloc", Command_SaveLoc, "[KZ] Save location. Usage: !saveloc "); + RegConsoleCmd("sm_loadloc", Command_LoadLoc, "[KZ] Load location. Usage: !loadloc <#id OR name>"); + RegConsoleCmd("sm_prevloc", Command_PrevLoc, "[KZ] Go back to the previous location."); + RegConsoleCmd("sm_nextloc", Command_NextLoc, "[KZ] Go forward to the next location."); + RegConsoleCmd("sm_locmenu", Command_LocMenu, "[KZ] Open location menu."); + RegConsoleCmd("sm_nameloc", Command_NameLoc, "[KZ] Name location. Usage: !nameloc <#id> "); +} + +public Action Command_SaveLoc(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + int target = -1; + if (!IsPlayerAlive(client)) + { + KZPlayer player = KZPlayer(client); + target = player.ObserverTarget; + if (target == -1) + { + GOKZ_PrintToChat(client, true, "%t", "Must Be Alive"); + GOKZ_PlayErrorSound(client); + return Plugin_Handled; + } + } + + if (args == 0) + { + // save location with empty + SaveLocation(client, "", target); + } + else if (args == 1) + { + // get location + char arg[MAX_LOCATION_NAME_LENGTH]; + GetCmdArg(1, arg, sizeof(arg)); + + if (IsValidLocationName(arg)) + { + // save location with + SaveLocation(client, arg, target); + } + else + { + GOKZ_PrintToChat(client, true, "%t", "NameLoc - Naming Format"); + } + } + else + { + GOKZ_PrintToChat(client, true, "%t", "SaveLoc - Usage"); + } + + return Plugin_Handled; +} + +public Action Command_LoadLoc(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + else if (!IsPlayerAlive(client)) + { + GOKZ_PrintToChat(client, true, "%t", "Must Be Alive"); + return Plugin_Handled; + } + else if (gA_Locations.Length == 0) + { + GOKZ_PrintToChat(client, true, "%t", "No Locations Found"); + return Plugin_Handled; + } + + if (args == 0) + { + // load most recent location + int id = gI_MostRecentLocation[client]; + LoadLocation(client, id); + } + else if (args == 1) + { + // get location <#id OR name> + char arg[MAX_LOCATION_NAME_LENGTH]; + GetCmdArg(1, arg, sizeof(arg)); + int id; + + if (arg[0] == '#') + { + // load location <#id> + id = StringToInt(arg[1]); + } + else + { + // load location + id = gA_Locations.FindString(arg); + } + + if (IsValidLocationId(id)) + { + if (LoadLocation(client, id)) + { + gF_LastLoadlocTime[client] = GetGameTime(); + } + } + else + { + GOKZ_PrintToChat(client, true, "%t", "Location Not Found"); + } + } + else + { + GOKZ_PrintToChat(client, true, "%t", "LoadLoc - Usage"); + } + + return Plugin_Handled; +} + +public Action Command_PrevLoc(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + else if (gA_Locations.Length == 0) + { + GOKZ_PrintToChat(client, true, "%t", "No Locations Found"); + return Plugin_Handled; + } + else if (gI_MostRecentLocation[client] <= 0) + { + GOKZ_PrintToChat(client, true, "%t", "PrevLoc - Can't Prev Location (No Location Found)"); + return Plugin_Handled; + } + LoadLocation(client, gI_MostRecentLocation[client] - 1); + return Plugin_Handled; +} + +public Action Command_NextLoc(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + else if (gA_Locations.Length == 0) + { + GOKZ_PrintToChat(client, true, "%t", "No Locations Found"); + return Plugin_Handled; + } + else if (gI_MostRecentLocation[client] >= gA_Locations.Length - 1) + { + GOKZ_PrintToChat(client, true, "%t", "NextLoc - Can't Next Location (No Location Found)"); + return Plugin_Handled; + } + LoadLocation(client, gI_MostRecentLocation[client] + 1); + return Plugin_Handled; +} + +public Action Command_NameLoc(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + else if (gA_Locations.Length == 0) + { + GOKZ_PrintToChat(client, true, "%t", "No Locations Found"); + return Plugin_Handled; + } + + if (args == 0) + { + GOKZ_PrintToChat(client, true, "%t", "NameLoc - Usage"); + } + else if (args == 1) + { + // name most recent location + char arg[MAX_LOCATION_NAME_LENGTH]; + GetCmdArg(1, arg, sizeof(arg)); + int id = gI_MostRecentLocation[client]; + + if (IsValidLocationName(arg) && IsClientLocationCreator(client, id)) + { + NameLocation(client, id, arg); + } + else if (!IsClientLocationCreator(client, id)) + { + GOKZ_PrintToChat(client, true, "%t", "NameLoc - Not Creator"); + } + else + { + GOKZ_PrintToChat(client, true, "%t", "NameLoc - Naming Format"); + } + } + else if (args == 2) + { + // name specified location + char arg1[MAX_LOCATION_NAME_LENGTH]; + char arg2[MAX_LOCATION_NAME_LENGTH]; + GetCmdArg(1, arg1, sizeof(arg1)); + GetCmdArg(2, arg2, sizeof(arg2)); + int id = StringToInt(arg1[1]); + + if (IsValidLocationId(id)) + { + + if (IsValidLocationName(arg2) && IsClientLocationCreator(client, id)) + { + NameLocation(client, id, arg2); + } + else if (!IsClientLocationCreator(client, id)) + { + GOKZ_PrintToChat(client, true, "%t", "NameLoc - Not Creator"); + } + else + { + GOKZ_PrintToChat(client, true, "%t", "NameLoc - Naming Format"); + } + } + else + { + GOKZ_PrintToChat(client, true, "%t", "Location Not Found"); + } + } + else + { + GOKZ_PrintToChat(client, true, "%t", "NameLoc - Usage"); + } + + return Plugin_Handled; +} + +public Action Command_LocMenu(int client, int args) +{ + if (!IsValidClient(client)) + { + return Plugin_Handled; + } + else if (!IsPlayerAlive(client)) + { + GOKZ_PrintToChat(client, true, "%t", "Must Be Alive"); + return Plugin_Handled; + } + else if (gA_Locations.Length == 0) + { + GOKZ_PrintToChat(client, true, "%t", "No Locations Found"); + return Plugin_Handled; + } + + ShowLocMenu(client); + + return Plugin_Handled; +} + +// ====[ SAVELOC MENU ]==== + +void ShowLocMenu(int client) +{ + Menu locMenu = new Menu(LocMenuHandler, MENU_ACTIONS_ALL); + locMenu.SetTitle("%t", "LocMenu - Title"); + + // fill the menu with all locations + for (int i = 0; i < gA_Locations.Length; i++) + { + char item[MAX_LOCATION_NAME_LENGTH]; + Format(item, sizeof(item), "%i", i); + locMenu.AddItem(item, item); + } + + // calculate which page of the menu contains client's most recent location + int firstItem; + if (gI_MostRecentLocation[client] > 5) + { + firstItem = gI_MostRecentLocation[client] - (gI_MostRecentLocation[client] % 6); + } + + locMenu.DisplayAt(client, firstItem, MENU_TIME_FOREVER); +} + + + +// ====[ SAVELOC MENU HANDLER ]==== + +public int LocMenuHandler(Menu menu, MenuAction action, int client, int choice) +{ + switch (action) + { + case MenuAction_Display: + { + gB_LocMenuOpen[client] = true; + } + + case MenuAction_DisplayItem: + { + Location loc; + char item[MAX_LOCATION_NAME_LENGTH]; + menu.GetItem(choice, item, sizeof(item)); + + int id = StringToInt(item); + gA_Locations.GetArray(id, loc); + char name[MAX_LOCATION_NAME_LENGTH]; + strcopy(name, sizeof(name), loc.locationName); + + if (id == gI_MostRecentLocation[client]) + { + Format(item, sizeof(item), "> #%i %s", id, name); + } + else + { + Format(item, sizeof(item), "#%i %s", id, name); + } + + return RedrawMenuItem(item); + } + + case MenuAction_Select: + { + char item[MAX_LOCATION_NAME_LENGTH]; + menu.GetItem(choice, item, sizeof(item)); + ReplaceString(item, sizeof(item), "#", ""); + int id = StringToInt(item); + + LoadLocation(client, id); + } + + case MenuAction_Cancel: + { + gB_LocMenuOpen[client] = false; + } + + case MenuAction_End: + { + delete menu; + } + } + + return 0; +} + + + +// ====[ SAVE LOCATION ]==== + +void SaveLocation(int client, char[] name, int target) +{ + Location loc; + if (target == -1) + { + target = client; + } + loc.Create(client, target); + strcopy(loc.locationName, sizeof(Location::locationName), name); + GetClientName(client, loc.locationCreator, sizeof(loc.locationCreator)); + gA_Locations.PushArray(loc); + gI_MostRecentLocation[client] = gA_Locations.Length - 1; + + GOKZ_PrintToChat(client, true, "%t", "SaveLoc - ID Name", gA_Locations.Length - 1, name); + + for (int i = 1; i <= MaxClients; i++) + { + RefreshLocMenu(i); + } +} + + + +// ====[ LOAD LOCATION ]==== + +bool LoadLocation(int client, int id) +{ + if (!IsPlayerAlive(client)) + { + GOKZ_PrintToChat(client, true, "%t", "Must Be Alive"); + return false; + } + char clientName[MAX_NAME_LENGTH]; + + GetClientName(client, clientName, sizeof(clientName)); + Location loc; + gA_Locations.GetArray(id, loc); + if (loc.Load(client)) + { + gB_UsedLoc[client] = true; + if (gB_GOKZHUD) + { + GOKZ_HUD_ForceUpdateTPMenu(client); + } + } + else + { + return false; + } + // print message if loading new location + if (gI_MostRecentLocation[client] != id) + { + gI_MostRecentLocation[client] = id; + + if (StrEqual(clientName, loc.locationCreator)) + { + GOKZ_PrintToChat(client, true, "%t", "LoadLoc - ID Name", id, loc.locationName); + } + else + { + if (StrEqual(loc.locationName, "")) + { + GOKZ_PrintToChat(client, true, "%t", "LoadLoc - ID Creator", id, loc.locationCreator); + } + else + { + GOKZ_PrintToChat(client, true, "%t", "LoadLoc - ID Name Creator", id, loc.locationName, loc.locationCreator); + } + } + } + + RefreshLocMenu(client); + + return true; +} + + + +// ====[ NAME LOCATION ]==== + +void NameLocation(int client, int id, char[] name) +{ + Location loc; + gA_Locations.GetArray(id, loc); + strcopy(loc.locationName, sizeof(Location::locationName), name); + + GOKZ_PrintToChat(client, true, "%t", "NameLoc - ID Name", id, name); + + for (int i = 1; i <= MaxClients; i++) + { + RefreshLocMenu(i); + } +} + + + +// =====[ HELPER FUNCTIONS ]===== + +void CreateArrays() +{ + gA_Locations = new ArrayList(sizeof(Location)); +} + +void ClearLocations() +{ + Location loc; + for (int i = 0; i < gA_Locations.Length; i++) + { + // Prevent memory leak + gA_Locations.GetArray(i, loc); + delete loc.checkpointData; + } + gA_Locations.Clear(); + for (int i = 1; i <= MaxClients; i++) + { + gI_MostRecentLocation[i] = -1; + gB_LocMenuOpen[i] = false; + } +} + +void RefreshLocMenu(int client) +{ + if (gB_LocMenuOpen[client]) + { + ShowLocMenu(client); + } +} + +void CloseLocMenu(int client) +{ + if (gB_LocMenuOpen[client]) + { + CancelClientMenu(client, true); + gB_LocMenuOpen[client] = false; + } +} + +bool IsValidLocationId(int id) +{ + return !(id < 0) && !(id > gA_Locations.Length - 1); +} + +bool IsValidLocationName(char[] name) +{ + // check if location name starts with letter and is unique + return IsCharAlpha(name[0]) && gA_Locations.FindString(name) == -1; +} + +bool IsClientLocationCreator(int client, int id) +{ + char clientName[MAX_NAME_LENGTH]; + Location loc; + gA_Locations.GetArray(id, loc); + GetClientName(client, clientName, sizeof(clientName)); + + return StrEqual(clientName, loc.locationCreator); +} + +void GetLastPositionAtFullCrouchSpeed(int client, float origin[2]) +{ + // m_vecLastPositionAtFullCrouchSpeed is right after m_flDuckSpeed. + int baseOffset = FindSendPropInfo("CBasePlayer", "m_flDuckSpeed"); + origin[0] = GetEntDataFloat(client, baseOffset + 4); + origin[1] = GetEntDataFloat(client, baseOffset + 8); +} + +void SetLastPositionAtFullCrouchSpeed(int client, float origin[2]) +{ + int baseOffset = FindSendPropInfo("CBasePlayer", "m_flDuckSpeed"); + SetEntDataFloat(client, baseOffset + 4, origin[0]); + SetEntDataFloat(client, baseOffset + 8, origin[1]); +} + +// ====[ PRIVATE ]==== + +static void PrintEndTimeString_SaveLoc(int client, int course, float time) +{ + if (course == 0) + { + switch (GOKZ_GetTimeType(client)) + { + case TimeType_Nub: + { + GOKZ_PrintToChat(client, true, "%t", "Beat Map (NUB)", + client, + GOKZ_FormatTime(time), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + case TimeType_Pro: + { + GOKZ_PrintToChat(client, true, "%t", "Beat Map (PRO)", + client, + GOKZ_FormatTime(time), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + } + } + else + { + switch (GOKZ_GetTimeType(client)) + { + case TimeType_Nub: + { + GOKZ_PrintToChat(client, true, "%t", "Beat Bonus (NUB)", + client, + GOKZ_GetCourse(client), + GOKZ_FormatTime(time), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + case TimeType_Pro: + { + GOKZ_PrintToChat(client, true, "%t", "Beat Bonus (PRO)", + client, + GOKZ_GetCourse(client), + GOKZ_FormatTime(time), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + } + } +} -- cgit v1.2.3