summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/include/gokz.inc
diff options
context:
space:
mode:
Diffstat (limited to 'sourcemod/scripting/include/gokz.inc')
-rw-r--r--sourcemod/scripting/include/gokz.inc1097
1 files changed, 1097 insertions, 0 deletions
diff --git a/sourcemod/scripting/include/gokz.inc b/sourcemod/scripting/include/gokz.inc
new file mode 100644
index 0000000..edbd896
--- /dev/null
+++ b/sourcemod/scripting/include/gokz.inc
@@ -0,0 +1,1097 @@
+/*
+ GOKZ General Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_included_
+#endinput
+#endif
+#define _gokz_included_
+#include <cstrike>
+#include <movement>
+
+#include <gokz/version>
+
+
+
+// =====[ ENUMS ]=====
+
+enum ObsMode
+{
+ ObsMode_None = 0, // Not in spectator mode
+ ObsMode_DeathCam, // Special mode for death cam animation
+ ObsMode_FreezeCam, // Zooms to a target, and freeze-frames on them
+ ObsMode_Fixed, // View from a fixed camera position
+ ObsMode_InEye, // Follow a player in first person view
+ ObsMode_Chase, // Follow a player in third person view
+ ObsMode_Roaming // Free roaming
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define GOKZ_SOURCE_URL "https://github.com/KZGlobalTeam/gokz"
+#define GOKZ_UPDATER_BASE_URL "http://updater.gokz.org/v2/"
+#define GOKZ_COLLISION_GROUP_STANDARD 2
+#define GOKZ_COLLISION_GROUP_NOTRIGGER 1
+#define GOKZ_TP_FREEZE_TICKS 5
+#define EPSILON 0.000001
+#define PI 3.14159265359
+#define SPEED_NORMAL 250.0
+#define SPEED_NO_WEAPON 260.0
+#define FLOAT_MAX view_as<float>(0x7F7FFFFF)
+#define SF_BUTTON_USE_ACTIVATES 1024
+#define IGNORE_JUMP_TIME 0.2
+stock float PLAYER_MINS[3] = {-16.0, -16.0, 0.0};
+stock float PLAYER_MAXS[3] = {16.0, 16.0, 72.0};
+stock float PLAYER_MAXS_DUCKED[3] = {16.0, 16.0, 54.0};
+
+
+
+// =====[ STOCKS ]=====
+
+/**
+ * Represents a time float as a string e.g. 01:23.45.
+ *
+ * @param time Time in seconds.
+ * @param precise Whether to include fractional seconds.
+ * @return String representation of time.
+ */
+stock char[] GOKZ_FormatTime(float time, bool precise = true)
+{
+ char formattedTime[12];
+
+ int roundedTime = RoundFloat(time * 100); // Time rounded to number of centiseconds
+
+ int centiseconds = roundedTime % 100;
+ roundedTime = (roundedTime - centiseconds) / 100;
+ int seconds = roundedTime % 60;
+ roundedTime = (roundedTime - seconds) / 60;
+ int minutes = roundedTime % 60;
+ roundedTime = (roundedTime - minutes) / 60;
+ int hours = roundedTime;
+
+ if (hours == 0)
+ {
+ if (precise)
+ {
+ FormatEx(formattedTime, sizeof(formattedTime), "%02d:%02d.%02d", minutes, seconds, centiseconds);
+ }
+ else
+ {
+ FormatEx(formattedTime, sizeof(formattedTime), "%d:%02d", minutes, seconds);
+ }
+ }
+ else
+ {
+ if (precise)
+ {
+ FormatEx(formattedTime, sizeof(formattedTime), "%d:%02d:%02d.%02d", hours, minutes, seconds, centiseconds);
+ }
+ else
+ {
+ FormatEx(formattedTime, sizeof(formattedTime), "%d:%02d:%02d", hours, minutes, seconds);
+ }
+ }
+ return formattedTime;
+}
+
+/**
+ * Checks if the value is a valid client entity index, if they are in-game and not GOTV.
+ *
+ * @param client Client index.
+ * @return Whether client is valid.
+ */
+stock bool IsValidClient(int client)
+{
+ return client >= 1 && client <= MaxClients && IsClientInGame(client) && !IsClientSourceTV(client);
+}
+
+/**
+ * Returns the greater of two float values.
+ *
+ * @param value1 First value.
+ * @param value2 Second value.
+ * @return Greatest value.
+ */
+stock float FloatMax(float value1, float value2)
+{
+ if (value1 >= value2)
+ {
+ return value1;
+ }
+ return value2;
+}
+
+/**
+ * Returns the lesser of two float values.
+ *
+ * @param value1 First value.
+ * @param value2 Second value.
+ * @return Lesser value.
+ */
+stock float FloatMin(float value1, float value2)
+{
+ if (value1 <= value2)
+ {
+ return value1;
+ }
+ return value2;
+}
+
+/**
+ * Clamp a float value between an upper and lower bound.
+ *
+ * @param value Preferred value.
+ * @param min Minimum value.
+ * @param max Maximum value.
+ * @return The closest value to the preferred value.
+ */
+stock float FloatClamp(float value, float min, float max)
+{
+ if (value >= max)
+ {
+ return max;
+ }
+ if (value <= min)
+ {
+ return min;
+ }
+ return value;
+}
+
+
+/**
+ * Returns the greater of two int values.
+ *
+ * @param value1 First value.
+ * @param value2 Second value.
+ * @return Greatest value.
+ */
+stock int IntMax(int value1, int value2)
+{
+ if (value1 >= value2)
+ {
+ return value1;
+ }
+ return value2;
+}
+
+/**
+ * Returns the lesser of two int values.
+ *
+ * @param value1 First value.
+ * @param value2 Second value.
+ * @return Lesser value.
+ */
+stock int IntMin(int value1, int value2)
+{
+ if (value1 <= value2)
+ {
+ return value1;
+ }
+ return value2;
+}
+
+/**
+ * Rounds a float to the nearest specified power of 10.
+ *
+ * @param value Value to round.
+ * @param power Power of 10 to round to.
+ * @return Rounded value.
+ */
+stock float RoundToPowerOfTen(float value, int power)
+{
+ float pow = Pow(10.0, float(power));
+ return RoundFloat(value / pow) * pow;
+}
+
+/**
+ * Sets all characters in a string to lower case.
+ *
+ * @param input Input string.
+ * @param output Output buffer.
+ * @param size Maximum size of output.
+ */
+stock void String_ToLower(const char[] input, char[] output, int size)
+{
+ size--;
+ int i = 0;
+ while (input[i] != '\0' && i < size)
+ {
+ output[i] = CharToLower(input[i]);
+ i++;
+ }
+ output[i] = '\0';
+}
+
+/**
+ * Gets the client's observer mode.
+ *
+ * @param client Client index.
+ * @return Current observer mode.
+ */
+stock ObsMode GetObserverMode(int client)
+{
+ return view_as<ObsMode>(GetEntProp(client, Prop_Send, "m_iObserverMode"));
+}
+
+/**
+ * Gets the player a client is spectating.
+ *
+ * @param client Client index.
+ * @return Client index of target, or -1 if not spectating anyone.
+ */
+stock int GetObserverTarget(int client)
+{
+ if (!IsValidClient(client))
+ {
+ return -1;
+ }
+ ObsMode mode = GetObserverMode(client);
+ if (mode == ObsMode_InEye || mode == ObsMode_Chase)
+ {
+ return GetEntPropEnt(client, Prop_Send, "m_hObserverTarget");
+ }
+ return -1;
+}
+
+/**
+ * Emits a sound to other players that are spectating the client.
+ *
+ * @param client Client being spectated.
+ * @param sound Sound to play.
+ */
+stock void EmitSoundToClientSpectators(int client, const char[] sound)
+{
+ for (int i = 1; i <= MaxClients; i++)
+ {
+ if (IsValidClient(i) && GetObserverTarget(i) == client)
+ {
+ EmitSoundToClient(i, sound);
+ }
+ }
+}
+
+/**
+ * Calculates the lowest angle from angle A to angle B.
+ * Input and result angles are between -180 and 180.
+ *
+ * @param angleA Angle A.
+ * @param angleB Angle B.
+ * @return Delta angle.
+ */
+stock float CalcDeltaAngle(float angleA, float angleB)
+{
+ float difference = angleB - angleA;
+
+ if (difference > 180.0)
+ {
+ difference = difference - 360.0;
+ }
+ else if (difference <= -180.0)
+ {
+ difference = difference + 360.0;
+ }
+
+ return difference;
+}
+
+/**
+ * Strips all color control characters in a string.
+ * The Output buffer can be the same as the input buffer.
+ * Original code by Psychonic, thanks.
+ * Source: smlib
+ *
+ * @param input Input String.
+ * @param output Output String.
+ * @param size Max Size of the Output string
+ */
+stock void Color_StripFromChatText(const char[] input, char[] output, int size)
+{
+ int x = 0;
+ for (int i = 0; input[i] != '\0'; i++) {
+
+ if (x + 1 == size)
+ {
+ break;
+ }
+
+ int character = input[i];
+
+ if (character > 0x08)
+ {
+ output[x++] = character;
+ }
+ }
+
+ output[x] = '\0';
+}
+
+/**
+ * Returns an integer as a string.
+ *
+ * @param num Integer to stringify.
+ * @return Integer as a string.
+ */
+stock char[] IntToStringEx(int num)
+{
+ char string[12];
+ IntToString(num, string, sizeof(string));
+ return string;
+}
+
+/**
+ * Returns a float as a string.
+ *
+ * @param num Float to stringify.
+ * @return Float as a string.
+ */
+stock char[] FloatToStringEx(float num)
+{
+ char string[32];
+ FloatToString(num, string, sizeof(string));
+ return string;
+}
+
+/**
+ * Increment an index, looping back to 0 if the max value is reached.
+ *
+ * @param index Current index.
+ * @param buffer Max value of index.
+ * @return Current index incremented, or 0 if max value is reached.
+ */
+stock int NextIndex(int index, int max)
+{
+ index++;
+ if (index == max)
+ {
+ return 0;
+ }
+ return index;
+}
+
+/**
+ * Reorders an array with current index at the front, and previous
+ * values after, including looping back to the end after reaching
+ * the start of the array.
+ *
+ * @param input Array to reorder.
+ * @param inputSize Size of input array.
+ * @param buffer Output buffer.
+ * @param bufferSize Size of buffer.
+ * @param index Index of current/most recent value of input array.
+ */
+stock void SortByRecent(const int[] input, int inputSize, int[] buffer, int bufferSize, int index)
+{
+ int reorderedIndex = 0;
+ for (int i = index; reorderedIndex < bufferSize && i >= 0; i--)
+ {
+ buffer[reorderedIndex] = input[i];
+ reorderedIndex++;
+ }
+ for (int i = inputSize - 1; reorderedIndex < bufferSize && i > index; i--)
+ {
+ buffer[reorderedIndex] = input[i];
+ reorderedIndex++;
+ }
+}
+
+/**
+ * Returns the Steam account ID for a given SteamID2.
+ * Checks for invalid input are not very extensive.
+ *
+ * @param steamID2 SteamID2 to convert.
+ * @return Steam account ID, or -1 if invalid.
+ */
+stock int Steam2ToSteamAccountID(const char[] steamID2)
+{
+ char pieces[3][16];
+ if (ExplodeString(steamID2, ":", pieces, sizeof(pieces), sizeof(pieces[])) != 3)
+ {
+ return -1;
+ }
+
+ int IDNumberPart1 = StringToInt(pieces[1]);
+ int IDNumberPart2 = StringToInt(pieces[2]);
+ if (pieces[1][0] != '0' && IDNumberPart1 == 0 || IDNumberPart1 != 0 && IDNumberPart1 != 1 || IDNumberPart2 <= 0)
+ {
+ return -1;
+ }
+
+ return IDNumberPart1 + (IDNumberPart2 << 1);
+}
+
+/**
+ * Teleports a player and removes their velocity and base velocity
+ * immediately and also every tick for the next 5 ticks. Automatically
+ * makes the player crouch if there is a ceiling above them.
+ *
+ * @param client Client index.
+ * @param origin Origin to teleport to.
+ * @param angles Eye angles to set.
+ */
+stock void TeleportPlayer(int client, const float origin[3], const float angles[3], bool setAngles = true, bool holdStill = true)
+{
+ // Clear the player's parent before teleporting to fix being
+ // teleported into seemingly random places if the player has a parent.
+ AcceptEntityInput(client, "ClearParent");
+
+ Movement_SetOrigin(client, origin);
+ Movement_SetVelocity(client, view_as<float>( { 0.0, 0.0, 0.0 } ));
+ Movement_SetBaseVelocity(client, view_as<float>( { 0.0, 0.0, 0.0 } ));
+ if (setAngles)
+ {
+ // NOTE: changing angles with TeleportEntity can fail due to packet loss!!!
+ // (Movement_SetEyeAngles is a thin wrapper of TeleportEntity)
+ Movement_SetEyeAngles(client, angles);
+ }
+ // Duck the player if there is something blocking them from above
+ Handle trace = TR_TraceHullFilterEx(origin,
+ origin,
+ view_as<float>( { -16.0, -16.0, 0.0 } ), // Standing players are 32 x 32 x 72
+ view_as<float>( { 16.0, 16.0, 72.0 } ),
+ MASK_PLAYERSOLID,
+ TraceEntityFilterPlayers,
+ client);
+ bool ducked = TR_DidHit(trace);
+
+ if (holdStill)
+ {
+ // Prevent noclip exploit
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD);
+
+ // Intelligently hold player still to prevent booster and trigger exploits
+ StartHoldStill(client, ducked);
+ }
+ else if (ducked)
+ {
+ ForcePlayerDuck(client);
+ }
+
+ delete trace;
+}
+
+static void StartHoldStill(int client, bool ducked)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(0); // tick counter
+ data.WriteCell(GOKZ_TP_FREEZE_TICKS); // number of ticks to hold still
+ data.WriteCell(ducked);
+ ContinueHoldStill(data);
+}
+
+public void ContinueHoldStill(DataPack data)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int ticks = data.ReadCell();
+ int tickCount = data.ReadCell();
+ bool ducked = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ if (ticks < tickCount)
+ {
+ Movement_SetVelocity(client, view_as<float>( { 0.0, 0.0, 0.0 } ));
+ Movement_SetBaseVelocity(client, view_as<float>( { 0.0, 0.0, 0.0 } ));
+ Movement_SetGravity(client, 1.0);
+
+ // Don't drop the player off of ladders.
+ // The game will automatically change the movetype back to MOVETYPE_WALK if it can't find a ladder.
+ // Don't change the movetype if it's currently MOVETYPE_NONE, as that means the player is paused.
+ if (Movement_GetMovetype(client) != MOVETYPE_NONE)
+ {
+ Movement_SetMovetype(client, MOVETYPE_LADDER);
+ }
+
+ // Prevent noclip exploit
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD);
+
+ // Force duck on player and make sure that the player can't trigger triggers above them.
+ // they can still trigger triggers even when we force ducking.
+ if (ducked)
+ {
+ ForcePlayerDuck(client);
+
+ if (ticks < tickCount - 1)
+ {
+ // Don't trigger triggers
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_NOTRIGGER);
+ }
+ else
+ {
+ // Let the player trigger triggers on the last tick
+ SetEntProp(client, Prop_Send, "m_CollisionGroup", GOKZ_COLLISION_GROUP_STANDARD);
+ }
+ }
+
+ ++ticks;
+ data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(ticks);
+ data.WriteCell(tickCount);
+ data.WriteCell(ducked);
+ RequestFrame(ContinueHoldStill, data);
+ }
+}
+
+/**
+ * Forces the player to instantly duck.
+ *
+ * @param client Client index.
+ */
+stock void ForcePlayerDuck(int client)
+{
+ // these are both necessary, because on their own the player will sometimes still be in a state that isn't fully ducked.
+ SetEntPropFloat(client, Prop_Send, "m_flDuckAmount", 1.0, 0);
+ SetEntProp(client, Prop_Send, "m_bDucking", false);
+ SetEntProp(client, Prop_Send, "m_bDucked", true);
+}
+
+/**
+ * Returns whether the player is stuck e.g. in a wall after noclipping.
+ *
+ * @param client Client index.
+ * @return Whether player is stuck.
+ */
+stock bool IsPlayerStuck(int client)
+{
+ float vecMin[3], vecMax[3], vecOrigin[3];
+
+ GetClientMins(client, vecMin);
+ GetClientMaxs(client, vecMax);
+ GetClientAbsOrigin(client, vecOrigin);
+
+ TR_TraceHullFilter(vecOrigin, vecOrigin, vecMin, vecMax, MASK_PLAYERSOLID, TraceEntityFilterPlayers);
+ return TR_DidHit(); // head in wall ?
+}
+
+/**
+ * Retrieves the absolute origin of an entity.
+ *
+ * @param entity Index of the entity.
+ * @param result Entity's origin if successful.
+ * @return Returns true if successful.
+ */
+stock bool GetEntityAbsOrigin(int entity, float result[3])
+{
+ if (!IsValidEntity(entity))
+ {
+ return false;
+ }
+
+ if (!HasEntProp(entity, Prop_Data, "m_vecAbsOrigin"))
+ {
+ return false;
+ }
+
+ GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", result);
+ return true;
+}
+
+/**
+ * Retrieves the name of an entity.
+ *
+ * @param entity Index of the entity.
+ * @param buffer Buffer to store the name.
+ * @param maxlength Maximum length of the buffer.
+ * @return Number of non-null bytes written.
+ */
+stock int GetEntityName(int entity, char[] buffer, int maxlength)
+{
+ return GetEntPropString(entity, Prop_Data, "m_iName", buffer, maxlength);
+}
+
+/**
+ * Finds an entity by name or by name and classname.
+ * Taken from smlib https://github.com/bcserv/smlib
+ * This can take anywhere from ~0.2% to ~11% of frametime (i5-7600k) in the worst case scenario where
+ * every entity which has a name (4096 of them) is iterated over. Your mileage may vary.
+ *
+ * @param name Name of the entity to find.
+ * @param className Optional classname to match along with name.
+ * @param ignorePlayers Ignore player entities.
+ * @return Entity index if successful, INVALID_ENT_REFERENCE if not.
+ */
+stock int GOKZFindEntityByName(const char[] name, const char[] className = "", bool ignorePlayers = false)
+{
+ int result = INVALID_ENT_REFERENCE;
+ if (className[0] == '\0')
+ {
+ // HACK: Double the limit to get non-networked entities too.
+ // https://developer.valvesoftware.com/wiki/Entity_limit
+ int realMaxEntities = GetMaxEntities() * 2;
+ int startEntity = 1;
+ if (ignorePlayers)
+ {
+ startEntity = MaxClients + 1;
+ }
+ for (int entity = startEntity; entity < realMaxEntities; entity++)
+ {
+ if (!IsValidEntity(entity))
+ {
+ continue;
+ }
+
+ char entName[65];
+ GetEntityName(entity, entName, sizeof(entName));
+ if (StrEqual(entName, name))
+ {
+ result = entity;
+ break;
+ }
+ }
+ }
+ else
+ {
+ int entity = INVALID_ENT_REFERENCE;
+ while ((entity = FindEntityByClassname(entity, className)) != INVALID_ENT_REFERENCE)
+ {
+ char entName[65];
+ GetEntityName(entity, entName, sizeof(entName));
+ if (StrEqual(entName, name))
+ {
+ result = entity;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/**
+ * Gets the current map's display name in lower case.
+ *
+ * @param buffer Buffer to store the map name.
+ * @param maxlength Maximum length of buffer.
+ */
+stock void GetCurrentMapDisplayName(char[] buffer, int maxlength)
+{
+ char map[PLATFORM_MAX_PATH];
+ GetCurrentMap(map, sizeof(map));
+ GetMapDisplayName(map, map, sizeof(map));
+ String_ToLower(map, buffer, maxlength);
+}
+
+/**
+ * Gets the current map's file size.
+ */
+stock int GetCurrentMapFileSize()
+{
+ char mapBuffer[PLATFORM_MAX_PATH];
+ GetCurrentMap(mapBuffer, sizeof(mapBuffer));
+ Format(mapBuffer, sizeof(mapBuffer), "maps/%s.bsp", mapBuffer);
+ return FileSize(mapBuffer);
+}
+
+/**
+ * Copies the elements of a source vector to a destination vector.
+ *
+ * @param src Source vector.
+ * @param dest Destination vector.
+ */
+stock void CopyVector(const any src[3], any dest[3])
+{
+ dest[0] = src[0];
+ dest[1] = src[1];
+ dest[2] = src[2];
+}
+
+/**
+ * Returns whether the player is spectating.
+ *
+ * @param client Client index.
+ */
+stock bool IsSpectating(int client)
+{
+ int team = GetClientTeam(client);
+ return team == CS_TEAM_SPECTATOR || team == CS_TEAM_NONE;
+}
+
+/**
+ * Rotate a vector on an axis.
+ *
+ * @param vec Vector to rotate.
+ * @param axis Axis to rotate around.
+ * @param theta Angle in radians.
+ * @param result Rotated vector.
+ */
+stock void RotateVectorAxis(float vec[3], float axis[3], float theta, float result[3])
+{
+ float cosTheta = Cosine(theta);
+ float sinTheta = Sine(theta);
+
+ float axisVecCross[3];
+ GetVectorCrossProduct(axis, vec, axisVecCross);
+
+ for (int i = 0; i < 3; i++)
+ {
+ result[i] = (vec[i] * cosTheta) + (axisVecCross[i] * sinTheta) + (axis[i] * GetVectorDotProduct(axis, vec)) * (1.0 - cosTheta);
+ }
+}
+
+/**
+ * Rotate a vector by pitch and yaw.
+ *
+ * @param vec Vector to rotate.
+ * @param pitch Pitch angle (in degrees).
+ * @param yaw Yaw angle (in degrees).
+ * @param result Rotated vector.
+ */
+stock void RotateVectorPitchYaw(float vec[3], float pitch, float yaw, float result[3])
+{
+ if (pitch != 0.0)
+ {
+ RotateVectorAxis(vec, view_as<float>({0.0, 1.0, 0.0}), DegToRad(pitch), result);
+ }
+ if (yaw != 0.0)
+ {
+ RotateVectorAxis(result, view_as<float>({0.0, 0.0, 1.0}), DegToRad(yaw), result);
+ }
+}
+
+/**
+ * Attempts to return a valid spawn location.
+ *
+ * @param origin Spawn origin if found.
+ * @param angles Spawn angles if found.
+ * @return Whether a valid spawn point is found.
+ */
+stock bool GetValidSpawn(float origin[3], float angles[3])
+{
+ // Return true if the spawn found is truly valid (not in the ground or out of bounds)
+ bool foundValidSpawn;
+ bool searchCT;
+ float spawnOrigin[3];
+ float spawnAngles[3];
+ int spawnEntity = -1;
+ while (!foundValidSpawn)
+ {
+ if (searchCT)
+ {
+ spawnEntity = FindEntityByClassname(spawnEntity, "info_player_counterterrorist");
+ }
+ else
+ {
+ spawnEntity = FindEntityByClassname(spawnEntity, "info_player_terrorist");
+ }
+
+ if (spawnEntity != -1)
+ {
+ GetEntPropVector(spawnEntity, Prop_Data, "m_vecOrigin", spawnOrigin);
+ GetEntPropVector(spawnEntity, Prop_Data, "m_angRotation", spawnAngles);
+ if (IsSpawnValid(spawnOrigin))
+ {
+ origin = spawnOrigin;
+ angles = spawnAngles;
+ foundValidSpawn = true;
+ }
+ }
+ else if (!searchCT)
+ {
+ searchCT = true;
+ }
+ else
+ {
+ break;
+ }
+ }
+ return foundValidSpawn;
+}
+
+/**
+ * Check whether a position is a valid spawn location.
+ * A spawn location is considered valid if it is in bounds and not stuck inside the ground.
+ *
+ * @param origin Origin vector.
+ * @return Whether the origin is a valid spawn location.
+ */
+stock bool IsSpawnValid(float origin[3])
+{
+ Handle trace = TR_TraceHullFilterEx(origin, origin, PLAYER_MINS, PLAYER_MAXS, MASK_PLAYERSOLID, TraceEntityFilterPlayers);
+ if (!TR_StartSolid(trace) && !TR_AllSolid(trace) && TR_GetFraction(trace) == 1.0)
+ {
+ delete trace;
+ return true;
+ }
+ delete trace;
+ return false;
+}
+
+/**
+ * Get an entity's origin, angles, its bounding box's center and the distance from the center to its bounding box's edges.
+ *
+ * @param entity Index of the entity.
+ * @param origin Entity's origin.
+ * @param center Center of the entity's bounding box.
+ * @param angles Entity's angles.
+ * @param distFromCenter The distance between the center of the entity's bounding box and its edges.
+ */
+stock void GetEntityPositions(int entity, float origin[3], float center[3], float angles[3], float distFromCenter[3])
+{
+ int ent = entity;
+ float maxs[3], mins[3];
+ GetEntPropVector(ent, Prop_Send, "m_vecOrigin", origin);
+ // Take parent entities into account.
+ while (GetEntPropEnt(ent, Prop_Send, "moveparent") != -1)
+ {
+ ent = GetEntPropEnt(ent, Prop_Send, "moveparent");
+ float tempOrigin[3];
+ GetEntPropVector(ent, Prop_Send, "m_vecOrigin", tempOrigin);
+ for (int i = 0; i < 3; i++)
+ {
+ origin[i] += tempOrigin[i];
+ }
+ }
+
+ GetEntPropVector(ent, Prop_Data, "m_angRotation", angles);
+
+ GetEntPropVector(ent, Prop_Send, "m_vecMaxs", maxs);
+ GetEntPropVector(ent, Prop_Send, "m_vecMins", mins);
+ for (int i = 0; i < 3; i++)
+ {
+ center[i] = origin[i] + (maxs[i] + mins[i]) / 2;
+ distFromCenter[i] = (maxs[i] - mins[i]) / 2;
+ }
+}
+
+/**
+ * Find a valid position around a timer.
+ *
+ * @param entity Index of the timer entity.
+ * @param originDest Result origin if a valid position is found.
+ * @param anglesDest Result angles if a valid position is found.
+ * @return Whether a valid position is found.
+ */
+stock bool FindValidPositionAroundTimerEntity(int entity, float originDest[3], float anglesDest[3], bool isButton)
+{
+ float origin[3], center[3], angles[3], distFromCenter[3];
+ GetEntityPositions(entity, origin, center, angles, distFromCenter);
+ float extraOffset[3];
+ if (isButton) // Test several positions within button press range.
+ {
+ extraOffset[0] = 32.0;
+ extraOffset[1] = 32.0;
+ extraOffset[2] = 32.0;
+ }
+ else // Test positions at the inner surface of the zone.
+ {
+ extraOffset[0] = -(PLAYER_MAXS[0] - PLAYER_MINS[0]) - 1.03125;
+ extraOffset[1] = -(PLAYER_MAXS[1] - PLAYER_MINS[1]) - 1.03125;
+ extraOffset[2] = -(PLAYER_MAXS[2] - PLAYER_MINS[2]) - 1.03125;
+ }
+ if (FindValidPositionAroundCenter(center, distFromCenter, extraOffset, originDest, anglesDest))
+ {
+ return true;
+ }
+ // Test the positions right next to the timer button/zones if the tests above fail.
+ // This can fail when the timer has a cover brush over it.
+ extraOffset[0] = 0.03125;
+ extraOffset[1] = 0.03125;
+ extraOffset[2] = 0.03125;
+ return FindValidPositionAroundCenter(center, distFromCenter, extraOffset, originDest, anglesDest);
+}
+
+static bool FindValidPositionAroundCenter(float center[3], float distFromCenter[3], float extraOffset[3], float originDest[3], float anglesDest[3])
+{
+ float testOrigin[3];
+ int x, y;
+
+ for (int i = 0; i < 3; i++)
+ {
+ // The search starts from the center then outwards to opposite directions.
+ x = i == 2 ? -1 : i;
+ for (int j = 0; j < 3; j++)
+ {
+ y = j == 2 ? -1 : j;
+ for (int z = -1; z <= 1; z++)
+ {
+ testOrigin = center;
+ testOrigin[0] = testOrigin[0] + (distFromCenter[0] + extraOffset[0]) * x + (PLAYER_MAXS[0] - PLAYER_MINS[0]) * x * 0.5;
+ testOrigin[1] = testOrigin[1] + (distFromCenter[1] + extraOffset[1]) * y + (PLAYER_MAXS[1] - PLAYER_MINS[1]) * y * 0.5;
+ testOrigin[2] = testOrigin[2] + (distFromCenter[2] + extraOffset[2]) * z + (PLAYER_MAXS[2] - PLAYER_MINS[2]) * z;
+
+ // Check if there's a line of sight towards the zone as well.
+ if (IsSpawnValid(testOrigin) && CanSeeBox(testOrigin, center, distFromCenter))
+ {
+ originDest = testOrigin;
+ // Always look towards the center.
+ float offsetVector[3];
+ offsetVector[0] = -(distFromCenter[0] + extraOffset[0]) * x;
+ offsetVector[1] = -(distFromCenter[1] + extraOffset[1]) * y;
+ offsetVector[2] = -(distFromCenter[2] + extraOffset[2]) * z;
+ GetVectorAngles(offsetVector, anglesDest);
+ anglesDest[2] = 0.0; // Roll should always be 0.0
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static bool CanSeeBox(float origin[3], float center[3], float distFromCenter[3])
+{
+ float traceOrigin[3], traceDest[3], mins[3], maxs[3];
+
+ CopyVector(origin, traceOrigin);
+
+
+ SubtractVectors(center, distFromCenter, mins);
+ AddVectors(center, distFromCenter, maxs);
+
+ for (int i = 0; i < 3; i++)
+ {
+ mins[i] += 0.03125;
+ maxs[i] -= 0.03125;
+ traceDest[i] = FloatClamp(traceOrigin[i], mins[i], maxs[i]);
+ }
+ int mask = (MASK_NPCSOLID_BRUSHONLY | MASK_OPAQUE_AND_NPCS) & ~CONTENTS_OPAQUE;
+ Handle trace = TR_TraceRayFilterEx(traceOrigin, traceDest, mask, RayType_EndPoint, TraceEntityFilterPlayers);
+ if (TR_DidHit(trace))
+ {
+ float end[3];
+ TR_GetEndPosition(end, trace);
+ for (int i = 0; i < 3; i++)
+ {
+ if (end[i] != traceDest[i])
+ {
+ delete trace;
+ return false;
+ }
+ }
+ }
+ delete trace;
+ return true;
+}
+
+/**
+ * Gets entity index from the address to an entity.
+ *
+ * @param pEntity Entity address.
+ * @return Entity index.
+ * @error Couldn't find offset for m_angRotation, m_vecViewOffset, couldn't confirm offset of m_RefEHandle.
+ */
+stock int GOKZGetEntityFromAddress(Address pEntity)
+{
+ static int offs_RefEHandle;
+ if (offs_RefEHandle)
+ {
+ return EntRefToEntIndex(LoadFromAddress(pEntity + view_as<Address>(offs_RefEHandle), NumberType_Int32) | (1 << 31));
+ }
+
+ // if we don't have it already, attempt to lookup offset based on SDK information
+ // CWorld is derived from CBaseEntity so it should have both offsets
+ int offs_angRotation = FindDataMapInfo(0, "m_angRotation"), offs_vecViewOffset = FindDataMapInfo(0, "m_vecViewOffset");
+ if (offs_angRotation == -1)
+ {
+ SetFailState("Could not find offset for ((CBaseEntity) CWorld)::m_angRotation");
+ }
+ else if (offs_vecViewOffset == -1)
+ {
+ SetFailState("Could not find offset for ((CBaseEntity) CWorld)::m_vecViewOffset");
+ }
+ else if ((offs_angRotation + 0x0C) != (offs_vecViewOffset - 0x04))
+ {
+ char game[32];
+ GetGameFolderName(game, sizeof(game));
+ SetFailState("Could not confirm offset of CBaseEntity::m_RefEHandle (incorrect assumption for game '%s'?)", game);
+ }
+
+ // offset seems right, cache it for the next call
+ offs_RefEHandle = offs_angRotation + 0x0C;
+ return GOKZGetEntityFromAddress(pEntity);
+}
+
+/**
+ * Gets client index from CGameMovement class.
+ *
+ * @param addr Address of CGameMovement class.
+ * @param offsetCGameMovement_player Offset of CGameMovement::player.
+ * @return Client index.
+ * @error Couldn't find offset for m_angRotation, m_vecViewOffset, couldn't confirm offset of m_RefEHandle.
+ */
+stock int GOKZGetClientFromGameMovementAddress(Address addr, int offsetCGameMovement_player)
+{
+ Address playerAddr = view_as<Address>(LoadFromAddress(view_as<Address>(view_as<int>(addr) + offsetCGameMovement_player), NumberType_Int32));
+ return GOKZGetEntityFromAddress(playerAddr);
+}
+
+/**
+ * Gets the nearest point in the oriented bounding box of an entity to a point.
+ *
+ * @param entity Entity index.
+ * @param origin Point's origin.
+ * @param result Result point.
+ */
+stock void CalcNearestPoint(int entity, float origin[3], float result[3])
+{
+ float entOrigin[3], entMins[3], entMaxs[3], trueMins[3], trueMaxs[3];
+ GetEntPropVector(entity, Prop_Send, "m_vecOrigin", entOrigin);
+ GetEntPropVector(entity, Prop_Send, "m_vecMaxs", entMaxs);
+ GetEntPropVector(entity, Prop_Send, "m_vecMins", entMins);
+
+ AddVectors(entOrigin, entMins, trueMins);
+ AddVectors(entOrigin, entMaxs, trueMaxs);
+
+ for (int i = 0; i < 3; i++)
+ {
+ result[i] = FloatClamp(origin[i], trueMins[i], trueMaxs[i]);
+ }
+}
+
+/**
+ * Get the shortest distance from P to the (infinite) line through vLineA and vLineB.
+ *
+ * @param P Point's origin.
+ * @param vLineA Origin of the first point of the line.
+ * @param vLineB Origin of the first point of the line.
+ * @return The shortest distance from the point to the line.
+ */
+stock float CalcDistanceToLine(float P[3], float vLineA[3], float vLineB[3])
+{
+ float vClosest[3];
+ float vDir[3];
+ float t;
+ float delta[3];
+ SubtractVectors(vLineB, vLineA, vDir);
+ float div = GetVectorDotProduct(vDir, vDir);
+ if (div < EPSILON)
+ {
+ t = 0.0;
+ }
+ else
+ {
+ t = (GetVectorDotProduct(vDir, P) - GetVectorDotProduct(vDir, vLineA)) / div;
+ }
+ for (int i = 0; i < 3; i++)
+ {
+ vClosest[i] = vLineA[i] + vDir[i]*t;
+ }
+ SubtractVectors(P, vClosest, delta);
+ return GetVectorLength(delta);
+}
+
+/**
+ * Gets the ideal amount of time the text should be held for HUD messages.
+ *
+ * The message buffer is only 16 slots long, and it is shared between 6 channels maximum.
+ * Assuming a message is sent every game frame, each channel used should be only taking around 2.5 slots on average.
+ * This also assumes all channels are used equally (so no other plugin taking all the channel buffer for itself).
+ * We want to use as much of the message buffer as possible to take into account latency variances.
+ *
+ * @param interval HUD message update interval, in tick intervals.
+ * @return How long the text should be held for.
+ */
+stock float GetTextHoldTime(int interval)
+{
+ return 3 * interval * GetTickInterval();
+}