summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/gokz-hud
diff options
context:
space:
mode:
authornavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
committernavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
commitaef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch)
tree43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/gokz-hud
parent38f1140c11724da05a23a10385061200b907cf6e (diff)
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/gokz-hud')
-rw-r--r--sourcemod/scripting/gokz-hud/commands.sp116
-rw-r--r--sourcemod/scripting/gokz-hud/hide_weapon.sp30
-rw-r--r--sourcemod/scripting/gokz-hud/info_panel.sp307
-rw-r--r--sourcemod/scripting/gokz-hud/menu.sp96
-rw-r--r--sourcemod/scripting/gokz-hud/natives.sp33
-rw-r--r--sourcemod/scripting/gokz-hud/options.sp190
-rw-r--r--sourcemod/scripting/gokz-hud/options_menu.sp181
-rw-r--r--sourcemod/scripting/gokz-hud/racing_text.sp167
-rw-r--r--sourcemod/scripting/gokz-hud/spectate_text.sp119
-rw-r--r--sourcemod/scripting/gokz-hud/speed_text.sp141
-rw-r--r--sourcemod/scripting/gokz-hud/timer_text.sp135
-rw-r--r--sourcemod/scripting/gokz-hud/tp_menu.sp415
12 files changed, 1930 insertions, 0 deletions
diff --git a/sourcemod/scripting/gokz-hud/commands.sp b/sourcemod/scripting/gokz-hud/commands.sp
new file mode 100644
index 0000000..fc5ed4e
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/commands.sp
@@ -0,0 +1,116 @@
+void RegisterCommands()
+{
+ RegConsoleCmd("sm_menu", CommandMenu, "[KZ] Toggle the simple teleport menu.");
+ RegConsoleCmd("sm_cpmenu", CommandMenu, "[KZ] Toggle the simple teleport menu.");
+ RegConsoleCmd("sm_adv", CommandToggleAdvancedMenu, "[KZ] Toggle the advanced teleport menu.");
+ RegConsoleCmd("sm_panel", CommandToggleInfoPanel, "[KZ] Toggle visibility of the centre information panel.");
+ RegConsoleCmd("sm_timerstyle", CommandToggleTimerStyle, "[KZ] Toggle the style of the timer text.");
+ RegConsoleCmd("sm_timertype", CommandToggleTimerType, "[KZ] Toggle visibility of your time type.");
+ RegConsoleCmd("sm_speed", CommandToggleSpeed, "[KZ] Toggle visibility of your speed and jump pre-speed.");
+ RegConsoleCmd("sm_hideweapon", CommandToggleShowWeapon, "[KZ] Toggle visibility of your weapon.");
+}
+
+public Action CommandMenu(int client, int args)
+{
+ if (GOKZ_HUD_GetOption(client, HUDOption_TPMenu) != TPMenu_Disabled)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TPMenu, TPMenu_Disabled);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TPMenu, TPMenu_Simple);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandToggleAdvancedMenu(int client, int args)
+{
+ if (GOKZ_HUD_GetOption(client, HUDOption_TPMenu) != TPMenu_Advanced)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TPMenu, TPMenu_Advanced);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TPMenu, TPMenu_Simple);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandToggleInfoPanel(int client, int args)
+{
+ if (GOKZ_HUD_GetOption(client, HUDOption_InfoPanel) == InfoPanel_Disabled)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_InfoPanel, InfoPanel_Enabled);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_InfoPanel, InfoPanel_Disabled);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandToggleTimerStyle(int client, int args)
+{
+ if (GOKZ_HUD_GetOption(client, HUDOption_TimerStyle) == TimerStyle_Standard)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TimerStyle, TimerStyle_Precise);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TimerStyle, TimerStyle_Standard);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandToggleTimerType(int client, int args)
+{
+ if (GOKZ_HUD_GetOption(client, HUDOption_TimerType) == TimerType_Disabled)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TimerType, TimerType_Enabled);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_TimerType, TimerType_Disabled);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandToggleSpeed(int client, int args)
+{
+ int speedText = GOKZ_HUD_GetOption(client, HUDOption_SpeedText);
+ int infoPanel = GOKZ_HUD_GetOption(client, HUDOption_InfoPanel);
+
+ if (speedText == SpeedText_Disabled)
+ {
+ if (infoPanel == InfoPanel_Enabled)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_SpeedText, SpeedText_InfoPanel);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_SpeedText, SpeedText_Bottom);
+ }
+ }
+ else if (infoPanel == InfoPanel_Disabled && speedText == SpeedText_InfoPanel)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_SpeedText, SpeedText_Bottom);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_SpeedText, SpeedText_Disabled);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandToggleShowWeapon(int client, int args)
+{
+ if (GOKZ_HUD_GetOption(client, HUDOption_ShowWeapon) == ShowWeapon_Disabled)
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_ShowWeapon, ShowWeapon_Enabled);
+ }
+ else
+ {
+ GOKZ_HUD_SetOption(client, HUDOption_ShowWeapon, ShowWeapon_Disabled);
+ }
+ return Plugin_Handled;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/hide_weapon.sp b/sourcemod/scripting/gokz-hud/hide_weapon.sp
new file mode 100644
index 0000000..b290d8f
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/hide_weapon.sp
@@ -0,0 +1,30 @@
+/*
+ Hides weapon view model.
+*/
+
+
+
+// =====[ EVENTS ]=====
+
+void OnPlayerSpawn_HideWeapon(int client)
+{
+ UpdateHideWeapon(client);
+}
+
+void OnOptionChanged_HideWeapon(int client, HUDOption option)
+{
+ if (option == HUDOption_ShowWeapon)
+ {
+ UpdateHideWeapon(client);
+ }
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void UpdateHideWeapon(int client)
+{
+ SetEntProp(client, Prop_Send, "m_bDrawViewmodel",
+ GOKZ_HUD_GetOption(client, HUDOption_ShowWeapon) == ShowWeapon_Enabled);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/info_panel.sp b/sourcemod/scripting/gokz-hud/info_panel.sp
new file mode 100644
index 0000000..3563404
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/info_panel.sp
@@ -0,0 +1,307 @@
+/*
+ Displays information using hint text.
+
+ This is manually refreshed whenever player has taken off so that they see
+ their pre-speed as soon as possible, improving responsiveness.
+*/
+
+
+
+static bool infoPanelDuckPressedLast[MAXPLAYERS + 1];
+static bool infoPanelOnGroundLast[MAXPLAYERS + 1];
+static bool infoPanelShowDuckString[MAXPLAYERS + 1];
+
+
+// =====[ PUBLIC ]=====
+
+bool IsDrawingInfoPanel(int client)
+{
+ KZPlayer player = KZPlayer(client);
+ return player.InfoPanel != InfoPanel_Disabled
+ && !NothingEnabledInInfoPanel(player);
+}
+
+
+
+// =====[ EVENTS ]=====
+
+void OnPlayerRunCmdPost_InfoPanel(int client, int cmdnum, HUDInfo info)
+{
+ int updateSpeed = gB_FastUpdateRate[client] ? 1 : 10;
+ if (cmdnum % updateSpeed == 0 || info.IsTakeoff)
+ {
+ UpdateInfoPanel(client, info);
+ }
+ infoPanelOnGroundLast[info.ID] = info.OnGround;
+ infoPanelDuckPressedLast[info.ID] = info.Ducking;
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void UpdateInfoPanel(int client, HUDInfo info)
+{
+ KZPlayer player = KZPlayer(client);
+
+ if (player.Fake || !IsDrawingInfoPanel(player.ID))
+ {
+ return;
+ }
+ char infoPanelText[512];
+ FormatEx(infoPanelText, sizeof(infoPanelText), GetInfoPanel(player, info));
+ if (infoPanelText[0] != '\0')
+ {
+ PrintCSGOHUDText(player.ID, infoPanelText);
+ }
+}
+
+static bool NothingEnabledInInfoPanel(KZPlayer player)
+{
+ bool noTimerText = player.TimerText != TimerText_InfoPanel;
+ bool noSpeedText = player.SpeedText != SpeedText_InfoPanel || player.Paused;
+ bool noKeys = player.ShowKeys == ShowKeys_Disabled
+ || player.ShowKeys == ShowKeys_Spectating && player.Alive;
+ return noTimerText && noSpeedText && noKeys;
+}
+
+static char[] GetInfoPanel(KZPlayer player, HUDInfo info)
+{
+ char infoPanelText[512];
+ FormatEx(infoPanelText, sizeof(infoPanelText),
+ "%s%s%s%s",
+ GetSpectatorString(player, info),
+ GetTimeString(player, info),
+ GetSpeedString(player, info),
+ GetKeysString(player, info));
+ if (infoPanelText[0] == '\0')
+ {
+ return infoPanelText;
+ }
+ else
+ {
+ Format(infoPanelText, sizeof(infoPanelText), "<font color='#ffffff08'>%s", infoPanelText);
+ }
+ TrimString(infoPanelText);
+ return infoPanelText;
+}
+
+static char[] GetSpectatorString(KZPlayer player, HUDInfo info)
+{
+ char spectatorString[255];
+ if (player.SpecListPosition != SpecListPosition_InfoPanel || player.ShowSpectators == ShowSpecs_Disabled)
+ {
+ return spectatorString;
+ }
+ // Only return something if the player is alive or observing someone else
+ if (player.Alive || player.ObserverTarget != -1)
+ {
+ FormatEx(spectatorString, sizeof(spectatorString), "%s", FormatSpectatorTextForInfoPanel(player, KZPlayer(info.ID)));
+ }
+ return spectatorString;
+}
+
+static char[] GetTimeString(KZPlayer player, HUDInfo info)
+{
+ char timeString[128];
+ if (player.TimerText != TimerText_InfoPanel)
+ {
+ timeString = "";
+ }
+ else if (info.TimerRunning)
+ {
+ if (player.GetHUDOption(HUDOption_TimerType) == TimerType_Enabled)
+ {
+ switch (info.TimeType)
+ {
+ case TimeType_Nub:
+ {
+ FormatEx(timeString, sizeof(timeString),
+ "%T: <font color='#ead18a'>%s</font> %s\n",
+ "Info Panel Text - Time", player.ID,
+ GOKZ_HUD_FormatTime(player.ID, info.Time),
+ GetPausedString(player, info));
+ }
+ case TimeType_Pro:
+ {
+ FormatEx(timeString, sizeof(timeString),
+ "%T: <font color='#b5d4ee'>%s</font> %s\n",
+ "Info Panel Text - Time", player.ID,
+ GOKZ_HUD_FormatTime(player.ID, info.Time),
+ GetPausedString(player, info));
+ }
+ }
+ }
+ else
+ {
+ FormatEx(timeString, sizeof(timeString),
+ "%T: <font color='#ffffff'>%s</font> %s\n",
+ "Info Panel Text - Time", player.ID,
+ GOKZ_HUD_FormatTime(player.ID, info.Time),
+ GetPausedString(player, info));
+ }
+ }
+ else
+ {
+ FormatEx(timeString, sizeof(timeString),
+ "%T: <font color='#ea4141'>%T</font> %s\n",
+ "Info Panel Text - Time", player.ID,
+ "Info Panel Text - Stopped", player.ID,
+ GetPausedString(player, info));
+ }
+ return timeString;
+}
+
+static char[] GetPausedString(KZPlayer player, HUDInfo info)
+{
+ char pausedString[64];
+ if (info.Paused)
+ {
+ FormatEx(pausedString, sizeof(pausedString),
+ "(<font color='#ffffff'>%T</font>)",
+ "Info Panel Text - PAUSED", player.ID);
+ }
+ else
+ {
+ pausedString = "";
+ }
+ return pausedString;
+}
+
+static char[] GetSpeedString(KZPlayer player, HUDInfo info)
+{
+ char speedString[128];
+ if (player.SpeedText != SpeedText_InfoPanel || info.Paused)
+ {
+ speedString = "";
+ }
+ else
+ {
+ if (info.OnGround || info.OnLadder || info.Noclipping)
+ {
+ FormatEx(speedString, sizeof(speedString),
+ "%T: <font color='#ffffff'>%.0f</font> u/s\n",
+ "Info Panel Text - Speed", player.ID,
+ RoundToPowerOfTen(info.Speed, -2));
+ infoPanelShowDuckString[info.ID] = false;
+ }
+ else
+ {
+ if (GOKZ_HUD_GetOption(player.ID, HUDOption_DeadstrafeColor) == DeadstrafeColor_Enabled
+ && Movement_GetVerticalVelocity(info.ID) > 0.0 && Movement_GetVerticalVelocity(info.ID) < 140.0)
+ {
+ FormatEx(speedString, sizeof(speedString),
+ "%T: <font color='#ff2020'>%.0f</font> %s\n",
+ "Info Panel Text - Speed", player.ID,
+ RoundToPowerOfTen(info.Speed, -2),
+ GetTakeoffString(info));
+ }
+ else
+ {
+ FormatEx(speedString, sizeof(speedString),
+ "%T: <font color='#ffffff'>%.0f</font> %s\n",
+ "Info Panel Text - Speed", player.ID,
+ RoundToPowerOfTen(info.Speed, -2),
+ GetTakeoffString(info));
+ }
+ }
+ }
+ return speedString;
+}
+
+static char[] GetTakeoffString(HUDInfo info)
+{
+ char takeoffString[96], duckString[32];
+
+ if (infoPanelShowDuckString[info.ID]
+ || (infoPanelOnGroundLast[info.ID]
+ && !info.HitBhop
+ && info.IsTakeoff
+ && info.Jumped
+ && info.Ducking
+ && (infoPanelDuckPressedLast[info.ID] || GOKZ_GetCoreOption(info.ID, Option_Mode) == Mode_Vanilla)))
+ {
+ duckString = " <font color='#71eeb8'>C</font>";
+ infoPanelShowDuckString[info.ID] = true;
+ }
+ else
+ {
+ duckString = "";
+ infoPanelShowDuckString[info.ID] = false;
+ }
+
+ if (info.HitJB)
+ {
+ FormatEx(takeoffString, sizeof(takeoffString),
+ "(<font color='#ffff20'>%.0f</font>)%s",
+ RoundToPowerOfTen(info.TakeoffSpeed, -2),
+ duckString);
+ }
+ else if (info.HitPerf)
+ {
+ FormatEx(takeoffString, sizeof(takeoffString),
+ "(<font color='#40ff40'>%.0f</font>)%s",
+ RoundToPowerOfTen(info.TakeoffSpeed, -2),
+ duckString);
+ }
+ else
+ {
+ FormatEx(takeoffString, sizeof(takeoffString),
+ "(<font color='#ffffff'>%.0f</font>)%s",
+ RoundToPowerOfTen(info.TakeoffSpeed, -2),
+ duckString);
+ }
+ return takeoffString;
+}
+
+static char[] GetKeysString(KZPlayer player, HUDInfo info)
+{
+ char keysString[64];
+ if (player.ShowKeys == ShowKeys_Disabled)
+ {
+ keysString = "";
+ }
+ else if (player.ShowKeys == ShowKeys_Spectating && player.Alive)
+ {
+ keysString = "";
+ }
+ else
+ {
+ int buttons = info.Buttons;
+ FormatEx(keysString, sizeof(keysString),
+ "%T: <font color='#ffffff'>%c %c %c %c %c %c</font>\n",
+ "Info Panel Text - Keys", player.ID,
+ buttons & IN_MOVELEFT ? 'A' : '_',
+ buttons & IN_FORWARD ? 'W' : '_',
+ buttons & IN_BACK ? 'S' : '_',
+ buttons & IN_MOVERIGHT ? 'D' : '_',
+ buttons & IN_DUCK ? 'C' : '_',
+ buttons & IN_JUMP ? 'J' : '_');
+ }
+ return keysString;
+}
+
+// Credits to Franc1sco (https://github.com/Franc1sco/FixHintColorMessages)
+void PrintCSGOHUDText(int client, const char[] format)
+{
+ char buff[HUD_MAX_HINT_SIZE];
+ Format(buff, sizeof(buff), "</font>%s", format);
+
+ for (int i = strlen(buff); i < sizeof(buff) - 1; i++)
+ {
+ buff[i] = ' ';
+ }
+
+ buff[sizeof(buff) - 1] = '\0';
+
+ Protobuf pb = view_as<Protobuf>(StartMessageOne("TextMsg", client, USERMSG_BLOCKHOOKS));
+ pb.SetInt("msg_dst", 4);
+ pb.AddString("params", "#SFUI_ContractKillStart");
+ pb.AddString("params", buff);
+ pb.AddString("params", NULL_STRING);
+ pb.AddString("params", NULL_STRING);
+ pb.AddString("params", NULL_STRING);
+ pb.AddString("params", NULL_STRING);
+
+ EndMessage();
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/menu.sp b/sourcemod/scripting/gokz-hud/menu.sp
new file mode 100644
index 0000000..4b7259e
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/menu.sp
@@ -0,0 +1,96 @@
+/*
+ Tracks whether a GOKZ HUD menu or panel element is being shown to the client.
+*/
+
+
+
+// Update the TP menu i.e. item text, item disabled/enabled
+void CancelGOKZHUDMenu(int client)
+{
+ // Only cancel the menu if we know it's the TP menu
+ if (gB_MenuShowing[client])
+ {
+ CancelClientMenu(client);
+ }
+}
+
+
+
+// =====[ EVENTS ]=====
+
+void OnPlayerSpawn_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnOptionChanged_Menu(int client, HUDOption option)
+{
+ if (option == HUDOption_TPMenu || option == HUDOption_TimerText)
+ {
+ CancelGOKZHUDMenu(client);
+ }
+}
+
+void OnTimerStart_Menu(int client)
+{
+ // Prevent the menu from getting cancelled every tick if player use start timer button zone.
+ if (GOKZ_GetTime(client) > 0.0)
+ {
+ CancelGOKZHUDMenu(client);
+ }
+}
+
+void OnTimerEnd_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnTimerStopped_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnPause_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnResume_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnMakeCheckpoint_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnCountedTeleport_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnJoinTeam_Menu(int client)
+{
+ CancelGOKZHUDMenu(client);
+}
+
+void OnStartPositionSet_Menu(int client)
+{
+ // Prevent the menu from getting cancelled every tick if player use start timer button zone.
+ if (GOKZ_GetTime(client) > 0.0)
+ {
+ CancelGOKZHUDMenu(client);
+ }
+}
+
+void OnPluginEnd_Menu()
+{
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (IsValidClient(client))
+ {
+ CancelGOKZHUDMenu(client);
+ }
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/natives.sp b/sourcemod/scripting/gokz-hud/natives.sp
new file mode 100644
index 0000000..bb877e2
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/natives.sp
@@ -0,0 +1,33 @@
+void CreateNatives()
+{
+ CreateNative("GOKZ_HUD_ForceUpdateTPMenu", Native_ForceUpdateTPMenu);
+ CreateNative("GOKZ_HUD_GetMenuShowing", Native_GetMenuShowing);
+ CreateNative("GOKZ_HUD_SetMenuShowing", Native_SetMenuShowing);
+ CreateNative("GOKZ_HUD_GetMenuSpectatorText", Native_GetSpectatorText);
+}
+
+public int Native_ForceUpdateTPMenu(Handle plugin, int numParams)
+{
+ SetForceUpdateTPMenu(GetNativeCell(1));
+ return 0;
+}
+
+public int Native_GetMenuShowing(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_MenuShowing[GetNativeCell(1)]);
+}
+
+public int Native_SetMenuShowing(Handle plugin, int numParams)
+{
+ gB_MenuShowing[GetNativeCell(1)] = view_as<bool>(GetNativeCell(2));
+ return 0;
+}
+
+public int Native_GetSpectatorText(Handle plugin, int numParams)
+{
+ HUDInfo info;
+ GetNativeArray(2, info, sizeof(HUDInfo));
+ KZPlayer player = KZPlayer(GetNativeCell(1));
+ FormatNativeString(3, 0, 0, GetNativeCell(4), _, "", FormatSpectatorTextForMenu(player, info));
+ return 0;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/options.sp b/sourcemod/scripting/gokz-hud/options.sp
new file mode 100644
index 0000000..84bbf2b
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/options.sp
@@ -0,0 +1,190 @@
+/*
+ Options for controlling appearance and behaviour of HUD and UI.
+*/
+
+
+
+// =====[ EVENTS ]=====
+
+void OnOptionsMenuReady_Options()
+{
+ RegisterOptions();
+}
+
+void OnOptionChanged_Options(int client, HUDOption option, any newValue)
+{
+ PrintOptionChangeMessage(client, option, newValue);
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void RegisterOptions()
+{
+ for (HUDOption option; option < HUDOPTION_COUNT; option++)
+ {
+ GOKZ_RegisterOption(gC_HUDOptionNames[option], gC_HUDOptionDescriptions[option],
+ OptionType_Int, gI_HUDOptionDefaults[option], 0, gI_HUDOptionCounts[option] - 1);
+ }
+}
+
+static void PrintOptionChangeMessage(int client, HUDOption option, any newValue)
+{
+ // NOTE: Not all options have a message for when they are changed.
+ switch (option)
+ {
+ case HUDOption_TPMenu:
+ {
+ switch (newValue)
+ {
+ case TPMenu_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Teleport Menu - Disable");
+ }
+ case TPMenu_Simple:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Teleport Menu - Enable (Simple)");
+ }
+ case TPMenu_Advanced:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Teleport Menu - Enable (Advanced)");
+ }
+ }
+ }
+ case HUDOption_InfoPanel:
+ {
+ switch (newValue)
+ {
+ case InfoPanel_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Info Panel - Disable");
+ }
+ case InfoPanel_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Info Panel - Enable");
+ }
+ }
+ }
+ case HUDOption_TimerStyle:
+ {
+ switch (newValue)
+ {
+ case TimerStyle_Standard:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Timer Style - Standard");
+ }
+ case TimerStyle_Precise:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Timer Style - Precise");
+ }
+ }
+ }
+ case HUDOption_TimerType:
+ {
+ switch (newValue)
+ {
+ case TimerType_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Timer Type - Disabled");
+ }
+ case TimerType_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Timer Type - Enabled");
+ }
+ }
+ }
+ case HUDOption_ShowWeapon:
+ {
+ switch (newValue)
+ {
+ case ShowWeapon_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Weapon - Disable");
+ }
+ case ShowWeapon_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Weapon - Enable");
+ }
+ }
+ }
+ case HUDOption_ShowControls:
+ {
+ switch (newValue)
+ {
+ case ReplayControls_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Controls - Disable");
+ }
+ case ReplayControls_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Controls - Enable");
+ }
+ }
+ }
+ case HUDOption_DeadstrafeColor:
+ {
+ switch (newValue)
+ {
+ case DeadstrafeColor_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Dead Strafe - Disable");
+ }
+ case DeadstrafeColor_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Dead Strafe - Enable");
+ }
+ }
+ }
+ case HUDOption_ShowSpectators:
+ {
+ switch (newValue)
+ {
+ case ShowSpecs_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Spectators - Disable");
+ }
+ case ShowSpecs_Number:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Spectators - Number");
+ }
+ case ShowSpecs_Full:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Show Spectators - Full");
+ }
+ }
+ }
+ case HUDOption_SpecListPosition:
+ {
+ switch (newValue)
+ {
+ case SpecListPosition_InfoPanel:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Spectator List Position - Info Panel");
+ }
+ case SpecListPosition_TPMenu:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Spectator List Position - TP Menu");
+ }
+ }
+ }
+ case HUDOption_DynamicMenu:
+ {
+ switch (newValue)
+ {
+ case DynamicMenu_Legacy:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Dynamic Menu - Legacy");
+ }
+ case DynamicMenu_Disabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Dynamic Menu - Disable");
+ }
+ case DynamicMenu_Enabled:
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Option - Dynamic Menu - Enable");
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/options_menu.sp b/sourcemod/scripting/gokz-hud/options_menu.sp
new file mode 100644
index 0000000..705df27
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/options_menu.sp
@@ -0,0 +1,181 @@
+static TopMenu optionsTopMenu;
+static TopMenuObject catHUD;
+static TopMenuObject itemsHUD[HUDOPTION_COUNT];
+
+
+
+// =====[ EVENTS ]=====
+
+void OnOptionsMenuCreated_OptionsMenu(TopMenu topMenu)
+{
+ if (optionsTopMenu == topMenu && catHUD != INVALID_TOPMENUOBJECT)
+ {
+ return;
+ }
+
+ catHUD = topMenu.AddCategory(HUD_OPTION_CATEGORY, TopMenuHandler_Categories);
+}
+
+void OnOptionsMenuReady_OptionsMenu(TopMenu topMenu)
+{
+ // Make sure category exists
+ if (catHUD == INVALID_TOPMENUOBJECT)
+ {
+ GOKZ_OnOptionsMenuCreated(topMenu);
+ }
+
+ if (optionsTopMenu == topMenu)
+ {
+ return;
+ }
+
+ optionsTopMenu = topMenu;
+
+ // Add HUD option items
+ for (int option = 0; option < view_as<int>(HUDOPTION_COUNT); option++)
+ {
+ itemsHUD[option] = optionsTopMenu.AddItem(gC_HUDOptionNames[option], TopMenuHandler_HUD, catHUD);
+ }
+}
+
+public void TopMenuHandler_Categories(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength)
+{
+ if (action == TopMenuAction_DisplayOption || action == TopMenuAction_DisplayTitle)
+ {
+ if (topobj_id == catHUD)
+ {
+ Format(buffer, maxlength, "%T", "Options Menu - HUD", param);
+ }
+ }
+}
+
+public void TopMenuHandler_HUD(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength)
+{
+ HUDOption option = HUDOPTION_INVALID;
+ for (int i = 0; i < view_as<int>(HUDOPTION_COUNT); i++)
+ {
+ if (topobj_id == itemsHUD[i])
+ {
+ option = view_as<HUDOption>(i);
+ break;
+ }
+ }
+
+ if (option == HUDOPTION_INVALID)
+ {
+ return;
+ }
+
+ if (action == TopMenuAction_DisplayOption)
+ {
+ switch (option)
+ {
+ case HUDOption_TPMenu:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_TPMenuPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_ShowKeys:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_ShowKeysPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_TimerText:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_TimerTextPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_TimerStyle:
+ {
+ int optionValue = GOKZ_HUD_GetOption(param, option);
+ if (optionValue == TimerStyle_Precise)
+ {
+ FormatEx(buffer, maxlength, "%T - 01:23.45",
+ gC_HUDOptionPhrases[option], param);
+ }
+ else
+ {
+ FormatEx(buffer, maxlength, "%T - 1:23",
+ gC_HUDOptionPhrases[option], param);
+ }
+ }
+ case HUDOption_TimerType:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_TimerTypePhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_SpeedText:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_SpeedTextPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_ShowControls:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_ShowControlsPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_DeadstrafeColor:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_DeadstrafeColorPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_UpdateRate:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_HUDUpdateRatePhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_ShowSpectators:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_ShowSpecsPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_SpecListPosition:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_SpecListPositionPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ case HUDOption_DynamicMenu:
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], param,
+ gC_DynamicMenuPhrases[GOKZ_HUD_GetOption(param, option)], param);
+ }
+ default:FormatToggleableOptionDisplay(param, option, buffer, maxlength);
+ }
+ }
+ else if (action == TopMenuAction_SelectOption)
+ {
+ GOKZ_HUD_CycleOption(param, option);
+ optionsTopMenu.Display(param, TopMenuPosition_LastCategory);
+ }
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void FormatToggleableOptionDisplay(int client, HUDOption option, char[] buffer, int maxlength)
+{
+ if (GOKZ_HUD_GetOption(client, option) == 0)
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], client,
+ "Options Menu - Disabled", client);
+ }
+ else
+ {
+ FormatEx(buffer, maxlength, "%T - %T",
+ gC_HUDOptionPhrases[option], client,
+ "Options Menu - Enabled", client);
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/racing_text.sp b/sourcemod/scripting/gokz-hud/racing_text.sp
new file mode 100644
index 0000000..ac6ea3d
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/racing_text.sp
@@ -0,0 +1,167 @@
+/*
+ Uses HUD text to show the race countdown and a start message.
+
+ This is manually refreshed when a race starts to show the start message as
+ soon as possible, improving responsiveness.
+*/
+
+
+
+static Handle racingHudSynchronizer;
+static float countdownStartTime[MAXPLAYERS + 1];
+
+
+
+// =====[ EVENTS ]=====
+
+void OnPluginStart_RacingText()
+{
+ racingHudSynchronizer = CreateHudSynchronizer();
+}
+
+void OnPlayerRunCmdPost_RacingText(int client, int cmdnum)
+{
+ int updateSpeed = gB_FastUpdateRate[client] ? 3 : 6;
+ if (gB_GOKZRacing && cmdnum % updateSpeed == 2)
+ {
+ UpdateRacingText(client);
+ }
+}
+
+void OnRaceInfoChanged_RacingText(int raceID, RaceInfo prop, int newValue)
+{
+ if (prop != RaceInfo_Status)
+ {
+ return;
+ }
+
+ if (newValue == RaceStatus_Countdown)
+ {
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (GOKZ_RC_GetRaceID(client) == raceID)
+ {
+ countdownStartTime[client] = GetGameTime();
+ }
+ }
+ }
+ else if (newValue == RaceStatus_Aborting)
+ {
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (GOKZ_RC_GetRaceID(client) == raceID)
+ {
+ ClearRacingText(client);
+ }
+ }
+ }
+ else if (newValue == RaceStatus_Started)
+ {
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (GOKZ_RC_GetRaceID(client) == raceID)
+ {
+ UpdateRacingText(client);
+ }
+ }
+ }
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void UpdateRacingText(int client)
+{
+ KZPlayer player = KZPlayer(client);
+
+ if (player.Fake)
+ {
+ return;
+ }
+
+ if (player.Alive)
+ {
+ ShowRacingText(player, player);
+ }
+ else
+ {
+ KZPlayer targetPlayer = KZPlayer(player.ObserverTarget);
+ if (targetPlayer.ID != -1 && !targetPlayer.Fake)
+ {
+ ShowRacingText(player, targetPlayer);
+ }
+ }
+}
+
+static void ClearRacingText(int client)
+{
+ ClearSyncHud(client, racingHudSynchronizer);
+}
+
+static void ShowRacingText(KZPlayer player, KZPlayer targetPlayer)
+{
+ if (GOKZ_RC_GetStatus(targetPlayer.ID) != RacerStatus_Racing)
+ {
+ return;
+ }
+
+ int raceStatus = GOKZ_RC_GetRaceInfo(GOKZ_RC_GetRaceID(targetPlayer.ID), RaceInfo_Status);
+ if (raceStatus == RaceStatus_Countdown)
+ {
+ ShowCountdownText(player, targetPlayer);
+ }
+ else if (raceStatus == RaceStatus_Started)
+ {
+ ShowStartedText(player, targetPlayer);
+ }
+}
+
+static void ShowCountdownText(KZPlayer player, KZPlayer targetPlayer)
+{
+ float timeToStart = (countdownStartTime[targetPlayer.ID] + RC_COUNTDOWN_TIME) - GetGameTime();
+ int colour[4];
+ GetCountdownColour(timeToStart, colour);
+
+ SetHudTextParams(-1.0, 0.3, GetTextHoldTime(gB_FastUpdateRate[player.ID] ? 3 : 6), colour[0], colour[1], colour[2], colour[3], 0, 1.0, 0.0, 0.0);
+ ShowSyncHudText(player.ID, racingHudSynchronizer, "%t\n\n%d", "Get Ready", IntMax(RoundToCeil(timeToStart), 1));
+}
+
+static void GetCountdownColour(float timeToStart, int buffer[4])
+{
+ // From red to green
+ if (timeToStart >= RC_COUNTDOWN_TIME)
+ {
+ buffer[0] = 255;
+ buffer[1] = 0;
+ }
+ else if (timeToStart > RC_COUNTDOWN_TIME / 2.0)
+ {
+ buffer[0] = 255;
+ buffer[1] = RoundFloat(-510.0 / RC_COUNTDOWN_TIME * timeToStart + 510.0);
+ }
+ else if (timeToStart > 0.0)
+ {
+ buffer[0] = RoundFloat(510.0 / RC_COUNTDOWN_TIME * timeToStart);
+ buffer[1] = 255;
+ }
+ else
+ {
+ buffer[0] = 0;
+ buffer[1] = 255;
+ }
+
+ buffer[2] = 0;
+ buffer[3] = 255;
+}
+
+static void ShowStartedText(KZPlayer player, KZPlayer targetPlayer)
+{
+ if (targetPlayer.TimerRunning)
+ {
+ return;
+ }
+
+ SetHudTextParams(-1.0, 0.3, GetTextHoldTime(gB_FastUpdateRate[player.ID] ? 3 : 6), 0, 255, 0, 255, 0, 1.0, 0.0, 0.0);
+ ShowSyncHudText(player.ID, racingHudSynchronizer, "%t", "Go!");
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/spectate_text.sp b/sourcemod/scripting/gokz-hud/spectate_text.sp
new file mode 100644
index 0000000..9700c38
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/spectate_text.sp
@@ -0,0 +1,119 @@
+/*
+ Responsible for spectator list on the HUD.
+*/
+
+#define SPECATATOR_LIST_MAX_COUNT 5
+
+// =====[ PUBLIC ]=====
+
+char[] FormatSpectatorTextForMenu(KZPlayer player, HUDInfo info)
+{
+ int specCount;
+ char spectatorTextString[224];
+ if (player.GetHUDOption(HUDOption_ShowSpectators) >= ShowSpecs_Number)
+ {
+ for (int i = 1; i <= MaxClients; i++)
+ {
+ if (gI_ObserverTarget[i] == info.ID)
+ {
+ specCount++;
+ if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full)
+ {
+ char buffer[64];
+ if (specCount < SPECATATOR_LIST_MAX_COUNT)
+ {
+ GetClientName(i, buffer, sizeof(buffer));
+ Format(spectatorTextString, sizeof(spectatorTextString), "%s\n%s", spectatorTextString, buffer);
+ }
+ else if (specCount == SPECATATOR_LIST_MAX_COUNT)
+ {
+ StrCat(spectatorTextString, sizeof(spectatorTextString), "\n...");
+ }
+ }
+ }
+ }
+ if (specCount > 0)
+ {
+ if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full)
+ {
+ Format(spectatorTextString, sizeof(spectatorTextString), "%t\n ", "Spectator List - Menu (Full)", specCount, spectatorTextString);
+ }
+ else
+ {
+ Format(spectatorTextString, sizeof(spectatorTextString), "%t\n ", "Spectator List - Menu (Number)", specCount);
+ }
+ }
+ else
+ {
+ FormatEx(spectatorTextString, sizeof(spectatorTextString), "");
+ }
+ }
+ return spectatorTextString;
+}
+
+char[] FormatSpectatorTextForInfoPanel(KZPlayer player, KZPlayer targetPlayer)
+{
+ int specCount;
+ char spectatorTextString[160];
+ if (player.GetHUDOption(HUDOption_ShowSpectators) >= ShowSpecs_Number)
+ {
+ for (int i = 1; i <= MaxClients; i++)
+ {
+ if (gI_ObserverTarget[i] == targetPlayer.ID)
+ {
+ specCount++;
+ if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full)
+ {
+ char buffer[64];
+ if (specCount < SPECATATOR_LIST_MAX_COUNT)
+ {
+ GetClientName(i, buffer, sizeof(buffer));
+ if (specCount == 1)
+ {
+ Format(spectatorTextString, sizeof(spectatorTextString), "%s", buffer);
+ }
+ else
+ {
+ Format(spectatorTextString, sizeof(spectatorTextString), "%s, %s", spectatorTextString, buffer);
+ }
+ }
+ else if (specCount == SPECATATOR_LIST_MAX_COUNT)
+ {
+ Format(spectatorTextString, sizeof(spectatorTextString), " ...");
+ }
+ }
+ }
+ }
+ if (specCount > 0)
+ {
+ if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full)
+ {
+ Format(spectatorTextString, sizeof(spectatorTextString), "%t\n", "Spectator List - Info Panel (Full)", specCount, spectatorTextString);
+ }
+ else
+ {
+ Format(spectatorTextString, sizeof(spectatorTextString), "%t\n", "Spectator List - Info Panel (Number)", specCount);
+ }
+ }
+ else
+ {
+ FormatEx(spectatorTextString, sizeof(spectatorTextString), "");
+ }
+ }
+ return spectatorTextString;
+}
+
+void UpdateSpecList()
+{
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (IsValidClient(client) && !IsFakeClient(client))
+ {
+ gI_ObserverTarget[client] = GetObserverTarget(client);
+ }
+ else
+ {
+ gI_ObserverTarget[client] = -1;
+ }
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/speed_text.sp b/sourcemod/scripting/gokz-hud/speed_text.sp
new file mode 100644
index 0000000..3f83949
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/speed_text.sp
@@ -0,0 +1,141 @@
+/*
+ Uses HUD text to show current speed somewhere on the screen.
+
+ This is manually refreshed whenever player has taken off so that they see
+ their pre-speed as soon as possible, improving responsiveness.
+*/
+
+
+
+static Handle speedHudSynchronizer;
+
+static bool speedTextDuckPressedLast[MAXPLAYERS + 1];
+static bool speedTextOnGroundLast[MAXPLAYERS + 1];
+static bool speedTextShowDuckString[MAXPLAYERS + 1];
+
+
+
+// =====[ EVENTS ]=====
+
+void OnPluginStart_SpeedText()
+{
+ speedHudSynchronizer = CreateHudSynchronizer();
+}
+
+void OnPlayerRunCmdPost_SpeedText(int client, int cmdnum, HUDInfo info)
+{
+ int updateSpeed = gB_FastUpdateRate[client] ? 3 : 6;
+ if (cmdnum % updateSpeed == 0 || info.IsTakeoff)
+ {
+ UpdateSpeedText(client, info);
+ }
+ speedTextOnGroundLast[info.ID] = info.OnGround;
+ speedTextDuckPressedLast[info.ID] = info.Ducking;
+}
+
+void OnOptionChanged_SpeedText(int client, HUDOption option)
+{
+ if (option == HUDOption_SpeedText)
+ {
+ ClearSpeedText(client);
+ }
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void UpdateSpeedText(int client, HUDInfo info)
+{
+ KZPlayer player = KZPlayer(client);
+
+ if (player.Fake
+ || player.SpeedText != SpeedText_Bottom)
+ {
+ return;
+ }
+
+ ShowSpeedText(player, info);
+}
+
+static void ClearSpeedText(int client)
+{
+ ClearSyncHud(client, speedHudSynchronizer);
+}
+
+static void ShowSpeedText(KZPlayer player, HUDInfo info)
+{
+ if (info.Paused)
+ {
+ return;
+ }
+
+ int colour[4] = { 255, 255, 255, 0 }; // RGBA
+ float velZ = Movement_GetVerticalVelocity(info.ID);
+ if (!info.OnGround && !info.OnLadder && !info.Noclipping)
+ {
+ if (GOKZ_HUD_GetOption(player.ID, HUDOption_DeadstrafeColor) == DeadstrafeColor_Enabled && velZ > 0.0 && velZ < 140.0)
+ {
+ colour = { 255, 32, 32, 0 };
+ }
+ else if (info.HitPerf)
+ {
+ if (info.HitJB)
+ {
+ colour = { 255, 255, 32, 0 };
+ }
+ else
+ {
+ colour = { 64, 255, 64, 0 };
+ }
+ }
+ }
+
+ switch (player.SpeedText)
+ {
+ case SpeedText_Bottom:
+ {
+ // Set params based on the available screen space at max scaling HUD
+ if (!IsDrawingInfoPanel(player.ID))
+ {
+ SetHudTextParams(-1.0, 0.75, GetTextHoldTime(gB_FastUpdateRate[player.ID] ? 3 : 6), colour[0], colour[1], colour[2], colour[3], 0, 1.0, 0.0, 0.0);
+ }
+ else
+ {
+ SetHudTextParams(-1.0, 0.65, GetTextHoldTime(gB_FastUpdateRate[player.ID] ? 3 : 6), colour[0], colour[1], colour[2], colour[3], 0, 1.0, 0.0, 0.0);
+ }
+ }
+ }
+
+ if (info.OnGround || info.OnLadder || info.Noclipping)
+ {
+ ShowSyncHudText(player.ID, speedHudSynchronizer,
+ "%.0f",
+ RoundFloat(info.Speed * 10) / 10.0);
+ speedTextShowDuckString[info.ID] = false;
+ }
+ else
+ {
+ if (speedTextShowDuckString[info.ID]
+ || (speedTextOnGroundLast[info.ID]
+ && !info.HitBhop
+ && info.IsTakeoff
+ && info.Jumped
+ && info.Ducking
+ && (speedTextDuckPressedLast[info.ID] || GOKZ_GetCoreOption(info.ID, Option_Mode) == Mode_Vanilla)))
+ {
+ ShowSyncHudText(player.ID, speedHudSynchronizer,
+ "%.0f\n (%.0f)C",
+ RoundToPowerOfTen(info.Speed, -2),
+ RoundToPowerOfTen(info.TakeoffSpeed, -2));
+ speedTextShowDuckString[info.ID] = true;
+ }
+ else {
+ ShowSyncHudText(player.ID, speedHudSynchronizer,
+ "%.0f\n(%.0f)",
+ RoundToPowerOfTen(info.Speed, -2),
+ RoundToPowerOfTen(info.TakeoffSpeed, -2));
+ speedTextShowDuckString[info.ID] = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/timer_text.sp b/sourcemod/scripting/gokz-hud/timer_text.sp
new file mode 100644
index 0000000..a5fd17b
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/timer_text.sp
@@ -0,0 +1,135 @@
+/*
+ Uses HUD text to show current run time somewhere on the screen.
+
+ This is manually refreshed whenever the players' timer is started, ended or
+ stopped to improve responsiveness.
+*/
+
+
+
+static Handle timerHudSynchronizer;
+
+
+
+// =====[ PUBLIC ]=====
+
+char[] FormatTimerTextForMenu(KZPlayer player, HUDInfo info)
+{
+ char timerTextString[32];
+ if (info.TimerRunning)
+ {
+ if (player.GetHUDOption(HUDOption_TimerType) == TimerType_Enabled)
+ {
+ FormatEx(timerTextString, sizeof(timerTextString),
+ "%s %s",
+ gC_TimeTypeNames[info.TimeType],
+ GOKZ_HUD_FormatTime(player.ID, info.Time));
+ }
+ else
+ {
+ FormatEx(timerTextString, sizeof(timerTextString),
+ "%s",
+ GOKZ_HUD_FormatTime(player.ID, info.Time));
+ }
+ if (info.Paused)
+ {
+ Format(timerTextString, sizeof(timerTextString), "%s (%T)", timerTextString, "Info Panel Text - PAUSED", player.ID);
+ }
+ }
+ return timerTextString;
+}
+
+
+
+// =====[ EVENTS ]=====
+
+void OnPluginStart_TimerText()
+{
+ timerHudSynchronizer = CreateHudSynchronizer();
+}
+
+void OnPlayerRunCmdPost_TimerText(int client, int cmdnum, HUDInfo info)
+{
+ int updateSpeed = gB_FastUpdateRate[client] ? 3 : 6;
+ if (cmdnum % updateSpeed == 1)
+ {
+ UpdateTimerText(client, info);
+ }
+}
+
+void OnOptionChanged_TimerText(int client, HUDOption option)
+{
+ if (option == HUDOption_TimerText)
+ {
+ ClearTimerText(client);
+ }
+}
+
+void OnTimerEnd_TimerText(int client)
+{
+ ClearTimerText(client);
+}
+
+void OnTimerStopped_TimerText(int client)
+{
+ ClearTimerText(client);
+}
+
+
+// =====[ PRIVATE ]=====
+
+static void UpdateTimerText(int client, HUDInfo info)
+{
+ KZPlayer player = KZPlayer(client);
+
+ if (player.Fake)
+ {
+ return;
+ }
+
+ ShowTimerText(player, info);
+}
+
+static void ClearTimerText(int client)
+{
+ ClearSyncHud(client, timerHudSynchronizer);
+}
+
+static void ShowTimerText(KZPlayer player, HUDInfo info)
+{
+ if (!info.TimerRunning)
+ {
+ if (player.ID != info.ID)
+ {
+ CancelGOKZHUDMenu(player.ID);
+ }
+ return;
+ }
+ if (player.TimerText == TimerText_Top || player.TimerText == TimerText_Bottom)
+ {
+ int colour[4]; // RGBA
+ if (player.GetHUDOption(HUDOption_TimerType) == TimerType_Enabled)
+ {
+ switch (info.TimeType)
+ {
+ case TimeType_Nub:colour = { 234, 209, 138, 0 };
+ case TimeType_Pro:colour = { 181, 212, 238, 0 };
+ }
+ }
+ else colour = { 255, 255, 255, 0};
+
+ switch (player.TimerText)
+ {
+ case TimerText_Top:
+ {
+ SetHudTextParams(-1.0, 0.07, GetTextHoldTime(gB_FastUpdateRate[player.ID] ? 3 : 6), colour[0], colour[1], colour[2], colour[3], 0, 1.0, 0.0, 0.0);
+ }
+ case TimerText_Bottom:
+ {
+ SetHudTextParams(-1.0, 0.9, GetTextHoldTime(gB_FastUpdateRate[player.ID] ? 3 : 6), colour[0], colour[1], colour[2], colour[3], 0, 1.0, 0.0, 0.0);
+ }
+ }
+
+ ShowSyncHudText(player.ID, timerHudSynchronizer, GOKZ_HUD_FormatTime(player.ID, info.Time));
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-hud/tp_menu.sp b/sourcemod/scripting/gokz-hud/tp_menu.sp
new file mode 100644
index 0000000..bb78f1e
--- /dev/null
+++ b/sourcemod/scripting/gokz-hud/tp_menu.sp
@@ -0,0 +1,415 @@
+/*
+ Lets players easily use teleport functionality.
+
+ This menu is displayed whenever the player is alive and there is
+ currently no other menu displaying.
+*/
+
+
+
+#define ITEM_INFO_CHECKPOINT "cp"
+#define ITEM_INFO_TELEPORT "tp"
+#define ITEM_INFO_PREV "prev"
+#define ITEM_INFO_NEXT "next"
+#define ITEM_INFO_UNDO "undo"
+#define ITEM_INFO_PAUSE "pause"
+#define ITEM_INFO_START "start"
+
+static bool oldCanMakeCP[MAXPLAYERS + 1];
+static bool oldCanTP[MAXPLAYERS + 1];
+static bool oldCanPrevCP[MAXPLAYERS + 1];
+static bool oldCanNextCP[MAXPLAYERS + 1];
+static bool oldCanUndoTP[MAXPLAYERS + 1];
+static bool oldCanPause[MAXPLAYERS + 1];
+static bool oldCanResume[MAXPLAYERS + 1];
+static bool forceRefresh[MAXPLAYERS + 1];
+
+// =====[ EVENTS ]=====
+
+void OnPlayerRunCmdPost_TPMenu(int client, int cmdnum, HUDInfo info)
+{
+ int updateSpeed = gB_FastUpdateRate[client] ? 3 : 6;
+ if (cmdnum % updateSpeed == 2)
+ {
+ UpdateTPMenu(client, info);
+ }
+}
+
+public int PanelHandler_Menu(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Cancel)
+ {
+ gB_MenuShowing[param1] = false;
+ }
+ return 0;
+}
+
+public int MenuHandler_TPMenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ char info[16];
+ menu.GetItem(param2, info, sizeof(info));
+
+ if (StrEqual(info, ITEM_INFO_CHECKPOINT, false))
+ {
+ GOKZ_MakeCheckpoint(param1);
+ }
+ else if (StrEqual(info, ITEM_INFO_TELEPORT, false))
+ {
+ GOKZ_TeleportToCheckpoint(param1);
+ }
+ else if (StrEqual(info, ITEM_INFO_PREV, false))
+ {
+ GOKZ_PrevCheckpoint(param1);
+ }
+ else if (StrEqual(info, ITEM_INFO_NEXT, false))
+ {
+ GOKZ_NextCheckpoint(param1);
+ }
+ else if (StrEqual(info, ITEM_INFO_UNDO, false))
+ {
+ GOKZ_UndoTeleport(param1);
+ }
+ else if (StrEqual(info, ITEM_INFO_PAUSE, false))
+ {
+ GOKZ_TogglePause(param1);
+ }
+ else if (StrEqual(info, ITEM_INFO_START, false))
+ {
+ GOKZ_TeleportToStart(param1);
+ }
+
+ // Menu closes when player selects something, so...
+ gB_MenuShowing[param1] = false;
+ }
+ else if (action == MenuAction_Cancel)
+ {
+ gB_MenuShowing[param1] = false;
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+// =====[ PUBLIC ]=====
+void SetForceUpdateTPMenu(int client)
+{
+ forceRefresh[client] = true;
+}
+
+// =====[ PRIVATE ]=====
+
+static void UpdateTPMenu(int client, HUDInfo info)
+{
+ KZPlayer player = KZPlayer(client);
+
+ if (player.Fake)
+ {
+ return;
+ }
+
+ bool force = forceRefresh[client]
+ || player.CanMakeCheckpoint != oldCanMakeCP[client]
+ || player.CanTeleportToCheckpoint != oldCanTP[client]
+ || player.CanPrevCheckpoint != oldCanPrevCP[client]
+ || player.CanNextCheckpoint != oldCanNextCP[client]
+ || player.CanUndoTeleport != oldCanUndoTP[client]
+ || player.CanPause != oldCanPause[client]
+ || player.CanResume != oldCanResume[client];
+
+
+ if (player.Alive)
+ {
+ if (player.TPMenu != TPMenu_Disabled)
+ {
+ if (GetClientMenu(client) == MenuSource_None
+ || gB_MenuShowing[player.ID] && GetClientAvgLoss(player.ID, NetFlow_Both) > EPSILON
+ || gB_MenuShowing[player.ID] && player.TimerRunning && !player.Paused && player.TimerText == TimerText_TPMenu
+ || gB_MenuShowing[player.ID] && force)
+ {
+ ShowTPMenu(player, info);
+ }
+ }
+ else
+ {
+ // There is no need to update this very often as there's no menu selection to be done here.
+ if (GetClientMenu(client) == MenuSource_None
+ || gB_MenuShowing[player.ID] && player.TimerRunning && !player.Paused && player.TimerText == TimerText_TPMenu)
+ {
+ ShowPanel(player, info);
+ }
+ }
+ }
+ else if (player.ObserverTarget != -1) // If the player is spectating someone else
+ {
+ // Check if the replay plugin wants to display the replay control menu.
+ if (!(IsFakeClient(player.ObserverTarget) && gB_GOKZReplays && GOKZ_RP_UpdateReplayControlMenu(client)))
+ {
+ ShowPanel(player, info);
+ }
+ }
+
+ oldCanMakeCP[client] = player.CanMakeCheckpoint;
+ oldCanTP[client] = player.CanTeleportToCheckpoint;
+ oldCanPrevCP[client] = player.CanPrevCheckpoint;
+ oldCanNextCP[client] = player.CanNextCheckpoint;
+ oldCanUndoTP[client] = player.CanUndoTeleport;
+ oldCanPause[client] = player.CanPause;
+ oldCanResume[client] = player.CanResume;
+ forceRefresh[client] = false;
+}
+
+static void ShowPanel(KZPlayer player, HUDInfo info)
+{
+ char panelTitle[256];
+ // Spectator List
+ if (player.ShowSpectators >= ShowSpecs_Number && player.SpecListPosition == SpecListPosition_TPMenu)
+ {
+ Format(panelTitle, sizeof(panelTitle), "%s", FormatSpectatorTextForMenu(player, info));
+ }
+ // Timer panel
+ if (player.TimerText == TimerText_TPMenu && info.TimerRunning)
+ {
+ if (panelTitle[0] != '\0')
+ {
+ Format(panelTitle, sizeof(panelTitle), "%s \n%s", panelTitle, FormatTimerTextForMenu(player, info));
+ }
+ else
+ {
+ Format(panelTitle, sizeof(panelTitle), "%s", FormatTimerTextForMenu(player, info));
+ }
+ if (info.TimeType == TimeType_Nub && info.CurrentTeleport != 0)
+ {
+ Format(panelTitle, sizeof(panelTitle), "%s\n%t", panelTitle, "TP Menu - Spectator Teleports", info.CurrentTeleport);
+ }
+ }
+
+ if (panelTitle[0] != '\0' && GetClientMenu(player.ID) == MenuSource_None || gB_MenuShowing[player.ID])
+ {
+ Panel panel = new Panel(null);
+ panel.SetTitle(panelTitle);
+ panel.Send(player.ID, PanelHandler_Menu, MENU_TIME_FOREVER);
+
+ delete panel;
+ gB_MenuShowing[player.ID] = true;
+ }
+}
+
+static void ShowTPMenu(KZPlayer player, HUDInfo info)
+{
+ Menu menu = new Menu(MenuHandler_TPMenu);
+ menu.OptionFlags = MENUFLAG_NO_SOUND;
+ menu.ExitButton = false;
+ menu.Pagination = MENU_NO_PAGINATION;
+ TPMenuSetTitle(player, menu, info);
+ TPMenuAddItems(player, menu);
+ menu.Display(player.ID, MENU_TIME_FOREVER);
+ gB_MenuShowing[player.ID] = true;
+}
+
+static void TPMenuSetTitle(KZPlayer player, Menu menu, HUDInfo info)
+{
+ char title[256];
+ if (player.ShowSpectators >= ShowSpecs_Number && player.SpecListPosition == SpecListPosition_TPMenu)
+ {
+ Format(title, sizeof(title), "%s", FormatSpectatorTextForMenu(player, info));
+ }
+ if (player.TimerRunning && player.TimerText == TimerText_TPMenu)
+ {
+ if (title[0] != '\0')
+ {
+ Format(title, sizeof(title), "%s \n%s", title, FormatTimerTextForMenu(player, info));
+ }
+ else
+ {
+ Format(title, sizeof(title), "%s", FormatTimerTextForMenu(player, info));
+ }
+ }
+ if (title[0] != '\0')
+ {
+ menu.SetTitle(title);
+ }
+}
+
+static void TPMenuAddItems(KZPlayer player, Menu menu)
+{
+ switch (player.TPMenu)
+ {
+ case TPMenu_Simple:
+ {
+ TPMenuAddItemCheckpoint(player, menu);
+ TPMenuAddItemTeleport(player, menu);
+ TPMenuAddItemPause(player, menu);
+ TPMenuAddItemStart(player, menu);
+ }
+ case TPMenu_Advanced:
+ {
+ TPMenuAddItemCheckpoint(player, menu);
+ TPMenuAddItemTeleport(player, menu);
+ TPMenuAddItemPrevCheckpoint(player, menu);
+ TPMenuAddItemNextCheckpoint(player, menu);
+ TPMenuAddItemUndo(player, menu);
+ TPMenuAddItemPause(player, menu);
+ TPMenuAddItemStart(player, menu);
+ }
+ }
+}
+
+static void TPMenuAddItemCheckpoint(KZPlayer player, Menu menu)
+{
+ char display[24];
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Checkpoint", player.ID);
+ if (player.TimerRunning)
+ {
+ Format(display, sizeof(display), "%s #%d", display, player.CheckpointCount);
+ }
+
+ // Legacy behavior: Always able to make checkpoint attempts.
+ if (gI_DynamicMenu[player.ID] == DynamicMenu_Enabled && !player.CanMakeCheckpoint)
+ {
+ menu.AddItem(ITEM_INFO_CHECKPOINT, display, ITEMDRAW_DISABLED);
+ }
+ else
+ {
+ menu.AddItem(ITEM_INFO_CHECKPOINT, display, ITEMDRAW_DEFAULT);
+ }
+
+}
+
+static void TPMenuAddItemTeleport(KZPlayer player, Menu menu)
+{
+ char display[24];
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Teleport", player.ID);
+ if (player.TimerRunning)
+ {
+ Format(display, sizeof(display), "%s #%d", display, player.TeleportCount);
+ }
+
+ // Legacy behavior: Only able to make TP attempts when there is a checkpoint.
+ if (gI_DynamicMenu[player.ID] == DynamicMenu_Disabled || player.CanTeleportToCheckpoint)
+ {
+ menu.AddItem(ITEM_INFO_TELEPORT, display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ menu.AddItem(ITEM_INFO_TELEPORT, display, ITEMDRAW_DISABLED);
+ }
+}
+
+static void TPMenuAddItemPrevCheckpoint(KZPlayer player, Menu menu)
+{
+ char display[24];
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Prev CP", player.ID);
+
+ // Legacy behavior: Only able to do prev CP when there is a previous checkpoint to go back to.
+ if (gI_DynamicMenu[player.ID] == DynamicMenu_Disabled || player.CanPrevCheckpoint)
+ {
+ menu.AddItem(ITEM_INFO_PREV, display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ menu.AddItem(ITEM_INFO_PREV, display, ITEMDRAW_DISABLED);
+ }
+}
+
+static void TPMenuAddItemNextCheckpoint(KZPlayer player, Menu menu)
+{
+ char display[24];
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Next CP", player.ID);
+
+ // Legacy behavior: Only able to do prev CP when there is a next checkpoint to go forward to.
+ if (gI_DynamicMenu[player.ID] == DynamicMenu_Disabled || player.CanNextCheckpoint)
+ {
+ menu.AddItem(ITEM_INFO_NEXT, display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ menu.AddItem(ITEM_INFO_NEXT, display, ITEMDRAW_DISABLED);
+ }
+}
+
+static void TPMenuAddItemUndo(KZPlayer player, Menu menu)
+{
+ char display[24];
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Undo TP", player.ID);
+
+ // Legacy behavior: Only able to attempt to undo TP when it is allowed.
+ if (gI_DynamicMenu[player.ID] == DynamicMenu_Disabled || player.CanUndoTeleport)
+ {
+ menu.AddItem(ITEM_INFO_UNDO, display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ menu.AddItem(ITEM_INFO_UNDO, display, ITEMDRAW_DISABLED);
+ }
+
+}
+
+static void TPMenuAddItemPause(KZPlayer player, Menu menu)
+{
+ char display[24];
+
+ // Legacy behavior: Always able to attempt to pause.
+ if (gI_DynamicMenu[player.ID] == DynamicMenu_Enabled)
+ {
+ if (player.Paused)
+ {
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Resume", player.ID);
+ if (player.CanResume)
+ {
+ menu.AddItem(ITEM_INFO_PAUSE, display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ menu.AddItem(ITEM_INFO_PAUSE, display, ITEMDRAW_DISABLED);
+ }
+ }
+ else
+ {
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Pause", player.ID);
+ if (player.CanPause)
+ {
+ menu.AddItem(ITEM_INFO_PAUSE, display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ menu.AddItem(ITEM_INFO_PAUSE, display, ITEMDRAW_DISABLED);
+ }
+ }
+ }
+ else
+ {
+ if (player.Paused)
+ {
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Resume", player.ID);
+ }
+ else
+ {
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Pause", player.ID);
+ }
+ menu.AddItem(ITEM_INFO_PAUSE, display, ITEMDRAW_DEFAULT);
+ }
+}
+
+static void TPMenuAddItemStart(KZPlayer player, Menu menu)
+{
+ char display[24];
+ if (player.StartPositionType == StartPositionType_Spawn)
+ {
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Respawn", player.ID);
+ menu.AddItem(ITEM_INFO_START, display, ITEMDRAW_DEFAULT);
+ }
+ else if (player.TimerRunning)
+ {
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Restart", player.ID);
+ menu.AddItem(ITEM_INFO_START, display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ FormatEx(display, sizeof(display), "%T", "TP Menu - Start", player.ID);
+ menu.AddItem(ITEM_INFO_START, display, ITEMDRAW_DEFAULT);
+ }
+} \ No newline at end of file