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-core/timer/pause.sp | 257 ++++++++++++++ sourcemod/scripting/gokz-core/timer/timer.sp | 368 +++++++++++++++++++++ .../scripting/gokz-core/timer/virtual_buttons.sp | 322 ++++++++++++++++++ 3 files changed, 947 insertions(+) create mode 100644 sourcemod/scripting/gokz-core/timer/pause.sp create mode 100644 sourcemod/scripting/gokz-core/timer/timer.sp create mode 100644 sourcemod/scripting/gokz-core/timer/virtual_buttons.sp (limited to 'sourcemod/scripting/gokz-core/timer') diff --git a/sourcemod/scripting/gokz-core/timer/pause.sp b/sourcemod/scripting/gokz-core/timer/pause.sp new file mode 100644 index 0000000..92ab1fb --- /dev/null +++ b/sourcemod/scripting/gokz-core/timer/pause.sp @@ -0,0 +1,257 @@ +static bool paused[MAXPLAYERS + 1]; +static bool pausedOnLadder[MAXPLAYERS + 1]; +static float lastPauseTime[MAXPLAYERS + 1]; +static bool hasPausedInThisRun[MAXPLAYERS + 1]; +static float lastResumeTime[MAXPLAYERS + 1]; +static bool hasResumedInThisRun[MAXPLAYERS + 1]; +static float lastDuckValue[MAXPLAYERS + 1]; +static float lastStaminaValue[MAXPLAYERS + 1]; + + + +// =====[ PUBLIC ]===== + +bool GetPaused(int client) +{ + return paused[client]; +} + +void SetPausedOnLadder(int client, bool onLadder) +{ + pausedOnLadder[client] = onLadder; +} + +void Pause(int client) +{ + if (!CanPause(client, true)) + { + return; + } + + // Call Pre Forward + Action result; + Call_GOKZ_OnPause(client, result); + if (result != Plugin_Continue) + { + GOKZ_PrintToChat(client, true, "%t", "Can't Pause (Generic)"); + GOKZ_PlayErrorSound(client); + return; + } + + // Pause + paused[client] = true; + pausedOnLadder[client] = Movement_GetMovetype(client) == MOVETYPE_LADDER; + lastDuckValue[client] = Movement_GetDuckSpeed(client); + lastStaminaValue[client] = GetEntPropFloat(client, Prop_Send, "m_flStamina"); + Movement_SetVelocity(client, view_as( { 0.0, 0.0, 0.0 } )); + Movement_SetMovetype(client, MOVETYPE_NONE); + if (GetTimerRunning(client)) + { + hasPausedInThisRun[client] = true; + lastPauseTime[client] = GetEngineTime(); + } + + // Call Post Forward + Call_GOKZ_OnPause_Post(client); +} + +bool CanPause(int client, bool showError = false) +{ + if (paused[client]) + { + return false; + } + + if (GetTimerRunning(client)) + { + if (hasResumedInThisRun[client] + && GetEngineTime() - lastResumeTime[client] < GOKZ_PAUSE_COOLDOWN) + { + if (showError) + { + GOKZ_PrintToChat(client, true, "%t", "Can't Pause (Just Resumed)"); + GOKZ_PlayErrorSound(client); + } + return false; + } + else if (!Movement_GetOnGround(client) + && !(Movement_GetSpeed(client) == 0 && Movement_GetVerticalVelocity(client) == 0)) + { + if (showError) + { + GOKZ_PrintToChat(client, true, "%t", "Can't Pause (Midair)"); + GOKZ_PlayErrorSound(client); + } + return false; + } + else if (BhopTriggersJustTouched(client)) + { + if (showError) + { + GOKZ_PrintToChat(client, true, "%t", "Can't Pause (Just Landed)"); + GOKZ_PlayErrorSound(client); + } + return false; + } + else if (AntiPauseTriggerIsTouched(client)) + { + if (showError) + { + GOKZ_PrintToChat(client, true, "%t", "Can't Pause (Anti Pause Area)"); + GOKZ_PlayErrorSound(client); + } + return false; + } + } + + return true; +} + +void Resume(int client, bool force = false) +{ + if (!paused[client]) + { + return; + } + if (!force && !CanResume(client, true)) + { + return; + } + + // Call Pre Forward + Action result; + Call_GOKZ_OnResume(client, result); + if (result != Plugin_Continue) + { + GOKZ_PrintToChat(client, true, "%t", "Can't Resume (Generic)"); + GOKZ_PlayErrorSound(client); + return; + } + + // Resume + if (pausedOnLadder[client]) + { + Movement_SetMovetype(client, MOVETYPE_LADDER); + } + else + { + Movement_SetMovetype(client, MOVETYPE_WALK); + } + + // Prevent noclip exploit + SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD); + paused[client] = false; + if (GetTimerRunning(client)) + { + hasResumedInThisRun[client] = true; + lastResumeTime[client] = GetEngineTime(); + } + Movement_SetDuckSpeed(client, lastDuckValue[client]); + SetEntPropFloat(client, Prop_Send, "m_flStamina", lastStaminaValue[client]); + + // Call Post Forward + Call_GOKZ_OnResume_Post(client); +} + +bool CanResume(int client, bool showError = false) +{ + if (GetTimerRunning(client) && hasPausedInThisRun[client] + && GetEngineTime() - lastPauseTime[client] < GOKZ_PAUSE_COOLDOWN) + { + if (showError) + { + GOKZ_PrintToChat(client, true, "%t", "Can't Resume (Just Paused)"); + GOKZ_PlayErrorSound(client); + } + return false; + } + return true; +} + +void TogglePause(int client) +{ + if (paused[client]) + { + Resume(client); + } + else + { + Pause(client); + } +} + + + +// =====[ EVENTS ]===== + +void OnClientPutInServer_Pause(int client) +{ + paused[client] = false; +} + +void OnTimerStart_Pause(int client) +{ + hasPausedInThisRun[client] = false; + hasResumedInThisRun[client] = false; + Resume(client, true); +} + +void OnChangeMovetype_Pause(int client, MoveType newMovetype) +{ + // Check if player has escaped MOVETYPE_NONE + if (!paused[client] || newMovetype == MOVETYPE_NONE) + { + return; + } + + // Player has escaped MOVETYPE_NONE, so resume + paused[client] = false; + if (GetTimerRunning(client)) + { + hasResumedInThisRun[client] = true; + lastResumeTime[client] = GetEngineTime(); + } + + // Call Post Forward + Call_GOKZ_OnResume_Post(client); +} + +void OnPlayerSpawn_Pause(int client) +{ + if (!paused[client]) + { + return; + } + + // Player has left paused state by spawning in, so resume + paused[client] = false; + if (GetTimerRunning(client)) + { + hasResumedInThisRun[client] = true; + lastResumeTime[client] = GetEngineTime(); + } + + Movement_SetDuckSpeed(client, lastDuckValue[client]); + SetEntPropFloat(client, Prop_Send, "m_flStamina", lastStaminaValue[client]); + + // Call Post Forward + Call_GOKZ_OnResume_Post(client); +} + +void OnJoinTeam_Pause(int client, int team) +{ + // Only handle joining spectators. Joining other teams is handled by OnPlayerSpawn. + if (team == CS_TEAM_SPECTATOR) + { + paused[client] = true; + + if (GetTimerRunning(client)) + { + hasPausedInThisRun[client] = true; + lastPauseTime[client] = GetEngineTime(); + } + + // Call Post Forward + Call_GOKZ_OnPause_Post(client); + } +} \ No newline at end of file diff --git a/sourcemod/scripting/gokz-core/timer/timer.sp b/sourcemod/scripting/gokz-core/timer/timer.sp new file mode 100644 index 0000000..f6696ac --- /dev/null +++ b/sourcemod/scripting/gokz-core/timer/timer.sp @@ -0,0 +1,368 @@ +static bool timerRunning[MAXPLAYERS + 1]; +static float currentTime[MAXPLAYERS + 1]; +static int currentCourse[MAXPLAYERS + 1]; +static float lastEndTime[MAXPLAYERS + 1]; +static float lastFalseEndTime[MAXPLAYERS + 1]; +static float lastStartSoundTime[MAXPLAYERS + 1]; +static int lastStartMode[MAXPLAYERS + 1]; +static bool validTime[MAXPLAYERS + 1]; + + +// =====[ PUBLIC ]===== + +bool GetTimerRunning(int client) +{ + return timerRunning[client]; +} + +bool GetValidTimer(int client) +{ + return validTime[client]; +} + +float GetCurrentTime(int client) +{ + return currentTime[client]; +} + +void SetCurrentTime(int client, float time) +{ + currentTime[client] = time; + // The timer should be running if time is not negative. + timerRunning[client] = time >= 0.0; +} + +int GetCurrentCourse(int client) +{ + return currentCourse[client]; +} + +void SetCurrentCourse(int client, int course) +{ + currentCourse[client] = course; +} + +int GetCurrentTimeType(int client) +{ + if (GetTeleportCount(client) == 0) + { + return TimeType_Pro; + } + return TimeType_Nub; +} + +bool TimerStart(int client, int course, bool allowMidair = false, bool playSound = true) +{ + if (!IsPlayerAlive(client) + || JustStartedTimer(client) + || JustTeleported(client) + || JustNoclipped(client) + || !IsPlayerValidMoveType(client) + || !allowMidair && (!Movement_GetOnGround(client) || JustLanded(client)) + || allowMidair && !Movement_GetOnGround(client) && (!GOKZ_GetValidJump(client) || GOKZ_GetHitPerf(client)) + || (GOKZ_GetTimerRunning(client) && GOKZ_GetCourse(client) != course)) + { + return false; + } + + // Call Pre Forward + Action result; + Call_GOKZ_OnTimerStart(client, course, result); + if (result != Plugin_Continue) + { + return false; + } + + // Prevent noclip exploit + SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD); + + // Start Timer + currentTime[client] = 0.0; + timerRunning[client] = true; + currentCourse[client] = course; + lastStartMode[client] = GOKZ_GetCoreOption(client, Option_Mode); + validTime[client] = true; + if (playSound) + { + PlayTimerStartSound(client); + } + + // Call Post Forward + Call_GOKZ_OnTimerStart_Post(client, course); + + return true; +} + +bool TimerEnd(int client, int course) +{ + if (!IsPlayerAlive(client)) + { + return false; + } + + if (!timerRunning[client] || course != currentCourse[client]) + { + PlayTimerFalseEndSound(client); + lastFalseEndTime[client] = GetGameTime(); + return false; + } + + float time = GetCurrentTime(client); + int teleportsUsed = GetTeleportCount(client); + + // Call Pre Forward + Action result; + Call_GOKZ_OnTimerEnd(client, course, time, teleportsUsed, result); + if (result != Plugin_Continue) + { + return false; + } + + if (!validTime[client]) + { + PlayTimerFalseEndSound(client); + lastFalseEndTime[client] = GetGameTime(); + TimerStop(client, false); + return false; + } + // End Timer + timerRunning[client] = false; + lastEndTime[client] = GetGameTime(); + PlayTimerEndSound(client); + + if (!IsFakeClient(client)) + { + // Print end timer message + Call_GOKZ_OnTimerEndMessage(client, course, time, teleportsUsed, result); + if (result == Plugin_Continue) + { + PrintEndTimeString(client); + } + } + + // Call Post Forward + Call_GOKZ_OnTimerEnd_Post(client, course, time, teleportsUsed); + + return true; +} + +bool TimerStop(int client, bool playSound = true) +{ + if (!timerRunning[client]) + { + return false; + } + + timerRunning[client] = false; + if (playSound) + { + PlayTimerStopSound(client); + } + + Call_GOKZ_OnTimerStopped(client); + + return true; +} + +void TimerStopAll(bool playSound = true) +{ + for (int client = 1; client <= MaxClients; client++) + { + if (IsValidClient(client)) + { + TimerStop(client, playSound); + } + } +} + +void PlayTimerStartSound(int client) +{ + if (GetGameTime() - lastStartSoundTime[client] > GOKZ_TIMER_SOUND_COOLDOWN) + { + GOKZ_EmitSoundToClient(client, gC_ModeStartSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer Start"); + GOKZ_EmitSoundToClientSpectators(client, gC_ModeStartSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer Start"); + lastStartSoundTime[client] = GetGameTime(); + } +} + +void InvalidateRun(int client) +{ + if (validTime[client]) + { + validTime[client] = false; + Call_GOKZ_OnRunInvalidated(client); + } +} + +// =====[ EVENTS ]===== + +void OnClientPutInServer_Timer(int client) +{ + timerRunning[client] = false; + currentTime[client] = 0.0; + currentCourse[client] = 0; + lastEndTime[client] = 0.0; + lastFalseEndTime[client] = 0.0; + lastStartSoundTime[client] = 0.0; + lastStartMode[client] = MODE_COUNT; // So it won't equal any mode +} + +void OnPlayerRunCmdPost_Timer(int client) +{ + if (IsPlayerAlive(client) && GetTimerRunning(client) && !GetPaused(client)) + { + currentTime[client] += GetTickInterval(); + } +} + +void OnChangeMovetype_Timer(int client, MoveType newMovetype) +{ + if (!IsValidMovetype(newMovetype)) + { + if (TimerStop(client)) + { + GOKZ_PrintToChat(client, true, "%t", "Timer Stopped (Noclipped)"); + } + } +} + +void OnTeleportToStart_Timer(int client) +{ + if (GetCurrentMapPrefix() == MapPrefix_KZPro) + { + TimerStop(client, false); + } +} + +void OnClientDisconnect_Timer(int client) +{ + TimerStop(client); +} + +void OnPlayerDeath_Timer(int client) +{ + TimerStop(client); +} + +void OnOptionChanged_Timer(int client, Option option) +{ + if (option == Option_Mode) + { + if (TimerStop(client)) + { + GOKZ_PrintToChat(client, true, "%t", "Timer Stopped (Changed Mode)"); + } + } +} + +void OnRoundStart_Timer() +{ + TimerStopAll(); +} + + + +// =====[ PRIVATE ]===== + +static bool IsPlayerValidMoveType(int client) +{ + return IsValidMovetype(Movement_GetMovetype(client)); +} + +static bool IsValidMovetype(MoveType movetype) +{ + return movetype == MOVETYPE_WALK + || movetype == MOVETYPE_LADDER + || movetype == MOVETYPE_NONE + || movetype == MOVETYPE_OBSERVER; +} + +static bool JustTeleported(int client) +{ + return gB_OriginTeleported[client] || gB_VelocityTeleported[client] + || gI_CmdNum[client] - gI_TeleportCmdNum[client] <= GOKZ_TIMER_START_GROUND_TICKS; +} + +static bool JustLanded(int client) +{ + return !gB_OldOnGround[client] + || gI_CmdNum[client] - Movement_GetLandingCmdNum(client) <= GOKZ_TIMER_START_NO_TELEPORT_TICKS; +} + +static bool JustStartedTimer(int client) +{ + return timerRunning[client] && GetCurrentTime(client) < EPSILON; +} + +static bool JustEndedTimer(int client) +{ + return GetGameTime() - lastEndTime[client] < 1.0; +} + +static void PlayTimerEndSound(int client) +{ + GOKZ_EmitSoundToClient(client, gC_ModeEndSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer End"); + GOKZ_EmitSoundToClientSpectators(client, gC_ModeEndSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer End"); +} + +static void PlayTimerFalseEndSound(int client) +{ + if (!JustEndedTimer(client) + && (GetGameTime() - lastFalseEndTime[client]) > GOKZ_TIMER_SOUND_COOLDOWN) + { + GOKZ_EmitSoundToClient(client, gC_ModeFalseEndSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer False End"); + GOKZ_EmitSoundToClientSpectators(client, gC_ModeFalseEndSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer False End"); + } +} + +static void PlayTimerStopSound(int client) +{ + GOKZ_EmitSoundToClient(client, GOKZ_SOUND_TIMER_STOP, _, "Timer Stop"); + GOKZ_EmitSoundToClientSpectators(client, GOKZ_SOUND_TIMER_STOP, _, "Timer Stop"); +} + +static void PrintEndTimeString(int client) +{ + if (GetCurrentCourse(client) == 0) + { + switch (GetCurrentTimeType(client)) + { + case TimeType_Nub: + { + GOKZ_PrintToChatAll(true, "%t", "Beat Map (NUB)", + client, + GOKZ_FormatTime(GetCurrentTime(client)), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + case TimeType_Pro: + { + GOKZ_PrintToChatAll(true, "%t", "Beat Map (PRO)", + client, + GOKZ_FormatTime(GetCurrentTime(client)), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + } + } + else + { + switch (GetCurrentTimeType(client)) + { + case TimeType_Nub: + { + GOKZ_PrintToChatAll(true, "%t", "Beat Bonus (NUB)", + client, + currentCourse[client], + GOKZ_FormatTime(GetCurrentTime(client)), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + case TimeType_Pro: + { + GOKZ_PrintToChatAll(true, "%t", "Beat Bonus (PRO)", + client, + currentCourse[client], + GOKZ_FormatTime(GetCurrentTime(client)), + gC_ModeNamesShort[GOKZ_GetCoreOption(client, Option_Mode)]); + } + } + } +} \ No newline at end of file diff --git a/sourcemod/scripting/gokz-core/timer/virtual_buttons.sp b/sourcemod/scripting/gokz-core/timer/virtual_buttons.sp new file mode 100644 index 0000000..aa88a9d --- /dev/null +++ b/sourcemod/scripting/gokz-core/timer/virtual_buttons.sp @@ -0,0 +1,322 @@ +/* + Most commonly referred to in the KZ community as timer tech. + Lets players press 'virtual' start and end buttons without looking. +*/ + + + +static int beamSprite; +static int haloSprite; +static float lastUsePressTime[MAXPLAYERS + 1]; +static int lastTeleportTick[MAXPLAYERS + 1]; +static bool startedTimerLastTick[MAXPLAYERS + 1]; +static bool onlyNaturalButtonPressed[MAXPLAYERS + 1]; +static int startTimerButtonPressTick[MAXPLAYERS + 1]; +static bool hasEndedTimerSincePressingUse[MAXPLAYERS + 1]; +static bool hasTeleportedSincePressingUse[MAXPLAYERS + 1]; +static bool hasVirtualStartButton[MAXPLAYERS + 1]; +static bool hasVirtualEndButton[MAXPLAYERS + 1]; +static bool wasInEndZone[MAXPLAYERS + 1]; +static float virtualStartOrigin[MAXPLAYERS + 1][3]; +static float virtualEndOrigin[MAXPLAYERS + 1][3]; +static int virtualStartCourse[MAXPLAYERS + 1]; +static int virtualEndCourse[MAXPLAYERS + 1]; +static bool virtualButtonsLocked[MAXPLAYERS + 1]; + + + +// =====[ PUBLIC ]===== + +bool GetHasVirtualStartButton(int client) +{ + return hasVirtualStartButton[client]; +} + +bool GetHasVirtualEndButton(int client) +{ + return hasVirtualEndButton[client]; +} + +bool ToggleVirtualButtonsLock(int client) +{ + virtualButtonsLocked[client] = !virtualButtonsLocked[client]; + return virtualButtonsLocked[client]; +} + +void LockVirtualButtons(int client) +{ + virtualButtonsLocked[client] = true; +} + +int GetVirtualButtonPosition(int client, float position[3], bool isStart) +{ + if (isStart && hasVirtualStartButton[client]) + { + position = virtualStartOrigin[client]; + return virtualStartCourse[client]; + } + else if (!isStart && hasVirtualEndButton[client]) + { + position = virtualEndOrigin[client]; + return virtualEndCourse[client]; + } + + return -1; +} + +void SetVirtualButtonPosition(int client, float position[3], int course, bool isStart) +{ + if (isStart) + { + virtualStartCourse[client] = course; + virtualStartOrigin[client] = position; + hasVirtualStartButton[client] = true; + } + else + { + virtualEndCourse[client] = course; + virtualEndOrigin[client] = position; + hasVirtualEndButton[client] = true; + } +} + +void ResetVirtualButtonPosition(int client, bool isStart) +{ + if (isStart) + { + virtualStartCourse[client] = -1; + virtualStartOrigin[client] = {0.0, 0.0, 0.0}; + hasVirtualStartButton[client] = false; + } + else + { + virtualEndCourse[client] = -1; + virtualEndOrigin[client] = {0.0, 0.0, 0.0}; + hasVirtualEndButton[client] = false; + } +} + +// =====[ EVENTS ]===== + +void OnMapStart_VirtualButtons() +{ + beamSprite = PrecacheModel("materials/sprites/laserbeam.vmt"); + haloSprite = PrecacheModel("materials/sprites/glow01.vmt"); +} + +void OnClientPutInServer_VirtualButtons(int client) +{ + startedTimerLastTick[client] = false; + hasVirtualEndButton[client] = false; + hasVirtualStartButton[client] = false; + virtualButtonsLocked[client] = false; + onlyNaturalButtonPressed[client] = false; + wasInEndZone[client] = false; + startTimerButtonPressTick[client] = 0; +} + +void OnStartButtonPress_VirtualButtons(int client, int course) +{ + if (!virtualButtonsLocked[client] && + lastTeleportTick[client] + GOKZ_TIMER_START_NO_TELEPORT_TICKS < GetGameTickCount()) + { + Movement_GetOrigin(client, virtualStartOrigin[client]); + virtualStartCourse[client] = course; + hasVirtualStartButton[client] = true; + startTimerButtonPressTick[client] = GetGameTickCount(); + } +} + +void OnEndButtonPress_VirtualButtons(int client, int course) +{ + // Prevent setting end virtual button to where it would usually be unreachable + if (IsPlayerStuck(client)) + { + return; + } + + if (!virtualButtonsLocked[client] && + lastTeleportTick[client] + GOKZ_TIMER_START_NO_TELEPORT_TICKS < GetGameTickCount()) + { + Movement_GetOrigin(client, virtualEndOrigin[client]); + virtualEndCourse[client] = course; + hasVirtualEndButton[client] = true; + } +} + +void OnPlayerRunCmdPost_VirtualButtons(int client, int buttons, int cmdnum) +{ + CheckForAndHandleUsage(client, buttons); + UpdateIndicators(client, cmdnum); +} + +void OnCountedTeleport_VirtualButtons(int client) +{ + hasTeleportedSincePressingUse[client] = true; +} + +void OnTeleport_DelayVirtualButtons(int client) +{ + lastTeleportTick[client] = GetGameTickCount(); +} + + + +// =====[ PRIVATE ]===== + +static void CheckForAndHandleUsage(int client, int buttons) +{ + if (buttons & IN_USE && !(gI_OldButtons[client] & IN_USE)) + { + lastUsePressTime[client] = GetGameTime(); + hasEndedTimerSincePressingUse[client] = false; + hasTeleportedSincePressingUse[client] = false; + onlyNaturalButtonPressed[client] = startTimerButtonPressTick[client] == GetGameTickCount(); + } + + bool useCheck = PassesUseCheck(client); + + // Start button + if ((useCheck || GOKZ_GetCoreOption(client, Option_TimerButtonZoneType) == TimerButtonZoneType_BothZones) + && GetHasVirtualStartButton(client) && InRangeOfVirtualStart(client) && CanReachVirtualStart(client)) + { + if (TimerStart(client, virtualStartCourse[client], .playSound = false)) + { + startedTimerLastTick[client] = true; + OnVirtualStartButtonPress_Teleports(client); + } + } + else if (startedTimerLastTick[client]) + { + // Without that check you get two sounds when pressing the natural timer button + if (!onlyNaturalButtonPressed[client]) + { + PlayTimerStartSound(client); + } + onlyNaturalButtonPressed[client] = false; + startedTimerLastTick[client] = false; + } + + // End button + if ((useCheck || GOKZ_GetCoreOption(client, Option_TimerButtonZoneType) != TimerButtonZoneType_BothButtons) + && GetHasVirtualEndButton(client) && InRangeOfVirtualEnd(client) && CanReachVirtualEnd(client)) + { + if (!wasInEndZone[client]) + { + TimerEnd(client, virtualEndCourse[client]); + hasEndedTimerSincePressingUse[client] = true; // False end counts as well + wasInEndZone[client] = true; + } + } + else + { + wasInEndZone[client] = false; + } +} + +static bool PassesUseCheck(int client) +{ + if (GetGameTime() - lastUsePressTime[client] < GOKZ_VIRTUAL_BUTTON_USE_DETECTION_TIME + EPSILON + && !hasEndedTimerSincePressingUse[client] + && !hasTeleportedSincePressingUse[client]) + { + return true; + } + + return false; +} + +bool InRangeOfVirtualStart(int client) +{ + return InRangeOfButton(client, virtualStartOrigin[client]); +} + +static bool InRangeOfVirtualEnd(int client) +{ + return InRangeOfButton(client, virtualEndOrigin[client]); +} + +static bool InRangeOfButton(int client, const float buttonOrigin[3]) +{ + float origin[3]; + Movement_GetOrigin(client, origin); + float distanceToButton = GetVectorDistance(origin, buttonOrigin); + return distanceToButton <= gF_ModeVirtualButtonRanges[GOKZ_GetCoreOption(client, Option_Mode)]; +} + +bool CanReachVirtualStart(int client) +{ + return CanReachButton(client, virtualStartOrigin[client]); +} + +static bool CanReachVirtualEnd(int client) +{ + return CanReachButton(client, virtualEndOrigin[client]); +} + +static bool CanReachButton(int client, const float buttonOrigin[3]) +{ + float origin[3]; + Movement_GetOrigin(client, origin); + Handle trace = TR_TraceRayFilterEx(origin, buttonOrigin, MASK_PLAYERSOLID, RayType_EndPoint, TraceEntityFilterPlayers); + bool didHit = TR_DidHit(trace); + delete trace; + return !didHit; +} + + + +// ===== [ INDICATOR ] ===== + +static void UpdateIndicators(int client, int cmdnum) +{ + if (cmdnum % 128 != 0 || !IsPlayerAlive(client) + || GOKZ_GetCoreOption(client, Option_VirtualButtonIndicators) == VirtualButtonIndicators_Disabled) + { + return; + } + + if (hasVirtualStartButton[client]) + { + DrawIndicator(client, virtualStartOrigin[client], { 0, 255, 0, 255 } ); + } + + if (hasVirtualEndButton[client]) + { + DrawIndicator(client, virtualEndOrigin[client], { 255, 0, 0, 255 } ); + } +} + +static void DrawIndicator(int client, const float origin[3], const int colour[4]) +{ + float radius = gF_ModeVirtualButtonRanges[GOKZ_GetCoreOption(client, Option_Mode)]; + if (radius <= EPSILON) // Don't draw circle of radius 0 + { + return; + } + + float x, y, start[3], end[3]; + + // Create the start position for the first part of the beam + start[0] = origin[0] + radius; + start[1] = origin[1]; + start[2] = origin[2]; + + for (int i = 1; i <= 31; i++) // Circle is broken into 31 segments + { + float angle = 2 * PI / 31 * i; + x = radius * Cosine(angle); + y = radius * Sine(angle); + + end[0] = origin[0] + x; + end[1] = origin[1] + y; + end[2] = origin[2]; + + TE_SetupBeamPoints(start, end, beamSprite, haloSprite, 0, 0, 0.97, 0.2, 0.2, 0, 0.0, colour, 0); + TE_SendToClient(client); + + start[0] = end[0]; + start[1] = end[1]; + start[2] = end[2]; + } +} -- cgit v1.2.3