summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/include
diff options
context:
space:
mode:
Diffstat (limited to 'sourcemod/scripting/include')
-rw-r--r--sourcemod/scripting/include/GlobalAPI.inc822
-rw-r--r--sourcemod/scripting/include/GlobalAPI/iterable.inc55
-rw-r--r--sourcemod/scripting/include/GlobalAPI/request.inc185
-rw-r--r--sourcemod/scripting/include/GlobalAPI/requestdata.inc534
-rw-r--r--sourcemod/scripting/include/GlobalAPI/responses.inc575
-rw-r--r--sourcemod/scripting/include/GlobalAPI/stocks.inc67
-rw-r--r--sourcemod/scripting/include/SteamWorks.inc413
-rw-r--r--sourcemod/scripting/include/autoexecconfig.inc765
-rw-r--r--sourcemod/scripting/include/colors.inc945
-rw-r--r--sourcemod/scripting/include/distbugfix.inc261
-rw-r--r--sourcemod/scripting/include/gamechaos.inc20
-rw-r--r--sourcemod/scripting/include/gamechaos/arrays.inc52
-rw-r--r--sourcemod/scripting/include/gamechaos/client.inc300
-rw-r--r--sourcemod/scripting/include/gamechaos/debug.inc19
-rw-r--r--sourcemod/scripting/include/gamechaos/isvalidclient.inc16
-rw-r--r--sourcemod/scripting/include/gamechaos/kreedzclimbing.inc226
-rw-r--r--sourcemod/scripting/include/gamechaos/maths.inc362
-rw-r--r--sourcemod/scripting/include/gamechaos/misc.inc245
-rw-r--r--sourcemod/scripting/include/gamechaos/strings.inc367
-rw-r--r--sourcemod/scripting/include/gamechaos/tempents.inc62
-rw-r--r--sourcemod/scripting/include/gamechaos/tracing.inc242
-rw-r--r--sourcemod/scripting/include/gamechaos/vectors.inc66
-rw-r--r--sourcemod/scripting/include/glib/addressutils.inc54
-rw-r--r--sourcemod/scripting/include/glib/assertutils.inc61
-rw-r--r--sourcemod/scripting/include/glib/memutils.inc232
-rw-r--r--sourcemod/scripting/include/gokz.inc1097
-rw-r--r--sourcemod/scripting/include/gokz/anticheat.inc168
-rw-r--r--sourcemod/scripting/include/gokz/chat.inc45
-rw-r--r--sourcemod/scripting/include/gokz/core.inc1920
-rw-r--r--sourcemod/scripting/include/gokz/global.inc317
-rw-r--r--sourcemod/scripting/include/gokz/hud.inc468
-rw-r--r--sourcemod/scripting/include/gokz/jumpbeam.inc148
-rw-r--r--sourcemod/scripting/include/gokz/jumpstats.inc442
-rw-r--r--sourcemod/scripting/include/gokz/kzplayer.inc584
-rw-r--r--sourcemod/scripting/include/gokz/localdb.inc353
-rw-r--r--sourcemod/scripting/include/gokz/localranks.inc176
-rw-r--r--sourcemod/scripting/include/gokz/momsurffix.inc23
-rw-r--r--sourcemod/scripting/include/gokz/paint.inc114
-rw-r--r--sourcemod/scripting/include/gokz/pistol.inc93
-rw-r--r--sourcemod/scripting/include/gokz/profile.inc291
-rw-r--r--sourcemod/scripting/include/gokz/quiet.inc205
-rw-r--r--sourcemod/scripting/include/gokz/racing.inc189
-rw-r--r--sourcemod/scripting/include/gokz/replays.inc275
-rw-r--r--sourcemod/scripting/include/gokz/slayonend.inc43
-rw-r--r--sourcemod/scripting/include/gokz/tips.inc59
-rw-r--r--sourcemod/scripting/include/gokz/tpanglefix.inc40
-rw-r--r--sourcemod/scripting/include/gokz/version.inc12
-rw-r--r--sourcemod/scripting/include/json.inc473
-rw-r--r--sourcemod/scripting/include/json/decode_helpers.inc312
-rw-r--r--sourcemod/scripting/include/json/definitions.inc103
-rw-r--r--sourcemod/scripting/include/json/encode_helpers.inc164
-rw-r--r--sourcemod/scripting/include/json/helpers/decode.inc502
-rw-r--r--sourcemod/scripting/include/json/helpers/encode.inc200
-rw-r--r--sourcemod/scripting/include/json/helpers/string.inc133
-rw-r--r--sourcemod/scripting/include/json/object.inc1014
-rw-r--r--sourcemod/scripting/include/json/string_helpers.inc77
-rw-r--r--sourcemod/scripting/include/movement.inc530
-rw-r--r--sourcemod/scripting/include/movementapi.inc663
-rw-r--r--sourcemod/scripting/include/smjansson.inc1328
-rw-r--r--sourcemod/scripting/include/sourcebanspp.inc106
-rw-r--r--sourcemod/scripting/include/sourcemod-colors.inc921
-rw-r--r--sourcemod/scripting/include/updater.inc97
62 files changed, 20631 insertions, 0 deletions
diff --git a/sourcemod/scripting/include/GlobalAPI.inc b/sourcemod/scripting/include/GlobalAPI.inc
new file mode 100644
index 0000000..8813645
--- /dev/null
+++ b/sourcemod/scripting/include/GlobalAPI.inc
@@ -0,0 +1,822 @@
+// ================== DOUBLE INCLUDE ========================= //
+
+#if defined _GlobalAPI_included_
+#endinput
+#endif
+#define _GlobalAPI_included_
+
+// ======================= DEFINITIONS ======================= //
+
+#define DEFAULT_DATA 0
+#define DEFAULT_INT -1
+#define DEFAULT_STRING ""
+#define DEFAULT_FLOAT -1.0
+#define DEFAULT_BOOL view_as<bool>(-1)
+
+#define GlobalAPI_Plugin_Version "2.0.0"
+#define GlobalAPI_Plugin_Desc "Plugin helper for GlobalAPI Production & Staging"
+#define GlobalAPI_Plugin_Url "https://bitbucket.org/kztimerglobalteam/GlobalAPI-SMPlugin"
+#define GlobalAPI_Plugin_NameVersion "GlobalAPI Plugin " ... GlobalAPI_Plugin_Version
+
+#define GlobalAPI_Backend_Version "v2.0"
+#define GlobalAPI_Backend_Staging_Version "v2.0"
+#define GlobalAPI_BaseUrl "https://kztimerglobal.com/api/" ... GlobalAPI_Backend_Version
+#define GlobalAPI_Staging_BaseUrl "https://globalapi.ruto.sh/api/" ... GlobalAPI_Backend_Staging_Version
+
+#define GlobalAPI_Max_BaseUrl_Length 128
+#define GlobalAPI_Max_QueryParam_Num 20
+#define GlobalAPI_Max_QueryParam_Length 64
+#define GlobalAPI_Max_QueryParams_Length (GlobalAPI_Max_QueryParam_Num * GlobalAPI_Max_QueryParam_Length)
+#define GlobalAPI_Max_QueryUrl_Length (GlobalAPI_Max_QueryParams_Length + GlobalAPI_Max_BaseUrl_Length)
+#define GlobalAPI_Max_QueryParam_Array_Length 64
+
+#define GlobalAPI_Max_APIKey_Length 128
+#define GlobalAPI_Max_PluginName_Length 64
+#define GlobalAPI_Max_PluginVersion_Length 32
+#define GlobalAPI_Data_File_Extension "GAPI"
+
+// ======================= INCLUDES ========================== //
+
+#include <GlobalAPI/requestdata>
+#include <GlobalAPI/responses>
+#include <GlobalAPI/stocks>
+
+// ======================= ENUMS ============================= //
+
+/**
+ * Defines what request method is used on requests
+ */
+enum
+{
+ GlobalAPIRequestType_GET = 0, /**< Request uses GET HTTP method */
+ GlobalAPIRequestType_POST /**< Request uses POST HTTP method */
+};
+
+/**
+ * Defines what accept type is used on requests
+ */
+enum
+{
+ GlobalAPIRequestAcceptType_JSON = 0, /**< Request uses application/json HTTP accept type */
+ GlobalAPIRequestAcceptType_OctetStream /**< Request uses application/octet-stream HTTP accept type */
+};
+
+/**
+ * Defines what content type is used on requests
+ */
+enum
+{
+ GlobalAPIRequestContentType_JSON = 0, /**< Request uses application/json HTTP content type */
+ GlobalAPIRequestContentType_OctetStream /**< Request uses application/octet-stream HTTP content type */
+};
+
+// ======================= TYPEDEFS ========================== //
+
+/*
+ Function types when API call finishes
+*/
+typeset OnAPICallFinished
+{
+ /**
+ * Called when an API call has finished
+ *
+ * @param hResponse JSON_Object handle to the response
+ * @param hData GlobalAPIRequestData handle for the request
+ * @noreturn
+ */
+ function void(JSON_Object hResponse, GlobalAPIRequestData hData);
+
+ /**
+ * Called when an API call has finished
+ *
+ * @param hResponse JSON_Object handle to the response
+ * @param hData GlobalAPIRequestData handle for the request
+ * @param data Optional data that was passed
+ * @noreturn
+ */
+ function void(JSON_Object hResponse, GlobalAPIRequestData hData, any data);
+};
+
+// ======================= FORWARDS ========================== //
+
+/**
+ * Called when GlobalAPI plugin is initialized,
+ * this means API Key is loaded and all the cvars are loaded
+ *
+ * @noreturn
+ */
+forward void GlobalAPI_OnInitialized();
+
+/**
+ * Called when GlobalAPI plugin has failed a request
+ *
+ * @param request Handle to the request failed
+ * @param hData Handle to request's GlobalAPIRequestData
+ * @noreturn
+ */
+forward void GlobalAPI_OnRequestFailed(Handle request, GlobalAPIRequestData hData);
+
+/**
+ * Called when GlobalAPI plugin has started a request
+ *
+ * @param request Handle to the request started
+ * @param hData Handle to request's GlobalAPIRequestData
+ * @noreturn
+ */
+forward void GlobalAPI_OnRequestStarted(Handle request, GlobalAPIRequestData hData);
+
+/**
+ * Called when GlobalAPI plugin has finished a request
+ *
+ * @param request Handle to the request finished
+ * @param hData Handle to request's GlobalAPIRequestData
+ * @noreturn
+ */
+forward void GlobalAPI_OnRequestFinished(Handle request, GlobalAPIRequestData hData);
+
+// ======================= NATIVES =========================== //
+
+/**
+ * Gets a boolean of whether GlobalAPI plugin is initialized.
+ *
+ * @note See GlobalAPI_OnInitialized for the event version.
+ * @return Whether GlobalAPI plugin is initialized.
+ */
+native bool GlobalAPI_IsInit();
+
+/**
+ * Gets the API Key used by GlobalAPI plugin
+ *
+ * @param buffer Buffer to store result in
+ * @param maxlength Max length of the buffer
+ * @noreturn
+ */
+native void GlobalAPI_GetAPIKey(char[] buffer, int maxlength);
+
+/**
+ * Gets whether GlobalAPI is using an API Key
+ *
+ * @note This does not mean the API Key is valid!
+ * @return Whether API Key is used by GlobalAPI plugin
+ */
+native bool GlobalAPI_HasAPIKey();
+
+/**
+ * Gets whether GlobalAPI is using the staging endpoint
+ *
+ * @note It is not safe to call this before GlobalAPI_OnInitialized!
+ * @return Whether staging endpoint is used by GlobalAPI plugin
+ */
+native bool GlobalAPI_IsStaging();
+
+/**
+ * Gets whether GlobalAPI is in debug mode
+ *
+ * @note It is not safe to call this before GlobalAPI_OnInitialized!
+ * @return Whether GlobalAPI plugin is in debug mode
+ */
+native bool GlobalAPI_IsDebugging();
+
+/**
+ * Sends a request in GlobalAPI plugin format
+ *
+ * @param hData Handle to GlobalAPIRequestData
+ * @return Whether the request was sent successfully
+ */
+native bool GlobalAPI_SendRequest(GlobalAPIRequestData hData);
+
+/**
+ * Sends a debug message to GlobalAPI plugin logs if debugging is enabled
+ *
+ * @param message Formatting rules
+ * @param ... Variable number of format parameters
+ * @note This is not safe to use before convars have loaded
+ * @return Whether the message was logged
+ */
+native bool GlobalAPI_DebugMessage(const char[] message, any ...);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/auth/status
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetAuthStatus(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/bans
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param banTypes Ban types to query
+ * @param banTypesList -Unsupported at the moment-
+ * @param isExpired Whether to query for isExpired or not
+ * @param ipAddress IP address to query
+ * @param steamId64 SteamID64 to query
+ * @param steamId SteamID2 to query
+ * @param notesContain Notes to query
+ * @param statsContain Stats to query
+ * @param serverId Server ID to query
+ * @param createdSince Created since date to query
+ * @param updatedSince Updated since date to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetBans(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] banTypes = DEFAULT_STRING,
+ const char[] banTypesList = DEFAULT_STRING, bool isExpired = DEFAULT_BOOL, const char[] ipAddress = DEFAULT_STRING,
+ const char[] steamId64 = DEFAULT_STRING, const char[] steamId = DEFAULT_STRING, const char[] notesContain = DEFAULT_STRING,
+ const char[] statsContain = DEFAULT_STRING, int serverId = DEFAULT_INT, const char[] createdSince = DEFAULT_STRING,
+ const char[] updatedSince = DEFAULT_STRING, int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a POST HTTP Request to /api/{version}/bans
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 of the user
+ * @param banType Type of the ban
+ * @param stats Stats of the ban
+ * @param notes Notes of the ban
+ * @param ipAddress IP address of the user
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_CreateBan(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ const char[] steamId, const char[] banType, const char[] stats,
+ const char[] notes, const char[] ipAddress);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/jumpstats
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param id Id to query
+ * @param serverId Server id to query
+ * @param steamId64 SteamID64 to query
+ * @param steamId SteamID2 to query
+ * @param jumpType Jump type to query
+ * @param steamId64List -Unsupported at the moment-
+ * @param jumpTypeList -Unsupported at the moment-
+ * @param greaterThanDistance Greater than distance to query
+ * @param lessThanDistance Less than distance to query
+ * @param isMsl Whether to query for isMsl or not
+ * @param isCrouchBind Whether to query for isCrouchBind or not
+ * @param isForwardBind Whether to query for isForwardBind or not
+ * @param isCrouchBoost Whether to query for isCrouchBoost or not
+ * @param updatedById Updated by id to query
+ * @param createdSince Created since date to query
+ * @param updatedSince Updated since date to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetJumpstats(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, int id = DEFAULT_INT,
+ int serverId = DEFAULT_INT, const char[] steamId64 = DEFAULT_STRING, const char[] steamId = DEFAULT_STRING,
+ const char[] jumpType = DEFAULT_STRING, const char[] steamId64List = DEFAULT_STRING,
+ const char[] jumpTypeList = DEFAULT_STRING, float greaterThanDistance = DEFAULT_FLOAT,
+ float lessThanDistance = DEFAULT_FLOAT, bool isMsl = DEFAULT_BOOL, bool isCrouchBind = DEFAULT_BOOL,
+ bool isForwardBind = DEFAULT_BOOL, bool isCrouchBoost = DEFAULT_BOOL, int updatedById = DEFAULT_INT,
+ const char[] createdSince = DEFAULT_STRING, const char[] updatedSince = DEFAULT_STRING,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a POST HTTP Request to /api/{version}/jumpstats
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 of the user
+ * @param jumpType Type of the jump
+ * @param distance Distance of the jump
+ * @param jumpJsonInfo Data of the jump
+ * @param tickRate Tickrate of the server
+ * @param mslCount Msl count of the jump
+ * @param isCrouchBind Whether crouch bind was used
+ * @param isForwardBind Whether forward bind was used
+ * @param isCrouchBoost Whether crouch boost was used
+ * @param strafeCount Strafe count of the jump
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_CreateJumpstat(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] steamId,
+ int jumpType, float distance, const char[] jumpJsonInfo, int tickRate, int mslCount,
+ bool isCrouchBind, bool isForwardBind, bool isCrouchBoost, int strafeCount);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/jumpstats/{jump_type}/top
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param jumpType Jump type to query
+ * @param id Id to query
+ * @param serverId Server Id to query
+ * @param steamId64 SteamID64 to query
+ * @param steamId SteamID2 to query
+ * @param steamId64List -Unsupported at the moment-
+ * @param jumpTypeList -Unsupported at the moment-
+ * @param greaterThanDistance Greater than distance to query
+ * @param lessThanDistance Less than distance to query
+ * @param isMsl Whether to query for isMsl or not
+ * @param isCrouchBind Whether to query for isCrouchBind or not
+ * @param isForwardBind Whether to query for isForwardBind or not
+ * @param isCrouchBoost Whether to query for isCrouchBoost or not
+ * @param updatedById Updated by id to query
+ * @param createdSince Created since date to query
+ * @param updatedSince Updated since date to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetJumpstatTop(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] jumpType,
+ int id = DEFAULT_INT, int serverId = DEFAULT_INT, const char[] steamId64 = DEFAULT_STRING,
+ const char[] steamId = DEFAULT_STRING, const char[] steamId64List = DEFAULT_STRING,
+ const char[] jumpTypeList = DEFAULT_STRING, float greaterThanDistance = DEFAULT_FLOAT,
+ float lessThanDistance = DEFAULT_FLOAT, bool isMsl = DEFAULT_BOOL, bool isCrouchBind = DEFAULT_BOOL,
+ bool isForwardBind = DEFAULT_BOOL, bool isCrouchBoost = DEFAULT_BOOL, int updatedById = DEFAULT_INT,
+ const char[] createdSince = DEFAULT_STRING, const char[] updatedSince = DEFAULT_STRING,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/jumpstats/{jump_type}/top30
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param jumpType Jump type to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetJumpstatTop30(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] jumpType);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/maps
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param name Map name to query
+ * @param largerThanFilesize Larger than filesize to query
+ * @param smallerThanFilesize Smaller than filesize to query
+ * @param isValidated Whether to query for isValidated or not
+ * @param difficulty Map difficulty to query
+ * @param createdSince Created since date to query
+ * @param updatedSince Updated since date to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetMaps(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] name = DEFAULT_STRING,
+ int largerThanFilesize = DEFAULT_INT, int smallerThanFilesize = DEFAULT_INT, bool isValidated = DEFAULT_BOOL,
+ int difficulty = DEFAULT_INT, const char[] createdSince = DEFAULT_STRING, const char[] updatedSince = DEFAULT_STRING,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/maps/{id}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param id Map id to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetMapById(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, int id);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/maps/name/{map_name}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param name Map name to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetMapByName(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] name);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/modes
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetModes(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/modes/id/{id}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param id Mode id to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetModeById(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, int id);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/modes/name/{mode_name}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param name Mode name to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetModeByName(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] name);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/players
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 to query
+ * @param isBanned Whether to query for isBanned or not
+ * @param totalRecords Total records to query
+ * @param ipAddress IP address to query
+ * @param steamId64List -Unsupported at the moment-
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetPlayers(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] steamId = DEFAULT_STRING,
+ bool isBanned = DEFAULT_BOOL, int totalRecords = DEFAULT_INT, const char[] ipAddress = DEFAULT_STRING,
+ const char[] steamId64List = DEFAULT_STRING);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/players/steamid/{steamid}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetPlayerBySteamId(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] steamId);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/players/steamid/{steamid}/ip/{ip}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 to query
+ * @param ipAddress IP address to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetPlayerBySteamIdAndIp(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ const char[] steamId, const char[] ipAddress);
+
+/**
+ * Starts a POST HTTP Request to /api/{version}/records
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 of the user
+ * @param mapId Map id of the record
+ * @param mode Mode of the record
+ * @param stage Stage of the record
+ * @param tickRate Tickrate of the server
+ * @param teleports Teleport count of the record
+ * @param time Elapsed time of the record
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_CreateRecord(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] steamId,
+ int mapId, const char[] mode, int stage, int tickRate, int teleports, float time);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/records/place/{id}
+ *
+ * @note This is deprecated!
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param id Id to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetRecordPlaceById(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, int id);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/records/top
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 to query
+ * @param steamId64 SteamID64 to query
+ * @param mapId Map id to query
+ * @param mapName Map name to query
+ * @param tickRate Tickrate to query
+ * @param stage Stage to query
+ * @param modes Mode(s) to query
+ * @param hasTeleports Whether to query for hasTeleports or not
+ * @param playerName Player name to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetRecordsTop(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ const char[] steamId = DEFAULT_STRING, const char[] steamId64 = DEFAULT_STRING, int mapId = DEFAULT_INT,
+ const char[] mapName = DEFAULT_STRING, int tickRate = DEFAULT_INT, int stage = DEFAULT_INT,
+ const char[] modes = DEFAULT_STRING, bool hasTeleports = DEFAULT_BOOL,
+ const char[] playerName = DEFAULT_STRING, int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/records/top/recent
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param steamId SteamID2 to query
+ * @param steamId64 SteamID64 to query
+ * @param mapId Map id to query
+ * @param mapName Map name to query
+ * @param tickRate Tickrate to query
+ * @param stage Stage to query
+ * @param modes Mode(s) to query
+ * @param topAtLeast Place top at least to query
+ * @param topOverallAtLeast Place top overall at least to query
+ * @param hasTeleports Whether to query for hasTeleports or not
+ * @param createdSince Created since date to query
+ * @param playerName Player name to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetRecordsTopRecent(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ const char[] steamId = DEFAULT_STRING, const char[] steamId64 = DEFAULT_STRING,
+ int mapId = DEFAULT_INT, const char[] mapName = DEFAULT_STRING,
+ int tickRate = DEFAULT_INT, int stage = DEFAULT_INT,
+ const char[] modes = DEFAULT_STRING, int topAtLeast = DEFAULT_INT,
+ int topOverallAtLeast = DEFAULT_INT, bool hasTeleports = DEFAULT_BOOL,
+ const char[] createdSince = DEFAULT_STRING, const char[] playerName = DEFAULT_STRING,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/records/top/world_records
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param ids Array of ids to query
+ * @param idsLength Length of the ids array
+ * @param mapIds Array of map ids to query
+ * @param mapIdsLength Length of the map ids array
+ * @param stages Array of stages to query
+ * @param stagesLength Length of the stages array
+ * @param modeIds Array of mode ids to query
+ * @param modeIdsLength Length of the mode ids array
+ * @param tickRates Array of tickrates to query
+ * @param tickRatesLength Length of the tickrates array
+ * @param hasTeleports Whether to query for hasTeleports or not
+ * @param mapTag Map tags to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetRecordsTopWorldRecords(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ int[] ids = {}, int idsLength = DEFAULT_INT,
+ int[] mapIds = {}, int mapIdsLength = DEFAULT_INT,
+ int[] stages = {}, int stagesLength = DEFAULT_INT,
+ int[] modeIds = {}, int modeIdsLength = DEFAULT_INT,
+ int[] tickRates = {}, int tickRatesLength = DEFAULT_INT,
+ bool hasTeleports = DEFAULT_BOOL, char[] mapTag = DEFAULT_STRING,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/servers
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param id Id to query
+ * @param port Port to query
+ * @param ip IP address to query
+ * @param name Server name to query
+ * @param ownerSteamId64 Owner's steamid64 to query
+ * @param approvalStatus Approval status to query
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetServers(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ int id = DEFAULT_INT, int port = DEFAULT_INT, const char[] ip = DEFAULT_STRING,
+ const char[] name = DEFAULT_STRING, const char[] ownerSteamId64 = DEFAULT_STRING,
+ int approvalStatus = DEFAULT_INT, int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/servers/{id}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param id Id to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetServerById(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, int id);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/servers/name/{server_name}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param serverName Server name to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetServersByName(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, const char[] serverName);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/player_ranks
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param pointsGreaterThan Points greater than to query
+ * @param averageGreaterThan Average greater than to query
+ * @param ratingGreaterThan Rating greater than to query
+ * @param finishesGreaterThan Finishes greater than to query
+ * @param steamId64List Comma-separated stirng of steamid64s to query
+ * @param recordFilterIds Array of record filter ids to query
+ * @param recordFilterIdsLength Length of the record filter ids array
+ * @param mapIds Array of map ids to query
+ * @param mapIdsLength Length of the map ids array
+ * @param stages Array of stages to query
+ * @param stagesLength Length of the stages array
+ * @param modeIds Array of mode ids to query
+ * @param modeIdsLength Length of the mode ids array
+ * @param tickRates Array of tickrates to query
+ * @param tickRatesLength Length of the tickrates array
+ * @param hasTeleports Whether to query for hasTeleports or not
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetPlayerRanks(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ int pointsGreaterThan = DEFAULT_INT, float averageGreaterThan = DEFAULT_FLOAT,
+ float ratingGreaterThan = DEFAULT_FLOAT, int finishesGreaterThan = DEFAULT_INT,
+ const char[] steamId64List = DEFAULT_STRING,
+ int[] recordFilterIds = {}, int recordFilterIdsLength = DEFAULT_INT,
+ int[] mapIds = {}, int mapIdsLength = DEFAULT_INT,
+ int[] stages = {}, int stagesLength = DEFAULT_INT,
+ int[] modeIds = {}, int modeIdsLength = DEFAULT_INT,
+ int[] tickRates = {}, int tickRatesLength = DEFAULT_INT,
+ bool hasTeleports = DEFAULT_BOOL, int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/record_filters
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param ids Array of ids to query
+ * @param idsLength Length of the ids array
+ * @param mapIds Array of map ids to query
+ * @param mapIdsLength Length of the map ids array
+ * @param stages Array of stages to query
+ * @param stagesLength Length of the stages array
+ * @param modeIds Array of mode ids to query
+ * @param modeIdsLength Length of the mode ids array
+ * @param tickRates Array of tickrates to query
+ * @param tickRatesLength Length of the tickrates array
+ * @param hasTeleports Whether to query for hasTeleports or not
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetRecordFilters(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ int[] ids = {}, int idsLength = DEFAULT_INT,
+ int[] mapIds = {}, int mapIdsLength = DEFAULT_INT,
+ int[] stages = {}, int stagesLength = DEFAULT_INT,
+ int[] modeIds = {}, int modeIdsLength = DEFAULT_INT,
+ int[] tickRates = {}, int tickRatesLength = DEFAULT_INT,
+ bool hasTeleports = DEFAULT_BOOL, bool isOverall = DEFAULT_BOOL,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/record_filters/distributions
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param ids Array of ids to query
+ * @param idsLength Length of the ids array
+ * @param mapIds Array of map ids to query
+ * @param mapIdsLength Length of the map ids array
+ * @param stages Array of stages to query
+ * @param stagesLength Length of the stages array
+ * @param modeIds Array of mode ids to query
+ * @param modeIdsLength Length of the mode ids array
+ * @param tickRates Array of tickrates to query
+ * @param tickRatesLength Length of the tickrates array
+ * @param hasTeleports Whether to query for hasTeleports or not
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetRecordFilterDistributions(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ int[] ids = {}, int idsLength = DEFAULT_INT,
+ int[] mapIds = {}, int mapIdsLength = DEFAULT_INT,
+ int[] stages = {}, int stagesLength = DEFAULT_INT,
+ int[] modeIds = {}, int modeIdsLength = DEFAULT_INT,
+ int[] tickRates = {}, int tickRatesLength = DEFAULT_INT,
+ bool hasTeleports = DEFAULT_BOOL, bool isOverall = DEFAULT_BOOL,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/records/replay/list
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param offset Offset of the dataset to query
+ * @param limit Amount of items returned for the query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetReplayList(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ int offset = DEFAULT_INT, int limit = DEFAULT_INT);
+
+/**
+ * Starts a GET HTTP Request to /api/{version}/records/{recordId}/replay
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param recordId Record id to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetReplayByRecordId(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, int recordId);
+/**
+ * Starts a GET HTTP Request to /api/{version}/records/replay/{replayId}
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param replayId Replay id to query
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_GetReplayByReplayId(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA, int replayId);
+
+/**
+ * Starts a POST HTTP Request to /api/{version}/records/{recordId}/replay
+ *
+ * @param callback Callback when request has finished
+ * @param data Optional data to pass
+ * @param recordId Id of the record
+ * @param replayFile Path to the replay file
+ * @return Whether request was successfully sent
+ */
+native bool GlobalAPI_CreateReplayForRecordId(OnAPICallFinished callback = INVALID_FUNCTION, any data = DEFAULT_DATA,
+ int recordId, const char[] replayFile);
+
+// ======================= PLUGIN INFO ======================= //
+
+public SharedPlugin __pl_GlobalAPI =
+{
+ name = "GlobalAPI",
+ file = "GlobalAPI.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_GlobalAPI_SetNTVOptional()
+{
+ // Plugin
+ MarkNativeAsOptional("GlobalAPI_IsInit");
+ MarkNativeAsOptional("GlobalAPI_GetAPIKey");
+ MarkNativeAsOptional("GlobalAPI_HasAPIKey");
+ MarkNativeAsOptional("GlobalAPI_IsStaging");
+ MarkNativeAsOptional("GlobalAPI_IsDebugging");
+ MarkNativeAsOptional("GlobalAPI_SendRequest");
+ MarkNativeAsOptional("GlobalAPI_DebugMessage");
+
+ // Auth
+ MarkNativeAsOptional("GlobalAPI_GetAuthStatus");
+
+ // Bans
+ MarkNativeAsOptional("GlobalAPI_GetBans");
+ MarkNativeAsOptional("GlobalAPI_CreateBan");
+
+ // Jumpstats
+ MarkNativeAsOptional("GlobalAPI_GetJumpstats");
+ MarkNativeAsOptional("GlobalAPI_GetJumpstatTop");
+ MarkNativeAsOptional("GlobalAPI_GetJumpstatTop30");
+
+ // Maps
+ MarkNativeAsOptional("GlobalAPI_GetMaps");
+ MarkNativeAsOptional("GlobalAPI_GetMapById");
+ MarkNativeAsOptional("GlobalAPI_GetMapByName");
+
+ // Modes
+ MarkNativeAsOptional("GlobalAPI_GetModes");
+ MarkNativeAsOptional("GlobalAPI_GetModeById");
+ MarkNativeAsOptional("GlobalAPI_GetModeByName");
+
+ // Players
+ MarkNativeAsOptional("GlobalAPI_GetPlayers");
+ MarkNativeAsOptional("GlobalAPI_GetPlayerBySteamId");
+ MarkNativeAsOptional("GlobalAPI_GetPlayerBySteamIdAndIp");
+
+ // Records
+ MarkNativeAsOptional("GlobalAPI_CreateRecord");
+ MarkNativeAsOptional("GlobalAPI_GetRecordPlaceById");
+ MarkNativeAsOptional("GlobalAPI_GetRecordsTop");
+ MarkNativeAsOptional("GlobalAPI_GetRecordsTopRecent");
+ MarkNativeAsOptional("GlobalAPI_GetRecordsTopWorldRecords");
+
+ // Servers
+ MarkNativeAsOptional("GlobalAPI_GetServers");
+ MarkNativeAsOptional("GlobalAPI_GetServerById");
+ MarkNativeAsOptional("GlobalAPI_GetServersByName");
+
+ // Ranks
+ MarkNativeAsOptional("GlobalAPI_GetPlayerRanks");
+
+ // Record Filters
+ MarkNativeAsOptional("GlobalAPI_GetRecordFilters");
+ MarkNativeAsOptional("GlobalAPI_GetRecordFilterDistributions");
+
+ // Replays
+ MarkNativeAsOptional("GlobalAPI_GetReplayList");
+ MarkNativeAsOptional("GlobalAPI_GetReplayByRecordId");
+ MarkNativeAsOptional("GlobalAPI_GetReplayByReplayId");
+ MarkNativeAsOptional("GlobalAPI_CreateReplayForRecordId");
+}
+#endif
diff --git a/sourcemod/scripting/include/GlobalAPI/iterable.inc b/sourcemod/scripting/include/GlobalAPI/iterable.inc
new file mode 100644
index 0000000..585873b
--- /dev/null
+++ b/sourcemod/scripting/include/GlobalAPI/iterable.inc
@@ -0,0 +1,55 @@
+// ================== DOUBLE INCLUDE ========================= //
+
+#if defined _GlobalAPI_Iterable_included_
+#endinput
+#endif
+#define _GlobalAPI_Iterable_included_
+
+// =========================================================== //
+
+#include <json>
+
+// =========================================================== //
+
+/*
+ Helper methodmap for JSON_Object arrays
+*/
+methodmap APIIterable < JSON_Object
+{
+ /**
+ * Creates a new APIIterable
+ *
+ * @param hItems JSON_Object array handle
+ * @return A new APIIterable handle
+ */
+ public APIIterable(JSON_Object hItems)
+ {
+ if (hItems.HasKey("result"))
+ {
+ return view_as<APIIterable>(hItems.GetObject("result"));
+ }
+ return view_as<APIIterable>(hItems);
+ }
+
+ /*
+ Gets count of the items in the array
+ */
+ property int Count
+ {
+ public get() { return this.Length; }
+ }
+
+ /**
+ * Gets an object from the array by index
+ *
+ * @note This is an alias to GetObjectIndexed
+ * @param index Index of the object we want to retrieve
+ * @return JSON_Object handle to the object retrieved
+ */
+ public JSON_Object GetById(int index)
+ {
+ return this.GetObjectIndexed(index);
+ }
+}
+
+// =========================================================== // \ No newline at end of file
diff --git a/sourcemod/scripting/include/GlobalAPI/request.inc b/sourcemod/scripting/include/GlobalAPI/request.inc
new file mode 100644
index 0000000..e683125
--- /dev/null
+++ b/sourcemod/scripting/include/GlobalAPI/request.inc
@@ -0,0 +1,185 @@
+// ================== DOUBLE INCLUDE ========================= //
+
+#if defined _GlobalAPI_Request_included_
+#endinput
+#endif
+#define _GlobalAPI_Request_included_
+
+// =========================================================== //
+
+static char gC_acceptTypePhrases[][] =
+{
+ "application/json",
+ "application/octet-stream"
+};
+
+static char gC_contentTypePhrases[][] =
+{
+ "application/json",
+ "application/octet-stream"
+};
+
+// =========================================================== //
+
+methodmap GlobalAPIRequest < Handle
+{
+ /**
+ * Creates a new GlobalAPIRequest
+ *
+ * @param url URL of the request
+ * @param method SteamWorks k_ETTPMethod of the request
+ * @return A new GlobalAPIRequest handle
+ */
+ public GlobalAPIRequest(char[] url, EHTTPMethod method)
+ {
+ Handle request = SteamWorks_CreateHTTPRequest(method, url);
+ return view_as<GlobalAPIRequest>(request);
+ }
+
+ /**
+ * Sets request timeout
+ *
+ * @param seconds Timeout in seconds
+ * @return Whether the operation was successful
+ */
+ public bool SetTimeout(int seconds)
+ {
+ return SteamWorks_SetHTTPRequestAbsoluteTimeoutMS(this, seconds * 1000);
+ }
+
+ /**
+ * Sets request body
+ *
+ * @param hData GlobalAPIRequestData containing contentType
+ * @param body Request body to set
+ * @param maxlength Maxlength of the body
+ * @return Whether the operation was successful
+ */
+ public bool SetBody(GlobalAPIRequestData hData, char[] body, int maxlength)
+ {
+ return SteamWorks_SetHTTPRequestRawPostBody(this, gC_contentTypePhrases[hData.ContentType], body, maxlength);
+ }
+
+ /**
+ * Sets request body from a file
+ *
+ * @param hData GlobalAPIRequestData containing contentType
+ * @return Whether the operation was successful
+ */
+ public bool SetBodyFromFile(GlobalAPIRequestData hData, char[] file)
+ {
+ return SteamWorks_SetHTTPRequestRawPostBodyFromFile(this, gC_contentTypePhrases[hData.ContentType], file);
+ }
+
+ /**
+ * Sets a request context value
+ *
+ * @param data Any data to pass
+ * @return Whether the operation was successful
+ */
+ public bool SetData(any data1, any data2 = 0)
+ {
+ return SteamWorks_SetHTTPRequestContextValue(this, data1, data2);
+ }
+
+ /**
+ * Sets predefined HTTP callbacks
+ *
+ * @note Predefined values respectively:
+ * @note Global_HTTP_Completed, Global_HTTP_Headers and Global_HTTP_DataReceived
+ * @noreturn
+ */
+ public void SetCallbacks()
+ {
+ SteamWorks_SetHTTPCallbacks(this, Global_HTTP_Completed, Global_HTTP_Headers, Global_HTTP_DataReceived);
+ }
+
+ /**
+ * Sets "Accept" header
+ *
+ * @param hData GlobalAPIRequestData containing acceptType
+ * @return Whether the operation was successful
+ */
+ public bool SetAcceptHeaders(GlobalAPIRequestData hData)
+ {
+ return SteamWorks_SetHTTPRequestHeaderValue(this, "Accept", gC_acceptTypePhrases[hData.AcceptType]);
+ }
+
+ /**
+ * Sets "powered by" header
+ *
+ * @return Whether the operation was successful
+ */
+ public bool SetPoweredByHeader()
+ {
+ return SteamWorks_SetHTTPRequestHeaderValue(this, "X-Powered-By", GlobalAPI_Plugin_NameVersion);
+ }
+
+ /**
+ * Sets authentication header
+ *
+ * @return Whether the operation was successful
+ */
+ public bool SetAuthenticationHeader(char[] apiKey)
+ {
+ return SteamWorks_SetHTTPRequestHeaderValue(this, "X-ApiKey", apiKey);
+ }
+
+ /**
+ * Sets envinroment headers (MetaMod & SourceMod)
+ *
+ * @param mmVersion MetaMod version string
+ * @param smVersion SourceMod version string
+ * @return Whether the operation was successful
+ */
+ public bool SetEnvironmentHeaders(char[] mmVersion, char[] smVersion)
+ {
+ return SteamWorks_SetHTTPRequestHeaderValue(this, "X-MetaMod-Version", mmVersion)
+ && SteamWorks_SetHTTPRequestHeaderValue(this, "X-SourceMod-Version", smVersion);
+ }
+
+ /**
+ * Sets content type header
+ *
+ * @param hData GlobalAPIRequestData containing contentType
+ * @return Whether the operation was successful
+ */
+ public bool SetContentTypeHeader(GlobalAPIRequestData hData)
+ {
+ return SteamWorks_SetHTTPRequestHeaderValue(this, "Content-Type", gC_contentTypePhrases[hData.ContentType]);
+ }
+
+ /**
+ * Sets request origin header
+ *
+ * @param hData GlobalAPIRequestData containing pluginName
+ * @return Whether the operation was successful
+ */
+ public bool SetRequestOriginHeader(GlobalAPIRequestData hData)
+ {
+ char pluginName[GlobalAPI_Max_PluginName_Length];
+ hData.GetString("pluginName", pluginName, sizeof(pluginName));
+
+ char pluginVersion[GlobalAPI_Max_PluginVersion_Length + 2];
+ hData.GetString("pluginVersion", pluginVersion, sizeof(pluginVersion));
+
+ char fullPluginDisplay[sizeof(pluginName) + sizeof(pluginVersion) + 6];
+ Format(fullPluginDisplay, sizeof(fullPluginDisplay), "%s (V.%s)", pluginName, pluginVersion);
+
+ return SteamWorks_SetHTTPRequestHeaderValue(this, "X-Request-Origin", fullPluginDisplay);
+ }
+
+ /**
+ * Sends our request with all available data
+ *
+ * @param hData GlobalAPIRequestData handle with all required keys
+ * @return Whether the operation was successful
+ */
+ public bool Send(GlobalAPIRequestData hData)
+ {
+ Call_Private_OnHTTPStart(this, hData);
+ return SteamWorks_SendHTTPRequest(this);
+ }
+}
+
+// =========================================================== // \ No newline at end of file
diff --git a/sourcemod/scripting/include/GlobalAPI/requestdata.inc b/sourcemod/scripting/include/GlobalAPI/requestdata.inc
new file mode 100644
index 0000000..f055ee8
--- /dev/null
+++ b/sourcemod/scripting/include/GlobalAPI/requestdata.inc
@@ -0,0 +1,534 @@
+// ================== DOUBLE INCLUDE ========================= //
+
+#if defined _GlobalAPI_RequestData_included_
+#endinput
+#endif
+#define _GlobalAPI_RequestData_included_
+
+// =========================================================== //
+
+#include <json>
+
+// =========================================================== //
+
+/*
+ Helper methodmap for wrapping data related to requests
+*/
+methodmap GlobalAPIRequestData < JSON_Object
+{
+ /**
+ * Creates a new GlobalAPIRequestData
+ *
+ * @note You can pass a plugin handle or name and/or version
+ * @note Plugin handle is always preferred
+ * @param plugin Handle to calling plugin
+ * @param pluginName Name of the calling plugin
+ * @param pluginVersion Version of the calling plugin
+ * @return A new GlobalAPIRequestData handle
+ */
+ public GlobalAPIRequestData(Handle plugin = null, char[] pluginName = "Unknown", char[] pluginVersion = "Unknown")
+ {
+ JSON_Object requestData = new JSON_Object();
+
+ if (plugin == null)
+ {
+ requestData.SetString("pluginName", pluginName);
+ requestData.SetString("pluginVersion", pluginVersion);
+ }
+ else
+ {
+ requestData.SetString("pluginName", GetPluginDisplayName(plugin));
+ requestData.SetString("pluginVersion", GetPluginVersion(plugin));
+ }
+
+ requestData.SetKeyHidden("pluginName", true);
+ requestData.SetKeyHidden("pluginVersion", true);
+
+ requestData.SetInt("acceptType", 0);
+ requestData.SetKeyHidden("acceptType", true);
+
+ requestData.SetInt("contentType", 0);
+ requestData.SetKeyHidden("contentType", true);
+
+ return view_as<GlobalAPIRequestData>(requestData);
+ }
+
+ /**
+ * Sets a key as default
+ *
+ * @note This sets them as "Handle" type
+ * @note - See GlobalAPI.inc for default values
+ * @param key Key to set as default
+ * @noreturn
+ */
+ public void SetDefault(char[] key)
+ {
+ this.SetHandle(key);
+ this.SetKeyHidden(key, true);
+ }
+
+ /**
+ * Sets url to the request data
+ *
+ * @param url Url to set
+ * @noreturn
+ */
+ public void AddUrl(char[] url)
+ {
+ this.SetString("url", url);
+ this.SetKeyHidden("url", true);
+ }
+
+ /**
+ * Sets endpoint to the request data
+ *
+ * @param endpoint Endpoint to set
+ * @noreturn
+ */
+ public void AddEndpoint(char[] endpoint)
+ {
+ this.SetString("endpoint", endpoint);
+ this.SetKeyHidden("endpoint", true);
+ }
+
+ /**
+ * Sets body file path to the request data
+ *
+ * @note Path to file with data to be posted
+ * @param path Body file (path) to set
+ * @noreturn
+ */
+ public void AddBodyFile(char[] path)
+ {
+ this.SetString("bodyFile", path);
+ this.SetKeyHidden("bodyFile", true);
+ }
+
+ /**
+ * Sets data file path to the request data
+ *
+ * @note Path for downloaded files
+ * @param path Data path to set
+ * @noreturn
+ */
+ public void AddDataPath(char[] path)
+ {
+ this.SetString("dataFilePath", path);
+ this.SetKeyHidden("dataFilePath", true);
+ }
+
+ /*
+ Get or set the request's "acceptType"
+ */
+ property int AcceptType
+ {
+ public get()
+ {
+ return this.GetInt("acceptType");
+ }
+ public set(int type)
+ {
+ this.SetInt("acceptType", type);
+ }
+ }
+
+ /*
+ Get or set the request's "contentType"
+ */
+ property int ContentType
+ {
+ public get()
+ {
+ return this.GetInt("contentType");
+ }
+ public set(int type)
+ {
+ this.SetInt("contentType", type);
+ }
+ }
+
+ /*
+ Get or set the request's "keyRequired"
+ */
+ property bool KeyRequired
+ {
+ public get()
+ {
+ return this.GetBool("keyRequired");
+ }
+ public set(bool required)
+ {
+ this.SetBool("keyRequired", required);
+ this.SetKeyHidden("keyRequired", true);
+ }
+ }
+
+ /*
+ Get or set the request's "isRetried"
+ */
+ property bool IsRetried
+ {
+ public get()
+ {
+ return this.GetBool("isRetried");
+ }
+ public set(bool retried)
+ {
+ this.SetBool("isRetried", retried);
+ this.SetKeyHidden("isRetried", true);
+ }
+ }
+
+ /*
+ Get or set the request's "bodyLength"
+ */
+ property int BodyLength
+ {
+ public get()
+ {
+ return this.GetInt("bodyLength");
+ }
+ public set(int length)
+ {
+ this.SetInt("bodyLength", length);
+ this.SetKeyHidden("bodyLength", true);
+ }
+ }
+
+ /*
+ Get or set the request's "status"
+ */
+ property int Status
+ {
+ public get()
+ {
+ return this.GetInt("status");
+ }
+ public set(int status)
+ {
+ this.SetInt("status", status);
+ this.SetKeyHidden("status", true);
+ }
+ }
+
+ /*
+ Get or set the request's "responseTime"
+ */
+ property int ResponseTime
+ {
+ public get()
+ {
+ return this.GetInt("responseTime");
+ }
+ public set(int responseTime)
+ {
+ this.SetInt("responseTime", responseTime);
+ this.SetKeyHidden("responseTime", true);
+ }
+ }
+
+ /*
+ Get or set the request's "requestType"
+ */
+ property int RequestType
+ {
+ public get()
+ {
+ return this.GetInt("requestType");
+ }
+ public set(int type)
+ {
+ this.SetInt("requestType", type);
+ this.SetKeyHidden("requestType", true);
+ }
+ }
+
+ /*
+ Get or set the request's "failure"
+ */
+ property bool Failure
+ {
+ public get()
+ {
+ return this.GetBool("failure");
+ }
+ public set(bool failure)
+ {
+ this.SetBool("failure", failure);
+ this.SetKeyHidden("failure", true);
+ }
+ }
+
+ /*
+ Get or set the request's "callback"
+ */
+ property Handle Callback
+ {
+ public get()
+ {
+ return view_as<Handle>(this.GetInt("callback"));
+ }
+ public set(Handle hFwd)
+ {
+ this.SetHandle("callback", hFwd);
+ this.SetKeyType("callback", Type_Int);
+ this.SetKeyHidden("callback", true);
+ }
+ }
+
+ /*
+ Get or set the request's "data"
+ */
+ property any Data
+ {
+ public get()
+ {
+ return this.GetInt("data");
+ }
+ public set(any data)
+ {
+ this.SetInt("data", data);
+ this.SetKeyHidden("data", true);
+ }
+ }
+
+ /**
+ * Adds a number to the request data
+ *
+ * @note Default values are added as "defaults"
+ * @note See GlobalAPI.inc for the default values
+ * @param key Key name to set
+ * @param value Value of the key
+ * @noreturn
+ */
+ public void AddNum(char[] key, int value)
+ {
+ if (value == -1)
+ {
+ this.SetDefault(key);
+ }
+ else
+ {
+ this.SetInt(key, value);
+ }
+ }
+
+ /**
+ * Adds a float to the request data
+ *
+ * @note Default values are added as "defaults"
+ * @note See GlobalAPI.inc for the default values
+ * @param key Key name to set
+ * @param value Value of the key
+ * @noreturn
+ */
+ public void AddFloat(char[] key, float value)
+ {
+ if (value == -1.000000)
+ {
+ this.SetDefault(key);
+ }
+ else
+ {
+ this.SetFloat(key, value);
+ }
+ }
+
+ /**
+ * Adds a string to the request data
+ *
+ * @note Default values are added as "defaults"
+ * @note See GlobalAPI.inc for the default values
+ * @param key Key name to set
+ * @param value Value of the key
+ * @noreturn
+ */
+ public void AddString(char[] key, char[] value)
+ {
+ if (StrEqual(value, ""))
+ {
+ this.SetDefault(key);
+ }
+ else
+ {
+ this.SetString(key, value);
+ }
+ }
+
+ /**
+ * Adds a boolean to the request data
+ *
+ * @note Default values are added as "defaults"
+ * @note See GlobalAPI.inc for the default values
+ * @param key Key name to set
+ * @param value Value of the key
+ * @noreturn
+ */
+ public void AddBool(char[] key, bool value)
+ {
+ if (value != true && value != false)
+ {
+ this.SetDefault(key);
+ }
+ else
+ {
+ this.SetBool(key, value);
+ }
+ }
+
+ /**
+ * Adds integer array to the request data
+ *
+ * @note Max length <= 0 are added as defaults
+ * @param key Key name to set
+ * @param value Values (array) of the key
+ * @param maxlength Max length of the values array
+ * @noreturn
+ */
+ public void AddIntArray(char[] key, int[] value, int maxlength)
+ {
+ if (maxlength <= 0)
+ {
+ this.SetDefault(key);
+ }
+ else
+ {
+ JSON_Object hArray = new JSON_Object(true);
+
+ for (int i = 0; i < maxlength; i++)
+ {
+ hArray.PushInt(value[i]);
+ }
+
+ this.SetObject(key, hArray);
+ }
+ }
+
+ /**
+ * Adds string array to the request data
+ *
+ * @note Item count <= 0 are added as defaults
+ * @param key Key name to set
+ * @param itemCount Amount of strings in the array
+ * @noreturn
+ */
+ public void AddStringArray(char[] key, char[][] value, int itemCount)
+ {
+ if (itemCount <= 0)
+ {
+ this.SetDefault(key);
+ }
+ else
+ {
+ JSON_Object hArray = new JSON_Object(true);
+
+ for (int i = 0; i < itemCount; i++)
+ {
+ hArray.PushString(value[i]);
+ }
+
+ this.SetObject(key, hArray);
+ }
+ }
+
+ /**
+ * Converts all of the request data into a query string representation
+ *
+ * @note This ignores "hidden" keys
+ * @param queryString Buffer to store the result in
+ * @param maxlength Max length of the buffer
+ * @noreturn
+ */
+ public void ToString(char[] queryString, int maxlength)
+ {
+ StringMapSnapshot paramsMap = this.Snapshot();
+
+ char key[64];
+ char value[1024];
+
+ int paramCount = 0;
+
+ for (int i = 0; i < paramsMap.Length; i++)
+ {
+ paramsMap.GetKey(i, key, sizeof(key));
+ if (this.GetKeyHidden(key) || json_is_meta_key(key))
+ {
+ continue;
+ }
+
+ switch(this.GetKeyType(key))
+ {
+ case Type_String:
+ {
+ this.GetString(key, value, sizeof(value));
+ AppendToQueryString(paramCount, queryString, maxlength, key, value);
+ }
+ case Type_Float:
+ {
+ float temp = this.GetFloat(key);
+ FloatToString(temp, value, sizeof(value));
+ AppendToQueryString(paramCount, queryString, maxlength, key, value);
+ }
+ case Type_Int:
+ {
+ int temp = this.GetInt(key);
+ IntToString(temp, value, sizeof(value));
+ AppendToQueryString(paramCount, queryString, maxlength, key, value);
+ }
+ case Type_Bool:
+ {
+ bool temp = this.GetBool(key);
+ BoolToString(temp, value, sizeof(value));
+ AppendToQueryString(paramCount, queryString, maxlength, key, value);
+ }
+ case Type_Object:
+ {
+ JSON_Object hObject = this.GetObject(key);
+
+ if (!hObject.IsArray) continue;
+
+ for (int x = 0; x < hObject.Length; x++)
+ {
+ switch (hObject.GetKeyTypeIndexed(x))
+ {
+ case Type_Int:
+ {
+ int temp = hObject.GetIntIndexed(x);
+ IntToString(temp, value, sizeof(value));
+ AppendToQueryString(paramCount, queryString, maxlength, key, value);
+ }
+ case Type_String:
+ {
+ hObject.GetStringIndexed(x, value, sizeof(value));
+ AppendToQueryString(paramCount, queryString, maxlength, key, value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ delete paramsMap;
+ }
+}
+
+// =====[ PRIVATE ]=====
+
+static void BoolToString(bool value, char[] buffer, int maxlength)
+{
+ FormatEx(buffer, maxlength, "%s", value ? "true" : "false");
+}
+
+static void AppendToQueryString(int &index, char[] buffer, int maxlength, char[] key, char[] value)
+{
+ if (index == 0)
+ {
+ index++;
+ Format(buffer, maxlength, "?%s=%s", key, value);
+ }
+ else
+ {
+ index++;
+ Format(buffer, maxlength, "%s&%s=%s", buffer, key, value);
+ }
+}
diff --git a/sourcemod/scripting/include/GlobalAPI/responses.inc b/sourcemod/scripting/include/GlobalAPI/responses.inc
new file mode 100644
index 0000000..62ebfd2
--- /dev/null
+++ b/sourcemod/scripting/include/GlobalAPI/responses.inc
@@ -0,0 +1,575 @@
+// ================== DOUBLE INCLUDE ========================= //
+
+#if defined _GlobalAPI_Responses_included_
+#endinput
+#endif
+#define _GlobalAPI_Responses_included_
+
+// =========================================================== //
+
+#include <json>
+#include <GlobalAPI/iterable>
+
+// =========================================================== //
+
+methodmap APIAuth < JSON_Object
+{
+ public APIAuth(JSON_Object hAuth)
+ {
+ return view_as<APIAuth>(hAuth);
+ }
+
+ public bool GetType(char[] buffer, int maxlength)
+ {
+ return this.GetString("Type", buffer, maxlength);
+ }
+
+ property bool IsValid
+ {
+ public get() { return this.GetBool("IsValid"); }
+ }
+
+ property int Identity
+ {
+ public get() { return this.GetInt("Identity"); }
+ }
+}
+
+// =========================================================== //
+
+methodmap APIBan < JSON_Object
+{
+ public APIBan(JSON_Object hBan)
+ {
+ return view_as<APIBan>(hBan);
+ }
+
+ property int Id
+ {
+ public get() { return this.GetInt("id"); }
+ }
+
+ property int UpdatedById
+ {
+ public get() { return this.GetInt("updated_by_id"); }
+ }
+
+ public void GetStats(char[] buffer, int maxlength)
+ {
+ this.GetString("stats", buffer, maxlength);
+ }
+
+ public void GetBanType(char[] buffer, int maxlength)
+ {
+ this.GetString("ban_type", buffer, maxlength);
+ }
+
+ public void GetExpiresOn(char[] buffer, int maxlength)
+ {
+ this.GetString("expires_on", buffer, maxlength);
+ }
+
+ public void GetSteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("steamid64", buffer, maxlength);
+ }
+
+ public void GetPlayerName(char[] buffer, int maxlength)
+ {
+ this.GetString("player_name", buffer, maxlength);
+ }
+
+ public void GetNotes(char[] buffer, int maxlength)
+ {
+ this.GetString("notes", buffer, maxlength);
+ }
+
+ public void GetSteamId(char[] buffer, int maxlength)
+ {
+ this.GetString("steam_id", buffer, maxlength);
+ }
+
+ public void GetUpdatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_on", buffer, maxlength);
+ }
+
+ property int ServerId
+ {
+ public get() { return this.GetInt("server_id"); }
+ }
+
+ public void GetCreatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("created_on", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIJumpstat < JSON_Object
+{
+ public APIJumpstat(JSON_Object hJump)
+ {
+ return view_as<APIJumpstat>(hJump);
+ }
+
+ property int Id
+ {
+ public get() { return this.GetInt("id"); }
+ }
+
+ property int ServerId
+ {
+ public get() { return this.GetInt("server_id"); }
+ }
+
+ public void GetSteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("steamid64", buffer, maxlength);
+ }
+
+ public void GetName(char[] buffer, int maxlength)
+ {
+ this.GetString("player_name", buffer, maxlength);
+ }
+
+ public void GetSteamId(char[] buffer, int maxlength)
+ {
+ this.GetString("steam_id", buffer, maxlength);
+ }
+
+ property int JumpType
+ {
+ public get() { return this.GetInt("jump_type"); }
+ }
+
+ property float Distance
+ {
+ public get() { return this.GetFloat("distance"); }
+ }
+
+ property int TickRate
+ {
+ public get() { return this.GetInt("tickrate"); }
+ }
+
+ property int MslCount
+ {
+ public get() { return this.GetInt("msl_count"); }
+ }
+
+ property int StrafeCount
+ {
+ public get() { return this.GetInt("strafe_count"); }
+ }
+
+ property bool IsCrouchBind
+ {
+ public get() { return this.GetBool("is_crouch_bind"); }
+ }
+
+ property bool IsForwardBind
+ {
+ public get() { return this.GetBool("is_forward_bind"); }
+ }
+
+ property bool IsCrouchBoost
+ {
+ public get() { return this.GetBool("is_crouch_boost"); }
+ }
+
+ property int UpdatedById
+ {
+ public get() { return this.GetInt("updated_by_id"); }
+ }
+
+ public void GetCreatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("created_on", buffer, maxlength);
+ }
+
+ public void GetUpdatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_on", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIMap < JSON_Object
+{
+ public APIMap(JSON_Object hMap)
+ {
+ return view_as<APIMap>(hMap);
+ }
+
+ property int Id
+ {
+ public get() { return this.GetInt("id"); }
+ }
+
+ public void GetName(char[] buffer, int maxlength)
+ {
+ this.GetString("name", buffer, maxlength);
+ }
+
+ property int Filesize
+ {
+ public get() { return this.GetInt("filesize"); }
+ }
+
+ property bool IsValidated
+ {
+ public get() { return this.GetBool("validated"); }
+ }
+
+ property int Difficulty
+ {
+ public get() { return this.GetInt("difficulty"); }
+ }
+
+ public void GetCreatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("created_on", buffer, maxlength);
+ }
+
+ public void GetUpdatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_on", buffer, maxlength);
+ }
+
+ public void GetApprovedBySteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("approved_by_steamid64", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIMode < JSON_Object
+{
+ public APIMode(JSON_Object hMode)
+ {
+ return view_as<APIMode>(hMode);
+ }
+
+ property int Id
+ {
+ public get() { return this.GetInt("id"); }
+ }
+
+ public void GetName(char[] buffer, int maxlength)
+ {
+ this.GetString("name", buffer, maxlength);
+ }
+
+ public void GetDescription(char[] buffer, int maxlength)
+ {
+ this.GetString("description", buffer, maxlength);
+ }
+
+ property int LatestVersion
+ {
+ public get() { return this.GetInt("latest_version"); }
+ }
+
+ public void GetLatestVersionDesc(char[] buffer, int maxlength)
+ {
+ this.GetString("latest_version_description", buffer, maxlength);
+ }
+
+ public void GetWebsite(char[] buffer, int maxlength)
+ {
+ this.GetString("website", buffer, maxlength);
+ }
+
+ public void GetRepository(char[] buffer, int maxlength)
+ {
+ this.GetString("repo", buffer, maxlength);
+ }
+
+ public void GetContactSteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("contact_steamid64", buffer, maxlength);
+ }
+
+ // TODO: Add supported_tickrates
+
+ public void GetCreatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("created_on", buffer, maxlength);
+ }
+
+ public void GetUpdatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_on", buffer, maxlength);
+ }
+
+ public void GetUpdatedById(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_by_id", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIPlayerRank < JSON_Object
+{
+ public APIPlayerRank(JSON_Object hPlayerRank)
+ {
+ return view_as<APIPlayerRank>(hPlayerRank);
+ }
+
+ property int Points
+ {
+ public get() { return this.GetInt("points"); }
+ }
+
+ property int PointsOverall
+ {
+ public get() { return this.GetInt("points_overall"); }
+ }
+
+ property float Average
+ {
+ public get() { return this.GetFloat("average"); }
+ }
+
+ property float Rating
+ {
+ public get() { return this.GetFloat("rating"); }
+ }
+
+ property int Finishes
+ {
+ public get() { return this.GetInt("finishes"); }
+ }
+
+ public void GetSteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("steamid64", buffer, maxlength);
+ }
+
+ public void GetSteamId(char[] buffer, int maxlength)
+ {
+ this.GetString("steamid", buffer, maxlength);
+ }
+
+ public void GetPlayerName(char[] buffer, int maxlength)
+ {
+ this.GetString("player_name", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIPlayer < JSON_Object
+{
+ public APIPlayer(JSON_Object hPlayer)
+ {
+ return view_as<APIPlayer>(hPlayer);
+ }
+
+ public void GetSteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("steamid64", buffer, maxlength);
+ }
+
+ public void GetSteamId(char[] buffer, int maxlength)
+ {
+ this.GetString("steam_id", buffer, maxlength);
+ }
+
+ property bool IsBanned
+ {
+ public get() { return this.GetBool("is_banned"); }
+ }
+
+ property int TotalRecords
+ {
+ public get() { return this.GetInt("total_records"); }
+ }
+
+ public void GetName(char[] buffer, int maxlength)
+ {
+ this.GetString("name", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIRecord < JSON_Object
+{
+ public APIRecord(JSON_Object hRecord)
+ {
+ return view_as<APIRecord>(hRecord);
+ }
+
+ property int Id
+ {
+ public get() { return this.GetInt("id"); }
+ }
+
+ public void GetSteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("steamid64", buffer, maxlength);
+ }
+
+ public void GetPlayerName(char[] buffer, int maxlength)
+ {
+ this.GetString("player_name", buffer, maxlength);
+ }
+
+ public void GetSteamId(char[] buffer, int maxlength)
+ {
+ this.GetString("steam_id", buffer, maxlength);
+ }
+
+ property int ServerId
+ {
+ public get() { return this.GetInt("server_id"); }
+ }
+
+ property int MapId
+ {
+ public get() { return this.GetInt("map_id"); }
+ }
+
+ property int Stage
+ {
+ public get() { return this.GetInt("stage"); }
+ }
+
+ public void GetMode(char[] buffer, int maxlength)
+ {
+ this.GetString("mode", buffer, maxlength);
+ }
+
+ property int TickRate
+ {
+ public get() { return this.GetInt("tickrate"); }
+ }
+
+ property float Time
+ {
+ public get() { return this.GetFloat("time"); }
+ }
+
+ property int Teleports
+ {
+ public get() { return this.GetInt("teleports"); }
+ }
+
+ public void GetCreatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("created_on", buffer, maxlength);
+ }
+
+ public void GetUpdatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_on", buffer, maxlength);
+ }
+
+ property int UpdatedBy
+ {
+ public get() { return this.GetInt("updated_by"); }
+ }
+
+ public void GetServerName(char[] buffer, int maxlength)
+ {
+ this.GetString("server_name", buffer, maxlength);
+ }
+
+ public void GetMapName(char[] buffer, int maxlength)
+ {
+ this.GetString("map_name", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIServer < JSON_Object
+{
+ public APIServer(JSON_Object hServer)
+ {
+ return view_as<APIServer>(hServer);
+ }
+
+ property int Port
+ {
+ public get() { return this.GetInt("port"); }
+ }
+
+ public void GetIPAddress(char[] buffer, int maxlength)
+ {
+ this.GetString("ip", buffer, maxlength);
+ }
+
+ public void GetName(char[] buffer, int maxlength)
+ {
+ this.GetString("name", buffer, maxlength);
+ }
+
+ public void GetOwnerSteamId64(char[] buffer, int maxlength)
+ {
+ this.GetString("owner_steamid64", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
+
+methodmap APIRecordFilter < JSON_Object
+{
+ public APIRecordFilter(JSON_Object hRecordFilter)
+ {
+ return view_as<APIRecordFilter>(hRecordFilter);
+ }
+
+ property int Id
+ {
+ public get() { return this.GetInt("id"); }
+ }
+
+ property int MapId
+ {
+ public get() { return this.GetInt("map_id"); }
+ }
+
+ property int Stage
+ {
+ public get() { return this.GetInt("stage"); }
+ }
+
+ property int ModeId
+ {
+ public get() { return this.GetInt("mode_id"); }
+ }
+
+ property int TickRate
+ {
+ public get() { return this.GetInt("tickrate"); }
+ }
+
+ property bool HasTeleports
+ {
+ public get() { return this.GetBool("has_teleports"); }
+ }
+
+ public void GetCreatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("created_on", buffer, maxlength);
+ }
+
+ public void GetUpdatedOn(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_on", buffer, maxlength);
+ }
+
+ public void GetUpdatedById(char[] buffer, int maxlength)
+ {
+ this.GetString("updated_by_id", buffer, maxlength);
+ }
+}
+
+// =========================================================== //
diff --git a/sourcemod/scripting/include/GlobalAPI/stocks.inc b/sourcemod/scripting/include/GlobalAPI/stocks.inc
new file mode 100644
index 0000000..52596c4
--- /dev/null
+++ b/sourcemod/scripting/include/GlobalAPI/stocks.inc
@@ -0,0 +1,67 @@
+// ================== DOUBLE INCLUDE ========================= //
+
+#if defined _GlobalAPI_Stocks_included_
+#endinput
+#endif
+#define _GlobalAPI_Stocks_included_
+
+// =========================================================== //
+
+/**
+ * Gets plugin's display name from its handle
+ *
+ * @param plugin Plugin handle to retrieve name from
+ * @return String representation of the plugin name
+ */
+stock char[] GetPluginDisplayName(Handle plugin)
+{
+ char pluginName[GlobalAPI_Max_PluginName_Length] = "Unknown";
+ GetPluginInfo(plugin, PlInfo_Name, pluginName, sizeof(pluginName));
+
+ return pluginName;
+}
+
+/**
+ * Gets plugin's version from its handle
+ *
+ * @param plugin Plugin handle to retrieve version from
+ * @return String representation of the plugin version
+ */
+stock char[] GetPluginVersion(Handle plugin)
+{
+ char pluginVersion[GlobalAPI_Max_PluginVersion_Length] = "Unknown";
+ GetPluginInfo(plugin, PlInfo_Version, pluginVersion, sizeof(pluginVersion));
+
+ return pluginVersion;
+}
+
+/**
+ * Gets current map's "display name"
+ *
+ * @param buffer Buffer to store the result in
+ * @param maxlength Max length of the buffer
+ * @noreturn
+ */
+stock void GetMapDisplay(char[] buffer, int maxlength)
+{
+ char map[PLATFORM_MAX_PATH];
+ GetCurrentMap(map, sizeof(map));
+ GetMapDisplayName(map, map, sizeof(map));
+
+ FormatEx(buffer, maxlength, map);
+}
+
+/**
+ * Gets current map's full (game dir) path
+ *
+ * @param buffer Buffer to store result in
+ * @param maxlength Max length of the buffer
+ * @noreturn
+ */
+stock void GetMapFullPath(char[] buffer, int maxlength)
+{
+ char mapPath[PLATFORM_MAX_PATH];
+ GetCurrentMap(mapPath, sizeof(mapPath));
+
+ Format(buffer, maxlength, "maps/%s.bsp", mapPath);
+}
diff --git a/sourcemod/scripting/include/SteamWorks.inc b/sourcemod/scripting/include/SteamWorks.inc
new file mode 100644
index 0000000..0e4aec3
--- /dev/null
+++ b/sourcemod/scripting/include/SteamWorks.inc
@@ -0,0 +1,413 @@
+#if defined _SteamWorks_Included
+ #endinput
+#endif
+#define _SteamWorks_Included
+
+/* results from UserHasLicenseForApp */
+enum EUserHasLicenseForAppResult
+{
+ k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app
+ k_EUserHasLicenseResultDoesNotHaveLicense = 1, // User does not have a license for the specified app
+ k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated
+};
+
+/* General result codes */
+enum EResult
+{
+ k_EResultOK = 1, // success
+ k_EResultFail = 2, // generic failure
+ k_EResultNoConnection = 3, // no/failed network connection
+// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed
+ k_EResultInvalidPassword = 5, // password/ticket is invalid
+ k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere
+ k_EResultInvalidProtocolVer = 7, // protocol version is incorrect
+ k_EResultInvalidParam = 8, // a parameter is incorrect
+ k_EResultFileNotFound = 9, // file was not found
+ k_EResultBusy = 10, // called method busy - action not taken
+ k_EResultInvalidState = 11, // called object was in an invalid state
+ k_EResultInvalidName = 12, // name is invalid
+ k_EResultInvalidEmail = 13, // email is invalid
+ k_EResultDuplicateName = 14, // name is not unique
+ k_EResultAccessDenied = 15, // access is denied
+ k_EResultTimeout = 16, // operation timed out
+ k_EResultBanned = 17, // VAC2 banned
+ k_EResultAccountNotFound = 18, // account not found
+ k_EResultInvalidSteamID = 19, // steamID is invalid
+ k_EResultServiceUnavailable = 20, // The requested service is currently unavailable
+ k_EResultNotLoggedOn = 21, // The user is not logged on
+ k_EResultPending = 22, // Request is pending (may be in process, or waiting on third party)
+ k_EResultEncryptionFailure = 23, // Encryption or Decryption failed
+ k_EResultInsufficientPrivilege = 24, // Insufficient privilege
+ k_EResultLimitExceeded = 25, // Too much of a good thing
+ k_EResultRevoked = 26, // Access has been revoked (used for revoked guest passes)
+ k_EResultExpired = 27, // License/Guest pass the user is trying to access is expired
+ k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again
+ k_EResultDuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time
+ k_EResultAlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user
+ k_EResultIPNotFound = 31, // IP address not found
+ k_EResultPersistFailed = 32, // failed to write change to the data store
+ k_EResultLockingFailed = 33, // failed to acquire access lock for this operation
+ k_EResultLogonSessionReplaced = 34,
+ k_EResultConnectFailed = 35,
+ k_EResultHandshakeFailed = 36,
+ k_EResultIOFailure = 37,
+ k_EResultRemoteDisconnect = 38,
+ k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart requested
+ k_EResultBlocked = 40, // a user didn't allow it
+ k_EResultIgnored = 41, // target is ignoring sender
+ k_EResultNoMatch = 42, // nothing matching the request found
+ k_EResultAccountDisabled = 43,
+ k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now
+ k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available
+ k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin
+ k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol.
+ k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another.
+ k_EResultPasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed.
+ k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait
+ k_EResultSuspended = 51, // Long running operation (content download) suspended/paused
+ k_EResultCancelled = 52, // Operation canceled (typically by user: content download)
+ k_EResultDataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable
+ k_EResultDiskFull = 54, // Operation canceled - not enough disk space.
+ k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed
+ k_EResultPasswordUnset = 56, // Password could not be verified as it's unset server side
+ k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account
+ k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid
+ k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first
+ k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files
+ k_EResultIllegalPassword = 61, // The requested new password is not legal
+ k_EResultSameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer )
+ k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure
+ k_EResultCannotUseOldPassword = 64, // The requested new password is not legal
+ k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code invalid
+ k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent
+ k_EResultHardwareNotCapableOfIPT = 67, //
+ k_EResultIPTInitError = 68, //
+ k_EResultParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user
+ k_EResultFacebookQueryError = 70, // Facebook query returned an error
+ k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth code expired
+ k_EResultIPLoginRestrictionFailed = 72,
+ k_EResultAccountLockedDown = 73,
+ k_EResultAccountLogonDeniedVerifiedEmailRequired = 74,
+ k_EResultNoMatchingURL = 75,
+ k_EResultBadResponse = 76, // parse failure, missing field, etc.
+ k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password
+ k_EResultValueOutOfRange = 78, // the value entered is outside the acceptable range
+ k_EResultUnexpectedError = 79, // something happened that we didn't expect to ever happen
+ k_EResultDisabled = 80, // The requested service has been configured to be unavailable
+ k_EResultInvalidCEGSubmission = 81, // The set of files submitted to the CEG server are not valid !
+ k_EResultRestrictedDevice = 82, // The device being used is not allowed to perform this action
+ k_EResultRegionLocked = 83, // The action could not be complete because it is region restricted
+ k_EResultRateLimitExceeded = 84, // Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent
+ k_EResultAccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to login
+ k_EResultItemDeleted = 86, // The thing we're trying to access has been deleted
+ k_EResultAccountLoginDeniedThrottle = 87, // login attempt failed, try to throttle response to possible attacker
+ k_EResultTwoFactorCodeMismatch = 88, // two factor code mismatch
+ k_EResultTwoFactorActivationCodeMismatch = 89, // activation code for two-factor didn't match
+ k_EResultAccountAssociatedToMultiplePartners = 90, // account has been associated with multiple partners
+ k_EResultNotModified = 91, // data not modified
+ k_EResultNoMobileDevice = 92, // the account does not have a mobile device associated with it
+ k_EResultTimeNotSynced = 93, // the time presented is out of range or tolerance
+ k_EResultSmsCodeFailed = 94, // SMS code failure (no match, none pending, etc.)
+ k_EResultAccountLimitExceeded = 95, // Too many accounts access this resource
+ k_EResultAccountActivityLimitExceeded = 96, // Too many changes to this account
+ k_EResultPhoneActivityLimitExceeded = 97, // Too many changes to this phone
+ k_EResultRefundToWallet = 98, // Cannot refund to payment method, must use wallet
+ k_EResultEmailSendFailure = 99, // Cannot send an email
+ k_EResultNotSettled = 100, // Can't perform operation till payment has settled
+ k_EResultNeedCaptcha = 101, // Needs to provide a valid captcha
+ k_EResultGSLTDenied = 102, // a game server login token owned by this token's owner has been banned
+ k_EResultGSOwnerDenied = 103, // game server owner is denied for other reason (account lock, community ban, vac ban, missing phone)
+ k_EResultInvalidItemType = 104 // the type of thing we were requested to act on is invalid
+};
+
+/* This enum is used in client API methods, do not re-number existing values. */
+enum EHTTPMethod
+{
+ k_EHTTPMethodInvalid = 0,
+ k_EHTTPMethodGET,
+ k_EHTTPMethodHEAD,
+ k_EHTTPMethodPOST,
+ k_EHTTPMethodPUT,
+ k_EHTTPMethodDELETE,
+ k_EHTTPMethodOPTIONS,
+ k_EHTTPMethodPATCH,
+
+ // The remaining HTTP methods are not yet supported, per rfc2616 section 5.1.1 only GET and HEAD are required for
+ // a compliant general purpose server. We'll likely add more as we find uses for them.
+
+ // k_EHTTPMethodTRACE,
+ // k_EHTTPMethodCONNECT
+};
+
+
+/* HTTP Status codes that the server can send in response to a request, see rfc2616 section 10.3 for descriptions
+ of each of these. */
+enum EHTTPStatusCode
+{
+ // Invalid status code (this isn't defined in HTTP, used to indicate unset in our code)
+ k_EHTTPStatusCodeInvalid = 0,
+
+ // Informational codes
+ k_EHTTPStatusCode100Continue = 100,
+ k_EHTTPStatusCode101SwitchingProtocols = 101,
+
+ // Success codes
+ k_EHTTPStatusCode200OK = 200,
+ k_EHTTPStatusCode201Created = 201,
+ k_EHTTPStatusCode202Accepted = 202,
+ k_EHTTPStatusCode203NonAuthoritative = 203,
+ k_EHTTPStatusCode204NoContent = 204,
+ k_EHTTPStatusCode205ResetContent = 205,
+ k_EHTTPStatusCode206PartialContent = 206,
+
+ // Redirection codes
+ k_EHTTPStatusCode300MultipleChoices = 300,
+ k_EHTTPStatusCode301MovedPermanently = 301,
+ k_EHTTPStatusCode302Found = 302,
+ k_EHTTPStatusCode303SeeOther = 303,
+ k_EHTTPStatusCode304NotModified = 304,
+ k_EHTTPStatusCode305UseProxy = 305,
+ //k_EHTTPStatusCode306Unused = 306, (used in old HTTP spec, now unused in 1.1)
+ k_EHTTPStatusCode307TemporaryRedirect = 307,
+
+ // Error codes
+ k_EHTTPStatusCode400BadRequest = 400,
+ k_EHTTPStatusCode401Unauthorized = 401, // You probably want 403 or something else. 401 implies you're sending a WWW-Authenticate header and the client can sent an Authorization header in response.
+ k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients
+ k_EHTTPStatusCode403Forbidden = 403,
+ k_EHTTPStatusCode404NotFound = 404,
+ k_EHTTPStatusCode405MethodNotAllowed = 405,
+ k_EHTTPStatusCode406NotAcceptable = 406,
+ k_EHTTPStatusCode407ProxyAuthRequired = 407,
+ k_EHTTPStatusCode408RequestTimeout = 408,
+ k_EHTTPStatusCode409Conflict = 409,
+ k_EHTTPStatusCode410Gone = 410,
+ k_EHTTPStatusCode411LengthRequired = 411,
+ k_EHTTPStatusCode412PreconditionFailed = 412,
+ k_EHTTPStatusCode413RequestEntityTooLarge = 413,
+ k_EHTTPStatusCode414RequestURITooLong = 414,
+ k_EHTTPStatusCode415UnsupportedMediaType = 415,
+ k_EHTTPStatusCode416RequestedRangeNotSatisfiable = 416,
+ k_EHTTPStatusCode417ExpectationFailed = 417,
+ k_EHTTPStatusCode4xxUnknown = 418, // 418 is reserved, so we'll use it to mean unknown
+ k_EHTTPStatusCode429TooManyRequests = 429,
+
+ // Server error codes
+ k_EHTTPStatusCode500InternalServerError = 500,
+ k_EHTTPStatusCode501NotImplemented = 501,
+ k_EHTTPStatusCode502BadGateway = 502,
+ k_EHTTPStatusCode503ServiceUnavailable = 503,
+ k_EHTTPStatusCode504GatewayTimeout = 504,
+ k_EHTTPStatusCode505HTTPVersionNotSupported = 505,
+ k_EHTTPStatusCode5xxUnknown = 599,
+};
+
+/* list of possible return values from the ISteamGameCoordinator API */
+enum EGCResults
+{
+ k_EGCResultOK = 0,
+ k_EGCResultNoMessage = 1, // There is no message in the queue
+ k_EGCResultBufferTooSmall = 2, // The buffer is too small for the requested message
+ k_EGCResultNotLoggedOn = 3, // The client is not logged onto Steam
+ k_EGCResultInvalidMessage = 4, // Something was wrong with the message being sent with SendMessage
+};
+
+native bool:SteamWorks_IsVACEnabled();
+native bool:SteamWorks_GetPublicIP(ipaddr[4]);
+native SteamWorks_GetPublicIPCell();
+native bool:SteamWorks_IsLoaded();
+native bool:SteamWorks_SetGameData(const String:sData[]);
+native bool:SteamWorks_SetGameDescription(const String:sDesc[]);
+native bool:SteamWorks_SetMapName(const String:sMapName[]);
+native bool:SteamWorks_IsConnected();
+native bool:SteamWorks_SetRule(const String:sKey[], const String:sValue[]);
+native bool:SteamWorks_ClearRules();
+native bool:SteamWorks_ForceHeartbeat();
+native bool:SteamWorks_GetUserGroupStatus(client, groupid);
+native bool:SteamWorks_GetUserGroupStatusAuthID(authid, groupid);
+
+native EUserHasLicenseForAppResult:SteamWorks_HasLicenseForApp(client, app);
+native EUserHasLicenseForAppResult:SteamWorks_HasLicenseForAppId(authid, app);
+native SteamWorks_GetClientSteamID(client, String:sSteamID[], length);
+
+native bool:SteamWorks_RequestStatsAuthID(authid, appid);
+native bool:SteamWorks_RequestStats(client, appid);
+native bool:SteamWorks_GetStatCell(client, const String:sKey[], &value);
+native bool:SteamWorks_GetStatAuthIDCell(authid, const String:sKey[], &value);
+native bool:SteamWorks_GetStatFloat(client, const String:sKey[], &Float:value);
+native bool:SteamWorks_GetStatAuthIDFloat(authid, const String:sKey[], &Float:value);
+
+native Handle:SteamWorks_CreateHTTPRequest(EHTTPMethod:method, const String:sURL[]);
+native bool:SteamWorks_SetHTTPRequestContextValue(Handle:hHandle, any:data1, any:data2=0);
+native bool:SteamWorks_SetHTTPRequestNetworkActivityTimeout(Handle:hHandle, timeout);
+native bool:SteamWorks_SetHTTPRequestHeaderValue(Handle:hHandle, const String:sName[], const String:sValue[]);
+native bool:SteamWorks_SetHTTPRequestGetOrPostParameter(Handle:hHandle, const String:sName[], const String:sValue[]);
+native bool:SteamWorks_SetHTTPRequestUserAgentInfo(Handle:hHandle, const String:sUserAgentInfo[]);
+native bool:SteamWorks_SetHTTPRequestRequiresVerifiedCertificate(Handle:hHandle, bool:bRequireVerifiedCertificate);
+native bool:SteamWorks_SetHTTPRequestAbsoluteTimeoutMS(Handle:hHandle, unMilliseconds);
+
+#if SOURCEMOD_V_MAJOR >= 1 && SOURCEMOD_V_MINOR >= 9
+typeset SteamWorksHTTPRequestCompleted
+{
+ function void (Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode);
+ function void (Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any data1);
+ function void (Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any data1, any data2);
+};
+
+typeset SteamWorksHTTPHeadersReceived
+{
+ function void (Handle hRequest, bool bFailure);
+ function void (Handle hRequest, bool bFailure, any data1);
+ function void (Handle hRequest, bool bFailure, any data1, any data2);
+};
+
+typeset SteamWorksHTTPDataReceived
+{
+ function void (Handle hRequest, bool bFailure, int offset, int bytesreceived);
+ function void (Handle hRequest, bool bFailure, int offset, int bytesreceived, any data1);
+ function void (Handle hRequest, bool bFailure, int offset, int bytesreceived, any data1, any data2);
+};
+
+typeset SteamWorksHTTPBodyCallback
+{
+ function void (const char[] sData);
+ function void (const char[] sData, any value);
+ function void (const int[] data, any value, int datalen);
+};
+
+#else
+
+funcenum SteamWorksHTTPRequestCompleted
+{
+ public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode),
+ public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode, any:data1),
+ public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode, any:data1, any:data2)
+};
+
+funcenum SteamWorksHTTPHeadersReceived
+{
+ public(Handle:hRequest, bool:bFailure),
+ public(Handle:hRequest, bool:bFailure, any:data1),
+ public(Handle:hRequest, bool:bFailure, any:data1, any:data2)
+};
+
+funcenum SteamWorksHTTPDataReceived
+{
+ public(Handle:hRequest, bool:bFailure, offset, bytesreceived),
+ public(Handle:hRequest, bool:bFailure, offset, bytesreceived, any:data1),
+ public(Handle:hRequest, bool:bFailure, offset, bytesreceived, any:data1, any:data2)
+};
+
+funcenum SteamWorksHTTPBodyCallback
+{
+ public(const String:sData[]),
+ public(const String:sData[], any:value),
+ public(const data[], any:value, datalen)
+};
+
+#endif
+
+native bool:SteamWorks_SetHTTPCallbacks(Handle:hHandle, SteamWorksHTTPRequestCompleted:fCompleted = INVALID_FUNCTION, SteamWorksHTTPHeadersReceived:fHeaders = INVALID_FUNCTION, SteamWorksHTTPDataReceived:fData = INVALID_FUNCTION, Handle:hCalling = INVALID_HANDLE);
+native bool:SteamWorks_SendHTTPRequest(Handle:hRequest);
+native bool:SteamWorks_SendHTTPRequestAndStreamResponse(Handle:hRequest);
+native bool:SteamWorks_DeferHTTPRequest(Handle:hRequest);
+native bool:SteamWorks_PrioritizeHTTPRequest(Handle:hRequest);
+native bool:SteamWorks_GetHTTPResponseHeaderSize(Handle:hRequest, const String:sHeader[], &size);
+native bool:SteamWorks_GetHTTPResponseHeaderValue(Handle:hRequest, const String:sHeader[], String:sValue[], size);
+native bool:SteamWorks_GetHTTPResponseBodySize(Handle:hRequest, &size);
+native bool:SteamWorks_GetHTTPResponseBodyData(Handle:hRequest, String:sBody[], length);
+native bool:SteamWorks_GetHTTPStreamingResponseBodyData(Handle:hRequest, cOffset, String:sBody[], length);
+native bool:SteamWorks_GetHTTPDownloadProgressPct(Handle:hRequest, &Float:percent);
+native bool:SteamWorks_GetHTTPRequestWasTimedOut(Handle:hRequest, &bool:bWasTimedOut);
+native bool:SteamWorks_SetHTTPRequestRawPostBody(Handle:hRequest, const String:sContentType[], const String:sBody[], bodylen);
+native bool:SteamWorks_SetHTTPRequestRawPostBodyFromFile(Handle:hRequest, const String:sContentType[], const String:sFileName[]);
+
+native bool:SteamWorks_GetHTTPResponseBodyCallback(Handle:hRequest, SteamWorksHTTPBodyCallback:fCallback, any:data = 0, Handle:hPlugin = INVALID_HANDLE); /* Look up, moved definition for 1.7+ compat. */
+native bool:SteamWorks_WriteHTTPResponseBodyToFile(Handle:hRequest, const String:sFileName[]);
+
+forward SW_OnValidateClient(ownerauthid, authid);
+forward SteamWorks_OnValidateClient(ownerauthid, authid);
+forward SteamWorks_SteamServersConnected();
+forward SteamWorks_SteamServersConnectFailure(EResult:result);
+forward SteamWorks_SteamServersDisconnected(EResult:result);
+
+forward Action:SteamWorks_RestartRequested();
+forward SteamWorks_TokenRequested(String:sToken[], maxlen);
+
+forward SteamWorks_OnClientGroupStatus(authid, groupid, bool:isMember, bool:isOfficer);
+
+forward EGCResults:SteamWorks_GCSendMessage(unMsgType, const String:pubData[], cubData);
+forward SteamWorks_GCMsgAvailable(cubData);
+forward EGCResults:SteamWorks_GCRetrieveMessage(punMsgType, const String:pubDest[], cubDest, pcubMsgSize);
+
+native EGCResults:SteamWorks_SendMessageToGC(unMsgType, const String:pubData[], cubData);
+
+public Extension:__ext_SteamWorks =
+{
+ name = "SteamWorks",
+ file = "SteamWorks.ext",
+#if defined AUTOLOAD_EXTENSIONS
+ autoload = 1,
+#else
+ autoload = 0,
+#endif
+#if defined REQUIRE_EXTENSIONS
+ required = 1,
+#else
+ required = 0,
+#endif
+};
+
+#if !defined REQUIRE_EXTENSIONS
+public __ext_SteamWorks_SetNTVOptional()
+{
+ MarkNativeAsOptional("SteamWorks_IsVACEnabled");
+ MarkNativeAsOptional("SteamWorks_GetPublicIP");
+ MarkNativeAsOptional("SteamWorks_GetPublicIPCell");
+ MarkNativeAsOptional("SteamWorks_IsLoaded");
+ MarkNativeAsOptional("SteamWorks_SetGameData");
+ MarkNativeAsOptional("SteamWorks_SetGameDescription");
+ MarkNativeAsOptional("SteamWorks_IsConnected");
+ MarkNativeAsOptional("SteamWorks_SetRule");
+ MarkNativeAsOptional("SteamWorks_ClearRules");
+ MarkNativeAsOptional("SteamWorks_ForceHeartbeat");
+ MarkNativeAsOptional("SteamWorks_GetUserGroupStatus");
+ MarkNativeAsOptional("SteamWorks_GetUserGroupStatusAuthID");
+
+ MarkNativeAsOptional("SteamWorks_HasLicenseForApp");
+ MarkNativeAsOptional("SteamWorks_HasLicenseForAppId");
+ MarkNativeAsOptional("SteamWorks_GetClientSteamID");
+
+ MarkNativeAsOptional("SteamWorks_RequestStatsAuthID");
+ MarkNativeAsOptional("SteamWorks_RequestStats");
+ MarkNativeAsOptional("SteamWorks_GetStatCell");
+ MarkNativeAsOptional("SteamWorks_GetStatAuthIDCell");
+ MarkNativeAsOptional("SteamWorks_GetStatFloat");
+ MarkNativeAsOptional("SteamWorks_GetStatAuthIDFloat");
+
+ MarkNativeAsOptional("SteamWorks_SendMessageToGC");
+
+ MarkNativeAsOptional("SteamWorks_CreateHTTPRequest");
+ MarkNativeAsOptional("SteamWorks_SetHTTPRequestContextValue");
+ MarkNativeAsOptional("SteamWorks_SetHTTPRequestNetworkActivityTimeout");
+ MarkNativeAsOptional("SteamWorks_SetHTTPRequestHeaderValue");
+ MarkNativeAsOptional("SteamWorks_SetHTTPRequestGetOrPostParameter");
+
+ MarkNativeAsOptional("SteamWorks_SetHTTPCallbacks");
+ MarkNativeAsOptional("SteamWorks_SendHTTPRequest");
+ MarkNativeAsOptional("SteamWorks_SendHTTPRequestAndStreamResponse");
+ MarkNativeAsOptional("SteamWorks_DeferHTTPRequest");
+ MarkNativeAsOptional("SteamWorks_PrioritizeHTTPRequest");
+ MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderSize");
+ MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderValue");
+ MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodySize");
+ MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyData");
+ MarkNativeAsOptional("SteamWorks_GetHTTPStreamingResponseBodyData");
+ MarkNativeAsOptional("SteamWorks_GetHTTPDownloadProgressPct");
+ MarkNativeAsOptional("SteamWorks_SetHTTPRequestRawPostBody");
+ MarkNativeAsOptional("SteamWorks_SetHTTPRequestRawPostBodyFromFile");
+
+ MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyCallback");
+ MarkNativeAsOptional("SteamWorks_WriteHTTPResponseBodyToFile");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/autoexecconfig.inc b/sourcemod/scripting/include/autoexecconfig.inc
new file mode 100644
index 0000000..e057b1b
--- /dev/null
+++ b/sourcemod/scripting/include/autoexecconfig.inc
@@ -0,0 +1,765 @@
+/**
+ * AutoExecConfig
+ *
+ * Copyright (C) 2013-2017 Impact
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ */
+
+#if defined _autoexecconfig_included
+ #endinput
+#endif
+#define _autoexecconfig_included
+
+
+#include <sourcemod>
+
+#define AUTOEXECCONFIG_VERSION "0.1.5"
+#define AUTOEXECCONFIG_URL "https://forums.alliedmods.net/showthread.php?t=204254"
+
+// Append
+#define AUTOEXEC_APPEND_BAD_FILENAME 0
+#define AUTOEXEC_APPEND_FILE_NOT_FOUND 1
+#define AUTOEXEC_APPEND_BAD_HANDLE 2
+#define AUTOEXEC_APPEND_SUCCESS 3
+
+
+
+// Find
+#define AUTOEXEC_FIND_BAD_FILENAME 10
+#define AUTOEXEC_FIND_FILE_NOT_FOUND 11
+#define AUTOEXEC_FIND_BAD_HANDLE 12
+#define AUTOEXEC_FIND_NOT_FOUND 13
+#define AUTOEXEC_FIND_SUCCESS 14
+
+
+
+// Clean
+#define AUTOEXEC_CLEAN_FILE_NOT_FOUND 20
+#define AUTOEXEC_CLEAN_BAD_HANDLE 21
+#define AUTOEXEC_CLEAN_SUCCESS 22
+
+
+
+// General
+#define AUTOEXEC_NO_CONFIG 30
+
+
+
+// Formatter
+#define AUTOEXEC_FORMAT_BAD_FILENAME 40
+#define AUTOEXEC_FORMAT_SUCCESS 41
+
+
+
+// Global variables
+static char g_sConfigFile[PLATFORM_MAX_PATH];
+static char g_sRawFileName[PLATFORM_MAX_PATH];
+static char g_sFolderPath[PLATFORM_MAX_PATH];
+
+static bool g_bCreateFile = false;
+static Handle g_hPluginHandle = null;
+
+static bool g_bCreateDirectory = false;
+static int g_bCreateDirectoryMode = FPERM_U_READ|FPERM_U_WRITE|FPERM_U_EXEC|FPERM_G_READ|FPERM_G_EXEC|FPERM_O_READ|FPERM_O_EXEC;
+
+
+// Workaround for now
+static int g_iLastFindResult;
+static int g_iLastAppendResult;
+
+
+
+
+/**
+ * Returns the last result from the parser.
+ *
+ * @return Returns one of the AUTOEXEC_FIND values or -1 if not set.
+*/
+stock int AutoExecConfig_GetFindResult()
+{
+ return g_iLastFindResult;
+}
+
+
+
+
+
+/**
+ * Returns the last result from the appender.
+ *
+ * @return Returns one of the AUTOEXEC_APPEND values or -1 if not set.
+*/
+stock int AutoExecConfig_GetAppendResult()
+{
+ return g_iLastAppendResult;
+}
+
+
+/**
+ * Set if the config file should be created by the autoexecconfig include itself if it doesn't exist.
+ *
+ * @param create True if config file should be created, false otherwise.
+ * @noreturn
+ */
+stock void AutoExecConfig_SetCreateFile(bool create)
+{
+ g_bCreateFile = create;
+}
+
+
+/**
+ * Set if the config file's folder should be created by the autoexecconfig include itself if it doesn't exist.
+ * Note: Must be used before AutoExecConfig_SetFile as the potential creation of it happens there
+ *
+ * @param create True if config file should be created, false otherwise.
+ * @param mode Folder permission mode, default is u=rwx,g=rx,o=rx.
+ * @noreturn
+ */
+stock void AutoExecConfig_SetCreateDirectory(bool create, int mode=FPERM_U_READ|FPERM_U_WRITE|FPERM_U_EXEC|FPERM_G_READ|FPERM_G_EXEC|FPERM_O_READ|FPERM_O_EXEC)
+{
+ g_bCreateDirectory = create;
+ g_bCreateDirectoryMode = mode;
+}
+
+
+/**
+ * Returns if the config file should be created if it doesn't exist.
+ *
+ * @return Returns true, if the config file should be created or false if it should not.
+ */
+stock bool AutoExecConfig_GetCreateFile()
+{
+ return g_bCreateFile;
+}
+
+
+/**
+ * Set the plugin for which the config file should be created.
+ * Set to null to use the calling plugin.
+ * Used to print the correct filename in the top comment when creating the file.
+ *
+ * @param plugin The plugin to create convars for or null to use the calling plugin.
+ * @noreturn
+ */
+stock void AutoExecConfig_SetPlugin(Handle plugin)
+{
+ g_hPluginHandle = plugin;
+}
+
+
+/**
+ * Returns the plugin's handle for which the config file is created.
+ *
+ * @return The plugin handle
+ */
+stock Handle AutoExecConfig_GetPlugin()
+{
+ return g_hPluginHandle;
+}
+
+
+/**
+ * Set the global autoconfigfile used by functions of this file.
+ * Note: does not support subfolders like folder1/folder2
+ *
+ * @param file Name of the config file, path and .cfg extension is being added if not given.
+ * @param folder Folder under cfg/ to use. By default this is "sourcemod."
+ * @return True if formatter returned success, false otherwise.
+*/
+stock bool AutoExecConfig_SetFile(char[] file, char[] folder="sourcemod")
+{
+ Format(g_sConfigFile, sizeof(g_sConfigFile), "%s", file);
+
+ // Global buffers for cfg execution
+ strcopy(g_sRawFileName, sizeof(g_sRawFileName), file);
+ strcopy(g_sFolderPath, sizeof(g_sFolderPath), folder);
+
+
+ // Format the filename
+ return AutoExecConfig_FormatFileName(g_sConfigFile, sizeof(g_sConfigFile), folder) == AUTOEXEC_FORMAT_SUCCESS;
+}
+
+
+
+
+
+
+/**
+ * Get the formatted autoconfigfile used by functions of this file.
+ *
+ * @param buffer String to format.
+ * @param size Maximum size of buffer
+ * @return True if filename was set, false otherwise.
+*/
+stock bool AutoExecConfig_GetFile(char[] buffer,int size)
+{
+ if (strlen(g_sConfigFile) > 0)
+ {
+ strcopy(buffer, size, g_sConfigFile);
+
+ return true;
+ }
+
+ // Security for decl users
+ buffer[0] = '\0';
+
+ return false;
+}
+
+
+
+
+
+
+/**
+ * Creates a convar and appends it to the autoconfigfile if not found.
+ * FCVAR_DONTRECORD will be skipped.
+ *
+ * @param name Name of new convar.
+ * @param defaultValue String containing the default value of new convar.
+ * @param description Optional description of the convar.
+ * @param flags Optional bitstring of flags determining how the convar should be handled. See FCVAR_* constants for more details.
+ * @param hasMin Optional boolean that determines if the convar has a minimum value.
+ * @param min Minimum floating point value that the convar can have if hasMin is true.
+ * @param hasMax Optional boolean that determines if the convar has a maximum value.
+ * @param max Maximum floating point value that the convar can have if hasMax is true.
+ * @return A handle to the newly created convar. If the convar already exists, a handle to it will still be returned.
+ * @error Convar name is blank or is the same as an existing console command.
+*/
+stock ConVar AutoExecConfig_CreateConVar(const char[] name, const char[] defaultValue, const char[] description="", int flags=0, bool hasMin=false, float min=0.0, bool hasMax=false, float max=0.0)
+{
+ // If configfile was set and convar has no dontrecord flag
+ if (!(flags & FCVAR_DONTRECORD) && strlen(g_sConfigFile) > 0)
+ {
+ // Reset the results
+ g_iLastFindResult = -1;
+ g_iLastAppendResult = -1;
+
+
+ // Add it if not found
+ char buffer[64];
+
+ g_iLastFindResult = AutoExecConfig_FindValue(name, buffer, sizeof(buffer), true);
+
+ // We only add this convar if it doesn't exist, or the file doesn't exist and it should be auto-generated
+ if (g_iLastFindResult == AUTOEXEC_FIND_NOT_FOUND || (g_iLastFindResult == AUTOEXEC_FIND_FILE_NOT_FOUND && g_bCreateFile))
+ {
+ g_iLastAppendResult = AutoExecConfig_AppendValue(name, defaultValue, description, flags, hasMin, min, hasMax, max);
+ }
+ }
+
+
+ // Create the convar
+ return CreateConVar(name, defaultValue, description, flags, hasMin, min, hasMax, max);
+}
+
+
+
+
+/**
+ * Executes the autoconfigfile and adds it to the OnConfigsExecuted forward.
+ * If we didn't create it ourselves we let SourceMod create it.
+ *
+ * @noreturn
+*/
+stock void AutoExecConfig_ExecuteFile()
+{
+ // Only let sourcemod create the file, if we didn't do that already.
+ AutoExecConfig(!g_bCreateFile, g_sRawFileName, g_sFolderPath);
+}
+
+
+
+
+
+/**
+ * Formats a autoconfigfile, prefixes path and adds .cfg extension if missing.
+ *
+ * @param buffer String to format.
+ * @param size Maximum size of buffer.
+ * @return Returns one of the AUTOEXEC_FORMAT values..
+*/
+stock static int AutoExecConfig_FormatFileName(char[] buffer, int size, char[] folder="sourcemod")
+{
+ // No config set
+ if (strlen(g_sConfigFile) < 1)
+ {
+ return AUTOEXEC_NO_CONFIG;
+ }
+
+
+ // Can't be an cfgfile
+ if (StrContains(g_sConfigFile, ".cfg") != -1 && strlen(g_sConfigFile) < 4)
+ {
+ return AUTOEXEC_FORMAT_BAD_FILENAME;
+ }
+
+
+ // Pathprefix
+ char pathprefixbuffer[PLATFORM_MAX_PATH];
+ if (strlen(folder) > 0)
+ {
+ Format(pathprefixbuffer, sizeof(pathprefixbuffer), "cfg/%s/", folder);
+
+ if (g_bCreateDirectory && !DirExists(pathprefixbuffer))
+ {
+ CreateDirectory(pathprefixbuffer, g_bCreateDirectoryMode);
+ }
+ }
+ else
+ {
+ Format(pathprefixbuffer, sizeof(pathprefixbuffer), "cfg/");
+ }
+
+
+ char filebuffer[PLATFORM_MAX_PATH];
+ filebuffer[0] = '\0';
+
+ // Add path if file doesn't begin with it
+ if (StrContains(buffer, pathprefixbuffer) != 0)
+ {
+ StrCat(filebuffer, sizeof(filebuffer), pathprefixbuffer);
+ }
+
+ StrCat(filebuffer, sizeof(filebuffer), g_sConfigFile);
+
+
+ // Add .cfg extension if file doesn't end with it
+ if (StrContains(filebuffer[strlen(filebuffer) - 4], ".cfg") != 0)
+ {
+ StrCat(filebuffer, sizeof(filebuffer), ".cfg");
+ }
+
+ strcopy(buffer, size, filebuffer);
+
+ return AUTOEXEC_FORMAT_SUCCESS;
+}
+
+
+
+
+
+
+/**
+ * Appends a convar to the global autoconfigfile
+ *
+ * @param name Name of new convar.
+ * @param defaultValue String containing the default value of new convar.
+ * @param description Optional description of the convar.
+ * @param flags Optional bitstring of flags determining how the convar should be handled. See FCVAR_* constants for more details.
+ * @param hasMin Optional boolean that determines if the convar has a minimum value.
+ * @param min Minimum floating point value that the convar can have if hasMin is true.
+ * @param hasMax Optional boolean that determines if the convar has a maximum value.
+ * @param max Maximum floating point value that the convar can have if hasMax is true.
+ * @return Returns one of the AUTOEXEC_APPEND values
+*/
+stock int AutoExecConfig_AppendValue(const char[] name, const char[] defaultValue, const char[] description, int flags, bool hasMin, float min, bool hasMax, float max)
+{
+ // No config set
+ if (strlen(g_sConfigFile) < 1)
+ {
+ return AUTOEXEC_NO_CONFIG;
+ }
+
+
+ char filebuffer[PLATFORM_MAX_PATH];
+ strcopy(filebuffer, sizeof(filebuffer), g_sConfigFile);
+
+
+ //PrintToServer("pathbuffer: %s", filebuffer);
+
+ bool bFileExists = FileExists(filebuffer);
+
+ if (g_bCreateFile || bFileExists)
+ {
+ // If the file already exists we open it in append mode, otherwise we use a write mode which creates the file
+ File fFile = OpenFile(filebuffer, (bFileExists ? "a" : "w"));
+ char writebuffer[2048];
+
+
+ if (fFile == null)
+ {
+ return AUTOEXEC_APPEND_BAD_HANDLE;
+ }
+
+ // We just created the file, so add some header about version and stuff
+ if (g_bCreateFile && !bFileExists)
+ {
+ fFile.WriteLine( "// This file was auto-generated by AutoExecConfig v%s (%s)", AUTOEXECCONFIG_VERSION, AUTOEXECCONFIG_URL);
+
+ GetPluginFilename(g_hPluginHandle, writebuffer, sizeof(writebuffer));
+ Format(writebuffer, sizeof(writebuffer), "// ConVars for plugin \"%s\"", writebuffer);
+ fFile.WriteLine(writebuffer);
+ }
+
+ // Spacer
+ fFile.WriteLine("\n");
+
+
+ // This is used for multiline comments
+ int newlines = GetCharCountInStr('\n', description);
+ if (newlines == 0)
+ {
+ // We have no newlines, we can write the description to the file as is
+ Format(writebuffer, sizeof(writebuffer), "// %s", description);
+ fFile.WriteLine(writebuffer);
+ }
+ else
+ {
+ char[][] newlineBuf = new char[newlines +1][2048];
+ ExplodeString(description, "\n", newlineBuf, newlines +1, 2048, false);
+
+ // Each newline gets a commented newline
+ for (int i; i <= newlines; i++)
+ {
+ if (strlen(newlineBuf[i]) > 0)
+ {
+ fFile.WriteLine("// %s", newlineBuf[i]);
+ }
+ }
+ }
+
+
+ // Descspacer
+ fFile.WriteLine("// -");
+
+
+ // Default
+ Format(writebuffer, sizeof(writebuffer), "// Default: \"%s\"", defaultValue);
+ fFile.WriteLine(writebuffer);
+
+
+ // Minimum
+ if (hasMin)
+ {
+ Format(writebuffer, sizeof(writebuffer), "// Minimum: \"%f\"", min);
+ fFile.WriteLine(writebuffer);
+ }
+
+
+ // Maximum
+ if (hasMax)
+ {
+ Format(writebuffer, sizeof(writebuffer), "// Maximum: \"%f\"", max);
+ fFile.WriteLine(writebuffer);
+ }
+
+
+ // Write end and defaultvalue
+ Format(writebuffer, sizeof(writebuffer), "%s \"%s\"", name, defaultValue);
+ fFile.WriteLine(writebuffer);
+
+
+ fFile.Close();
+
+ return AUTOEXEC_APPEND_SUCCESS;
+ }
+
+ return AUTOEXEC_APPEND_FILE_NOT_FOUND;
+}
+
+
+
+
+
+
+/**
+ * Returns a convar's value from the global autoconfigfile
+ *
+ * @param cvar Cvar to search for.
+ * @param value Buffer to store result into.
+ * @param size Maximum size of buffer.
+ * @param caseSensitive Whether or not the search should be case sensitive.
+ * @return Returns one of the AUTOEXEC_FIND values
+*/
+stock int AutoExecConfig_FindValue(const char[] cvar, char[] value, int size, bool caseSensitive=false)
+{
+ // Security for decl users
+ value[0] = '\0';
+
+
+ // No config set
+ if (strlen(g_sConfigFile) < 1)
+ {
+ return AUTOEXEC_NO_CONFIG;
+ }
+
+
+ char filebuffer[PLATFORM_MAX_PATH];
+ strcopy(filebuffer, sizeof(filebuffer), g_sConfigFile);
+
+
+
+ //PrintToServer("pathbuffer: %s", filebuffer);
+
+ bool bFileExists = FileExists(filebuffer);
+
+ // We want to create the config file and it doesn't exist yet.
+ if (g_bCreateFile && !bFileExists)
+ {
+ return AUTOEXEC_FIND_FILE_NOT_FOUND;
+ }
+
+
+ if (bFileExists)
+ {
+ File fFile = OpenFile(filebuffer, "r");
+ int valuestart;
+ int valueend;
+ int cvarend;
+
+ // Just an reminder to self, leave the values that high
+ char sConvar[64];
+ char sValue[64];
+ char readbuffer[2048];
+ char copybuffer[2048];
+
+ if (fFile == null)
+ {
+ return AUTOEXEC_FIND_BAD_HANDLE;
+ }
+
+
+ while (!fFile.EndOfFile() && fFile.ReadLine(readbuffer, sizeof(readbuffer)))
+ {
+ // Is a comment or not valid
+ if (IsCharSpace(readbuffer[0]) || readbuffer[0] == '/' || (!IsCharNumeric(readbuffer[0]) && !IsCharAlpha(readbuffer[0])) )
+ {
+ continue;
+ }
+
+
+ // Has not enough spaces, must have at least 1
+ if (GetCharCountInStr(' ', readbuffer) < 1)
+ {
+ continue;
+ }
+
+
+ // Ignore cvars which aren't quoted
+ if (GetCharCountInStr('"', readbuffer) != 2)
+ {
+ continue;
+ }
+
+
+
+ // Get the start of the value
+ if ( (valuestart = StrContains(readbuffer, "\"")) == -1 )
+ {
+ continue;
+ }
+
+
+ // Get the end of the value
+ if ( (valueend = StrContains(readbuffer[valuestart+1], "\"")) == -1 )
+ {
+ continue;
+ }
+
+
+ // Get the start of the cvar,
+ if ( (cvarend = StrContains(readbuffer, " ")) == -1 || cvarend >= valuestart)
+ {
+ continue;
+ }
+
+
+ // Skip if cvarendindex is before valuestartindex
+ if (cvarend >= valuestart)
+ {
+ continue;
+ }
+
+
+ // Convar
+ // Tempcopy for security
+ strcopy(copybuffer, sizeof(copybuffer), readbuffer);
+ copybuffer[cvarend] = '\0';
+
+ strcopy(sConvar, sizeof(sConvar), copybuffer);
+
+
+ // Value
+ // Tempcopy for security
+ strcopy(copybuffer, sizeof(copybuffer), readbuffer[valuestart+1]);
+ copybuffer[valueend] = '\0';
+
+ strcopy(sValue, sizeof(sValue), copybuffer);
+
+
+ //PrintToServer("Cvar %s has a value of %s", sConvar, sValue);
+
+ if (StrEqual(sConvar, cvar, caseSensitive))
+ {
+ Format(value, size, "%s", sConvar);
+
+ fFile.Close();
+ return AUTOEXEC_FIND_SUCCESS;
+ }
+ }
+
+ fFile.Close();
+ return AUTOEXEC_FIND_NOT_FOUND;
+ }
+
+
+ return AUTOEXEC_FIND_FILE_NOT_FOUND;
+}
+
+
+
+
+
+
+/**
+ * Cleans the global autoconfigfile from too much spaces
+ *
+ * @return One of the AUTOEXEC_CLEAN values.
+*/
+stock int AutoExecConfig_CleanFile()
+{
+ // No config set
+ if (strlen(g_sConfigFile) < 1)
+ {
+ return AUTOEXEC_NO_CONFIG;
+ }
+
+
+ char sfile[PLATFORM_MAX_PATH];
+ strcopy(sfile, sizeof(sfile), g_sConfigFile);
+
+
+ // Security
+ if (!FileExists(sfile))
+ {
+ return AUTOEXEC_CLEAN_FILE_NOT_FOUND;
+ }
+
+
+
+ char sfile2[PLATFORM_MAX_PATH];
+ Format(sfile2, sizeof(sfile2), "%s_tempcopy", sfile);
+
+
+ char readbuffer[2048];
+ int count;
+ bool firstreached;
+
+
+ // Open files
+ File fFile1 = OpenFile(sfile, "r");
+ File fFile2 = OpenFile(sfile2, "w");
+
+
+
+ // Check filehandles
+ if (fFile1 == null || fFile2 == null)
+ {
+ if (fFile1 != null)
+ {
+ //PrintToServer("Handle1 invalid");
+ fFile1.Close();
+ }
+
+ if (fFile2 != null)
+ {
+ //PrintToServer("Handle2 invalid");
+ fFile2.Close();
+ }
+
+ return AUTOEXEC_CLEAN_BAD_HANDLE;
+ }
+
+
+
+ while (!fFile1.EndOfFile() && fFile1.ReadLine(readbuffer, sizeof(readbuffer)))
+ {
+ // Is space
+ if (IsCharSpace(readbuffer[0]))
+ {
+ count++;
+ }
+ // No space, count from start
+ else
+ {
+ count = 0;
+ }
+
+
+ // Don't write more than 1 space if seperation after informations have been reached
+ if (count < 2 || !firstreached)
+ {
+ ReplaceString(readbuffer, sizeof(readbuffer), "\n", "");
+ fFile2.WriteLine(readbuffer);
+ }
+
+
+ // First bigger seperation after informations has been reached
+ if (count == 2)
+ {
+ firstreached = true;
+ }
+ }
+
+
+ fFile1.Close();
+ fFile2.Close();
+
+
+ // This might be a risk, for now it works
+ DeleteFile(sfile);
+ RenameFile(sfile, sfile2);
+
+ return AUTOEXEC_CLEAN_SUCCESS;
+}
+
+
+
+
+
+
+/**
+ * Returns how many times the given char occures in the given string.
+ *
+ * @param str String to search for in.
+ * @return Occurences of the given char found in string.
+*/
+stock static int GetCharCountInStr(int character, const char[] str)
+{
+ int len = strlen(str);
+ int count;
+
+ for (int i; i < len; i++)
+ {
+ if (str[i] == character)
+ {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+
+
+
+
+
+#pragma deprecated
+stock bool AutoExecConfig_CacheConvars()
+{
+ return false;
+}
diff --git a/sourcemod/scripting/include/colors.inc b/sourcemod/scripting/include/colors.inc
new file mode 100644
index 0000000..3ce8b6a
--- /dev/null
+++ b/sourcemod/scripting/include/colors.inc
@@ -0,0 +1,945 @@
+/**************************************************************************
+ * *
+ * Colored Chat Functions *
+ * Author: exvel, Editor: Popoklopsi, Powerlord, Bara *
+ * Version: 1.2.3 *
+ * by modified by 1NutWunDeR *
+ **************************************************************************/
+
+/*Info: purple works only with CPrintToChat and CPrintToChatAll and without {blue} in the same string
+ (volvo gave them the same color code and saytext2 overrides purple with your current teamcolor.)*/
+
+#if defined _colors_included
+ #endinput
+#endif
+#define _colors_included
+
+#define MAX_MESSAGE_LENGTH 320
+#define MAX_COLORS 17
+
+#define SERVER_INDEX 0
+#define NO_INDEX -1
+#define NO_PLAYER -2
+
+enum Colors
+{
+ Color_Default = 0,
+ Color_Darkred,
+ Color_Green,
+ Color_Lightgreen,
+ Color_Red,
+ Color_Blue,
+ Color_Olive,
+ Color_Lime,
+ Color_Orange,
+ Color_Purple,
+ Color_Grey,
+ Color_Yellow,
+ Color_Lightblue,
+ Color_Steelblue,
+ Color_Darkblue,
+ Color_Pink,
+ Color_Lightred,
+}
+
+/* Colors' properties */
+// {"{default}", "{darkred}", "{green}", "{lightgreen}", "{orange}", "{blue}", "{olive}", "{lime}", "{red}", "{purple}", "{grey}", "{yellow}", "{lightblue}", "{steelblue}", "{darkblue}", "{pink}", "{lightred}"};
+new String:CTag[][] = {"{d}", "{dr}", "{gr}", "{lg}", "{o}", "{b}", "{ol}", "{l}", "{r}", "{p}", "{g}", "{y}", "{lb}", "{sb}", "{db}", "{pi}", "{lr}"};
+new String:CTagCode[][] = {"\x01", "\x02", "\x04", "\x03", "\x03", "\x03", "\x05", "\x06", "\x07", "\x03", "\x08", "\x09","\x0A","\x0B","\x0C","\x0E","\x0F"};
+new bool:CTagReqSayText2[] = {false, false, false, true, true, true, false, false, false, false, false, false, false, false, false, false, false};
+new bool:CEventIsHooked = false;
+new bool:CSkipList[MAXPLAYERS+1] = {false,...};
+
+/* Game default profile */
+new bool:CProfile_Colors[] = {true, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
+new CProfile_TeamIndex[] = {NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX, NO_INDEX};
+new bool:CProfile_SayText2 = false;
+
+
+static Handle:sm_show_activity = INVALID_HANDLE;
+
+/**
+ * Prints a message to a specific client in the chat area.
+ * Supports color tags.
+ *
+ * @param client Client index.
+ * @param szMessage Message (formatting rules).
+ * @return No return
+ *
+ * On error/Errors: If the client is not connected an error will be thrown.
+ */
+stock CPrintToChat(client, const String:szMessage[], any:...)
+{
+ if (client <= 0 || client > MaxClients)
+ ThrowError("Invalid client index %d", client);
+
+ if (!IsClientInGame(client))
+ ThrowError("Client %d is not in game", client);
+
+ decl String:szBuffer[MAX_MESSAGE_LENGTH];
+ decl String:szCMessage[MAX_MESSAGE_LENGTH];
+
+ SetGlobalTransTarget(client);
+
+ Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage);
+ VFormat(szCMessage, sizeof(szCMessage), szBuffer, 3);
+
+ new index = CFormat(szCMessage, sizeof(szCMessage));
+
+ if (index == NO_INDEX)
+ PrintToChat(client, "%s", szCMessage);
+ else
+ CSayText2(client, index, szCMessage);
+}
+
+/**
+ * Reples to a message in a command. A client index of 0 will use PrintToServer().
+ * If the command was from the console, PrintToConsole() is used. If the command was from chat, CPrintToChat() is used.
+ * Supports color tags.
+ *
+ * @param client Client index, or 0 for server.
+ * @param szMessage Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @return No return
+ *
+ * On error/Errors: If the client is not connected or invalid.
+ */
+stock CReplyToCommand(client, const String:szMessage[], any:...)
+{
+ decl String:szCMessage[MAX_MESSAGE_LENGTH];
+ SetGlobalTransTarget(client);
+ VFormat(szCMessage, sizeof(szCMessage), szMessage, 3);
+
+ if (client == 0)
+ {
+ CRemoveTags(szCMessage, sizeof(szCMessage));
+ PrintToServer("%s", szCMessage);
+ }
+ else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE)
+ {
+ CRemoveTags(szCMessage, sizeof(szCMessage));
+ PrintToConsole(client, "%s", szCMessage);
+ }
+ else
+ {
+ CPrintToChat(client, "%s", szCMessage);
+ }
+}
+
+/**
+ * Reples to a message in a command. A client index of 0 will use PrintToServer().
+ * If the command was from the console, PrintToConsole() is used. If the command was from chat, CPrintToChat() is used.
+ * Supports color tags.
+ *
+ * @param client Client index, or 0 for server.
+ * @param author Author index whose color will be used for teamcolor tag.
+ * @param szMessage Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @return No return
+ *
+ * On error/Errors: If the client is not connected or invalid.
+ */
+stock CReplyToCommandEx(client, author, const String:szMessage[], any:...)
+{
+ decl String:szCMessage[MAX_MESSAGE_LENGTH];
+ SetGlobalTransTarget(client);
+ VFormat(szCMessage, sizeof(szCMessage), szMessage, 4);
+
+ if (client == 0)
+ {
+ CRemoveTags(szCMessage, sizeof(szCMessage));
+ PrintToServer("%s", szCMessage);
+ }
+ else if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE)
+ {
+ CRemoveTags(szCMessage, sizeof(szCMessage));
+ PrintToConsole(client, "%s", szCMessage);
+ }
+ else
+ {
+ CPrintToChatEx(client, author, "%s", szCMessage);
+ }
+}
+
+/**
+ * Prints a message to all clients in the chat area.
+ * Supports color tags.
+ *
+ * @param client Client index.
+ * @param szMessage Message (formatting rules)
+ * @return No return
+ */
+stock CPrintToChatAll(const String:szMessage[], any:...)
+{
+ decl String:szBuffer[MAX_MESSAGE_LENGTH];
+
+ for (new i = 1; i <= MaxClients; i++)
+ {
+ if (IsClientInGame(i) && !IsFakeClient(i) && !CSkipList[i])
+ {
+ SetGlobalTransTarget(i);
+ VFormat(szBuffer, sizeof(szBuffer), szMessage, 2);
+
+ CPrintToChat(i, "%s", szBuffer);
+ }
+
+ CSkipList[i] = false;
+ }
+}
+
+/**
+ * Prints a message to a specific client in the chat area.
+ * Supports color tags and teamcolor tag.
+ *
+ * @param client Client index.
+ * @param author Author index whose color will be used for teamcolor tag.
+ * @param szMessage Message (formatting rules).
+ * @return No return
+ *
+ * On error/Errors: If the client or author are not connected an error will be thrown.
+ */
+stock CPrintToChatEx(client, author, const String:szMessage[], any:...)
+{
+ if (client <= 0 || client > MaxClients)
+ ThrowError("Invalid client index %d", client);
+
+ if (!IsClientInGame(client))
+ ThrowError("Client %d is not in game", client);
+
+ if (author < 0 || author > MaxClients)
+ ThrowError("Invalid client index %d", author);
+
+ decl String:szBuffer[MAX_MESSAGE_LENGTH];
+ decl String:szCMessage[MAX_MESSAGE_LENGTH];
+
+ SetGlobalTransTarget(client);
+
+ Format(szBuffer, sizeof(szBuffer), "\x01%s", szMessage);
+ VFormat(szCMessage, sizeof(szCMessage), szBuffer, 4);
+
+ new index = CFormat(szCMessage, sizeof(szCMessage), author);
+
+ if (index == NO_INDEX)
+ PrintToChat(client, "%s", szCMessage);
+ else
+ CSayText2(client, author, szCMessage);
+}
+
+/**
+ * Prints a message to all clients in the chat area.
+ * Supports color tags and teamcolor tag.
+ *
+ * @param author Author index whos color will be used for teamcolor tag.
+ * @param szMessage Message (formatting rules).
+ * @return No return
+ *
+ * On error/Errors: If the author is not connected an error will be thrown.
+ */
+stock CPrintToChatAllEx(author, const String:szMessage[], any:...)
+{
+ if (author < 0 || author > MaxClients)
+ ThrowError("Invalid client index %d", author);
+
+ if (!IsClientInGame(author))
+ ThrowError("Client %d is not in game", author);
+
+ decl String:szBuffer[MAX_MESSAGE_LENGTH];
+
+ for (new i = 1; i <= MaxClients; i++)
+ {
+ if (IsClientInGame(i) && !IsFakeClient(i) && !CSkipList[i])
+ {
+ SetGlobalTransTarget(i);
+ VFormat(szBuffer, sizeof(szBuffer), szMessage, 3);
+
+ CPrintToChatEx(i, author, "%s", szBuffer);
+ }
+
+ CSkipList[i] = false;
+ }
+}
+
+/**
+ * Removes color tags from the string.
+ *
+ * @param szMessage String.
+ * @return No return
+ */
+stock CRemoveTags(String:szMessage[], maxlength)
+{
+ for (new i = 0; i < MAX_COLORS; i++)
+ ReplaceString(szMessage, maxlength, CTag[i], "", false);
+
+ ReplaceString(szMessage, maxlength, "{teamcolor}", "", false);
+}
+
+/**
+ * Checks whether a color is allowed or not
+ *
+ * @param tag Color Tag.
+ * @return True when color is supported, otherwise false
+ */
+stock CColorAllowed(Colors:color)
+{
+ if (!CEventIsHooked)
+ {
+ CSetupProfile();
+
+ CEventIsHooked = true;
+ }
+
+ return CProfile_Colors[color];
+}
+
+/**
+ * Replace the color with another color
+ * Handle with care!
+ *
+ * @param color color to replace.
+ * @param newColor color to replace with.
+ * @noreturn
+ */
+stock CReplaceColor(Colors:color, Colors:newColor)
+{
+ if (!CEventIsHooked)
+ {
+ CSetupProfile();
+
+ CEventIsHooked = true;
+ }
+
+ CProfile_Colors[color] = CProfile_Colors[newColor];
+ CProfile_TeamIndex[color] = CProfile_TeamIndex[newColor];
+
+ CTagReqSayText2[color] = CTagReqSayText2[newColor];
+ Format(CTagCode[color], sizeof(CTagCode[]), CTagCode[newColor])
+}
+
+/**
+ * This function should only be used right in front of
+ * CPrintToChatAll or CPrintToChatAllEx and it tells
+ * to those funcions to skip specified client when printing
+ * message to all clients. After message is printed client will
+ * no more be skipped.
+ *
+ * @param client Client index
+ * @return No return
+ */
+stock CSkipNextClient(client)
+{
+ if (client <= 0 || client > MaxClients)
+ ThrowError("Invalid client index %d", client);
+
+ CSkipList[client] = true;
+}
+
+/**
+ * Replaces color tags in a string with color codes
+ *
+ * @param szMessage String.
+ * @param maxlength Maximum length of the string buffer.
+ * @return Client index that can be used for SayText2 author index
+ *
+ * On error/Errors: If there is more then one team color is used an error will be thrown.
+ */
+stock CFormat(String:szMessage[], maxlength, author=NO_INDEX)
+{
+ decl String:szGameName[30];
+
+ GetGameFolderName(szGameName, sizeof(szGameName));
+
+ /* Hook event for auto profile setup on map start */
+ if (!CEventIsHooked)
+ {
+ CSetupProfile();
+ HookEvent("server_spawn", CEvent_MapStart, EventHookMode_PostNoCopy);
+
+ CEventIsHooked = true;
+ }
+
+ new iRandomPlayer = NO_INDEX;
+
+ // On CS:GO set invisible precolor
+ if (StrEqual(szGameName, "csgo", false))
+ Format(szMessage, maxlength, " \x01\x0B\x01%s", szMessage);
+
+ /* If author was specified replace {teamcolor} tag */
+ if (author != NO_INDEX)
+ {
+ if (CProfile_SayText2)
+ {
+ ReplaceString(szMessage, maxlength, "{teamcolor}", "\x03", false);
+
+ iRandomPlayer = author;
+ }
+ /* If saytext2 is not supported by game replace {teamcolor} with green tag */
+ else
+ ReplaceString(szMessage, maxlength, "{teamcolor}", CTagCode[Color_Green], false);
+ }
+ else
+ ReplaceString(szMessage, maxlength, "{teamcolor}", "", false);
+
+ /* For other color tags we need a loop */
+ for (new i = 0; i < MAX_COLORS; i++)
+ {
+ /* If tag not found - skip */
+ if (StrContains(szMessage, CTag[i], false) == -1)
+ continue;
+
+ /* If tag is not supported by game replace it with green tag */
+ else if (!CProfile_Colors[i])
+ ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green], false);
+
+ /* If tag doesn't need saytext2 simply replace */
+ else if (!CTagReqSayText2[i])
+ ReplaceString(szMessage, maxlength, CTag[i], CTagCode[i], false);
+
+ /* Tag needs saytext2 */
+ else
+ {
+ /* If saytext2 is not supported by game replace tag with green tag */
+ if (!CProfile_SayText2)
+ ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green], false);
+
+ /* Game supports saytext2 */
+ else
+ {
+ /* If random player for tag wasn't specified replace tag and find player */
+ if (iRandomPlayer == NO_INDEX)
+ {
+ /* Searching for valid client for tag */
+ iRandomPlayer = CFindRandomPlayerByTeam(CProfile_TeamIndex[i]);
+
+ /* If player not found replace tag with green color tag */
+ if (iRandomPlayer == NO_PLAYER)
+ ReplaceString(szMessage, maxlength, CTag[i], CTagCode[Color_Green], false);
+
+ /* If player was found simply replace */
+ else
+ ReplaceString(szMessage, maxlength, CTag[i], CTagCode[i], false);
+
+ }
+ /* If found another team color tag throw error */
+ else
+ {
+ //ReplaceString(szMessage, maxlength, CTag[i], "");
+ ThrowError("Using two team colors in one message is not allowed");
+ }
+ }
+
+ }
+ }
+
+ return iRandomPlayer;
+}
+
+/**
+ * Founds a random player with specified team
+ *
+ * @param color_team Client team.
+ * @return Client index or NO_PLAYER if no player found
+ */
+stock CFindRandomPlayerByTeam(color_team)
+{
+ if (color_team == SERVER_INDEX)
+ return 0;
+ else
+ {
+ for (new i = 1; i <= MaxClients; i++)
+ {
+ if (IsClientInGame(i) && GetClientTeam(i) == color_team)
+ return i;
+ }
+ }
+
+ return NO_PLAYER;
+}
+
+/**
+ * Sends a SayText2 usermessage to a client
+ *
+ * @param szMessage Client index
+ * @param maxlength Author index
+ * @param szMessage Message
+ * @return No return.
+ */
+stock CSayText2(client, author, const String:szMessage[])
+{
+ new Handle:hBuffer = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS);
+
+ if(GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf)
+ {
+ PbSetInt(hBuffer, "ent_idx", author);
+ PbSetBool(hBuffer, "chat", true);
+ PbSetString(hBuffer, "msg_name", szMessage);
+ PbAddString(hBuffer, "params", "");
+ PbAddString(hBuffer, "params", "");
+ PbAddString(hBuffer, "params", "");
+ PbAddString(hBuffer, "params", "");
+ }
+ else
+ {
+ BfWriteByte(hBuffer, author);
+ BfWriteByte(hBuffer, true);
+ BfWriteString(hBuffer, szMessage);
+ }
+
+ EndMessage();
+}
+
+/**
+ * Creates game color profile
+ * This function must be edited if you want to add more games support
+ *
+ * @return No return.
+ */
+stock CSetupProfile()
+{
+ decl String:szGameName[30];
+ GetGameFolderName(szGameName, sizeof(szGameName));
+
+ if (StrEqual(szGameName, "cstrike", false))
+ {
+ CProfile_Colors[Color_Lightgreen] = true;
+ CProfile_Colors[Color_Orange] = true;
+ CProfile_Colors[Color_Blue] = true;
+ CProfile_Colors[Color_Olive] = true;
+ CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX;
+ CProfile_TeamIndex[Color_Orange] = 2;
+ CProfile_TeamIndex[Color_Blue] = 3;
+ CProfile_SayText2 = true;
+ }
+ else if (StrEqual(szGameName, "csgo", false))
+ {
+ CProfile_Colors[Color_Red] = true;
+ CProfile_Colors[Color_Blue] = true;
+ CProfile_Colors[Color_Olive] = true;
+ CProfile_Colors[Color_Darkred] = true;
+ CProfile_Colors[Color_Lime] = true;
+ CProfile_Colors[Color_Purple] = true;
+ CProfile_Colors[Color_Grey] = true;
+ CProfile_Colors[Color_Yellow] = true;
+ CProfile_Colors[Color_Lightblue] = true;
+ CProfile_Colors[Color_Steelblue] = true;
+ CProfile_Colors[Color_Darkblue] = true;
+ CProfile_Colors[Color_Pink] = true;
+ CProfile_Colors[Color_Lightred] = true;
+ CProfile_TeamIndex[Color_Orange] = 2;
+ CProfile_TeamIndex[Color_Blue] = 3;
+ CProfile_SayText2 = true;
+ }
+ else if (StrEqual(szGameName, "tf", false))
+ {
+ CProfile_Colors[Color_Lightgreen] = true;
+ CProfile_Colors[Color_Orange] = true;
+ CProfile_Colors[Color_Blue] = true;
+ CProfile_Colors[Color_Olive] = true;
+ CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX;
+ CProfile_TeamIndex[Color_Orange] = 2;
+ CProfile_TeamIndex[Color_Blue] = 3;
+ CProfile_SayText2 = true;
+ }
+ else if (StrEqual(szGameName, "left4dead", false) || StrEqual(szGameName, "left4dead2", false))
+ {
+ CProfile_Colors[Color_Lightgreen] = true;
+ CProfile_Colors[Color_Orange] = true;
+ CProfile_Colors[Color_Blue] = true;
+ CProfile_Colors[Color_Olive] = true;
+ CProfile_TeamIndex[Color_Lightgreen] = SERVER_INDEX;
+ CProfile_TeamIndex[Color_Orange] = 3;
+ CProfile_TeamIndex[Color_Blue] = 2;
+ CProfile_SayText2 = true;
+ }
+ else if (StrEqual(szGameName, "hl2mp", false))
+ {
+ /* hl2mp profile is based on mp_teamplay convar */
+ if (GetConVarBool(FindConVar("mp_teamplay")))
+ {
+ CProfile_Colors[Color_Orange] = true;
+ CProfile_Colors[Color_Blue] = true;
+ CProfile_Colors[Color_Olive] = true;
+ CProfile_TeamIndex[Color_Orange] = 3;
+ CProfile_TeamIndex[Color_Blue] = 2;
+ CProfile_SayText2 = true;
+ }
+ else
+ {
+ CProfile_SayText2 = false;
+ CProfile_Colors[Color_Olive] = true;
+ }
+ }
+ else if (StrEqual(szGameName, "dod", false))
+ {
+ CProfile_Colors[Color_Olive] = true;
+ CProfile_SayText2 = false;
+ }
+ /* Profile for other games */
+ else
+ {
+ if (GetUserMessageId("SayText2") == INVALID_MESSAGE_ID)
+ {
+ CProfile_SayText2 = false;
+ }
+ else
+ {
+ CProfile_Colors[Color_Orange] = true;
+ CProfile_Colors[Color_Blue] = true;
+ CProfile_TeamIndex[Color_Orange] = 2;
+ CProfile_TeamIndex[Color_Blue] = 3;
+ CProfile_SayText2 = true;
+ }
+ }
+}
+
+public Action:CEvent_MapStart(Handle:event, const String:name[], bool:dontBroadcast)
+{
+ CSetupProfile();
+
+ for (new i = 1; i <= MaxClients; i++)
+ CSkipList[i] = false;
+}
+
+/**
+ * Displays usage of an admin command to users depending on the
+ * setting of the sm_show_activity cvar.
+ *
+ * This version does not display a message to the originating client
+ * if used from chat triggers or menus. If manual replies are used
+ * for these cases, then this function will suffice. Otherwise,
+ * CShowActivity2() is slightly more useful.
+ * Supports color tags.
+ *
+ * @param client Client index doing the action, or 0 for server.
+ * @param format Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @noreturn
+ * @error
+ */
+stock CShowActivity(client, const String:format[], any:...)
+{
+ if (sm_show_activity == INVALID_HANDLE)
+ sm_show_activity = FindConVar("sm_show_activity");
+
+ new String:tag[] = "[SM] ";
+
+ decl String:szBuffer[MAX_MESSAGE_LENGTH];
+ //decl String:szCMessage[MAX_MESSAGE_LENGTH];
+ new value = GetConVarInt(sm_show_activity);
+ new ReplySource:replyto = GetCmdReplySource();
+
+ new String:name[MAX_NAME_LENGTH] = "Console";
+ new String:sign[MAX_NAME_LENGTH] = "ADMIN";
+ new bool:display_in_chat = false;
+ if (client != 0)
+ {
+ if (client < 0 || client > MaxClients || !IsClientConnected(client))
+ ThrowError("Client index %d is invalid", client);
+
+ GetClientName(client, name, sizeof(name));
+ new AdminId:id = GetUserAdmin(client);
+ if (id == INVALID_ADMIN_ID
+ || !GetAdminFlag(id, Admin_Generic, Access_Effective))
+ {
+ sign = "PLAYER";
+ }
+
+ /* Display the message to the client? */
+ if (replyto == SM_REPLY_TO_CONSOLE)
+ {
+ SetGlobalTransTarget(client);
+ VFormat(szBuffer, sizeof(szBuffer), format, 3);
+
+ CRemoveTags(szBuffer, sizeof(szBuffer));
+ PrintToConsole(client, "%s%s\n", tag, szBuffer);
+ display_in_chat = true;
+ }
+ }
+ else
+ {
+ SetGlobalTransTarget(LANG_SERVER);
+ VFormat(szBuffer, sizeof(szBuffer), format, 3);
+
+ CRemoveTags(szBuffer, sizeof(szBuffer));
+ PrintToServer("%s%s\n", tag, szBuffer);
+ }
+
+ if (!value)
+ {
+ return 1;
+ }
+
+ for (new i = 1; i <= MaxClients; i++)
+ {
+ if (!IsClientInGame(i)
+ || IsFakeClient(i)
+ || (display_in_chat && i == client))
+ {
+ continue;
+ }
+ new AdminId:id = GetUserAdmin(i);
+ SetGlobalTransTarget(i);
+ if (id == INVALID_ADMIN_ID
+ || !GetAdminFlag(id, Admin_Generic, Access_Effective))
+ {
+ /* Treat this as a normal user. */
+ if ((value & 1) | (value & 2))
+ {
+ new String:newsign[MAX_NAME_LENGTH];
+ newsign = sign;
+ if ((value & 2) || (i == client))
+ {
+ newsign = name;
+ }
+ VFormat(szBuffer, sizeof(szBuffer), format, 3);
+
+ CPrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer);
+ }
+ }
+ else
+ {
+ /* Treat this as an admin user */
+ new bool:is_root = GetAdminFlag(id, Admin_Root, Access_Effective);
+ if ((value & 4)
+ || (value & 8)
+ || ((value & 16) && is_root))
+ {
+ new String:newsign[MAX_NAME_LENGTH]
+ newsign = sign;
+ if ((value & 8) || ((value & 16) && is_root) || (i == client))
+ {
+ newsign = name;
+ }
+ VFormat(szBuffer, sizeof(szBuffer), format, 3);
+
+ CPrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer);
+ }
+ }
+ }
+
+ return 1;
+}
+
+/**
+ * Same as CShowActivity(), except the tag parameter is used instead of "[SM] " (note that you must supply any spacing).
+ * Supports color tags.
+ *
+ * @param client Client index doing the action, or 0 for server.
+ * @param tags Tag to display with.
+ * @param format Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @noreturn
+ * @error
+ */
+stock CShowActivityEx(client, const String:tag[], const String:format[], any:...)
+{
+ if (sm_show_activity == INVALID_HANDLE)
+ sm_show_activity = FindConVar("sm_show_activity");
+
+ decl String:szBuffer[MAX_MESSAGE_LENGTH];
+ //decl String:szCMessage[MAX_MESSAGE_LENGTH];
+ new value = GetConVarInt(sm_show_activity);
+ new ReplySource:replyto = GetCmdReplySource();
+
+ new String:name[MAX_NAME_LENGTH] = "Console";
+ new String:sign[MAX_NAME_LENGTH] = "ADMIN";
+ new bool:display_in_chat = false;
+ if (client != 0)
+ {
+ if (client < 0 || client > MaxClients || !IsClientConnected(client))
+ ThrowError("Client index %d is invalid", client);
+
+ GetClientName(client, name, sizeof(name));
+ new AdminId:id = GetUserAdmin(client);
+ if (id == INVALID_ADMIN_ID
+ || !GetAdminFlag(id, Admin_Generic, Access_Effective))
+ {
+ sign = "PLAYER";
+ }
+
+ /* Display the message to the client? */
+ if (replyto == SM_REPLY_TO_CONSOLE)
+ {
+ SetGlobalTransTarget(client);
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ CRemoveTags(szBuffer, sizeof(szBuffer));
+ PrintToConsole(client, "%s%s\n", tag, szBuffer);
+ display_in_chat = true;
+ }
+ }
+ else
+ {
+ SetGlobalTransTarget(LANG_SERVER);
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ CRemoveTags(szBuffer, sizeof(szBuffer));
+ PrintToServer("%s%s\n", tag, szBuffer);
+ }
+
+ if (!value)
+ {
+ return 1;
+ }
+
+ for (new i = 1; i <= MaxClients; i++)
+ {
+ if (!IsClientInGame(i)
+ || IsFakeClient(i)
+ || (display_in_chat && i == client))
+ {
+ continue;
+ }
+ new AdminId:id = GetUserAdmin(i);
+ SetGlobalTransTarget(i);
+ if (id == INVALID_ADMIN_ID
+ || !GetAdminFlag(id, Admin_Generic, Access_Effective))
+ {
+ /* Treat this as a normal user. */
+ if ((value & 1) | (value & 2))
+ {
+ new String:newsign[MAX_NAME_LENGTH];
+ newsign = sign;
+ if ((value & 2) || (i == client))
+ {
+ newsign = name;
+ }
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ CPrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer);
+ }
+ }
+ else
+ {
+ /* Treat this as an admin user */
+ new bool:is_root = GetAdminFlag(id, Admin_Root, Access_Effective);
+ if ((value & 4)
+ || (value & 8)
+ || ((value & 16) && is_root))
+ {
+ new String:newsign[MAX_NAME_LENGTH];
+ newsign = sign;
+ if ((value & 8) || ((value & 16) && is_root) || (i == client))
+ {
+ newsign = name;
+ }
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ CPrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer);
+ }
+ }
+ }
+
+ return 1;
+}
+
+/**
+ * Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar.
+ * All users receive a message in their chat text, except for the originating client,
+ * who receives the message based on the current ReplySource.
+ * Supports color tags.
+ *
+ * @param client Client index doing the action, or 0 for server.
+ * @param tags Tag to prepend to the message.
+ * @param format Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @noreturn
+ * @error
+ */
+stock CShowActivity2(client, const String:tag[], const String:format[], any:...)
+{
+ if (sm_show_activity == INVALID_HANDLE)
+ sm_show_activity = FindConVar("sm_show_activity");
+
+ decl String:szBuffer[MAX_MESSAGE_LENGTH];
+ //decl String:szCMessage[MAX_MESSAGE_LENGTH];
+ new value = GetConVarInt(sm_show_activity);
+ GetCmdReplySource();
+
+ new String:name[MAX_NAME_LENGTH] = "Console";
+ new String:sign[MAX_NAME_LENGTH] = "ADMIN";
+ if (client != 0)
+ {
+ if (client < 0 || client > MaxClients || !IsClientConnected(client))
+ ThrowError("Client index %d is invalid", client);
+
+ GetClientName(client, name, sizeof(name));
+ new AdminId:id = GetUserAdmin(client);
+ if (id == INVALID_ADMIN_ID
+ || !GetAdminFlag(id, Admin_Generic, Access_Effective))
+ {
+ sign = "PLAYER";
+ }
+
+ SetGlobalTransTarget(client);
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ /* We don't display directly to the console because the chat text
+ * simply gets added to the console, so we don't want it to print
+ * twice.
+ */
+ CPrintToChatEx(client, client, "%s%s", tag, szBuffer);
+ }
+ else
+ {
+ SetGlobalTransTarget(LANG_SERVER);
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ CRemoveTags(szBuffer, sizeof(szBuffer));
+ PrintToServer("%s%s\n", tag, szBuffer);
+ }
+
+ if (!value)
+ {
+ return 1;
+ }
+
+ for (new i = 1; i <= MaxClients; i++)
+ {
+ if (!IsClientInGame(i)
+ || IsFakeClient(i)
+ || i == client)
+ {
+ continue;
+ }
+ new AdminId:id = GetUserAdmin(i);
+ SetGlobalTransTarget(i);
+ if (id == INVALID_ADMIN_ID
+ || !GetAdminFlag(id, Admin_Generic, Access_Effective))
+ {
+ /* Treat this as a normal user. */
+ if ((value & 1) | (value & 2))
+ {
+ new String:newsign[MAX_NAME_LENGTH];
+ newsign = sign;
+ if ((value & 2))
+ {
+ newsign = name;
+ }
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ CPrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer);
+ }
+ }
+ else
+ {
+ /* Treat this as an admin user */
+ new bool:is_root = GetAdminFlag(id, Admin_Root, Access_Effective);
+ if ((value & 4)
+ || (value & 8)
+ || ((value & 16) && is_root))
+ {
+ new String:newsign[MAX_NAME_LENGTH];
+ newsign = sign;
+ if ((value & 8) || ((value & 16) && is_root))
+ {
+ newsign = name;
+ }
+ VFormat(szBuffer, sizeof(szBuffer), format, 4);
+
+ CPrintToChatEx(i, client, "%s%s: %s", tag, newsign, szBuffer);
+ }
+ }
+ }
+
+ return 1;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/distbugfix.inc b/sourcemod/scripting/include/distbugfix.inc
new file mode 100644
index 0000000..6a7ed18
--- /dev/null
+++ b/sourcemod/scripting/include/distbugfix.inc
@@ -0,0 +1,261 @@
+
+#if defined _distbug_included
+ #endinput
+#endif
+#define _distbug_included
+
+#define DISTBUG_CONFIG_NAME "distbugfix"
+
+#define CHAT_PREFIX "{d}[{l}GC{d}]"
+#define CONSOLE_PREFIX "[GC]"
+#define CHAT_SPACER " {d}|{g} "
+#define DISTBUG_VERSION "2.0.0"
+
+#define MAX_STRAFES 32
+#define MAX_EDGE 32.0
+#define MAX_JUMP_FRAMES 150 // for frame based arrays
+#define MAX_BHOP_FRAMES 8
+
+#define MAX_COOKIE_SIZE 32
+
+enum
+{
+ SETTINGS_DISTBUG_ENABLED = (1 << 0),
+ SETTINGS_SHOW_VEER_BEAM = (1 << 1),
+ SETTINGS_SHOW_JUMP_BEAM = (1 << 2),
+ SETTINGS_SHOW_HUD_GRAPH = (1 << 3),
+ SETTINGS_DISABLE_STRAFE_STATS = (1 << 4),
+ SETTINGS_DISABLE_STRAFE_GRAPH = (1 << 5),
+ SETTINGS_ADV_CHAT_STATS = (1 << 6),
+}
+
+enum JumpType
+{
+ // unprintable jumptypes. only for tracking
+ JUMPTYPE_NONE,
+
+ JUMPTYPE_LJ, // longjump
+ JUMPTYPE_WJ, // weirdjump
+ JUMPTYPE_LAJ, // ladderjump
+ JUMPTYPE_BH, // bunnyhop
+ JUMPTYPE_CBH, // crouched bunnyhop
+};
+
+enum JumpDir
+{
+ JUMPDIR_FORWARDS,
+ JUMPDIR_BACKWARDS,
+ JUMPDIR_LEFT,
+ JUMPDIR_RIGHT,
+};
+
+enum StrafeType
+{
+ STRAFETYPE_OVERLAP, // IN_MOVELEFT and IN_MOVERIGHT are overlapping and sidespeed is 0
+ STRAFETYPE_NONE, // IN_MOVELEFT and IN_MOVERIGHT are both not pressed and sidespeed is 0
+
+ STRAFETYPE_LEFT, // only IN_MOVELEFT is down and sidespeed isn't 0.
+ STRAFETYPE_OVERLAP_LEFT, // IN_MOVELEFT and IN_MOVERIGHT are overlapping, but sidespeed is smaller than 0 (not 0)
+ STRAFETYPE_NONE_LEFT, // IN_MOVELEFT and IN_MOVERIGHT are both not pressed and sidespeed is smaller than 0 (not 0)
+
+ STRAFETYPE_RIGHT, // only IN_MOVERIGHT is down and sidespeed isn't 0.
+ STRAFETYPE_OVERLAP_RIGHT, // IN_MOVELEFT and IN_MOVERIGHT are overlapping, but sidespeed is bigger than 0 (not 0)
+ STRAFETYPE_NONE_RIGHT, // IN_MOVELEFT and IN_MOVERIGHT are both not pressed and sidespeed is bigger than 0 (not 0)
+};
+
+enum JumpBeamColour
+{
+ JUMPBEAM_NEUTRAL, // speed stays the same
+ JUMPBEAM_LOSS, // speed loss
+ JUMPBEAM_GAIN, // speed gain
+ JUMPBEAM_DUCK, // duck key down
+};
+
+enum struct PlayerData
+{
+ int tickCount;
+ int buttons;
+ int lastButtons;
+ int flags;
+ int lastFlags;
+ int framesOnGround;
+ int framesInAir;
+ MoveType movetype;
+ MoveType lastMovetype;
+ float stamina;
+ float lastStamina;
+ float forwardmove;
+ float lastForwardmove;
+ float sidemove;
+ float lastSidemove;
+ float gravity;
+ float angles[3];
+ float lastAngles[3];
+ float position[3];
+ float lastPosition[3];
+ float velocity[3];
+ float lastVelocity[3];
+ float lastGroundPos[3]; // last position where the player left the ground.
+ float ladderNormal[3];
+ bool lastGroundPosWalkedOff;
+ bool landedDucked;
+
+ int prespeedFog;
+ float prespeedStamina;
+
+ float jumpGroundZ;
+ float jumpPos[3];
+ float jumpAngles[3];
+ float landGroundZ;
+ float landPos[3];
+
+ int fwdReleaseFrame;
+ int jumpFrame;
+ bool trackingJump;
+ bool failedJump;
+ bool jumpGotFailstats;
+
+ // jump data
+ JumpType jumpType;
+ JumpType lastJumpType;
+ JumpDir jumpDir;
+ float jumpDistance;
+ float jumpPrespeed;
+ float jumpMaxspeed;
+ float jumpVeer;
+ float jumpAirpath;
+ float jumpSync;
+ float jumpEdge;
+ float jumpLandEdge;
+ float jumpBlockDist;
+ float jumpHeight;
+ float jumpJumpoffAngle;
+ int jumpAirtime;
+ int jumpFwdRelease;
+ int jumpOverlap;
+ int jumpDeadair;
+
+ // strafes!
+ int strafeCount;
+ float strafeSync[MAX_STRAFES];
+ float strafeGain[MAX_STRAFES];
+ float strafeLoss[MAX_STRAFES];
+ float strafeMax[MAX_STRAFES];
+ int strafeAirtime[MAX_STRAFES];
+ int strafeOverlap[MAX_STRAFES];
+ int strafeDeadair[MAX_STRAFES];
+ float strafeAvgGain[MAX_STRAFES];
+
+ float strafeAvgEfficiency[MAX_STRAFES];
+ int strafeAvgEfficiencyCount[MAX_STRAFES]; // how many samples are in strafeAvgEfficiency
+ float strafeMaxEfficiency[MAX_STRAFES];
+
+ StrafeType strafeGraph[MAX_JUMP_FRAMES];
+ float mouseGraph[MAX_JUMP_FRAMES];
+ float jumpBeamX[MAX_JUMP_FRAMES];
+ float jumpBeamY[MAX_JUMP_FRAMES];
+ JumpBeamColour jumpBeamColour[MAX_JUMP_FRAMES];
+}
+
+/**
+ * Check if player is overlapping their MOVERIGHT and MOVELEFT buttons.
+ *
+ * @param x Buttons;
+ * @return True if overlapping, false otherwise.
+ */
+stock bool IsOverlapping(int buttons, JumpDir jumpDir)
+{
+ if (jumpDir == JUMPDIR_FORWARDS || jumpDir == JUMPDIR_BACKWARDS)
+ {
+ return (buttons & IN_MOVERIGHT) && (buttons & IN_MOVELEFT);
+ }
+ // else if (jumpDir == JUMPDIR_LEFT || jumpDir == JUMPDIR_RIGHT)
+ return (buttons & IN_FORWARD) && (buttons & IN_BACK);
+}
+
+/**
+ * Checks if the player is not holding down their MOVERIGHT and MOVELEFT buttons.
+ *
+ * @param x Buttons.
+ * @return True if they're not holding either, false otherwise.
+ */
+stock bool IsDeadAirtime(int buttons, JumpDir jumpDir)
+{
+ if (jumpDir == JUMPDIR_FORWARDS || jumpDir == JUMPDIR_BACKWARDS)
+ {
+ return (!(buttons & IN_MOVERIGHT) && !(buttons & IN_MOVELEFT));
+ }
+ // else if (jumpDir == JUMPDIR_LEFT || jumpDir == JUMPDIR_RIGHT)
+ return (!(buttons & IN_FORWARD) && !(buttons & IN_BACK));
+}
+
+stock void ToggleCVar(ConVar cvar)
+{
+ cvar.BoolValue = !cvar.BoolValue;
+}
+
+stock void GetRealLandingOrigin(float landGroundZ, const float origin[3], const float velocity[3], float result[3])
+{
+ if ((origin[2] - landGroundZ) == 0.0)
+ {
+ result = origin;
+ return;
+ }
+
+ // this is like this because it works
+ float frametime = GetTickInterval();
+ float verticalDistance = origin[2] - (origin[2] + velocity[2] * frametime);
+ float fraction = (origin[2] - landGroundZ) / verticalDistance;
+
+ float addDistance[3];
+ addDistance = velocity;
+ ScaleVector(addDistance, frametime * fraction);
+
+ AddVectors(origin, addDistance, result);
+}
+
+stock int FloatSign(float value)
+{
+ if (value > 0.0)
+ {
+ return 1;
+ }
+ else if (value < 0.0)
+ {
+ return -1;
+ }
+ return 0;
+}
+
+stock int FloatSign2(float value)
+{
+ if (value >= 0.0)
+ {
+ return 1;
+ }
+ // else if (value < 0.0)
+ return -1;
+}
+
+stock void ShowPanel(int client, int duration, const char[] message)
+{
+ Event show_survival_respawn_status = CreateEvent("show_survival_respawn_status");
+ if (show_survival_respawn_status == INVALID_HANDLE)
+ {
+ return;
+ }
+
+ show_survival_respawn_status.SetString("loc_token", message);
+ show_survival_respawn_status.SetInt("duration", duration);
+ show_survival_respawn_status.SetInt("userid", -1);
+
+ if (client == -1)
+ {
+ show_survival_respawn_status.Fire();
+ }
+ else
+ {
+ show_survival_respawn_status.FireToClient(client);
+ show_survival_respawn_status.Cancel();
+ }
+}
diff --git a/sourcemod/scripting/include/gamechaos.inc b/sourcemod/scripting/include/gamechaos.inc
new file mode 100644
index 0000000..eeaf040
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos.inc
@@ -0,0 +1,20 @@
+
+// GameChaos's includes for various specific stuff
+
+#if defined _gamechaos_stocks_included
+ #endinput
+#endif
+#define _gamechaos_stocks_included
+
+#define GC_INCLUDES_VERSION 0x01_00_00
+#define GC_INCLUDES_VERSION_STRING "1.0.0"
+
+#include <gamechaos/arrays>
+#include <gamechaos/client>
+#include <gamechaos/debug>
+#include <gamechaos/maths>
+#include <gamechaos/misc>
+#include <gamechaos/strings>
+#include <gamechaos/tempents>
+#include <gamechaos/tracing>
+#include <gamechaos/vectors> \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/arrays.inc b/sourcemod/scripting/include/gamechaos/arrays.inc
new file mode 100644
index 0000000..eba62bb
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/arrays.inc
@@ -0,0 +1,52 @@
+
+#if defined _gamechaos_stocks_arrays_included
+ #endinput
+#endif
+#define _gamechaos_stocks_arrays_included
+
+#define GC_ARRAYS_VERSION 0x01_00_00
+#define GC_ARRAYS_VERSION_STRING "1.0.0"
+
+/**
+ * Copies an array into an arraylist.
+ *
+ * @param array Arraylist Handle.
+ * @param index Index in the arraylist.
+ * @param values Array to copy.
+ * @param size Size of the array to copy.
+ * @param offset Arraylist offset to set.
+ * @return Number of cells copied.
+ * @error Invalid Handle or invalid index.
+ */
+stock int GCSetArrayArrayIndexOffset(ArrayList array, int index, const any[] values, int size, int offset)
+{
+ int cells;
+ for (int i; i < size; i++)
+ {
+ array.Set(index, values[i], offset + i);
+ cells++;
+ }
+ return cells;
+}
+
+/**
+ * Copies an arraylist's specified cells to an array.
+ *
+ * @param array Arraylist Handle.
+ * @param index Index in the arraylist.
+ * @param result Array to copy to.
+ * @param size Size of the array to copy to.
+ * @param offset Arraylist offset.
+ * @return Number of cells copied.
+ * @error Invalid Handle or invalid index.
+ */
+stock int GCCopyArrayArrayIndex(const ArrayList array, int index, any[] result, int size, int offset)
+{
+ int cells;
+ for (int i = offset; i < (size + offset); i++)
+ {
+ result[i] = array.Get(index, i);
+ cells++;
+ }
+ return cells;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/client.inc b/sourcemod/scripting/include/gamechaos/client.inc
new file mode 100644
index 0000000..cb2114a
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/client.inc
@@ -0,0 +1,300 @@
+
+#if defined _gamechaos_stocks_client_included
+ #endinput
+#endif
+#define _gamechaos_stocks_client_included
+
+#define GC_CLIENT_VERSION 0x01_00_00
+#define GC_CLIENT_VERSION_STRING "1.0.0"
+
+/**
+ * Credit: Don't remember.
+ * Removes a player's weapon from the specified slot.
+ *
+ * @param client Client index.
+ * @param slot Weapon slot.
+ * @return True if removed, false otherwise.
+ */
+stock bool GCRemoveWeaponBySlot(int client, int slot)
+{
+ int entity = GetPlayerWeaponSlot(client, slot);
+ if (IsValidEdict(entity))
+ {
+ RemovePlayerItem(client, entity);
+ AcceptEntityInput(entity, "kill");
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Checks if a client is valid and not the server and optionally, whether he's alive.
+ *
+ * @param client Client index.
+ * @param alive Whether to check alive.
+ * @return True if valid, false otherwise.
+ */
+stock bool GCIsValidClient(int client, bool alive = false)
+{
+ return (client >= 1 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client) && !IsClientSourceTV(client) && (!alive || IsPlayerAlive(client)));
+}
+
+
+
+/**
+ * Gets the value of m_flForwardMove.
+ *
+ * @param client Client index.
+ * @return Value of m_flForwardMove.
+ */
+stock float GCGetClientForwardMove(int client)
+{
+ return GetEntPropFloat(client, Prop_Data, "m_flForwardMove");
+}
+
+/**
+ * Gets the value of m_flSideMove.
+ *
+ * @param client Client index.
+ * @return Value of m_flSideMove.
+ */
+stock float GCGetClientSideMove(int client)
+{
+ return GetEntPropFloat(client, Prop_Data, "m_flSideMove");
+}
+
+/**
+ * Gets the client's abs origin.
+ *
+ * @param client Client index.
+ * @return result Player's origin.
+ */
+stock float[] GCGetClientAbsOriginRet(int client)
+{
+ float result[3]
+ GetClientAbsOrigin(client, result);
+ return result;
+}
+
+/**
+ * Copies the client's velocity to a vector.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+stock void GCGetClientVelocity(int client, float result[3])
+{
+ GetEntPropVector(client, Prop_Data, "m_vecVelocity", result);
+}
+
+/**
+ * Gets the client's velocity (m_vecVelocity).
+ *
+ * @param client Client index.
+ * @return result m_vecVelocity.
+ */
+stock float[] GCGetClientVelocityRet(int client)
+{
+ float result[3]
+ GetEntPropVector(client, Prop_Data, "m_vecVelocity", result);
+ return result
+}
+
+/**
+ * Copies the client's basevelocity to a vector.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+stock void GCGetClientBaseVelocity(int client, float result[3])
+{
+ GetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", result);
+}
+
+/**
+ * Gets the client's basevelocity (m_vecBaseVelocity).
+ *
+ * @param client Client index.
+ * @return result m_vecBaseVelocity.
+ */
+stock float[] GCGetClientBaseVelocityRet(int client)
+{
+ float result[3];
+ GetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", result);
+ return result;
+}
+
+
+/**
+ * Gets the client's "m_flDuckSpeed" value.
+ *
+ * @param client Client index.
+ * @return "m_flDuckSpeed".
+ */
+stock float GCGetClientDuckSpeed(int client)
+{
+ return GetEntPropFloat(client, Prop_Send, "m_flDuckSpeed");
+}
+
+/**
+ * Gets the client's "m_flDuckAmount" value.
+ *
+ * @param client Client index.
+ * @return "m_flDuckAmount".
+ */
+stock float GCGetClientDuckAmount(int client)
+{
+ return GetEntPropFloat(client, Prop_Send, "m_flDuckAmount");
+}
+
+/**
+ * Gets the client's "m_bDucking" value.
+ *
+ * @param client Client index.
+ * @return "m_bDucking".
+ */
+stock int GCGetClientDucking(int client)
+{
+ return GetEntProp(client, Prop_Data, "m_bDucking");
+}
+
+/**
+ * Gets the client's "m_flMaxspeed" value.
+ *
+ * @param client Client index.
+ * @return "m_flMaxspeed".
+ */
+stock float GCGetClientMaxspeed(int client)
+{
+ return GetEntPropFloat(client, Prop_Send, "m_flMaxspeed");
+}
+
+/**
+ * Gets the client's "m_afButtonPressed" value.
+ *
+ * @param client Client index.
+ * @return "m_afButtonPressed".
+ */
+stock int GCGetClientButtonPressed(int client)
+{
+ return GetEntProp(client, Prop_Data, "m_afButtonPressed");
+}
+
+/**
+ * Gets the client's "m_afButtonReleased" value.
+ *
+ * @param client Client index.
+ * @return "m_afButtonReleased".
+ */
+stock int GCGetClientButtonReleased(int client)
+{
+ return GetEntProp(client, Prop_Data, "m_afButtonReleased");
+}
+
+/**
+ * Gets the client's "m_afButtonLast" value.
+ *
+ * @param client Client index.
+ * @return "m_afButtonLast".
+ */
+stock int GCGetClientButtonLast(int client)
+{
+ return GetEntProp(client, Prop_Data, "m_afButtonLast");
+}
+
+/**
+ * Gets the client's "m_afButtonForced" value.
+ *
+ * @param client Client index.
+ * @return "m_afButtonForced".
+ */
+stock int GCGetClientForcedButtons(int client)
+{
+ return GetEntProp(client, Prop_Data, "m_afButtonForced");
+}
+
+/**
+ * Gets the client's "m_flStamina" value.
+ *
+ * @param client Client index.
+ * @return "m_flStamina".
+ */
+stock float GCGetClientStamina(int client)
+{
+ return GetEntPropFloat(client, Prop_Send, "m_flStamina");
+}
+
+
+
+/**
+ * Sets the client's origin.
+ *
+ * @param client Client index.
+ * @param origin New origin.
+ */
+stock void GCSetClientAbsOrigin(int client, const float origin[3])
+{
+ SetEntPropVector(client, Prop_Data, "m_vecAbsOrigin", origin);
+}
+
+/**
+ * Sets the client's velocity.
+ *
+ * @param client Client index.
+ * @param velocity New velocity.
+ */
+stock void GCSetClientVelocity(int client, const float velocity[3])
+{
+ SetEntPropVector(client, Prop_Data, "m_vecVelocity", velocity);
+}
+
+/**
+ * Sets the client's "m_vecAbsVelocity".
+ *
+ * @param client Client index.
+ * @param velocity New "m_vecAbsVelocity".
+ */
+stock void GCSetClientAbsVelocity(int client, const float velocity[3])
+{
+ SetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity);
+}
+
+/**
+ * Sets the client's eye angles.
+ * Ang has to be a 2 member array or more
+ *
+ * @param client Client index.
+ * @param ang New eyeangles.
+ */
+stock void GCSetClientEyeAngles(int client, const float[] ang)
+{
+ SetEntPropFloat(client, Prop_Send, "m_angEyeAngles[0]", ang[0]);
+ SetEntPropFloat(client, Prop_Send, "m_angEyeAngles[1]", ang[1]);
+}
+
+
+/**
+ * Sets the client's "m_flDuckSpeed".
+ *
+ * @param client Client index.
+ * @param value New "m_flDuckSpeed".
+ */
+stock void GCSetClientDuckSpeed(int client, float value)
+{
+ SetEntPropFloat(client, Prop_Send, "m_flDuckSpeed", value);
+}
+
+stock void GCSetClientDuckAmount(int client, float value)
+{
+ SetEntPropFloat(client, Prop_Send, "m_flDuckAmount", value);
+}
+
+stock void GCSetClientForcedButtons(int client, int buttons)
+{
+ SetEntProp(client, Prop_Data, "m_afButtonForced", buttons);
+}
+
+stock void GCSetClientStamina(int client, float stamina)
+{
+ SetEntPropFloat(client, Prop_Send, "m_flStamina", stamina)
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/debug.inc b/sourcemod/scripting/include/gamechaos/debug.inc
new file mode 100644
index 0000000..4e5b7e7
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/debug.inc
@@ -0,0 +1,19 @@
+
+// gamechaos's debug stocks
+// useful stocks for debugging
+
+#if defined _gamechaos_debug_included
+ #endinput
+#endif
+#define _gamechaos_debug_included
+
+#define GC_DEBUG_VERSION 0x1_00_00
+#define GC_DEBUG_VERSION_STRING "1.0.0"
+
+#if defined GC_DEBUG
+ #define GC_ASSERT(%1) if (!(%1))SetFailState("Assertion failed: \""...#%1..."\"")
+ #define GC_DEBUGPRINT(%1) PrintToChatAll(%1)
+#else
+ #define GC_ASSERT(%1)%2;
+ #define GC_DEBUGPRINT(%1)%2;
+#endif
diff --git a/sourcemod/scripting/include/gamechaos/isvalidclient.inc b/sourcemod/scripting/include/gamechaos/isvalidclient.inc
new file mode 100644
index 0000000..bf80246
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/isvalidclient.inc
@@ -0,0 +1,16 @@
+
+#if defined _gamechaos_isvalidclient_client_included
+ #endinput
+#endif
+#define _gamechaos_isvalidclient_client_included
+
+/**
+ * Checks if a client is valid.
+ *
+ * @param client Client index.
+ * @return True if valid, false otherwise.
+ */
+stock bool IsValidClient(int client)
+{
+ return (client >= 0 && client <= MaxClients && IsValidEntity(client) && IsClientConnected(client) && IsClientInGame(client));
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/kreedzclimbing.inc b/sourcemod/scripting/include/gamechaos/kreedzclimbing.inc
new file mode 100644
index 0000000..0cbb828
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/kreedzclimbing.inc
@@ -0,0 +1,226 @@
+//
+// Useful things for making plugins for Kreedz Climbing
+//
+
+#if defined _gamechaos_kreedzclimbing_included
+ #endinput
+#endif
+#define _gamechaos_kreedzclimbing_included
+
+#define GC_KREEDZCLIMBING_VERSION 0x01_00_00
+#define GC_KREEDZCLIMBING_VERSION_STRING "1.0.0"
+
+
+#define MAX_COURSE_SIZE 128 // Reasonable maximum characters a course name can have
+#define COURSE_CVAR_COUNT 20 // the amount of Course<int> cvars
+
+// Kreedz Climbing Client Commands:
+// These may be executed by a player via the console, with / in chat, or via binds.
+
+// specmode - Cycles spectator mode (F3 by default).
+// kz_pause - Pauses the timer.
+// flare - Fires a flare.
+// gototimer | start - Returns to the last pressed start timer.
+// spectate | spec - Enters spectator mode.
+// forcespectator - Becomes a spectator no matter what (force respawns a dead player as well).
+// stoptimer - Instantly stops the player's timer.
+// climb | ct - Respawns at the map spawnpoint.
+// InvalidateTimer - Invalidates the player's timer. An invalid timer can't earn rewards for completing the course. InvalidateTimer 1 displays the message, without the 1 it does not.
+
+// Kreedz Climbing Constants
+
+// Timer state (player->m_Local->Timer_Active)
+#define TIMER_STATE_INVISIBLE 0
+#define TIMER_STATE_ACTIVE 1
+#define TIMER_STATE_INACTIVE 2
+#define TIMER_STATE_PAUSED 3
+
+// Timer flags (player_manager->m_iTimerFlags[32])
+// These are replicated flags for player's timer (most timer data is local to it's own player).
+// Note that these flags are mirrors of data local to the player - they are set to the player's
+// state every frame and cannot be changed.
+
+#define TIMER_FLAG_INVALID (1 << 0)
+#define TIMER_FLAG_ACTIVE (1 << 1) // We need to broadcast this because Timer_State is local only.
+#define TIMER_FLAG_PAUSED (1 << 2) // A paused timer cannot be active and vice versa.
+
+// Environmental Attributes (player->m_iEnvironmentalAttributes)
+#define PLAYER_ENV_ATTRIBUTES_BHOP (1 << 0)
+#define PLAYER_ENV_ATTRIBUTES_SURF (1 << 1)
+#define PLAYER_ENV_ATTRIBUTES_AUTOBHOP (1 << 2)
+#define PLAYER_ENV_ATTRIBUTES_CSGOMOVEMENT (1 << 3)
+#define PLAYER_ENV_ATTRIBUTES_CSGODUCKHULL (1 << 4)
+
+// Movement restriction flags (player->m_iMovementRestrictions) (new version of Environmental Restrictions below)
+#define PLAYER_MOVEMENT_RESTRICTION_NOJUMP (1 << 0)
+#define PLAYER_MOVEMENT_RESTRICTION_NOBHOP (1 << 1)
+#define PLAYER_MOVEMENT_RESTRICTION_NODOUBLEDUCK (1 << 2)
+
+// OBSOLETE: ONLY IN OLD MAPS: Environmental Restrictions (player->m_iEnvironmentalRestrictions), note not flags, complete integer.
+#define PLAYER_ENV_RESTRICTION_NOJUMP 1
+#define PLAYER_ENV_RESTRICTION_NOBHOP 2
+#define PLAYER_ENV_RESTRICTION_BOTH 3
+
+// Cooperative status (player->m_Local.m_multiplayercoursedata.Player1Status, Player2Status etc)
+#define COOPERATIVE_STATUS_NONE 0
+#define COOPERATIVE_STATUS_WAITING 1
+#define COOPERATIVE_STATUS_READY 2
+#define COOPERATIVE_STATUS_TIMER_ACTIVE 3
+#define COOPERATIVE_STATUS_TIMER_COMPLETE 4
+#define COOPERATIVE_STATUS_PLAYER_DISCONNECTED 5 // Player disconnected from server, waiting for them to reconnect.
+#define COOPERATIVE_STATUS_TIMER_PAUSED 6
+
+// Kreedz Climbing Button Constants
+#define IN_CHECKPOINT (1 << 25)
+#define IN_TELEPORT (1 << 26)
+#define IN_SPECTATE (1 << 27)
+//#define IN_AVAILABLE (1 << 28) // Unused
+#define IN_HOOK (1 << 29)
+
+// converts the course id from the obsolete "player_starttimer" event into the course name
+stock void GCCourseidToString(int courseid, char[] course, int size)
+{
+ char szCourseid[16];
+ if (courseid < 1 || courseid > COURSE_CVAR_COUNT)
+ {
+ return;
+ }
+ FormatEx(szCourseid, sizeof(szCourseid), "Course%i", courseid);
+ FindConVar(szCourseid).GetString(course, size);
+}
+
+stock void GCGetCurrentMapCourses(ArrayList &array)
+{
+ if (array == null)
+ {
+ // 1 for endurance bool
+ array = new ArrayList(ByteCountToCells(MAX_COURSE_SIZE) + 1);
+ }
+ else
+ {
+ array.Clear();
+ }
+
+ char course[MAX_COURSE_SIZE];
+
+ int ent;
+ while((ent = FindEntityByClassname(ent, "func_stoptimer")) != -1)
+ {
+ int courseid = GetEntProp(ent, Prop_Data, "CourseID");
+ GCCourseidToString(courseid, course, sizeof(course));
+ array.PushString(course);
+
+ bool endurance = GCIsCourseEndurance(course, ent);
+ array.Set(array.Length - 1, endurance, ByteCountToCells(MAX_COURSE_SIZE));
+ }
+
+ int courseStringtableCount;
+ int courseNamesIdx = FindStringTable("CourseNames");
+ courseStringtableCount = GetStringTableNumStrings(courseNamesIdx);
+
+ for (int i; i < courseStringtableCount; i++)
+ {
+ ReadStringTable(courseNamesIdx, i, course, sizeof(course));
+ array.PushString(course);
+
+ bool endurance = GCIsCourseEndurance(course, ent);
+ array.Set(array.Length - 1, endurance, ByteCountToCells(MAX_COURSE_SIZE));
+ }
+}
+
+stock int GCGetTimerState(int client)
+{
+ return GetEntProp(client, Prop_Send, "Timer_Active");
+}
+
+stock void GCSetTimerState(int client, int timerstate)
+{
+ SetEntProp(client, Prop_Send, "Timer_Active", timerstate);
+}
+
+stock int GCGetPlayerEnvAttributes(int client)
+{
+ return GetEntProp(client, Prop_Send, "m_iEnvironmentalAttributes");
+}
+
+stock void GCSetPlayerEnvAttributes(int client, int attributes)
+{
+ SetEntProp(client, Prop_Send, "m_iEnvironmentalAttributes", attributes);
+}
+
+stock int GCGetPlayerMovementRestrictions(int client)
+{
+ return GetEntProp(client, Prop_Send, "m_iMovementRestrictions");
+}
+
+stock void GCSetPlayerMovementRestrictions(int client, int restrictions)
+{
+ SetEntProp(client, Prop_Send, "m_iMovementRestrictions", restrictions);
+}
+
+stock void GCSetActiveCourse(int client, int course)
+{
+ int ent = FindEntityByClassname(0, "player_manager");
+ int courseOffset = FindSendPropInfo("CPlayerResource", "m_iActiveCourse");
+ SetEntData(ent, courseOffset + (client * 4), course);
+}
+
+stock int GCGetTimerFlags(int client)
+{
+ int ent = FindEntityByClassname(0, "player_manager");
+ int courseOffset = FindSendPropInfo("CPlayerResource", "m_iTimerFlags");
+ return GetEntData(ent, courseOffset + (client * 4));
+}
+
+stock bool GCInvalidateTimer(int client)
+{
+ if (~GCGetTimerFlags(client) & TIMER_FLAG_INVALID)
+ {
+ FakeClientCommand(client, "InvalidateTimer 1");
+ return true;
+ }
+
+ return false;
+}
+
+stock bool GCIsCourseEndurance(char[] course, int ent = -1)
+{
+ if (ent != -1)
+ {
+ if (IsValidEntity(ent))
+ {
+ return !!(GetEntProp(ent, Prop_Data, "m_bEnduranceCourse"));
+ }
+ }
+
+ while ((ent = FindEntityByClassname(ent, "point_climbtimer")) != -1)
+ {
+ if (IsValidEntity(ent))
+ {
+ char buffer[MAX_COURSE_SIZE];
+ GetEntPropString(ent, Prop_Data, "m_strCourseName", buffer, sizeof(buffer));
+
+ if (StrEqual(buffer, course))
+ {
+ return !!(GetEntProp(ent, Prop_Data, "m_bEnduranceCourse"));
+ }
+ }
+ }
+
+ while ((ent = FindEntityByClassname(ent, "func_stoptimer")) != -1)
+ {
+ if (IsValidEntity(ent))
+ {
+ char buffer[MAX_COURSE_SIZE];
+ int courseid = GetEntProp(ent, Prop_Data, "CourseID");
+ GCCourseidToString(courseid, buffer, sizeof(buffer));
+
+ if (StrEqual(buffer, course))
+ {
+ return !!(GetEntProp(ent, Prop_Data, "m_bEnduranceCourse"));
+ }
+ }
+ }
+
+ return false;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/maths.inc b/sourcemod/scripting/include/gamechaos/maths.inc
new file mode 100644
index 0000000..f3c94af
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/maths.inc
@@ -0,0 +1,362 @@
+
+#if defined _gamechaos_stocks_maths_included
+ #endinput
+#endif
+#define _gamechaos_stocks_maths_included
+
+#define GC_MATHS_VERSION 0x02_00_00
+#define GC_MATHS_VERSION_STRING "2.0.0"
+
+#include <gamechaos/vectors>
+
+#define GC_PI 3.14159265359
+
+#define GC_DEGREES(%1) ((%1) * 180.0 / GC_PI) // convert radians to degrees
+#define GC_RADIANS(%1) ((%1) * GC_PI / 180.0) // convert degrees to radians
+
+#define GC_FLOAT_NAN view_as<float>(0xffffffff)
+#define GC_FLOAT_INFINITY view_as<float>(0x7f800000)
+#define GC_FLOAT_NEGATIVE_INFINITY view_as<float>(0xff800000)
+
+#define GC_FLOAT_LARGEST_POSITIVE view_as<float>(0x7f7fffff)
+#define GC_FLOAT_SMALLEST_NEGATIVE view_as<float>(0xff7fffff)
+
+#define GC_FLOAT_SMALLEST_POSITIVE view_as<float>(0x00000001)
+#define GC_FLOAT_LARGEST_NEGATIVE view_as<float>(0x80000001)
+
+#define GC_INT_MAX 0x7fffffff
+#define GC_INT_MIN 0xffffffff
+
+
+/**
+ * Credit: https://stackoverflow.com/questions/5666222/3d-line-plane-intersection
+ * Determines the point of intersection between a plane defined by a point and a normal vector and a line defined by a point and a direction vector.
+ *
+ * @param planePoint A point on the plane.
+ * @param planeNormal Normal vector of the plane.
+ * @param linePoint A point on the line.
+ * @param lineDirection Direction vector of the line.
+ * @param result Resultant vector.
+ */
+stock void GCLineIntersection(const float planePoint[3], const float planeNormal[3], const float linePoint[3], const float lineDirection[3], float result[3])
+{
+ if (GetVectorDotProduct(planeNormal, lineDirection) == 0)
+ {
+ return;
+ }
+
+ float t = (GetVectorDotProduct(planeNormal, planePoint)
+ - GetVectorDotProduct(planeNormal, linePoint))
+ / GetVectorDotProduct(planeNormal, lineDirection);
+
+ float lineDir[3];
+ lineDir = lineDirection;
+ NormalizeVector(lineDir, lineDir);
+
+ ScaleVector(lineDir, t);
+
+ AddVectors(linePoint, lineDir, result);
+}
+
+/**
+ * Calculates a point according to angles supplied that is a certain distance away.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ * @param distance Maximum distance to trace.
+ * @return True on success, false otherwise.
+ */
+stock void GCCalcPointAngleDistance(const float start[3], const float angle[3], float distance, float result[3])
+{
+ float zsine = Sine(DegToRad(-angle[0]));
+ float zcos = Cosine(DegToRad(-angle[0]));
+
+ result[0] = Cosine(DegToRad(angle[1])) * zcos;
+ result[1] = Sine(DegToRad(angle[1])) * zcos;
+ result[2] = zsine;
+
+ ScaleVector(result, distance);
+ AddVectors(start, result, result);
+}
+
+/**
+ * Compares how close 2 floats are.
+ *
+ * @param z1 Float 1
+ * @param z2 Float 2
+ * @param tolerance How close the floats have to be to return true.
+ * @return True on success, false otherwise.
+ */
+stock bool GCIsRoughlyEqual(float z1, float z2, float tolerance)
+{
+ return FloatAbs(z1 - z2) < tolerance;
+}
+
+/**
+ * Checks if a float is within a range
+ *
+ * @param number Float to check.
+ * @param min Minimum range.
+ * @param max Maximum range.
+ * @return True on success, false otherwise.
+ */
+stock bool GCIsFloatInRange(float number, float min, float max)
+{
+ return number >= min && number <= max;
+}
+
+/**
+ * Keeps the yaw angle within the range of -180 to 180.
+ *
+ * @param angle Angle.
+ * @return Normalised angle.
+ */
+stock float GCNormaliseYaw(float angle)
+{
+ if (angle <= -180.0)
+ {
+ angle += 360.0;
+ }
+
+ if (angle > 180.0)
+ {
+ angle -= 360.0;
+ }
+
+ return angle;
+}
+
+/**
+ * Keeps the yaw angle within the range of -180 to 180.
+ *
+ * @param angle Angle.
+ * @return Normalised angle.
+ */
+stock float GCNormaliseYawRad(float angle)
+{
+ if (angle <= -FLOAT_PI)
+ {
+ angle += FLOAT_PI * 2;
+ }
+
+ if (angle > FLOAT_PI)
+ {
+ angle -= FLOAT_PI * 2;
+ }
+
+ return angle;
+}
+
+/**
+ * Linearly interpolates between 2 values.
+ *
+ * @param f1 Float 1.
+ * @param f2 Float 2.
+ * @param fraction Amount to interpolate.
+ * @return Interpolated value.
+ */
+stock float GCInterpLinear(float f1, float f2, float fraction)
+{
+ float diff = f2 - f1;
+
+ return diff * fraction + f1;
+}
+
+/**
+ * Calculates the linear fraction from a value that was interpolated and 2 values it was interpolated from.
+ *
+ * @param f1 Float 1.
+ * @param f2 Float 2.
+ * @param fraction Interpolated value.
+ * @return Fraction.
+ */
+stock float GCCalcLerpFraction(float f1, float f2, float lerped)
+{
+ float diff = f2 - f1;
+
+ float fraction = lerped - f1 / diff;
+ return fraction;
+}
+
+/**
+ * Calculate absolute value of an integer.
+ *
+ * @param x Integer.
+ * @return Absolute value of integer.
+ */
+stock int GCIntAbs(int x)
+{
+ return x >= 0 ? x : -x;
+}
+
+/**
+ * Get the maximum of 2 integers.
+ *
+ * @param n1 Integer.
+ * @param n2 Integer.
+ * @return The biggest of n1 and n2.
+ */
+stock int GCIntMax(int n1, int n2)
+{
+ return n1 > n2 ? n1 : n2;
+}
+
+/**
+ * Get the minimum of 2 integers.
+ *
+ * @param n1 Integer.
+ * @param n2 Integer.
+ * @return The smallest of n1 and n2.
+ */
+stock int GCIntMin(int n1, int n2)
+{
+ return n1 < n2 ? n1 : n2;
+}
+
+/**
+ * Checks if an integer is within a range
+ *
+ * @param number Integer to check.
+ * @param min Minimum range.
+ * @param max Maximum range.
+ * @return True on success, false otherwise.
+ */
+stock bool GCIsIntInRange(int number, int min, int max)
+{
+ return number >= min && number <= max;
+}
+
+/**
+ * Calculates a float percentage from a common fraction.
+ *
+ * @param numerator Numerator.
+ * @param denominator Denominator.
+ * @return Float percentage. -1.0 on failure.
+ */
+stock float GCCalcIntPercentage(int numerator, int denominator)
+{
+ return float(numerator) / float(denominator) * 100.0;
+}
+
+/**
+ * Integer power.
+ * Returns the base raised to the power of the exponent.
+ * Returns 0 if exponent is negative.
+ *
+ * @param base Base to be raised.
+ * @param exponent Value to raise the base.
+ * @return Value to the power of exponent.
+ */
+stock int GCIntPow(int base, int exponent)
+{
+ if (exponent < 0)
+ {
+ return 0;
+ }
+
+ int result = 1;
+ for (;;)
+ {
+ if (exponent & 1)
+ {
+ result *= base
+ }
+
+ exponent >>= 1;
+
+ if (!exponent)
+ {
+ break;
+ }
+
+ base *= base;
+ }
+ return result;
+}
+
+/**
+ * Swaps the values of 2 variables.
+ *
+ * @param cell1 Cell 1.
+ * @param cell2 Cell 2.
+ */
+stock void GCSwapCells(any &cell1, any &cell2)
+{
+ any temp = cell1;
+ cell1 = cell2;
+ cell2 = temp;
+}
+
+/**
+ * Clamps an int between min and max.
+ *
+ * @param value Float to clamp.
+ * @param min Minimum range.
+ * @param max Maximum range.
+ * @return Clamped value.
+ */
+stock int GCIntClamp(int value, int min, int max)
+{
+ if (value < min)
+ {
+ return min;
+ }
+ if (value > max)
+ {
+ return max;
+ }
+ return value;
+}
+
+/**
+ * Returns the biggest of 2 values.
+ *
+ * @param num1 Number 1.
+ * @param num2 Number 2.
+ * @return Biggest number.
+ */
+stock float GCFloatMax(float num1, float num2)
+{
+ if (num1 > num2)
+ {
+ return num1;
+ }
+ return num2;
+}
+
+/**
+ * Returns the smallest of 2 values.
+ *
+ * @param num1 Number 1.
+ * @param num2 Number 2.
+ * @return Smallest number.
+ */
+stock float GCFloatMin(float num1, float num2)
+{
+ if (num1 < num2)
+ {
+ return num1;
+ }
+ return num2;
+}
+
+/**
+ * Clamps a float between min and max.
+ *
+ * @param value Float to clamp.
+ * @param min Minimum range.
+ * @param max Maximum range.
+ * @return Clamped value.
+ */
+stock float GCFloatClamp(float value, float min, float max)
+{
+ if (value < min)
+ {
+ return min;
+ }
+ if (value > max)
+ {
+ return max;
+ }
+ return value;
+}
diff --git a/sourcemod/scripting/include/gamechaos/misc.inc b/sourcemod/scripting/include/gamechaos/misc.inc
new file mode 100644
index 0000000..f964862
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/misc.inc
@@ -0,0 +1,245 @@
+
+#if defined _gamechaos_stocks_misc_included
+ #endinput
+#endif
+#define _gamechaos_stocks_misc_included
+
+#define GC_MISC_VERSION 0x01_00_00
+#define GC_MISC_VERSION_STRING "1.0.0"
+
+/**
+ * Check if player is overlapping their MOVERIGHT and MOVELEFT buttons.
+ *
+ * @param x Buttons;
+ * @return True if overlapping, false otherwise.
+ */
+stock bool GCIsOverlapping(int buttons)
+{
+ return buttons & IN_MOVERIGHT && buttons & IN_MOVELEFT
+}
+
+/**
+ * Checks if player gained speed.
+ *
+ * @param speed Current player speed.
+ * @param lastspeed Player speed from previous tick.
+ * @return True if player gained speed, false otherwise.
+ */
+stock bool GCIsStrafeSynced(float speed, float lastspeed)
+{
+ return speed > lastspeed;
+}
+
+/**
+ * Checks if the player is not holding down their MOVERIGHT and MOVELEFT buttons.
+ *
+ * @param x Buttons.
+ * @return True if they're not holding either, false otherwise.
+ */
+stock bool GCIsDeadAirtime(int buttons)
+{
+ return !(buttons & IN_MOVERIGHT) && !(buttons & IN_MOVELEFT);
+}
+
+/**
+* Source: https://forums.alliedmods.net/showthread.php?p=2535972
+* Runs a single line of vscript code.
+* NOTE: Dont use the "script" console command, it startes a new instance and leaks memory. Use this instead!
+*
+* @param code The code to run.
+* @noreturn
+*/
+stock void GCRunScriptCode(const char[] code, any ...)
+{
+ static int scriptLogic = INVALID_ENT_REFERENCE;
+
+ if (scriptLogic == INVALID_ENT_REFERENCE || !IsValidEntity(scriptLogic))
+ {
+ scriptLogic = EntIndexToEntRef(CreateEntityByName("logic_script"));
+ if (scriptLogic == INVALID_ENT_REFERENCE || !IsValidEntity(scriptLogic))
+ {
+ SetFailState("Could not create a 'logic_script' entity.");
+ }
+
+ DispatchSpawn(scriptLogic);
+ }
+
+ char buffer[512];
+ VFormat(buffer, sizeof(buffer), code, 2);
+
+ SetVariantString(buffer);
+ AcceptEntityInput(scriptLogic, "RunScriptCode");
+}
+
+stock void GCTE_SendBeamBox(int client,
+ const float origin[3],
+ const float mins[3],
+ const float maxs[3],
+ int ModelIndex,
+ int HaloIndex = 0,
+ float Life = 3.0,
+ float Width = 2.0,
+ const int Colour[4] = { 255, 255, 255, 255 },
+ float EndWidth = 2.0,
+ int StartFrame = 0,
+ int FrameRate = 0,
+ int FadeLength = 0,
+ float Amplitude = 0.0,
+ int Speed = 0)
+{
+ // credit to some bhop timer by shavit? thanks
+ int pairs[8][3] = { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 }, { 0, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, { 0, 1, 1 } };
+ int edges[12][2] = { { 0, 1 }, { 0, 3 }, { 0, 4 }, { 2, 1 }, { 2, 3 }, { 2, 6 }, { 5, 4 }, { 5, 6 }, { 5, 1 }, { 7, 4 }, { 7, 6 }, { 7, 3 } };
+
+ float corners[8][3];
+ float corner[2][3];
+
+ AddVectors(origin, mins, corner[0]);
+ AddVectors(origin, maxs, corner[1]);
+
+ for (int i = 0; i < 8; i++)
+ {
+ corners[i][0] = corner[pairs[i][0]][0];
+ corners[i][1] = corner[pairs[i][1]][1];
+ corners[i][2] = corner[pairs[i][2]][2];
+ }
+
+ for (int i = 0; i < 12; i++)
+ {
+ TE_SetupBeamPoints(corners[edges[i][0]],
+ corners[edges[i][1]],
+ ModelIndex,
+ HaloIndex,
+ StartFrame,
+ FrameRate,
+ Life,
+ Width,
+ EndWidth,
+ FadeLength,
+ Amplitude,
+ Colour,
+ Speed);
+ TE_SendToClient(client);
+ }
+}
+
+stock void GCTE_SendBeamCross(int client,
+ const float origin[3],
+ int ModelIndex,
+ int HaloIndex = 0,
+ float Life = 3.0,
+ float Width = 2.0,
+ const int Colour[4] = { 255, 255, 255, 255 },
+ float EndWidth = 2.0,
+ int StartFrame = 0,
+ int FrameRate = 0,
+ int FadeLength = 0,
+ float Amplitude = 0.0,
+ int Speed = 0)
+{
+ float points[4][3];
+
+ for (int i; i < 4; i++)
+ {
+ points[i][2] = origin[2];
+ }
+
+ // -x; -y
+ points[0][0] = origin[0] - 8.0;
+ points[0][1] = origin[1] - 8.0;
+
+ // +x; -y
+ points[1][0] = origin[0] + 8.0;
+ points[1][1] = origin[1] - 8.0;
+
+ // +x; +y
+ points[2][0] = origin[0] + 8.0;
+ points[2][1] = origin[1] + 8.0;
+
+ // -x; +y
+ points[3][0] = origin[0] - 8.0;
+ points[3][1] = origin[1] + 8.0;
+
+ //draw cross
+ for (int corner; corner < 4; corner++)
+ {
+ TE_SetupBeamPoints(origin, points[corner], ModelIndex, HaloIndex, StartFrame, FrameRate, Life, Width, EndWidth, FadeLength, Amplitude, Colour, Speed);
+ TE_SendToClient(client);
+ }
+}
+
+stock void GCTE_SendBeamRectangle(int client,
+ const float origin[3],
+ const float mins[3],
+ const float maxs[3],
+ int modelIndex,
+ int haloIndex = 0,
+ float life = 3.0,
+ float width = 2.0,
+ const int colour[4] = { 255, 255, 255, 255 },
+ float endWidth = 2.0,
+ int startFrame = 0,
+ int frameRate = 0,
+ int fadeLength = 0,
+ float amplitude = 0.0,
+ int speed = 0)
+{
+ float vertices[4][3];
+ GCRectangleVerticesFromPoint(vertices, origin, mins, maxs);
+
+ // send the square
+ for (int i; i < 4; i++)
+ {
+ int j = (i == 3) ? (0) : (i + 1);
+ TE_SetupBeamPoints(vertices[i],
+ vertices[j],
+ modelIndex,
+ haloIndex,
+ startFrame,
+ frameRate,
+ life,
+ width,
+ endWidth,
+ fadeLength,
+ amplitude,
+ colour,
+ speed);
+ TE_SendToClient(client);
+ }
+}
+
+/**
+ * Calculates vertices for a rectangle from a point, mins and maxs.
+ *
+ * @param result Vertex array result.
+ * @param origin Origin to offset mins and maxs by.
+ * @param mins Minimum size of the rectangle.
+ * @param maxs Maximum size of the rectangle.
+ * @return True if overlapping, false otherwise.
+ */
+stock void GCRectangleVerticesFromPoint(float result[4][3], const float origin[3], const float mins[3], const float maxs[3])
+{
+ // Vertices are set clockwise starting from top left (-x; -y)
+
+ // -x; -y
+ result[0][0] = origin[0] + mins[0];
+ result[0][1] = origin[1] + mins[1];
+
+ // +x; -y
+ result[1][0] = origin[0] + maxs[0];
+ result[1][1] = origin[1] + mins[1];
+
+ // +x; +y
+ result[2][0] = origin[0] + maxs[0];
+ result[2][1] = origin[1] + maxs[1];
+
+ // -x; +y
+ result[3][0] = origin[0] + mins[0];
+ result[3][1] = origin[1] + maxs[1];
+
+ // z is the same for every vertex
+ for (int vertex; vertex < 4; vertex++)
+ {
+ result[vertex][2] = origin[2];
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/strings.inc b/sourcemod/scripting/include/gamechaos/strings.inc
new file mode 100644
index 0000000..8ffcb60
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/strings.inc
@@ -0,0 +1,367 @@
+
+#if defined _gamechaos_stocks_strings_included
+ #endinput
+#endif
+#define _gamechaos_stocks_strings_included
+
+// these are used for functions that return strings.
+// you can change these if they're too small/big.
+#define GC_FIXED_BUFFER_SIZE_SMALL 64
+#define GC_FIXED_BUFFER_SIZE_LARGE 4096
+
+/**
+ * Puts the values from a string of integers into an array
+ *
+ * @param string
+ * @param separator
+ * @param array
+ * @param arraysize
+ */
+stock void GCSeparateIntsFromString(const char[] string, const char[] separator, int[] array, int arraysize)
+{
+ char[][] explodedbuffer = new char[arraysize][32];
+
+ ExplodeString(string, separator, explodedbuffer, arraysize, 32);
+
+ for (int i; i < arraysize; i++)
+ {
+ array[i] = StringToInt(explodedbuffer[i]);
+ }
+}
+
+/**
+ * Prints a message to all admins in the chat area.
+ *
+ * @param format Formatting rules.
+ * @param ... Variable number of format parameters.
+ */
+stock void GCPrintToChatAdmins(const char[] format, any ...)
+{
+ char buffer[256];
+
+ for (int i = 1; i <= MaxClients; i++)
+ {
+ if (GCIsValidClient(i))
+ {
+ AdminId id = GetUserAdmin(i);
+ if (!GetAdminFlag(id, Admin_Generic))
+ {
+ continue;
+ }
+ SetGlobalTransTarget(i);
+ VFormat(buffer, sizeof(buffer), format, 2);
+ PrintToChat(i, "%s", buffer);
+ }
+ }
+}
+
+/**
+ * Removes trailings zeroes from a string. Also removes the decimal point if it can.
+ *
+ * @param buffer Buffer to trim.
+ * @return Whether anything was removed.
+ */
+stock bool GCRemoveTrailing0s(char[] buffer)
+{
+ bool removed;
+ int maxlen = strlen(buffer);
+
+ if (maxlen == 0)
+ {
+ return removed;
+ }
+
+ for (int i = maxlen - 1; i > 0 && (buffer[i] == '0' || buffer[i] == '.' || buffer[i] == 0); i--)
+ {
+ if (buffer[i] == 0)
+ {
+ continue;
+ }
+ if (buffer[i] == '.')
+ {
+ buffer[i] = 0;
+ removed = true;
+ break;
+ }
+ buffer[i] = 0;
+ removed = true;
+ }
+ return removed;
+}
+
+/**
+ * Formats time by HHMMSS. Uses ticks for the time.
+ *
+ * @param timeInTicks Time in ticks.
+ * @param tickRate Tickrate.
+ * @param formattedTime String to use for formatting.
+ * @param size String size.
+ */
+stock void GCFormatTickTimeHHMMSS(int timeInTicks, float tickRate, char[] formattedTime, int size)
+{
+ if (timeInTicks <= 0)
+ {
+ FormatEx(formattedTime, size, "-00:00:00");
+ return;
+ }
+
+ int time = RoundFloat(float(timeInTicks) / tickRate * 100.0); // centiseconds
+ int iHours = time / 360000;
+ int iMinutes = time / 6000 - iHours * 6000;
+ int iSeconds = (time - iHours * 360000 - iMinutes * 6000) / 100;
+ int iCentiSeconds = time % 100;
+
+ if (iHours != 0)
+ {
+ FormatEx(formattedTime, size, "%02i:", iHours);
+ }
+ if (iMinutes != 0)
+ {
+ Format(formattedTime, size, "%s%02i:", formattedTime, iMinutes);
+ }
+
+ Format(formattedTime, size, "%s%02i.%02i", formattedTime, iSeconds, iCentiSeconds);
+}
+
+/**
+ * Formats time by HHMMSS. Uses seconds.
+ *
+ * @param seconds Time in seconds.
+ * @param formattedTime String to use for formatting.
+ * @param size String size.
+ * @param decimals Amount of decimals to use for the fractional part.
+ */
+stock void GCFormatTimeHHMMSS(float seconds, char[] formattedTime, int size, int decimals)
+{
+ int iFlooredTime = RoundToFloor(seconds);
+ int iHours = iFlooredTime / 3600;
+ int iMinutes = iFlooredTime / 60 - iHours * 60;
+ int iSeconds = iFlooredTime - iHours * 3600 - iMinutes * 60;
+ int iFraction = RoundToFloor(FloatFraction(seconds) * Pow(10.0, float(decimals)));
+
+ if (iHours != 0)
+ {
+ FormatEx(formattedTime, size, "%02i:", iHours);
+ }
+ if (iMinutes != 0)
+ {
+ Format(formattedTime, size, "%s%02i:", formattedTime, iMinutes);
+ }
+ char szFraction[32];
+ FormatEx(szFraction, sizeof(szFraction), "%i", iFraction);
+
+ int iTest = strlen(szFraction);
+ for (int i; i < decimals - iTest; i++)
+ {
+ Format(szFraction, sizeof(szFraction), "%s%s", "0", szFraction);
+ }
+
+ Format(formattedTime, size, "%s%02i.%s", formattedTime, iSeconds, szFraction);
+}
+
+/**
+ * Encodes and appends a number onto the end of a UTF-8 string.
+ *
+ * @param string String to append to.
+ * @param strsize String size.
+ * @param number Unicode codepoint to encode.
+ */
+stock void GCEncodeUtf8(char[] string, char strsize, int number)
+{
+ // UTF-8 octet sequence (only change digits marked with x)
+ /*
+ Char. number range | UTF-8 octet sequence
+ (hexadecimal) | (binary)
+ --------------------+---------------------------------------------
+ 0000 0000-0000 007F | 0xxxxxxx
+ 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
+ 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
+ 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ // byte 4 | byte 3 | byte 2 | byte 1*/
+
+ //char encodedChar = 0b_11110000_10000000_10000000_10000000;
+
+ int zeropos = strlen(string);
+
+ if (zeropos >= strsize - 1) // need one byte for null terminator
+ {
+ return;
+ }
+
+ if (number < 0)
+ {
+ //PrintToServer("ERROR: Encode() - Can't encode negative numbers");
+ return;
+ }
+
+ if (number >= 0x110_000)
+ {
+ //PrintToServer("ERROR: Encode() - Number is too big to encode");
+ return;
+ }
+
+ // 1 byte
+ if (number < 0x80)
+ {
+ string[zeropos] = number;
+ string[zeropos + 1] = '\0';
+ }
+ // 2 bytes
+ else if (number < 0x800)
+ {
+ // can't encode if we don't have enough room
+ if (zeropos + 2 >= strsize)
+ {
+ return;
+ }
+
+ string[zeropos] = 0b_1100_0000 | (number >> 6); // don't need to mask out bits over 0x7FF
+ string[zeropos + 1] = 0b_1000_0000 | (number & 0b_0011_1111);
+
+ string[zeropos + 2] = '\0';
+ }
+ // 3 bytes
+ else if (number < 0x10_000)
+ {
+ // can't encode if we don't have enough room
+ if (zeropos + 3 >= strsize)
+ {
+ return;
+ }
+
+ string[zeropos] = 0b_1110_0000 | (number >> 12); // don't need to mask out bits over 0xFFFF
+ string[zeropos + 1] = 0b_1000_0000 | ((number >> 6) & 0b_0011_1111);
+ string[zeropos + 2] = 0b_1000_0000 | (number & 0b_0011_1111);
+
+ string[zeropos + 3] = '\0';
+ }
+ // 4 bytes
+ else if (number < 0x110_000)
+ {
+ // can't encode if we don't have enough room
+ if (zeropos + 4 >= strsize)
+ {
+ return;
+ }
+
+ string[zeropos] = 0b_1111_0000 | (number >> 18); // don't need to mask out bits over 0x10FFFF
+ string[zeropos + 1] = 0b_1000_0000 | ((number >> 12) & 0b_0011_1111);
+ string[zeropos + 2] = 0b_1000_0000 | ((number >> 6) & 0b_0011_1111);
+ string[zeropos + 3] = 0b_1000_0000 | (number & 0b_0011_1111);
+
+ string[zeropos + 4] = '\0';
+ }
+}
+
+// decode a UTF-8 string into an array of unicode codepoints
+/**
+ * Decodes a UTF-8 string into an array of unicode codepoints.
+ *
+ * @param string String to decode.
+ * @param strsize String size.
+ * @param codepoints Array to use to store the codepoints.
+ * @param cplength Array length.
+ */
+stock void GCDecodeUtf8(char[] string, int strsize, int[] codepoints, int cplength)
+{
+ int charindex;
+ int cpindex;
+
+ while (charindex < strsize && cpindex < cplength)
+ {
+ if (string[charindex] == '\0')
+ {
+ break;
+ }
+
+ int bytes = GetCharBytes(string[charindex]);
+
+ switch (bytes)
+ {
+ case 1:
+ {
+ codepoints[cpindex] = string[charindex];
+ }
+ case 2:
+ {
+ codepoints[cpindex] = (string[charindex++] & 0b_0001_1111) << 6; // byte 2
+ codepoints[cpindex] |= string[charindex] & 0b_0011_1111; // byte 1
+ }
+ case 3:
+ {
+ codepoints[cpindex] = (string[charindex++] & 0b_0000_1111) << 12; // byte 3
+ codepoints[cpindex] |= (string[charindex++] & 0b_0011_1111) << 6; // byte 2
+ codepoints[cpindex] |= string[charindex] & 0b_0011_1111; // byte 1
+ }
+ case 4:
+ {
+ codepoints[cpindex] = (string[charindex++] & 0b_0000_0111) << 18; // byte 4
+ codepoints[cpindex] |= (string[charindex++] & 0b_0011_1111) << 12; // byte 3
+ codepoints[cpindex] |= (string[charindex++] & 0b_0011_1111) << 6; // byte 2
+ codepoints[cpindex] |= string[charindex] & 0b_0011_1111; // byte 1
+ }
+ }
+
+ charindex++;
+ cpindex++;
+ }
+}
+
+/**
+ * Converts an integer to a string.
+ * Same as IntToString, but it returns the string.
+ *
+ * @param num Integer to convert.
+ * @return String of the number.
+ */
+stock char[] GCIntToStringRet(int num)
+{
+ char string[GC_FIXED_BUFFER_SIZE_SMALL];
+ IntToString(num, string, sizeof string);
+ return string;
+}
+
+ /**
+ * Converts a floating point number to a string.
+ * Same as FloatToString, but it returns the string.
+ *
+ * @param num Floating point number to convert.
+ * @return String of the number.
+ */
+stock char[] GCFloatToStringRet(float num)
+{
+ char string[GC_FIXED_BUFFER_SIZE_SMALL];
+ FloatToString(num, string, sizeof string);
+ return string;
+}
+
+/**
+ * Formats a string according to the SourceMod format rules (see documentation).
+ * Same as Format, except it returns the formatted string.
+ *
+ * @param format Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @return Formatted string.
+ */
+stock char[] GCFormatReturn(const char[] format, any ...)
+{
+ char string[GC_FIXED_BUFFER_SIZE_LARGE];
+ VFormat(string, sizeof string, format, 2);
+ return string;
+}
+
+/**
+ * Removes whitespace characters from the beginning and end of a string.
+ * Same as TrimString, except it returns the formatted string and
+ * it doesn't modify the passed string.
+ *
+ * @param str The string to trim.
+ * @return Number of bytes written (UTF-8 safe).
+ */
+stock char[] GCTrimStringReturn(char[] str)
+{
+ char string[GC_FIXED_BUFFER_SIZE_LARGE];
+ strcopy(string, sizeof string, str);
+ TrimString(string);
+ return string;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/tempents.inc b/sourcemod/scripting/include/gamechaos/tempents.inc
new file mode 100644
index 0000000..7ec9b5a
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/tempents.inc
@@ -0,0 +1,62 @@
+
+#if defined _gamechaos_stocks_tempents_included
+ #endinput
+#endif
+#define _gamechaos_stocks_tempents_included
+
+// improved api of some tempents
+
+#define GC_TEMPENTS_VERSION 0x01_00_00
+#define GC_TEMPENTS_VERSION_STRING "1.0.0"
+
+#include <sdktools_tempents>
+
+/**
+ * Sets up a point to point beam effect.
+ *
+ * @param start Start position of the beam.
+ * @param end End position of the beam.
+ * @param modelIndex Precached model index.
+ * @param life Time duration of the beam.
+ * @param width Initial beam width.
+ * @param endWidth Final beam width.
+ * @param colour Color array (r, g, b, a).
+ * @param haloIndex Precached model index.
+ * @param amplitude Beam amplitude.
+ * @param speed Speed of the beam.
+ * @param fadeLength Beam fade time duration.
+ * @param frameRate Beam frame rate.
+ * @param startFrame Initial frame to render.
+ */
+stock void GCTE_SetupBeamPoints(const float start[3],
+ const float end[3],
+ int modelIndex,
+ float life = 2.0,
+ float width = 2.0,
+ float endWidth = 2.0,
+ const int colour[4] = {255, 255, 255, 255},
+ int haloIndex = 0,
+ float amplitude = 0.0,
+ int speed = 0,
+ int fadeLength = 0,
+ int frameRate = 0,
+ int startFrame = 0)
+{
+ TE_Start("BeamPoints");
+ TE_WriteVector("m_vecStartPoint", start);
+ TE_WriteVector("m_vecEndPoint", end);
+ TE_WriteNum("m_nModelIndex", modelIndex);
+ TE_WriteNum("m_nHaloIndex", haloIndex);
+ TE_WriteNum("m_nStartFrame", startFrame);
+ TE_WriteNum("m_nFrameRate", frameRate);
+ TE_WriteFloat("m_fLife", life);
+ TE_WriteFloat("m_fWidth", width);
+ TE_WriteFloat("m_fEndWidth", endWidth);
+ TE_WriteFloat("m_fAmplitude", amplitude);
+ TE_WriteNum("r", colour[0]);
+ TE_WriteNum("g", colour[1]);
+ TE_WriteNum("b", colour[2]);
+ TE_WriteNum("a", colour[3]);
+ TE_WriteNum("m_nSpeed", speed);
+ TE_WriteNum("m_nFadeLength", fadeLength);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/tracing.inc b/sourcemod/scripting/include/gamechaos/tracing.inc
new file mode 100644
index 0000000..65d54a8
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/tracing.inc
@@ -0,0 +1,242 @@
+
+#if defined _gamechaos_stocks_tracing_included
+ #endinput
+#endif
+#define _gamechaos_stocks_tracing_included
+
+#include <sdktools_trace>
+
+#define GC_TRACING_VERSION 0x01_00_00
+#define GC_TRACING_VERSION_STRING "1.0.0"
+
+/**
+ * Trace ray filter that filters players from being traced.
+ *
+ * @param entity Entity.
+ * @param data Data.
+ * @return True on success, false otherwise.
+ */
+stock bool GCTraceEntityFilterPlayer(int entity, any data)
+{
+ return entity > MAXPLAYERS;
+}
+
+/**
+ * Traces the player hull beneath the player in the direction of
+ * the player's velocity. This should be used on the tick when the player lands
+ *
+ * @param client Player's index.
+ * @param pos Player's position vector.
+ * @param velocity Player's velocity vector. This shuold have the current tick's x and y velocities, but the previous tick's z velocity, since when you're on ground, your z velocity is 0.
+ * @param result Trace endpoint on success, player's position on failure.
+ * @param bugged Whether to add gravity to the player's velocity or not.
+ * @return True on success, false otherwise.
+ */
+stock bool GCTraceLandPos(int client, const float pos[3], const float velocity[3], float result[3], float fGravity, bool bugged = false)
+{
+ float newVel[3];
+ newVel = velocity;
+
+ if (bugged)
+ {
+ // add 0.5 gravity
+ newVel[2] -= fGravity * GetTickInterval() * 0.5;
+ }
+ else
+ {
+ // add 1.5 gravity
+ newVel[2] -= fGravity * GetTickInterval() * 1.5;
+ }
+
+ ScaleVector(newVel, GetTickInterval() * 2.0);
+ float pos2[3];
+ AddVectors(pos, newVel, pos2);
+
+ float mins[3];
+ float maxs[3];
+ GetClientMins(client, mins);
+ GetClientMaxs(client, maxs);
+
+ Handle trace = TR_TraceHullFilterEx(pos, pos2, mins, maxs, MASK_PLAYERSOLID, GCTraceEntityFilterPlayer);
+
+ if (!TR_DidHit(trace))
+ {
+ result = pos;
+ CloseHandle(trace);
+ return false;
+ }
+
+ TR_GetEndPosition(result, trace);
+ CloseHandle(trace);
+
+ return true;
+}
+
+/**
+ * Traces the player hull 2 units straight down beneath the player.
+ *
+ * @param client Player's index.
+ * @param pos Player's position vector.
+ * @param result Trace endpoint on success, player's position on failure.
+ * @return True on success, false otherwise.
+ */
+stock bool GCTraceGround(int client, const float pos[3], float result[3])
+{
+ float mins[3];
+ float maxs[3];
+
+ GetClientMins(client, mins);
+ GetClientMaxs(client, maxs);
+
+ float startpos[3];
+ float endpos[3];
+
+ startpos = pos;
+ endpos = pos;
+
+ endpos[2] -= 2.0;
+
+ TR_TraceHullFilter(startpos, endpos, mins, maxs, MASK_PLAYERSOLID, GCTraceEntityFilterPlayer);
+
+ if (TR_DidHit())
+ {
+ TR_GetEndPosition(result);
+ return true;
+ }
+ else
+ {
+ result = endpos;
+ return false;
+ }
+}
+
+/**
+ * Traces a hull between 2 positions.
+ *
+ * @param pos1 Position 1.
+ * @param pos2 Position 2
+ * @param result Trace endpoint on success, player's position on failure.
+ * @return True on success, false otherwise.
+ */
+stock bool GCTraceBlock(const float pos1[3], const float pos2[3], float result[3])
+{
+ float mins[3] = {-16.0, -16.0, -1.0};
+ float maxs[3] = { 16.0, 16.0, 0.0};
+
+ TR_TraceHullFilter(pos1, pos2, mins, maxs, MASK_PLAYERSOLID, GCTraceEntityFilterPlayer);
+
+ if (TR_DidHit())
+ {
+ TR_GetEndPosition(result);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+/**
+ * Traces from player eye position in the direction of where the player is looking.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ * @return True on success, false otherwise.
+ */
+stock bool GCGetEyeRayPosition(int client, float result[3], TraceEntityFilter filter, any data = 0, int flags = MASK_PLAYERSOLID)
+{
+ float start[3];
+ float angle[3];
+
+ GetClientEyePosition(client, start);
+ GetClientEyeAngles(client, angle);
+
+ TR_TraceRayFilter(start, angle, flags, RayType_Infinite, filter, data);
+
+ if (TR_DidHit(INVALID_HANDLE))
+ {
+ TR_GetEndPosition(result, INVALID_HANDLE);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Traces from player eye position in the direction of where the player is looking, up to a certain distance.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ * @param distance Maximum distance to trace.
+ * @return True on success, false otherwise.
+ */
+stock bool GCTraceEyeRayPositionDistance(int client, float result[3], float distance)
+{
+ float start[3];
+ float angle[3];
+
+ GetClientEyePosition(client, start);
+ GetClientEyeAngles(client, angle);
+
+ float endpoint[3];
+ float zsine = Sine(DegToRad(-angle[0]));
+ float zcos = Cosine(DegToRad(-angle[0]));
+
+ endpoint[0] = Cosine(DegToRad(angle[1])) * zcos;
+ endpoint[1] = Sine(DegToRad(angle[1])) * zcos;
+ endpoint[2] = zsine;
+
+ ScaleVector(endpoint, distance);
+ AddVectors(start, endpoint, endpoint);
+
+ TR_TraceRayFilter(start, endpoint, MASK_PLAYERSOLID, RayType_EndPoint, GCTraceEntityFilterPlayer, client);
+
+ if (TR_DidHit())
+ {
+ TR_GetEndPosition(result);
+ return true;
+ }
+
+ result = endpoint;
+ return false;
+}
+
+/**
+ * Traces a hull in a certain direction and distance.
+ *
+ * @param origin Position to trace from.
+ * @param direction Trace direction.
+ * @param mins Minimum size of the hull.
+ * @param maxs Maximum size of the hull.
+ * @param result Resultant vector.
+ * @return True on success, false otherwise.
+ */
+stock bool GCTraceHullDirection(const float origin[3],
+ const float direction[3],
+ const float mins[3],
+ const float maxs[3],
+ float result[3],
+ float distance,
+ TraceEntityFilter filter,
+ any data = 0,
+ int flags = MASK_PLAYERSOLID)
+{
+ float pos2[3];
+ float zsine = Sine(DegToRad(-direction[0]));
+ float zcos = Cosine(DegToRad(-direction[0]));
+
+ pos2[0] = Cosine(DegToRad(direction[1])) * zcos;
+ pos2[1] = Sine(DegToRad(direction[1])) * zcos;
+ pos2[2] = zsine;
+
+ ScaleVector(pos2, distance);
+ AddVectors(origin, pos2, pos2);
+
+ TR_TraceHullFilter(origin, pos2, mins, maxs, flags, filter, data);
+ if (TR_DidHit())
+ {
+ TR_GetEndPosition(result);
+ return true;
+ }
+ result = pos2;
+ return false;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gamechaos/vectors.inc b/sourcemod/scripting/include/gamechaos/vectors.inc
new file mode 100644
index 0000000..79d5e8f
--- /dev/null
+++ b/sourcemod/scripting/include/gamechaos/vectors.inc
@@ -0,0 +1,66 @@
+
+#if defined _gamechaos_stocks_vectors_included
+ #endinput
+#endif
+#define _gamechaos_stocks_vectors_included
+
+#define GC_VECTORS_VERSION 0x01_00_01
+#define GC_VECTORS_VERSION_STRING "1.0.1"
+
+/**
+ * Calculates the horizontal (x, y) length of a vector.
+ *
+ * @param vec Vector.
+ * @return Vector length (magnitude).
+ */
+stock float GCGetVectorLength2D(const float vec[3])
+{
+ float tempVec[3];
+ tempVec = vec;
+ tempVec[2] = 0.0;
+
+ return GetVectorLength(tempVec);
+}
+
+/**
+ * Calculates the horizontal (x, y) distance between 2 vectors.
+ *
+ * @param x Vector 1.
+ * @param y Vector 2.
+ * @param tolerance How close the floats have to be to return true.
+ * @return True on success, false otherwise.
+ */
+stock float GCGetVectorDistance2D(const float x[3], const float y[3])
+{
+ float x2[3];
+ float y2[3];
+
+ x2 = x;
+ y2 = y;
+
+ x2[2] = 0.0;
+ y2[2] = 0.0;
+
+ return GetVectorDistance(x2, y2);
+}
+
+/**
+ * Checks if 2 vectors are exactly equal.
+ *
+ * @param a Vector 1.
+ * @param b Vector 2.
+ * @return True on success, false otherwise.
+ */
+stock bool GCVectorsEqual(const float a[3], const float b[3])
+{
+ bool result = true;
+ for (int i = 0; i < 3; i++)
+ {
+ if (a[i] != b[i])
+ {
+ result = false;
+ break;
+ }
+ }
+ return result;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/glib/addressutils.inc b/sourcemod/scripting/include/glib/addressutils.inc
new file mode 100644
index 0000000..bbe8f14
--- /dev/null
+++ b/sourcemod/scripting/include/glib/addressutils.inc
@@ -0,0 +1,54 @@
+#if defined _addressutils_included
+#endinput
+#endif
+#define _addressutils_included
+
+methodmap AddressBase
+{
+ property Address Address
+ {
+ public get() { return view_as<Address>(this); }
+ }
+}
+
+//-==Operator overloadings
+stock Address operator+(Address l, int r)
+{
+ return l + view_as<Address>(r);
+}
+
+stock Address operator+(int l, Address r)
+{
+ return view_as<Address>(l) + r;
+}
+
+stock Address operator-(Address l, int r)
+{
+ return l - view_as<Address>(r);
+}
+
+stock Address operator-(int l, Address r)
+{
+ return view_as<Address>(l) - r;
+}
+
+stock Address operator*(Address l, int r)
+{
+ return l * view_as<Address>(r);
+}
+
+stock Address operator*(int l, Address r)
+{
+ return view_as<Address>(l) * r;
+}
+
+stock Address operator/(Address l, int r)
+{
+ return l / view_as<Address>(r);
+}
+
+stock Address operator/(int l, Address r)
+{
+ return view_as<Address>(l) / r;
+}
+//Operator overloadings==- \ No newline at end of file
diff --git a/sourcemod/scripting/include/glib/assertutils.inc b/sourcemod/scripting/include/glib/assertutils.inc
new file mode 100644
index 0000000..83cd90d
--- /dev/null
+++ b/sourcemod/scripting/include/glib/assertutils.inc
@@ -0,0 +1,61 @@
+#if defined _assertutils_included
+#endinput
+#endif
+#define _assertutils_included
+
+/* Compile time settings for this include. Should be defined before including this file.
+* #define ASSERTUTILS_DISABLE //Disables all assertions
+* #define ASSERTUTILS_FAILSTATE_FUNC //Define the name of the function that should be called when assertion is hit
+*/
+
+#if !defined SNAME
+#define __SNAME ""
+#else
+#define __SNAME SNAME
+#endif
+
+#define ASSERT_FMT_STRING_LEN 512
+
+#if defined ASSERTUTILS_DISABLE
+
+#define ASSERT(%1)%2;
+#define ASSERT_MSG(%1,%2)%3;
+#define ASSERT_FMT(%1,%2)%3;
+#define ASSERT_FINAL(%1)%2;
+#define ASSERT_FINAL_MSG(%1,%2)%3;
+
+#elseif defined ASSERTUTILS_FAILSTATE_FUNC
+
+#define ASSERT(%1) if(!(%1)) ASSERTUTILS_FAILSTATE_FUNC(__SNAME..."Assertion failed: \""...#%1..."\"")
+#define ASSERT_MSG(%1,%2) if(!(%1)) ASSERTUTILS_FAILSTATE_FUNC(__SNAME...%2)
+#define ASSERT_FMT(%1,%2) if(!(%1)) ASSERTUTILS_FAILSTATE_FUNC(__SNAME...%2)
+#define ASSERT_FINAL(%1) if(!(%1)) SetFailState(__SNAME..."Assertion failed: \""...#%1..."\"")
+#define ASSERT_FINAL_MSG(%1,%2) if(!(%1)) SetFailState(__SNAME...%2)
+
+#else
+
+#define ASSERT(%1) if(!(%1)) SetFailState(__SNAME..."Assertion failed: \""...#%1..."\"")
+#define ASSERT_MSG(%1,%2) if(!(%1)) SetFailState(__SNAME...%2)
+#define ASSERT_FMT(%1,%2) if(!(%1)) SetFailState(__SNAME...%2)
+#define ASSERT_FINAL(%1) ASSERT(%1)
+#define ASSERT_FINAL_MSG(%1,%2) ASSERT_MSG(%1,%2)
+
+#endif
+
+// Might be redundant as default ASSERT_MSG accept format arguments just fine.
+#if 0
+stock void ASSERT_FMT(bool result, char[] fmt, any ...)
+{
+#if !defined ASSERTUTILS_DISABLE
+ if(!result)
+ {
+ char buff[ASSERT_FMT_STRING_LEN];
+ VFormat(buff, sizeof(buff), fmt, 3);
+
+ SetFailState(__SNAME..."%s", buff);
+ }
+#endif
+}
+#endif
+
+#undef ASSERT_FMT_STRING_LEN \ No newline at end of file
diff --git a/sourcemod/scripting/include/glib/memutils.inc b/sourcemod/scripting/include/glib/memutils.inc
new file mode 100644
index 0000000..5813d92
--- /dev/null
+++ b/sourcemod/scripting/include/glib/memutils.inc
@@ -0,0 +1,232 @@
+#if defined _memutils_included
+#endinput
+#endif
+#define _memutils_included
+
+#include "glib/assertutils"
+#include "glib/addressutils"
+
+/* Compile time settings for this include. Should be defined before including this file.
+* #define MEMUTILS_PLUGINENDCALL //This should be defined if main plugin has OnPluginEnd() forward used.
+*/
+
+#define MEM_LEN_SAFE_THRESHOLD 2000
+
+//-==PatchHandling methodmap
+static StringMap gPatchStack;
+
+methodmap PatchHandler < AddressBase
+{
+ public PatchHandler(Address addr)
+ {
+ ASSERT(addr != Address_Null);
+
+ if(!gPatchStack)
+ gPatchStack = new StringMap();
+
+ return view_as<PatchHandler>(addr);
+ }
+
+ property any Any
+ {
+ public get() { return view_as<any>(this); }
+ }
+
+ public void Save(int len)
+ {
+ ASSERT(gPatchStack);
+ ASSERT(len > 0 && len < MEM_LEN_SAFE_THRESHOLD);
+
+ len++;
+
+ int[] arr = new int[len];
+ arr[0] = len;
+
+ for(int i = 0; i < len - 1; i++)
+ arr[i + 1] = LoadFromAddress(this.Address + i, NumberType_Int8);
+
+ char buff[32];
+ IntToString(this.Any, buff, sizeof(buff));
+ gPatchStack.SetArray(buff, arr, len);
+ }
+
+ public void PatchNSave(int len, char byte = 0x90)
+ {
+ ASSERT(gPatchStack);
+ ASSERT(len > 0 && len < MEM_LEN_SAFE_THRESHOLD);
+
+ len++;
+
+ int[] arr = new int[len];
+ arr[0] = len;
+
+ for(int i = 0; i < len - 1; i++)
+ {
+ arr[i + 1] = LoadFromAddress(this.Address + i, NumberType_Int8);
+ StoreToAddress(this.Address + i, byte, NumberType_Int8);
+ }
+
+ char buff[32];
+ IntToString(this.Any, buff, sizeof(buff));
+ gPatchStack.SetArray(buff, arr, len);
+ }
+
+ public void PatchNSaveSeq(const char[] data, int len)
+ {
+ ASSERT(gPatchStack);
+ ASSERT(len > 0 && len < MEM_LEN_SAFE_THRESHOLD);
+
+ len++;
+
+ int[] arr = new int[len];
+ arr[0] = len;
+
+ for(int i = 0; i < len - 1; i++)
+ {
+ arr[i + 1] = LoadFromAddress(this.Address + i, NumberType_Int8);
+ StoreToAddress(this.Address + i, data[i], NumberType_Int8);
+ }
+
+ char buff[32];
+ IntToString(this.Any, buff, sizeof(buff));
+ gPatchStack.SetArray(buff, arr, len);
+ }
+
+ public void Restore()
+ {
+ if(!gPatchStack)
+ return;
+
+ char buff[32];
+ IntToString(this.Any, buff, sizeof(buff));
+
+ int arrSize[1];
+ if(!gPatchStack.GetArray(buff, arrSize, sizeof(arrSize)))
+ return;
+
+ int[] arr = new int[arrSize[0]];
+ gPatchStack.GetArray(buff, arr, arrSize[0]);
+ gPatchStack.Remove(buff);
+
+ for(int i = 0; i < arrSize[0] - 1; i++)
+ StoreToAddress(this.Address + i, arr[i + 1], NumberType_Int8);
+
+ if(gPatchStack.Size == 0)
+ delete gPatchStack;
+ }
+}
+
+public void OnPluginEnd()
+{
+ if(gPatchStack)
+ {
+ StringMapSnapshot sms = gPatchStack.Snapshot();
+ char buff[32];
+ Address addr;
+
+ for(int i = 0; i < sms.Length; i++)
+ {
+ sms.GetKey(i, buff, sizeof(buff));
+ addr = view_as<Address>(StringToInt(buff));
+ view_as<PatchHandler>(addr).Restore();
+ }
+ }
+
+#if defined MEMUTILS_PLUGINENDCALL
+ OnPluginEnd_MemUtilsRedefined();
+#endif
+}
+#undef OnPluginEnd
+#if defined MEMUTILS_PLUGINENDCALL
+#define OnPluginEnd OnPluginEnd_MemUtilsRedefined
+#else
+#define OnPluginEnd OnPluginEnd_Redifined(){}\
+void MEMUTILS_INCLUDE_WARNING_OnPluginEnd_REDIFINITION
+#endif
+//PatchHandling methodmap==-
+
+//-==Other util functions
+stock int LoadStringFromAddress(Address addr, char[] buff, int length)
+{
+ int i;
+ for(i = 0; i < length && (buff[i] = LoadFromAddress(addr + i, NumberType_Int8)) != '\0'; i++) { }
+ buff[i == length ? i - 1 : i] = '\0';
+ return i;
+}
+
+stock void DumpOnAddress(Address addr, int len, int columns = 10)
+{
+ char buff[128], buff2[128];
+
+ ASSERT(addr != Address_Null);
+ ASSERT(len > 0 && len < MEM_LEN_SAFE_THRESHOLD);
+
+ Format(buff, sizeof(buff), "[0x%08X]", addr);
+ char chr;
+ for(int i = 0; i < len; i++)
+ {
+ chr = LoadFromAddress(addr + i, NumberType_Int8);
+ Format(buff, sizeof(buff), "%s %02X", buff, chr);
+ Format(buff2, sizeof(buff2), "%s%c", buff2, (chr > ' ' && chr != 0x7F && chr != 0xFF ? chr : '.'));
+ if(i % columns == columns - 1)
+ {
+ PrintToServer(__SNAME..."%s %s", buff, buff2);
+ Format(buff, sizeof(buff), "[0x%08X]", addr + i);
+ buff2[0] = '\0';
+ }
+ }
+
+ if((len - 1) % columns != columns - 1)
+ PrintToServer(__SNAME..."%s %s", buff, buff2);
+}
+
+//NO OVERLAPPING!!
+stock void MoveBytes(Address from, Address to, int len, char replace_with = 0x90)
+{
+ ASSERT(from != Address_Null);
+ ASSERT(to != Address_Null);
+ ASSERT(len > 0 && len < MEM_LEN_SAFE_THRESHOLD);
+ ASSERT(to < from || to > from + len);
+
+ if(from == to)
+ return;
+
+ for(int i = 0; i < len; i++)
+ {
+ StoreToAddress(to + i, LoadFromAddress(from + i, NumberType_Int8), NumberType_Int8);
+ StoreToAddress(from + i, replace_with, NumberType_Int8);
+ }
+}
+
+stock void CutNCopyBytes(Address from, Address to, int len, char replace_with = 0x90)
+{
+ ASSERT(from != Address_Null);
+ ASSERT(to != Address_Null);
+ ASSERT(len > 0 && len < MEM_LEN_SAFE_THRESHOLD);
+
+ if(from == to)
+ return;
+
+ int[] arr = new int[len];
+
+ for(int i = 0; i < len; i++)
+ {
+ arr[i] = LoadFromAddress(from + i, NumberType_Int8);
+ StoreToAddress(from + i, replace_with, NumberType_Int8);
+ }
+
+ for(int i = 0; i < len; i++)
+ StoreToAddress(to + i, arr[i], NumberType_Int8);
+}
+
+stock void PatchArea(Address addr, int len, char byte = 0x90)
+{
+ ASSERT(addr != Address_Null);
+ ASSERT(len > 0 && len < MEM_LEN_SAFE_THRESHOLD);
+
+ for(int i = 0; i < len; i++)
+ StoreToAddress(addr + i, byte, NumberType_Int8);
+}
+//Other util functions==-
+
+#undef MEM_LEN_SAFE_THRESHOLD \ No newline at end of file
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();
+}
diff --git a/sourcemod/scripting/include/gokz/anticheat.inc b/sourcemod/scripting/include/gokz/anticheat.inc
new file mode 100644
index 0000000..7fa5409
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/anticheat.inc
@@ -0,0 +1,168 @@
+/*
+ gokz-anticheat Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_anticheat_included_
+#endinput
+#endif
+#define _gokz_anticheat_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum ACReason:
+{
+ ACReason_BhopMacro = 0,
+ ACReason_BhopHack,
+ ACREASON_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define AC_MAX_BUTTON_SAMPLES 40
+#define AC_MAX_BHOP_GROUND_TICKS 8
+#define AC_MAX_BHOP_SAMPLES 30
+#define AC_BINDEXCEPTION_SAMPLES 5
+#define AC_LOG_PATH "logs/gokz-anticheat.log"
+
+stock char gC_ACReasons[ACREASON_COUNT][] =
+{
+ "BHop Macro",
+ "BHop Hack"
+};
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when gokz-anticheat suspects a player of cheating.
+ *
+ * @param client Client index.
+ * @param reason Reason for suspicion.
+ * @param notes Additional reasoning, description etc.
+ * @param stats Data supporting the suspicion e.g. scroll pattern.
+ */
+forward void GOKZ_AC_OnPlayerSuspected(int client, ACReason reason, const char[] notes, const char[] stats);
+
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets the number of recent bhop samples available for a player.
+ *
+ * @param client Client index.
+ * @return Number of bhop samples available.
+ */
+native int GOKZ_AC_GetSampleSize(int client);
+
+/**
+ * Gets whether a player hit a perfect bhop for a number of
+ * recent bhops. Buffer must be large enough to fit the sample
+ * size.
+ *
+ * @param client Client index.
+ * @param buffer Buffer for perfect bhop booleans, with the first element being the most recent bhop.
+ * @param sampleSize Maximum recent bhop samples.
+ * @return Number of bhop samples.
+ */
+native int GOKZ_AC_GetHitPerf(int client, bool[] buffer, int sampleSize);
+
+/**
+ * Gets a player's number of perfect bhops out of a sample
+ * size of bhops.
+ *
+ * @param client Client index.
+ * @param sampleSize Maximum recent bhop samples to include in calculation.
+ * @return Player's number of perfect bhops.
+ */
+native int GOKZ_AC_GetPerfCount(int client, int sampleSize);
+
+/**
+ * Gets a player's ratio of perfect bhops to normal bhops.
+ *
+ * @param client Client index.
+ * @param sampleSize Maximum recent bhop samples to include in calculation.
+ * @return Player's ratio of perfect bhops to normal bhops.
+ */
+native float GOKZ_AC_GetPerfRatio(int client, int sampleSize);
+
+/**
+ * Gets a player's jump input counts for a number of recent
+ * bhops. Buffer must be large enough to fit the sample size.
+ *
+ * @param client Client index.
+ * @param buffer Buffer for jump input counts, with the first element being the most recent bhop.
+ * @param sampleSize Maximum recent bhop samples.
+ * @return Number of bhop samples.
+ */
+native int GOKZ_AC_GetJumpInputs(int client, int[] buffer, int sampleSize);
+
+/**
+ * Gets a player's average number of jump inputs for a number
+ * of recent bhops.
+ *
+ * @param client Client index.
+ * @param sampleSize Maximum recent bhop samples to include in calculation.
+ * @return Player's average number of jump inputs.
+ */
+native float GOKZ_AC_GetAverageJumpInputs(int client, int sampleSize);
+
+/**
+ * Gets a player's jump input counts prior to a number of recent
+ * bhops. Buffer must be large enough to fit the sample size.
+ * Includes the jump input that resulted in the jump.
+ *
+ * @param client Client index.
+ * @param buffer Buffer for jump input counts, with the first element being the most recent bhop.
+ * @param sampleSize Maximum recent bhop samples.
+ * @return Number of bhop samples.
+ */
+native int GOKZ_AC_GetPreJumpInputs(int client, int[] buffer, int sampleSize);
+
+/**
+ * Gets a player's jump input counts after a number of recent
+ * bhops. Buffer must be large enough to fit the sample size.
+ * Excludes the jump input that resulted in the jump.
+ *
+ * @param client Client index.
+ * @param buffer Buffer for jump input counts, with the first element being the most recent bhop.
+ * @param sampleSize Maximum recent bhop samples.
+ * @return Number of bhop samples.
+ */
+native int GOKZ_AC_GetPostJumpInputs(int client, int[] buffer, int sampleSize);
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_anticheat =
+{
+ name = "gokz-anticheat",
+ file = "gokz-anticheat.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_anticheat_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_AC_GetSampleSize");
+ MarkNativeAsOptional("GOKZ_AC_GetHitPerf");
+ MarkNativeAsOptional("GOKZ_AC_GetPerfCount");
+ MarkNativeAsOptional("GOKZ_AC_GetPerfRatio");
+ MarkNativeAsOptional("GOKZ_AC_GetJumpInputs");
+ MarkNativeAsOptional("GOKZ_AC_GetAverageJumpInputs");
+ MarkNativeAsOptional("GOKZ_AC_GetPreJumpInputs");
+ MarkNativeAsOptional("GOKZ_AC_GetPostJumpInputs");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/chat.inc b/sourcemod/scripting/include/gokz/chat.inc
new file mode 100644
index 0000000..0264a57
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/chat.inc
@@ -0,0 +1,45 @@
+/*
+ gokz-chat Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_chat_included_
+#endinput
+#endif
+#define _gokz_chat_included_
+
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets whether a mode is loaded.
+ *
+ * @param client Client.
+ * @param tag Tag to prepend to the player name in chat.
+ * @param color Color to use for the tag.
+ */
+native void GOKZ_CH_SetChatTag(int client, const char[] tag, const char[] color);
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_chat =
+{
+ name = "gokz-chat",
+ file = "gokz-chat.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_chat_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_CH_SetChatTag");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/core.inc b/sourcemod/scripting/include/gokz/core.inc
new file mode 100644
index 0000000..fb450d1
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/core.inc
@@ -0,0 +1,1920 @@
+/*
+ gokz-core Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_core_included_
+#endinput
+#endif
+#define _gokz_core_included_
+
+#include <cstrike>
+#include <regex>
+#include <topmenus>
+
+#include <gokz>
+
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ TimeType_Nub = 0,
+ TimeType_Pro,
+ TIMETYPE_COUNT
+};
+
+enum
+{
+ MapPrefix_Other = 0,
+ MapPrefix_KZPro,
+ MAPPREFIX_COUNT
+};
+
+enum StartPositionType:
+{
+ StartPositionType_Spawn,
+ StartPositionType_Custom,
+ StartPositionType_MapButton,
+ StartPositionType_MapStart,
+ STARTPOSITIONTYPE_COUNT
+};
+
+enum CourseTimerType:
+{
+ CourseTimerType_None,
+ CourseTimerType_Default,
+ CourseTimerType_Button,
+ CourseTimerType_ZoneLegacy,
+ CourseTimerType_ZoneNew,
+ CourseTimerType_COUNT
+};
+
+enum OptionProp:
+{
+ OptionProp_Cookie = 0,
+ OptionProp_Type,
+ OptionProp_DefaultValue,
+ OptionProp_MinValue,
+ OptionProp_MaxValue,
+ OPTIONPROP_COUNT
+};
+
+enum OptionType:
+{
+ OptionType_Int = 0,
+ OptionType_Float
+};
+
+enum Option:
+{
+ OPTION_INVALID = -1,
+ Option_Mode,
+ Option_Style,
+ Option_CheckpointMessages,
+ Option_CheckpointSounds,
+ Option_TeleportSounds,
+ Option_ErrorSounds,
+ Option_VirtualButtonIndicators,
+ Option_TimerButtonZoneType,
+ Option_ButtonThroughPlayers,
+ Option_Safeguard,
+ OPTION_COUNT
+};
+
+enum
+{
+ Mode_Vanilla = 0,
+ Mode_SimpleKZ,
+ Mode_KZTimer,
+ MODE_COUNT
+};
+
+enum
+{
+ Style_Normal = 0,
+ STYLE_COUNT
+};
+
+enum
+{
+ CheckpointMessages_Disabled = 0,
+ CheckpointMessages_Enabled,
+ CHECKPOINTMESSAGES_COUNT
+};
+
+enum
+{
+ CheckpointSounds_Disabled = 0,
+ CheckpointSounds_Enabled,
+ CHECKPOINTSOUNDS_COUNT
+};
+
+enum
+{
+ TeleportSounds_Disabled = 0,
+ TeleportSounds_Enabled,
+ TELEPORTSOUNDS_COUNT
+};
+
+enum
+{
+ ErrorSounds_Disabled = 0,
+ ErrorSounds_Enabled,
+ ERRORSOUNDS_COUNT
+};
+
+enum
+{
+ VirtualButtonIndicators_Disabled = 0,
+ VirtualButtonIndicators_Enabled,
+ VIRTUALBUTTONINDICATORS_COUNT
+};
+
+enum
+{
+ TimerButtonZoneType_BothButtons = 0,
+ TimerButtonZoneType_EndZone,
+ TimerButtonZoneType_BothZones,
+ TIMERBUTTONZONETYPE_COUNT
+};
+
+enum
+{
+ ButtonThroughPlayers_Disabled = 0,
+ ButtonThroughPlayers_Enabled,
+ BUTTONTHROUGHPLAYERS_COUNT
+};
+
+enum
+{
+ Safeguard_Disabled = 0,
+ Safeguard_EnabledNUB,
+ Safeguard_EnabledPRO,
+ SAFEGUARD_COUNT
+};
+
+enum
+{
+ ModeCVar_Accelerate = 0,
+ ModeCVar_AccelerateUseWeaponSpeed,
+ ModeCVar_AirAccelerate,
+ ModeCVar_AirMaxWishSpeed,
+ ModeCVar_EnableBunnyhopping,
+ ModeCVar_Friction,
+ ModeCVar_Gravity,
+ ModeCVar_JumpImpulse,
+ ModeCVar_LadderScaleSpeed,
+ ModeCVar_LedgeMantleHelper,
+ ModeCVar_MaxSpeed,
+ ModeCVar_MaxVelocity,
+ ModeCVar_StaminaJumpCost,
+ ModeCVar_StaminaLandCost,
+ ModeCVar_StaminaMax,
+ ModeCVar_StaminaRecoveryRate,
+ ModeCVar_StandableNormal,
+ ModeCVar_TimeBetweenDucks,
+ ModeCVar_WalkableNormal,
+ ModeCVar_WaterAccelerate,
+ MoveCVar_WaterMoveSpeedMultiplier,
+ MoveCVar_WaterSwimMode,
+ MoveCVar_WeaponEncumbrancePerItem,
+ ModeCVar_WeaponEncumbranceScale,
+ MODECVAR_COUNT
+};
+
+// NOTE: gokz-core/map/entlump.sp
+enum EntlumpTokenType
+{
+ EntlumpTokenType_OpenBrace, // {
+ EntlumpTokenType_CloseBrace, // }
+ EntlumpTokenType_Identifier, // everything that's inside quotations
+ EntlumpTokenType_Unknown,
+ EntlumpTokenType_EndOfStream
+};
+
+// NOTE: gokz-core/map/triggers.sp
+// NOTE: corresponds to climb_teleport_type in kz_mapping_api.fgd
+enum TeleportType
+{
+ TeleportType_Invalid = -1,
+ TeleportType_Normal,
+ TeleportType_MultiBhop,
+ TeleportType_SingleBhop,
+ TeleportType_SequentialBhop,
+ TELEPORTTYPE_COUNT
+};
+
+enum TriggerType
+{
+ TriggerType_Invalid = 0,
+ TriggerType_Teleport,
+ TriggerType_Antibhop
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define GOKZ_CHECKPOINT_VERSION 2
+#define GOKZ_MAX_CHECKPOINTS 2048
+#define GOKZ_MAX_COURSES 100
+
+#define GOKZ_BHOP_NO_CHECKPOINT_TIME 0.15
+#define GOKZ_MULT_NO_CHECKPOINT_TIME 0.11
+#define GOKZ_LADDER_NO_CHECKPOINT_TIME 1.5
+#define GOKZ_PAUSE_COOLDOWN 1.0
+#define GOKZ_TIMER_START_NO_TELEPORT_TICKS 4
+#define GOKZ_TIMER_START_GROUND_TICKS 4
+#define GOKZ_TIMER_START_NOCLIP_TICKS 4
+#define GOKZ_JUMPSTATS_NOCLIP_RESET_TICKS 4
+#define GOKZ_TIMER_SOUND_COOLDOWN 0.15
+#define GOKZ_VIRTUAL_BUTTON_USE_DETECTION_TIME 2.0
+#define GOKZ_TURNBIND_COOLDOWN 0.3
+
+#define GOKZ_MAPPING_API_VERSION_NONE 0 // the map doesn't have a mapping api version
+#define GOKZ_MAPPING_API_VERSION 1
+
+#define GOKZ_ANTI_BHOP_TRIGGER_DEFAULT_DELAY 0.2
+#define GOKZ_TELEPORT_TRIGGER_DEFAULT_TYPE TeleportType_Normal
+#define GOKZ_TELEPORT_TRIGGER_DEFAULT_DELAY 0.0
+#define GOKZ_TELEPORT_TRIGGER_DEFAULT_USE_DEST_ANGLES true
+#define GOKZ_TELEPORT_TRIGGER_DEFAULT_RESET_SPEED true
+#define GOKZ_TELEPORT_TRIGGER_DEFAULT_RELATIVE_DESTINATION false
+#define GOKZ_TELEPORT_TRIGGER_DEFAULT_REORIENT_PLAYER false
+#define GOKZ_TELEPORT_TRIGGER_BHOP_MIN_DELAY 0.08
+
+#define GOKZ_SOUND_CHECKPOINT "buttons/blip1.wav"
+#define GOKZ_SOUND_TELEPORT "buttons/blip1.wav"
+#define GOKZ_SOUND_TIMER_STOP "buttons/button18.wav"
+
+#define GOKZ_START_NAME "climb_start"
+#define GOKZ_BONUS_START_NAME_REGEX "^climb_bonus(\\d+)_start$"
+#define GOKZ_BONUS_END_NAME_REGEX "^climb_bonus(\\d+)_end$"
+
+#define GOKZ_START_BUTTON_NAME "climb_startbutton"
+#define GOKZ_END_BUTTON_NAME "climb_endbutton"
+#define GOKZ_BONUS_START_BUTTON_NAME_REGEX "^climb_bonus(\\d+)_startbutton$"
+#define GOKZ_BONUS_END_BUTTON_NAME_REGEX "^climb_bonus(\\d+)_endbutton$"
+#define GOKZ_ANTI_BHOP_TRIGGER_NAME "climb_anti_bhop"
+#define GOKZ_ANTI_CP_TRIGGER_NAME "climb_anti_checkpoint"
+#define GOKZ_ANTI_PAUSE_TRIGGER_NAME "climb_anti_pause"
+#define GOKZ_ANTI_JUMPSTAT_TRIGGER_NAME "climb_anti_jumpstat"
+#define GOKZ_BHOP_RESET_TRIGGER_NAME "climb_bhop_reset"
+#define GOKZ_TELEPORT_TRIGGER_NAME "climb_teleport"
+
+#define GOKZ_START_ZONE_NAME "climb_startzone"
+#define GOKZ_END_ZONE_NAME "climb_endzone"
+#define GOKZ_BONUS_START_ZONE_NAME_REGEX "^climb_bonus(\\d+)_startzone$"
+#define GOKZ_BONUS_END_ZONE_NAME_REGEX "^climb_bonus(\\d+)_endzone$"
+
+#define GOKZ_CFG_SERVER "sourcemod/gokz/gokz.cfg"
+#define GOKZ_CFG_OPTIONS "cfg/sourcemod/gokz/options.cfg"
+#define GOKZ_CFG_OPTIONS_SORTING "cfg/sourcemod/gokz/options_menu_sorting.cfg"
+#define GOKZ_CFG_OPTIONS_ROOT "Options"
+#define GOKZ_CFG_OPTIONS_DESCRIPTION "description"
+#define GOKZ_CFG_OPTIONS_DEFAULT "default"
+
+#define GOKZ_OPTION_MAX_NAME_LENGTH 30
+#define GOKZ_OPTION_MAX_DESC_LENGTH 255
+#define GENERAL_OPTION_CATEGORY "General"
+
+// TODO: where do i put the defines?
+#define GOKZ_BSP_HEADER_IDENTIFIER (('P' << 24) | ('S' << 16) | ('B' << 8) | 'V')
+#define GOKZ_ENTLUMP_MAX_KEY 32
+#define GOKZ_ENTLUMP_MAX_VALUE 1024
+
+#define GOKZ_MAX_MAPTRIGGERS_ERROR_LENGTH 256
+
+#define CHAR_ESCAPE view_as<char>(27)
+
+#define GOKZ_SAFEGUARD_RESTART_MIN_DELAY 0.6
+#define GOKZ_SAFEGUARD_RESTART_MAX_DELAY 5.0
+
+// Prevents the player from retouching a trigger too often.
+#define GOKZ_MAX_RETOUCH_TRIGGER_COUNT 4
+
+stock char gC_TimeTypeNames[TIMETYPE_COUNT][] =
+{
+ "NUB",
+ "PRO"
+};
+
+stock char gC_ModeNames[MODE_COUNT][] =
+{
+ "Vanilla",
+ "SimpleKZ",
+ "KZTimer"
+};
+
+stock char gC_ModeNamesShort[MODE_COUNT][] =
+{
+ "VNL",
+ "SKZ",
+ "KZT"
+};
+
+stock char gC_ModeKeys[MODE_COUNT][] =
+{
+ "vanilla",
+ "simplekz",
+ "kztimer"
+};
+
+stock float gF_ModeVirtualButtonRanges[MODE_COUNT] =
+{
+ 0.0,
+ 32.0,
+ 70.0
+};
+
+stock char gC_ModeStartSounds[MODE_COUNT][] =
+{
+ "common/wpn_select.wav",
+ "buttons/button9.wav",
+ "buttons/button3.wav"
+};
+
+stock char gC_ModeEndSounds[MODE_COUNT][] =
+{
+ "common/wpn_select.wav",
+ "buttons/bell1.wav",
+ "buttons/button3.wav"
+};
+
+stock char gC_ModeFalseEndSounds[MODE_COUNT][] =
+{
+ "common/wpn_select.wav",
+ "buttons/button11.wav",
+ "buttons/button2.wav"
+};
+
+stock char gC_StyleNames[STYLE_COUNT][] =
+{
+ "Normal"
+};
+
+stock char gC_StyleNamesShort[STYLE_COUNT][] =
+{
+ "NRM"
+};
+
+stock char gC_CoreOptionNames[OPTION_COUNT][] =
+{
+ "GOKZ - Mode",
+ "GOKZ - Style",
+ "GOKZ - Checkpoint Messages",
+ "GOKZ - Checkpoint Sounds",
+ "GOKZ - Teleport Sounds",
+ "GOKZ - Error Sounds",
+ "GOKZ - VB Indicators",
+ "GOKZ - Timer Button Zone Type",
+ "GOKZ - Button Through Players",
+ "GOKZ - Safeguard"
+};
+
+stock char gC_CoreOptionDescriptions[OPTION_COUNT][] =
+{
+ "Movement Mode - 0 = Vanilla, 1 = SimpleKZ, 2 = KZTimer",
+ "Movement Style - 0 = Normal",
+ "Checkpoint Messages - 0 = Disabled, 1 = Enabled",
+ "Checkpoint Sounds - 0 = Disabled, 1 = Enabled",
+ "Teleport Sounds - 0 = Disabled, 1 = Enabled",
+ "Error Sounds - 0 = Disabled, 1 = Enabled",
+ "Virtual Button Indicators - 0 = Disabled, 1 = Enabled",
+ "Timer Button Zone Type - 0 = Both buttons, 1 = Only end zone, 2 = Both zones",
+ "Button Through Players - 0 = Disabled, 1 = Enabled",
+ "Safeguard - 0 = Disabled, 1 = Enabled (NUB), 2 = Enabled (PRO)"
+};
+
+stock char gC_CoreOptionPhrases[OPTION_COUNT][] =
+{
+ "Options Menu - Mode",
+ "Options Menu - Style",
+ "Options Menu - Checkpoint Messages",
+ "Options Menu - Checkpoint Sounds",
+ "Options Menu - Teleport Sounds",
+ "Options Menu - Error Sounds",
+ "Options Menu - Virtual Button Indicators",
+ "Options Menu - Timer Button Zone Type",
+ "Options Menu - Button Through Players",
+ "Options Menu - Safeguard"
+};
+
+stock char gC_TimerButtonZoneTypePhrases[TIMERBUTTONZONETYPE_COUNT][] =
+{
+ "Timer Button Zone Type - Both Buttons",
+ "Timer Button Zone Type - Only End Zone",
+ "Timer Button Zone Type - Both Zones"
+};
+
+stock char gC_SafeGuardPhrases[SAFEGUARD_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Safeguard - Enabled NUB",
+ "Safeguard - Enabled PRO"
+}
+
+stock int gI_CoreOptionCounts[OPTION_COUNT] =
+{
+ MODE_COUNT,
+ STYLE_COUNT,
+ CHECKPOINTMESSAGES_COUNT,
+ CHECKPOINTSOUNDS_COUNT,
+ TELEPORTSOUNDS_COUNT,
+ ERRORSOUNDS_COUNT,
+ VIRTUALBUTTONINDICATORS_COUNT,
+ TIMERBUTTONZONETYPE_COUNT,
+ BUTTONTHROUGHPLAYERS_COUNT,
+ SAFEGUARD_COUNT
+};
+
+stock int gI_CoreOptionDefaults[OPTION_COUNT] =
+{
+ Mode_KZTimer,
+ Style_Normal,
+ CheckpointMessages_Disabled,
+ CheckpointSounds_Enabled,
+ TeleportSounds_Disabled,
+ ErrorSounds_Enabled,
+ VirtualButtonIndicators_Disabled,
+ TimerButtonZoneType_BothButtons,
+ ButtonThroughPlayers_Enabled,
+ Safeguard_Disabled
+};
+
+stock char gC_ModeCVars[MODECVAR_COUNT][] =
+{
+ "sv_accelerate",
+ "sv_accelerate_use_weapon_speed",
+ "sv_airaccelerate",
+ "sv_air_max_wishspeed",
+ "sv_enablebunnyhopping",
+ "sv_friction",
+ "sv_gravity",
+ "sv_jump_impulse",
+ "sv_ladder_scale_speed",
+ "sv_ledge_mantle_helper",
+ "sv_maxspeed",
+ "sv_maxvelocity",
+ "sv_staminajumpcost",
+ "sv_staminalandcost",
+ "sv_staminamax",
+ "sv_staminarecoveryrate",
+ "sv_standable_normal",
+ "sv_timebetweenducks",
+ "sv_walkable_normal",
+ "sv_wateraccelerate",
+ "sv_water_movespeed_multiplier",
+ "sv_water_swim_mode",
+ "sv_weapon_encumbrance_per_item",
+ "sv_weapon_encumbrance_scale"
+};
+
+
+// =====[ STRUCTS ]=====
+
+enum struct Checkpoint
+{
+ float origin[3];
+ float angles[3];
+ float ladderNormal[3];
+ bool onLadder;
+ int groundEnt;
+
+ void Create(int client)
+ {
+ Movement_GetOrigin(client, this.origin);
+ Movement_GetEyeAngles(client, this.angles);
+ GetEntPropVector(client, Prop_Send, "m_vecLadderNormal", this.ladderNormal);
+ this.onLadder = Movement_GetMovetype(client) == MOVETYPE_LADDER;
+ this.groundEnt = GetEntPropEnt(client, Prop_Data, "m_hGroundEntity");
+ }
+}
+
+enum struct UndoTeleportData
+{
+ float tempOrigin[3];
+ float tempAngles[3];
+ float origin[3];
+ float angles[3];
+ // Undo TP properties
+ bool lastTeleportOnGround;
+ bool lastTeleportInBhopTrigger;
+ bool lastTeleportInAntiCpTrigger;
+
+ void Init(int client, bool lastTeleportInBhopTrigger, bool lastTeleportOnGround, bool lastTeleportInAntiCpTrigger)
+ {
+ Movement_GetOrigin(client, this.tempOrigin);
+ Movement_GetEyeAngles(client, this.tempAngles);
+ this.lastTeleportInBhopTrigger = lastTeleportInBhopTrigger;
+ this.lastTeleportOnGround = lastTeleportOnGround;
+ this.lastTeleportInAntiCpTrigger = lastTeleportInAntiCpTrigger;
+ }
+
+ void Update()
+ {
+ this.origin = this.tempOrigin;
+ this.angles = this.tempAngles;
+ }
+}
+
+
+// NOTE: gokz-core/map/entlump.sp
+enum struct EntlumpToken
+{
+ EntlumpTokenType type;
+ char string[GOKZ_ENTLUMP_MAX_VALUE];
+}
+
+// NOTE: gokz-core/map/triggers.sp
+enum struct AntiBhopTrigger
+{
+ int entRef;
+ int hammerID;
+ float time;
+}
+
+enum struct TeleportTrigger
+{
+ int hammerID;
+ TeleportType type;
+ float delay;
+ char tpDestination[256];
+ bool useDestAngles;
+ bool resetSpeed;
+ bool relativeDestination;
+ bool reorientPlayer;
+}
+
+enum struct TouchedTrigger
+{
+ TriggerType triggerType;
+ int entRef; // entref of one of the TeleportTriggers
+ int startTouchTick; // tick where the player touched the trigger
+ int groundTouchTick; // tick where the player touched the ground
+}
+
+// Legacy triggers that activate timer buttons.
+enum struct TimerButtonTrigger
+{
+ int hammerID;
+ int course;
+ bool isStartTimer;
+}
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when a player's options values are loaded from clientprefs.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnOptionsLoaded(int client);
+
+/**
+ * Called when a player's option's value is changed.
+ * Only called if client is in game.
+ *
+ * @param client Client index.
+ * @param option Option name.
+ * @param newValue New value of the option.
+ */
+forward void GOKZ_OnOptionChanged(int client, const char[] option, any newValue);
+
+/**
+ * Called when a player starts their timer.
+ *
+ * @param client Client index.
+ * @param course Course number.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnTimerStart(int client, int course);
+
+/**
+ * Called when a player has started their timer.
+ *
+ * @param client Client index.
+ * @param course Course number.
+ */
+forward void GOKZ_OnTimerStart_Post(int client, int course);
+
+/**
+ * Called when a player ends their timer.
+ *
+ * @param client Client index.
+ * @param course Course number.
+ * @param time Player's end time.
+ * @param teleportsUsed Number of teleports used by player.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnTimerEnd(int client, int course, float time, int teleportsUsed);
+
+/**
+ * Called when a player has ended their timer.
+ *
+ * @param client Client index.
+ * @param course Course number.
+ * @param time Player's end time.
+ * @param teleportsUsed Number of teleports used by player.
+ */
+forward void GOKZ_OnTimerEnd_Post(int client, int course, float time, int teleportsUsed);
+
+/**
+ * Called when the end timer message is printed to chat.
+ *
+ * @param client Client index.
+ * @param course Course number.
+ * @param time Player's end time.
+ * @param teleportsUsed Number of teleports used by player.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnTimerEndMessage(int client, int course, float time, int teleportsUsed);
+
+/**
+ * Called when a player's timer has been forcefully stopped.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnTimerStopped(int client);
+
+/**
+ * Called when a player pauses.
+ *
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnPause(int client);
+
+/**
+ * Called when a player has paused.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnPause_Post(int client);
+
+/**
+ * Called when a player resumes.
+ *
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnResume(int client);
+
+/**
+ * Called when a player has resumed.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnResume_Post(int client);
+
+/**
+ * Called when a player makes a checkpoint.
+ *
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnMakeCheckpoint(int client);
+
+/**
+ * Called when a player has made a checkpoint.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnMakeCheckpoint_Post(int client);
+
+/**
+ * Called when a player teleports to their checkpoint.
+ *
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnTeleportToCheckpoint(int client);
+
+/**
+ * Called when a player has teleported to their checkpoint.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnTeleportToCheckpoint_Post(int client);
+
+/**
+ * Called when a player goes to a previous checkpoint.
+ *
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnPrevCheckpoint(int client);
+
+/**
+ * Called when a player has gone to a previous checkpoint.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnPrevCheckpoint_Post(int client);
+
+/**
+ * Called when a player goes to a next checkpoint.
+ *
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnNextCheckpoint(int client);
+
+/**
+ * Called when a player has gone to a next checkpoint.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnNextCheckpoint_Post(int client);
+
+/**
+ * Called when a player teleports to start.
+ *
+ * @param client Client index.
+ * @param course Course index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnTeleportToStart(int client, int course);
+
+/**
+ * Called when a player has teleported to start.
+ *
+ * @param client Client index.
+ * @param course Course index.
+ */
+forward void GOKZ_OnTeleportToStart_Post(int client, int course);
+
+/**
+ * Called when a player teleports to end.
+ *
+ * @param client Client index.
+ * @param course Course index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnTeleportToEnd(int client, int course);
+
+/**
+ * Called when a player has teleported to end.
+ *
+ * @param client Client index.
+ * @param course Course index.
+ */
+forward void GOKZ_OnTeleportToEnd_Post(int client, int course);
+
+/**
+ * Called when a player undoes a teleport.
+ *
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnUndoTeleport(int client);
+
+/**
+ * Called when a player has undone a teleport.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnUndoTeleport_Post(int client);
+
+/**
+ * Called when a player has performed a counted teleport (teleport count went up)
+ * i.e. a catch-all for teleport to checkpoint, teleport to start, undo teleport etc.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnCountedTeleport_Post(int client);
+
+/**
+ * Called when a player's start position is set.
+ *
+ * @param client Client index.
+ * @param type Start position type.
+ * @param origin Start position origin.
+ * @param angles Start position eye angles.
+ */
+forward void GOKZ_OnStartPositionSet_Post(int client, StartPositionType type, const float origin[3], const float angles[3]);
+
+/**
+ * Called when player's begins a jump that is deemed valid.
+ * A jump is deemed invalid if a player is teleported.
+ *
+ * @param client Client index.
+ * @param jumped Whether player jumped.
+ * @param ladderJump Whether it was a ladder jump.
+ * @param jumpbug Whether player performed a jumpbug.
+ */
+forward void GOKZ_OnJumpValidated(int client, bool jumped, bool ladderJump, bool jumpbug);
+
+/**
+ * Called when player's current jump is invalidated.
+ * A jump is deemed invalid if a player is teleported.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnJumpInvalidated(int client);
+
+/**
+ * Called when a player has been switched to a team.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnJoinTeam(int client, int team);
+
+/**
+ * Called the first time a player spawns in on a team.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnFirstSpawn(int client);
+
+/**
+ * Called when a mode has been loaded.
+ *
+ * @param mode Mode loaded.
+ */
+forward void GOKZ_OnModeLoaded(int mode);
+
+/**
+ * Called when a mode has been unloaded.
+ *
+ * @param mode Mode unloaded.
+ */
+forward void GOKZ_OnModeUnloaded(int mode);
+
+/**
+ * Called when a plugin other than gokz-core calls a native
+ * that may affect a player's timer or teleport count in
+ * their favour e.g. GOKZ_StartTimer, GOKZ_EndTimer,
+ * GOKZ_SetTime and GOKZ_SetTeleportCount.
+ *
+ * @param plugin Handle of the calling plugin.
+ * @param client Client index.
+ * @return Plugin_Handled or Plugin_Stop to block, Plugin_Continue to proceed.
+ */
+forward Action GOKZ_OnTimerNativeCalledExternally(Handle plugin, int client);
+
+/**
+ * Called when the options menu has been created and 3rd
+ * party plugins can grab the handle or add categories.
+ *
+ * @param topMenu Options top menu handle.
+ */
+forward void GOKZ_OnOptionsMenuCreated(TopMenu topMenu);
+
+/**
+ * Called when the options menu is ready to have items added.
+ *
+ * @param topMenu Options top menu handle.
+ */
+forward void GOKZ_OnOptionsMenuReady(TopMenu topMenu);
+
+/**
+ * Called when a course is registered. A course is registered if both the
+ * start and end of it (e.g. timer buttons) have been detected.
+ *
+ * @param course Course number.
+ */
+forward void GOKZ_OnCourseRegistered(int course);
+
+/**
+ * Called when a player's run becomes invalidated.
+ * An invalidated run doesn't necessarily stop the timer.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_OnRunInvalidated(int client);
+
+/**
+ * Called when a sound is emitted to the client via GOKZ Core.
+ *
+ * @param client Client index.
+ * @param sample Sound file name relative to the "sound" folder.
+ * @param volume Sound volume.
+ * @param description Optional description.
+ * @return Plugin_Continue to allow the sound to be played, Plugin_Stop to block it,
+ * Plugin_Changed when any parameter has been modified.
+ */
+forward Action GOKZ_OnEmitSoundToClient(int client, const char[] sample, float &volume, const char[] description);
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets whether a mode is loaded.
+ *
+ * @param mode Mode.
+ * @return Whether mode is loaded.
+ */
+native bool GOKZ_GetModeLoaded(int mode);
+
+/**
+ * Gets the version number of a loaded mode.
+ *
+ * @param mode Mode.
+ * @return Version number of the mode, or -1 if not loaded.
+ */
+native int GOKZ_GetModeVersion(int mode);
+
+/**
+ * Sets whether a mode is loaded. To be used by mode plugins.
+ *
+ * @param mode Mode.
+ * @param loaded Whether mode is loaded.
+ * @param version Version number of the mode.
+ */
+native void GOKZ_SetModeLoaded(int mode, bool loaded, int version = -1);
+
+/**
+ * Gets the total number of loaded modes.
+ *
+ * @return Number of loaded modes.
+ */
+native int GOKZ_GetLoadedModeCount();
+
+/**
+ * Sets the player's current mode.
+ * If the player's timer is running, it will be stopped.
+ *
+ * @param client Client index.
+ * @param mode Mode.
+ * @return Whether the operation was successful.
+ */
+native bool GOKZ_SetMode(int client, int mode);
+
+/**
+ * Gets the Handle to the options top menu.
+ *
+ * @return Handle to the options top menu,
+ * or null if not created yet.
+ */
+native TopMenu GOKZ_GetOptionsTopMenu();
+
+/**
+ * Gets whether a course is registered. A course is registered if both the
+ * start and end of it (e.g. timer buttons) have been detected.
+ *
+ * @param course Course number.
+ * @return Whether course has been registered.
+ */
+native bool GOKZ_GetCourseRegistered(int course);
+
+/**
+ * Prints a message to a client's chat, formatting colours and optionally
+ * adding the chat prefix. If using the chat prefix, specify a colour at
+ * the beginning of the message e.g. "{default}Hello!".
+ *
+ * @param client Client index.
+ * @param addPrefix Whether to add the chat prefix.
+ * @param format Formatting rules.
+ * @param any Variable number of format parameters.
+ */
+native void GOKZ_PrintToChat(int client, bool addPrefix, const char[] format, any...);
+
+/**
+ * Prints a message to a client's chat, formatting colours and optionally
+ * adding the chat prefix. If using the chat prefix, specify a colour at
+ * the beginning of the message e.g. "{default}Hello!". Also prints the
+ * message to the server log.
+ *
+ * @param client Client index.
+ * @param addPrefix Whether to add the chat prefix.
+ * @param format Formatting rules.
+ * @param any Variable number of format parameters.
+ */
+native void GOKZ_PrintToChatAndLog(int client, bool addPrefix, const char[] format, any...);
+
+/**
+ * Starts a player's timer for a course on the current map.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param course Course number.
+ * @param allowMidair Whether player is allowed to start timer midair.
+ * @return Whether player's timer was started.
+ */
+native bool GOKZ_StartTimer(int client, int course, bool allowOffGround = false);
+
+/**
+ * Ends a player's timer for a course on the current map.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param course Course number.
+ * @return Whether player's timer was ended.
+ */
+native bool GOKZ_EndTimer(int client, int course);
+
+/**
+ * Forces a player's timer to stop. Intended for run invalidation.
+ *
+ * @param client Client index.
+ * @param playSound Whether to play the timer stop sound.
+ * @return Whether player's timer was stopped.
+ */
+native bool GOKZ_StopTimer(int client, bool playSound = true);
+
+/**
+ * Forces all players' timers to stop. Intended for run invalidation.
+ *
+ * @param playSound Whether to play the timer stop sound.
+ */
+native void GOKZ_StopTimerAll(bool playSound = true);
+
+/**
+ * Gets whether or not a player's timer is running i.e. isn't 'stopped'.
+ *
+ * @param client Client index.
+ * @return Whether player's timer is running.
+ */
+native bool GOKZ_GetTimerRunning(int client);
+
+/**
+ * Gets whether or not a player's timer is valid i.e the run is a valid run.
+ *
+ * @param client Client index.
+ * @return Whether player's timer is running.
+ */
+native bool GOKZ_GetValidTimer(int client);
+
+/**
+ * Gets the course a player is currently running.
+ *
+ * @param client Client index.
+ * @return Course number.
+ */
+native int GOKZ_GetCourse(int client);
+
+/**
+ * Set the player's current course.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param course Course number.
+ * @return Whether native was allowed to proceed.
+ */
+native bool GOKZ_SetCourse(int client, int course);
+
+/**
+ * Gets whether a player is paused.
+ *
+ * @param client Client index.
+ * @return Whether player is paused.
+ */
+native bool GOKZ_GetPaused(int client);
+
+/**
+ * Gets a player's current run time.
+ *
+ * @param client Client index.
+ * @return Player's current run time.
+ */
+native float GOKZ_GetTime(int client);
+
+/**
+ * Gets a player's current run time.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param time Run time to set to.
+ * @return Whether native was allowed to proceed.
+ */
+native bool GOKZ_SetTime(int client, float time);
+
+/**
+ * Mark a player's run as invalid without stopping the timer.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_InvalidateRun(int client);
+
+/**
+ * Gets a player's current checkpoint count.
+ *
+ * @param client Client index.
+ * @return Player's current checkpoint count.
+ */
+native int GOKZ_GetCheckpointCount(int client);
+
+/**
+ * Sets a player's current checkpoint count.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param cpCount Checkpoint count to set to.
+ * @return Whether native was allowed to proceed.
+ */
+native int GOKZ_SetCheckpointCount(int client, int cpCount);
+
+/**
+ * Gets checkpoint data of a player.
+ *
+ * @param client Client index.
+ * @return Client's checkpoint data.
+ */
+native ArrayList GOKZ_GetCheckpointData(int client);
+
+/**
+ * Sets checkpoint data of a player. The checkpoint data is assumed to be ordered.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param checkpoints Checkpoint data.
+ * @param version Checkpoint version.
+ * @return Whether native was allowed to proceed and operation was successful.
+ */
+native bool GOKZ_SetCheckpointData(int client, ArrayList checkpoints, int version);
+
+/**
+ * Get undo teleport data of a player.
+ *
+ * @param client Client index.
+ * @return ArrayList of length 1 containing player's undo teleport data.
+ */
+native ArrayList GOKZ_GetUndoTeleportData(int client);
+
+/**
+ * Set undo teleport data of a player.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param undoTeleportDataArray ArrayList of length 1 containing player's undo teleport data.
+ * @param version Checkpoint version.
+ * @return Whether native was allowed to proceed and operation was successful.
+ */
+native bool GOKZ_SetUndoTeleportData(int client, ArrayList undoTeleportDataArray, int version);
+
+/**
+ * Gets a player's current teleport count.
+ *
+ * @param client Client index.
+ * @return Player's current teleport count.
+ */
+native int GOKZ_GetTeleportCount(int client);
+
+/**
+ * Sets a player's current teleport count.
+ * This can be blocked by OnTimerNativeCalledExternally().
+ *
+ * @param client Client index.
+ * @param tpCount Teleport count to set to.
+ * @return Whether native was allowed to proceed.
+ */
+native bool GOKZ_SetTeleportCount(int client, int tpCount);
+
+/**
+ * Teleports a player to start, or respawns them.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_TeleportToStart(int client);
+
+/**
+ * Teleports a player to the start zone/button of the specified course.
+ *
+ * @param client Client index.
+ * @param course Course index.
+ */
+native void GOKZ_TeleportToSearchStart(int client, int course);
+
+/**
+ * Gets the virtual button position the player currently has.
+ *
+ * @param client Client index.
+ * @param position Returns the client's virtual button position.
+ * @param isStart True to get the start button position, false for the end button.
+ * @return The course the button belongs to.
+ */
+native int GOKZ_GetVirtualButtonPosition(int client, float position[3], bool isStart);
+
+/**
+ * Sets the virtual button position the player currently has.
+ *
+ * @param client Client index.
+ * @param position The client's virtual button position.
+ * @param course The course the virtual button belongs to.
+ * @param isStart True to get the start button position, false for the end button.
+ */
+native void GOKZ_SetVirtualButtonPosition(int client, const float position[3], int course, bool isStart);
+
+/**
+ * Resets the player's virtual button.
+ *
+ * @param client Client index.
+ * @param isStart True to get the start button position, false for the end button.
+ */
+native void GOKZ_ResetVirtualButtonPosition(int client, bool isStart);
+
+/**
+ * Locks the virtual button position of a player.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_LockVirtualButtons(int client);
+
+/**
+ * Gets the start position the player currently has.
+ *
+ * @param client Client index.
+ * @param position Returns the client's start position.
+ * @param angles Returns the client's start angles.
+ * @return Player's current start position type.
+ */
+native StartPositionType GOKZ_GetStartPosition(int client, float position[3], float angles[3]);
+
+/**
+ * Sets the start position the player currently has.
+ *
+ * @param client Client index.
+ * @param type The start position type.
+ * @param position The client's start position.
+ * @param angles The client's start angles.
+ */
+native void GOKZ_SetStartPosition(int client, StartPositionType type, const float position[3], const float angles[3]);
+
+/**
+ * Gets the type of start position the player currently has.
+ * The "Spawn" type means teleport to start will respawn the player.
+ *
+ * @param client Client index.
+ * @return Player's current start position type.
+ */
+native StartPositionType GOKZ_GetStartPositionType(int client);
+
+/**
+ * Set the start position of the player to the start of a course.
+ *
+ * @param client Client index.
+ * @param course Course index.
+ *
+ * @return False if the course start was not found.
+ */
+native bool GOKZ_SetStartPositionToMapStart(int client, int course);
+
+/**
+ * Teleports a player to end.
+ *
+ * @param client Client index.
+ * @param course Course index.
+ */
+native void GOKZ_TeleportToEnd(int client, int course);
+
+/**
+ * Set a new checkpoint at a player's current position.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_MakeCheckpoint(int client);
+
+/**
+ * Gets whether a player can make a new checkpoint.
+ * @param client Client index.
+ * @return Whether player can set a checkpoint.
+ */
+native bool GOKZ_GetCanMakeCheckpoint(int client);
+
+/**
+ * Teleports a player to their last checkpoint.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_TeleportToCheckpoint(int client);
+
+/**
+ * Gets whether a player can teleport to their checkpoint
+ * e.g. will return false if player has no checkpoints.
+ *
+ * @param client Client index.
+ * @return Whether player can teleport to checkpoint.
+ */
+native bool GOKZ_GetCanTeleportToCheckpoint(int client);
+
+/**
+ * Teleport a player back to a previous checkpoint.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_PrevCheckpoint(int client);
+
+/**
+ * Gets whether a player can go to their previous checkpoint
+ * e.g. will return false if player has no checkpoints.
+ *
+ * @param client Client index.
+ * @return Whether player can go to previous checkpoint.
+ */
+native bool GOKZ_GetCanPrevCheckpoint(int client);
+
+/**
+ * Teleport a player to a more recent checkpoint.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_NextCheckpoint(int client);
+
+/**
+ * Gets whether a player can go to their next checkpoint
+ * e.g. will return false if player has no checkpoints.
+ *
+ * @param client Client index.
+ * @return Whether player can go to next checkpoint.
+ */
+native bool GOKZ_GetCanNextCheckpoint(int client);
+
+/**
+ * Teleport a player to where they last teleported from.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_UndoTeleport(int client);
+
+/**
+ * Gets whether a player can undo their teleport
+ * e.g. will return false if teleport was from midair.
+ *
+ * @param client Client index.
+ * @return Whether player can undo teleport.
+ */
+native bool GOKZ_GetCanUndoTeleport(int client);
+
+/**
+ * Pause a player's timer and freeze them.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_Pause(int client);
+
+/**
+ * Gets whether a player can pause. Pausing is not allowed
+ * under some circumstance when the timer is running.
+ *
+ * @param client Client index.
+ */
+native bool GOKZ_GetCanPause(int client);
+
+/**
+ * Resumes a player's timer and unfreezes them.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_Resume(int client);
+
+/**
+ * Gets whether a player can resume. Resuming is not allowed
+ * under some circumstance when the timer is running.
+ *
+ * @param client Client index.
+ */
+native bool GOKZ_GetCanResume(int client);
+
+/**
+ * Toggles the paused state of a player.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_TogglePause(int client);
+
+/**
+ * Gets whether a player can teleport to start.
+ *
+ * @param client Client index.
+ * @return Whether player can teleport to start.
+ */
+native bool GOKZ_GetCanTeleportToStartOrEnd(int client);
+
+/**
+ * Plays the error sound to a player if they have the option enabled.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_PlayErrorSound(int client);
+
+/**
+ * Set the origin of a player without invalidating any jumpstats.
+ *
+ * Only use this in plugins that create a new mode!
+ *
+ * @param client Client index.
+ * @param origin The new origin.
+ */
+native void GOKZ_SetValidJumpOrigin(int client, const float origin[3]);
+
+/**
+ * Registers an option with gokz-core, which uses clientprefs to
+ * keep track of the option's value and to save it to a database.
+ * This also effectively provides natives and forwards for other
+ * plugins to access any options that have been registered.
+ *
+ * @param name Option name.
+ * @param description Option description.
+ * @param type Type to treat the option value as.
+ * @param defaultValue Default value of option.
+ * @param minValue Minimum value of option.
+ * @param maxValue Maximum value of option.
+ * @return Whether registration was successful.
+ */
+native bool GOKZ_RegisterOption(const char[] name, const char[] description, OptionType type, any defaultValue, any minValue, any maxValue);
+
+/**
+ * Gets a property of a registered option. If used outside of
+ * gokz-core to get the cookie, a clone of its Handle is returned.
+ *
+ * @param option Option name.
+ * @param prop Option property to get.
+ * @return Value of property, or -1 (int) if option isn't registered.
+ */
+native any GOKZ_GetOptionProp(const char[] option, OptionProp prop);
+
+/**
+ * Sets a property of a registered option. For safety and simplicity,
+ * the cookie property is read-only and will fail to be set.
+ *
+ * @param option Option name.
+ * @param prop Option property to set.
+ * @param value Value to set the property to.
+ * @return Whether option property was successfully set.
+ */
+native bool GOKZ_SetOptionProp(const char[] option, OptionProp prop, any value);
+
+/**
+ * Gets the current value of a player's option.
+ *
+ * @param client Client index.
+ * @param option Option name.
+ * @return Current value of option, or -1 (int) if option isn't registered.
+ */
+native any GOKZ_GetOption(int client, const char[] option);
+
+/**
+ * Sets a player's option's value. Fails if option doesn't exist,
+ * or if desired value is outside the registered value range.
+ *
+ * @param client Client index.
+ * @param option Option name.
+ * @param value New option value.
+ * @return Whether option was successfully set.
+ */
+native bool GOKZ_SetOption(int client, const char[] option, any value);
+
+/**
+ * Gets whether player's last takeoff was a perfect bunnyhop as adjusted by GOKZ.
+ *
+ * @param client Client index.
+ * @return Whether player's last takeoff was a GOKZ perfect b-hop.
+ */
+native bool GOKZ_GetHitPerf(int client);
+
+/**
+ * Sets whether player's last takeoff was a perfect bunnyhop as adjusted by GOKZ.
+ * Intended to be called by GOKZ mode plugins only.
+ *
+ * @param client Client index.
+ * @param hitPerf Whether player's last takeoff was a GOKZ perfect b-hop.
+ */
+native void GOKZ_SetHitPerf(int client, bool hitPerf);
+
+/**
+ * Gets a player's horizontal speed at the time of their last takeoff as recorded by GOKZ.
+ *
+ * @param client Client index.
+ * @return Player's last takeoff speed as recorded by GOKZ.
+ */
+native float GOKZ_GetTakeoffSpeed(int client);
+
+/**
+ * Sets a player's recorded horizontal speed at the time of their last takeoff.
+ * Intended to be called by GOKZ mode plugins only.
+ *
+ * @param client Client index.
+ * @param takeoffSpeed Player's last takeoff speed as recorded by GOKZ.
+ */
+native void GOKZ_SetTakeoffSpeed(int client, float takeoffSpeed);
+
+/**
+ * Gets whether a player's current or last jump/airtime is valid.
+ * A jump is deemed invalid if the player is teleported.
+ *
+ * @param client Client index.
+ * @return Validity of player's current or last jump.
+ */
+native bool GOKZ_GetValidJump(int client);
+
+/**
+ * Has a player switch to a team via GOKZ Core.
+ *
+ * @param client Client index.
+ * @param team Which team to switch to.
+ * @param restorePos Whether to restore saved position if leaving spectators.
+ * @param forceBroadcast Force JoinTeam forward calling even if client's team did not change.
+ */
+native void GOKZ_JoinTeam(int client, int team, bool restorePos = true, bool forceBroadcast = false);
+
+/**
+ * Emit a sound to a player via GOKZ Core.
+ * Sounds emitted by this native will call GOKZ_OnEmitSoundToClient forward.
+ *
+ * @param client Client index.
+ * @param sample Sound file name relative to the "sound" folder.
+ * @param volume Sound volume.
+ * @param description Optional description.
+ */
+native void GOKZ_EmitSoundToClient(int client, const char[] sample, float volume = SNDVOL_NORMAL, const char[] description = "");
+
+
+// =====[ STOCKS ]=====
+
+/**
+ * Makes a player join a team if they aren't on one and respawns them.
+ *
+ * @param client Client index.
+ * @param team Which team to switch to if not on one.
+ * @param restorePos Whether to restore saved position if leaving spectators.
+ */
+stock void GOKZ_RespawnPlayer(int client, int team = CS_TEAM_T, bool restorePos = true)
+{
+ if (IsSpectating(client))
+ {
+ GOKZ_JoinTeam(client, team, restorePos);
+ }
+ else
+ {
+ CS_RespawnPlayer(client);
+ }
+}
+
+/**
+ * Prints a message to all client's chat, formatting colours and optionally
+ * adding the chat prefix. If using the chat prefix, specify a colour at
+ * the beginning of the message e.g. "{default}Hello!".
+ *
+ * @param addPrefix Whether to add the chat prefix.
+ * @param format Formatting rules.
+ * @param any Variable number of format parameters.
+ */
+stock void GOKZ_PrintToChatAll(bool addPrefix, const char[] format, any...)
+{
+ char buffer[1024];
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (IsClientInGame(client))
+ {
+ SetGlobalTransTarget(client);
+ VFormat(buffer, sizeof(buffer), format, 3);
+ GOKZ_PrintToChat(client, addPrefix, buffer);
+ }
+ }
+}
+
+/**
+ * Prints a chat message to those spectating the client, formatting colours
+ * and optionally adding the chat prefix. If using the chat prefix, specify
+ * a colour at the beginning of the message e.g. "{default}Hello!".
+ *
+ * @param client Client index.
+ * @param addPrefix Whether to add the chat prefix.
+ * @param format Formatting rules.
+ * @param any Variable number of format parameters.
+ */
+stock void GOKZ_PrintToChatSpectators(int client, bool addPrefix, const char[] format, any...)
+{
+ char buffer[1024];
+ for (int target = 1; target <= MaxClients; target++)
+ {
+ if (IsClientInGame(target) && GetObserverTarget(target) == client)
+ {
+ SetGlobalTransTarget(target);
+ VFormat(buffer, sizeof(buffer), format, 4);
+ GOKZ_PrintToChat(target, addPrefix, buffer);
+ }
+ }
+}
+
+/**
+ * Gets the player's current time type.
+ *
+ * @param client Client index.
+ * @return Player's current time type.
+ */
+stock int GOKZ_GetTimeType(int client)
+{
+ return GOKZ_GetTimeTypeEx(GOKZ_GetTeleportCount(client));
+}
+
+/**
+ * Gets the time type given a teleport count.
+ *
+ * @param teleports Teleport count.
+ * @return Time type.
+ */
+stock int GOKZ_GetTimeTypeEx(int teleportCount)
+{
+ if (teleportCount == 0)
+ {
+ return TimeType_Pro;
+ }
+ return TimeType_Nub;
+}
+
+/**
+ * Clears and populates a menu with an item for each mode
+ * in order of the mode enumeration. Highlights the client's
+ * selected mode with an asterisk.
+ *
+ * @param client Client index to check selected mode.
+ * @param menu Menu to populate items with.
+ * @param disableUnloadedModes Draw items for unloaded modes as disabled.
+ */
+stock void GOKZ_MenuAddModeItems(int client, Menu menu, bool disableUnloadedModes)
+{
+ int selectedMode = GOKZ_GetCoreOption(client, Option_Mode);
+ char display[32];
+
+ menu.RemoveAllItems();
+
+ for (int mode = 0; mode < MODE_COUNT; mode++)
+ {
+ FormatEx(display, sizeof(display), "%s", gC_ModeNames[mode]);
+ // Add asterisk to selected mode
+ if (mode == selectedMode)
+ {
+ Format(display, sizeof(display), "%s*", display);
+ }
+
+ if (GOKZ_GetModeLoaded(mode))
+ {
+ menu.AddItem("", display, ITEMDRAW_DEFAULT);
+ }
+ else
+ {
+ menu.AddItem("", display, ITEMDRAW_DISABLED);
+ }
+ }
+}
+
+/**
+ * Increment an (integer-type) option's value.
+ * Loops back to min. value if max. value is exceeded.
+ *
+ * @param client Client index.
+ * @param option Option name.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_CycleOption(int client, const char[] option)
+{
+ int maxValue = GOKZ_GetOptionProp(option, OptionProp_MaxValue);
+ if (maxValue == -1)
+ {
+ return false;
+ }
+
+ int newValue = GOKZ_GetOption(client, option) + 1;
+ if (newValue > GOKZ_GetOptionProp(option, OptionProp_MaxValue))
+ {
+ newValue = GOKZ_GetOptionProp(option, OptionProp_MinValue);
+ }
+ return GOKZ_SetOption(client, option, newValue);
+}
+
+/**
+ * Returns whether an option is a gokz-core option.
+ *
+ * @param option Option name.
+ * @param optionEnum Variable to store enumerated gokz-core option (if it is one).
+ * @return Whether option is a gokz-core option.
+ */
+stock bool GOKZ_IsCoreOption(const char[] option, Option &optionEnum = OPTION_INVALID)
+{
+ for (Option i; i < OPTION_COUNT; i++)
+ {
+ if (StrEqual(option, gC_CoreOptionNames[i]))
+ {
+ optionEnum = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Gets a property of a gokz-core option.
+ *
+ * @param coreOption gokz-core option.
+ * @param prop Option property to get.
+ * @return Value of property, or -1 if option isn't registered.
+ */
+stock any GOKZ_GetCoreOptionProp(Option option, OptionProp prop)
+{
+ return GOKZ_GetOptionProp(gC_CoreOptionNames[option], prop);
+}
+
+/**
+ * Gets the current value of a player's gokz-core option.
+ *
+ * @param client Client index.
+ * @param option gokz-core option.
+ * @return Current value of option.
+ */
+stock any GOKZ_GetCoreOption(int client, Option option)
+{
+ return GOKZ_GetOption(client, gC_CoreOptionNames[option]);
+}
+
+/**
+ * Sets the player's gokz-core option's value.
+ *
+ * @param client Client index.
+ * @param option gokz-core option.
+ * @param value New option value.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_SetCoreOption(int client, Option option, any value)
+{
+ return GOKZ_SetOption(client, gC_CoreOptionNames[option], value);
+}
+
+/**
+ * Increment an integer-type gokz-core option's value.
+ * Loops back to '0' if max value is exceeded.
+ *
+ * @param client Client index.
+ * @param option gokz-core option.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_CycleCoreOption(int client, Option option)
+{
+ return GOKZ_CycleOption(client, gC_CoreOptionNames[option]);
+}
+
+/**
+ * Gets the current default mode.
+ *
+ * @return Default mode.
+ */
+stock int GOKZ_GetDefaultMode()
+{
+ return GOKZ_GetCoreOptionProp(Option_Mode, OptionProp_DefaultValue);
+}
+
+/**
+ * Returns whether a course number is a valid (within valid range).
+ *
+ * @param course Course number.
+ * @param bonus Whether to only consider bonus course numbers as valid.
+ * @return Whether course number is valid.
+ */
+stock bool GOKZ_IsValidCourse(int course, bool bonus = false)
+{
+ return (!bonus && course == 0) || (0 < course && course < GOKZ_MAX_COURSES);
+}
+
+/**
+ * Returns an integer from an entity's name as matched using a regular expression.
+ *
+ * @param entity Entity index.
+ * @param re Regular expression to match the integer with.
+ * @param substringID ID of the substring that will contain the integer.
+ * @returns Integer found in the entity's name, or -1 if not found.
+ */
+stock int GOKZ_MatchIntFromEntityName(int entity, Regex re, int substringID)
+{
+ int num = -1;
+ char buffer[32];
+ GetEntityName(entity, buffer, sizeof(buffer));
+
+ if (re.Match(buffer) > 0)
+ {
+ re.GetSubString(1, buffer, sizeof(buffer));
+ num = StringToInt(buffer);
+ }
+
+ return num;
+}
+
+/**
+ * Emits a sound to other players that are spectating the client.
+ * Sounds emitted by this function will call GOKZ_OnEmitSoundToClient forward.
+ *
+ * @param sample Sound file name relative to the "sound" folder.
+ * @param volume Sound volume.
+ * @param description Optional description.
+ */
+stock void GOKZ_EmitSoundToAll(const char[] sample, float volume = SNDVOL_NORMAL, const char[] description = "")
+{
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (IsClientInGame(client))
+ {
+ GOKZ_EmitSoundToClient(client, sample, volume, description);
+ }
+ }
+}
+
+/**
+ * Emits a sound to other players that are spectating the client.
+ * Sounds emitted by this function will call GOKZ_OnEmitSoundToClient forward.
+ *
+ * @param client Client being spectated.
+ * @param sample Sound file name relative to the "sound" folder.
+ * @param volume Sound volume.
+ * @param description Optional description.
+ */
+stock void GOKZ_EmitSoundToClientSpectators(int client, const char[] sample, float volume = SNDVOL_NORMAL, const char[] description = "")
+{
+ for (int i = 1; i <= MaxClients; i++)
+ {
+ if (IsValidClient(i) && GetObserverTarget(i) == client)
+ {
+ GOKZ_EmitSoundToClient(i, sample, volume);
+ }
+ }
+}
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_core =
+{
+ name = "gokz-core",
+ file = "gokz-core.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_core_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_GetModeLoaded");
+ MarkNativeAsOptional("GOKZ_GetModeVersion");
+ MarkNativeAsOptional("GOKZ_SetModeLoaded");
+ MarkNativeAsOptional("GOKZ_GetLoadedModeCount");
+ MarkNativeAsOptional("GOKZ_SetMode");
+ MarkNativeAsOptional("GOKZ_GetOptionsTopMenu");
+ MarkNativeAsOptional("GOKZ_GetCourseRegistered");
+ MarkNativeAsOptional("GOKZ_PrintToChat");
+ MarkNativeAsOptional("GOKZ_PrintToChatAndLog");
+ MarkNativeAsOptional("GOKZ_StartTimer");
+ MarkNativeAsOptional("GOKZ_EndTimer");
+ MarkNativeAsOptional("GOKZ_StopTimer");
+ MarkNativeAsOptional("GOKZ_StopTimerAll");
+ MarkNativeAsOptional("GOKZ_TeleportToStart");
+ MarkNativeAsOptional("GOKZ_TeleportToSearchStart");
+ MarkNativeAsOptional("GOKZ_GetVirtualButtonPosition");
+ MarkNativeAsOptional("GOKZ_SetVirtualButtonPosition");
+ MarkNativeAsOptional("GOKZ_ResetVirtualButtonPosition");
+ MarkNativeAsOptional("GOKZ_LockVirtualButtons");
+ MarkNativeAsOptional("GOKZ_GetStartPosition");
+ MarkNativeAsOptional("GOKZ_SetStartPosition");
+ MarkNativeAsOptional("GOKZ_GetStartPositionType");
+ MarkNativeAsOptional("GOKZ_SetStartPositionToMapStart");
+ MarkNativeAsOptional("GOKZ_TeleportToEnd");
+ MarkNativeAsOptional("GOKZ_MakeCheckpoint");
+ MarkNativeAsOptional("GOKZ_GetCanMakeCheckpoint");
+ MarkNativeAsOptional("GOKZ_TeleportToCheckpoint");
+ MarkNativeAsOptional("GOKZ_GetCanTeleportToCheckpoint");
+ MarkNativeAsOptional("GOKZ_PrevCheckpoint");
+ MarkNativeAsOptional("GOKZ_GetCanPrevCheckpoint");
+ MarkNativeAsOptional("GOKZ_NextCheckpoint");
+ MarkNativeAsOptional("GOKZ_GetCanNextCheckpoint");
+ MarkNativeAsOptional("GOKZ_UndoTeleport");
+ MarkNativeAsOptional("GOKZ_GetCanUndoTeleport");
+ MarkNativeAsOptional("GOKZ_Pause");
+ MarkNativeAsOptional("GOKZ_GetCanPause");
+ MarkNativeAsOptional("GOKZ_Resume");
+ MarkNativeAsOptional("GOKZ_GetCanResume");
+ MarkNativeAsOptional("GOKZ_TogglePause");
+ MarkNativeAsOptional("GOKZ_GetCanTeleportToStartOrEnd");
+ MarkNativeAsOptional("GOKZ_PlayErrorSound");
+ MarkNativeAsOptional("GOKZ_SetValidJumpOrigin");
+ MarkNativeAsOptional("GOKZ_GetTimerRunning");
+ MarkNativeAsOptional("GOKZ_GetCourse");
+ MarkNativeAsOptional("GOKZ_SetCourse");
+ MarkNativeAsOptional("GOKZ_GetPaused");
+ MarkNativeAsOptional("GOKZ_GetTime");
+ MarkNativeAsOptional("GOKZ_SetTime");
+ MarkNativeAsOptional("GOKZ_InvalidateRun");
+ MarkNativeAsOptional("GOKZ_GetCheckpointCount");
+ MarkNativeAsOptional("GOKZ_SetCheckpointCount");
+ MarkNativeAsOptional("GOKZ_GetCheckpointData");
+ MarkNativeAsOptional("GOKZ_SetCheckpointData");
+ MarkNativeAsOptional("GOKZ_GetUndoTeleportData");
+ MarkNativeAsOptional("GOKZ_SetUndoTeleportData");
+ MarkNativeAsOptional("GOKZ_GetTeleportCount");
+ MarkNativeAsOptional("GOKZ_SetTeleportCount");
+ MarkNativeAsOptional("GOKZ_RegisterOption");
+ MarkNativeAsOptional("GOKZ_GetOptionProp");
+ MarkNativeAsOptional("GOKZ_SetOptionProp");
+ MarkNativeAsOptional("GOKZ_GetOption");
+ MarkNativeAsOptional("GOKZ_SetOption");
+ MarkNativeAsOptional("GOKZ_GetHitPerf");
+ MarkNativeAsOptional("GOKZ_SetHitPerf");
+ MarkNativeAsOptional("GOKZ_GetTakeoffSpeed");
+ MarkNativeAsOptional("GOKZ_SetTakeoffSpeed");
+ MarkNativeAsOptional("GOKZ_GetValidJump");
+ MarkNativeAsOptional("GOKZ_JoinTeam");
+ MarkNativeAsOptional("GOKZ_EmitSoundToClient");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/global.inc b/sourcemod/scripting/include/gokz/global.inc
new file mode 100644
index 0000000..0f23a0c
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/global.inc
@@ -0,0 +1,317 @@
+/*
+ gokz-global Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_global_included_
+#endinput
+#endif
+#define _gokz_global_included_
+
+#include <GlobalAPI>
+
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ EnforcedCVar_Cheats = 0,
+ EnforcedCVar_ClampUnsafeVelocities,
+ EnforcedCVar_DropKnifeEnable,
+ EnforcedCVar_AutoBunnyhopping,
+ EnforcedCVar_MinUpdateRate,
+ EnforcedCVar_MaxUpdateRate,
+ EnforcedCVar_MinCmdRate,
+ EnforcedCVar_MaxCmdRate,
+ EnforcedCVar_ClientCmdrateDifference,
+ EnforcedCVar_Turbophysics,
+ ENFORCEDCVAR_COUNT
+};
+
+enum
+{
+ BannedPluginCommand_Funcommands = 0,
+ BannedPluginCommand_Playercommands,
+ BANNEDPLUGINCOMMAND_COUNT
+};
+
+enum
+{
+ BannedPlugin_Funcommands = 0,
+ BannedPlugin_Playercommands,
+ BANNEDPLUGIN_COUNT
+};
+
+enum GlobalMode
+{
+ GlobalMode_Invalid = -1,
+ GlobalMode_KZTimer = 200,
+ GlobalMode_KZSimple,
+ GlobalMode_Vanilla
+}
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define GL_SOUND_NEW_RECORD "gokz/holyshit.mp3"
+#define GL_FPS_MAX_CHECK_INTERVAL 1.0
+#define GL_FPS_MAX_KICK_TIMEOUT 10.0
+#define GL_FPS_MAX_MIN_VALUE 120
+#define GL_MYAW_MAX_VALUE 0.3
+
+stock char gC_EnforcedCVars[ENFORCEDCVAR_COUNT][] =
+{
+ "sv_cheats",
+ "sv_clamp_unsafe_velocities",
+ "mp_drop_knife_enable",
+ "sv_autobunnyhopping",
+ "sv_minupdaterate",
+ "sv_maxupdaterate",
+ "sv_mincmdrate",
+ "sv_maxcmdrate",
+ "sv_client_cmdrate_difference",
+ "sv_turbophysics"
+};
+
+stock char gC_BannedPluginCommands[BANNEDPLUGINCOMMAND_COUNT][] =
+{
+ "sm_beacon",
+ "sm_slap"
+};
+
+stock char gC_BannedPlugins[BANNEDPLUGIN_COUNT][] =
+{
+ "Fun Commands",
+ "Player Commands"
+};
+
+stock float gF_EnforcedCVarValues[ENFORCEDCVAR_COUNT] =
+{
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 128.0,
+ 128.0,
+ 128.0,
+ 128.0,
+ 0.0,
+ 0.0
+};
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when a player sets a new global top time.
+ *
+ * @param client Client index.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode Player's movement mode.
+ * @param timeType Time type i.e. NUB or PRO.
+ * @param rank Ranking within the same time type.
+ * @param rankOverall Overall (NUB and PRO) ranking (0 if not ranked high enough).
+ * @param runTime Player's end time.
+ */
+forward void GOKZ_GL_OnNewTopTime(int client, int course, int mode, int timeType, int rank, int rankOverall, float runTime);
+
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Prints to chat the global records for a map, course and mode.
+ *
+ * @param client Client index.
+ * @param map Map name or "" for current map.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode GOKZ mode.
+ */
+native void GOKZ_GL_PrintRecords(int client, const char[] map = "", int course, int mode, const char[] steamid = DEFAULT_STRING);
+
+/**
+ * Opens up the global map top menu for a map, course and mode.
+ *
+ * @param client Client index.
+ * @param map Map name or "" for current map.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode GOKZ mode.
+ * @param timeType Type of time i.e. NUB or PRO.
+ */
+native void GOKZ_GL_DisplayMapTopMenu(int client, const char[] map = "", int course, int mode, int timeType);
+
+/**
+ * Get the total global points of a player.
+ *
+ * @param client Client index.
+ * @param mode GOKZ mode.
+ * @param timeType Type of time i.e. NUB or PRO.
+ */
+native void GOKZ_GL_GetPoints(int client, int mode, int timeType);
+
+/**
+ * Get the global points on the main coruse of the current map.
+ *
+ * @param client Client index.
+ * @param mode GOKZ mode.
+ * @param timeType Type of time i.e. NUB or PRO.
+ */
+native void GOKZ_GL_GetMapPoints(int client, int mode, int timeType);
+
+/**
+ * Get the total global ranking points of a player.
+ *
+ * @param client Client index.
+ * @param mode GOKZ mode.
+ * @return The points.
+ */
+native int GOKZ_GL_GetRankPoints(int client, int mode);
+
+/**
+ * Get the amount of maps a player finished.
+ *
+ * @param client Client index.
+ * @param mode GOKZ mode.
+ * @param timeType Type of time i.e. NUB or PRO.
+ */
+native void GOKZ_GL_GetFinishes(int client, int mode, int timeType);
+
+/**
+ * Fetch the points a player got from the Global API.
+ *
+ * @param client Client index. -1 to update all indices.
+ * @param mode GOKZ mode. -1 to update all modes.
+ */
+native void GOKZ_GL_UpdatePoints(int client = -1, int mode = -1);
+
+/**
+ * Gets whether the Global API key is valid or not for global status.
+ *
+ * @return True if the API key is valid, false otherwise or if there is no connection to the Global API.
+ */
+native bool GOKZ_GL_GetAPIKeyValid();
+
+/**
+ * Gets whether the running plugins are valid or not for global status.
+ *
+ * @return True if the plugins are valid, false otherwise.
+ */
+native bool GOKZ_GL_GetPluginsValid();
+
+/**
+ * Gets whether the setting enforcer is valid or not for global status.
+ *
+ * @return True if the setting enforcer is valid, false otherwise.
+ */
+native bool GOKZ_GL_GetSettingsEnforcerValid();
+
+/**
+ * Gets whether the current map is valid or not for global status.
+ *
+ * @return True if the map is valid, false otherwise or if there is no connection to the Global API.
+ */
+native bool GOKZ_GL_GetMapValid();
+
+/**
+ * Gets whether the current player is valid or not for global status.
+ *
+ * @param client Client index.
+ * @return True if the player is valid, false otherwise or if there is no connection to the Global API.
+ */
+native bool GOKZ_GL_GetPlayerValid(int client);
+
+
+
+// =====[ STOCKS ]=====
+
+/**
+ * Gets the global mode enumeration equivalent for the GOKZ mode.
+ *
+ * @param mode GOKZ mode.
+ * @return Global mode enumeration equivalent.
+ */
+stock GlobalMode GOKZ_GL_GetGlobalMode(int mode)
+{
+ switch (mode)
+ {
+ case Mode_Vanilla:return GlobalMode_Vanilla;
+ case Mode_SimpleKZ:return GlobalMode_KZSimple;
+ case Mode_KZTimer:return GlobalMode_KZTimer;
+ }
+ return GlobalMode_Invalid;
+}
+
+/**
+ * Gets the global mode enumeration equivalent for the GOKZ mode.
+ *
+ * @param mode GOKZ mode.
+ * @return Global mode enumeration equivalent.
+ */
+stock int GOKZ_GL_FromGlobalMode(GlobalMode mode)
+{
+ switch (mode)
+ {
+ case GlobalMode_Vanilla:return Mode_Vanilla;
+ case GlobalMode_KZSimple:return Mode_SimpleKZ;
+ case GlobalMode_KZTimer:return Mode_KZTimer;
+ }
+ return -1;
+}
+
+/**
+ * Gets the string representation of a mode.
+ *
+ * @param mode GOKZ mode.
+ * @param mode_str String version of the mode.
+ * @param size Max length of mode_str.
+ * @return True if the conversion was successful.
+ */
+stock bool GOKZ_GL_GetModeString(int mode, char[] mode_str, int size)
+{
+ switch (mode)
+ {
+ case Mode_Vanilla:strcopy(mode_str, size, "kz_vanilla");
+ case Mode_SimpleKZ:strcopy(mode_str, size, "kz_simple");
+ case Mode_KZTimer:strcopy(mode_str, size, "kz_timer");
+ default:return false;
+ }
+ return true;
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_global =
+{
+ name = "gokz-global",
+ file = "gokz-global.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_global_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_GL_PrintRecords");
+ MarkNativeAsOptional("GOKZ_GL_DisplayMapTopMenu");
+ MarkNativeAsOptional("GOKZ_GL_UpdatePoints");
+ MarkNativeAsOptional("GOKZ_GL_GetAPIKeyValid");
+ MarkNativeAsOptional("GOKZ_GL_GetPluginsValid");
+ MarkNativeAsOptional("GOKZ_GL_GetSettingsEnforcerValid");
+ MarkNativeAsOptional("GOKZ_GL_GetMapValid");
+ MarkNativeAsOptional("GOKZ_GL_GetPlayerValid");
+ MarkNativeAsOptional("GOKZ_GL_GetPoints");
+ MarkNativeAsOptional("GOKZ_GL_GetMapPoints");
+ MarkNativeAsOptional("GOKZ_GL_GetRankPoints");
+ MarkNativeAsOptional("GOKZ_GL_GetFinishes");
+ MarkNativeAsOptional("GOKZ_GL_UpdatePoints");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/hud.inc b/sourcemod/scripting/include/gokz/hud.inc
new file mode 100644
index 0000000..5d658ff
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/hud.inc
@@ -0,0 +1,468 @@
+/*
+ gokz-hud Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_hud_included_
+#endinput
+#endif
+#define _gokz_hud_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum HUDOption:
+{
+ HUDOPTION_INVALID = -1,
+ HUDOption_TPMenu,
+ HUDOption_InfoPanel,
+ HUDOption_ShowKeys,
+ HUDOption_TimerText,
+ HUDOption_TimerStyle,
+ HUDOption_TimerType,
+ HUDOption_SpeedText,
+ HUDOption_ShowWeapon,
+ HUDOption_ShowControls,
+ HUDOption_DeadstrafeColor,
+ HUDOption_ShowSpectators,
+ HUDOption_SpecListPosition,
+ HUDOption_UpdateRate,
+ HUDOption_DynamicMenu,
+ HUDOPTION_COUNT
+};
+
+enum
+{
+ TPMenu_Disabled = 0,
+ TPMenu_Simple,
+ TPMenu_Advanced,
+ TPMENU_COUNT
+};
+
+enum
+{
+ InfoPanel_Disabled = 0,
+ InfoPanel_Enabled,
+ INFOPANEL_COUNT
+};
+
+enum
+{
+ ShowKeys_Spectating = 0,
+ ShowKeys_Always,
+ ShowKeys_Disabled,
+ SHOWKEYS_COUNT
+};
+
+enum
+{
+ TimerText_Disabled = 0,
+ TimerText_InfoPanel,
+ TimerText_TPMenu,
+ TimerText_Bottom,
+ TimerText_Top,
+ TIMERTEXT_COUNT
+};
+
+enum
+{
+ TimerStyle_Standard = 0,
+ TimerStyle_Precise,
+ TIMERSTYLE_COUNT
+};
+
+enum
+{
+ TimerType_Disabled = 0,
+ TimerType_Enabled,
+ TIMERTYPE_COUNT
+};
+
+enum
+{
+ SpeedText_Disabled = 0,
+ SpeedText_InfoPanel,
+ SpeedText_Bottom,
+ SPEEDTEXT_COUNT
+};
+
+enum
+{
+ ShowWeapon_Disabled = 0,
+ ShowWeapon_Enabled,
+ SHOWWEAPON_COUNT
+};
+
+enum
+{
+ ReplayControls_Disabled = 0,
+ ReplayControls_Enabled,
+ REPLAYCONTROLS_COUNT
+};
+
+enum
+{
+ DeadstrafeColor_Disabled = 0,
+ DeadstrafeColor_Enabled,
+ DEADSTRAFECOLOR_COUNT
+};
+
+enum
+{
+ ShowSpecs_Disabled = 0,
+ ShowSpecs_Number,
+ ShowSpecs_Full,
+ SHOWSPECS_COUNT
+};
+
+enum
+{
+ SpecListPosition_TPMenu = 0,
+ SpecListPosition_InfoPanel,
+ SPECLISTPOSITION_COUNT
+}
+
+enum
+{
+ UpdateRate_Slow = 0,
+ UpdateRate_Fast,
+ UPDATERATE_COUNT,
+};
+
+enum
+{
+ DynamicMenu_Legacy = 0,
+ DynamicMenu_Disabled,
+ DynamicMenu_Enabled,
+ DYNAMICMENU_COUNT
+};
+
+// =====[ STRUCTS ]======
+
+enum struct HUDInfo
+{
+ bool TimerRunning;
+ int TimeType;
+ float Time;
+ bool Paused;
+ bool OnGround;
+ bool OnLadder;
+ bool Noclipping;
+ bool Ducking;
+ bool HitBhop;
+ bool IsTakeoff;
+ float Speed;
+ int ID;
+ bool Jumped;
+ bool HitPerf;
+ bool HitJB;
+ float TakeoffSpeed;
+ int Buttons;
+ int CurrentTeleport;
+}
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define HUD_OPTION_CATEGORY "HUD"
+#define HUD_MAX_BHOP_GROUND_TICKS 5
+#define HUD_MAX_HINT_SIZE 227
+
+stock char gC_HUDOptionNames[HUDOPTION_COUNT][] =
+{
+ "GOKZ HUD - Teleport Menu",
+ "GOKZ HUD - Centre Panel",
+ "GOKZ HUD - Show Keys",
+ "GOKZ HUD - Timer Text",
+ "GOKZ HUD - Timer Style",
+ "GOKZ HUD - Show Time Type",
+ "GOKZ HUD - Speed Text",
+ "GOKZ HUD - Show Weapon",
+ "GOKZ HUD - Show Controls",
+ "GOKZ HUD - Dead Strafe",
+ "GOKZ HUD - Show Spectators",
+ "GOKZ HUD - Spec List Pos",
+ "GOKZ HUD - Update Rate",
+ "GOKZ HUD - Dynamic Menu"
+};
+
+stock char gC_HUDOptionDescriptions[HUDOPTION_COUNT][] =
+{
+ "Teleport Menu - 0 = Disabled, 1 = Simple, 2 = Advanced",
+ "Centre Information Panel - 0 = Disabled, 1 = Enabled",
+ "Key Press Display - 0 = Spectating, 1 = Always, 2 = Disabled",
+ "Timer Display - 0 = Disabled, 1 = Centre Panel, 2 = Teleport Menu, 3 = Bottom, 4 = Top",
+ "Timer Style - 0 = Standard, 1 = Precise",
+ "Timer Type - 0 = Disabled, 1 = Enabled",
+ "Speed Display - 0 = Disabled, 1 = Centre Panel, 2 = Bottom",
+ "Weapon Viewmodel - 0 = Disabled, 1 = Enabled",
+ "Replay Controls Display - 0 = Disbled, 1 = Enabled",
+ "Dead Strafe Indicator - 0 = Disabled, 1 = Enabled",
+ "Show Spectators - 0 = Disabled, 1 = Number Only, 2 = Number and Names",
+ "Spectator List Position - 0 = Teleport Menu, 2 = Center Panel",
+ "HUD Update Rate - 0 = Slow, 1 = Fast",
+ "Dynamic Menu - 0 = Legacy, 1 = Disabled, 2 = Enabled"
+};
+
+stock char gC_HUDOptionPhrases[HUDOPTION_COUNT][] =
+{
+ "Options Menu - Teleport Menu",
+ "Options Menu - Info Panel",
+ "Options Menu - Show Keys",
+ "Options Menu - Timer Text",
+ "Options Menu - Timer Style",
+ "Options Menu - Timer Type",
+ "Options Menu - Speed Text",
+ "Options Menu - Show Weapon",
+ "Options Menu - Show Controls",
+ "Options Menu - Dead Strafe Indicator",
+ "Options Menu - Show Spectators",
+ "Options Menu - Spectator List Position",
+ "Options Menu - Update Rate",
+ "Options Menu - Dynamic Menu"
+};
+
+stock int gI_HUDOptionCounts[HUDOPTION_COUNT] =
+{
+ TPMENU_COUNT,
+ INFOPANEL_COUNT,
+ SHOWKEYS_COUNT,
+ TIMERTEXT_COUNT,
+ TIMERSTYLE_COUNT,
+ TIMERTYPE_COUNT,
+ SPEEDTEXT_COUNT,
+ SHOWWEAPON_COUNT,
+ REPLAYCONTROLS_COUNT,
+ DEADSTRAFECOLOR_COUNT,
+ SHOWSPECS_COUNT,
+ SPECLISTPOSITION_COUNT,
+ UPDATERATE_COUNT,
+ DYNAMICMENU_COUNT
+};
+
+stock int gI_HUDOptionDefaults[HUDOPTION_COUNT] =
+{
+ TPMenu_Advanced,
+ InfoPanel_Enabled,
+ ShowKeys_Spectating,
+ TimerText_InfoPanel,
+ TimerStyle_Standard,
+ TimerType_Enabled,
+ SpeedText_InfoPanel,
+ ShowWeapon_Enabled,
+ ReplayControls_Enabled,
+ DeadstrafeColor_Disabled,
+ ShowSpecs_Disabled,
+ SpecListPosition_TPMenu,
+ UpdateRate_Slow,
+ DynamicMenu_Legacy
+};
+
+stock char gC_TPMenuPhrases[TPMENU_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Simple",
+ "Options Menu - Advanced"
+};
+
+stock char gC_ShowKeysPhrases[SHOWKEYS_COUNT][] =
+{
+ "Options Menu - Spectating",
+ "Options Menu - Always",
+ "Options Menu - Disabled"
+};
+
+stock char gC_TimerTextPhrases[TIMERTEXT_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Info Panel",
+ "Options Menu - Teleport Menu",
+ "Options Menu - Bottom",
+ "Options Menu - Top"
+};
+
+stock char gC_TimerTypePhrases[TIMERTYPE_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Enabled"
+};
+
+stock char gC_SpeedTextPhrases[SPEEDTEXT_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Info Panel",
+ "Options Menu - Bottom"
+};
+
+stock char gC_ShowControlsPhrases[REPLAYCONTROLS_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Enabled"
+};
+
+stock char gC_DeadstrafeColorPhrases[DEADSTRAFECOLOR_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Enabled"
+};
+
+stock char gC_ShowSpecsPhrases[SHOWSPECS_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Number",
+ "Options Menu - Number and Names"
+};
+
+stock char gC_SpecListPositionPhrases[SPECLISTPOSITION_COUNT][] =
+{
+ "Options Menu - Teleport Menu",
+ "Options Menu - Info Panel"
+};
+
+stock char gC_HUDUpdateRatePhrases[UPDATERATE_COUNT][]=
+{
+ "Options Menu - Slow",
+ "Options Menu - Fast"
+};
+
+stock char gC_DynamicMenuPhrases[DYNAMICMENU_COUNT][]=
+{
+ "Options Menu - Legacy",
+ "Options Menu - Disabled",
+ "Options Menu - Enabled"
+};
+
+// =====[ NATIVES ]=====
+
+/**
+ * Returns whether the GOKZ HUD menu is showing for a client.
+ *
+ * @param client Client index.
+ * @return Whether the GOKZ HUD menu is showing.
+ */
+native bool GOKZ_HUD_GetMenuShowing(int client);
+
+/**
+ * Sets whether the GOKZ HUD menu would be showing for a client.
+ *
+ * @param client Client index.
+ * @param value Whether the GOKZ HUD menu would be showing for a client.
+ */
+native void GOKZ_HUD_SetMenuShowing(int client, bool value);
+
+/**
+ * Gets the spectator text for the menu. Used by GOKZ-replays.
+ *
+ * @param client Client index.
+ * @param value Whether the GOKZ HUD menu would be showing for a client.
+ */
+native void GOKZ_HUD_GetMenuSpectatorText(int client, any[] info, char[] buffer, int size);
+
+/**
+ * Forces the client's TP menu to update.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_HUD_ForceUpdateTPMenu(int client);
+
+// =====[ STOCKS ]=====
+
+/**
+ * Returns whether an option is a gokz-hud option.
+ *
+ * @param option Option name.
+ * @param optionEnum Variable to store enumerated gokz-hud option (if it is one).
+ * @return Whether option is a gokz-hud option.
+ */
+stock bool GOKZ_HUD_IsHUDOption(const char[] option, HUDOption &optionEnum = HUDOPTION_INVALID)
+{
+ for (HUDOption i; i < HUDOPTION_COUNT; i++)
+ {
+ if (StrEqual(option, gC_HUDOptionNames[i]))
+ {
+ optionEnum = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Gets the current value of a player's gokz-hud option.
+ *
+ * @param client Client index.
+ * @param option gokz-hud option.
+ * @return Current value of option.
+ */
+stock any GOKZ_HUD_GetOption(int client, HUDOption option)
+{
+ return GOKZ_GetOption(client, gC_HUDOptionNames[option]);
+}
+
+/**
+ * Sets a player's gokz-hud option's value.
+ *
+ * @param client Client index.
+ * @param option gokz-hud option.
+ * @param value New option value.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_HUD_SetOption(int client, HUDOption option, any value)
+{
+ return GOKZ_SetOption(client, gC_HUDOptionNames[option], value);
+}
+
+/**
+ * Increment an integer-type gokz-hud option's value.
+ * Loops back to '0' if max value is exceeded.
+ *
+ * @param client Client index.
+ * @param option gokz-hud option.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_HUD_CycleOption(int client, HUDOption option)
+{
+ return GOKZ_CycleOption(client, gC_HUDOptionNames[option]);
+}
+
+/**
+ * Represents a time float as a string e.g. 01:23.45
+ * and according to the client's HUD options.
+ *
+ * @param client Client index.
+ * @param time Time in seconds.
+ * @return String representation of time.
+ */
+stock char[] GOKZ_HUD_FormatTime(int client, float time)
+{
+ bool precise = GOKZ_HUD_GetOption(client, HUDOption_TimerStyle) == TimerStyle_Precise;
+ return GOKZ_FormatTime(time, precise);
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_hud =
+{
+ name = "gokz-hud",
+ file = "gokz-hud.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_hud_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_HUD_GetMenuShowing");
+ MarkNativeAsOptional("GOKZ_HUD_SetMenuShowing");
+ MarkNativeAsOptional("GOKZ_HUD_GetMenuSpectatorText");
+ MarkNativeAsOptional("GOKZ_HUD_ForceUpdateTPMenu");
+}
+#endif
diff --git a/sourcemod/scripting/include/gokz/jumpbeam.inc b/sourcemod/scripting/include/gokz/jumpbeam.inc
new file mode 100644
index 0000000..1b92479
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/jumpbeam.inc
@@ -0,0 +1,148 @@
+/*
+ gokz-jumpbeam Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_jumpbeam_included_
+#endinput
+#endif
+#define _gokz_jumpbeam_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum JBOption:
+{
+ JBOPTION_INVALID = -1,
+ JBOption_Type,
+ JBOPTION_COUNT
+};
+
+enum
+{
+ JBType_Disabled = 0,
+ JBType_Feet,
+ JBType_Head,
+ JBType_FeetAndHead,
+ JBType_Ground,
+ JBTYPE_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define JB_BEAM_LIFETIME 4.0
+
+stock char gC_JBOptionNames[JBOPTION_COUNT][] =
+{
+ "GOKZ JB - Jump Beam Type"
+};
+
+stock char gC_JBOptionDescriptions[JBOPTION_COUNT][] =
+{
+ "Jump Beam Type - 0 = Disabled, 1 = Feet, 2 = Head, 3 = Feet & Head, 4 = Ground"
+};
+
+stock int gI_JBOptionDefaultValues[JBOPTION_COUNT] =
+{
+ JBType_Disabled
+};
+
+stock int gI_JBOptionCounts[JBOPTION_COUNT] =
+{
+ JBTYPE_COUNT
+};
+
+stock char gC_JBOptionPhrases[JBOPTION_COUNT][] =
+{
+ "Options Menu - Jump Beam"
+};
+
+stock char gC_JBTypePhrases[JBTYPE_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Feet",
+ "Options Menu - Head",
+ "Options Menu - Feet and Head",
+ "Options Menu - Ground"
+};
+
+
+
+// =====[ STOCKS ]=====
+
+/**
+ * Returns whether an option is a gokz-jumpbeam option.
+ *
+ * @param option Option name.
+ * @param optionEnum Variable to store enumerated gokz-jumpbeam option (if it is one).
+ * @return Whether option is a gokz-jumpbeam option.
+ */
+stock bool GOKZ_JB_IsJBOption(const char[] option, JBOption &optionEnum = JBOPTION_INVALID)
+{
+ for (JBOption i; i < JBOPTION_COUNT; i++)
+ {
+ if (StrEqual(option, gC_JBOptionNames[i]))
+ {
+ optionEnum = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Gets the current value of a player's gokz-jumpbeam option.
+ *
+ * @param client Client index.
+ * @param option gokz-jumpbeam option.
+ * @return Current value of option.
+ */
+stock any GOKZ_JB_GetOption(int client, JBOption option)
+{
+ return GOKZ_GetOption(client, gC_JBOptionNames[option]);
+}
+
+/**
+ * Sets a player's gokz-jumpbeam option's value.
+ *
+ * @param client Client index.
+ * @param option gokz-jumpbeam option.
+ * @param value New option value.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_JB_SetOption(int client, JBOption option, any value)
+{
+ return GOKZ_SetOption(client, gC_JBOptionNames[option], value);
+}
+
+/**
+ * Increment an integer-type gokz-jumpbeam option's value.
+ * Loops back to '0' if max value is exceeded.
+ *
+ * @param client Client index.
+ * @param option gokz-jumpbeam option.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_JB_CycleOption(int client, JBOption option)
+{
+ return GOKZ_CycleOption(client, gC_JBOptionNames[option]);
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_jumpbeam =
+{
+ name = "gokz-jumpbeam",
+ file = "gokz-jumpbeam.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+}; \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/jumpstats.inc b/sourcemod/scripting/include/gokz/jumpstats.inc
new file mode 100644
index 0000000..452ae28
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/jumpstats.inc
@@ -0,0 +1,442 @@
+/*
+ gokz-jumpstats Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_jumpstats_included_
+#endinput
+#endif
+#define _gokz_jumpstats_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ JumpType_FullInvalid = -1,
+ JumpType_LongJump,
+ JumpType_Bhop,
+ JumpType_MultiBhop,
+ JumpType_WeirdJump,
+ JumpType_LadderJump,
+ JumpType_Ladderhop,
+ JumpType_Jumpbug,
+ JumpType_LowpreBhop,
+ JumpType_LowpreWeirdJump,
+ JumpType_Fall,
+ JumpType_Other,
+ JumpType_Invalid,
+ JUMPTYPE_COUNT
+};
+
+enum
+{
+ StrafeDirection_None,
+ StrafeDirection_Left,
+ StrafeDirection_Right
+};
+
+enum
+{
+ DistanceTier_None = 0,
+ DistanceTier_Meh,
+ DistanceTier_Impressive,
+ DistanceTier_Perfect,
+ DistanceTier_Godlike,
+ DistanceTier_Ownage,
+ DistanceTier_Wrecker,
+ DISTANCETIER_COUNT
+};
+
+enum JSOption:
+{
+ JSOPTION_INVALID = -1,
+ JSOption_JumpstatsMaster,
+ JSOption_MinChatTier,
+ JSOption_MinConsoleTier,
+ JSOption_MinSoundTier,
+ JSOption_FailstatsConsole,
+ JSOption_FailstatsChat,
+ JSOption_JumpstatsAlways,
+ JSOption_ExtendedChatReport,
+ JSOption_MinChatBroadcastTier,
+ JSOption_MinSoundBroadcastTier,
+ JSOPTION_COUNT
+};
+
+enum
+{
+ JSToggleOption_Disabled = 0,
+ JSToggleOption_Enabled,
+ JSTOGGLEOPTION_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define JS_CFG_TIERS "cfg/sourcemod/gokz/gokz-jumpstats-tiers.cfg"
+#define JS_CFG_SOUNDS "cfg/sourcemod/gokz/gokz-jumpstats-sounds.cfg"
+#define JS_CFG_BROADCAST "cfg/sourcemod/gokz/gokz-jumpstats-broadcast.cfg"
+#define JS_OPTION_CATEGORY "Jumpstats"
+#define JS_MAX_LADDERJUMP_OFFSET 2.0
+#define JS_MAX_BHOP_GROUND_TICKS 5
+#define JS_MAX_DUCKBUG_RESET_TICKS 6
+#define JS_MAX_WEIRDJUMP_FALL_OFFSET 64.0
+#define JS_TOUCH_GRACE_TICKS 3
+#define JS_MAX_TRACKED_STRAFES 48
+#define JS_MIN_BLOCK_DISTANCE 186
+#define JS_MIN_LAJ_BLOCK_DISTANCE 50
+#define JS_MAX_LAJ_FAILSTAT_DISTANCE 250
+#define JS_TOP_RECORD_COUNT 20
+#define JS_MAX_JUMP_DISTANCE 500
+#define JS_FAILSTATS_MAX_TRACKED_TICKS 128
+#define JS_MIN_TELEPORT_DELAY 5
+#define JS_SPEED_MODIFICATION_TOLERANCE 0.1
+#define JS_OFFSET_EPSILON 0.03125
+
+stock char gC_JumpTypes[JUMPTYPE_COUNT][] =
+{
+ "Long Jump",
+ "Bunnyhop",
+ "Multi Bunnyhop",
+ "Weird Jump",
+ "Ladder Jump",
+ "Ladderhop",
+ "Jumpbug",
+ "Lowpre Bunnyhop",
+ "Lowpre Weird Jump",
+ "Fall",
+ "Unknown Jump",
+ "Invalid Jump"
+};
+
+stock char gC_JumpTypesShort[JUMPTYPE_COUNT][] =
+{
+ "LJ",
+ "BH",
+ "MBH",
+ "WJ",
+ "LAJ",
+ "LAH",
+ "JB",
+ "LBH",
+ "LWJ",
+ "FL",
+ "UNK",
+ "INV"
+};
+
+stock char gC_JumpTypeKeys[JUMPTYPE_COUNT][] =
+{
+ "longjump",
+ "bhop",
+ "multibhop",
+ "weirdjump",
+ "ladderjump",
+ "ladderhop",
+ "jumpbug",
+ "lowprebhop",
+ "lowpreweirdjump",
+ "fall",
+ "unknown",
+ "invalid"
+};
+
+stock char gC_DistanceTiers[DISTANCETIER_COUNT][] =
+{
+ "None",
+ "Meh",
+ "Impressive",
+ "Perfect",
+ "Godlike",
+ "Ownage",
+ "Wrecker"
+};
+
+stock char gC_DistanceTierKeys[DISTANCETIER_COUNT][] =
+{
+ "none",
+ "meh",
+ "impressive",
+ "perfect",
+ "godlike",
+ "ownage",
+ "wrecker"
+};
+
+stock char gC_DistanceTierChatColours[DISTANCETIER_COUNT][] =
+{
+ "{grey}",
+ "{grey}",
+ "{blue}",
+ "{green}",
+ "{darkred}",
+ "{gold}",
+ "{orchid}"
+};
+
+stock char gC_JSOptionNames[JSOPTION_COUNT][] =
+{
+ "GOKZ JS - Master Switch",
+ "GOKZ JS - Chat Report",
+ "GOKZ JS - Console Report",
+ "GOKZ JS - Sounds",
+ "GOKZ JS - Failstats Console",
+ "GOKZ JS - Failstats Chat",
+ "GOKZ JS - Jumpstats Always",
+ "GOKZ JS - Ext Chat Report",
+ "GOKZ JS - Min Chat Broadcast",
+ "GOKZ JS - Min Sound Broadcast"
+};
+
+stock char gC_JSOptionDescriptions[JSOPTION_COUNT][] =
+{
+ "Master Switch for All Jumpstats Functionality - 0 = Disabled, 1 = Enabled",
+ "Minimum Tier for Jumpstats Chat Report - 0 = Disabled, 1 = Meh+, 2 = Impressive+, 3 = Perfect+, 4 = Godlike+, 5 = Ownage+, 6 = Wrecker",
+ "Minimum Tier for Jumpstats Console report - 0 = Disabled, 1 = Meh+, 2 = Impressive+, 3 = Perfect+, 4 = Godlike+, 5 = Ownage+, 6 = Wrecker",
+ "Minimum Tier for Jumpstats Sounds - 0 = Disabled, 2 = Impressive+, 3 = Perfect+, 4 = Godlike+, 5 = Ownage+, 6 = Wrecker",
+ "Print Failstats To Console - 0 = Disabled, 1 = Enabled",
+ "Print Failstats To Chat - 0 = Disabled, 1 = Enabled",
+ "Always show jumpstats, even for invalid jumps - 0 = Disabled, 1 = Enabled",
+ "Extended Chat Report - 0 = Disabled, 1 = Enabled",
+ "Minimum Jump Tier for Jumpstat Chat Broadcast - 0 = Disabled, 1 = Meh+, 2 = Impressive+, 3 = Perfect+, 4 = Godlike+, 5 = Ownage+, 6 = Wrecker",
+ "Minimum Jump Tier for Jumpstat Sound Broadcast - 0 = Disabled, 1 = Meh+, 2 = Impressive+, 3 = Perfect+, 4 = Godlike+, 5 = Ownage+, 6 = Wrecker"
+};
+
+stock char gI_JSOptionPhrases[JSOPTION_COUNT][] =
+{
+ "Options Menu - Jumpstats Master Switch",
+ "Options Menu - Jumpstats Chat Report",
+ "Options Menu - Jumpstats Console Report",
+ "Options Menu - Jumpstats Sounds",
+ "Options Menu - Failstats Console Report",
+ "Options Menu - Failstats Chat Report",
+ "Options Menu - Jumpstats Always",
+ "Options Menu - Extended Jump Chat Report",
+ "Options Menu - Minimal Jump Chat Broadcast Tier",
+ "Options Menu - Minimal Jump Sound Broadcast Tier"
+};
+
+stock int gI_JSOptionDefaults[JSOPTION_COUNT] =
+{
+ JSToggleOption_Enabled,
+ DistanceTier_Meh,
+ DistanceTier_Meh,
+ DistanceTier_Impressive,
+ JSToggleOption_Enabled,
+ JSToggleOption_Disabled,
+ JSToggleOption_Disabled,
+ JSToggleOption_Disabled,
+ DistanceTier_Ownage,
+ DistanceTier_None
+};
+
+stock int gI_JSOptionCounts[JSOPTION_COUNT] =
+{
+ JSTOGGLEOPTION_COUNT,
+ DISTANCETIER_COUNT,
+ DISTANCETIER_COUNT,
+ DISTANCETIER_COUNT,
+ JSTOGGLEOPTION_COUNT,
+ JSTOGGLEOPTION_COUNT,
+ JSTOGGLEOPTION_COUNT,
+ JSTOGGLEOPTION_COUNT,
+ DISTANCETIER_COUNT,
+ DISTANCETIER_COUNT
+};
+
+
+
+// =====[ STRUCTS ]=====
+
+enum struct Jump
+{
+ int jumper;
+ int block;
+ int crouchRelease;
+ int crouchTicks;
+ int deadair;
+ int duration;
+ int originalType;
+ int overlap;
+ int releaseW;
+ int strafes;
+ int type;
+ float deviation;
+ float distance;
+ float edge;
+ float height;
+ float maxSpeed;
+ float offset;
+ float preSpeed;
+ float sync;
+ float width;
+
+ // For the 'always' stats
+ float miss;
+
+ // We can't make a separate enum struct for that cause it won't let us
+ // index an array of enum structs.
+ int strafes_gainTicks[JS_MAX_TRACKED_STRAFES];
+ int strafes_deadair[JS_MAX_TRACKED_STRAFES];
+ int strafes_overlap[JS_MAX_TRACKED_STRAFES];
+ int strafes_ticks[JS_MAX_TRACKED_STRAFES];
+ float strafes_gain[JS_MAX_TRACKED_STRAFES];
+ float strafes_loss[JS_MAX_TRACKED_STRAFES];
+ float strafes_sync[JS_MAX_TRACKED_STRAFES];
+ float strafes_width[JS_MAX_TRACKED_STRAFES];
+}
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when a player begins their jump.
+ *
+ * @param client Client index.
+ * @param jumpType Type of jump.
+ */
+forward void GOKZ_JS_OnTakeoff(int client, int jumpType);
+
+/**
+ * Called when a player lands their jump.
+ *
+ * @param jump The jumpstats.
+ */
+forward void GOKZ_JS_OnLanding(Jump jump);
+
+/**
+ * Called when player's current jump has been declared an invalid jumpstat.
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_JS_OnJumpInvalidated(int client);
+
+/**
+ * Called when a player fails a blockjump.
+ *
+ * @param jump The jumpstats.
+ */
+forward void GOKZ_JS_OnFailstat(Jump jump);
+
+/**
+ * Called when a player lands a jump and has always-on jumpstats enabled.
+ *
+ * @param jump The jumpstats.
+ */
+forward void GOKZ_JS_OnJumpstatAlways(Jump jump);
+
+/**
+ * Called when a player fails a jump and has always-on failstats enabled.
+ *
+ * @param jump The failstats.
+ */
+forward void GOKZ_JS_OnFailstatAlways(Jump jump);
+
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets the default jumpstats option value as set by a config file.
+ *
+ * @param option GOKZ Jumpstats option.
+ * @return Default option value.
+ */
+native int GOKZ_JS_GetDefaultOption(JSOption option);
+
+/**
+ * Declare a player's current jump an invalid jumpstat.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_JS_InvalidateJump(int client);
+
+
+
+// =====[ STOCKS ]=====
+
+/**
+ * Returns whether an option is a gokz-jumpstats option.
+ *
+ * @param option Option name.
+ * @param optionEnum Variable to store enumerated gokz-jumpstats option (if it is one).
+ * @return Whether option is a gokz-jumpstats option.
+ */
+stock bool GOKZ_JS_IsJSOption(const char[] option, JSOption &optionEnum = JSOPTION_INVALID)
+{
+ for (JSOption i; i < JSOPTION_COUNT; i++)
+ {
+ if (StrEqual(option, gC_JSOptionNames[i]))
+ {
+ optionEnum = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Gets the current value of a player's gokz-jumpstats option.
+ *
+ * @param client Client index.
+ * @param option gokz-jumpstats option.
+ * @return Current value of option.
+ */
+stock any GOKZ_JS_GetOption(int client, JSOption option)
+{
+ return GOKZ_GetOption(client, gC_JSOptionNames[option]);
+}
+
+/**
+ * Sets a player's gokz-jumpstats option's value.
+ *
+ * @param client Client index.
+ * @param option gokz-jumpstats option.
+ * @param value New option value.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_JS_SetOption(int client, JSOption option, any value)
+{
+ return GOKZ_SetOption(client, gC_JSOptionNames[option], value);
+}
+
+/**
+ * Increment an integer-type gokz-jumpstats option's value.
+ * Loops back to '0' if max value is exceeded.
+ *
+ * @param client Client index.
+ * @param option gokz-jumpstats option.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_JS_CycleOption(int client, JSOption option)
+{
+ return GOKZ_CycleOption(client, gC_JSOptionNames[option]);
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_jumpstats =
+{
+ name = "gokz-jumpstats",
+ file = "gokz-jumpstats.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_jumpstats_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_JS_GetDefaultOption");
+ MarkNativeAsOptional("GOKZ_JS_InvalidateJump");
+}
+#endif
diff --git a/sourcemod/scripting/include/gokz/kzplayer.inc b/sourcemod/scripting/include/gokz/kzplayer.inc
new file mode 100644
index 0000000..8176d39
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/kzplayer.inc
@@ -0,0 +1,584 @@
+/*
+ GOKZ KZPlayer Methodmap Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_kzplayer_included_
+#endinput
+#endif
+#define _gokz_kzplayer_included_
+
+#include <movementapi>
+
+#include <gokz>
+
+
+
+methodmap KZPlayer < MovementAPIPlayer {
+
+ public KZPlayer(int client) {
+ return view_as<KZPlayer>(MovementAPIPlayer(client));
+ }
+
+
+
+ // =====[ GENERAL ]=====
+
+ property bool Valid {
+ public get() {
+ return IsValidClient(this.ID);
+ }
+ }
+
+ property bool InGame {
+ public get() {
+ return IsClientInGame(this.ID);
+ }
+ }
+
+ property bool Authorized {
+ public get() {
+ return IsClientAuthorized(this.ID);
+ }
+ }
+
+ property bool Fake {
+ public get() {
+ return IsFakeClient(this.ID);
+ }
+ }
+
+ property bool Alive {
+ public get() {
+ return IsPlayerAlive(this.ID);
+ }
+ }
+
+ property ObsMode ObserverMode {
+ public get() {
+ return GetObserverMode(this.ID);
+ }
+ }
+
+ property int ObserverTarget {
+ public get() {
+ return GetObserverTarget(this.ID);
+ }
+ }
+
+
+
+ // =====[ CORE ]=====
+ #if defined _gokz_core_included_
+
+ public void StartTimer(int course) {
+ GOKZ_StartTimer(this.ID, course);
+ }
+
+ public void EndTimer(int course) {
+ GOKZ_EndTimer(this.ID, course);
+ }
+
+ public bool StopTimer() {
+ return GOKZ_StopTimer(this.ID);
+ }
+
+ public void TeleportToStart() {
+ GOKZ_TeleportToStart(this.ID);
+ }
+
+ public void TeleportToSearchStart(int course) {
+ GOKZ_TeleportToSearchStart(this.ID, course);
+ }
+
+ public void TeleportToEnd(int course) {
+ GOKZ_TeleportToEnd(this.ID, course);
+ }
+
+ property StartPositionType StartPositionType {
+ public get() {
+ return GOKZ_GetStartPositionType(this.ID);
+ }
+ }
+
+ public void MakeCheckpoint() {
+ GOKZ_MakeCheckpoint(this.ID);
+ }
+
+ property bool CanMakeCheckpoint {
+ public get() {
+ return GOKZ_GetCanMakeCheckpoint(this.ID);
+ }
+ }
+
+ public void TeleportToCheckpoint() {
+ GOKZ_TeleportToCheckpoint(this.ID);
+ }
+
+ property bool CanTeleportToCheckpoint {
+ public get() {
+ return GOKZ_GetCanTeleportToCheckpoint(this.ID);
+ }
+ }
+
+ public void PrevCheckpoint() {
+ GOKZ_PrevCheckpoint(this.ID);
+ }
+
+ property bool CanPrevCheckpoint {
+ public get() {
+ return GOKZ_GetCanPrevCheckpoint(this.ID);
+ }
+ }
+
+ public void NextCheckpoint() {
+ GOKZ_NextCheckpoint(this.ID);
+ }
+
+ property bool CanNextCheckpoint {
+ public get() {
+ return GOKZ_GetCanNextCheckpoint(this.ID);
+ }
+ }
+
+ public void UndoTeleport() {
+ GOKZ_UndoTeleport(this.ID);
+ }
+
+ property bool CanUndoTeleport {
+ public get() {
+ return GOKZ_GetCanUndoTeleport(this.ID);
+ }
+ }
+
+ public void Pause() {
+ GOKZ_Pause(this.ID);
+ }
+
+ property bool CanPause {
+ public get() {
+ return GOKZ_GetCanPause(this.ID);
+ }
+ }
+
+ public void Resume() {
+ GOKZ_Resume(this.ID);
+ }
+
+ property bool CanResume {
+ public get() {
+ return GOKZ_GetCanResume(this.ID);
+ }
+ }
+
+ public void TogglePause() {
+ GOKZ_TogglePause(this.ID);
+ }
+
+ public void PlayErrorSound() {
+ GOKZ_PlayErrorSound(this.ID);
+ }
+
+ property bool TimerRunning {
+ public get() {
+ return GOKZ_GetTimerRunning(this.ID);
+ }
+ }
+
+ property int Course {
+ public get() {
+ return GOKZ_GetCourse(this.ID);
+ }
+ }
+
+ property bool Paused {
+ public get() {
+ return GOKZ_GetPaused(this.ID);
+ }
+ public set(bool pause) {
+ if (pause) {
+ this.Pause();
+ }
+ else {
+ this.Resume();
+ }
+ }
+ }
+
+ property bool CanTeleportToStart {
+ public get() {
+ return GOKZ_GetCanTeleportToStartOrEnd(this.ID);
+ }
+ }
+
+ property float Time {
+ public get() {
+ return GOKZ_GetTime(this.ID);
+ }
+ public set(float value) {
+ GOKZ_SetTime(this.ID, value);
+ }
+ }
+
+ property int CheckpointCount {
+ public get() {
+ return GOKZ_GetCheckpointCount(this.ID);
+ }
+ public set(int cpCount) {
+ GOKZ_SetCheckpointCount(this.ID, cpCount);
+ }
+ }
+
+ property ArrayList CheckpointData {
+ public get() {
+ return GOKZ_GetCheckpointData(this.ID);
+ }
+ public set(ArrayList checkpoints) {
+ GOKZ_SetCheckpointData(this.ID, checkpoints, GOKZ_CHECKPOINT_VERSION);
+ }
+ }
+
+ property int TeleportCount {
+ public get() {
+ return GOKZ_GetTeleportCount(this.ID);
+ }
+ public set(int value) {
+ GOKZ_SetTeleportCount(this.ID, value);
+ }
+ }
+
+ property int TimeType {
+ public get() {
+ return GOKZ_GetTimeType(this.ID);
+ }
+ }
+
+ property bool GOKZHitPerf {
+ public get() {
+ return GOKZ_GetHitPerf(this.ID);
+ }
+ public set(bool value) {
+ GOKZ_SetHitPerf(this.ID, value);
+ }
+ }
+
+ property float GOKZTakeoffSpeed {
+ public get() {
+ return GOKZ_GetTakeoffSpeed(this.ID);
+ }
+ public set(float value) {
+ GOKZ_SetTakeoffSpeed(this.ID, value);
+ }
+ }
+
+ property bool ValidJump {
+ public get() {
+ return GOKZ_GetValidJump(this.ID);
+ }
+ }
+
+ public any GetOption(const char[] option) {
+ return GOKZ_GetOption(this.ID, option);
+ }
+
+ public bool SetOption(const char[] option, any value) {
+ return GOKZ_SetOption(this.ID, option, value);
+ }
+
+ public bool CycleOption(const char[] option) {
+ return GOKZ_CycleOption(this.ID, option);
+ }
+
+ public any GetCoreOption(Option option) {
+ return GOKZ_GetCoreOption(this.ID, option);
+ }
+
+ public bool SetCoreOption(Option option, int value) {
+ return GOKZ_SetCoreOption(this.ID, option, value);
+ }
+
+ public bool CycleCoreOption(Option option) {
+ return GOKZ_CycleCoreOption(this.ID, option);
+ }
+
+ property int Mode {
+ public get() {
+ return this.GetCoreOption(Option_Mode);
+ }
+ public set(int value) {
+ this.SetCoreOption(Option_Mode, value);
+ }
+ }
+
+ property int Style {
+ public get() {
+ return this.GetCoreOption(Option_Style);
+ }
+ public set(int value) {
+ this.SetCoreOption(Option_Style, value);
+ }
+ }
+
+ property int CheckpointMessages {
+ public get() {
+ return this.GetCoreOption(Option_CheckpointMessages);
+ }
+ public set(int value) {
+ this.SetCoreOption(Option_CheckpointMessages, value);
+ }
+ }
+
+ property int CheckpointSounds {
+ public get() {
+ return this.GetCoreOption(Option_CheckpointSounds);
+ }
+ public set(int value) {
+ this.SetCoreOption(Option_CheckpointSounds, value);
+ }
+ }
+
+ property int TeleportSounds {
+ public get() {
+ return this.GetCoreOption(Option_TeleportSounds);
+ }
+ public set(int value) {
+ this.SetCoreOption(Option_TeleportSounds, value);
+ }
+ }
+
+ property int ErrorSounds {
+ public get() {
+ return this.GetCoreOption(Option_ErrorSounds);
+ }
+ public set(int value) {
+ this.SetCoreOption(Option_ErrorSounds, value);
+ }
+ }
+
+ #endif
+ // =====[ END CORE ]=====
+
+
+
+ // =====[ HUD ]=====
+ #if defined _gokz_hud_included_
+
+ public any GetHUDOption(HUDOption option) {
+ return GOKZ_HUD_GetOption(this.ID, option);
+ }
+
+ public bool SetHUDOption(HUDOption option, any value) {
+ return GOKZ_HUD_SetOption(this.ID, option, value);
+ }
+
+ public bool CycleHUDOption(HUDOption option) {
+ return GOKZ_HUD_CycleOption(this.ID, option);
+ }
+
+ property int TPMenu {
+ public get() {
+ return this.GetHUDOption(HUDOption_TPMenu);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_TPMenu, value);
+ }
+ }
+
+ property int InfoPanel {
+ public get() {
+ return this.GetHUDOption(HUDOption_InfoPanel);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_InfoPanel, value);
+ }
+ }
+
+ property int ShowKeys {
+ public get() {
+ return this.GetHUDOption(HUDOption_ShowKeys);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_ShowKeys, value);
+ }
+ }
+
+ property int TimerText {
+ public get() {
+ return this.GetHUDOption(HUDOption_TimerText);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_TimerText, value);
+ }
+ }
+
+ property int TimerStyle {
+ public get() {
+ return this.GetHUDOption(HUDOption_TimerStyle);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_TimerStyle, value);
+ }
+ }
+
+ property int SpeedText {
+ public get() {
+ return this.GetHUDOption(HUDOption_SpeedText);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_SpeedText, value);
+ }
+ }
+
+ property int ShowWeapon {
+ public get() {
+ return this.GetHUDOption(HUDOption_ShowWeapon);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_ShowWeapon, value);
+ }
+ }
+
+ property int ReplayControls {
+ public get() {
+ return this.GetHUDOption(HUDOption_ShowControls);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_ShowControls, value);
+ }
+ }
+
+ property int ShowSpectators {
+ public get() {
+ return this.GetHUDOption(HUDOption_ShowSpectators);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_ShowSpectators, value);
+ }
+ }
+
+ property int SpecListPosition {
+ public get() {
+ return this.GetHUDOption(HUDOption_SpecListPosition);
+ }
+ public set(int value){
+ this.SetHUDOption(HUDOption_SpecListPosition, value);
+ }
+ }
+
+ property bool MenuShowing {
+ public get() {
+ return GOKZ_HUD_GetMenuShowing(this.ID);
+ }
+ public set(bool value) {
+ GOKZ_HUD_SetMenuShowing(this.ID, value);
+ }
+ }
+ property int DynamicMenu {
+ public get() {
+ return this.GetHUDOption(HUDOption_DynamicMenu);
+ }
+ public set(int value) {
+ this.SetHUDOption(HUDOption_DynamicMenu, value);
+ }
+ }
+ #endif
+ // =====[ END HUD ]=====
+
+
+
+ // =====[ PISTOL ]=====
+ #if defined _gokz_pistol_included_
+
+ property int Pistol {
+ public get() {
+ return this.GetOption(PISTOL_OPTION_NAME);
+ }
+ public set(int value) {
+ this.SetOption(PISTOL_OPTION_NAME, value);
+ }
+ }
+
+ #endif
+ // =====[ END PISTOL ]=====
+
+
+
+ // =====[ JUMP BEAM ]=====
+ #if defined _gokz_jumpbeam_included_
+
+ public any GetJBOption(JBOption option) {
+ return GOKZ_JB_GetOption(this.ID, option);
+ }
+
+ public bool SetJBOption(JBOption option, any value) {
+ return GOKZ_JB_SetOption(this.ID, option, value);
+ }
+
+ public bool CycleJBOption(JBOption option) {
+ return GOKZ_JB_CycleOption(this.ID, option);
+ }
+
+ property int JBType {
+ public get() {
+ return this.GetJBOption(JBOption_Type);
+ }
+ public set(int value) {
+ this.SetJBOption(JBOption_Type, value);
+ }
+ }
+
+ #endif
+ // =====[ END JUMP BEAM ]=====
+
+
+
+ // =====[ TIPS ]=====
+ #if defined _gokz_tips_included_
+
+ property int Tips {
+ public get() {
+ return this.GetOption(TIPS_OPTION_NAME);
+ }
+ public set(int value) {
+ this.SetOption(TIPS_OPTION_NAME, value);
+ }
+ }
+
+ #endif
+ // =====[ END TIPS ]=====
+
+
+
+ // =====[ QUIET ]=====
+ #if defined _gokz_quiet_included_
+
+ property int ShowPlayers {
+ public get() {
+ return this.GetOption(gC_QTOptionNames[QTOption_ShowPlayers]);
+ }
+ public set(int value) {
+ this.SetOption(gC_QTOptionNames[QTOption_ShowPlayers], value);
+ }
+ }
+
+ #endif
+ // =====[ END QUIET ]=====
+
+
+
+ // =====[ SLAY ON END ]=====
+ #if defined _gokz_slayonend_included_
+
+ property int SlayOnEnd {
+ public get() {
+ return this.GetOption(SLAYONEND_OPTION_NAME);
+ }
+ public set(int value) {
+ this.SetOption(SLAYONEND_OPTION_NAME, value);
+ }
+ }
+
+ #endif
+ // =====[ END SLAY ON END ]=====
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/localdb.inc b/sourcemod/scripting/include/gokz/localdb.inc
new file mode 100644
index 0000000..472a120
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/localdb.inc
@@ -0,0 +1,353 @@
+/*
+ gokz-localdb Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_localdb_included_
+#endinput
+#endif
+#define _gokz_localdb_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum DatabaseType
+{
+ DatabaseType_None = -1,
+ DatabaseType_MySQL,
+ DatabaseType_SQLite
+};
+
+enum
+{
+ JumpstatDB_Lookup_JumpID = 0,
+ JumpstatDB_Lookup_Distance,
+ JumpstatDB_Lookup_Block
+};
+
+enum
+{
+ JumpstatDB_FindPlayer_SteamID32 = 0,
+ JumpstatDB_FindPlayer_Alias
+};
+
+enum
+{
+ JumpstatDB_Top20_JumpID = 0,
+ JumpstatDB_Top20_SteamID,
+ JumpstatDB_Top20_Alias,
+ JumpstatDB_Top20_Block,
+ JumpstatDB_Top20_Distance,
+ JumpstatDB_Top20_Strafes,
+ JumpstatDB_Top20_Sync,
+ JumpstatDB_Top20_Pre,
+ JumpstatDB_Top20_Max,
+ JumpstatDB_Top20_Air
+};
+
+enum
+{
+ JumpstatDB_PBMenu_JumpID = 0,
+ JumpstatDB_PBMenu_JumpType,
+ JumpstatDB_PBMenu_Distance,
+ JumpstatDB_PBMenu_Strafes,
+ JumpstatDB_PBMenu_Sync,
+ JumpstatDB_PBMenu_Pre,
+ JumpstatDB_PBMenu_Max,
+ JumpstatDB_PBMenu_Air
+};
+
+enum
+{
+ JumpstatDB_BlockPBMenu_JumpID = 0,
+ JumpstatDB_BlockPBMenu_JumpType,
+ JumpstatDB_BlockPBMenu_Block,
+ JumpstatDB_BlockPBMenu_Distance,
+ JumpstatDB_BlockPBMenu_Strafes,
+ JumpstatDB_BlockPBMenu_Sync,
+ JumpstatDB_BlockPBMenu_Pre,
+ JumpstatDB_BlockPBMenu_Max,
+ JumpstatDB_BlockPBMenu_Air
+};
+
+enum
+{
+ JumpstatDB_Cache_Distance = 0,
+ JumpstatDB_Cache_Block,
+ JumpstatDB_Cache_BlockDistance,
+ JUMPSTATDB_CACHE_COUNT
+};
+
+enum
+{
+ TimerSetupDB_GetVBPos_SteamID = 0,
+ TimerSetupDB_GetVBPos_MapID,
+ TimerSetupDB_GetVBPos_Course,
+ TimerSetupDB_GetVBPos_IsStart,
+ TimerSetupDB_GetVBPos_PositionX,
+ TimerSetupDB_GetVBPos_PositionY,
+ TimerSetupDB_GetVBPos_PositionZ
+};
+
+enum
+{
+ TimerSetupDB_GetStartPos_SteamID = 0,
+ TimerSetupDB_GetStartPos_MapID,
+ TimerSetupDB_GetStartPos_PositionX,
+ TimerSetupDB_GetStartPos_PositionY,
+ TimerSetupDB_GetStartPos_PositionZ,
+ TimerSetupDB_GetStartPos_Angle0,
+ TimerSetupDB_GetStartPos_Angle1
+};
+
+enum DBOption:
+{
+ DBOption_AutoLoadTimerSetup = 0,
+ DBOPTION_COUNT
+};
+
+enum
+{
+ DBOption_Disabled = 0,
+ DBOption_Enabled,
+ DBOPTIONBOOL_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define GOKZ_DB_JS_DISTANCE_PRECISION 10000
+#define GOKZ_DB_JS_SYNC_PRECISION 100
+#define GOKZ_DB_JS_PRE_PRECISION 100
+#define GOKZ_DB_JS_MAX_PRECISION 100
+#define GOKZ_DB_JS_AIRTIME_PRECISION 10000
+#define GOKZ_DB_JS_MAX_JUMPS_PER_PLAYER 100
+
+stock char gC_DBOptionNames[DBOPTION_COUNT][] =
+{
+ "GOKZ DB - Auto Load Setup"
+};
+
+stock char gC_DBOptionDescriptions[DBOPTION_COUNT][] =
+{
+ "Automatically load timer setup on map start - 0 = Disabled, 1 = Enabled"
+}
+
+stock int gI_DBOptionDefaultValues[DBOPTION_COUNT] =
+{
+ DBOption_Disabled
+};
+
+stock int gI_DBOptionCounts[DBOPTION_COUNT] =
+{
+ DBOPTIONBOOL_COUNT
+};
+
+stock char gC_DBOptionPhrases[DBOPTION_COUNT][] =
+{
+ "Options Menu - Auto Load Timer Setup"
+};
+
+
+
+// =====[ TYPES ]=====
+
+typeset GetVBPositionCallback
+{
+ function Action(int client, const float position[3], int course, bool isStart);
+};
+
+typeset GetStartPositionCallback
+{
+ function Action(int client, const float position[3], const float angles[3]);
+};
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when gokz-localdb has connected to the database.
+ * Use GOKZ_DB_GetDatabase to get a clone of the database handle.
+ *
+ * @param DBType Database type.
+ */
+forward void GOKZ_DB_OnDatabaseConnect(DatabaseType DBType);
+
+/**
+ * Called when a player is ready for database interaction.
+ * At this point, the player is present and updated in the "Players" table.
+ *
+ * @param client Client index.
+ * @param steamID SteamID32 of the player (from GetSteamAccountID()).
+ * @param cheater Whether player is marked as a cheater in the database.
+ */
+forward void GOKZ_DB_OnClientSetup(int client, int steamID, bool cheater);
+
+/**
+ * Called when the current map is ready for database interaction.
+ * At this point, the map is present and updated in the "Maps" table.
+ *
+ * @param mapID MapID from the "Maps" table.
+ */
+forward void GOKZ_DB_OnMapSetup(int mapID);
+
+/**
+ * Called when a time has been inserted into the database.
+ *
+ * @param client Client index.
+ * @param steamID SteamID32 of the player (from GetSteamAccountID()).
+ * @param mapID MapID from the "Maps" table.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode Player's movement mode.
+ * @param style Player's movement style.
+ * @param runTimeMS Player's end time in milliseconds.
+ * @param teleportsUsed Number of teleports used by player.
+ */
+forward void GOKZ_DB_OnTimeInserted(
+ int client,
+ int steamID,
+ int mapID,
+ int course,
+ int mode,
+ int style,
+ int runTimeMS,
+ int teleportsUsed);
+
+/**
+ * Called when jumpstat PB has been achieved.
+ *
+ * @param client Client index.
+ * @param jumptype Type of the jump.
+ * @param mode Mode the jump was performed in.
+ * @param distance Distance jumped.
+ * @param block The size of the block jumped across.
+ * @param strafes The amount of strafes used.
+ * @param sync Keyboard/mouse synchronisation of the jump.
+ * @param pre Speed at takeoff.
+ * @param max Maximum speed during the jump.
+ * @param airtime Amount of time spend airborne.
+ */
+forward void GOKZ_DB_OnJumpstatPB(
+ int client,
+ int jumptype,
+ int mode,
+ float distance,
+ int block,
+ int strafes,
+ float sync,
+ float pre,
+ float max,
+ int airtime);
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets a clone of the GOKZ local database handle.
+ *
+ * @param database Database handle, or null if connection hasn't been made.
+ */
+native Database GOKZ_DB_GetDatabase();
+
+/**
+ * Gets the GOKZ local database type.
+ *
+ * @return Database type.
+ */
+native DatabaseType GOKZ_DB_GetDatabaseType();
+
+/**
+ * Gets whether client has been set up for GOKZ Local DB.
+ *
+ * @param client Client index.
+ * @return Whether GOKZ Local DB has set up the client.
+ */
+native bool GOKZ_DB_IsClientSetUp(int client);
+
+/**
+ * Gets whether GOKZ Local DB is set up for the current map.
+ *
+ * @return Whether GOKZ Local DB has set up the current map.
+ */
+native bool GOKZ_DB_IsMapSetUp();
+
+/**
+ * Gets the current map's MapID as in the "Maps" table.
+ *
+ * @return MapID from the "Maps" table.
+ */
+native int GOKZ_DB_GetCurrentMapID();
+
+/**
+ * Gets whether player is marked as a cheater in the database.
+ *
+ * @param client Client index.
+ * @return Whether player is marked as a cheater in the database.
+ */
+native bool GOKZ_DB_IsCheater(int client);
+
+/**
+ * Sets wheter player is marked as a cheater in the database.
+ *
+ * @param client Client index.
+ * @param cheater Whether to mark the player as a cheater.
+ */
+native void GOKZ_DB_SetCheater(int client, bool cheater);
+
+
+
+// =====[ STOCKS ]=====
+
+/**
+ * Converts a time float (seconds) to an integer (milliseconds).
+ *
+ * @param time Time in seconds.
+ * @return Time in milliseconds.
+ */
+stock int GOKZ_DB_TimeFloatToInt(float time)
+{
+ return RoundFloat(time * 1000.0);
+}
+
+/**
+ * Converts a time integer (milliseconds) to a float (seconds).
+ *
+ * @param time Time in milliseconds.
+ * @return Time in seconds.
+ */
+stock float GOKZ_DB_TimeIntToFloat(int time)
+{
+ return time / 1000.0;
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_localdb =
+{
+ name = "gokz-localdb",
+ file = "gokz-localdb.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_localdb_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_DB_GetDatabase");
+ MarkNativeAsOptional("GOKZ_DB_GetDatabaseType");
+ MarkNativeAsOptional("GOKZ_DB_IsClientSetUp");
+ MarkNativeAsOptional("GOKZ_DB_IsMapSetUp");
+ MarkNativeAsOptional("GOKZ_DB_GetCurrentMapID");
+ MarkNativeAsOptional("GOKZ_DB_IsCheater");
+ MarkNativeAsOptional("GOKZ_DB_SetCheater");
+}
+#endif
diff --git a/sourcemod/scripting/include/gokz/localranks.inc b/sourcemod/scripting/include/gokz/localranks.inc
new file mode 100644
index 0000000..914c6cb
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/localranks.inc
@@ -0,0 +1,176 @@
+/*
+ gokz-localranks Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_localranks_included_
+#endinput
+#endif
+#define _gokz_localranks_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ RecordType_Nub = 0,
+ RecordType_Pro,
+ RecordType_NubAndPro,
+ RECORDTYPE_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define LR_CFG_MAP_POOL "cfg/sourcemod/gokz/gokz-localranks-mappool.cfg"
+#define LR_CFG_SOUNDS "cfg/sourcemod/gokz/gokz-localranks-sounds.cfg"
+#define LR_COMMAND_COOLDOWN 2.5
+#define LR_MAP_TOP_CUTOFF 20
+#define LR_PLAYER_TOP_CUTOFF 20
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when a player's time has been processed by GOKZ Local Ranks.
+ *
+ * @param client Client index.
+ * @param steamID SteamID32 of the player (from GetSteamAccountID()).
+ * @param mapID MapID from the "Maps" database table.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode Player's movement mode.
+ * @param style Player's movement style.
+ * @param runTime Player's end time.
+ * @param teleportsUsed Number of teleportsUsed used by player.
+ * @param firstTime Whether this is player's first time on this course.
+ * @param pbDiff Difference between new time and PB in seconds (-'ve means beat PB).
+ * @param rank New rank of the player's PB time.
+ * @param maxRank New total number of players with times.
+ * @param firstTimePro Whether this is player's first PRO time on this course.
+ * @param pbDiffPro Difference between new time and PRO PB in seconds (-'ve means beat PB).
+ * @param rankPro New rank of the player's PB PRO time.
+ * @param maxRankPro New total number of players with PRO times.
+ */
+forward void GOKZ_LR_OnTimeProcessed(
+ int client,
+ int steamID,
+ int mapID,
+ int course,
+ int mode,
+ int style,
+ float runTime,
+ int teleportsUsed,
+ bool firstTime,
+ float pbDiff,
+ int rank,
+ int maxRank,
+ bool firstTimePro,
+ float pbDiffPro,
+ int rankPro,
+ int maxRankPro);
+
+/**
+ * Called when a player sets a new local record.
+ *
+ * @param client Client index.
+ * @param steamID SteamID32 of the player (from GetSteamAccountID()).
+ * @param mapID MapID from the "Maps" table.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode Player's movement mode.
+ * @param style Player's movement style.
+ * @param recordType Type of record.
+ */
+forward void GOKZ_LR_OnNewRecord(
+ int client,
+ int steamID,
+ int mapID,
+ int course,
+ int mode,
+ int style,
+ int recordType,
+ float pbDiff,
+ int teleportsUsed);
+
+/**
+ * Called when a player misses the server record time.
+ * Is called regardless of player's current run type.
+ *
+ * @param client Client index.
+ * @param recordTime Record time missed.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode Player's movement mode.
+ * @param style Player's movement style.
+ * @param recordType Type of record.
+ */
+forward void GOKZ_LR_OnRecordMissed(int client, float recordTime, int course, int mode, int style, int recordType);
+
+/**
+ * Called when a player misses their personal best time.
+ * Is called regardless of player's current run type.
+ *
+ * @param client Client index.
+ * @param pbTime Personal best time missed.
+ * @param course Course number e.g. 0=main, 1='bonus1' etc.
+ * @param mode Player's movement mode.
+ * @param style Player's movement style.
+ * @param recordType Type of record.
+ */
+forward void GOKZ_LR_OnPBMissed(int client, float pbTime, int course, int mode, int style, int recordType);
+
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets whether player has missed the server record time.
+ *
+ * @param client Client index.
+ * @param timeType Which record time i.e. NUB or PRO.
+ * @return Whether player has missed the server record time.
+ */
+native bool GOKZ_LR_GetRecordMissed(int client, int timeType);
+
+/**
+ * Gets whether player has missed their personal best time.
+ *
+ * @param client Client index.
+ * @param timeType Which PB time i.e. NUB or PRO.
+ * @return Whether player has missed their PB time.
+ */
+native bool GOKZ_LR_GetPBMissed(int client, int timeType);
+
+/**
+ * Reopens the map top menu with the already selected parameters.
+ * Don't use if the client hasn't opened the map top menu before.
+ *
+ * @param client Client index.
+ */
+native void GOKZ_LR_ReopenMapTopMenu(int client);
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_localranks =
+{
+ name = "gokz-localranks",
+ file = "gokz-localranks.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_localranks_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_LR_GetRecordMissed");
+ MarkNativeAsOptional("GOKZ_LR_GetPBMissed");
+ MarkNativeAsOptional("GOKZ_LR_ReopenMapTopMenu");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/momsurffix.inc b/sourcemod/scripting/include/gokz/momsurffix.inc
new file mode 100644
index 0000000..65f603e
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/momsurffix.inc
@@ -0,0 +1,23 @@
+/*
+ gokz-momsurffix Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_momsurffix_included_
+#endinput
+#endif
+#define _gokz_momsurffix_included_
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_momsurffix =
+{
+ name = "gokz-momsurffix",
+ file = "gokz-momsurffix.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+}; \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/paint.inc b/sourcemod/scripting/include/gokz/paint.inc
new file mode 100644
index 0000000..19f4fb5
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/paint.inc
@@ -0,0 +1,114 @@
+/*
+ gokz-paint Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_paint_included_
+#endinput
+#endif
+#define _gokz_paint_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum PaintOption:
+{
+ PAINTOPTION_INVALID = -1,
+ PaintOption_Color,
+ PaintOption_Size,
+ PAINTOPTION_COUNT
+};
+
+enum
+{
+ PaintColor_Red = 0,
+ PaintColor_White,
+ PaintColor_Black,
+ PaintColor_Blue,
+ PaintColor_Brown,
+ PaintColor_Green,
+ PaintColor_Yellow,
+ PaintColor_Purple,
+ PAINTCOLOR_COUNT
+};
+
+enum
+{
+ PaintSize_Small = 0,
+ PaintSize_Medium,
+ PaintSize_Big,
+ PAINTSIZE_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define PAINT_OPTION_CATEGORY "Paint"
+#define MIN_PAINT_SPACING 1.0
+
+stock char gC_PaintOptionNames[PAINTOPTION_COUNT][] =
+{
+ "GOKZ Paint - Color",
+ "GOKZ Paint - Size"
+};
+
+stock char gC_PaintOptionDescriptions[PAINTOPTION_COUNT][] =
+{
+ "Paint Color",
+ "Paint Size - 0 = Small, 1 = Medium, 2 = Big"
+};
+
+stock char gC_PaintOptionPhrases[PAINTOPTION_COUNT][] =
+{
+ "Options Menu - Paint Color",
+ "Options Menu - Paint Size"
+};
+
+stock int gI_PaintOptionCounts[PAINTOPTION_COUNT] =
+{
+ PAINTCOLOR_COUNT,
+ PAINTSIZE_COUNT
+};
+
+stock int gI_PaintOptionDefaults[PAINTOPTION_COUNT] =
+{
+ PaintColor_Red,
+ PaintSize_Medium
+};
+
+stock char gC_PaintColorPhrases[PAINTCOLOR_COUNT][] =
+{
+ "Options Menu - Red",
+ "Options Menu - White",
+ "Options Menu - Black",
+ "Options Menu - Blue",
+ "Options Menu - Brown",
+ "Options Menu - Green",
+ "Options Menu - Yellow",
+ "Options Menu - Purple"
+};
+
+stock char gC_PaintSizePhrases[PAINTSIZE_COUNT][] =
+{
+ "Options Menu - Small",
+ "Options Menu - Medium",
+ "Options Menu - Big"
+};
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_paint =
+{
+ name = "gokz-paint",
+ file = "gokz-paint.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
diff --git a/sourcemod/scripting/include/gokz/pistol.inc b/sourcemod/scripting/include/gokz/pistol.inc
new file mode 100644
index 0000000..1edd5f9
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/pistol.inc
@@ -0,0 +1,93 @@
+/*
+ gokz-pistol Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_pistol_included_
+#endinput
+#endif
+#define _gokz_pistol_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ Pistol_Disabled = 0,
+ Pistol_USPS,
+ Pistol_Glock18,
+ Pistol_DualBerettas,
+ Pistol_P250,
+ Pistol_FiveSeveN,
+ Pistol_Tec9,
+ Pistol_CZ75Auto,
+ Pistol_DesertEagle,
+ Pistol_R8Revolver,
+ PISTOL_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define PISTOL_OPTION_NAME "GOKZ - Pistol"
+#define PISTOL_OPTION_DESCRIPTION "Pistol - 0 = Disabled, 1 = USP-S / P2000, 2 = Glock-18, 3 = Dual Berettas, 4 = P250, 5 = Five-SeveN, 6 = Tec-9, 7 = CZ75-Auto, 8 = Desert Eagle, 9 = R8 Revolver"
+
+stock char gC_PistolNames[PISTOL_COUNT][] =
+{
+ "", // Disabled
+ "USP-S / P2000",
+ "Glock-18",
+ "Dual Berettas",
+ "P250",
+ "Five-SeveN",
+ "Tec-9",
+ "CZ75-Auto",
+ "Desert Eagle",
+ "R8 Revolver"
+};
+
+stock char gC_PistolClassNames[PISTOL_COUNT][] =
+{
+ "", // Disabled
+ "weapon_hkp2000",
+ "weapon_glock",
+ "weapon_elite",
+ "weapon_p250",
+ "weapon_fiveseven",
+ "weapon_tec9",
+ "weapon_cz75a",
+ "weapon_deagle",
+ "weapon_revolver"
+};
+
+stock int gI_PistolTeams[PISTOL_COUNT] =
+{
+ CS_TEAM_NONE, // Disabled
+ CS_TEAM_CT,
+ CS_TEAM_T,
+ CS_TEAM_NONE,
+ CS_TEAM_NONE,
+ CS_TEAM_CT,
+ CS_TEAM_T,
+ CS_TEAM_NONE,
+ CS_TEAM_NONE,
+ CS_TEAM_NONE
+};
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_pistol =
+{
+ name = "gokz-pistol",
+ file = "gokz-pistol.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+}; \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/profile.inc b/sourcemod/scripting/include/gokz/profile.inc
new file mode 100644
index 0000000..70d314a
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/profile.inc
@@ -0,0 +1,291 @@
+/*
+ gokz-profile Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_profile_included_
+#endinput
+#endif
+#define _gokz_profile_included_
+
+
+// =====[ RANKS ]=====
+
+#define RANK_COUNT 23
+
+stock int gI_rankThreshold[MODE_COUNT][RANK_COUNT] = {
+ {
+ 0,
+ 1,
+ 500,
+ 1000,
+
+ 2000,
+ 5000,
+ 10000,
+
+ 20000,
+ 30000,
+ 40000,
+
+ 60000,
+ 70000,
+ 80000,
+
+ 100000,
+ 120000,
+ 140000,
+
+ 160000,
+ 180000,
+ 200000,
+
+ 250000,
+ 300000,
+ 400000,
+ 600000
+ },
+ {
+ 0,
+ 1,
+ 500,
+ 1000,
+
+ 2000,
+ 5000,
+ 10000,
+
+ 20000,
+ 30000,
+ 40000,
+
+ 60000,
+ 70000,
+ 80000,
+
+ 100000,
+ 120000,
+ 150000,
+
+ 200000,
+ 230000,
+ 250000,
+
+ 300000,
+ 400000,
+ 500000,
+ 800000
+ },
+ {
+ 0,
+ 1,
+ 500,
+ 1000,
+
+ 2000,
+ 5000,
+ 10000,
+
+ 20000,
+ 30000,
+ 40000,
+
+ 60000,
+ 70000,
+ 80000,
+
+ 100000,
+ 120000,
+ 150000,
+
+ 200000,
+ 230000,
+ 250000,
+
+ 400000,
+ 600000,
+ 800000,
+ 1000000
+ },
+};
+
+stock char gC_rankName[RANK_COUNT][] = {
+ "New",
+ "Beginner-",
+ "Beginner",
+ "Beginner+",
+ "Amateur-",
+ "Amateur",
+ "Amateur+",
+ "Casual-",
+ "Casual",
+ "Casual+",
+ "Regular-",
+ "Regular",
+ "Regular+",
+ "Skilled-",
+ "Skilled",
+ "Skilled+",
+ "Expert-",
+ "Expert",
+ "Expert+",
+ "Semipro",
+ "Pro",
+ "Master",
+ "Legend"
+};
+
+stock char gC_rankColor[RANK_COUNT][] = {
+ "{grey}",
+ "{default}",
+ "{default}",
+ "{default}",
+ "{blue}",
+ "{blue}",
+ "{blue}",
+ "{lightgreen}",
+ "{lightgreen}",
+ "{lightgreen}",
+ "{green}",
+ "{green}",
+ "{green}",
+ "{purple}",
+ "{purple}",
+ "{purple}",
+ "{orchid}",
+ "{orchid}",
+ "{orchid}",
+ "{lightred}",
+ "{lightred}",
+ "{red}",
+ "{gold}"
+};
+
+
+// =====[ ENUMS ]=====
+
+enum ProfileOption:
+{
+ PROFILEOPTION_INVALID = -1,
+ ProfileOption_ShowRankChat,
+ ProfileOption_ShowRankClanTag,
+ ProfileOption_TagType,
+ PROFILEOPTION_COUNT
+};
+
+enum
+{
+ ProfileOptionBool_Disabled = 0,
+ ProfileOptionBool_Enabled,
+ PROFILEOPTIONBOOL_COUNT
+};
+
+enum
+{
+ ProfileTagType_Rank = 0,
+ ProfileTagType_Admin,
+ ProfileTagType_VIP,
+ PROFILETAGTYPE_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+stock char gC_ProfileOptionNames[PROFILEOPTION_COUNT][] =
+{
+ "GOKZ Profile - Show Rank Chat",
+ "GOKZ Profile - Show Rank Clan",
+ "GOKZ Profile - Tag Type"
+};
+
+stock char gC_ProfileOptionDescriptions[PROFILEOPTION_COUNT][] =
+{
+ "Show Rank Tag in Chat - 0 = Disabled, 1 = Enabled",
+ "Show Rank in Clan - 0 = Disabled, 1 = Enabled",
+ "Type of Tag to Show - 0 = Rank, 1 = Admin, 2 = VIP"
+};
+
+stock char gC_ProfileOptionPhrases[PROFILEOPTION_COUNT][] =
+{
+ "Options Menu - Show Rank Chat",
+ "Options Menu - Show Rank Clan",
+ "Options Menu - Tag Type",
+};
+
+stock char gC_ProfileBoolPhrases[PROFILEOPTIONBOOL_COUNT][] =
+{
+ "Options Menu - Disabled",
+ "Options Menu - Enabled"
+};
+
+stock char gC_ProfileTagTypePhrases[PROFILETAGTYPE_COUNT][] =
+{
+ "Options Menu - Tag Rank",
+ "Options Menu - Tag Admin",
+ "Options Menu - Tag VIP"
+};
+
+stock int gI_ProfileOptionCounts[PROFILEOPTION_COUNT] =
+{
+ PROFILEOPTIONBOOL_COUNT,
+ PROFILEOPTIONBOOL_COUNT,
+ PROFILETAGTYPE_COUNT
+};
+
+stock int gI_ProfileOptionDefaults[PROFILEOPTION_COUNT] =
+{
+ ProfileOptionBool_Enabled,
+ ProfileOptionBool_Enabled,
+ ProfileTagType_Rank
+};
+
+#define PROFILE_OPTION_CATEGORY "Profile"
+#define TAG_COLOR_ADMIN "{red}"
+#define TAG_COLOR_VIP "{purple}"
+
+
+// =====[ FORWARDS ]=====
+
+
+/**
+ * Called when the rank of a player is updated.
+ *
+ * @param client Client index.
+ * @param mode Game mode.
+ * @param rank The new rank.
+ */
+forward void GOKZ_PF_OnRankUpdated(int client, int mode, int rank);
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets whether a mode is loaded.
+ *
+ * @param client Client.
+ * @param tag Mode.
+ * @returns Integer representing the player rank.
+ */
+native int GOKZ_PF_GetRank(int client, int mode);
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_profile =
+{
+ name = "gokz-profile",
+ file = "gokz-profile.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_profile_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_PF_GetRank");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/quiet.inc b/sourcemod/scripting/include/gokz/quiet.inc
new file mode 100644
index 0000000..a328b7e
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/quiet.inc
@@ -0,0 +1,205 @@
+/*
+ gokz-quiet Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_quiet_included_
+#endinput
+#endif
+#define _gokz_quiet_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum QTOption:
+{
+ QTOPTION_INVALID = -1,
+ QTOption_ShowPlayers,
+ QTOption_Soundscapes,
+ QTOption_FallDamageSound,
+ QTOption_AmbientSounds,
+ QTOption_CheckpointVolume,
+ QTOption_TeleportVolume,
+ QTOption_TimerVolume,
+ QTOption_ErrorVolume,
+ QTOption_ServerRecordVolume,
+ QTOption_WorldRecordVolume,
+ QTOption_JumpstatsVolume,
+ QTOPTION_COUNT
+};
+
+enum
+{
+ ShowPlayers_Disabled = 0,
+ ShowPlayers_Enabled,
+ SHOWPLAYERS_COUNT
+};
+
+enum
+{
+ Soundscapes_Disabled = 0,
+ Soundscapes_Enabled,
+ SOUNDSCAPES_COUNT
+};
+
+// =====[ CONSTANTS ]=====
+
+#define QUIET_OPTION_CATEGORY "Quiet"
+#define DEFAULT_VOLUME 10
+#define VOLUME_COUNT 21 // Maximum of 200%
+
+#define EFFECT_IMPACT 8
+#define EFFECT_KNIFESLASH 2
+#define BLANK_SOUNDSCAPEINDEX 482 // Search for "coopcementplant.missionselect_blank" id with sv_soundscape_printdebuginfo.
+
+stock char gC_QTOptionNames[QTOPTION_COUNT][] =
+{
+ "GOKZ QT - Show Players",
+ "GOKZ QT - Soundscapes",
+ "GOKZ QT - Fall Damage Sound",
+ "GOKZ QT - Ambient Sounds",
+ "GOKZ QT - Checkpoint Volume",
+ "GOKZ QT - Teleport Volume",
+ "GOKZ QT - Timer Volume",
+ "GOKZ QT - Error Volume",
+ "GOKZ QT - Server Record Volume",
+ "GOKZ QT - World Record Volume",
+ "GOKZ QT - Jumpstats Volume"
+};
+
+stock char gC_QTOptionDescriptions[QTOPTION_COUNT][] =
+{
+ "Visibility of Other Players - 0 = Disabled, 1 = Enabled",
+ "Play Soundscapes - 0 = Disabled, 1 = Enabled",
+ "Play Fall Damage Sound - 0 to 20 = 0% to 200%",
+ "Play Ambient Sounds - 0 to 20 = 0% to 200%",
+ "Checkpoint Volume - 0 to 20 = 0% to 200%",
+ "Teleport Volume - 0 to 20 = 0% to 200%",
+ "Timer Volume - 0 to 20 = 0% to 200%",
+ "Error Volume - 0 to 20 = 0% to 200%",
+ "Server Record Volume - 0 to 20 = 0% to 200%",
+ "World Record Volume - 0 to 20 = 0% to 200%",
+ "Jumpstats Volume - 0 to 20 = 0% to 200%"
+};
+
+stock int gI_QTOptionDefaultValues[QTOPTION_COUNT] =
+{
+ ShowPlayers_Enabled,
+ Soundscapes_Enabled,
+ DEFAULT_VOLUME, // Fall damage volume
+ DEFAULT_VOLUME, // Ambient volume
+ DEFAULT_VOLUME, // Checkpoint volume
+ DEFAULT_VOLUME, // Teleport volume
+ DEFAULT_VOLUME, // Timer volume
+ DEFAULT_VOLUME, // Error volume
+ DEFAULT_VOLUME, // Server Record Volume
+ DEFAULT_VOLUME, // World Record Volume
+ DEFAULT_VOLUME // Jumpstats Volume
+};
+
+stock int gI_QTOptionCounts[QTOPTION_COUNT] =
+{
+ SHOWPLAYERS_COUNT,
+ SOUNDSCAPES_COUNT,
+ VOLUME_COUNT, // Fall damage volume
+ VOLUME_COUNT, // Ambient volume
+ VOLUME_COUNT, // Checkpoint volume
+ VOLUME_COUNT, // Teleport volume
+ VOLUME_COUNT, // Timer volume
+ VOLUME_COUNT, // Error volume
+ VOLUME_COUNT, // Server Record volume
+ VOLUME_COUNT, // World Record volume
+ VOLUME_COUNT // Jumpstats volume
+};
+
+stock char gC_QTOptionPhrases[QTOPTION_COUNT][] =
+{
+ "Options Menu - Show Players",
+ "Options Menu - Soundscapes",
+ "Options Menu - Fall Damage Sounds",
+ "Options Menu - Ambient Sounds",
+ "Options Menu - Checkpoint Volume",
+ "Options Menu - Teleport Volume",
+ "Options Menu - Timer Volume",
+ "Options Menu - Error Volume",
+ "Options Menu - Server Record Volume",
+ "Options Menu - World Record Volume",
+ "Options Menu - Jumpstats Volume"
+};
+
+// =====[ STOCKS ]=====
+
+/**
+ * Returns whether an option is a gokz-quiet option.
+ *
+ * @param option Option name.
+ * @param optionEnum Variable to store enumerated gokz-quiet option (if it is one).
+ * @return Whether option is a gokz-quiet option.
+ */
+stock bool GOKZ_QT_IsQTOption(const char[] option, QTOption &optionEnum = QTOPTION_INVALID)
+{
+ for (QTOption i; i < QTOPTION_COUNT; i++)
+ {
+ if (StrEqual(option, gC_QTOptionNames[i]))
+ {
+ optionEnum = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Gets the current value of a player's gokz-quiet option.
+ *
+ * @param client Client index.
+ * @param option gokz-quiet option.
+ * @return Current value of option.
+ */
+stock any GOKZ_QT_GetOption(int client, QTOption option)
+{
+ return GOKZ_GetOption(client, gC_QTOptionNames[option]);
+}
+
+/**
+ * Sets a player's gokz-quiet option's value.
+ *
+ * @param client Client index.
+ * @param option gokz-quiet option.
+ * @param value New option value.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_QT_SetOption(int client, QTOption option, any value)
+{
+ return GOKZ_SetOption(client, gC_QTOptionNames[option], value);
+}
+
+/**
+ * Increment an integer-type gokz-quiet option's value.
+ * Loops back to '0' if max value is exceeded.
+ *
+ * @param client Client index.
+ * @param option gokz-quiet option.
+ * @return Whether option was successfully set.
+ */
+stock bool GOKZ_QT_CycleOption(int client, QTOption option)
+{
+ return GOKZ_CycleOption(client, gC_QTOptionNames[option]);
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_quiet =
+{
+ name = "gokz-quiet",
+ file = "gokz-quiet.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+}; \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/racing.inc b/sourcemod/scripting/include/gokz/racing.inc
new file mode 100644
index 0000000..d6819ea
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/racing.inc
@@ -0,0 +1,189 @@
+/*
+ gokz-racing Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_racing_included_
+#endinput
+#endif
+#define _gokz_racing_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum RaceInfo:
+{
+ RaceInfo_ID,
+ RaceInfo_Status,
+ RaceInfo_HostUserID,
+ RaceInfo_FinishedRacerCount,
+ RaceInfo_Type,
+ RaceInfo_Course,
+ RaceInfo_Mode,
+ RaceInfo_CheckpointRule,
+ RaceInfo_CooldownRule,
+ RACEINFO_COUNT
+};
+
+enum
+{
+ RaceType_Normal,
+ RaceType_Duel,
+ RACETYPE_COUNT
+};
+
+enum
+{
+ RaceStatus_Pending,
+ RaceStatus_Countdown,
+ RaceStatus_Started,
+ RaceStatus_Aborting,
+ RACESTATUS_COUNT
+};
+
+enum
+{
+ RacerStatus_Available,
+ RacerStatus_Pending,
+ RacerStatus_Accepted,
+ RacerStatus_Racing,
+ RacerStatus_Finished,
+ RacerStatus_Surrendered,
+ RACERSTATUS_COUNT
+};
+
+enum
+{
+ CheckpointRule_None,
+ CheckpointRule_Limit,
+ CheckpointRule_Cooldown,
+ CheckpointRule_Unlimited,
+ CHECKPOINTRULE_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define RC_COUNTDOWN_TIME 10.0
+#define RC_REQUEST_TIMEOUT_TIME 15.0
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when a player has finished their race.
+ *
+ * @param client Client index.
+ * @param raceID ID of the race.
+ * @param place Final placement in the race.
+ */
+forward void GOKZ_RC_OnFinish(int client, int raceID, int place);
+
+/**
+ * Called when a player has surrendered their race.
+ *
+ * @param client Client index.
+ * @param raceID ID of the race.
+ */
+forward void GOKZ_RC_OnSurrender(int client, int raceID);
+
+/**
+ * Called when a player receives a race request.
+ *
+ * @param client Client index.
+ * @param raceID ID of the race.
+ */
+forward void GOKZ_RC_OnRequestReceived(int client, int raceID)
+
+/**
+ * Called when a player accepts a race request.
+ *
+ * @param client Client index.
+ * @param raceID ID of the race.
+ */
+forward void GOKZ_RC_OnRequestAccepted(int client, int raceID)
+
+/**
+ * Called when a player declines a race request.
+ *
+ * @param client Client index.
+ * @param raceID ID of the race.
+ * @param timeout Whether the client was too late to respond.
+ */
+forward void GOKZ_RC_OnRequestDeclined(int client, int raceID, bool timeout)
+
+/**
+ * Called when a race has been registered.
+ * The initial status of a race is RaceStatus_Pending.
+ *
+ * @param raceID ID of the race.
+ */
+forward void GOKZ_RC_OnRaceRegistered(int raceID);
+
+/**
+ * Called when a race's info property has changed.
+ *
+ * @param raceID ID of the race.
+ * @param prop Info property that was changed.
+ * @param oldValue Previous value.
+ * @param newValue New value.
+ */
+forward void GOKZ_RC_OnRaceInfoChanged(int raceID, RaceInfo prop, int oldValue, int newValue);
+
+
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets the value of a race info property.
+ *
+ * @param raceID Race index.
+ * @param prop Info property to get.
+ * @return Value of the info property.
+ */
+native int GOKZ_RC_GetRaceInfo(int raceID, RaceInfo prop);
+
+/**
+ * Gets a player's racer status.
+ * Refer to the RacerStatus enumeration.
+ *
+ * @param client Client index.
+ * @return Racer status of the client.
+ */
+native int GOKZ_RC_GetStatus(int client);
+
+/**
+ * Gets the ID of the race a player is in.
+ *
+ * @param client Client index.
+ * @return ID of the race the player is in, or -1 if not in a race.
+ */
+native int GOKZ_RC_GetRaceID(int client);
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_racing =
+{
+ name = "gokz-racing",
+ file = "gokz-racing.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_racing_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_RC_GetRaceInfo");
+ MarkNativeAsOptional("GOKZ_RC_GetStatus");
+ MarkNativeAsOptional("GOKZ_RC_GetRaceID");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/replays.inc b/sourcemod/scripting/include/gokz/replays.inc
new file mode 100644
index 0000000..6aabdbd
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/replays.inc
@@ -0,0 +1,275 @@
+/*
+ gokz-replays Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_replays_included_
+#endinput
+#endif
+#define _gokz_replays_included_
+
+// Bit of a hack, but need it for other plugins that depend on replays to compile
+#if defined REQUIRE_PLUGIN
+#undef REQUIRE_PLUGIN
+#include <gokz/anticheat>
+#define REQUIRE_PLUGIN
+#else
+#include <gokz/anticheat>
+#endif
+
+
+
+// =====[ ENUMS ]=====
+enum
+{
+ ReplayType_Run = 0,
+ ReplayType_Cheater,
+ ReplayType_Jump,
+ REPLAYTYPE_COUNT
+};
+
+enum ReplaySaveState
+{
+ ReplaySave_Local = 0,
+ ReplaySave_Temp,
+ ReplaySave_Disabled
+};
+
+// NOTE: Replays use delta compression for storage.
+// This enum is the indices of the ReplayTickData enum struct.
+// NOTE: This has to match the ReplayTickData enum struct!!!
+enum
+{
+ RPDELTA_DELTAFLAGS = 0,
+ RPDELTA_DELTAFLAGS2,
+ RPDELTA_VEL_X,
+ RPDELTA_VEL_Y,
+ RPDELTA_VEL_Z,
+ RPDELTA_MOUSE_X,
+ RPDELTA_MOUSE_Y,
+ RPDELTA_ORIGIN_X,
+ RPDELTA_ORIGIN_Y,
+ RPDELTA_ORIGIN_Z,
+ RPDELTA_ANGLES_X,
+ RPDELTA_ANGLES_Y,
+ RPDELTA_ANGLES_Z,
+ RPDELTA_VELOCITY_X,
+ RPDELTA_VELOCITY_Y,
+ RPDELTA_VELOCITY_Z,
+ RPDELTA_FLAGS,
+ RPDELTA_PACKETSPERSECOND,
+ RPDELTA_LAGGEDMOVEMENTVALUE,
+ RPDELTA_BUTTONSFORCED,
+
+ RP_V2_TICK_DATA_BLOCKSIZE
+};
+
+
+
+// =====[ STRUCTS ] =====
+
+enum struct GeneralReplayHeader
+{
+ int magicNumber;
+ int formatVersion;
+ int replayType;
+ char gokzVersion[32];
+ char mapName[64];
+ int mapFileSize;
+ int serverIP;
+ int timestamp;
+ char playerAlias[MAX_NAME_LENGTH];
+ int playerSteamID;
+ int mode;
+ int style;
+ float playerSensitivity;
+ float playerMYaw;
+ float tickrate;
+ int tickCount;
+ int equippedWeapon;
+ int equippedKnife;
+}
+
+enum struct JumpReplayHeader
+{
+ int jumpType;
+ float distance;
+ int blockDistance;
+ int strafeCount;
+ float sync;
+ float pre;
+ float max;
+ int airtime;
+}
+
+enum struct CheaterReplayHeader
+{
+ ACReason ACReason;
+}
+
+enum struct RunReplayHeader
+{
+ float time;
+ int course;
+ int teleportsUsed;
+}
+
+// NOTE: Make sure to change the RPDELTA_* enum, TickDataToArray() and TickDataFromArray() when adding/removing stuff from this!!!
+enum struct ReplayTickData
+{
+ int deltaFlags;
+ int deltaFlags2;
+ float vel[3];
+ int mouse[2];
+ float origin[3];
+ float angles[3];
+ float velocity[3];
+ int flags;
+ float packetsPerSecond;
+ float laggedMovementValue;
+ int buttonsForced;
+}
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define RP_DIRECTORY "data/gokz-replays" // In Path_SM
+#define RP_DIRECTORY_RUNS "data/gokz-replays/_runs" // In Path_SM
+#define RP_DIRECTORY_RUNS_TEMP "data/gokz-replays/_tempRuns" // In Path_SM
+#define RP_DIRECTORY_CHEATERS "data/gokz-replays/_cheaters" // In Path_SM
+#define RP_DIRECTORY_JUMPS "data/gokz-replays/_jumps" // In Path_SM
+#define RP_DIRECTORY_BLOCKJUMPS "blocks"
+#define RP_FILE_EXTENSION "replay"
+#define RP_MAGIC_NUMBER 0x676F6B7A
+#define RP_FORMAT_VERSION 0x02
+#define RP_NAV_FILE "maps/gokz-replays.nav"
+#define RP_V1_TICK_DATA_BLOCKSIZE 7
+#define RP_CACHE_BLOCKSIZE 4
+#define RP_MAX_BOTS 4
+#define RP_PLAYBACK_BREATHER_TIME 2.0
+#define RP_MIN_CHEATER_REPLAY_LENGTH 30 // 30 seconds
+#define RP_MAX_CHEATER_REPLAY_LENGTH 120 // 2 minutes
+#define RP_MAX_BHOP_GROUND_TICKS 5
+#define RP_SKIP_TIME 10 // 10 seconds
+#define RP_MAX_DURATION 6451200 // 14 hours on 128 tick
+#define RP_JUMP_STEP_SOUND_THRESHOLD 140.0
+#define RP_PLAYER_ACCELSPEED 450.0
+
+#define RP_MOVETYPE_MASK (0xF)
+#define RP_IN_ATTACK (1 << 4)
+#define RP_IN_ATTACK2 (1 << 5)
+#define RP_IN_JUMP (1 << 6)
+#define RP_IN_DUCK (1 << 7)
+#define RP_IN_FORWARD (1 << 8)
+#define RP_IN_BACK (1 << 9)
+#define RP_IN_LEFT (1 << 10)
+#define RP_IN_RIGHT (1 << 11)
+#define RP_IN_MOVELEFT (1 << 12)
+#define RP_IN_MOVERIGHT (1 << 13)
+#define RP_IN_RELOAD (1 << 14)
+#define RP_IN_SPEED (1 << 15)
+#define RP_IN_USE (1 << 16)
+#define RP_IN_BULLRUSH (1 << 17)
+#define RP_FL_ONGROUND (1 << 18)
+#define RP_FL_DUCKING (1 << 19)
+#define RP_FL_SWIM (1 << 20)
+#define RP_UNDER_WATER (1 << 21)
+#define RP_TELEPORT_TICK (1 << 22)
+#define RP_TAKEOFF_TICK (1 << 23)
+#define RP_HIT_PERF (1 << 24)
+#define RP_SECONDARY_EQUIPPED (1 << 25)
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when a replay of a player is written to disk.
+ * This includes replays of cheaters which are saved if
+ * the player is marked as a cheater by gokz-localdb.
+ *
+ * @param client The client ID of the player who completed the run.
+ * @param replayType The type of the replay (Run/Jump/Cheater).
+ * @param map The name of the map the run was completed on.
+ * @param course The specific course on the map the run was completed on.
+ * @param timeType The type of time (Pro/Nub).
+ * @param time The time the run was completed in.
+ * @param filePath Replay file path.
+ * @param tempReplay Whether the replay file should only be temporaily stored.
+ * @return Plugin_Handled to take over the temporary replay deletion, Plugin_Continue to allow temporary replay deletion by the replay plugin.
+ */
+forward Action GOKZ_RP_OnReplaySaved(int client, int replayType, const char[] map, int course, int timeType, float time, const char[] filePath, bool tempReplay);
+
+/**
+ * Called when a currently being recorded replay is discarded from
+ * memory and recording has been stopped (without writing it to disk).
+ *
+ * @param client Client index.
+ */
+forward void GOKZ_RP_OnReplayDiscarded(int client);
+
+/**
+ * Called when a player has ended their timer, and gokz-replays has
+ * processed the time and has possibly written a replay to disk.
+ *
+ * @param client Client index.
+ * @param filePath Replay file path, or "" if no replay saved.
+ * @param course Course number.
+ * @param time Player's end time.
+ * @param teleportsUsed Number of teleports used by player.
+ */
+forward void GOKZ_RP_OnTimerEnd_Post(int client, const char[] filePath, int course, float time, int teleportsUsed);
+
+
+
+// =====[ NATIVES ]====
+
+/**
+ * Called by the HUD to get the state of the current replay.
+ *
+ * @param client Client index.
+ * @param info Struct to pass the values into.
+ * @return If successful
+ */
+native int GOKZ_RP_GetPlaybackInfo(int client, any[] info);
+
+/**
+ * Called by the LocalDB to initiate a replay of a jump
+ *
+ * @param client Client index.
+ * @param path Path to the replay file.
+ * @return The client ID of the bot performing the replay.
+ */
+native int GOKZ_RP_LoadJumpReplay(int client, char[] path);
+
+/**
+ * Called by the HUD to show the replay control menu.
+ *
+ * @param client Client index.
+ */
+native bool GOKZ_RP_UpdateReplayControlMenu(int client);
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_replays =
+{
+ name = "gokz-replays",
+ file = "gokz-replays.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_gokz_replays_SetNTVOptional()
+{
+ MarkNativeAsOptional("GOKZ_RP_GetPlaybackInfo");
+ MarkNativeAsOptional("GOKZ_RP_LoadJumpReplay");
+ MarkNativeAsOptional("GOKZ_RP_UpdateReplayControlMenu");
+}
+#endif
diff --git a/sourcemod/scripting/include/gokz/slayonend.inc b/sourcemod/scripting/include/gokz/slayonend.inc
new file mode 100644
index 0000000..2ed01e5
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/slayonend.inc
@@ -0,0 +1,43 @@
+/*
+ gokz-slayonend Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_slayonend_included_
+#endinput
+#endif
+#define _gokz_slayonend_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ SlayOnEnd_Disabled = 0,
+ SlayOnEnd_Enabled,
+ SLAYONEND_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define SLAYONEND_OPTION_NAME "GOKZ - Slay On End"
+#define SLAYONEND_OPTION_DESCRIPTION "Automatic Slaying Upon Ending Timer - 0 = Disabled, 1 = Enabled"
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_slayonend =
+{
+ name = "gokz-slayonend",
+ file = "gokz-slayonend.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+}; \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/tips.inc b/sourcemod/scripting/include/gokz/tips.inc
new file mode 100644
index 0000000..b5e2d3e
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/tips.inc
@@ -0,0 +1,59 @@
+/*
+ gokz-tips Plugin Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+*/
+
+#if defined _gokz_tips_included_
+#endinput
+#endif
+#define _gokz_tips_included_
+
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ Tips_Disabled = 0,
+ Tips_Enabled,
+ TIPS_COUNT
+};
+
+
+
+// =====[ CONSTANTS ]=====
+
+#define TIPS_PLUGINS_COUNT 9
+#define TIPS_CORE "gokz-tips-core.phrases.txt"
+#define TIPS_TIPS "gokz-tips-tips.phrases.txt"
+#define TIPS_OPTION_NAME "GOKZ - Tips"
+#define TIPS_OPTION_DESCRIPTION "Random Tips Periodically in Chat - 0 = Disabled, 1 = Enabled"
+
+stock char gC_PluginsWithTips[TIPS_PLUGINS_COUNT][] =
+{
+ "goto",
+ "hud",
+ "jumpstats",
+ "localranks",
+ "measure",
+ "pistol",
+ "quiet",
+ "replays",
+ "spec"
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_tips =
+{
+ name = "gokz-tips",
+ file = "gokz-tips.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+}; \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/tpanglefix.inc b/sourcemod/scripting/include/gokz/tpanglefix.inc
new file mode 100644
index 0000000..fc8faa2
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/tpanglefix.inc
@@ -0,0 +1,40 @@
+/*
+ gokz-tpanglefix Plugin Include
+
+ Website: https://github.com/KZGlobalTeam/gokz
+*/
+
+#if defined _gokz_tpanglefix_included_
+#endinput
+#endif
+#define _gokz_tpanglefix_included_
+
+
+// =====[ ENUMS ]=====
+
+enum
+{
+ TPAngleFix_Disabled = 0,
+ TPAngleFix_Enabled,
+ TPANGLEFIX_COUNT
+};
+
+
+// =====[ CONSTANTS ]=====
+
+#define TPANGLEFIX_OPTION_NAME "GOKZ - TPAngleFix"
+#define TPANGLEFIX_OPTION_DESCRIPTION "TPAngleFix - 0 = Disabled, 1 = Enabled"
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_gokz_tpanglefix =
+{
+ name = "gokz-tpanglefix",
+ file = "gokz-tpanglefix.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+}; \ No newline at end of file
diff --git a/sourcemod/scripting/include/gokz/version.inc b/sourcemod/scripting/include/gokz/version.inc
new file mode 100644
index 0000000..34cdb19
--- /dev/null
+++ b/sourcemod/scripting/include/gokz/version.inc
@@ -0,0 +1,12 @@
+/*
+ GOKZ Version Include
+
+ Website: https://bitbucket.org/kztimerglobalteam/gokz
+
+ You should not need to edit this file.
+ This file is overwritten in the build pipeline for versioning.
+*/
+
+
+
+#define GOKZ_VERSION "" \ No newline at end of file
diff --git a/sourcemod/scripting/include/json.inc b/sourcemod/scripting/include/json.inc
new file mode 100644
index 0000000..ebc46e4
--- /dev/null
+++ b/sourcemod/scripting/include/json.inc
@@ -0,0 +1,473 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2019 James Dickens. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_included
+ #endinput
+#endif
+#define _json_included
+
+#include <string>
+#include <json/definitions>
+#include <json/helpers/decode>
+#include <json/helpers/encode>
+#include <json/helpers/string>
+#include <json/object>
+
+/**
+ * Encodes a JSON instance into its string representation.
+ *
+ * @param obj Object to encode.
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ * @param pretty_print Should the output be pretty printed (newlines and spaces)? [default: false]
+ * @param depth The current depth of the encoder. [default: 0]
+ */
+stock void json_encode(
+ JSON_Object obj,
+ char[] output,
+ int max_size,
+ bool pretty_print = false,
+ int depth = 0
+)
+{
+ bool is_array = obj.IsArray;
+ bool is_empty = true;
+ int builder_size;
+
+ // used in key iterator
+ int str_length = 1;
+ int int_value;
+ int cell_length = 0;
+
+ StringMapSnapshot snap = null;
+ int json_size = 0;
+ if (is_array) {
+ json_size = obj.CurrentIndex;
+
+ strcopy(output, max_size, "[");
+ } else {
+ snap = obj.Snapshot();
+ json_size = snap.Length;
+
+ strcopy(output, max_size, "{");
+ }
+
+ int key_length = 0;
+ for (int i = 0; i < json_size; ++i) {
+ key_length = (is_array) ? JSON_INDEX_BUFFER_SIZE : snap.KeyBufferSize(i);
+ char[] key = new char[key_length];
+
+ if (is_array) {
+ obj.GetIndexString(key, key_length, i);
+ } else {
+ snap.GetKey(i, key, key_length);
+ }
+
+ // skip meta-keys
+ if (json_is_meta_key(key)) {
+ continue;
+ }
+
+ // skip keys that are marked as hidden
+ if (obj.GetKeyHidden(key)) {
+ continue;
+ }
+
+ JSON_CELL_TYPE type = obj.GetKeyType(key);
+ // skip keys of unknown type
+ if (type == Type_Invalid) {
+ continue;
+ }
+
+ // if we are dealing with a string, prepare the str_value variable for fetching
+ if (type == Type_String) {
+ str_length = obj.GetKeyLength(key);
+ }
+ char[] str_value = new char[str_length + 1];
+
+ // determine the length of the char[] needed to represent our cell data
+ cell_length = 0;
+ switch (type) {
+ case Type_String: {
+ // get the string value early, as its cell_length is determined by its contents
+ obj.GetString(key, str_value, str_length + 1);
+ cell_length = json_cell_string_size(str_length);
+ }
+ case Type_Int: {
+ // get the int value early, as its cell_length is determined by its contents
+ int_value = obj.GetInt(key);
+ cell_length = json_cell_int_size(int_value);
+ }
+ case Type_Float: {
+ cell_length = json_cell_float_size();
+ }
+ case Type_Bool: {
+ cell_length = json_cell_bool_size();
+ }
+ case Type_Null: {
+ cell_length = json_cell_null_size();
+ }
+ case Type_Object: {
+ cell_length = max_size;
+ }
+ }
+
+ // fit the contents into the cell
+ char[] cell = new char[cell_length];
+ switch (type) {
+ case Type_String: {
+ json_cell_string(str_value, cell, cell_length);
+ }
+ case Type_Int: {
+ json_cell_int(int_value, cell, cell_length);
+ }
+ case Type_Float: {
+ float value = obj.GetFloat(key);
+ json_cell_float(value, cell, cell_length);
+ }
+ case Type_Bool: {
+ bool value = obj.GetBool(key);
+ json_cell_bool(value, cell, cell_length);
+ }
+ case Type_Null: {
+ json_cell_null(cell, cell_length);
+ }
+ case Type_Object: {
+ JSON_Object child = obj.GetObject(key);
+ json_encode(child, cell, cell_length, pretty_print, depth + 1);
+ }
+ }
+
+ // make the builder fit our key:value
+ // use previously determined cell length and + 1 for ,
+ builder_size = cell_length + 1;
+ if (! is_array) {
+ // get the length of the key and + 1 for :
+ builder_size += json_cell_string_size(strlen(key)) + 1;
+
+ if (pretty_print) {
+ builder_size += strlen(JSON_PP_AFTER_COLON);
+ }
+ }
+
+ char[] builder = new char[builder_size];
+ strcopy(builder, builder_size, "");
+
+ // add the key if we're working with an object
+ if (! is_array) {
+ json_cell_string(key, builder, builder_size);
+ StrCat(builder, builder_size, ":");
+
+ if (pretty_print) {
+ StrCat(builder, builder_size, JSON_PP_AFTER_COLON);
+ }
+ }
+
+ // add the value and a trailing comma
+ StrCat(builder, builder_size, cell);
+ StrCat(builder, builder_size, ",");
+
+ // prepare pretty printing then send builder to output afterwards
+ if (pretty_print) {
+ StrCat(output, max_size, JSON_PP_NEWLINE);
+
+ for (int j = 0; j < depth + 1; ++j) {
+ StrCat(output, max_size, JSON_PP_INDENT);
+ }
+ }
+
+ StrCat(output, max_size, builder);
+
+ is_empty = false;
+ }
+
+ if (snap != null) {
+ delete snap;
+ }
+
+ if (! is_empty) {
+ // remove the final comma
+ output[strlen(output) - 1] = '\0';
+
+ if (pretty_print) {
+ StrCat(output, max_size, JSON_PP_NEWLINE);
+
+ for (int j = 0; j < depth; ++j) {
+ StrCat(output, max_size, JSON_PP_INDENT);
+ }
+ }
+ }
+
+ // append closing bracket
+ StrCat(output, max_size, (is_array) ? "]" : "}");
+}
+
+/**
+ * Decodes a JSON string into a JSON instance.
+ *
+ * @param buffer Buffer to decode.
+ * @param result Object to store output in. Setting this allows loading over
+ * an existing JSON instance, 'refreshing' it as opposed to
+ * creating a new one. [default: null]
+ * @param pos Current position of the decoder as a bytes offset into the buffer.
+ * @param depth Current depth of the decoder as child elements in the object.
+ * @returns JSON instance or null if decoding failed (buffer didn't contain valid JSON).
+ */
+stock JSON_Object json_decode(
+ const char[] buffer,
+ JSON_Object result = null,
+ int &pos = 0,
+ int depth = 0
+)
+{
+ int length = strlen(buffer);
+ bool is_array = false;
+
+ // skip preceding whitespace
+ if (! json_skip_whitespace(buffer, length, pos)) {
+ LogError("json_decode: buffer ended early at position %d", pos);
+
+ return null;
+ }
+
+ if (json_is_object(buffer[pos])) {
+ is_array = false;
+ } else if (json_is_array(buffer[pos])) {
+ is_array = true;
+ } else {
+ LogError("json_decode: character not identified as object or array at position %d", pos);
+
+ return null;
+ }
+
+ if (result == null) {
+ result = new JSON_Object(is_array);
+ }
+
+ bool empty_checked = false;
+ char[] key = new char[length];
+ char[] cell = new char[length];
+
+ // while we haven't reached the end of our structure
+ while (! is_array && ! json_is_object_end(buffer[pos])
+ || is_array && ! json_is_array_end(buffer[pos])) {
+ // pos is either an opening structure or comma, so increment past it
+ ++pos;
+
+ // skip any whitespace preceding the element
+ if (! json_skip_whitespace(buffer, length, pos)) {
+ LogError("json_decode: buffer ended early at position %d", pos);
+
+ return null;
+ }
+
+ // if we are at the end of an object or array
+ // and haven't checked for empty yet, we can stop here (empty structure)
+ if ((! is_array && json_is_object_end(buffer[pos])
+ || is_array && json_is_array_end(buffer[pos]))
+ && ! empty_checked) {
+ break;
+ }
+
+ empty_checked = true;
+
+ // if dealing with an object, look for the key
+ if (! is_array) {
+ if (! json_is_string(buffer[pos])) {
+ LogError("json_decode: expected key string at position %d", pos);
+
+ return null;
+ }
+
+ // extract the key from the buffer
+ json_extract_string(buffer, length, pos, key, length, is_array);
+
+ // skip any whitespace following the key
+ if (! json_skip_whitespace(buffer, length, pos)) {
+ LogError("json_decode: buffer ended early at position %d", pos);
+
+ return null;
+ }
+
+ // ensure that we find a colon
+ if (buffer[pos++] != ':') {
+ LogError("json_decode: expected colon after key at position %d", pos);
+
+ return null;
+ }
+
+ // skip any whitespace following the colon
+ if (! json_skip_whitespace(buffer, length, pos)) {
+ LogError("json_decode: buffer ended early at position %d", pos);
+
+ return null;
+ }
+ }
+
+ if (json_is_object(buffer[pos]) || json_is_array(buffer[pos])) {
+ // if we are dealing with an object or array
+ // fetch the existing object if one exists at the key
+ JSON_Object current = (! is_array) ? result.GetObject(key) : null;
+
+ // decode recursively
+ JSON_Object value = json_decode(buffer, current, pos, depth + 1);
+
+ // decoding failed, error will be logged in json_decode
+ if (value == null) {
+ return null;
+ }
+
+ if (is_array) {
+ result.PushObject(value);
+ } else {
+ result.SetObject(key, value);
+ }
+ } else if (json_is_string(buffer[pos])) {
+ // if we are dealing with a string, attempt to extract it
+ if (! json_extract_string(buffer, length, pos, cell, length, is_array)) {
+ LogError("json_decode: couldn't extract string at position %d", pos);
+
+ return null;
+ }
+
+ if (is_array) {
+ result.PushString(cell);
+ } else {
+ result.SetString(key, cell);
+ }
+ } else {
+ if (! json_extract_until_end(buffer, length, pos, cell, length, is_array)) {
+ LogError("json_decode: couldn't extract until end at position %d", pos);
+
+ return null;
+ }
+
+ if (strlen(cell) == 0) {
+ LogError("json_decode: empty cell encountered at position %d", pos);
+
+ return null;
+ }
+
+ if (json_is_int(cell)) {
+ int value = json_extract_int(cell);
+ if (is_array) {
+ result.PushInt(value);
+ } else {
+ result.SetInt(key, value);
+ }
+ } else if (json_is_float(cell)) {
+ float value = json_extract_float(cell);
+ if (is_array) {
+ result.PushFloat(value);
+ } else {
+ result.SetFloat(key, value);
+ }
+ } else if (json_is_bool(cell)) {
+ bool value = json_extract_bool(cell);
+ if (is_array) {
+ result.PushBool(value);
+ } else {
+ result.SetBool(key, value);
+ }
+ } else if (json_is_null(cell)) {
+ if (is_array) {
+ result.PushHandle(null);
+ } else {
+ result.SetHandle(key, null);
+ }
+ } else {
+ LogError("json_decode: unknown type encountered at position %d: %s", pos, cell);
+
+ return null;
+ }
+ }
+
+ if (! json_skip_whitespace(buffer, length, pos)) {
+ LogError("json_decode: buffer ended early at position %d", pos);
+
+ return null;
+ }
+ }
+
+ // skip remaining whitespace and ensure we're at the end of the buffer
+ ++pos;
+ if (json_skip_whitespace(buffer, length, pos) && depth == 0) {
+ LogError("json_decode: unexpected data after end of structure at position %d", pos);
+
+ return null;
+ }
+
+ return result;
+}
+
+/**
+ * Recursively cleans up a JSON instance and any JSON instances stored within.
+ *
+ * @param obj JSON instance to clean up.
+ */
+stock void json_cleanup(JSON_Object obj)
+{
+ bool is_array = obj.IsArray;
+
+ int key_length = 0;
+ StringMapSnapshot snap = obj.Snapshot();
+ for (int i = 0; i < snap.Length; ++i) {
+ key_length = snap.KeyBufferSize(i);
+ char[] key = new char[key_length];
+
+ // ignore meta keys
+ snap.GetKey(i, key, key_length);
+ if (json_is_meta_key(key)) {
+ continue;
+ }
+
+ // only clean up objects
+ JSON_CELL_TYPE type = obj.GetKeyType(key);
+ if (type != Type_Object) {
+ continue;
+ }
+
+ JSON_Object nested_obj = obj.GetObject(key);
+ if (nested_obj != null) {
+ nested_obj.Cleanup();
+ delete nested_obj;
+ }
+ }
+
+ obj.Clear();
+ delete snap;
+
+ if (is_array) {
+ obj.SetValue(JSON_ARRAY_INDEX_KEY, 0);
+ }
+}
diff --git a/sourcemod/scripting/include/json/decode_helpers.inc b/sourcemod/scripting/include/json/decode_helpers.inc
new file mode 100644
index 0000000..0032cc3
--- /dev/null
+++ b/sourcemod/scripting/include/json/decode_helpers.inc
@@ -0,0 +1,312 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2018 James D. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_decode_helpers_included
+ #endinput
+#endif
+#define _json_decode_helpers_included
+
+#include <string>
+
+/**
+ * @section Analysing format of incoming JSON cells.
+ */
+
+/**
+ * Checks whether the character at the given
+ * position in the buffer is whitespace.
+ *
+ * @param buffer String buffer of data.
+ * @param pos Position to check in buffer.
+ * @return True if buffer[pos] is whitespace, false otherwise.
+ */
+stock bool json_is_whitespace(const char[] buffer, int &pos) {
+ return buffer[pos] == ' ' || buffer[pos] == '\t' ||
+ buffer[pos] == '\r' || buffer[pos] == '\n';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the start of a string.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer[0] is the start of a string, false otherwise.
+ */
+stock bool json_is_string(const char[] buffer) {
+ return buffer[0] == '"';
+}
+
+/**
+ * Checks whether the buffer provided contains an int.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer contains an int, false otherwise.
+ */
+stock bool json_is_int(const char[] buffer) {
+ int length = strlen(buffer);
+ if (buffer[0] != '+' && buffer[0] != '-' && !IsCharNumeric(buffer[0])) {
+ return false;
+ }
+
+ for (int i = 0; i < length; ++i) {
+ if (!IsCharNumeric(buffer[i])) return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks whether the buffer provided contains a float.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer contains a float, false otherwise.
+ */
+stock bool json_is_float(const char[] buffer) {
+ bool has_decimal = false;
+ int length = strlen(buffer);
+ if (buffer[0] != '+' && buffer[0] != '-' && buffer[0] != '.' && !IsCharNumeric(buffer[0])) {
+ return false;
+ }
+
+ for (int i = 0; i < length; ++i) {
+ if (buffer[i] == '.') {
+ if (has_decimal) {
+ return false;
+ }
+
+ has_decimal = true;
+ } else if (!IsCharNumeric(buffer[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Checks whether the buffer provided contains a bool.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer contains a bool, false otherwise.
+ */
+stock bool json_is_bool(const char[] buffer) {
+ return StrEqual(buffer, "true") ||
+ StrEqual(buffer, "false");
+}
+
+/**
+ * Checks whether the buffer provided contains null.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer contains null, false otherwise.
+ */
+stock bool json_is_null(const char[] buffer) {
+ return StrEqual(buffer, "null");
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the start of an object.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer[0] is the start of an object, false otherwise.
+ */
+stock bool json_is_object(const char[] buffer) {
+ return buffer[0] == '{';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the end of an object.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer[0] is the end of an object, false otherwise.
+ */
+stock bool json_is_object_end(const char[] buffer) {
+ return buffer[0] == '}';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the start of an array.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer[0] is the start of an array, false otherwise.
+ */
+stock bool json_is_array(const char[] buffer) {
+ return buffer[0] == '[';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the start of an array.
+ *
+ * @param buffer String buffer of data.
+ * @return True if buffer[0] is the start of an array, false otherwise.
+ */
+stock bool json_is_array_end(const char[] buffer) {
+ return buffer[0] == ']';
+}
+
+/**
+ * Checks whether the character at the given position in the buffer
+ * is considered a valid 'end point' for some data, such as a
+ * colon (indicating a key), a comma (indicating a new element),
+ * or the end of an object or array.
+ *
+ * @param buffer String buffer of data.
+ * @param pos Position to check in buffer.
+ * @return True if buffer[pos] is a valid data end point, false otherwise.
+ */
+stock bool json_is_at_end(const char[] buffer, int &pos, bool is_array) {
+ return buffer[pos] == ',' ||
+ (!is_array && buffer[pos] == ':') ||
+ json_is_object_end(buffer[pos]) ||
+ json_is_array_end(buffer[pos]);
+}
+
+/**
+ * Moves the position until it reaches a non-whitespace
+ * character or the end of the buffer's maximum size.
+ *
+ * @param buffer String buffer of data.
+ * @param maxlen Maximum size of string buffer.
+ * @param pos Position to increment.
+ * @return True if pos is not at the end of the buffer, false otherwise.
+ */
+stock bool json_skip_whitespace(const char[] buffer, int maxlen, int &pos) {
+ while (json_is_whitespace(buffer, pos) && pos < maxlen) {
+ ++pos;
+ }
+
+ return pos < maxlen;
+}
+
+/**
+ * Extracts a JSON cell from the buffer until
+ * a valid end point is reached.
+ *
+ * @param buffer String buffer of data.
+ * @param maxlen Maximum size of string buffer.
+ * @param pos Position to increment.
+ * @param output String buffer to store output.
+ * @param output_maxlen Maximum size of output string buffer.
+ * @param is_array Whether the decoder is currently processing an array.
+ * @return True if pos is not at the end of the buffer, false otherwise.
+ */
+stock bool json_extract_until_end(const char[] buffer, int maxlen, int &pos, char[] output, int output_maxlen, bool is_array) {
+ // extracts a string from current pos until a valid 'end point'
+ strcopy(output, output_maxlen, "");
+
+ int start = pos;
+ while (!json_is_whitespace(buffer, pos) && !json_is_at_end(buffer, pos, is_array) && pos < maxlen) {
+ ++pos;
+ }
+ int end = pos - 1;
+
+ // skip trailing whitespace
+ json_skip_whitespace(buffer, maxlen, pos);
+
+ if (!json_is_at_end(buffer, pos, is_array)) return false;
+ strcopy(output, end - start + 2, buffer[start]);
+
+ return pos < maxlen;
+}
+
+
+/**
+ * Extracts a JSON string from the buffer until
+ * a valid end point is reached.
+ *
+ * @param buffer String buffer of data.
+ * @param maxlen Maximum size of string buffer.
+ * @param pos Position to increment.
+ * @param output String buffer to store output.
+ * @param output_maxlen Maximum size of output string buffer.
+ * @param is_array Whether the decoder is currently processing an array.
+ * @return True if pos is not at the end of the buffer, false otherwise.
+ */
+stock bool json_extract_string(const char[] buffer, int maxlen, int &pos, char[] output, int output_maxlen, bool is_array) {
+ // extracts a string which needs to be quote-escaped
+ strcopy(output, output_maxlen, "");
+
+ ++pos;
+ int start = pos;
+ while (!(buffer[pos] == '"' && buffer[pos - 1] != '\\') && pos < maxlen) {
+ ++pos;
+ }
+ int end = pos - 1;
+
+ // jump 1 ahead since we ended on " instead of an ending char
+ ++pos;
+
+ // skip trailing whitespace
+ json_skip_whitespace(buffer, maxlen, pos);
+
+ if (!json_is_at_end(buffer, pos, is_array)) return false;
+ // copy only from start with length end - start + 2 (+2 for NULL terminator and something else)
+ strcopy(output, end - start + 2, buffer[start]);
+ json_unescape_string(output, maxlen);
+
+ return pos < maxlen;
+}
+
+/**
+ * Extracts an int from the buffer.
+ *
+ * @param buffer String buffer of data.
+ * @return Int value of the buffer.
+ */
+stock int json_extract_int(const char[] buffer) {
+ return StringToInt(buffer);
+}
+
+/**
+ * Extracts a float from the buffer.
+ *
+ * @param buffer String buffer of data.
+ * @return Float value of the buffer.
+ */
+stock float json_extract_float(const char[] buffer) {
+ return StringToFloat(buffer);
+}
+
+/**
+ * Extracts a bool from the buffer.
+ *
+ * @param buffer String buffer of data.
+ * @return Bool value of the buffer.
+ */
+stock bool json_extract_bool(const char[] buffer) {
+ return StrEqual(buffer, "true");
+}
diff --git a/sourcemod/scripting/include/json/definitions.inc b/sourcemod/scripting/include/json/definitions.inc
new file mode 100644
index 0000000..63063d3
--- /dev/null
+++ b/sourcemod/scripting/include/json/definitions.inc
@@ -0,0 +1,103 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2019 James Dickens. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_definitions_included
+ #endinput
+#endif
+#define _json_definitions_included
+
+#include <string>
+#include <json/helpers/string>
+
+/**
+ * @section Pretty Print Constants
+ *
+ * Used to determine how pretty printed JSON should be formatted when encoded.
+ * You can modify these if you prefer your JSON formatted differently.
+ */
+
+#define JSON_PP_AFTER_COLON " "
+#define JSON_PP_INDENT " "
+#define JSON_PP_NEWLINE "\n"
+
+/**
+ * @section Buffer Size Constants
+ *
+ * You may need to change these if you are working with very large arrays or floating point numbers.
+ */
+
+#define JSON_FLOAT_BUFFER_SIZE 32
+#define JSON_INDEX_BUFFER_SIZE 16
+
+/**
+ * @section Meta-key Constants
+ *
+ * Used to store metadata for each key in an object.
+ * You shouldn't need to change these unless working with keys that may clash with them.
+ */
+
+#define JSON_ARRAY_INDEX_KEY "__array_index"
+#define JSON_META_TYPE_KEY ":type"
+#define JSON_META_LENGTH_KEY ":length"
+#define JSON_META_HIDDEN_KEY ":hidden"
+
+/**
+ * @section General
+ */
+
+/**
+ * Types of cells within a JSON object
+ */
+enum JSON_CELL_TYPE {
+ Type_Invalid = -1,
+ Type_String = 0,
+ Type_Int,
+ Type_Float,
+ Type_Bool,
+ Type_Null,
+ Type_Object
+};
+
+/**
+ * Checks whether the key provided is a meta-key that should only be used internally.
+ *
+ * @param key Key to check.
+ * @returns True when it is a meta-key, false otherwise.
+ */
+stock bool json_is_meta_key(char[] key)
+{
+ return json_string_endswith(key, JSON_META_TYPE_KEY)
+ || json_string_endswith(key, JSON_META_LENGTH_KEY)
+ || json_string_endswith(key, JSON_META_HIDDEN_KEY)
+ || StrEqual(key, JSON_ARRAY_INDEX_KEY);
+}
diff --git a/sourcemod/scripting/include/json/encode_helpers.inc b/sourcemod/scripting/include/json/encode_helpers.inc
new file mode 100644
index 0000000..37cb83d
--- /dev/null
+++ b/sourcemod/scripting/include/json/encode_helpers.inc
@@ -0,0 +1,164 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2018 James D. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_encode_helpers_included
+ #endinput
+#endif
+#define _json_encode_helpers_included
+
+#include <string>
+
+/**
+ * @section Calculating buffer sizes for JSON cells.
+ */
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of a string.
+ *
+ * @param maxlen The string's current length or buffer size.
+ * @return Maximum buffer length.
+ */
+stock int json_cell_string_size(int maxlen) {
+ return (maxlen * 2) + 3; // * 2 for potential escaping, + 2 for surrounding quotes + NULL
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of an int.
+ *
+ * @param input The int.
+ * @return Maximum buffer length.
+ */
+stock int json_cell_int_size(int input) {
+ if (input == 0) {
+ return 2; // "0" + NULL
+ }
+
+ return ((input < 0) ? 1 : 0) + RoundToFloor(Logarithm(FloatAbs(float(input)), 10.0)) + 2; // neg sign + number of digits + NULL
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of a float.
+ *
+ * @return Maximum buffer length.
+ */
+stock int json_cell_float_size() {
+ return JSON_FLOAT_BUFFER_SIZE; // fixed-length
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of a bool.
+ *
+ * @return Maximum buffer length.
+ */
+stock int json_cell_bool_size() {
+ return 6; // "true|false" + NULL
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of null.
+ *
+ * @return Maximum buffer length.
+ */
+stock int json_cell_null_size() {
+ return 5; // "null" + NULL
+}
+
+/**
+ * @section Generating JSON cells.
+ */
+
+/**
+ * Generates the JSON cell representation of a string.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param maxlen Maximum size of string buffer.
+ */
+stock void json_cell_string(const char[] input, char[] output, int maxlen) {
+ strcopy(output, maxlen, "_"); // add dummy char at start so first quotation isn't escaped
+ StrCat(output, maxlen, input); // add input string to output
+ // escape everything according to JSON spec
+ json_escape_string(output, maxlen);
+
+ // surround string with quotations
+ output[0] = '"';
+ StrCat(output, maxlen, "\"");
+}
+
+/**
+ * Generates the JSON cell representation of an int.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param maxlen Maximum size of string buffer.
+ */
+stock void json_cell_int(int input, char[] output, int maxlen) {
+ IntToString(input, output, maxlen);
+}
+
+/**
+ * Generates the JSON cell representation of a float.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param maxlen Maximum size of string buffer.
+ */
+stock void json_cell_float(float input, char[] output, int maxlen) {
+ FloatToString(input, output, maxlen);
+}
+
+/**
+ * Generates the JSON cell representation of a bool.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param maxlen Maximum size of string buffer.
+ */
+stock void json_cell_bool(bool input, char[] output, int maxlen) {
+ strcopy(output, maxlen, (input) ? "true" : "false");
+}
+
+/**
+ * Generates the JSON cell representation of null.
+ *
+ * @param output String buffer to store output.
+ * @param maxlen Maximum size of string buffer.
+ */
+stock void json_cell_null(char[] output, int maxlen) {
+ strcopy(output, maxlen, "null");
+}
diff --git a/sourcemod/scripting/include/json/helpers/decode.inc b/sourcemod/scripting/include/json/helpers/decode.inc
new file mode 100644
index 0000000..f420222
--- /dev/null
+++ b/sourcemod/scripting/include/json/helpers/decode.inc
@@ -0,0 +1,502 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2019 James Dickens. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_helpers_decode_included
+ #endinput
+#endif
+#define _json_helpers_decode_included
+
+#include <string>
+
+/**
+ * @section Determine Buffer Contents
+ */
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is whitespace.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if the first character in the buffer
+ * is whitespace, false otherwise.
+ */
+stock bool json_is_whitespace(const char[] buffer)
+{
+ return buffer[0] == ' '
+ || buffer[0] == '\t'
+ || buffer[0] == '\r'
+ || buffer[0] == '\n';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the start of a string.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if the first character in the buffer
+ * is the start of a string, false otherwise.
+ */
+stock bool json_is_string(const char[] buffer)
+{
+ return buffer[0] == '"';
+}
+
+/**
+ * Checks whether the buffer provided contains an int.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if buffer contains an int, false otherwise.
+ */
+stock bool json_is_int(const char[] buffer)
+{
+ bool starts_with_zero = false;
+ bool has_digit_gt_zero = false;
+
+ int length = strlen(buffer);
+ for (int i = 0; i < length; ++i) {
+ // allow minus as first character only
+ if (i == 0 && buffer[i] == '-') {
+ continue;
+ }
+
+ if (IsCharNumeric(buffer[i])) {
+ if (buffer[i] == '0') {
+ if (starts_with_zero) {
+ // detect repeating leading zeros
+ return false;
+ } else if (! has_digit_gt_zero) {
+ starts_with_zero = true;
+ }
+ } else {
+ has_digit_gt_zero = true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // buffer must start with zero and have no other numerics before decimal
+ // OR not start with zero and have other numerics
+ return (starts_with_zero && ! has_digit_gt_zero)
+ || (! starts_with_zero && has_digit_gt_zero);
+}
+
+/**
+ * Checks whether the buffer provided contains a float.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if buffer contains a float, false otherwise.
+ */
+stock bool json_is_float(const char[] buffer)
+{
+ bool starts_with_zero = false;
+ bool has_digit_gt_zero = false;
+ bool after_decimal = false;
+ bool has_digit_after_decimal = false;
+ bool after_exponent = false;
+ bool has_digit_after_exponent = false;
+
+ int length = strlen(buffer);
+ for (int i = 0; i < length; ++i) {
+ // allow minus as first character only
+ if (i == 0 && buffer[i] == '-') {
+ continue;
+ }
+
+ // if we haven't encountered a decimal or exponent yet
+ if (! after_decimal && ! after_exponent) {
+ if (buffer[i] == '.') {
+ // if we encounter a decimal before any digits
+ if (! starts_with_zero && ! has_digit_gt_zero) {
+ return false;
+ }
+
+ after_decimal = true;
+ } else if (buffer[i] == 'e' || buffer[i] == 'E') {
+ // if we encounter an exponent before any non-zero digits
+ if (starts_with_zero && ! has_digit_gt_zero) {
+ return false;
+ }
+
+ after_exponent = true;
+ } else if (IsCharNumeric(buffer[i])) {
+ if (buffer[i] == '0') {
+ if (starts_with_zero) {
+ // detect repeating leading zeros
+ return false;
+ } else if (! has_digit_gt_zero) {
+ starts_with_zero = true;
+ }
+ } else {
+ has_digit_gt_zero = true;
+ }
+ } else {
+ return false;
+ }
+ } else if (after_decimal && ! after_exponent) {
+ // after decimal has been encountered, allow any numerics
+ if (IsCharNumeric(buffer[i])) {
+ has_digit_after_decimal = true;
+ } else if (buffer[i] == 'e' || buffer[i] == 'E') {
+ if (! has_digit_after_decimal) {
+ // detect exponents directly after decimal
+ return false;
+ }
+
+ after_exponent = true;
+ } else {
+ return false;
+ }
+ } else if (after_exponent) {
+ if (
+ (buffer[i] == '+' || buffer[i] == '-')
+ && (buffer[i - 1] == 'e' || buffer[i - 1] == 'E')
+ ) {
+ // allow + or - directly after exponent
+ continue;
+ } else if (IsCharNumeric(buffer[i])) {
+ has_digit_after_exponent = true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ if (starts_with_zero && has_digit_gt_zero) {
+ /* if buffer starts with zero, there should
+ be no other digits before the decimal */
+ return false;
+ }
+
+ // if we have a decimal, there should be digit(s) after it
+ if (after_decimal) {
+ if (! has_digit_after_decimal) {
+ return false;
+ }
+ }
+
+ // if we have an exponent, there should be digit(s) after it
+ if (after_exponent) {
+ if (! has_digit_after_exponent) {
+ return false;
+ }
+ }
+
+ /* we should have reached an exponent, decimal, or both.
+ otherwise, this number can be handled by the int parser */
+ return after_decimal || after_exponent;
+}
+
+/**
+ * Checks whether the buffer provided contains a bool.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if buffer contains a bool, false otherwise.
+ */
+stock bool json_is_bool(const char[] buffer)
+{
+ return StrEqual(buffer, "true") || StrEqual(buffer, "false");
+}
+
+/**
+ * Checks whether the buffer provided contains null.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if buffer contains null, false otherwise.
+ */
+stock bool json_is_null(const char[] buffer)
+{
+ return StrEqual(buffer, "null");
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the start of an object.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if the first character in the buffer is
+ * the start of an object, false otherwise.
+ */
+stock bool json_is_object(const char[] buffer)
+{
+ return buffer[0] == '{';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the end of an object.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if the first character in the buffer is
+ * the end of an object, false otherwise.
+ */
+stock bool json_is_object_end(const char[] buffer)
+{
+ return buffer[0] == '}';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the start of an array.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if the first character in the buffer is
+ * the start of an array, false otherwise.
+ */
+stock bool json_is_array(const char[] buffer)
+{
+ return buffer[0] == '[';
+}
+
+/**
+ * Checks whether the character at the beginning
+ * of the buffer is the end of an array.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if the first character in the buffer is
+ * the end of an array, false otherwise.
+ */
+stock bool json_is_array_end(const char[] buffer)
+{
+ return buffer[0] == ']';
+}
+
+/**
+ * Checks whether the character at the beginning of the buffer
+ * is considered a valid 'end point' for some data, such as a
+ * colon (indicating a key), a comma (indicating a new element),
+ * or the end of an object or array.
+ *
+ * @param buffer String buffer of data.
+ * @returns True if the first character in the buffer
+ * is a valid data end point, false otherwise.
+ */
+stock bool json_is_at_end(const char[] buffer, bool is_array)
+{
+ return buffer[0] == ','
+ || (! is_array && buffer[0] == ':')
+ || json_is_object_end(buffer[0])
+ || json_is_array_end(buffer[0]);
+}
+
+/**
+ * @section Extract Contents from Buffer
+ */
+
+/**
+ * Moves the position until it reaches a non-whitespace
+ * character or the end of the buffer's maximum size.
+ *
+ * @param buffer String buffer of data.
+ * @param max_size Maximum size of string buffer.
+ * @param pos Position to increment.
+ * @returns True if pos has not reached the end
+ * of the buffer, false otherwise.
+ */
+stock bool json_skip_whitespace(const char[] buffer, int max_size, int &pos)
+{
+ while (json_is_whitespace(buffer[pos]) && pos < max_size) {
+ ++pos;
+ }
+
+ return pos < max_size;
+}
+
+/**
+ * Extracts a JSON cell from the buffer until
+ * a valid end point is reached.
+ *
+ * @param buffer String buffer of data.
+ * @param max_size Maximum size of string buffer.
+ * @param pos Position to increment.
+ * @param output String buffer to store output.
+ * @param output_max_size Maximum size of output string buffer.
+ * @param is_array Whether the decoder is processing an array.
+ * @returns True if pos has not reached the end
+ * of the buffer, false otherwise.
+ */
+stock bool json_extract_until_end(
+ const char[] buffer,
+ int max_size,
+ int &pos,
+ char[] output,
+ int output_max_size,
+ bool is_array
+) {
+ strcopy(output, output_max_size, "");
+
+ // set start to position of first character in cell
+ int start = pos;
+
+ // while we haven't hit whitespace, an end point or the end of the buffer
+ while (
+ ! json_is_whitespace(buffer[pos])
+ && ! json_is_at_end(buffer[pos], is_array)
+ && pos < max_size
+ ) {
+ ++pos;
+ }
+
+ // set end to the current position
+ int end = pos;
+
+ // skip any following whitespace
+ json_skip_whitespace(buffer, max_size, pos);
+
+ // if we aren't at a valid endpoint, extraction has failed
+ if (! json_is_at_end(buffer[pos], is_array)) {
+ return false;
+ }
+
+ // copy only from start with length end - start + NULL terminator
+ strcopy(output, end - start + 1, buffer[start]);
+
+ return pos < max_size;
+}
+
+/**
+ * Extracts a JSON string from the buffer until
+ * a valid end point is reached.
+ *
+ * @param buffer String buffer of data.
+ * @param max_size Maximum size of string buffer.
+ * @param pos Position to increment.
+ * @param output String buffer to store output.
+ * @param output_max_size Maximum size of output string buffer.
+ * @param is_array Whether the decoder is processing an array.
+ * @returns True if pos has not reached the end
+ * of the buffer, false otherwise.
+ */
+stock bool json_extract_string(
+ const char[] buffer,
+ int max_size,
+ int &pos,
+ char[] output,
+ int output_max_size,
+ bool is_array
+) {
+ strcopy(output, output_max_size, "");
+
+ // increment past opening quote
+ ++pos;
+
+ // set start to position of first character in string
+ int start = pos;
+
+ // while we haven't hit the end of the buffer
+ while (pos < max_size) {
+ // check for unescaped control characters
+ if (
+ buffer[pos] == '\b'
+ || buffer[pos] == '\f'
+ || buffer[pos] == '\n'
+ || buffer[pos] == '\r'
+ || buffer[pos] == '\t'
+ ) {
+ return false;
+ }
+
+ if (buffer[pos] == '"') {
+ // count preceding backslashes to check if quote is escaped
+ int search_pos = pos;
+ int preceding_backslashes = 0;
+ while (search_pos > 0 && buffer[--search_pos] == '\\') {
+ ++preceding_backslashes;
+ }
+
+ // if we have an even number of backslashes, the quote is not escaped
+ if (preceding_backslashes % 2 == 0) {
+ break;
+ }
+ }
+
+ // pass over the character as it is part of the string
+ ++pos;
+ }
+
+ // set end to the current position
+ int end = pos;
+
+ // increment past closing quote
+ ++pos;
+
+ // skip trailing whitespace
+ if (! json_skip_whitespace(buffer, max_size, pos)) {
+ return false;
+ }
+
+ // if we haven't reached an ending character at the end of the cell,
+ // there is likely junk data not encapsulated by a string
+ if (! json_is_at_end(buffer[pos], is_array)) {
+ return false;
+ }
+
+ // copy only from start with length end - start + NULL terminator
+ strcopy(output, end - start + 1, buffer[start]);
+ json_unescape_string(output, max_size);
+
+ return pos < max_size;
+}
+
+/**
+ * Extracts an int from the buffer.
+ *
+ * @param buffer String buffer of data.
+ * @returns Int value of the buffer.
+ */
+stock int json_extract_int(const char[] buffer)
+{
+ return StringToInt(buffer);
+}
+
+/**
+ * Extracts a float from the buffer.
+ *
+ * @param buffer String buffer of data.
+ * @returns Float value of the buffer.
+ */
+stock float json_extract_float(const char[] buffer)
+{
+ return StringToFloat(buffer);
+}
+
+/**
+ * Extracts a bool from the buffer.
+ *
+ * @param buffer String buffer of data.
+ * @returns Bool value of the buffer.
+ */
+stock bool json_extract_bool(const char[] buffer)
+{
+ return StrEqual(buffer, "true");
+}
diff --git a/sourcemod/scripting/include/json/helpers/encode.inc b/sourcemod/scripting/include/json/helpers/encode.inc
new file mode 100644
index 0000000..ceae54d
--- /dev/null
+++ b/sourcemod/scripting/include/json/helpers/encode.inc
@@ -0,0 +1,200 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2019 James Dickens. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_helpers_encode_included
+ #endinput
+#endif
+#define _json_helpers_encode_included
+
+#include <string>
+
+/**
+ * @section Calculate Buffer Size for Value
+ */
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of a string.
+ *
+ * @param length The length of the string.
+ * @returns Maximum buffer length.
+ */
+stock int json_cell_string_size(int length)
+{
+ // double for potential escaping, + 2 for outside quotes + NULL terminator
+ return (length * 2) + 3;
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of an int.
+ *
+ * @param input Value to calculate maximum buffer length for.
+ * @returns Maximum buffer length.
+ */
+stock int json_cell_int_size(int input)
+{
+ if (input == 0) {
+ // "0" + NULL terminator
+ return 2;
+ }
+
+ int result = 0;
+ if (input < 0) {
+ // negative sign
+ result += 1;
+ }
+
+ // calculate number of digits in number
+ result += RoundToFloor(Logarithm(FloatAbs(float(input)), 10.0)) + 1;
+
+ // NULL terminator
+ result += 1;
+
+ return result;
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of a float.
+ *
+ * @returns Maximum buffer length.
+ */
+stock int json_cell_float_size()
+{
+ return JSON_FLOAT_BUFFER_SIZE;
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of a bool.
+ *
+ * @returns Maximum buffer length.
+ */
+stock int json_cell_bool_size()
+{
+ // "true"|"false" + NULL terminator
+ return 6;
+}
+
+/**
+ * Calculates the maximum buffer length required to
+ * store the JSON cell representation of null.
+ *
+ * @returns Maximum buffer length.
+ */
+stock int json_cell_null_size()
+{
+ // "null" + NULL terminator
+ return 5;
+}
+
+/**
+ * @section Convert Values to JSON Cells
+ */
+
+/**
+ * Generates the JSON cell representation of a string.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ */
+stock void json_cell_string(const char[] input, char[] output, int max_size)
+{
+ // add dummy char that won't be escaped to replace with a quote later
+ strcopy(output, max_size, "?");
+
+ // add input string to output
+ StrCat(output, max_size, input);
+
+ // escape the output string
+ json_escape_string(output, max_size);
+
+ // surround string with quotations
+ output[0] = '"';
+ StrCat(output, max_size, "\"");
+}
+
+/**
+ * Generates the JSON cell representation of an int.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ */
+stock void json_cell_int(int input, char[] output, int max_size)
+{
+ IntToString(input, output, max_size);
+}
+
+/**
+ * Generates the JSON cell representation of a float.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ */
+stock void json_cell_float(float input, char[] output, int max_size)
+{
+ FloatToString(input, output, max_size);
+
+ // trim trailing 0s from float output up until decimal point
+ int last_char = strlen(output) - 1;
+ while (output[last_char] == '0' && output[last_char - 1] != '.') {
+ output[last_char--] = '\0';
+ }
+}
+
+/**
+ * Generates the JSON cell representation of a bool.
+ *
+ * @param input Value to generate output for.
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ */
+stock void json_cell_bool(bool input, char[] output, int max_size)
+{
+ strcopy(output, max_size, (input) ? "true" : "false");
+}
+
+/**
+ * Generates the JSON cell representation of null.
+ *
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ */
+stock void json_cell_null(char[] output, int max_size)
+{
+ strcopy(output, max_size, "null");
+}
diff --git a/sourcemod/scripting/include/json/helpers/string.inc b/sourcemod/scripting/include/json/helpers/string.inc
new file mode 100644
index 0000000..14fe38a
--- /dev/null
+++ b/sourcemod/scripting/include/json/helpers/string.inc
@@ -0,0 +1,133 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2019 James Dickens. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_helpers_string_included
+ #endinput
+#endif
+#define _json_helpers_string_included
+
+/**
+ * Mapping characters to their escaped form.
+ */
+char JSON_STRING_NORMAL[][] = {
+ "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t"
+};
+char JSON_STRING_ESCAPED[][] = {
+ "\\\\", "\\\"", "\\/", "\\b", "\\f", "\\n", "\\r", "\\t"
+};
+
+/**
+ * Escapes a string in-place in a buffer.
+ *
+ * @param buffer String buffer.
+ * @param max_size Maximum size of string buffer.
+ */
+stock void json_escape_string(char[] buffer, int max_size)
+{
+ for (int i = 0; i < sizeof(JSON_STRING_NORMAL); ++i) {
+ ReplaceString(
+ buffer,
+ max_size,
+ JSON_STRING_NORMAL[i],
+ JSON_STRING_ESCAPED[i]
+ );
+ }
+}
+
+/**
+ * Unescapes a string in-place in a buffer.
+ *
+ * @param buffer String buffer.
+ * @param max_size Maximum size of string buffer.
+ */
+stock void json_unescape_string(char[] buffer, int max_size)
+{
+ for (int i = 0; i < sizeof(JSON_STRING_NORMAL); ++i) {
+ ReplaceString(
+ buffer,
+ max_size,
+ JSON_STRING_ESCAPED[i],
+ JSON_STRING_NORMAL[i]
+ );
+ }
+}
+
+/**
+ * Checks if a string starts with another string.
+ *
+ * @param haystack String to check that starts with needle.
+ * @param max_size Maximum size of string buffer.
+ * @param needle String to check that haystack starts with.
+ * @returns True if haystack begins with needle, false otherwise.
+ */
+stock bool json_string_startswith(const char[] haystack, const char[] needle)
+{
+ int haystack_length = strlen(haystack);
+ int needle_length = strlen(needle);
+ if (needle_length > haystack_length) {
+ return false;
+ }
+
+ for (int i = 0; i < needle_length; ++i) {
+ if (haystack[i] != needle[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Checks if a string ends with another string.
+ *
+ * @param haystack String to check that ends with needle.
+ * @param max_size Maximum size of string buffer.
+ * @param needle String to check that haystack ends with.
+ * @returns True if haystack ends with needle, false otherwise.
+ */
+stock bool json_string_endswith(const char[] haystack, const char[] needle)
+{
+ int haystack_length = strlen(haystack);
+ int needle_length = strlen(needle);
+ if (needle_length > haystack_length) {
+ return false;
+ }
+
+ for (int i = 0; i < needle_length; ++i) {
+ if (haystack[haystack_length - needle_length + i] != needle[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/sourcemod/scripting/include/json/object.inc b/sourcemod/scripting/include/json/object.inc
new file mode 100644
index 0000000..8d17568
--- /dev/null
+++ b/sourcemod/scripting/include/json/object.inc
@@ -0,0 +1,1014 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2019 James Dickens. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_object_included
+ #endinput
+#endif
+#define _json_object_included
+
+#include <string>
+#include <json/definitions>
+#include <json/helpers/encode>
+#include <json>
+
+methodmap JSON_Object < StringMap
+{
+ /**
+ * Creates a new JSON_Object.
+ *
+ * @param is_array Should the object created be an array? [default: false]
+ * @returns A new JSON_Object.
+ */
+ public JSON_Object(bool is_array = false)
+ {
+ StringMap self = CreateTrie();
+ if (is_array) {
+ self.SetValue(JSON_ARRAY_INDEX_KEY, 0);
+ }
+
+ return view_as<JSON_Object>(self);
+ }
+
+ /**
+ * Checks whether the object has a key.
+ *
+ * @param key Key to check existence of.
+ * @returns True if the key exists, false otherwise.
+ */
+ public bool HasKey(const char[] key)
+ {
+ int dummy_int;
+ char dummy_str[1];
+
+ return this.GetValue(key, dummy_int)
+ || this.GetString(key, dummy_str, sizeof(dummy_str));
+ }
+
+ /**
+ * @section Array helpers.
+ */
+
+ /**
+ * Whether the current object is an array.
+ */
+ property bool IsArray {
+ public get()
+ {
+ return this.HasKey(JSON_ARRAY_INDEX_KEY);
+ }
+ }
+
+ /**
+ * The current index of the object if it is an array, or -1 otherwise.
+ */
+ property int CurrentIndex {
+ public get()
+ {
+ if (! this.IsArray) {
+ return -1;
+ }
+
+ int result;
+ return (this.GetValue(JSON_ARRAY_INDEX_KEY, result)) ? result : -1;
+ }
+
+ public set(int value)
+ {
+ this.SetValue(JSON_ARRAY_INDEX_KEY, value);
+ }
+ }
+
+ /**
+ * The number of items in the object if it is an array,
+ * or the number of keys (including meta-keys) otherwise.
+ */
+ property int Length {
+ public get()
+ {
+ StringMapSnapshot snap = this.Snapshot();
+ int length = (this.IsArray) ? this.CurrentIndex : snap.Length;
+ delete snap;
+
+ return length;
+ }
+ }
+
+ /**
+ * Increments the current index of the object.
+ *
+ * @returns True on success, false if the current object is not an array.
+ */
+ public bool IncrementIndex()
+ {
+ if (! this.HasKey(JSON_ARRAY_INDEX_KEY)) {
+ return false;
+ }
+
+ this.CurrentIndex += 1;
+
+ return true;
+ }
+
+ /**
+ * Checks whether the object has an index.
+ *
+ * @param index Index to check existence of.
+ * @returns True if the index exists, false otherwise.
+ */
+ public bool HasIndex(int index)
+ {
+ return index >= 0 && index < this.Length;
+ }
+
+ /**
+ * Gets the string representation of an array index.
+ *
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ * @param key Key to get string for. [default: current index]
+ * @returns True on success, false otherwise.
+ */
+ public int GetIndexString(char[] output, int max_size, int key = -1)
+ {
+ key = (key == -1) ? this.CurrentIndex : key;
+ if (key == -1) {
+ return false;
+ }
+
+ return IntToString(key, output, max_size);
+ }
+
+ /**
+ * @section Internal Getters
+ */
+
+ /**
+ * Gets the cell type stored at a key.
+ *
+ * @param key Key to get value type for.
+ * @returns Value type for key provided,
+ * or Type_Invalid if it does not exist.
+ */
+ public JSON_CELL_TYPE GetKeyType(const char[] key)
+ {
+ int max_size = strlen(key) + strlen(JSON_META_TYPE_KEY) + 1;
+ char[] type_key = new char[max_size];
+ Format(type_key, max_size, "%s%s", key, JSON_META_TYPE_KEY);
+
+ JSON_CELL_TYPE type;
+ return (this.GetValue(type_key, type)) ? type : Type_Invalid;
+ }
+
+ /**
+ * Gets the cell type stored at an index.
+ *
+ * @param index Index to get value type for.
+ * @returns Value type for index provided, or Type_Invalid if it does not exist.
+ */
+ public JSON_CELL_TYPE GetKeyTypeIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return Type_Invalid;
+ }
+
+ return this.GetKeyType(key);
+ }
+
+ /**
+ * Gets the length of the string stored at a key.
+ *
+ * @param key Key to get string length for.
+ * @returns Length of string at key provided,
+ * or -1 if it is not a string/does not exist.
+ */
+ public int GetKeyLength(const char[] key)
+ {
+ int max_size = strlen(key) + strlen(JSON_META_LENGTH_KEY) + 1;
+ char[] length_key = new char[max_size];
+ Format(length_key, max_size, "%s%s", key, JSON_META_LENGTH_KEY);
+
+ int length;
+ return (this.GetValue(length_key, length)) ? length : -1;
+ }
+
+ /**
+ * Gets the length of the string stored at an index.
+ *
+ * @param index Index to get string length for.
+ * @returns Length of string at index provided, or -1 if it is not a string/does not exist.
+ */
+ public int GetKeyLengthIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return -1;
+ }
+
+ return this.GetKeyLength(key);
+ }
+
+ /**
+ * Gets whether the key should be hidden from encoding.
+ *
+ * @param key Key to get hidden state for.
+ * @returns Whether or not the key should be hidden.
+ */
+ public bool GetKeyHidden(const char[] key)
+ {
+ int max_size = strlen(key) + strlen(JSON_META_HIDDEN_KEY) + 1;
+ char[] length_key = new char[max_size];
+ Format(length_key, max_size, "%s%s", key, JSON_META_HIDDEN_KEY);
+
+ bool hidden;
+ return (this.GetValue(length_key, hidden)) ? hidden : false;
+ }
+
+ /**
+ * Gets whether the index should be hidden from encoding.
+ *
+ * @param index Index to get hidden state for.
+ * @returns Whether or not the index should be hidden.
+ */
+ public bool GetKeyHiddenIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.GetKeyHidden(key);
+ }
+
+ /**
+ * @section Internal Setters
+ */
+
+ /**
+ * Sets the cell type stored at a key.
+ *
+ * @param key Key to set value type for.
+ * @param type Type to set key to.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetKeyType(const char[] key, JSON_CELL_TYPE type)
+ {
+ int max_size = strlen(key) + strlen(JSON_META_TYPE_KEY) + 1;
+ char[] type_key = new char[max_size];
+ Format(type_key, max_size, "%s%s", key, JSON_META_TYPE_KEY);
+
+ return this.SetValue(type_key, type);
+ }
+
+ /**
+ * Sets the cell type stored at an index.
+ *
+ * @param index Index to set value type for.
+ * @param type Type to set index to.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetKeyTypeIndexed(int index, JSON_CELL_TYPE value)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetKeyType(key, value);
+ }
+
+ /**
+ * Sets the length of the string stored at a key.
+ *
+ * @param key Key to set string length for.
+ * @param length Length to set string to.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetKeyLength(const char[] key, int length)
+ {
+ int max_size = strlen(key) + strlen(JSON_META_LENGTH_KEY) + 1;
+ char[] length_key = new char[max_size];
+ Format(length_key, max_size, "%s%s", key, JSON_META_LENGTH_KEY);
+
+ return this.SetValue(length_key, length);
+ }
+
+ /**
+ * Sets the length of the string stored at an index.
+ *
+ * @param index Index to set string length for.
+ * @param length Length to set string to.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetKeyLengthIndexed(int index, int length)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetKeyLength(key, length);
+ }
+
+ /**
+ * Sets whether the key should be hidden from encoding.
+ *
+ * @param key Key to set hidden state for.
+ * @param hidden Wheter or not the key should be hidden.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetKeyHidden(const char[] key, bool hidden)
+ {
+ int max_size = strlen(key) + strlen(JSON_META_HIDDEN_KEY) + 1;
+ char[] hidden_key = new char[max_size];
+ Format(hidden_key, max_size, "%s%s", key, JSON_META_HIDDEN_KEY);
+
+ return this.SetValue(hidden_key, hidden);
+ }
+
+ /**
+ * Sets whether the index should be hidden from encoding.
+ *
+ * @param index Index to set hidden state for.
+ * @param hidden Wheter or not the index should be hidden.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetKeyHiddenIndexed(int index, bool hidden)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetKeyHidden(key, hidden);
+ }
+
+ /**
+ * @section Getters
+ */
+
+ // GetValue is implemented natively by StringMap
+
+ /**
+ * Retrieves the value stored at an index.
+ *
+ * @param index Index to retrieve value for.
+ * @param value Variable to store value.
+ * @returns Value stored at index.
+ */
+ public bool GetValueIndexed(int index, any &value)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.GetValue(key, value);
+ }
+
+ // GetString is implemented natively by StringMap
+
+ /**
+ * Retrieves the string stored at an index.
+ *
+ * @param index Index to retrieve string value for.
+ * @param value String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ * @returns True on success. False if the key is not set, or the key is set as a value or array (not a string).
+ */
+ public bool GetStringIndexed(int index, char[] value, int max_size)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.GetString(key, value, max_size);
+ }
+
+ /**
+ * Retrieves the int stored at a key.
+ *
+ * @param key Key to retrieve int value for.
+ * @returns Value stored at key.
+ */
+ public int GetInt(const char[] key)
+ {
+ int value;
+ return (this.GetValue(key, value)) ? value : -1;
+ }
+
+ /**
+ * Retrieves the int stored at an index.
+ *
+ * @param index Index to retrieve int value for.
+ * @returns Value stored at index.
+ */
+ public int GetIntIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return -1;
+ }
+
+ return this.GetInt(key);
+ }
+
+ /**
+ * Retrieves the float stored at a key.
+ *
+ * @param key Key to retrieve float value for.
+ * @returns Value stored at key.
+ */
+ public float GetFloat(const char[] key)
+ {
+ float value;
+ return (this.GetValue(key, value)) ? value : -1.0;
+ }
+
+ /**
+ * Retrieves the float stored at an index.
+ *
+ * @param index Index to retrieve float value for.
+ * @returns Value stored at index.
+ */
+ public float GetFloatIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return -1.0;
+ }
+
+ return this.GetFloat(key);
+ }
+
+ /**
+ * Retrieves the bool stored at a key.
+ *
+ * @param key Key to retrieve bool value for.
+ * @returns Value stored at key.
+ */
+ public bool GetBool(const char[] key)
+ {
+ bool value;
+ return (this.GetValue(key, value)) ? value : false;
+ }
+
+ /**
+ * Retrieves the bool stored at an index.
+ *
+ * @param index Index to retrieve bool value for.
+ * @returns Value stored at index.
+ */
+ public bool GetBoolIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.GetBool(key);
+ }
+
+ /**
+ * Retrieves the handle stored at a key.
+ *
+ * @param key Key to retrieve handle value for.
+ * @returns Value stored at key.
+ */
+ public Handle GetHandle(const char[] key)
+ {
+ Handle value;
+ return (this.GetValue(key, value)) ? value : null;
+ }
+
+ /**
+ * Retrieves the handle stored at an index.
+ *
+ * @param index Index to retrieve handle value for.
+ * @returns Value stored at index.
+ */
+ public Handle GetHandleIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return null;
+ }
+
+ return this.GetHandle(key);
+ }
+
+ /**
+ * Retrieves the JSON object stored at a key.
+ *
+ * @param key Key to retrieve object value for.
+ * @returns Value stored at key.
+ */
+ public JSON_Object GetObject(const char[] key)
+ {
+ return view_as<JSON_Object>(this.GetHandle(key));
+ }
+
+ /**
+ * Retrieves the object stored at an index.
+ *
+ * @param index Index to retrieve object value for.
+ * @returns Value stored at index.
+ */
+ public JSON_Object GetObjectIndexed(int index)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return null;
+ }
+
+ return this.GetObject(key);
+ }
+
+ /**
+ * @section Setters
+ */
+
+ /**
+ * Sets the string stored at a key.
+ *
+ * @param key Key to set to string value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetString(const char[] key, const char[] value, bool replace = true)
+ {
+ return this.SetString(key, value, replace)
+ && this.SetKeyType(key, Type_String)
+ && this.SetKeyLength(key, strlen(value));
+ }
+
+ /**
+ * Sets the string stored at an index.
+ *
+ * @param index Index to set to string value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetStringIndexed(int index, const char[] value, bool replace = true)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetString(key, value, replace);
+ }
+
+ /**
+ * Sets the int stored at a key.
+ *
+ * @param key Key to set to int value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetInt(const char[] key, int value, bool replace = true)
+ {
+ return this.SetValue(key, value, replace)
+ && this.SetKeyType(key, Type_Int);
+ }
+
+ /**
+ * Sets the int stored at an index.
+ *
+ * @param index Index to set to int value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetIntIndexed(int index, int value, bool replace = true)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetInt(key, value, replace);
+ }
+
+ /**
+ * Sets the float stored at a key.
+ *
+ * @param key Key to set to float value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetFloat(const char[] key, float value, bool replace = true)
+ {
+ return this.SetValue(key, value, replace)
+ && this.SetKeyType(key, Type_Float);
+ }
+
+ /**
+ * Sets the float stored at an index.
+ *
+ * @param index Index to set to float value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetFloatIndexed(int index, float value, bool replace = true)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetFloat(key, value, replace);
+ }
+
+ /**
+ * Sets the bool stored at a key.
+ *
+ * @param key Key to set to bool value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetBool(const char[] key, bool value, bool replace = true)
+ {
+ return this.SetValue(key, value, replace)
+ && this.SetKeyType(key, Type_Bool);
+ }
+
+ /**
+ * Sets the bool stored at an index.
+ *
+ * @param index Index to set to bool value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetBoolIndexed(int index, bool value, bool replace = true)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetBool(key, value, replace);
+ }
+
+ /**
+ * Sets the handle stored at a key.
+ *
+ * @param key Key to set to handle value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetHandle(const char[] key, Handle value = null, bool replace = true)
+ {
+ return this.SetValue(key, value, replace)
+ && this.SetKeyType(key, Type_Null);
+ }
+
+ /**
+ * Sets the handle stored at an index.
+ *
+ * @param index Index to set to handle value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetHandleIndexed(int index, Handle value = null, bool replace = true)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetHandle(key, value, replace);
+ }
+
+ /**
+ * Sets the object stored at a key.
+ *
+ * @param key Key to set to object value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetObject(const char[] key, JSON_Object value, bool replace = true)
+ {
+ return this.SetValue(key, value, replace)
+ && this.SetKeyType(key, Type_Object);
+ }
+
+ /**
+ * Sets the object stored at an index.
+ *
+ * @param index Index to set to object value.
+ * @param value Value to set.
+ * @returns True on success, false otherwise.
+ */
+ public bool SetObjectIndexed(int index, JSON_Object value, bool replace = true)
+ {
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ return this.SetObject(key, value, replace);
+ }
+
+ /**
+ * @section Array setters.
+ */
+
+ /**
+ * Pushes a string to the end of the array.
+ *
+ * @param value Value to push.
+ * @returns True on success, false otherwise.
+ */
+ public bool PushString(const char[] value)
+ {
+ return this.SetStringIndexed(this.CurrentIndex, value)
+ && this.IncrementIndex();
+ }
+
+ /**
+ * Pushes an int to the end of the array.
+ *
+ * @param value Value to push.
+ * @returns True on success, false otherwise.
+ */
+ public bool PushInt(int value)
+ {
+ return this.SetIntIndexed(this.CurrentIndex, value)
+ && this.IncrementIndex();
+ }
+
+ /**
+ * Pushes a float to the end of the array.
+ *
+ * @param value Value to push.
+ * @returns True on success, false otherwise.
+ */
+ public bool PushFloat(float value)
+ {
+ return this.SetFloatIndexed(this.CurrentIndex, value)
+ && this.IncrementIndex();
+ }
+
+ /**
+ * Pushes a bool to the end of the array.
+ *
+ * @param value Value to push.
+ * @returns True on success, false otherwise.
+ */
+ public bool PushBool(bool value)
+ {
+ return this.SetBoolIndexed(this.CurrentIndex, value)
+ && this.IncrementIndex();
+ }
+
+ /**
+ * Pushes a handle to the end of the array.
+ *
+ * @param value Value to push.
+ * @returns True on success, false otherwise.
+ */
+ public bool PushHandle(Handle value = null)
+ {
+ return this.SetHandleIndexed(this.CurrentIndex, value)
+ && this.IncrementIndex();
+ }
+
+ /**
+ * Pushes an object to the end of the array.
+ *
+ * @param value Value to push.
+ * @returns True on success, false otherwise.
+ */
+ public bool PushObject(JSON_Object value)
+ {
+ return this.SetObjectIndexed(this.CurrentIndex, value)
+ && this.IncrementIndex();
+ }
+
+ /**
+ * @section Generic.
+ */
+
+ /**
+ * Finds the index of a value in the array.
+ *
+ * @param value Value to search for.
+ * @returns The index of the value if it is found, -1 otherwise.
+ */
+ public int IndexOf(any value)
+ {
+ any current;
+ for (int i = 0; i < this.CurrentIndex; ++i) {
+ if (this.GetValueIndexed(i, current) && value == current) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Finds the index of a string in the array.
+ *
+ * @param value String to search for.
+ * @returns The index of the string if it is found, -1 otherwise.
+ */
+ public int IndexOfString(const char[] value)
+ {
+ for (int i = 0; i < this.CurrentIndex; ++i) {
+ if (this.GetKeyTypeIndexed(i) != Type_String) {
+ continue;
+ }
+
+ int current_size = this.GetKeyLengthIndexed(i) + 1;
+ char[] current = new char[current_size];
+ this.GetStringIndexed(i, current, current_size);
+ if (StrEqual(value, current)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Determines whether the array contains a value.
+ *
+ * @param value Value to search for.
+ * @returns True if the value is found, false otherwise.
+ */
+ public bool Contains(any value)
+ {
+ return this.IndexOf(value) != -1;
+ }
+
+ /**
+ * Determines whether the array contains a string.
+ *
+ * @param value String to search for.
+ * @returns True if the string is found, false otherwise.
+ */
+ public bool ContainsString(const char[] value)
+ {
+ return this.IndexOfString(value) != -1;
+ }
+
+ /**
+ * Removes an item from the object by key.
+ *
+ * @param key Key of object to remove.
+ * @returns True on success, false if the value was never set.
+ */
+ public bool Remove(const char[] key) {
+ static char meta_keys[][] = {
+ JSON_META_TYPE_KEY, JSON_META_LENGTH_KEY, JSON_META_HIDDEN_KEY
+ };
+
+ // create a new char[] which will fit the longest meta-key
+ int meta_key_size = strlen(key) + 8;
+ char[] meta_key = new char[meta_key_size];
+
+ // view ourselves as a StringMap so we can call underlying Remove() method
+ StringMap self = view_as<StringMap>(this);
+
+ bool success = true;
+ for (int i = 0; i < sizeof(meta_keys); ++i) {
+ Format(meta_key, meta_key_size, "%s%s", key, meta_keys[i]);
+
+ if (this.HasKey(meta_key)) {
+ success = success && self.Remove(meta_key);
+ }
+ }
+
+ return success && self.Remove(key);
+ }
+
+ /**
+ * Removes a key and its related meta-keys from the object.
+ *
+ * @param key Key to remove.
+ * @returns True on success, false if the value was never set.
+ */
+ public bool RemoveIndexed(int index)
+ {
+ if (! this.HasIndex(index)) {
+ return false;
+ }
+
+ char key[JSON_INDEX_BUFFER_SIZE];
+ if (! this.GetIndexString(key, sizeof(key), index)) {
+ return false;
+ }
+
+ if (! this.Remove(key)) {
+ return false;
+ }
+
+ for (int i = index + 1; i < this.CurrentIndex; ++i) {
+ if (! this.GetIndexString(key, sizeof(key), i)) {
+ return false;
+ }
+
+ int target = i - 1;
+
+ JSON_CELL_TYPE type = this.GetKeyTypeIndexed(i);
+
+ switch (type) {
+ case Type_String: {
+ int str_length = this.GetKeyLengthIndexed(i);
+ char[] str_value = new char[str_length];
+
+ this.GetStringIndexed(i, str_value, str_length + 1);
+ this.SetStringIndexed(target, str_value);
+ }
+ case Type_Int: {
+ this.SetIntIndexed(target, this.GetIntIndexed(i));
+ }
+ case Type_Float: {
+ this.SetFloatIndexed(target, this.GetFloatIndexed(i));
+ }
+ case Type_Bool: {
+ this.SetBoolIndexed(target, this.GetBoolIndexed(i));
+ }
+ case Type_Null: {
+ this.SetHandleIndexed(target, this.GetHandleIndexed(i));
+ }
+ case Type_Object: {
+ this.SetObjectIndexed(target, this.GetObjectIndexed(i));
+ }
+ }
+
+ if (this.GetKeyHiddenIndexed(i)) {
+ this.SetKeyHiddenIndexed(target, true);
+ }
+
+ this.Remove(key);
+ }
+
+ this.CurrentIndex -= 1;
+
+ return true;
+ }
+
+ /**
+ * Encodes the instance into its string representation.
+ *
+ * @param output String buffer to store output.
+ * @param max_size Maximum size of string buffer.
+ * @param pretty_print Should the output be pretty printed (newlines and spaces)? [default: false]
+ * @param depth The current depth of the encoder. [default: 0]
+ */
+ public void Encode(char[] output, int max_size, bool pretty_print = false, int depth = 0)
+ {
+ json_encode(this, output, max_size, pretty_print, depth);
+ }
+
+ /**
+ * Decodes a JSON string into this object.
+ *
+ * @param buffer Buffer to decode.
+ */
+ public void Decode(const char[] buffer)
+ {
+ json_decode(buffer, this);
+ }
+
+ /**
+ * Recursively cleans up the object and any objects referenced within.
+ */
+ public void Cleanup()
+ {
+ json_cleanup(this);
+ }
+};
diff --git a/sourcemod/scripting/include/json/string_helpers.inc b/sourcemod/scripting/include/json/string_helpers.inc
new file mode 100644
index 0000000..6063d47
--- /dev/null
+++ b/sourcemod/scripting/include/json/string_helpers.inc
@@ -0,0 +1,77 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * sm-json
+ * Provides a pure SourcePawn implementation of JSON encoding and decoding.
+ * https://github.com/clugg/sm-json
+ *
+ * sm-json (C)2018 James D. (clug)
+ * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation. You must obey the GNU General Public License in
+ * all respects for all other code used. Additionally, AlliedModders LLC grants
+ * this exception to all derivative works. AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ */
+
+#if defined _json_string_helpers_included
+ #endinput
+#endif
+#define _json_string_helpers_included
+
+/**
+ * Checks if a string starts with another string.
+ *
+ * @param haystack String to check that starts with needle.
+ * @param maxlen Maximum size of string buffer.
+ * @param needle String to check that haystack starts with.
+ * @return True if haystack begins with needle, false otherwise.
+ */
+stock bool json_string_startswith(const char[] haystack, const char[] needle) {
+ int haystack_length = strlen(haystack);
+ int needle_length = strlen(needle);
+ if (needle_length > haystack_length) return false;
+
+ for (int i = 0; i < needle_length; ++i) {
+ if (haystack[i] != needle[i]) return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks if a string ends with another string.
+ *
+ * @param haystack String to check that ends with needle.
+ * @param maxlen Maximum size of string buffer.
+ * @param needle String to check that haystack ends with.
+ * @return True if haystack ends with needle, false otherwise.
+ */
+stock bool json_string_endswith(const char[] haystack, const char[] needle) {
+ int haystack_length = strlen(haystack);
+ int needle_length = strlen(needle);
+ if (needle_length > haystack_length) return false;
+
+ for (int i = 0; i < needle_length; ++i) {
+ if (haystack[haystack_length - needle_length + i] != needle[i]) return false;
+ }
+
+ return true;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/movement.inc b/sourcemod/scripting/include/movement.inc
new file mode 100644
index 0000000..7cc5b29
--- /dev/null
+++ b/sourcemod/scripting/include/movement.inc
@@ -0,0 +1,530 @@
+/*
+ MovementAPI Function Stock Library
+
+ Website: https://github.com/danzayau/MovementAPI
+*/
+
+#if defined _movement_included_
+ #endinput
+#endif
+#define _movement_included_
+
+#include <sdktools>
+
+
+
+// =====[ STOCKS ]=====
+
+/**
+ * Calculates the horizontal distance between two vectors.
+ *
+ * @param vec1 First vector.
+ * @param vec2 Second vector.
+ * @return Vector horizontal distance.
+ */
+stock float GetVectorHorizontalDistance(const float vec1[3], const float vec2[3])
+{
+ return SquareRoot(Pow(vec2[0] - vec1[0], 2.0) + Pow(vec2[1] - vec1[1], 2.0));
+}
+
+/**
+ * Calculates a vector's horizontal length.
+ *
+ * @param vec Vector.
+ * @return Vector horizontal length (magnitude).
+ */
+stock float GetVectorHorizontalLength(const float vec[3])
+{
+ return SquareRoot(Pow(vec[0], 2.0) + Pow(vec[1], 2.0));
+}
+
+/**
+ * Scales a vector to a certain horizontal length.
+ *
+ * @param vec Vector.
+ * @param length New horizontal length.
+ */
+stock void SetVectorHorizontalLength(float vec[3], float length)
+{
+ float newVec[3];
+ newVec = vec;
+ newVec[2] = 0.0;
+ NormalizeVector(newVec, newVec);
+ ScaleVector(newVec, length);
+ newVec[2] = vec[2];
+ vec = newVec;
+}
+
+/**
+ * Gets a player's currently pressed buttons.
+ *
+ * @param client Client index.
+ * @return Bitsum of buttons.
+ */
+stock int Movement_GetButtons(int client)
+{
+ return GetClientButtons(client);
+}
+
+/**
+ * Gets a player's origin.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+stock void Movement_GetOrigin(int client, float result[3])
+{
+ GetClientAbsOrigin(client, result);
+}
+
+/**
+ * Gets a player's origin.
+ * If the player is on the ground, a trace hull is used to find the
+ * exact height of the ground the player is standing on. This is thus
+ * more accurate than Movement_GetOrigin when player is on ground.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+stock void Movement_GetOriginEx(int client, float result[3])
+{
+ if (!Movement_GetOnGround(client))
+ {
+ GetClientAbsOrigin(client, result);
+ return;
+ }
+
+ // Get the coordinate of the solid beneath the player's origin
+ // More accurate than GetClientAbsOrigin when on ground
+ float startPosition[3], endPosition[3];
+ GetClientAbsOrigin(client, startPosition);
+ endPosition = startPosition;
+ endPosition[2] = startPosition[2] - 2.0; // Should be less than 2.0 units away
+ Handle trace = TR_TraceHullFilterEx(
+ startPosition,
+ endPosition,
+ view_as<float>( { -16.0, -16.0, 0.0 } ), // Players are 32 x 32 x 72
+ view_as<float>( { 16.0, 16.0, 72.0 } ),
+ MASK_PLAYERSOLID,
+ TraceEntityFilterPlayers,
+ client);
+ if (TR_DidHit(trace))
+ {
+ TR_GetEndPosition(result, trace);
+ // Do not get rid of the offset. The offset is correct, as the player must be
+ // at least 0.03125 units away from the ground.
+ }
+ else
+ {
+ result = startPosition; // Fallback to GetClientAbsOrigin
+ }
+ delete trace;
+}
+
+public bool TraceEntityFilterPlayers(int entity, int contentsMask)
+{
+ return entity > MaxClients;
+}
+
+/**
+ * Sets a player's origin by teleporting them.
+ *
+ * @param client Client index.
+ * @param origin New origin.
+ */
+stock void Movement_SetOrigin(int client, const float origin[3])
+{
+ TeleportEntity(client, origin, NULL_VECTOR, NULL_VECTOR);
+}
+
+/**
+ * Gets a player's velocity.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+stock void Movement_GetVelocity(int client, float result[3])
+{
+ GetEntPropVector(client, Prop_Data, "m_vecVelocity", result);
+}
+
+/**
+ * Sets a player's velocity by teleporting them.
+ *
+ * @param client Client index.
+ * @param velocity New velocity.
+ */
+stock void Movement_SetVelocity(int client, const float velocity[3])
+{
+ TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, velocity);
+}
+
+/**
+ * Gets a player's horizontal speed.
+ *
+ * @param client Client index.
+ * @return Player's horizontal speed.
+ */
+stock float Movement_GetSpeed(int client)
+{
+ float velocity[3];
+ Movement_GetVelocity(client, velocity);
+ return GetVectorHorizontalLength(velocity);
+}
+
+/**
+ * Sets a player's horizontal speed.
+ *
+ * @param client Client index.
+ * @param value New horizontal speed.
+ * @param applyBaseVel Whether to apply base velocity as well.
+ */
+stock void Movement_SetSpeed(int client, float value, bool applyBaseVel = false)
+{
+ float velocity[3];
+ Movement_GetVelocity(client, velocity);
+ SetVectorHorizontalLength(velocity, value)
+ if (applyBaseVel)
+ {
+ float baseVelocity[3];
+ Movement_GetBaseVelocity(client, baseVelocity);
+ AddVectors(velocity, baseVelocity, velocity);
+ }
+ Movement_SetVelocity(client, velocity);
+}
+
+/**
+ * Gets a player's vertical velocity.
+ *
+ * @param client Client index.
+ * @return Player's vertical velocity.
+ */
+stock float Movement_GetVerticalVelocity(int client)
+{
+ float velocity[3];
+ Movement_GetVelocity(client, velocity);
+ return velocity[2];
+}
+
+/**
+ * Sets a player's vertical velocity.
+ *
+ * @param client Client index.
+ * @param value New vertical velocity.
+ */
+stock void Movement_SetVerticalVelocity(int client, float value)
+{
+ float velocity[3];
+ Movement_GetVelocity(client, velocity);
+ velocity[2] = value;
+ Movement_SetVelocity(client, velocity);
+}
+
+/**
+ * Gets a player's base velocity.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+stock void Movement_GetBaseVelocity(int client, float result[3])
+{
+ GetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", result);
+}
+
+/**
+ * Sets a player's base velocity.
+ *
+ * @param client Client index.
+ * @param baseVelocity New base velocity.
+ */
+stock void Movement_SetBaseVelocity(int client, const float baseVelocity[3])
+{
+ SetEntPropVector(client, Prop_Data, "m_vecBaseVelocity", baseVelocity);
+}
+
+/**
+ * Gets a player's eye angles.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+stock void Movement_GetEyeAngles(int client, float result[3])
+{
+ GetClientEyeAngles(client, result);
+}
+
+/**
+ * Sets a player's eye angles by teleporting them.
+ *
+ * @param client Client index.
+ * @param eyeAngles New eye angles.
+ */
+stock void Movement_SetEyeAngles(int client, const float eyeAngles[3])
+{
+ TeleportEntity(client, NULL_VECTOR, eyeAngles, NULL_VECTOR);
+}
+
+/**
+ * Gets whether a player is on the ground.
+ *
+ * @param client Client index.
+ * @return Whether player is on the ground.
+ */
+stock bool Movement_GetOnGround(int client)
+{
+ return view_as<bool>(GetEntityFlags(client) & FL_ONGROUND);
+}
+
+/**
+ * Gets whether a player is ducking or ducked.
+ *
+ * @param client Client index.
+ * @return Whether player is ducking or ducked.
+ */
+stock bool Movement_GetDucking(int client)
+{
+ return GetEntProp(client, Prop_Send, "m_bDucked") || GetEntProp(client, Prop_Send, "m_bDucking");
+}
+
+/**
+ * Gets a player's "m_flDuckSpeed" value.
+ *
+ * @param client Client index.
+ * @return Value of "m_flDuckSpeed".
+ */
+stock float Movement_GetDuckSpeed(int client)
+{
+ return GetEntPropFloat(client, Prop_Send, "m_flDuckSpeed");
+}
+
+/**
+ * Sets a player's "m_flDuckSpeed" value.
+ *
+ * @param client Client index.
+ * @param value New "m_flDuckSpeed" value.
+ */
+stock void Movement_SetDuckSpeed(int client, float value)
+{
+ SetEntPropFloat(client, Prop_Send, "m_flDuckSpeed", value);
+}
+
+/**
+ * Gets a player's "m_flVelocityModifier" value.
+ *
+ * @param client Client index.
+ * @return Value of "m_flVelocityModifier".
+ */
+stock float Movement_GetVelocityModifier(int client)
+{
+ return GetEntPropFloat(client, Prop_Send, "m_flVelocityModifier");
+}
+
+/**
+ * Sets a player's "m_flVelocityModifier" value.
+ *
+ * @param client Client index.
+ * @param value New "m_flVelocityModifier" value.
+ */
+stock void Movement_SetVelocityModifier(int client, float value)
+{
+ SetEntPropFloat(client, Prop_Send, "m_flVelocityModifier", value);
+}
+
+/**
+ * Gets a player's gravity scale factor.
+ *
+ * @param client Client index.
+ * @return Gravity scale factor.
+ */
+stock float Movement_GetGravity(int client)
+{
+ return GetEntityGravity(client);
+}
+
+/**
+ * Sets a player's gravity scale factor.
+ *
+ * @param client Client index.
+ * @param value Desired gravity scale factor.
+ */
+stock void Movement_SetGravity(int client, float value)
+{
+ SetEntityGravity(client, value);
+}
+
+/**
+ * Gets a player's movetype.
+ *
+ * @param client Client index.
+ * @return Player's movetype.
+ */
+stock MoveType Movement_GetMovetype(int client)
+{
+ return GetEntityMoveType(client);
+}
+
+/**
+ * Sets a player's movetype.
+ *
+ * @param client Client index.
+ * @param movetype New movetype.
+ */
+stock void Movement_SetMovetype(int client, MoveType movetype)
+{
+ SetEntityMoveType(client, movetype);
+}
+
+/**
+ * Gets whether a player is on a ladder.
+ *
+ * @param client Client index.
+ * @return Whether player is on a ladder.
+ */
+stock bool Movement_GetOnLadder(int client)
+{
+ return GetEntityMoveType(client) == MOVETYPE_LADDER;
+}
+
+/**
+ * Gets whether a player is noclipping.
+ *
+ * @param client Client index.
+ * @return Whether player is noclipping.
+ */
+stock bool Movement_GetNoclipping(int client)
+{
+ return GetEntityMoveType(client) == MOVETYPE_NOCLIP;
+}
+
+
+
+// =====[ METHODMAP ]=====
+
+methodmap MovementPlayer {
+
+ public MovementPlayer(int client) {
+ return view_as<MovementPlayer>(client);
+ }
+
+ property int ID {
+ public get() {
+ return view_as<int>(this);
+ }
+ }
+
+ property int Buttons {
+ public get() {
+ return Movement_GetButtons(this.ID);
+ }
+ }
+
+ public void GetOrigin(float result[3]) {
+ Movement_GetOrigin(this.ID, result);
+ }
+
+ public void SetOrigin(const float origin[3]) {
+ Movement_SetOrigin(this.ID, origin);
+ }
+
+ public void GetVelocity(float result[3]) {
+ Movement_GetVelocity(this.ID, result);
+ }
+
+ public void SetVelocity(const float velocity[3]) {
+ Movement_SetVelocity(this.ID, velocity);
+ }
+
+ property float Speed {
+ public get() {
+ return Movement_GetSpeed(this.ID);
+ }
+ public set(float value) {
+ Movement_SetSpeed(this.ID, value);
+ }
+ }
+
+ property float VerticalVelocity {
+ public get() {
+ return Movement_GetVerticalVelocity(this.ID);
+ }
+ public set(float value) {
+ Movement_SetVerticalVelocity(this.ID, value);
+ }
+ }
+
+ public void GetBaseVelocity(float result[3]) {
+ Movement_GetBaseVelocity(this.ID, result);
+ }
+
+ public void SetBaseVelocity(const float baseVelocity[3]) {
+ Movement_SetBaseVelocity(this.ID, baseVelocity);
+ }
+
+ public void GetEyeAngles(float result[3]) {
+ Movement_GetEyeAngles(this.ID, result);
+ }
+
+ public void SetEyeAngles(const float eyeAngles[3]) {
+ Movement_SetEyeAngles(this.ID, eyeAngles);
+ }
+
+ property bool OnGround {
+ public get() {
+ return Movement_GetOnGround(this.ID);
+ }
+ }
+
+ property bool Ducking {
+ public get() {
+ return Movement_GetDucking(this.ID);
+ }
+ }
+
+ property float DuckSpeed {
+ public get() {
+ return Movement_GetDuckSpeed(this.ID);
+ }
+ public set(float value) {
+ Movement_SetDuckSpeed(this.ID, value);
+ }
+ }
+
+ property float VelocityModifier {
+ public get() {
+ return Movement_GetVelocityModifier(this.ID);
+ }
+ public set(float value) {
+ Movement_SetVelocityModifier(this.ID, value);
+ }
+ }
+
+ property float Gravity {
+ public get() {
+ return Movement_GetGravity(this.ID);
+ }
+ public set(float value) {
+ Movement_SetGravity(this.ID, value);
+ }
+ }
+
+ property MoveType Movetype {
+ public get() {
+ return Movement_GetMovetype(this.ID);
+ }
+ public set(MoveType movetype) {
+ Movement_SetMovetype(this.ID, movetype);
+ }
+ }
+
+ property bool OnLadder {
+ public get() {
+ return Movement_GetOnLadder(this.ID);
+ }
+ }
+
+ property bool Noclipping {
+ public get() {
+ return Movement_GetNoclipping(this.ID);
+ }
+ }
+}
diff --git a/sourcemod/scripting/include/movementapi.inc b/sourcemod/scripting/include/movementapi.inc
new file mode 100644
index 0000000..290c3f2
--- /dev/null
+++ b/sourcemod/scripting/include/movementapi.inc
@@ -0,0 +1,663 @@
+/*
+ MovementAPI Plugin Include
+
+ Website: https://github.com/danzayau/MovementAPI
+*/
+
+#if defined _movementapi_included_
+ #endinput
+#endif
+#define _movementapi_included_
+
+#include <movement>
+
+
+
+/*
+ Terminology
+
+ Takeoff
+ Becoming airborne, including jumping, falling, getting off a ladder and leaving noclip.
+
+ Landing
+ Leaving the air, including landing on the ground, grabbing a ladder and entering noclip.
+
+ Perfect Bunnyhop (Perf)
+ When the player has jumped in the tick after landing and keeps their speed.
+
+ Duckbug/Crouchbug
+ When the player sucessfully lands due to uncrouching from mid air and not by falling
+ down. This causes no stamina loss or fall damage upon landing.
+
+ Jumpbug
+ This is achieved by duckbugging and jumping at the same time. The player is never seen
+ as 'on ground' when bunnyhopping from a tick by tick perspective. A jumpbug inherits
+ the same behavior as a duckbug/crouchbug, along with its effects such as maintaining
+ speed due to no stamina loss.
+
+ Distbug
+ Landing behavior varies depending on whether the player lands close to the edge of a
+ block or not:
+
+ 1. If the player lands close to the edge of a block, this causes the jump duration to
+ be one tick longer and the player can "slide" on the ground during the landing tick,
+ using the position post-tick as landing position becomes inaccurate.
+
+ 2. On the other hand, if the player does not land close to the edge, the player will
+ be considered on the ground one tick earlier, using this position as landing position
+ is not accurate as the player has yet to be fully on the ground.
+
+ In scenario 1, GetNobugLandingOrigin calculates the correct landing position of the
+ player before the sliding effect takes effect.
+
+ In scenario 2, GetNobugLandingOrigin attempts to extrapolate the player's fully on
+ ground position to make landing positions consistent across scenarios.
+*/
+
+
+
+// =====[ FORWARDS ]=====
+
+/**
+ * Called when a player's movetype changes.
+ *
+ * @param client Client index.
+ * @param oldMovetype Player's old movetype.
+ * @param newMovetype Player's new movetype.
+ */
+forward void Movement_OnChangeMovetype(int client, MoveType oldMovetype, MoveType newMovetype);
+
+/**
+ * Called when a player touches the ground.
+ *
+ * @param client Client index.
+ */
+forward void Movement_OnStartTouchGround(int client);
+
+/**
+ * Called when a player leaves the ground.
+ *
+ * @param client Client index.
+ * @param jumped Whether player jumped to leave ground.
+ * @param ladderJump Whether player jumped from a ladder.
+ * @param jumpbug Whether player performed a jumpbug.
+ */
+forward void Movement_OnStopTouchGround(int client, bool jumped, bool ladderJump, bool jumpbug);
+
+/**
+ * Called when a player starts ducking.
+ *
+ * @param client Client index.
+ */
+forward void Movement_OnStartDucking(int client);
+
+/**
+ * Called when a player stops ducking.
+ *
+ * @param client Client index.
+ */
+forward void Movement_OnStopDucking(int client);
+
+/**
+ * Called when a player jumps (player_jump event), including 'jumpbugs'.
+ * Setting velocity when this is called may not be effective.
+ *
+ * @param client Client index.
+ * @param jumpbug Whether player 'jumpbugged'.
+ */
+forward void Movement_OnPlayerJump(int client, bool jumpbug);
+
+/**
+ * Called before PlayerMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnPlayerMovePre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after PlayerMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnPlayerMovePost(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called before Duck movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnDuckPre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after Duck movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnDuckPost(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called before LadderMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnLadderMovePre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after LadderMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnLadderMovePost(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called before FullLadderMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnFullLadderMovePre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after FullLadderMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnFullLadderMovePost(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after the player jumps, but before jumping stamina is applied and takeoff variables are not set yet.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnJumpPre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after the player jumps and after jumping stamina is applied and takeoff variables are already set here.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnJumpPost(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called before AirAccelerate movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnAirAcceleratePre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after AirAccelerate movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnAirAcceleratePost(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called before WalkMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnWalkMovePre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after WalkMove movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnWalkMovePost(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called before CategorizePosition movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnCategorizePositionPre(int client, float origin[3], float velocity[3]);
+
+/**
+ * Called after CategorizePosition movement function is called.
+ * Modifying origin or velocity parameters will change player's origin and velocity accordingly.
+ *
+ * @param client Client index.
+ * @param origin Player origin.
+ * @param velocity Player velocity.
+ * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise.
+ */
+forward Action Movement_OnCategorizePositionPost(int client, float origin[3], float velocity[3]);
+
+// =====[ NATIVES ]=====
+
+/**
+ * Gets whether a player's last takeoff was a jump.
+ *
+ * @param client Client index.
+ * @return Whether player's last takeoff was a jump.
+ */
+native bool Movement_GetJumped(int client);
+
+/**
+ * Gets whether a player's last takeoff was a perfect bunnyhop.
+ *
+ * @param client Client index.
+ * @return Whether player's last takeoff was a perfect bunnyhop.
+ */
+native bool Movement_GetHitPerf(int client);
+
+/**
+ * Gets a player's origin at the time of their last takeoff.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+native void Movement_GetTakeoffOrigin(int client, float result[3]);
+
+/**
+ * Gets a player's velocity at the time of their last takeoff.
+ *
+ * If sv_enablebunnyhopping is 0, CS:GO may adjust the player's
+ * velocity after the takeoff velocity has already been measured.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+native void Movement_GetTakeoffVelocity(int client, float result[3]);
+
+/**
+ * Gets a player's horizontal speed at the time of their last takeoff.
+ *
+ * If sv_enablebunnyhopping is 0, CS:GO may adjust the player's
+ * velocity after the takeoff velocity has already been measured.
+ *
+ * @param client Client index.
+ * @return Player's last takeoff speed.
+ */
+native float Movement_GetTakeoffSpeed(int client);
+
+/**
+ * Gets a player's 'tickcount' at the time of their last takeoff.
+ *
+ * @param client Client index.
+ * @return Player's last takeoff 'tickcount'.
+ */
+native int Movement_GetTakeoffTick(int client);
+
+/**
+ * Gets a player's 'cmdnum' at the time of their last takeoff.
+ *
+ * @param client Client index.
+ * @return Player's last takeoff 'cmdnum'.
+ */
+native int Movement_GetTakeoffCmdNum(int client);
+
+/**
+ * Gets a player's origin at the time of their last landing with the distbug fixed.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+native void Movement_GetNobugLandingOrigin(int client, float result[3]);
+
+/**
+ * Gets a player's origin at the time of their last landing.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+native void Movement_GetLandingOrigin(int client, float result[3]);
+
+/**
+ * Gets a player's velocity at the time of their last landing.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+native void Movement_GetLandingVelocity(int client, float result[3]);
+
+/**
+ * Gets a player's horizontal speed at the time of their last landing.
+ *
+ * @param client Client index.
+ * @return Last landing speed of the player (horizontal).
+ */
+native float Movement_GetLandingSpeed(int client);
+
+/**
+ * Gets a player's 'tickcount' at the time of their last landing.
+ *
+ * @param client Client index.
+ * @return Player's last landing 'tickcount'.
+ */
+native int Movement_GetLandingTick(int client);
+
+/**
+ * Gets a player's 'cmdnum' at the time of their last landing.
+ *
+ * @param client Client index.
+ * @return Player's last landing 'cmdnum'.
+ */
+native int Movement_GetLandingCmdNum(int client);
+
+/**
+ * Gets whether a player is turning their aim horizontally.
+ *
+ * @param client Client index.
+ * @return Whether player is turning their aim horizontally.
+ */
+native bool Movement_GetTurning(int client);
+
+/**
+ * Gets whether a player is turning their aim left.
+ *
+ * @param client Client index.
+ * @return Whether player is turning their aim left.
+ */
+native bool Movement_GetTurningLeft(int client);
+
+/**
+ * Gets whether a player is turning their aim right.
+ *
+ * @param client Client index.
+ * @return Whether player is turning their aim right.
+ */
+native bool Movement_GetTurningRight(int client);
+
+/**
+ * Gets result of CCSPlayer::GetPlayerMaxSpeed(client), which
+ * is the player's max speed as limited by their weapon.
+ *
+ * @param client Client index.
+ * @return Player's max speed as limited by their weapon.
+ */
+native float Movement_GetMaxSpeed(int client);
+
+/**
+ * Gets whether a player duckbugged on this tick.
+ *
+ * @param client Client index.
+ * @return Whether a player duckbugged on this tick.
+ */
+native bool Movement_GetDuckbugged(int client);
+
+/**
+ * Gets whether a player jumpbugged on this tick.
+ *
+ * @param client Client index.
+ * @return Whether a player jumpbugged on this tick.
+ */
+native bool Movement_GetJumpbugged(int client);
+
+/**
+ * Get the player's origin during movement processing.
+ *
+ * @param client Client index.
+ * @param result Resultant vector.
+ */
+native void Movement_GetProcessingOrigin(int client, float result[3]);
+
+/**
+ * Get the player's velocity during movement processing.
+ *
+ * @param client Param description
+ * @param result Resultant vector.
+ */
+native void Movement_GetProcessingVelocity(int client, float result[3]);
+
+/**
+ * Set the player's takeoff origin.
+ *
+ * @param client Client index.
+ * @param origin Desired origin.
+ */
+native void Movement_SetTakeoffOrigin(int client, float origin[3]);
+
+/**
+ * Set the player's takeoff velocity.
+ *
+ * @param client Client index.
+ * @param origin Desired velocity.
+ */
+native void Movement_SetTakeoffVelocity(int client, float velocity[3]);
+
+/**
+ * Set the player's landing origin.
+ *
+ * @param client Client index.
+ * @param origin Desired origin.
+ */
+native void Movement_SetLandingOrigin(int client, float origin[3]);
+
+/**
+ * Set the player's landing velocity.
+ *
+ * @param client Client index.
+ * @param origin Desired velocity.
+ */
+native void Movement_SetLandingVelocity(int client, float velocity[3]);
+
+// =====[ METHODMAP ]=====
+
+methodmap MovementAPIPlayer < MovementPlayer {
+
+ public MovementAPIPlayer(int client) {
+ return view_as<MovementAPIPlayer>(MovementPlayer(client));
+ }
+
+ property bool Jumped {
+ public get() {
+ return Movement_GetJumped(this.ID);
+ }
+ }
+
+ property bool HitPerf {
+ public get() {
+ return Movement_GetHitPerf(this.ID);
+ }
+ }
+
+ public void GetTakeoffOrigin(float buffer[3]) {
+ Movement_GetTakeoffOrigin(this.ID, buffer);
+ }
+
+ public void GetTakeoffVelocity(float buffer[3]) {
+ Movement_GetTakeoffVelocity(this.ID, buffer);
+ }
+
+ public void SetTakeoffOrigin(float buffer[3])
+ {
+ Movement_SetTakeoffOrigin(this.ID, buffer);
+ }
+
+ public void SetTakeoffVelocity(float buffer[3])
+ {
+ Movement_SetTakeoffVelocity(this.ID, buffer);
+ }
+
+ property float TakeoffSpeed {
+ public get() {
+ return Movement_GetTakeoffSpeed(this.ID);
+ }
+ }
+
+ property int TakeoffTick {
+ public get() {
+ return Movement_GetTakeoffTick(this.ID);
+ }
+ }
+
+ property int TakeoffCmdNum {
+ public get() {
+ return Movement_GetTakeoffCmdNum(this.ID);
+ }
+ }
+
+ public void GetLandingOrigin(float buffer[3]) {
+ Movement_GetLandingOrigin(this.ID, buffer);
+ }
+
+ public void GetLandingVelocity(float buffer[3]) {
+ Movement_GetLandingVelocity(this.ID, buffer);
+ }
+
+ public void SetLandingOrigin(float buffer[3])
+ {
+ Movement_SetLandingOrigin(this.ID, buffer);
+ }
+
+ public void SetLandingVelocity(float buffer[3])
+ {
+ Movement_SetLandingVelocity(this.ID, buffer);
+ }
+
+ property float LandingSpeed {
+ public get() {
+ return Movement_GetLandingSpeed(this.ID);
+ }
+ }
+
+ property int LandingTick {
+ public get() {
+ return Movement_GetLandingTick(this.ID);
+ }
+ }
+
+ property int LandingCmdNum {
+ public get() {
+ return Movement_GetLandingCmdNum(this.ID);
+ }
+ }
+
+ property bool Turning {
+ public get() {
+ return Movement_GetTurning(this.ID);
+ }
+ }
+
+ property bool TurningLeft {
+ public get() {
+ return Movement_GetTurningLeft(this.ID);
+ }
+ }
+
+ property bool TurningRight {
+ public get() {
+ return Movement_GetTurningRight(this.ID);
+ }
+ }
+
+ property float MaxSpeed {
+ public get() {
+ return Movement_GetMaxSpeed(this.ID);
+ }
+ }
+
+ public void GetProcessingVelocity(float buffer[3])
+ {
+ Movement_GetProcessingVelocity(this.ID, buffer);
+ }
+
+ public void GetProcessingOrigin(float buffer[3])
+ {
+ Movement_GetProcessingOrigin(this.ID, buffer);
+ }
+}
+
+
+
+// =====[ DEPENDENCY ]=====
+
+public SharedPlugin __pl_movementapi =
+{
+ name = "movementapi",
+ file = "movementapi.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1,
+ #else
+ required = 0,
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_movementapi_SetNTVOptional()
+{
+ MarkNativeAsOptional("Movement_GetJumped");
+ MarkNativeAsOptional("Movement_GetHitPerf");
+ MarkNativeAsOptional("Movement_GetTakeoffOrigin");
+ MarkNativeAsOptional("Movement_GetTakeoffVelocity");
+ MarkNativeAsOptional("Movement_GetTakeoffSpeed");
+ MarkNativeAsOptional("Movement_GetTakeoffTick");
+ MarkNativeAsOptional("Movement_GetTakeoffCmdNum");
+ MarkNativeAsOptional("Movement_GetLandingOrigin");
+ MarkNativeAsOptional("Movement_GetLandingVelocity");
+ MarkNativeAsOptional("Movement_GetLandingSpeed");
+ MarkNativeAsOptional("Movement_GetLandingTick");
+ MarkNativeAsOptional("Movement_GetLandingCmdNum");
+ MarkNativeAsOptional("Movement_GetTurning");
+ MarkNativeAsOptional("Movement_GetTurningLeft");
+ MarkNativeAsOptional("Movement_GetTurningRight");
+ MarkNativeAsOptional("Movement_GetMaxSpeed");
+ MarkNativeAsOptional("Movement_GetProcessingOrigin");
+ MarkNativeAsOptional("Movement_GetProcessingVelocity");
+ MarkNativeAsOptional("Movement_SetTakeoffOrigin");
+ MarkNativeAsOptional("Movement_SetTakeoffVelocity");
+ MarkNativeAsOptional("Movement_SetLandingOrigin");
+ MarkNativeAsOptional("Movement_SetLandingVelocity");
+}
+#endif \ No newline at end of file
diff --git a/sourcemod/scripting/include/smjansson.inc b/sourcemod/scripting/include/smjansson.inc
new file mode 100644
index 0000000..029a492
--- /dev/null
+++ b/sourcemod/scripting/include/smjansson.inc
@@ -0,0 +1,1328 @@
+#if defined _jansson_included_
+ #endinput
+#endif
+#define _jansson_included_
+
+
+/**
+ * --- Type
+ *
+ * The JSON specification (RFC 4627) defines the following data types:
+ * object, array, string, number, boolean, and null.
+ * JSON types are used dynamically; arrays and objects can hold any
+ * other data type, including themselves. For this reason, Jansson�s
+ * type system is also dynamic in nature. There�s one Handle type to
+ * represent all JSON values, and the referenced structure knows the
+ * type of the JSON value it holds.
+ *
+ */
+enum json_type {
+ JSON_OBJECT,
+ JSON_ARRAY,
+ JSON_STRING,
+ JSON_INTEGER,
+ JSON_REAL,
+ JSON_TRUE,
+ JSON_FALSE,
+ JSON_NULL
+}
+
+/**
+ * Return the type of the JSON value.
+ *
+ * @param hObj Handle to the JSON value
+ *
+ * @return json_type of the value.
+ */
+native json_type:json_typeof(Handle:hObj);
+
+/**
+ * The type of a JSON value is queried and tested using these macros
+ *
+ * @param %1 Handle to the JSON value
+ *
+ * @return True if the value has the correct type.
+ */
+#define json_is_object(%1) ( json_typeof(%1) == JSON_OBJECT )
+#define json_is_array(%1) ( json_typeof(%1) == JSON_ARRAY )
+#define json_is_string(%1) ( json_typeof(%1) == JSON_STRING )
+#define json_is_integer(%1) ( json_typeof(%1) == JSON_INTEGER )
+#define json_is_real(%1) ( json_typeof(%1) == JSON_REAL )
+#define json_is_true(%1) ( json_typeof(%1) == JSON_TRUE )
+#define json_is_false(%1) ( json_typeof(%1) == JSON_FALSE )
+#define json_is_null(%1) ( json_typeof(%1) == JSON_NULL )
+#define json_is_number(%1) ( json_typeof(%1) == JSON_INTEGER || json_typeof(%1) == JSON_REAL )
+#define json_is_boolean(%1) ( json_typeof(%1) == JSON_TRUE || json_typeof(%1) == JSON_FALSE )
+
+/**
+ * Saves json_type as a String in output
+ *
+ * @param input json_type value to convert to string
+ * @param output Buffer to store the json_type value
+ * @param maxlength Maximum length of string buffer.
+ *
+ * @return False if the type does not exist.
+ */
+stock bool:Stringify_json_type(json_type:input, String:output[], maxlength) {
+ switch(input) {
+ case JSON_OBJECT: strcopy(output, maxlength, "Object");
+ case JSON_ARRAY: strcopy(output, maxlength, "Array");
+ case JSON_STRING: strcopy(output, maxlength, "String");
+ case JSON_INTEGER: strcopy(output, maxlength, "Integer");
+ case JSON_REAL: strcopy(output, maxlength, "Real");
+ case JSON_TRUE: strcopy(output, maxlength, "True");
+ case JSON_FALSE: strcopy(output, maxlength, "False");
+ case JSON_NULL: strcopy(output, maxlength, "Null");
+ default: return false;
+ }
+
+ return true;
+}
+
+
+
+/**
+ * --- Equality
+ *
+ * - Two integer or real values are equal if their contained numeric
+ * values are equal. An integer value is never equal to a real value,
+ * though.
+ * - Two strings are equal if their contained UTF-8 strings are equal,
+ * byte by byte. Unicode comparison algorithms are not implemented.
+ * - Two arrays are equal if they have the same number of elements and
+ * each element in the first array is equal to the corresponding
+ * element in the second array.
+ * - Two objects are equal if they have exactly the same keys and the
+ * value for each key in the first object is equal to the value of
+ * the corresponding key in the second object.
+ * - Two true, false or null values have no "contents", so they are
+ * equal if their types are equal.
+ *
+ */
+
+/**
+ * Test whether two JSON values are equal.
+ *
+ * @param hObj Handle to the first JSON object
+ * @param hOther Handle to the second JSON object
+ *
+ * @return Returns false if they are inequal or one
+ * or both of the pointers are NULL.
+ */
+native bool:json_equal(Handle:hObj, Handle:hOther);
+
+
+
+
+/**
+ * --- Copying
+ *
+ * Jansson supports two kinds of copying: shallow and deep. There is
+ * a difference between these methods only for arrays and objects.
+ *
+ * Shallow copying only copies the first level value (array or object)
+ * and uses the same child values in the copied value.
+ *
+ * Deep copying makes a fresh copy of the child values, too. Moreover,
+ * all the child values are deep copied in a recursive fashion.
+ *
+ */
+
+/**
+ * Get a shallow copy of the passed object
+ *
+ * @param hObj Handle to JSON object to be copied
+ *
+ * @return Returns a shallow copy of the object,
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_copy(Handle:hObj);
+
+/**
+ * Get a deep copy of the passed object
+ *
+ * @param hObj Handle to JSON object to be copied
+ *
+ * @return Returns a deep copy of the object,
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_deep_copy(Handle:hObj);
+
+
+
+
+/**
+ * --- Objects
+ *
+ * A JSON object is a dictionary of key-value pairs, where the
+ * key is a Unicode string and the value is any JSON value.
+ *
+ */
+
+/**
+ * Returns a handle to a new JSON object, or INVALID_HANDLE on error.
+ * Initially, the object is empty.
+ *
+ * @return Handle to a new JSON object.
+ */
+native Handle:json_object();
+
+/**
+ * Returns the number of elements in hObj
+ *
+ * @param hObj Handle to JSON object
+ *
+ * @return Number of elements in hObj,
+ * or 0 if hObj is not a JSON object.
+ */
+native json_object_size(Handle:hObj);
+
+/**
+ * Get a value corresponding to sKey from hObj
+ *
+ * @param hObj Handle to JSON object to get a value from
+ * @param sKey Key to retrieve
+ *
+ * @return Handle to a the JSON object or
+ * INVALID_HANDLE on error.
+ */
+native Handle:json_object_get(Handle:hObj, const String:sKey[]);
+
+/**
+ * Set the value of sKey to hValue in hObj.
+ * If there already is a value for key, it is replaced by the new value.
+ *
+ * @param hObj Handle to JSON object to set a value on
+ * @param sKey Key to store in the object
+ * Must be a valid null terminated UTF-8 encoded
+ * Unicode string.
+ * @param hValue Value to store in the object
+ *
+ * @return True on success.
+ */
+native bool:json_object_set(Handle:hObj, const String:sKey[], Handle:hValue);
+
+/**
+ * Set the value of sKey to hValue in hObj.
+ * If there already is a value for key, it is replaced by the new value.
+ * This function automatically closes the Handle to the value object.
+ *
+ * @param hObj Handle to JSON object to set a value on
+ * @param sKey Key to store in the object
+ * Must be a valid null terminated UTF-8 encoded
+ * Unicode string.
+ * @param hValue Value to store in the object
+ *
+ * @return True on success.
+ */
+native bool:json_object_set_new(Handle:hObj, const String:sKey[], Handle:hValue);
+
+/**
+ * Delete sKey from hObj if it exists.
+ *
+ * @param hObj Handle to JSON object to delete a key from
+ * @param sKey Key to delete
+ *
+ * @return True on success.
+ */
+native bool:json_object_del(Handle:hObj, const String:sKey[]);
+
+/**
+ * Remove all elements from hObj.
+ *
+ * @param hObj Handle to JSON object to remove all
+ * elements from.
+ *
+ * @return True on success.
+ */
+native bool:json_object_clear(Handle:hObj);
+
+/**
+ * Update hObj with the key-value pairs from hOther, overwriting
+ * existing keys.
+ *
+ * @param hObj Handle to JSON object to update
+ * @param hOther Handle to JSON object to get update
+ * keys/values from.
+ *
+ * @return True on success.
+ */
+native bool:json_object_update(Handle:hObj, Handle:hOther);
+
+/**
+ * Like json_object_update(), but only the values of existing keys
+ * are updated. No new keys are created.
+ *
+ * @param hObj Handle to JSON object to update
+ * @param hOther Handle to JSON object to get update
+ * keys/values from.
+ *
+ * @return True on success.
+ */
+native bool:json_object_update_existing(Handle:hObj, Handle:hOther);
+
+/**
+ * Like json_object_update(), but only new keys are created.
+ * The value of any existing key is not changed.
+ *
+ * @param hObj Handle to JSON object to update
+ * @param hOther Handle to JSON object to get update
+ * keys/values from.
+ *
+ * @return True on success.
+ */
+native bool:json_object_update_missing(Handle:hObj, Handle:hOther);
+
+
+
+
+/**
+ * Object iteration
+ *
+ * Example code:
+ * - We assume hObj is a Handle to a valid JSON object.
+ *
+ *
+ * new Handle:hIterator = json_object_iter(hObj);
+ * while(hIterator != INVALID_HANDLE)
+ * {
+ * new String:sKey[128];
+ * json_object_iter_key(hIterator, sKey, sizeof(sKey));
+ *
+ * new Handle:hValue = json_object_iter_value(hIterator);
+ *
+ * // Do something with sKey and hValue
+ *
+ * CloseHandle(hValue);
+ *
+ * hIterator = json_object_iter_next(hObj, hIterator);
+ * }
+ *
+ */
+
+/**
+ * Returns a handle to an iterator which can be used to iterate over
+ * all key-value pairs in hObj.
+ * If you are not iterating to the end of hObj make sure to close the
+ * handle to the iterator manually.
+ *
+ * @param hObj Handle to JSON object to get an iterator
+ * for.
+ *
+ * @return Handle to JSON object iterator,
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_object_iter(Handle:hObj);
+
+/**
+ * Like json_object_iter(), but returns an iterator to the key-value
+ * pair in object whose key is equal to key.
+ * Iterating forward to the end of object only yields all key-value
+ * pairs of the object if key happens to be the first key in the
+ * underlying hash table.
+ *
+ * @param hObj Handle to JSON object to get an iterator
+ * for.
+ * @param sKey Start key for the iterator
+ *
+ * @return Handle to JSON object iterator,
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_object_iter_at(Handle:hObj, const String:key[]);
+
+/**
+ * Returns an iterator pointing to the next key-value pair in object.
+ * This automatically closes the Handle to the iterator hIter.
+ *
+ * @param hObj Handle to JSON object.
+ * @param hIter Handle to JSON object iterator.
+ *
+ * @return Handle to JSON object iterator,
+ * or INVALID_HANDLE on error, or if the
+ * whole object has been iterated through.
+ */
+native Handle:json_object_iter_next(Handle:hObj, Handle:hIter);
+
+/**
+ * Extracts the associated key of hIter as a null terminated UTF-8
+ * encoded string in the passed buffer.
+ *
+ * @param hIter Handle to the JSON String object
+ * @param sKeyBuffer Buffer to store the value of the String.
+ * @param maxlength Maximum length of string buffer.
+ * @error Invalid JSON Object Iterator.
+ * @return Length of the returned string or -1 on error.
+ */
+native json_object_iter_key(Handle:hIter, String:sKeyBuffer[], maxlength);
+
+/**
+ * Returns a handle to the value hIter is pointing at.
+ *
+ * @param hIter Handle to JSON object iterator.
+ *
+ * @return Handle to value or INVALID_HANDLE on error.
+ */
+native Handle:json_object_iter_value(Handle:hIter);
+
+/**
+ * Set the value of the key-value pair in hObj, that is pointed to
+ * by hIter, to hValue.
+ *
+ * @param hObj Handle to JSON object.
+ * @param hIter Handle to JSON object iterator.
+ * @param hValue Handle to JSON value.
+ *
+ * @return True on success.
+ */
+native bool:json_object_iter_set(Handle:hObj, Handle:hIter, Handle:hValue);
+
+/**
+ * Set the value of the key-value pair in hObj, that is pointed to
+ * by hIter, to hValue.
+ * This function automatically closes the Handle to the value object.
+ *
+ * @param hObj Handle to JSON object.
+ * @param hIter Handle to JSON object iterator.
+ * @param hValue Handle to JSON value.
+ *
+ * @return True on success.
+ */
+native bool:json_object_iter_set_new(Handle:hObj, Handle:hIter, Handle:hValue);
+
+
+
+
+/**
+ * Arrays
+ *
+ * A JSON array is an ordered collection of other JSON values.
+ *
+ */
+
+/**
+ * Returns a handle to a new JSON array, or INVALID_HANDLE on error.
+ *
+ * @return Handle to the new JSON array
+ */
+native Handle:json_array();
+
+/**
+ * Returns the number of elements in hArray
+ *
+ * @param hObj Handle to JSON array
+ *
+ * @return Number of elements in hArray,
+ * or 0 if hObj is not a JSON array.
+ */
+native json_array_size(Handle:hArray);
+
+/**
+ * Returns the element in hArray at position iIndex.
+ *
+ * @param hArray Handle to JSON array to get a value from
+ * @param iIndex Position to retrieve
+ *
+ * @return Handle to a the JSON object or
+ * INVALID_HANDLE on error.
+ */
+native Handle:json_array_get(Handle:hArray, iIndex);
+
+/**
+ * Replaces the element in array at position iIndex with hValue.
+ * The valid range for iIndex is from 0 to the return value of
+ * json_array_size() minus 1.
+ *
+ * @param hArray Handle to JSON array
+ * @param iIndex Position to replace
+ * @param hValue Value to store in the array
+ *
+ * @return True on success.
+ */
+native bool:json_array_set(Handle:hArray, iIndex, Handle:hValue);
+
+/**
+ * Replaces the element in array at position iIndex with hValue.
+ * The valid range for iIndex is from 0 to the return value of
+ * json_array_size() minus 1.
+ * This function automatically closes the Handle to the value object.
+ *
+ * @param hArray Handle to JSON array
+ * @param iIndex Position to replace
+ * @param hValue Value to store in the array
+ *
+ * @return True on success.
+ */
+native bool:json_array_set_new(Handle:hArray, iIndex, Handle:hValue);
+
+/**
+ * Appends value to the end of array, growing the size of array by 1.
+ *
+ * @param hArray Handle to JSON array
+ * @param hValue Value to append to the array
+ *
+ * @return True on success.
+ */
+native bool:json_array_append(Handle:hArray, Handle:hValue);
+
+/**
+ * Appends value to the end of array, growing the size of array by 1.
+ * This function automatically closes the Handle to the value object.
+ *
+ * @param hArray Handle to JSON array
+ * @param hValue Value to append to the array
+ *
+ * @return True on success.
+ */
+native bool:json_array_append_new(Handle:hArray, Handle:hValue);
+
+/**
+ * Inserts value to hArray at position iIndex, shifting the elements at
+ * iIndex and after it one position towards the end of the array.
+ *
+ * @param hArray Handle to JSON array
+ * @param iIndex Position to insert at
+ * @param hValue Value to store in the array
+ *
+ * @return True on success.
+ */
+native bool:json_array_insert(Handle:hArray, iIndex, Handle:hValue);
+
+/**
+ * Inserts value to hArray at position iIndex, shifting the elements at
+ * iIndex and after it one position towards the end of the array.
+ * This function automatically closes the Handle to the value object.
+ *
+ * @param hArray Handle to JSON array
+ * @param iIndex Position to insert at
+ * @param hValue Value to store in the array
+ *
+ * @return True on success.
+ */
+native bool:json_array_insert_new(Handle:hArray, iIndex, Handle:hValue);
+
+/**
+ * Removes the element in hArray at position iIndex, shifting the
+ * elements after iIndex one position towards the start of the array.
+ *
+ * @param hArray Handle to JSON array
+ * @param iIndex Position to insert at
+ *
+ * @return True on success.
+ */
+native bool:json_array_remove(Handle:hArray, iIndex);
+
+/**
+ * Removes all elements from hArray.
+ *
+ * @param hArray Handle to JSON array
+ *
+ * @return True on success.
+ */
+native bool:json_array_clear(Handle:hArray);
+
+/**
+ * Appends all elements in hOther to the end of hArray.
+ *
+ * @param hArray Handle to JSON array to be extended
+ * @param hOther Handle to JSON array, source to copy from
+ *
+ * @return True on success.
+ */
+native bool:json_array_extend(Handle:hArray, Handle:hOther);
+
+
+
+
+/**
+ * Booleans & NULL
+ *
+ */
+
+/**
+ * Returns a handle to a new JSON Boolean with value true,
+ * or INVALID_HANDLE on error.
+ *
+ * @return Handle to the new Boolean object
+ */
+native Handle:json_true();
+
+/**
+ * Returns a handle to a new JSON Boolean with value false,
+ * or INVALID_HANDLE on error.
+ *
+ * @return Handle to the new Boolean object
+ */
+native Handle:json_false();
+
+/**
+ * Returns a handle to a new JSON Boolean with the value passed
+ * in bState or INVALID_HANDLE on error.
+ *
+ * @param bState Value for the new Boolean object
+ * @return Handle to the new Boolean object
+ */
+native Handle:json_boolean(bool:bState);
+
+/**
+ * Returns a handle to a new JSON NULL or INVALID_HANDLE on error.
+ *
+ * @return Handle to the new NULL object
+ */
+native Handle:json_null();
+
+
+
+
+/**
+ * Strings
+ *
+ * Jansson uses UTF-8 as the character encoding. All JSON strings must
+ * be valid UTF-8 (or ASCII, as it�s a subset of UTF-8). Normal null
+ * terminated C strings are used, so JSON strings may not contain
+ * embedded null characters.
+ *
+ */
+
+/**
+ * Returns a handle to a new JSON string, or INVALID_HANDLE on error.
+ *
+ * @param sValue Value for the new String object
+ * Must be a valid UTF-8 encoded Unicode string.
+ * @return Handle to the new String object
+ */
+native Handle:json_string(const String:sValue[]);
+
+/**
+ * Saves the associated value of hString as a null terminated UTF-8
+ * encoded string in the passed buffer.
+ *
+ * @param hString Handle to the JSON String object
+ * @param sValueBuffer Buffer to store the value of the String.
+ * @param maxlength Maximum length of string buffer.
+ * @error Invalid JSON String Object.
+ * @return Length of the returned string or -1 on error.
+ */
+native json_string_value(Handle:hString, String:sValueBuffer[], maxlength);
+
+/**
+ * Sets the associated value of JSON String object to value.
+ *
+ * @param hString Handle to the JSON String object
+ * @param sValue Value to set the object to.
+ * Must be a valid UTF-8 encoded Unicode string.
+ * @error Invalid JSON String Object.
+ * @return True on success.
+ */
+native bool:json_string_set(Handle:hString, String:sValue[]);
+
+
+
+
+/**
+ * Numbers
+ *
+ * The JSON specification only contains one numeric type, 'number'.
+ * The C (and Pawn) programming language has distinct types for integer
+ * and floating-point numbers, so for practical reasons Jansson also has
+ * distinct types for the two. They are called 'integer' and 'real',
+ * respectively. (Whereas 'real' is a 'Float' for Pawn).
+ * Therefore a number is represented by either a value of the type
+ * JSON_INTEGER or of the type JSON_REAL.
+ *
+ */
+
+/**
+ * Returns a handle to a new JSON integer, or INVALID_HANDLE on error.
+ *
+ * @param iValue Value for the new Integer object
+ * @return Handle to the new Integer object
+ */
+native Handle:json_integer(iValue);
+
+/**
+ * Returns the associated value of a JSON Integer Object.
+ *
+ * @param hInteger Handle to the JSON Integer object
+ * @error Invalid JSON Integer Object.
+ * @return Value of the hInteger,
+ * or 0 if hInteger is not a JSON integer.
+ */
+native json_integer_value(Handle:hInteger);
+
+/**
+ * Sets the associated value of JSON Integer to value.
+ *
+ * @param hInteger Handle to the JSON Integer object
+ * @param iValue Value to set the object to.
+ * @error Invalid JSON Integer Object.
+ * @return True on success.
+ */
+native bool:json_integer_set(Handle:hInteger, iValue);
+
+/**
+ * Returns a handle to a new JSON real, or INVALID_HANDLE on error.
+ *
+ * @param fValue Value for the new Real object
+ * @return Handle to the new String object
+ */
+native Handle:json_real(Float:fValue);
+
+/**
+ * Returns the associated value of a JSON Real.
+ *
+ * @param hReal Handle to the JSON Real object
+ * @error Invalid JSON Real Object.
+ * @return Float value of hReal,
+ * or 0.0 if hReal is not a JSON Real.
+ */
+native Float:json_real_value(Handle:hReal);
+
+/**
+ * Sets the associated value of JSON Real to fValue.
+ *
+ * @param hReal Handle to the JSON Integer object
+ * @param fValue Value to set the object to.
+ * @error Invalid JSON Real handle.
+ * @return True on success.
+ */
+native bool:json_real_set(Handle:hReal, Float:value);
+
+/**
+ * Returns the associated value of a JSON integer or a
+ * JSON Real, cast to Float regardless of the actual type.
+ *
+ * @param hNumber Handle to the JSON Number
+ * @error Not a JSON Real or JSON Integer
+ * @return Float value of hNumber,
+ * or 0.0 on error.
+ */
+native Float:json_number_value(Handle:hNumber);
+
+
+
+
+/**
+ * Decoding
+ *
+ * This sections describes the functions that can be used to decode JSON text
+ * to the Jansson representation of JSON data. The JSON specification requires
+ * that a JSON text is either a serialized array or object, and this
+ * requirement is also enforced with the following functions. In other words,
+ * the top level value in the JSON text being decoded must be either array or
+ * object.
+ *
+ */
+
+/**
+ * Decodes the JSON string sJSON and returns the array or object it contains.
+ * Errors while decoding can be found in the sourcemod error log.
+ *
+ * @param sJSON String containing valid JSON
+
+ * @return Handle to JSON object or array.
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_load(const String:sJSON[]);
+
+/**
+ * Decodes the JSON string sJSON and returns the array or object it contains.
+ * This function provides additional error feedback and does not log errors
+ * to the sourcemod error log.
+ *
+ * @param sJSON String containing valid JSON
+ * @param sErrorText This buffer will be filled with the error
+ * message.
+ * @param maxlen Size of the buffer
+ * @param iLine This int will contain the line of the error
+ * @param iColumn This int will contain the column of the error
+ *
+ * @return Handle to JSON object or array.
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_load_ex(const String:sJSON[], String:sErrorText[], maxlen, &iLine, &iColumn);
+
+/**
+ * Decodes the JSON text in file sFilePath and returns the array or object
+ * it contains.
+ * Errors while decoding can be found in the sourcemod error log.
+ *
+ * @param sFilePath Path to a file containing pure JSON
+ *
+ * @return Handle to JSON object or array.
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_load_file(const String:sFilePath[PLATFORM_MAX_PATH]);
+
+/**
+ * Decodes the JSON text in file sFilePath and returns the array or object
+ * it contains.
+ * This function provides additional error feedback and does not log errors
+ * to the sourcemod error log.
+ *
+ * @param sFilePath Path to a file containing pure JSON
+ * @param sErrorText This buffer will be filled with the error
+ * message.
+ * @param maxlen Size of the buffer
+ * @param iLine This int will contain the line of the error
+ * @param iColumn This int will contain the column of the error
+ *
+ * @return Handle to JSON object or array.
+ * or INVALID_HANDLE on error.
+ */
+native Handle:json_load_file_ex(const String:sFilePath[PLATFORM_MAX_PATH], String:sErrorText[], maxlen, &iLine, &iColumn);
+
+
+
+/**
+ * Encoding
+ *
+ * This sections describes the functions that can be used to encode values
+ * to JSON. By default, only objects and arrays can be encoded directly,
+ * since they are the only valid root values of a JSON text.
+ *
+ */
+
+/**
+ * Saves the JSON representation of hObject in sJSON.
+ *
+ * @param hObject String containing valid JSON
+ * @param sJSON Buffer to store the created JSON string.
+ * @param maxlength Maximum length of string buffer.
+ * @param iIndentWidth Indenting with iIndentWidth spaces.
+ * The valid range for this is between 0 and 31 (inclusive),
+ * other values result in an undefined output. If this is set
+ * to 0, no newlines are inserted between array and object items.
+ * @param bEnsureAscii If this is set, the output is guaranteed
+ * to consist only of ASCII characters. This is achieved
+ * by escaping all Unicode characters outside the ASCII range.
+ * @param bSortKeys If this flag is used, all the objects in output are sorted
+ * by key. This is useful e.g. if two JSON texts are diffed
+ * or visually compared.
+ * @param bPreserveOrder If this flag is used, object keys in the output are sorted
+ * into the same order in which they were first inserted to
+ * the object. For example, decoding a JSON text and then
+ * encoding with this flag preserves the order of object keys.
+ * @return Length of the returned string or -1 on error.
+ */
+native json_dump(Handle:hObject, String:sJSON[], maxlength, iIndentWidth = 4, bool:bEnsureAscii = false, bool:bSortKeys = false, bool:bPreserveOrder = false);
+
+/**
+ * Write the JSON representation of hObject to the file sFilePath.
+ * If sFilePath already exists, it is overwritten.
+ *
+ * @param hObject String containing valid JSON
+ * @param sFilePath Buffer to store the created JSON string.
+ * @param iIndentWidth Indenting with iIndentWidth spaces.
+ * The valid range for this is between 0 and 31 (inclusive),
+ * other values result in an undefined output. If this is set
+ * to 0, no newlines are inserted between array and object items.
+ * @param bEnsureAscii If this is set, the output is guaranteed
+ * to consist only of ASCII characters. This is achieved
+ * by escaping all Unicode characters outside the ASCII range.
+ * @param bSortKeys If this flag is used, all the objects in output are sorted
+ * by key. This is useful e.g. if two JSON texts are diffed
+ * or visually compared.
+ * @param bPreserveOrder If this flag is used, object keys in the output are sorted
+ * into the same order in which they were first inserted to
+ * the object. For example, decoding a JSON text and then
+ * encoding with this flag preserves the order of object keys.
+ * @return Length of the returned string or -1 on error.
+ */
+native bool:json_dump_file(Handle:hObject, const String:sFilePath[], iIndentWidth = 4, bool:bEnsureAscii = false, bool:bSortKeys = false, bool:bPreserveOrder = false);
+
+
+
+/**
+ * Convenience stocks
+ *
+ * These are some custom functions to ease the development using this
+ * extension.
+ *
+ */
+
+/**
+ * Returns a handle to a new JSON string, or INVALID_HANDLE on error.
+ * Formats the string according to the SourceMod format rules.
+ * The result must be a valid UTF-8 encoded Unicode string.
+ *
+ * @param sFormat Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @return Handle to the new String object
+ */
+stock Handle:json_string_format(const String:sFormat[], any:...) {
+ new String:sTmp[4096];
+ VFormat(sTmp, sizeof(sTmp), sFormat, 2);
+
+ return json_string(sTmp);
+}
+
+/**
+ * Returns a handle to a new JSON string, or INVALID_HANDLE on error.
+ * This stock allows to specify the size of the temporary buffer used
+ * to create the string. Use this if the default of 4096 is not enough
+ * for your string.
+ * Formats the string according to the SourceMod format rules.
+ * The result must be a valid UTF-8 encoded Unicode string.
+ *
+ * @param tmpBufferLength Size of the temporary buffer
+ * @param sFormat Formatting rules.
+ * @param ... Variable number of format parameters.
+ * @return Handle to the new String object
+ */
+stock Handle:json_string_format_ex(tmpBufferLength, const String:sFormat[], any:...) {
+ new String:sTmp[tmpBufferLength];
+ VFormat(sTmp, sizeof(sTmp), sFormat, 3);
+
+ return json_string(sTmp);
+}
+
+
+/**
+ * Returns the boolean value of the element in hArray at position iIndex.
+ *
+ * @param hArray Handle to JSON array to get a value from
+ * @param iIndex Position to retrieve
+ *
+ * @return True if it's a boolean and TRUE,
+ * false otherwise.
+ */
+stock bool:json_array_get_bool(Handle:hArray, iIndex) {
+ new Handle:hElement = json_array_get(hArray, iIndex);
+
+ new bool:bResult = (json_is_true(hElement) ? true : false);
+
+ CloseHandle(hElement);
+ return bResult;
+}
+
+/**
+ * Returns the float value of the element in hArray at position iIndex.
+ *
+ * @param hArray Handle to JSON array to get a value from
+ * @param iIndex Position to retrieve
+ *
+ * @return Float value,
+ * or 0.0 if element is not a JSON Real.
+ */
+stock Float:json_array_get_float(Handle:hArray, iIndex) {
+ new Handle:hElement = json_array_get(hArray, iIndex);
+
+ new Float:fResult = (json_is_number(hElement) ? json_number_value(hElement) : 0.0);
+
+ CloseHandle(hElement);
+ return fResult;
+}
+
+/**
+ * Returns the integer value of the element in hArray at position iIndex.
+ *
+ * @param hArray Handle to JSON array to get a value from
+ * @param iIndex Position to retrieve
+ *
+ * @return Integer value,
+ * or 0 if element is not a JSON Integer.
+ */
+stock json_array_get_int(Handle:hArray, iIndex) {
+ new Handle:hElement = json_array_get(hArray, iIndex);
+
+ new iResult = (json_is_integer(hElement) ? json_integer_value(hElement) : 0);
+
+ CloseHandle(hElement);
+ return iResult;
+}
+
+/**
+ * Saves the associated value of the element in hArray at position iIndex
+ * as a null terminated UTF-8 encoded string in the passed buffer.
+ *
+ * @param hArray Handle to JSON array to get a value from
+ * @param iIndex Position to retrieve
+ * @param sBuffer Buffer to store the value of the String.
+ * @param maxlength Maximum length of string buffer.
+ *
+ * @error Element is not a JSON String.
+ * @return Length of the returned string or -1 on error.
+ */
+stock json_array_get_string(Handle:hArray, iIndex, String:sBuffer[], maxlength) {
+ new Handle:hElement = json_array_get(hArray, iIndex);
+
+ new iResult = -1;
+ if(json_is_string(hElement)) {
+ iResult = json_string_value(hElement, sBuffer, maxlength);
+ }
+ CloseHandle(hElement);
+
+ return iResult;
+}
+
+/**
+ * Returns the boolean value of the element in hObj at entry sKey.
+ *
+ * @param hObj Handle to JSON object to get a value from
+ * @param sKey Entry to retrieve
+ *
+ * @return True if it's a boolean and TRUE,
+ * false otherwise.
+ */
+stock bool:json_object_get_bool(Handle:hObj, const String:sKey[]) {
+ new Handle:hElement = json_object_get(hObj, sKey);
+
+ new bool:bResult = (json_is_true(hElement) ? true : false);
+
+ CloseHandle(hElement);
+ return bResult;
+}
+
+/**
+ * Returns the float value of the element in hObj at entry sKey.
+ *
+ * @param hObj Handle to JSON object to get a value from
+ * @param sKey Position to retrieve
+ *
+ * @return Float value,
+ * or 0.0 if element is not a JSON Real.
+ */
+stock Float:json_object_get_float(Handle:hObj, const String:sKey[]) {
+ new Handle:hElement = json_object_get(hObj, sKey);
+
+ new Float:fResult = (json_is_number(hElement) ? json_number_value(hElement) : 0.0);
+
+ CloseHandle(hElement);
+ return fResult;
+}
+
+/**
+ * Returns the integer value of the element in hObj at entry sKey.
+ *
+ * @param hObj Handle to JSON object to get a value from
+ * @param sKey Position to retrieve
+ *
+ * @return Integer value,
+ * or 0 if element is not a JSON Integer.
+ */
+stock json_object_get_int(Handle:hObj, const String:sKey[]) {
+ new Handle:hElement = json_object_get(hObj, sKey);
+
+ new iResult = (json_is_integer(hElement) ? json_integer_value(hElement) : 0);
+
+ CloseHandle(hElement);
+ return iResult;
+}
+
+/**
+ * Saves the associated value of the element in hObj at entry sKey
+ * as a null terminated UTF-8 encoded string in the passed buffer.
+ *
+ * @param hObj Handle to JSON object to get a value from
+ * @param sKey Entry to retrieve
+ * @param sBuffer Buffer to store the value of the String.
+ * @param maxlength Maximum length of string buffer.
+ *
+ * @error Element is not a JSON String.
+ * @return Length of the returned string or -1 on error.
+ */
+stock json_object_get_string(Handle:hObj, const String:sKey[], String:sBuffer[], maxlength) {
+ new Handle:hElement = json_object_get(hObj, sKey);
+
+ new iResult = -1;
+ if(json_is_string(hElement)) {
+ iResult = json_string_value(hElement, sBuffer, maxlength);
+ }
+ CloseHandle(hElement);
+
+ return iResult;
+}
+
+
+
+/**
+ * Pack String Rules
+ *
+ * Here�s the full list of format characters:
+ * n Output a JSON null value. No argument is consumed.
+ * s Output a JSON string, consuming one argument.
+ * b Output a JSON bool value, consuming one argument.
+ * i Output a JSON integer value, consuming one argument.
+ * f Output a JSON real value, consuming one argument.
+ * r Output a JSON real value, consuming one argument.
+ * [] Build an array with contents from the inner format string,
+ * recursive value building is supported.
+ * No argument is consumed.
+ * {} Build an array with contents from the inner format string.
+ * The first, third, etc. format character represent a key,
+ * and must be s (as object keys are always strings). The
+ * second, fourth, etc. format character represent a value.
+ * Recursive value building is supported.
+ * No argument is consumed.
+ *
+ */
+
+/**
+ * This method can be used to create json objects/arrays directly
+ * without having to create the structure.
+ * See 'Pack String Rules' for more details.
+ *
+ * @param sPackString Pack string similiar to Format()s fmt.
+ * See 'Pack String Rules'.
+ * @param hParams ADT Array containing all keys and values
+ * in the order they appear in the pack string.
+ *
+ * @error Invalid pack string or pack string and
+ * ADT Array don't match up regarding type
+ * or size.
+ * @return Handle to JSON element.
+ */
+stock Handle:json_pack(const String:sPackString[], Handle:hParams) {
+ new iPos = 0;
+ return json_pack_element_(sPackString, iPos, hParams);
+}
+
+
+
+
+
+/**
+* Internal stocks used by json_pack(). Don't use these directly!
+*
+*/
+stock Handle:json_pack_array_(const String:sFormat[], &iPos, Handle:hParams) {
+ new Handle:hObj = json_array();
+ new iStrLen = strlen(sFormat);
+ for(; iPos < iStrLen;) {
+ new this_char = sFormat[iPos];
+
+ if(this_char == 32 || this_char == 58 || this_char == 44) {
+ // Skip whitespace, ',' and ':'
+ iPos++;
+ continue;
+ }
+
+ if(this_char == 93) {
+ // array end
+ iPos++;
+ break;
+ }
+
+ // Get the next entry as value
+ // This automatically increments the position!
+ new Handle:hValue = json_pack_element_(sFormat, iPos, hParams);
+
+ // Append the value to the array.
+ json_array_append_new(hObj, hValue);
+ }
+
+ return hObj;
+}
+
+stock Handle:json_pack_object_(const String:sFormat[], &iPos, Handle:hParams) {
+ new Handle:hObj = json_object();
+ new iStrLen = strlen(sFormat);
+ for(; iPos < iStrLen;) {
+ new this_char = sFormat[iPos];
+
+ if(this_char == 32 || this_char == 58 || this_char == 44) {
+ // Skip whitespace, ',' and ':'
+ iPos++;
+ continue;
+ }
+
+ if(this_char == 125) {
+ // } --> object end
+ iPos++;
+ break;
+ }
+
+ if(this_char != 115) {
+ LogError("Object keys must be strings at %d.", iPos);
+ return INVALID_HANDLE;
+ }
+
+ // Get the key string for this object from
+ // the hParams array.
+ decl String:sKey[255];
+ GetArrayString(hParams, 0, sKey, sizeof(sKey));
+ RemoveFromArray(hParams, 0);
+
+ // Advance one character in the pack string,
+ // because we've just read the Key string for
+ // this object.
+ iPos++;
+
+ // Get the next entry as value
+ // This automatically increments the position!
+ new Handle:hValue = json_pack_element_(sFormat, iPos, hParams);
+
+ // Insert into object
+ json_object_set_new(hObj, sKey, hValue);
+ }
+
+ return hObj;
+}
+
+stock Handle:json_pack_element_(const String:sFormat[], &iPos, Handle:hParams) {
+ new this_char = sFormat[iPos];
+ while(this_char == 32 || this_char == 58 || this_char == 44) {
+ iPos++;
+ this_char = sFormat[iPos];
+ }
+
+ // Advance one character in the pack string
+ iPos++;
+
+ switch(this_char) {
+ case 91: {
+ // { --> Array
+ return json_pack_array_(sFormat, iPos, hParams);
+ }
+
+ case 123: {
+ // { --> Object
+ return json_pack_object_(sFormat, iPos, hParams);
+
+ }
+
+ case 98: {
+ // b --> Boolean
+ new iValue = GetArrayCell(hParams, 0);
+ RemoveFromArray(hParams, 0);
+
+ return json_boolean(bool:iValue);
+ }
+
+ case 102, 114: {
+ // r,f --> Real (Float)
+ new Float:iValue = GetArrayCell(hParams, 0);
+ RemoveFromArray(hParams, 0);
+
+ return json_real(iValue);
+ }
+
+ case 110: {
+ // n --> NULL
+ return json_null();
+ }
+
+ case 115: {
+ // s --> String
+ decl String:sKey[255];
+ GetArrayString(hParams, 0, sKey, sizeof(sKey));
+ RemoveFromArray(hParams, 0);
+
+ return json_string(sKey);
+ }
+
+ case 105: {
+ // i --> Integer
+ new iValue = GetArrayCell(hParams, 0);
+ RemoveFromArray(hParams, 0);
+
+ return json_integer(iValue);
+ }
+ }
+
+ SetFailState("Invalid pack String '%s'. Type '%s' not supported at %i", sFormat, this_char, iPos);
+ return json_null();
+}
+
+
+
+
+
+/**
+ * Not yet implemented
+ *
+ * native json_object_foreach(Handle:hObj, ForEachCallback:cb);
+ * native Handle:json_unpack(const String:sFormat[], ...);
+ *
+ */
+
+
+
+
+
+
+/**
+ * Do not edit below this line!
+ */
+public Extension:__ext_smjansson =
+{
+ name = "SMJansson",
+ file = "smjansson.ext",
+#if defined AUTOLOAD_EXTENSIONS
+ autoload = 1,
+#else
+ autoload = 0,
+#endif
+#if defined REQUIRE_EXTENSIONS
+ required = 1,
+#else
+ required = 0,
+#endif
+};
+
+#if !defined REQUIRE_EXTENSIONS
+public __ext_smjansson_SetNTVOptional()
+{
+ MarkNativeAsOptional("json_typeof");
+ MarkNativeAsOptional("json_equal");
+
+ MarkNativeAsOptional("json_copy");
+ MarkNativeAsOptional("json_deep_copy");
+
+ MarkNativeAsOptional("json_object");
+ MarkNativeAsOptional("json_object_size");
+ MarkNativeAsOptional("json_object_get");
+ MarkNativeAsOptional("json_object_set");
+ MarkNativeAsOptional("json_object_set_new");
+ MarkNativeAsOptional("json_object_del");
+ MarkNativeAsOptional("json_object_clear");
+ MarkNativeAsOptional("json_object_update");
+ MarkNativeAsOptional("json_object_update_existing");
+ MarkNativeAsOptional("json_object_update_missing");
+
+ MarkNativeAsOptional("json_object_iter");
+ MarkNativeAsOptional("json_object_iter_at");
+ MarkNativeAsOptional("json_object_iter_next");
+ MarkNativeAsOptional("json_object_iter_key");
+ MarkNativeAsOptional("json_object_iter_value");
+ MarkNativeAsOptional("json_object_iter_set");
+ MarkNativeAsOptional("json_object_iter_set_new");
+
+ MarkNativeAsOptional("json_array");
+ MarkNativeAsOptional("json_array_size");
+ MarkNativeAsOptional("json_array_get");
+ MarkNativeAsOptional("json_array_set");
+ MarkNativeAsOptional("json_array_set_new");
+ MarkNativeAsOptional("json_array_append");
+ MarkNativeAsOptional("json_array_append_new");
+ MarkNativeAsOptional("json_array_insert");
+ MarkNativeAsOptional("json_array_insert_new");
+ MarkNativeAsOptional("json_array_remove");
+ MarkNativeAsOptional("json_array_clear");
+ MarkNativeAsOptional("json_array_extend");
+
+ MarkNativeAsOptional("json_string");
+ MarkNativeAsOptional("json_string_value");
+ MarkNativeAsOptional("json_string_set");
+
+ MarkNativeAsOptional("json_integer");
+ MarkNativeAsOptional("json_integer_value");
+ MarkNativeAsOptional("json_integer_set");
+
+ MarkNativeAsOptional("json_real");
+ MarkNativeAsOptional("json_real_value");
+ MarkNativeAsOptional("json_real_set");
+ MarkNativeAsOptional("json_number_value");
+
+ MarkNativeAsOptional("json_boolean");
+ MarkNativeAsOptional("json_true");
+ MarkNativeAsOptional("json_false");
+ MarkNativeAsOptional("json_null");
+
+ MarkNativeAsOptional("json_load");
+ MarkNativeAsOptional("json_load_file");
+
+ MarkNativeAsOptional("json_dump");
+ MarkNativeAsOptional("json_dump_file");
+}
+#endif
diff --git a/sourcemod/scripting/include/sourcebanspp.inc b/sourcemod/scripting/include/sourcebanspp.inc
new file mode 100644
index 0000000..c984bff
--- /dev/null
+++ b/sourcemod/scripting/include/sourcebanspp.inc
@@ -0,0 +1,106 @@
+// *************************************************************************
+// This file is part of SourceBans++.
+//
+// Copyright (C) 2014-2019 SourceBans++ Dev Team <https://github.com/sbpp>
+//
+// SourceBans++ is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, per version 3 of the License.
+//
+// SourceBans++ is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
+//
+// This file based off work(s) covered by the following copyright(s):
+//
+// SourceBans 1.4.11
+// Copyright (C) 2007-2015 SourceBans Team - Part of GameConnect
+// Licensed under GNU GPL version 3, or later.
+// Page: <http://www.sourcebans.net/> - <https://github.com/GameConnect/sourcebansv1>
+//
+// *************************************************************************
+
+#if defined _sourcebanspp_included
+#endinput
+#endif
+#define _sourcebanspp_included
+
+public SharedPlugin __pl_sourcebanspp =
+{
+ name = "sourcebans++",
+ file = "sbpp_main.smx",
+ #if defined REQUIRE_PLUGIN
+ required = 1
+ #else
+ required = 0
+ #endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public void __pl_sourcebanspp_SetNTVOptional()
+{
+ MarkNativeAsOptional("SBBanPlayer");
+ MarkNativeAsOptional("SBPP_BanPlayer");
+ MarkNativeAsOptional("SBPP_ReportPlayer");
+}
+#endif
+
+
+/*********************************************************
+ * Ban Player from server
+ *
+ * @param iAdmin The client index of the admin who is banning the client
+ * @param iTarget The client index of the player to ban
+ * @param iTime The time to ban the player for (in minutes, 0 = permanent)
+ * @param sReason The reason to ban the player from the server
+ * @noreturn
+ *********************************************************/
+#pragma deprecated Use SBPP_BanPlayer() instead.
+native void SBBanPlayer(int iAdmin, int iTarget, int iTime, const char[] sReason);
+
+/*********************************************************
+ * Ban Player from server
+ *
+ * @param iAdmin The client index of the admin who is banning the client
+ * @param iTarget The client index of the player to ban
+ * @param iTime The time to ban the player for (in minutes, 0 = permanent)
+ * @param sReason The reason to ban the player from the server
+ * @noreturn
+ *********************************************************/
+native void SBPP_BanPlayer(int iAdmin, int iTarget, int iTime, const char[] sReason);
+
+/*********************************************************
+ * Reports a player
+ *
+ * @param iReporter The client index of the reporter
+ * @param iTarget The client index of the player to report
+ * @param sReason The reason to report the player
+ * @noreturn
+ *********************************************************/
+native void SBPP_ReportPlayer(int iReporter, int iTarget, const char[] sReason);
+
+/*********************************************************
+ * Called when the admin banning the player.
+ *
+ * @param iAdmin The client index of the admin who is banning the client
+ * @param iTarget The client index of the player to ban
+ * @param iTime The time to ban the player for (in minutes, 0 = permanent)
+ * @param sReason The reason to ban the player from the server
+ *********************************************************/
+forward void SBPP_OnBanPlayer(int iAdmin, int iTarget, int iTime, const char[] sReason);
+
+/*********************************************************
+ * Called when a new report is inserted
+ *
+ * @param iReporter The client index of the reporter
+ * @param iTarget The client index of the player to report
+ * @param sReason The reason to report the player
+ * @noreturn
+ *********************************************************/
+forward void SBPP_OnReportPlayer(int iReporter, int iTarget, const char[] sReason);
+
+//Yarr!
diff --git a/sourcemod/scripting/include/sourcemod-colors.inc b/sourcemod/scripting/include/sourcemod-colors.inc
new file mode 100644
index 0000000..66bc97b
--- /dev/null
+++ b/sourcemod/scripting/include/sourcemod-colors.inc
@@ -0,0 +1,921 @@
+#if defined _sourcemod_colors_included
+ #endinput
+#endif
+#define _sourcemod_colors_included "1.0"
+
+/*
+* _____ _ _____ _
+* / ____| | | / ____| | |
+* | (___ ___ _ _ _ __ ___ ___ _ __ ___ ___ __| | | | ___ | | ___ _ __ ___
+* \___ \ / _ \| | | | '__/ __/ _ \ '_ ` _ \ / _ \ / _` | | | / _ \| |/ _ \| '__/ __|
+* ____) | (_) | |_| | | | (_| __/ | | | | | (_) | (_| | | |___| (_) | | (_) | | \__ \
+* |_____/ \___/ \__,_|_| \___\___|_| |_| |_|\___/ \__,_| \_____\___/|_|\___/|_| |___/
+*
+*
+* - Author: Keith Warren (Drixevel)
+* - Original By: Raska aka KissLick (ColorVariables)
+*
+* This is meant to be a drop-in replacement for every Source Engine game to add colors to chat and more cheat features.
+*/
+
+// ----------------------------------------------------------------------------------------
+#define MAX_BUFFER_SIZE 1024
+
+static bool g_bInit;
+static StringMap g_hColors;
+static char g_sChatPrefix[64];
+
+static bool g_bIgnorePrefix;
+static int g_iAuthor;
+static bool g_bSkipPlayers[MAXPLAYERS + 1];
+// ----------------------------------------------------------------------------------------
+
+/*
+* Sets the prefix for all chat prints to use.
+*
+* prefix - String to use.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CSetPrefix(const char[] prefix, any ...)
+{
+ VFormat(g_sChatPrefix, sizeof(g_sChatPrefix), prefix, 2);
+}
+
+/*
+* Setup the next print to skip using the prefix.
+*
+*
+* Return - N/A
+*/
+stock void CSkipNextPrefix()
+{
+ g_bIgnorePrefix = true;
+}
+
+/*
+* Sets the author for the next print. (Mostly applies colors)
+*
+* client - Author index.
+*
+* Return - N/A
+*/
+stock void CSetNextAuthor(int client)
+{
+ if (client < 1 || client > MaxClients || !IsClientInGame(client))
+ ThrowError("Invalid client index %i", client);
+
+ g_iAuthor = client;
+}
+
+/*
+* Setup the next chat print to not be sent to this client.
+*
+* client - Client index.
+*
+* Return - N/A
+*/
+stock void CSkipNextClient(int client)
+{
+ if (client < 1 || client > MaxClients)
+ ThrowError("Invalid client index %i", client);
+
+ g_bSkipPlayers[client] = true;
+}
+
+/*
+* Sends a chat print to the client.
+*
+* client - Client index.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChat(int client, const char[] message, any ...)
+{
+ if ((client < 1 || client > MaxClients || !IsClientInGame(client) || IsFakeClient(client)) && !IsClientSourceTV(client))
+ return;
+
+ SetGlobalTransTarget(client);
+
+ char buffer[MAX_BUFFER_SIZE];
+ VFormat(buffer, sizeof(buffer), message, 3);
+
+ AddPrefixAndDefaultColor(buffer, sizeof(buffer));
+ g_bIgnorePrefix = false;
+
+ CProcessVariables(buffer, sizeof(buffer));
+ CAddWhiteSpace(buffer, sizeof(buffer));
+
+ SendPlayerMessage(client, buffer, g_iAuthor);
+ g_iAuthor = 0;
+}
+
+/*
+* Sends a chat print to the client with a specified author.
+*
+* client - Client index.
+* author - Author index.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChatEx(int client, int author, const char[] message, any ...)
+{
+ CSetNextAuthor(author);
+ char buffer[MAX_BUFFER_SIZE];
+ VFormat(buffer, sizeof(buffer), message, 4);
+ CPrintToChat(client, buffer);
+}
+
+/*
+* Sends a chat print to all clients.
+*
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChatAll(const char[] message, any ...)
+{
+ char buffer[MAX_BUFFER_SIZE];
+
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (!IsClientInGame(client) || g_bSkipPlayers[client])
+ {
+ g_bSkipPlayers[client] = false;
+ continue;
+ }
+
+ SetGlobalTransTarget(client);
+
+ VFormat(buffer, sizeof(buffer), message, 2);
+
+ AddPrefixAndDefaultColor(buffer, sizeof(buffer));
+ g_bIgnorePrefix = false;
+
+ CProcessVariables(buffer, sizeof(buffer));
+ CAddWhiteSpace(buffer, sizeof(buffer));
+
+ SendPlayerMessage(client, buffer, g_iAuthor);
+ }
+
+ g_iAuthor = 0;
+}
+
+/*
+* Sends a chat print to all clients with a specified author.
+*
+* author - Author index.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChatAllEx(int author, const char[] message, any ...)
+{
+ CSetNextAuthor(author);
+ char buffer[MAX_BUFFER_SIZE];
+ VFormat(buffer, sizeof(buffer), message, 3);
+ CPrintToChatAll(buffer);
+}
+
+/*
+* Sends a chat print to a specified team.
+*
+* team - Team index.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChatTeam(int team, const char[] message, any ...)
+{
+ char buffer[MAX_BUFFER_SIZE];
+
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (!IsClientInGame(client) || GetClientTeam(client) != team || g_bSkipPlayers[client])
+ {
+ g_bSkipPlayers[client] = false;
+ continue;
+ }
+
+ SetGlobalTransTarget(client);
+ VFormat(buffer, sizeof(buffer), message, 3);
+
+ AddPrefixAndDefaultColor(buffer, sizeof(buffer));
+ g_bIgnorePrefix = false;
+
+ CProcessVariables(buffer, sizeof(buffer));
+ CAddWhiteSpace(buffer, sizeof(buffer));
+
+ SendPlayerMessage(client, buffer, g_iAuthor);
+ }
+
+ g_iAuthor = 0;
+}
+
+/*
+* Sends a chat print to a specified team with a specified author.
+*
+* team - Team index.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChatTeamEx(int team, int author, const char[] message, any ...)
+{
+ CSetNextAuthor(author);
+ char buffer[MAX_BUFFER_SIZE];
+ VFormat(buffer, sizeof(buffer), message, 4);
+ CPrintToChatTeam(team, buffer);
+}
+
+/*
+* Sends a chat print to available admins.
+* Example for bitflags: (ADMFLAG_RESERVATION | ADMFLAG_GENERIC)
+*
+* bitflags - Bit Flags.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChatAdmins(int bitflags, const char[] message, any ...)
+{
+ char buffer[MAX_BUFFER_SIZE];
+ AdminId iAdminID;
+
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (!IsClientInGame(client) || g_bSkipPlayers[client])
+ {
+ g_bSkipPlayers[client] = false;
+ continue;
+ }
+
+ iAdminID = GetUserAdmin(client);
+
+ if (iAdminID == INVALID_ADMIN_ID || !(GetAdminFlags(iAdminID, Access_Effective) & bitflags))
+ continue;
+
+ SetGlobalTransTarget(client);
+ VFormat(buffer, sizeof(buffer), message, 3);
+
+ AddPrefixAndDefaultColor(buffer, sizeof(buffer));
+ g_bIgnorePrefix = false;
+
+ CProcessVariables(buffer, sizeof(buffer));
+ CAddWhiteSpace(buffer, sizeof(buffer));
+
+ SendPlayerMessage(client, buffer, g_iAuthor);
+ }
+
+ g_iAuthor = 0;
+}
+
+/*
+* Sends a chat print to available admins with a specified author.
+* Example for bitflags: (ADMFLAG_RESERVATION | ADMFLAG_GENERIC)
+*
+* bitflags - Bit Flags.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CPrintToChatAdminsEx(int bitflags, int author, const char[] message, any ...)
+{
+ CSetNextAuthor(author);
+ char buffer[MAX_BUFFER_SIZE];
+ VFormat(buffer, sizeof(buffer), message, 4);
+ CPrintToChatTeam(bitflags, buffer);
+}
+
+/*
+* Sends a reply message to the client. (This is useful because it works for console as well)
+*
+* client - Client index.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CReplyToCommand(int client, const char[] message, any ...)
+{
+ if (client < 0 || client > MaxClients)
+ ThrowError("Invalid client index %d", client);
+
+ if (client != 0 && !IsClientInGame(client))
+ ThrowError("Client %d is not in game", client);
+
+ char buffer[MAX_BUFFER_SIZE];
+ SetGlobalTransTarget(client);
+ VFormat(buffer, sizeof(buffer), message, 3);
+
+ AddPrefixAndDefaultColor(buffer, sizeof(buffer), "engine 1");
+ g_bIgnorePrefix = false;
+
+ if (GetCmdReplySource() == SM_REPLY_TO_CONSOLE)
+ {
+ CRemoveColors(buffer, sizeof(buffer));
+ PrintToConsole(client, "%s", buffer);
+ }
+ else
+ CPrintToChat(client, "%s", buffer);
+}
+
+/*
+* Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar.
+* This version does not display a message to the originating client if used from chat triggers or menus.
+* If manual replies are used for these cases, then this function will suffice.
+* Otherwise, ShowActivity2() is slightly more useful.
+*
+* client - Client index.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CShowActivity(int client, const char[] message, any ...)
+{
+ if (client < 0 || client > MaxClients)
+ ThrowError("Invalid client index %d", client);
+
+ if (client != 0 && !IsClientInGame(client))
+ ThrowError("Client %d is not in game", client);
+
+ char buffer[MAX_BUFFER_SIZE];
+ SetGlobalTransTarget(client);
+ VFormat(buffer, sizeof(buffer), message, 3);
+ Format(buffer, sizeof(buffer), "{engine 1}%s", buffer);
+ CProcessVariables(buffer, sizeof(buffer));
+ CAddWhiteSpace(buffer, sizeof(buffer));
+
+ ShowActivity(client, "%s", buffer);
+}
+
+/*
+* Displays usage of an admin command to users depending on the setting of the sm_show_activity cvar.
+* All users receive a message in their chat text, except for the originating client, who receives the message based on the current ReplySource.
+*
+* client - Client index.
+* tag - Tag to show.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CShowActivityEx(int client, const char[] tag, const char[] message, any ...)
+{
+ if (client < 0 || client > MaxClients)
+ ThrowError("Invalid client index %d", client);
+
+ if (client != 0 && !IsClientInGame(client))
+ ThrowError("Client %d is not in game", client);
+
+ char buffer[MAX_BUFFER_SIZE]; char sBufferTag[MAX_BUFFER_SIZE];
+ SetGlobalTransTarget(client);
+ VFormat(buffer, sizeof(buffer), message, 4);
+ Format(buffer, sizeof(buffer), "{engine 1}%s", buffer);
+ CProcessVariables(buffer, sizeof(buffer));
+ Format(sBufferTag, sizeof(sBufferTag), "{prefix}%s", tag);
+ CProcessVariables(sBufferTag, sizeof(sBufferTag));
+ CAddWhiteSpace(buffer, sizeof(buffer));
+ CAddWhiteSpace(sBufferTag, sizeof(sBufferTag));
+
+ ShowActivityEx(client, sBufferTag, " %s", buffer);
+}
+
+/*
+* Same as ShowActivity(), except the tag parameter is used instead of "[SM] " (note that you must supply any spacing).
+*
+* client - Client index.
+* tag - Tag to show.
+* message - Message string.
+* any - Extra Parameters
+*
+* Return - N/A
+*/
+stock void CShowActivity2(int client, const char[] tag, const char[] message, any ...)
+{
+ if (client < 0 || client > MaxClients)
+ ThrowError("Invalid client index %d", client);
+
+ if (client != 0 && !IsClientInGame(client))
+ ThrowError("Client %d is not in game", client);
+
+ char buffer[MAX_BUFFER_SIZE]; char sBufferTag[MAX_BUFFER_SIZE];
+ SetGlobalTransTarget(client);
+ VFormat(buffer, sizeof(buffer), message, 4);
+ Format(buffer, sizeof(buffer), "{engine 2}%s", buffer);
+ CProcessVariables(buffer, sizeof(buffer));
+ Format(sBufferTag, sizeof(sBufferTag), "{prefix}%s", tag);
+ CProcessVariables(sBufferTag, sizeof(sBufferTag));
+ CAddWhiteSpace(buffer, sizeof(buffer));
+ CAddWhiteSpace(sBufferTag, sizeof(sBufferTag));
+
+ ShowActivityEx(client, sBufferTag, " %s", buffer);
+}
+
+/*
+* Strips all colors from the specified string.
+*
+* msg - String buffer.
+* size - Size of the string.
+*
+* Return - N/A
+*/
+stock void CRemoveColors(char[] msg, int size)
+{
+ CProcessVariables(msg, size, true);
+}
+
+/*
+* Processes colors in a string by replacing found tags with color/hex codes.
+*
+* msg - String buffer.
+* size - Size of the string.
+* removecolors - Whether to remove colors or keep them. (same as CRemoveColors)
+*
+* Return - N/A
+*/
+stock void CProcessVariables(char[] msg, int size, bool removecolors = false)
+{
+ Init();
+
+ char[] sOut = new char[size]; char[] sCode = new char[size]; char[] color = new char[size];
+ int iOutPos = 0; int iCodePos = -1;
+ int iMsgLen = strlen(msg);
+
+ for (int i = 0; i < iMsgLen; i++)
+ {
+ if (msg[i] == '{')
+ iCodePos = 0;
+
+ if (iCodePos > -1)
+ {
+ sCode[iCodePos] = msg[i];
+ sCode[iCodePos + 1] = '\0';
+
+ if (msg[i] == '}' || i == iMsgLen - 1)
+ {
+ strcopy(sCode, strlen(sCode) - 1, sCode[1]);
+ StringToLower(sCode);
+
+ if (CGetColor(sCode, color, size))
+ {
+ if (!removecolors)
+ {
+ StrCat(sOut, size, color);
+ iOutPos += strlen(color);
+ }
+ }
+ else
+ {
+ Format(sOut, size, "%s{%s}", sOut, sCode);
+ iOutPos += strlen(sCode) + 2;
+ }
+
+ iCodePos = -1;
+ strcopy(sCode, size, "");
+ strcopy(color, size, "");
+ }
+ else
+ iCodePos++;
+
+ continue;
+ }
+
+ sOut[iOutPos] = msg[i];
+ iOutPos++;
+ sOut[iOutPos] = '\0';
+ }
+
+ strcopy(msg, size, sOut);
+}
+
+/*
+* Retrieves the color/hex code for a specified color name.
+*
+* name - Color to search for.
+* color - String buffer.
+* size - Size of the string.
+*
+* Return - True if found, false otherwise.
+*/
+stock bool CGetColor(const char[] name, char[] color, int size)
+{
+ if (name[0] == '\0')
+ return false;
+
+ if (name[0] == '@')
+ {
+ int iSpace;
+ char sData[64]; char m_sName[64];
+ strcopy(m_sName, sizeof(m_sName), name[1]);
+
+ if ((iSpace = FindCharInString(m_sName, ' ')) != -1 && (iSpace + 1 < strlen(m_sName)))
+ {
+ strcopy(m_sName, iSpace + 1, m_sName);
+ strcopy(sData, sizeof(sData), m_sName[iSpace + 1]);
+ }
+
+ if (color[0] != '\0')
+ return true;
+ }
+ else if (name[0] == '#')
+ {
+ if (strlen(name) == 7)
+ {
+ Format(color, size, "\x07%s", name[1]);
+ return true;
+ }
+
+ if (strlen(name) == 9)
+ {
+ Format(color, size, "\x08%s", name[1]);
+ return true;
+ }
+ }
+ else if (StrContains(name, "player ", false) == 0 && strlen(name) > 7)
+ {
+ int client = StringToInt(name[7]);
+
+ if (client < 1 || client > MaxClients || !IsClientInGame(client))
+ {
+ strcopy(color, size, "\x01");
+ LogError("Invalid client index %d", client);
+ return false;
+ }
+
+ strcopy(color, size, "\x01");
+
+ switch (GetClientTeam(client))
+ {
+ case 1: g_hColors.GetString("engine 8", color, size);
+ case 2: g_hColors.GetString("engine 9", color, size);
+ case 3: g_hColors.GetString("engine 11", color, size);
+ }
+
+ return true;
+ }
+ else
+ return g_hColors.GetString(name, color, size);
+
+ return false;
+}
+
+/*
+* Checks if the specified color exists.
+*
+* name - Color to search for.
+*
+* Return - True if found, false otherwise.
+*/
+stock bool CExistColor(const char[] name)
+{
+ if (name[0] == '\0' || name[0] == '@' || name[0] == '#')
+ return false;
+
+ char color[64];
+ return g_hColors.GetString(name, color, sizeof(color));
+}
+
+/*
+* Sends a raw SayText2 usermsg to the specified client with settings.
+*
+* client - Client index.
+* message - Message string.
+* author - Author index.
+* chat - "0 - raw text, 1 - sets CHAT_FILTER_PUBLICCHAT "
+*
+* Return - N/A
+*/
+stock void CSayText2(int client, const char[] message, int author, bool chat = true)
+{
+ if (client < 1 || client > MaxClients)
+ return;
+
+ Handle hMsg = StartMessageOne("SayText2", client, USERMSG_RELIABLE|USERMSG_BLOCKHOOKS);
+ if (GetFeatureStatus(FeatureType_Native, "GetUserMessageType") == FeatureStatus_Available && GetUserMessageType() == UM_Protobuf)
+ {
+ PbSetInt(hMsg, "ent_idx", author);
+ PbSetBool(hMsg, "chat", chat);
+ PbSetString(hMsg, "msg_name", message);
+ PbAddString(hMsg, "params", "");
+ PbAddString(hMsg, "params", "");
+ PbAddString(hMsg, "params", "");
+ PbAddString(hMsg, "params", "");
+ }
+ else
+ {
+ BfWriteByte(hMsg, author);
+ BfWriteByte(hMsg, true);
+ BfWriteString(hMsg, message);
+ }
+
+ EndMessage();
+}
+
+/*
+* Adds a space to the start a string buffer.
+*
+* buffer - String buffer.
+* size - Size of the string.
+*
+* Return - N/A
+*/
+stock void CAddWhiteSpace(char[] buffer, int size)
+{
+ if (!IsSource2009())
+ Format(buffer, size, " %s", buffer);
+}
+
+// ----------------------------------------------------------------------------------------
+// Private stuff
+// ----------------------------------------------------------------------------------------
+
+stock bool Init()
+{
+ if (g_bInit)
+ {
+ LoadColors();
+ return true;
+ }
+
+ for (int i = 1; i <= MaxClients; i++)
+ g_bSkipPlayers[i] = false;
+
+ LoadColors();
+ g_bInit = true;
+
+ return true;
+}
+
+stock void LoadColors()
+{
+ if (g_hColors == null)
+ g_hColors = new StringMap();
+ else
+ g_hColors.Clear();
+
+ g_hColors.SetString("default", "\x01");
+ g_hColors.SetString("teamcolor", "\x03");
+
+ if (IsSource2009())
+ {
+ g_hColors.SetString("aliceblue", "\x07F0F8FF");
+ g_hColors.SetString("allies", "\x074D7942");
+ g_hColors.SetString("ancient", "\x07EB4B4B");
+ g_hColors.SetString("antiquewhite", "\x07FAEBD7");
+ g_hColors.SetString("aqua", "\x0700FFFF");
+ g_hColors.SetString("aquamarine", "\x077FFFD4");
+ g_hColors.SetString("arcana", "\x07ADE55C");
+ g_hColors.SetString("axis", "\x07FF4040");
+ g_hColors.SetString("azure", "\x07007FFF");
+ g_hColors.SetString("beige", "\x07F5F5DC");
+ g_hColors.SetString("bisque", "\x07FFE4C4");
+ g_hColors.SetString("black", "\x07000000");
+ g_hColors.SetString("blanchedalmond", "\x07FFEBCD");
+ g_hColors.SetString("blue", "\x0799CCFF");
+ g_hColors.SetString("blueviolet", "\x078A2BE2");
+ g_hColors.SetString("brown", "\x07A52A2A");
+ g_hColors.SetString("burlywood", "\x07DEB887");
+ g_hColors.SetString("cadetblue", "\x075F9EA0");
+ g_hColors.SetString("chartreuse", "\x077FFF00");
+ g_hColors.SetString("chocolate", "\x07D2691E");
+ g_hColors.SetString("collectors", "\x07AA0000");
+ g_hColors.SetString("common", "\x07B0C3D9");
+ g_hColors.SetString("community", "\x0770B04A");
+ g_hColors.SetString("coral", "\x07FF7F50");
+ g_hColors.SetString("cornflowerblue", "\x076495ED");
+ g_hColors.SetString("cornsilk", "\x07FFF8DC");
+ g_hColors.SetString("corrupted", "\x07A32C2E");
+ g_hColors.SetString("crimson", "\x07DC143C");
+ g_hColors.SetString("cyan", "\x0700FFFF");
+ g_hColors.SetString("darkblue", "\x0700008B");
+ g_hColors.SetString("darkcyan", "\x07008B8B");
+ g_hColors.SetString("darkgoldenrod", "\x07B8860B");
+ g_hColors.SetString("darkgray", "\x07A9A9A9");
+ g_hColors.SetString("darkgrey", "\x07A9A9A9");
+ g_hColors.SetString("darkgreen", "\x07006400");
+ g_hColors.SetString("darkkhaki", "\x07BDB76B");
+ g_hColors.SetString("darkmagenta", "\x078B008B");
+ g_hColors.SetString("darkolivegreen", "\x07556B2F");
+ g_hColors.SetString("darkorange", "\x07FF8C00");
+ g_hColors.SetString("darkorchid", "\x079932CC");
+ g_hColors.SetString("darkred", "\x078B0000");
+ g_hColors.SetString("darksalmon", "\x07E9967A");
+ g_hColors.SetString("darkseagreen", "\x078FBC8F");
+ g_hColors.SetString("darkslateblue", "\x07483D8B");
+ g_hColors.SetString("darkslategray", "\x072F4F4F");
+ g_hColors.SetString("darkslategrey", "\x072F4F4F");
+ g_hColors.SetString("darkturquoise", "\x0700CED1");
+ g_hColors.SetString("darkviolet", "\x079400D3");
+ g_hColors.SetString("deeppink", "\x07FF1493");
+ g_hColors.SetString("deepskyblue", "\x0700BFFF");
+ g_hColors.SetString("dimgray", "\x07696969");
+ g_hColors.SetString("dimgrey", "\x07696969");
+ g_hColors.SetString("dodgerblue", "\x071E90FF");
+ g_hColors.SetString("exalted", "\x07CCCCCD");
+ g_hColors.SetString("firebrick", "\x07B22222");
+ g_hColors.SetString("floralwhite", "\x07FFFAF0");
+ g_hColors.SetString("forestgreen", "\x07228B22");
+ g_hColors.SetString("frozen", "\x074983B3");
+ g_hColors.SetString("fuchsia", "\x07FF00FF");
+ g_hColors.SetString("fullblue", "\x070000FF");
+ g_hColors.SetString("fullred", "\x07FF0000");
+ g_hColors.SetString("gainsboro", "\x07DCDCDC");
+ g_hColors.SetString("genuine", "\x074D7455");
+ g_hColors.SetString("ghostwhite", "\x07F8F8FF");
+ g_hColors.SetString("gold", "\x07FFD700");
+ g_hColors.SetString("goldenrod", "\x07DAA520");
+ g_hColors.SetString("gray", "\x07CCCCCC");
+ g_hColors.SetString("grey", "\x07CCCCCC");
+ g_hColors.SetString("green", "\x073EFF3E");
+ g_hColors.SetString("greenyellow", "\x07ADFF2F");
+ g_hColors.SetString("haunted", "\x0738F3AB");
+ g_hColors.SetString("honeydew", "\x07F0FFF0");
+ g_hColors.SetString("hotpink", "\x07FF69B4");
+ g_hColors.SetString("immortal", "\x07E4AE33");
+ g_hColors.SetString("indianred", "\x07CD5C5C");
+ g_hColors.SetString("indigo", "\x074B0082");
+ g_hColors.SetString("ivory", "\x07FFFFF0");
+ g_hColors.SetString("khaki", "\x07F0E68C");
+ g_hColors.SetString("lavender", "\x07E6E6FA");
+ g_hColors.SetString("lavenderblush", "\x07FFF0F5");
+ g_hColors.SetString("lawngreen", "\x077CFC00");
+ g_hColors.SetString("legendary", "\x07D32CE6");
+ g_hColors.SetString("lemonchiffon", "\x07FFFACD");
+ g_hColors.SetString("lightblue", "\x07ADD8E6");
+ g_hColors.SetString("lightcoral", "\x07F08080");
+ g_hColors.SetString("lightcyan", "\x07E0FFFF");
+ g_hColors.SetString("lightgoldenrodyellow", "\x07FAFAD2");
+ g_hColors.SetString("lightgray", "\x07D3D3D3");
+ g_hColors.SetString("lightgrey", "\x07D3D3D3");
+ g_hColors.SetString("lightgreen", "\x0799FF99");
+ g_hColors.SetString("lightpink", "\x07FFB6C1");
+ g_hColors.SetString("lightsalmon", "\x07FFA07A");
+ g_hColors.SetString("lightseagreen", "\x0720B2AA");
+ g_hColors.SetString("lightskyblue", "\x0787CEFA");
+ g_hColors.SetString("lightslategray", "\x07778899");
+ g_hColors.SetString("lightslategrey", "\x07778899");
+ g_hColors.SetString("lightsteelblue", "\x07B0C4DE");
+ g_hColors.SetString("lightyellow", "\x07FFFFE0");
+ g_hColors.SetString("lime", "\x0700FF00");
+ g_hColors.SetString("limegreen", "\x0732CD32");
+ g_hColors.SetString("linen", "\x07FAF0E6");
+ g_hColors.SetString("magenta", "\x07FF00FF");
+ g_hColors.SetString("maroon", "\x07800000");
+ g_hColors.SetString("mediumaquamarine", "\x0766CDAA");
+ g_hColors.SetString("mediumblue", "\x070000CD");
+ g_hColors.SetString("mediumorchid", "\x07BA55D3");
+ g_hColors.SetString("mediumpurple", "\x079370D8");
+ g_hColors.SetString("mediumseagreen", "\x073CB371");
+ g_hColors.SetString("mediumslateblue", "\x077B68EE");
+ g_hColors.SetString("mediumspringgreen", "\x0700FA9A");
+ g_hColors.SetString("mediumturquoise", "\x0748D1CC");
+ g_hColors.SetString("mediumvioletred", "\x07C71585");
+ g_hColors.SetString("midnightblue", "\x07191970");
+ g_hColors.SetString("mintcream", "\x07F5FFFA");
+ g_hColors.SetString("mistyrose", "\x07FFE4E1");
+ g_hColors.SetString("moccasin", "\x07FFE4B5");
+ g_hColors.SetString("mythical", "\x078847FF");
+ g_hColors.SetString("navajowhite", "\x07FFDEAD");
+ g_hColors.SetString("navy", "\x07000080");
+ g_hColors.SetString("normal", "\x07B2B2B2");
+ g_hColors.SetString("oldlace", "\x07FDF5E6");
+ g_hColors.SetString("olive", "\x079EC34F");
+ g_hColors.SetString("olivedrab", "\x076B8E23");
+ g_hColors.SetString("orange", "\x07FFA500");
+ g_hColors.SetString("orangered", "\x07FF4500");
+ g_hColors.SetString("orchid", "\x07DA70D6");
+ g_hColors.SetString("palegoldenrod", "\x07EEE8AA");
+ g_hColors.SetString("palegreen", "\x0798FB98");
+ g_hColors.SetString("paleturquoise", "\x07AFEEEE");
+ g_hColors.SetString("palevioletred", "\x07D87093");
+ g_hColors.SetString("papayawhip", "\x07FFEFD5");
+ g_hColors.SetString("peachpuff", "\x07FFDAB9");
+ g_hColors.SetString("peru", "\x07CD853F");
+ g_hColors.SetString("pink", "\x07FFC0CB");
+ g_hColors.SetString("plum", "\x07DDA0DD");
+ g_hColors.SetString("powderblue", "\x07B0E0E6");
+ g_hColors.SetString("purple", "\x07800080");
+ g_hColors.SetString("rare", "\x074B69FF");
+ g_hColors.SetString("red", "\x07FF4040");
+ g_hColors.SetString("rosybrown", "\x07BC8F8F");
+ g_hColors.SetString("royalblue", "\x074169E1");
+ g_hColors.SetString("saddlebrown", "\x078B4513");
+ g_hColors.SetString("salmon", "\x07FA8072");
+ g_hColors.SetString("sandybrown", "\x07F4A460");
+ g_hColors.SetString("seagreen", "\x072E8B57");
+ g_hColors.SetString("seashell", "\x07FFF5EE");
+ g_hColors.SetString("selfmade", "\x0770B04A");
+ g_hColors.SetString("sienna", "\x07A0522D");
+ g_hColors.SetString("silver", "\x07C0C0C0");
+ g_hColors.SetString("skyblue", "\x0787CEEB");
+ g_hColors.SetString("slateblue", "\x076A5ACD");
+ g_hColors.SetString("slategray", "\x07708090");
+ g_hColors.SetString("slategrey", "\x07708090");
+ g_hColors.SetString("snow", "\x07FFFAFA");
+ g_hColors.SetString("springgreen", "\x0700FF7F");
+ g_hColors.SetString("steelblue", "\x074682B4");
+ g_hColors.SetString("strange", "\x07CF6A32");
+ g_hColors.SetString("tan", "\x07D2B48C");
+ g_hColors.SetString("teal", "\x07008080");
+ g_hColors.SetString("thistle", "\x07D8BFD8");
+ g_hColors.SetString("tomato", "\x07FF6347");
+ g_hColors.SetString("turquoise", "\x0740E0D0");
+ g_hColors.SetString("uncommon", "\x07B0C3D9");
+ g_hColors.SetString("unique", "\x07FFD700");
+ g_hColors.SetString("unusual", "\x078650AC");
+ g_hColors.SetString("valve", "\x07A50F79");
+ g_hColors.SetString("vintage", "\x07476291");
+ g_hColors.SetString("violet", "\x07EE82EE");
+ g_hColors.SetString("wheat", "\x07F5DEB3");
+ g_hColors.SetString("white", "\x07FFFFFF");
+ g_hColors.SetString("whitesmoke", "\x07F5F5F5");
+ g_hColors.SetString("yellow", "\x07FFFF00");
+ g_hColors.SetString("yellowgreen", "\x079ACD32");
+ }
+ else
+ {
+ g_hColors.SetString("red", "\x07");
+ g_hColors.SetString("lightred", "\x0F");
+ g_hColors.SetString("darkred", "\x02");
+ g_hColors.SetString("bluegrey", "\x0A");
+ g_hColors.SetString("blue", "\x0B");
+ g_hColors.SetString("darkblue", "\x0C");
+ g_hColors.SetString("purple", "\x03");
+ g_hColors.SetString("orchid", "\x0E");
+ g_hColors.SetString("yellow", "\x09");
+ g_hColors.SetString("gold", "\x10");
+ g_hColors.SetString("lightgreen", "\x05");
+ g_hColors.SetString("green", "\x04");
+ g_hColors.SetString("lime", "\x06");
+ g_hColors.SetString("grey", "\x08");
+ g_hColors.SetString("grey2", "\x0D");
+ }
+
+ g_hColors.SetString("engine 1", "\x01");
+ g_hColors.SetString("engine 2", "\x02");
+ g_hColors.SetString("engine 3", "\x03");
+ g_hColors.SetString("engine 4", "\x04");
+ g_hColors.SetString("engine 5", "\x05");
+ g_hColors.SetString("engine 6", "\x06");
+ g_hColors.SetString("engine 7", "\x07");
+ g_hColors.SetString("engine 8", "\x08");
+ g_hColors.SetString("engine 9", "\x09");
+ g_hColors.SetString("engine 10", "\x0A");
+ g_hColors.SetString("engine 11", "\x0B");
+ g_hColors.SetString("engine 12", "\x0C");
+ g_hColors.SetString("engine 13", "\x0D");
+ g_hColors.SetString("engine 14", "\x0E");
+ g_hColors.SetString("engine 15", "\x0F");
+ g_hColors.SetString("engine 16", "\x10");
+}
+
+stock bool HasBrackets(const char[] sSource)
+{
+ return (sSource[0] == '{' && sSource[strlen(sSource) - 1] == '}');
+}
+
+stock void StringToLower(char[] sSource)
+{
+ for (int i = 0; i < strlen(sSource); i++)
+ {
+ if (sSource[i] == '\0')
+ break;
+
+ sSource[i] = CharToLower(sSource[i]);
+ }
+}
+
+stock bool IsSource2009()
+{
+ EngineVersion iEngineVersion = GetEngineVersion();
+ return (iEngineVersion == Engine_CSS || iEngineVersion == Engine_TF2 || iEngineVersion == Engine_HL2DM || iEngineVersion == Engine_DODS);
+}
+
+stock void AddPrefixAndDefaultColor(char[] message, int size, char[] sDefaultColor = "engine 1", char[] sPrefixColor = "engine 2")
+{
+ if (g_sChatPrefix[0] != '\0' && !g_bIgnorePrefix)
+ Format(message, size, "{%s}[%s]{%s} %s", sPrefixColor, g_sChatPrefix, sDefaultColor, message);
+ else
+ Format(message, size, "{%s}%s", sDefaultColor, message);
+}
+
+stock void SendPlayerMessage(int client, char[] message, int author = 0)
+{
+ if (author > 0 && author <= MaxClients && IsClientInGame(author))
+ CSayText2(client, message, author);
+ else
+ PrintToChat(client, message);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/include/updater.inc b/sourcemod/scripting/include/updater.inc
new file mode 100644
index 0000000..f37bdf2
--- /dev/null
+++ b/sourcemod/scripting/include/updater.inc
@@ -0,0 +1,97 @@
+#if defined _updater_included
+ #endinput
+#endif
+#define _updater_included
+
+/**
+ * Adds your plugin to the updater. The URL will be updated if
+ * your plugin was previously added.
+ *
+ * @param url URL to your plugin's update file.
+ * @noreturn
+ */
+native Updater_AddPlugin(const String:url[]);
+
+/**
+ * Removes your plugin from the updater. This does not need to
+ * be called during OnPluginEnd.
+ *
+ * @noreturn
+ */
+native Updater_RemovePlugin();
+
+/**
+ * Forces your plugin to be checked for updates. The behaviour
+ * of the update is dependant on the server's configuration.
+ *
+ * @return True if an update was triggered. False otherwise.
+ * @error Plugin not found in updater.
+ */
+native bool:Updater_ForceUpdate();
+
+/**
+ * Called when your plugin is about to be checked for updates.
+ *
+ * @return Plugin_Handled to prevent checking, Plugin_Continue to allow it.
+ */
+forward Action:Updater_OnPluginChecking();
+
+/**
+ * Called when your plugin is about to begin downloading an available update.
+ *
+ * @return Plugin_Handled to prevent downloading, Plugin_Continue to allow it.
+ */
+forward Action:Updater_OnPluginDownloading();
+
+/**
+ * Called when your plugin's update files have been fully downloaded
+ * and are about to write to their proper location. This should be used
+ * to free read-only resources that require write access for your update.
+ *
+ * @note OnPluginUpdated will be called later during the same frame.
+ *
+ * @noreturn
+ */
+forward Updater_OnPluginUpdating();
+
+/**
+ * Called when your plugin's update has been completed. It is safe
+ * to reload your plugin at this time.
+ *
+ * @noreturn
+ */
+forward Updater_OnPluginUpdated();
+
+/**
+ * @brief Reloads a plugin.
+ *
+ * @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin).
+ * @noreturn
+ */
+stock ReloadPlugin(Handle:plugin=INVALID_HANDLE)
+{
+ decl String:filename[64];
+ GetPluginFilename(plugin, filename, sizeof(filename));
+ ServerCommand("sm plugins reload %s", filename);
+}
+
+
+public SharedPlugin:__pl_updater =
+{
+ name = "updater",
+ file = "updater.smx",
+#if defined REQUIRE_PLUGIN
+ required = 1,
+#else
+ required = 0,
+#endif
+};
+
+#if !defined REQUIRE_PLUGIN
+public __pl_updater_SetNTVOptional()
+{
+ MarkNativeAsOptional("Updater_AddPlugin");
+ MarkNativeAsOptional("Updater_RemovePlugin");
+ MarkNativeAsOptional("Updater_ForceUpdate");
+}
+#endif