diff options
| author | navewindre <nw@moneybot.cc> | 2023-12-04 18:06:10 +0100 |
|---|---|---|
| committer | navewindre <nw@moneybot.cc> | 2023-12-04 18:06:10 +0100 |
| commit | aef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch) | |
| tree | 43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/include/gokz.inc | |
| parent | 38f1140c11724da05a23a10385061200b907cf6e (diff) | |
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/include/gokz.inc')
| -rw-r--r-- | sourcemod/scripting/include/gokz.inc | 1097 |
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(); +} |
