summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/gokz-core/misc.sp
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-core/misc.sp
parent38f1140c11724da05a23a10385061200b907cf6e (diff)
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/gokz-core/misc.sp')
-rw-r--r--sourcemod/scripting/gokz-core/misc.sp803
1 files changed, 803 insertions, 0 deletions
diff --git a/sourcemod/scripting/gokz-core/misc.sp b/sourcemod/scripting/gokz-core/misc.sp
new file mode 100644
index 0000000..a117880
--- /dev/null
+++ b/sourcemod/scripting/gokz-core/misc.sp
@@ -0,0 +1,803 @@
+/*
+ Small features that aren't worth splitting into their own file.
+*/
+
+
+
+// =====[ GOKZ.CFG ]=====
+
+void OnMapStart_KZConfig()
+{
+ char gokzCfgFullPath[PLATFORM_MAX_PATH];
+ FormatEx(gokzCfgFullPath, sizeof(gokzCfgFullPath), "cfg/%s", GOKZ_CFG_SERVER);
+
+ if (FileExists(gokzCfgFullPath))
+ {
+ ServerCommand("exec %s", GOKZ_CFG_SERVER);
+ }
+ else
+ {
+ SetFailState("Failed to load file: \"%s\". Check that it exists.", gokzCfgFullPath);
+ }
+}
+
+
+
+// =====[ GODMODE ]=====
+
+void OnPlayerSpawn_GodMode(int client)
+{
+ // Stop players from taking damage
+ SetEntProp(client, Prop_Data, "m_takedamage", 0);
+ SetEntityFlags(client, GetEntityFlags(client) | FL_GODMODE);
+}
+
+
+
+// =====[ NOCLIP ]=====
+
+int noclipReleaseTime[MAXPLAYERS + 1];
+
+void ToggleNoclip(int client)
+{
+ if (Movement_GetMovetype(client) != MOVETYPE_NOCLIP)
+ {
+ EnableNoclip(client);
+ }
+ else
+ {
+ DisableNoclip(client);
+ }
+}
+
+void EnableNoclip(int client)
+{
+ if (IsValidClient(client) && IsPlayerAlive(client))
+ {
+ 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;
+ }
+ Movement_SetMovetype(client, MOVETYPE_NOCLIP);
+ GOKZ_StopTimer(client);
+ }
+}
+
+void DisableNoclip(int client)
+{
+ if (IsValidClient(client) && IsPlayerAlive(client) && Movement_GetMovetype(client) == MOVETYPE_NOCLIP)
+ {
+ noclipReleaseTime[client] = GetGameTickCount();
+ Movement_SetMovetype(client, MOVETYPE_WALK);
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD);
+
+ // Prevents an exploit that would let you noclip out of start zones
+ RemoveNoclipGroundFlag(client);
+ }
+}
+
+void ToggleNoclipNotrigger(int client)
+{
+ if (Movement_GetMovetype(client) != MOVETYPE_NOCLIP)
+ {
+ EnableNoclipNotrigger(client);
+ }
+ else
+ {
+ DisableNoclipNotrigger(client);
+ }
+}
+
+void EnableNoclipNotrigger(int client)
+{
+ if (IsValidClient(client) && IsPlayerAlive(client))
+ {
+ 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;
+ }
+ Movement_SetMovetype(client, MOVETYPE_NOCLIP);
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_NOTRIGGER);
+ GOKZ_StopTimer(client);
+ }
+}
+
+void DisableNoclipNotrigger(int client)
+{
+ if (IsValidClient(client) && IsPlayerAlive(client) && Movement_GetMovetype(client) == MOVETYPE_NOCLIP)
+ {
+ noclipReleaseTime[client] = GetGameTickCount();
+ Movement_SetMovetype(client, MOVETYPE_WALK);
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD);
+
+ // Prevents an exploit that would let you noclip out of start zones
+ RemoveNoclipGroundFlag(client);
+ }
+}
+
+void RemoveNoclipGroundFlag(int client)
+{
+ float startPosition[3], endPosition[3];
+ GetClientAbsOrigin(client, startPosition);
+ endPosition = startPosition;
+ endPosition[2] = startPosition[2] - 2.0;
+ Handle trace = TR_TraceHullFilterEx(
+ startPosition,
+ endPosition,
+ view_as<float>( { -16.0, -16.0, 0.0 } ),
+ view_as<float>( { 16.0, 16.0, 72.0 } ),
+ MASK_PLAYERSOLID,
+ TraceEntityFilterPlayers,
+ client);
+
+ if (!TR_DidHit(trace))
+ {
+ SetEntityFlags(client, GetEntityFlags(client) & ~FL_ONGROUND);
+ }
+ delete trace;
+}
+
+bool JustNoclipped(int client)
+{
+ return GetGameTickCount() - noclipReleaseTime[client] <= GOKZ_TIMER_START_NOCLIP_TICKS;
+}
+
+void OnClientPutInServer_Noclip(int client)
+{
+ noclipReleaseTime[client] = 0;
+}
+
+// =====[ TURNBINDS ]=====
+
+static int turnbindsLastLeftStart[MAXPLAYERS + 1];
+static int turnbindsLastRightStart[MAXPLAYERS + 1];
+static float turnbindsLastValidYaw[MAXPLAYERS + 1];
+static int turnbindsOldButtons[MAXPLAYERS + 1];
+
+void OnClientPutInServer_Turnbinds(int client)
+{
+ turnbindsLastLeftStart[client] = 0;
+ turnbindsLastRightStart[client] = 0;
+}
+// Ensures that there is a minimum time between starting to turnbind in one direction
+// and then starting to turnbind in the other direction
+void OnPlayerRunCmd_Turnbinds(int client, int buttons, int tickcount, float angles[3])
+{
+ if (IsFakeClient(client))
+ {
+ return;
+ }
+ if (buttons & IN_LEFT && tickcount < turnbindsLastRightStart[client] + RoundToNearest(GOKZ_TURNBIND_COOLDOWN / GetTickInterval()))
+ {
+ angles[1] = turnbindsLastValidYaw[client];
+ TeleportEntity(client, NULL_VECTOR, angles, NULL_VECTOR);
+ buttons = 0;
+ }
+ else if (buttons & IN_RIGHT && tickcount < turnbindsLastLeftStart[client] + RoundToNearest(GOKZ_TURNBIND_COOLDOWN / GetTickInterval()))
+ {
+ angles[1] = turnbindsLastValidYaw[client];
+ TeleportEntity(client, NULL_VECTOR, angles, NULL_VECTOR);
+ buttons = 0;
+ }
+ else
+ {
+ turnbindsLastValidYaw[client] = angles[1];
+
+ if (!(turnbindsOldButtons[client] & IN_LEFT) && (buttons & IN_LEFT))
+ {
+ turnbindsLastLeftStart[client] = tickcount;
+ }
+
+ if (!(turnbindsOldButtons[client] & IN_RIGHT) && (buttons & IN_RIGHT))
+ {
+ turnbindsLastRightStart[client] = tickcount;
+ }
+
+ turnbindsOldButtons[client] = buttons;
+ }
+}
+
+
+
+// =====[ PLAYER COLLISION ]=====
+
+void OnPlayerSpawn_PlayerCollision(int client)
+{
+ // Let players go through other players
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD);
+}
+
+void OnSetModel_PlayerCollision(int client)
+{
+ // Fix custom models temporarily changing player collisions
+ SetEntPropVector(client, Prop_Data, "m_vecMins", PLAYER_MINS);
+ if (GetEntityFlags(client) & FL_DUCKING == 0)
+ {
+ SetEntPropVector(client, Prop_Data, "m_vecMaxs", PLAYER_MAXS);
+ }
+ else
+ {
+ SetEntPropVector(client, Prop_Data, "m_vecMaxs", PLAYER_MAXS_DUCKED);
+ }
+}
+
+
+// =====[ FORCE SV_FULL_ALLTALK 1 ]=====
+
+void OnRoundStart_ForceAllTalk()
+{
+ gCV_sv_full_alltalk.BoolValue = true;
+}
+
+
+
+// =====[ ERROR SOUNDS ]=====
+
+#define SOUND_ERROR "buttons/button10.wav"
+
+void PlayErrorSound(int client)
+{
+ if (GOKZ_GetCoreOption(client, Option_ErrorSounds) == ErrorSounds_Enabled)
+ {
+ GOKZ_EmitSoundToClient(client, SOUND_ERROR, _, "Error");
+ }
+}
+
+
+
+// =====[ STOP SOUNDS ]=====
+
+Action OnNormalSound_StopSounds(int entity)
+{
+ char className[20];
+ GetEntityClassname(entity, className, sizeof(className));
+ if (StrEqual(className, "func_button", false))
+ {
+ return Plugin_Handled; // No sounds directly from func_button
+ }
+ return Plugin_Continue;
+}
+
+
+
+// =====[ JOIN TEAM HANDLING ]=====
+
+static bool hasSavedPosition[MAXPLAYERS + 1];
+static float savedOrigin[MAXPLAYERS + 1][3];
+static float savedAngles[MAXPLAYERS + 1][3];
+static bool savedOnLadder[MAXPLAYERS + 1];
+
+void OnClientPutInServer_JoinTeam(int client)
+{
+ // Automatically put the player on a team if he doesn't choose one.
+ // The mp_force_pick_time convar is the built in way to do this, but that obviously
+ // does not call GOKZ_JoinTeam which includes a fix for spawning in the void when
+ // there is no valid spawns available.
+ CreateTimer(12.0, Timer_ForceJoinTeam, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);
+
+ hasSavedPosition[client] = false;
+}
+
+public Action Timer_ForceJoinTeam(Handle timer, int userid)
+{
+ int client = GetClientOfUserId(userid);
+ if (IsValidClient(client))
+ {
+ int team = GetClientTeam(client);
+ if (team == CS_TEAM_NONE)
+ {
+ GOKZ_JoinTeam(client, CS_TEAM_SPECTATOR, false);
+ }
+ }
+ return Plugin_Stop;
+}
+
+void OnTimerStart_JoinTeam(int client)
+{
+ hasSavedPosition[client] = false;
+}
+
+void JoinTeam(int client, int newTeam, bool restorePos, bool forceBroadcast = false)
+{
+ KZPlayer player = KZPlayer(client);
+ int currentTeam = GetClientTeam(client);
+
+ // Don't use CS_TEAM_NONE
+ if (newTeam == CS_TEAM_NONE)
+ {
+ newTeam = CS_TEAM_SPECTATOR;
+ }
+
+ if (newTeam == CS_TEAM_SPECTATOR && currentTeam != CS_TEAM_SPECTATOR)
+ {
+ if (currentTeam != CS_TEAM_NONE)
+ {
+ player.GetOrigin(savedOrigin[client]);
+ player.GetEyeAngles(savedAngles[client]);
+ savedOnLadder[client] = player.Movetype == MOVETYPE_LADDER;
+ hasSavedPosition[client] = true;
+ }
+
+ if (!player.Paused && !player.CanPause)
+ {
+ player.StopTimer();
+ }
+ ChangeClientTeam(client, CS_TEAM_SPECTATOR);
+ Call_GOKZ_OnJoinTeam(client, newTeam);
+ }
+ else if (newTeam == CS_TEAM_CT && currentTeam != CS_TEAM_CT
+ || newTeam == CS_TEAM_T && currentTeam != CS_TEAM_T)
+ {
+ ForcePlayerSuicide(client);
+ CS_SwitchTeam(client, newTeam);
+ CS_RespawnPlayer(client);
+ if (restorePos && hasSavedPosition[client])
+ {
+ TeleportPlayer(client, savedOrigin[client], savedAngles[client]);
+ if (savedOnLadder[client])
+ {
+ player.Movetype = MOVETYPE_LADDER;
+ }
+ }
+ else
+ {
+ player.StopTimer();
+ // Just joining a team alone can put you into weird invalid spawns.
+ // Need to teleport the player to a valid one.
+ float spawnOrigin[3];
+ float spawnAngles[3];
+ GetValidSpawn(spawnOrigin, spawnAngles);
+ TeleportPlayer(client, spawnOrigin, spawnAngles);
+ }
+ hasSavedPosition[client] = false;
+ Call_GOKZ_OnJoinTeam(client, newTeam);
+ }
+ else if (forceBroadcast)
+ {
+ Call_GOKZ_OnJoinTeam(client, newTeam);
+ }
+}
+
+void SendFakeTeamEvent(int client)
+{
+ // Send a fake event to close the team menu
+ Event event = CreateEvent("player_team");
+ event.SetInt("userid", GetClientUserId(client));
+ event.FireToClient(client);
+ event.Cancel();
+}
+
+// =====[ VALID JUMP TRACKING ]=====
+
+/*
+ Valid jump tracking is intended to detect when the player
+ has performed a normal jump that hasn't been affected by
+ (unexpected) teleports or other cases that may result in
+ the player becoming airborne, such as spawning.
+
+ There are ways to trick the plugin, but it is rather
+ unlikely to happen during normal gameplay.
+*/
+
+static bool validJump[MAXPLAYERS + 1];
+static float validJumpTeleportOrigin[MAXPLAYERS + 1][3];
+static int lastInvalidatedTick[MAXPLAYERS + 1];
+bool GetValidJump(int client)
+{
+ return validJump[client];
+}
+
+static void InvalidateJump(int client)
+{
+ lastInvalidatedTick[client] = GetGameTickCount();
+ if (validJump[client])
+ {
+ validJump[client] = false;
+ Call_GOKZ_OnJumpInvalidated(client);
+ }
+}
+
+void OnStopTouchGround_ValidJump(int client, bool jumped, bool ladderJump, bool jumpbug)
+{
+ if (Movement_GetMovetype(client) == MOVETYPE_WALK && lastInvalidatedTick[client] != GetGameTickCount())
+ {
+ validJump[client] = true;
+ Call_GOKZ_OnJumpValidated(client, jumped, ladderJump, jumpbug);
+ }
+ else
+ {
+ InvalidateJump(client);
+ }
+}
+
+void OnPlayerRunCmdPost_ValidJump(int client)
+{
+ if (gB_VelocityTeleported[client] || gB_OriginTeleported[client])
+ {
+ InvalidateJump(client);
+ }
+}
+
+void OnChangeMovetype_ValidJump(int client, MoveType oldMovetype, MoveType newMovetype)
+{
+ if (oldMovetype == MOVETYPE_LADDER && newMovetype == MOVETYPE_WALK && lastInvalidatedTick[client] != GetGameTickCount()) // Ladderjump
+ {
+ validJump[client] = true;
+ Call_GOKZ_OnJumpValidated(client, false, true, false);
+ }
+ else
+ {
+ InvalidateJump(client);
+ }
+}
+
+void OnClientDisconnect_ValidJump(int client)
+{
+ InvalidateJump(client);
+}
+
+void OnPlayerSpawn_ValidJump(int client)
+{
+ // That should definitely be out of bounds
+ CopyVector({ 40000.0, 40000.0, 40000.0 }, validJumpTeleportOrigin[client]);
+ InvalidateJump(client);
+}
+
+void OnPlayerDeath_ValidJump(int client)
+{
+ InvalidateJump(client);
+}
+
+void OnValidOriginChange_ValidJump(int client, const float origin[3])
+{
+ CopyVector(origin, validJumpTeleportOrigin[client]);
+}
+
+void OnTeleport_ValidJump(int client)
+{
+ float origin[3];
+ Movement_GetOrigin(client, origin);
+ if (gB_OriginTeleported[client] && GetVectorDistance(validJumpTeleportOrigin[client], origin, true) <= EPSILON)
+ {
+ gB_OriginTeleported[client] = false;
+ CopyVector({ 40000.0, 40000.0, 40000.0 }, validJumpTeleportOrigin[client]);
+ return;
+ }
+
+ if (gB_OriginTeleported[client])
+ {
+ InvalidateJump(client);
+ Call_GOKZ_OnTeleport(client);
+ }
+
+ if (gB_VelocityTeleported[client])
+ {
+ InvalidateJump(client);
+ }
+}
+
+
+
+// =====[ FIRST SPAWN ]=====
+
+static bool hasSpawned[MAXPLAYERS + 1];
+
+void OnClientPutInServer_FirstSpawn(int client)
+{
+ hasSpawned[client] = false;
+}
+
+void OnPlayerSpawn_FirstSpawn(int client)
+{
+ int team = GetClientTeam(client);
+ if (!hasSpawned[client] && (team == CS_TEAM_CT || team == CS_TEAM_T))
+ {
+ hasSpawned[client] = true;
+ Call_GOKZ_OnFirstSpawn(client);
+ }
+}
+
+
+
+// =====[ TIME LIMIT ]=====
+
+void OnConfigsExecuted_TimeLimit()
+{
+ CreateTimer(1.0, Timer_TimeLimit, _, TIMER_REPEAT);
+}
+
+public Action Timer_TimeLimit(Handle timer)
+{
+ int timelimit;
+ if (!GetMapTimeLimit(timelimit) || timelimit == 0)
+ {
+ return Plugin_Continue;
+ }
+
+ int timeleft;
+ // Check for less than -1 in case we miss 0 - ignore -1 because that means infinite time limit
+ if (GetMapTimeLeft(timeleft) && (timeleft == 0 || timeleft < -1))
+ {
+ CreateTimer(5.0, Timer_EndRound); // End the round after a delay or it won't end the map
+ return Plugin_Stop;
+ }
+
+ return Plugin_Continue;
+}
+
+public Action Timer_EndRound(Handle timer)
+{
+ CS_TerminateRound(1.0, CSRoundEnd_Draw, true);
+ return Plugin_Continue;
+}
+
+
+
+// =====[ COURSE REGISTER ]=====
+
+static bool startRegistered[GOKZ_MAX_COURSES];
+static bool endRegistered[GOKZ_MAX_COURSES];
+static bool courseRegistered[GOKZ_MAX_COURSES];
+
+bool GetCourseRegistered(int course)
+{
+ return courseRegistered[course];
+}
+
+void RegisterCourseStart(int course)
+{
+ startRegistered[course] = true;
+ TryRegisterCourse(course);
+}
+
+void RegisterCourseEnd(int course)
+{
+ endRegistered[course] = true;
+ TryRegisterCourse(course);
+}
+
+void OnMapStart_CourseRegister()
+{
+ for (int course = 0; course < GOKZ_MAX_COURSES; course++)
+ {
+ courseRegistered[course] = false;
+ }
+}
+
+static void TryRegisterCourse(int course)
+{
+ if (!courseRegistered[course] && startRegistered[course] && endRegistered[course])
+ {
+ courseRegistered[course] = true;
+ Call_GOKZ_OnCourseRegistered(course);
+ }
+}
+
+
+
+// =====[ SPAWN FIXES ]=====
+
+void OnMapStart_FixMissingSpawns()
+{
+ int tSpawn = FindEntityByClassname(-1, "info_player_terrorist");
+ int ctSpawn = FindEntityByClassname(-1, "info_player_counterterrorist");
+
+ if (tSpawn == -1 && ctSpawn == -1)
+ {
+ LogMessage("Couldn't fix spawns because none exist.");
+ return;
+ }
+
+ if (tSpawn == -1 || ctSpawn == -1)
+ {
+ float origin[3], angles[3];
+ GetValidSpawn(origin, angles);
+
+ int newSpawn = CreateEntityByName((tSpawn == -1) ? "info_player_terrorist" : "info_player_counterterrorist");
+ if (DispatchSpawn(newSpawn))
+ {
+ TeleportEntity(newSpawn, origin, angles, NULL_VECTOR);
+ }
+ }
+}
+
+// =====[ BUTTONS ]=====
+
+void OnClientPreThinkPost_UseButtons(int client)
+{
+ if (GOKZ_GetCoreOption(client, Option_ButtonThroughPlayers) == ButtonThroughPlayers_Enabled && GetEntProp(client, Prop_Data, "m_afButtonPressed") & IN_USE)
+ {
+ int entity = FindUseEntity(client);
+ if (entity != -1)
+ {
+ AcceptEntityInput(entity, "Use", client, client, 1);
+ }
+ }
+}
+
+static int FindUseEntity(int client)
+{
+ float fwd[3];
+ float angles[3];
+ GetClientEyeAngles(client, angles);
+ GetAngleVectors(angles, fwd, NULL_VECTOR, NULL_VECTOR);
+
+ Handle trace;
+
+ float eyeOrigin[3];
+ GetClientEyePosition(client, eyeOrigin);
+ int useableContents = (MASK_NPCSOLID_BRUSHONLY | MASK_OPAQUE_AND_NPCS) & ~CONTENTS_OPAQUE;
+
+ float endpos[3];
+
+ // Check if +use trace collide with a player first, so we don't activate any button twice
+ trace = TR_TraceRayFilterEx(eyeOrigin, angles, useableContents, RayType_Infinite, TRFOtherPlayersOnly, client);
+ if (TR_DidHit(trace))
+ {
+ int ent = TR_GetEntityIndex(trace);
+ if (ent < 1 || ent > MaxClients)
+ {
+ return -1;
+ }
+ // Search for a button behind it.
+ trace = TR_TraceRayFilterEx(eyeOrigin, angles, useableContents, RayType_Infinite, TraceEntityFilterPlayers);
+ if (TR_DidHit(trace))
+ {
+ char buffer[20];
+ ent = TR_GetEntityIndex(trace);
+ // Make sure that it is a button, and this button activates when pressed.
+ // If it is not a button, check its parent to see if it is a button.
+ bool isButton;
+ while (ent != -1)
+ {
+ GetEntityClassname(ent, buffer, sizeof(buffer));
+ if (StrEqual("func_button", buffer, false) && GetEntProp(ent, Prop_Data, "m_spawnflags") & SF_BUTTON_USE_ACTIVATES)
+ {
+ isButton = true;
+ break;
+ }
+ else
+ {
+ ent = GetEntPropEnt(ent, Prop_Data, "m_hMoveParent");
+ }
+ }
+ if (isButton)
+ {
+ TR_GetEndPosition(endpos, trace);
+ float delta[3];
+ for (int i = 0; i < 2; i++)
+ {
+ delta[i] = endpos[i] - eyeOrigin[i];
+ }
+ // Z distance is treated differently.
+ float m_vecMins[3];
+ float m_vecMaxs[3];
+ float m_vecOrigin[3];
+ GetEntPropVector(ent, Prop_Send, "m_vecOrigin", m_vecOrigin);
+ GetEntPropVector(ent, Prop_Send, "m_vecMins", m_vecMins);
+ GetEntPropVector(ent, Prop_Send, "m_vecMaxs", m_vecMaxs);
+
+ delta[2] = IntervalDistance(endpos[2], m_vecOrigin[2] + m_vecMins[2], m_vecOrigin[2] + m_vecMaxs[2]);
+ if (GetVectorLength(delta) < 80.0)
+ {
+ return ent;
+ }
+ }
+ }
+ }
+
+ int nearestEntity;
+ float nearestPoint[3];
+ float nearestDist = FLOAT_MAX;
+ ArrayList entities = new ArrayList();
+ TR_EnumerateEntitiesSphere(eyeOrigin, 80.0, 1<<5, AddEntities, entities);
+ for (int i = 0; i < entities.Length; i++)
+ {
+ char buffer[64];
+ int ent = entities.Get(i);
+ GetEntityClassname(ent, buffer, sizeof(buffer));
+ // Check if the entity is a button and it is pressable.
+ if (StrEqual("func_button", buffer, false) && GetEntProp(ent, Prop_Data, "m_spawnflags") & SF_BUTTON_USE_ACTIVATES)
+ {
+ float point[3];
+ CalcNearestPoint(ent, eyeOrigin, point);
+
+ float dir[3];
+ for (int j = 0; j < 3; j++)
+ {
+ dir[j] = point[j] - eyeOrigin[2];
+ }
+ // Check the maximum angle the player can be away from the button.
+ float minimumDot = GetEntPropFloat(ent, Prop_Send, "m_flUseLookAtAngle");
+ NormalizeVector(dir, dir);
+ float dot = GetVectorDotProduct(dir, fwd);
+ if (dot < minimumDot)
+ {
+ continue;
+ }
+
+ float dist = CalcDistanceToLine(point, eyeOrigin, fwd);
+ if (dist < nearestDist)
+ {
+ trace = TR_TraceRayFilterEx(eyeOrigin, point, useableContents, RayType_EndPoint, TraceEntityFilterPlayers);
+ if (TR_GetFraction(trace) == 1.0 || TR_GetEntityIndex(trace) == ent)
+ {
+ CopyVector(point, nearestPoint);
+ nearestDist = dist;
+ nearestEntity = ent;
+ }
+ }
+ }
+ }
+ // We found the closest button, but we still need to check if there is a player in front of it or not.
+ // In the case that there isn't a player inbetween, we don't return the entity index, because that button will be pressed by the game function anyway.
+ // If there is, we will press two buttons at once, the "right" button found by this function and the "wrong" button that we only happen to press because
+ // there is a player in the way.
+
+ trace = TR_TraceRayFilterEx(eyeOrigin, nearestPoint, useableContents, RayType_EndPoint, TRFOtherPlayersOnly);
+ if (TR_DidHit(trace))
+ {
+ return nearestEntity;
+ }
+ return -1;
+}
+
+public bool AddEntities(int entity, ArrayList entities)
+{
+ entities.Push(entity);
+ return true;
+}
+
+static float IntervalDistance(float x, float x0, float x1)
+{
+ if (x0 > x1)
+ {
+ float tmp = x0;
+ x0 = x1;
+ x1 = tmp;
+ }
+ if (x < x0)
+ {
+ return x0 - x;
+ }
+ else if (x > x1)
+ {
+ return x - x1;
+ }
+ return 0.0;
+}
+// TraceRay filter for other players exclusively.
+public bool TRFOtherPlayersOnly(int entity, int contentmask, int client)
+{
+ return (0 < entity <= MaxClients) && (entity != client);
+}
+
+// =====[ SAFE MODE ]=====
+
+void ToggleNubSafeGuard(int client)
+{
+ if (GOKZ_GetCoreOption(client, Option_Safeguard) == Safeguard_EnabledNUB)
+ {
+ GOKZ_SetCoreOption(client, Option_Safeguard, Safeguard_Disabled);
+ }
+ else
+ {
+ GOKZ_SetCoreOption(client, Option_Safeguard, Safeguard_EnabledNUB);
+ }
+}
+
+void ToggleProSafeGuard(int client)
+{
+ if (GOKZ_GetCoreOption(client, Option_Safeguard) == Safeguard_EnabledPRO)
+ {
+ GOKZ_SetCoreOption(client, Option_Safeguard, Safeguard_Disabled);
+ }
+ else
+ {
+ GOKZ_SetCoreOption(client, Option_Safeguard, Safeguard_EnabledPRO);
+ }
+}