From 01f65c9ee5687ca3222a24d87e4d415c381e341f Mon Sep 17 00:00:00 2001 From: mbalex Date: Sat, 4 Oct 2008 22:59:11 +0200 Subject: [PATCH] Initial check in. --- Makefile | 32 + bin/spcomp | Bin 0 -> 351855 bytes src/include/admin.inc | 622 ++++++++++++++++ src/include/adminmenu.inc | 153 ++++ src/include/adt.inc | 39 + src/include/adt_array.inc | 283 ++++++++ src/include/adt_trie.inc | 158 ++++ src/include/banning.inc | 157 ++++ src/include/bitbuffer.inc | 325 +++++++++ src/include/clients.inc | 684 +++++++++++++++++ src/include/commandfilters.inc | 134 ++++ src/include/console.inc | 779 ++++++++++++++++++++ src/include/core.inc | 141 ++++ src/include/cstrike.inc | 88 +++ src/include/datapack.inc | 142 ++++ src/include/dbi.inc | 633 ++++++++++++++++ src/include/entity.inc | 677 +++++++++++++++++ src/include/entity_prop_stocks.inc | 324 +++++++++ src/include/events.inc | 243 +++++++ src/include/files.inc | 388 ++++++++++ src/include/float.inc | 415 +++++++++++ src/include/functions.inc | 489 +++++++++++++ src/include/geoip.inc | 102 +++ src/include/hacks.inc | 199 +++++ src/include/halflife.inc | 544 ++++++++++++++ src/include/handles.inc | 96 +++ src/include/helpers.inc | 294 ++++++++ src/include/keyvalues.inc | 429 +++++++++++ src/include/lang.inc | 96 +++ src/include/logging.inc | 153 ++++ src/include/market.inc | 62 ++ src/include/menus.inc | 787 ++++++++++++++++++++ src/include/profiler.inc | 77 ++ src/include/regex.inc | 173 +++++ src/include/sdktools.inc | 212 ++++++ src/include/sdktools_engine.inc | 69 ++ src/include/sdktools_entinput.inc | 116 +++ src/include/sdktools_entoutput.inc | 91 +++ src/include/sdktools_functions.inc | 310 ++++++++ src/include/sdktools_sound.inc | 442 +++++++++++ src/include/sdktools_stocks.inc | 76 ++ src/include/sdktools_stringtables.inc | 180 +++++ src/include/sdktools_tempents.inc | 229 ++++++ src/include/sdktools_tempents_stocks.inc | 458 ++++++++++++ src/include/sdktools_trace.inc | 367 ++++++++++ src/include/sdktools_voice.inc | 92 +++ src/include/sorting.inc | 176 +++++ src/include/sourcebans.inc | 31 + src/include/sourcemod.inc | 554 ++++++++++++++ src/include/string.inc | 557 ++++++++++++++ src/include/textparse.inc | 203 ++++++ src/include/tf2.inc | 121 ++++ src/include/tf2_stocks.inc | 219 ++++++ src/include/timers.inc | 208 ++++++ src/include/topmenus.inc | 290 ++++++++ src/include/usermessages.inc | 200 +++++ src/include/vector.inc | 188 +++++ src/include/version.inc | 42 ++ src/include/zr.inc | 42 ++ src/zombiereloaded.sp | 209 ++++++ src/zr/ambience.inc | 97 +++ src/zr/classes.inc | 295 ++++++++ src/zr/commands.inc | 118 +++ src/zr/cvars.inc | 191 +++++ src/zr/damagecontrol.inc | 178 +++++ src/zr/event.inc | 465 ++++++++++++ src/zr/global.inc | 36 + src/zr/menu.inc | 175 +++++ src/zr/models.inc | 139 ++++ src/zr/offsets.inc | 190 +++++ src/zr/overlays.inc | 47 ++ src/zr/sayhooks.inc | 346 +++++++++ src/zr/translation.inc | 118 +++ src/zr/weaponrestrict.inc | 261 +++++++ src/zr/zombie.inc | 887 +++++++++++++++++++++++ src/zr/zombiereloaded.inc | 177 +++++ 76 files changed, 19320 insertions(+) create mode 100755 Makefile create mode 100755 bin/spcomp create mode 100644 src/include/admin.inc create mode 100644 src/include/adminmenu.inc create mode 100644 src/include/adt.inc create mode 100644 src/include/adt_array.inc create mode 100644 src/include/adt_trie.inc create mode 100644 src/include/banning.inc create mode 100644 src/include/bitbuffer.inc create mode 100644 src/include/clients.inc create mode 100644 src/include/commandfilters.inc create mode 100644 src/include/console.inc create mode 100644 src/include/core.inc create mode 100644 src/include/cstrike.inc create mode 100644 src/include/datapack.inc create mode 100644 src/include/dbi.inc create mode 100644 src/include/entity.inc create mode 100644 src/include/entity_prop_stocks.inc create mode 100644 src/include/events.inc create mode 100644 src/include/files.inc create mode 100644 src/include/float.inc create mode 100644 src/include/functions.inc create mode 100644 src/include/geoip.inc create mode 100644 src/include/hacks.inc create mode 100644 src/include/halflife.inc create mode 100644 src/include/handles.inc create mode 100644 src/include/helpers.inc create mode 100644 src/include/keyvalues.inc create mode 100644 src/include/lang.inc create mode 100644 src/include/logging.inc create mode 100644 src/include/market.inc create mode 100644 src/include/menus.inc create mode 100644 src/include/profiler.inc create mode 100644 src/include/regex.inc create mode 100644 src/include/sdktools.inc create mode 100644 src/include/sdktools_engine.inc create mode 100644 src/include/sdktools_entinput.inc create mode 100644 src/include/sdktools_entoutput.inc create mode 100644 src/include/sdktools_functions.inc create mode 100644 src/include/sdktools_sound.inc create mode 100644 src/include/sdktools_stocks.inc create mode 100644 src/include/sdktools_stringtables.inc create mode 100644 src/include/sdktools_tempents.inc create mode 100644 src/include/sdktools_tempents_stocks.inc create mode 100644 src/include/sdktools_trace.inc create mode 100644 src/include/sdktools_voice.inc create mode 100644 src/include/sorting.inc create mode 100644 src/include/sourcebans.inc create mode 100644 src/include/sourcemod.inc create mode 100644 src/include/string.inc create mode 100644 src/include/textparse.inc create mode 100644 src/include/tf2.inc create mode 100644 src/include/tf2_stocks.inc create mode 100644 src/include/timers.inc create mode 100644 src/include/topmenus.inc create mode 100644 src/include/usermessages.inc create mode 100644 src/include/vector.inc create mode 100644 src/include/version.inc create mode 100644 src/include/zr.inc create mode 100644 src/zombiereloaded.sp create mode 100644 src/zr/ambience.inc create mode 100644 src/zr/classes.inc create mode 100644 src/zr/commands.inc create mode 100644 src/zr/cvars.inc create mode 100644 src/zr/damagecontrol.inc create mode 100644 src/zr/event.inc create mode 100644 src/zr/global.inc create mode 100644 src/zr/menu.inc create mode 100644 src/zr/models.inc create mode 100644 src/zr/offsets.inc create mode 100644 src/zr/overlays.inc create mode 100644 src/zr/sayhooks.inc create mode 100644 src/zr/translation.inc create mode 100644 src/zr/weaponrestrict.inc create mode 100644 src/zr/zombie.inc create mode 100644 src/zr/zombiereloaded.inc diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..c5eddd1 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ + +SOURCEDIR=src +BUILDDIR=build +SPCOMP=bin/spcomp + +vpath %.sp $(SOURCEDIR) +vpath %.inc $(SOURCEDIR)/include +vpath %.smx $(BUILDDIR) + +SOURCEFILES=$(SOURCEDIR)/*.sp +OBJECTS=$(patsubst %.sp, %.smx, $(notdir $(wildcard $(SOURCEFILES)))) + +all: prepare $(OBJECTS) + +prepare: prepare_newlines prepare_builddir + +prepare_newlines: + @echo "Removing windows newlines" + @find $(SOURCEDIR) -name \*.inc -exec dos2unix -p '{}' \; + @find $(SOURCEDIR) -name \*.sp -exec dos2unix -p '{}' \; + +prepare_builddir: + @echo "Creating build directory" + @mkdir -p $(BUILDDIR) + +%.smx: %.sp + $(SPCOMP) -i$(SOURCEDIR) -i$(SOURCEDIR)/include -o$(BUILDDIR)/$@ $< + +clean: + @echo "Removing build directory" + @rm -fr $(BUILDDIR) + diff --git a/bin/spcomp b/bin/spcomp new file mode 100755 index 0000000000000000000000000000000000000000..04c6cb8fe98b733ce4e35d58279731a9481e5e19 GIT binary patch literal 351855 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 R5}ekpf5Kzl`YclM{{XY%0K)(P literal 0 HcmV?d00001 diff --git a/src/include/admin.inc b/src/include/admin.inc new file mode 100644 index 0000000..ece7259 --- /dev/null +++ b/src/include/admin.inc @@ -0,0 +1,622 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: admin.inc 1409 2007-09-10 23:38:58Z dvander $ + */ + +#if defined _admin_included + #endinput +#endif +#define _admin_included + +/** + * Access levels (flags) for admins. + */ +enum AdminFlag +{ + Admin_Reservation = 0, /**< Reserved slot */ + Admin_Generic, /**< Generic admin abilities */ + Admin_Kick, /**< Kick another user */ + Admin_Ban, /**< Ban another user */ + Admin_Unban, /**< Unban another user */ + Admin_Slay, /**< Slay/kill/damage another user */ + Admin_Changemap, /**< Change the map */ + Admin_Convars, /**< Change basic convars */ + Admin_Config, /**< Change configuration */ + Admin_Chat, /**< Special chat privileges */ + Admin_Vote, /**< Special vote privileges */ + Admin_Password, /**< Set a server password */ + Admin_RCON, /**< Use RCON */ + Admin_Cheats, /**< Change sv_cheats and use its commands */ + Admin_Root, /**< All access by default */ + Admin_Custom1, /**< First custom flag type */ + Admin_Custom2, /**< Second custom flag type */ + Admin_Custom3, /**< Third custom flag type */ + Admin_Custom4, /**< Fourth custom flag type */ + Admin_Custom5, /**< Fifth custom flag type */ + Admin_Custom6, /**< Sixth custom flag type */ + /* --- */ +}; + +#define AdminFlags_TOTAL 21 /**< Total number of admin flags */ + +/** + * @section Bitwise values definitions for admin flags. + */ +#define ADMFLAG_RESERVATION (1<<0) /**< Convenience macro for Admin_Reservation as a FlagBit */ +#define ADMFLAG_GENERIC (1<<1) /**< Convenience macro for Admin_Generic as a FlagBit */ +#define ADMFLAG_KICK (1<<2) /**< Convenience macro for Admin_Kick as a FlagBit */ +#define ADMFLAG_BAN (1<<3) /**< Convenience macro for Admin_Ban as a FlagBit */ +#define ADMFLAG_UNBAN (1<<4) /**< Convenience macro for Admin_Unban as a FlagBit */ +#define ADMFLAG_SLAY (1<<5) /**< Convenience macro for Admin_Slay as a FlagBit */ +#define ADMFLAG_CHANGEMAP (1<<6) /**< Convenience macro for Admin_Changemap as a FlagBit */ +#define ADMFLAG_CONVARS (1<<7) /**< Convenience macro for Admin_Convars as a FlagBit */ +#define ADMFLAG_CONFIG (1<<8) /**< Convenience macro for Admin_Config as a FlagBit */ +#define ADMFLAG_CHAT (1<<9) /**< Convenience macro for Admin_Chat as a FlagBit */ +#define ADMFLAG_VOTE (1<<10) /**< Convenience macro for Admin_Vote as a FlagBit */ +#define ADMFLAG_PASSWORD (1<<11) /**< Convenience macro for Admin_Password as a FlagBit */ +#define ADMFLAG_RCON (1<<12) /**< Convenience macro for Admin_RCON as a FlagBit */ +#define ADMFLAG_CHEATS (1<<13) /**< Convenience macro for Admin_Cheats as a FlagBit */ +#define ADMFLAG_ROOT (1<<14) /**< Convenience macro for Admin_Root as a FlagBit */ +#define ADMFLAG_CUSTOM1 (1<<15) /**< Convenience macro for Admin_Custom1 as a FlagBit */ +#define ADMFLAG_CUSTOM2 (1<<16) /**< Convenience macro for Admin_Custom2 as a FlagBit */ +#define ADMFLAG_CUSTOM3 (1<<17) /**< Convenience macro for Admin_Custom3 as a FlagBit */ +#define ADMFLAG_CUSTOM4 (1<<18) /**< Convenience macro for Admin_Custom4 as a FlagBit */ +#define ADMFLAG_CUSTOM5 (1<<19) /**< Convenience macro for Admin_Custom5 as a FlagBit */ +#define ADMFLAG_CUSTOM6 (1<<20) /**< Convenience macro for Admin_Custom6 as a FlagBit */ + +/** + * @endsection + */ + +/** + * @section Hardcoded authentication methods + */ +#define AUTHMETHOD_STEAM "steam" /**< SteamID based authentication */ +#define AUTHMETHOD_IP "ip" /**< IP based authentication */ +#define AUTHMETHOD_NAME "name" /**< Name based authentication */ + +/** + * @endsection + */ + +/** + * Access override types. + */ +enum OverrideType +{ + Override_Command = 1, /**< Command */ + Override_CommandGroup, /**< Command group */ +}; + +/** + * Access override rules. + */ +enum OverrideRule +{ + Command_Deny = 0, + Command_Allow = 1, +}; + +/** + * DEPRECATED, do not use. + */ +enum ImmunityType +{ + Immunity_Default = 1, /**< Deprecated. */ + Immunity_Global, /**< Deprecated. */ +}; + +/** + * Identifies a unique entry in the group permissions cache. These are not Handles. + */ +enum GroupId +{ + INVALID_GROUP_ID = -1, /**< An invalid/nonexistant group */ +}; + +/** + * Identifies a unique entry in the admin permissions cache. These are not Handles. + */ +enum AdminId +{ + INVALID_ADMIN_ID = -1, /**< An invalid/nonexistant admin */ +}; + +/** + * Methods of computing access permissions. + */ +enum AdmAccessMode +{ + Access_Real, /**< Access the user has inherently */ + Access_Effective, /**< Access the user has from their groups */ +}; + +/** + * Represents the various cache regions. + */ +enum AdminCachePart +{ + AdminCache_Overrides = 0, /**< Global overrides */ + AdminCache_Groups = 1, /**< All groups (automatically invalidates admins too) */ + AdminCache_Admins = 2, /**< All admins */ +}; + +/** + * Called when part of the cache which needs to be rebuilt. + * + * @param part Part of the admin cache to rebuild. + */ +forward OnRebuildAdminCache(AdminCachePart:part); + +/** + * Tells the admin system to dump a portion of the cache. + * + * @param part Part of the cache to dump. Specifying groups also dumps admins. + * @param rebuild If true, the rebuild forwards will fire. + * @noreturn + */ +native DumpAdminCache(AdminCachePart:part, bool:rebuild); + +/** + * Adds a global command flag override. Any command registered with this name + * will assume the new flag. This is applied retroactively as well. + * + * @param cmd String containing command name (case sensitive). + * @param type Override type (specific command or group). + * @param flags New admin flag. + * @noreturn + */ +native AddCommandOverride(const String:cmd[], OverrideType:type, flags); + +/** + * Returns a command override. + * + * @param cmd String containing command name (case sensitive). + * @param type Override type (specific command or group). + * @param flags By-reference cell to store the flag (undefined if not found). + * @return True if there is an override, false otherwise. + */ +native bool:GetCommandOverride(const String:cmd[], OverrideType:type, &flags); + +/** + * Unsets a command override. + * + * @param cmd String containing command name (case sensitive). + * @param type Override type (specific command or group). + * @noreturn + */ +native UnsetCommandOverride(const String:cmd[], OverrideType:type); + +/** + * Adds a new group. Name must be unique. + * + * @param group_name String containing the group name. + * @return A new group id, INVALID_GROUP_ID if it already exists. + */ +native GroupId:CreateAdmGroup(const String:group_name[]); + +/** + * Finds a group by name. + * + * @param group_name String containing the group name. + * @return A group id, or INVALID_GROUP_ID if not found. + */ +native GroupId:FindAdmGroup(const String:group_name[]); + +/** + * Adds or removes a flag from a group's flag set. + * @note These are called "add flags" because they add to a user's flags. + * + * @param id Group id. + * @param flag Admin flag to toggle. + * @param enabled True to set the flag, false to unset/disable. + * @noreturn + */ +native SetAdmGroupAddFlag(GroupId:id, AdminFlag:flag, bool:enabled); + +/** + * Gets the set value of an add flag on a group's flag set. + * @note These are called "add flags" because they add to a user's flags. + * + * @param id Group id. + * @param flag Admin flag to retrieve. + * @return True if enabled, false otherwise, + */ +native bool:GetAdmGroupAddFlag(GroupId:id, AdminFlag:flag); + +/** + * Returns the flag set that is added to a user from their group. + * @note These are called "add flags" because they add to a user's flags. + * + * @param id GroupId of the group. + * @return Bitstring containing the flags enabled. + */ +native GetAdmGroupAddFlags(GroupId:id); + +/** + * @deprecated Functionality removed. + */ +#pragma deprecated Use SetAdmGroupImmunityLevel() instead. +native SetAdmGroupImmunity(GroupId:id, ImmunityType:type, bool:enabled); + +/** + * @deprecated Functionality removed. + */ +#pragma deprecated Use GetAdmGroupImmunityLevel() instead. +native bool:GetAdmGroupImmunity(GroupId:id, ImmunityType:type); + +/** + * Adds immunity to a specific group. + * + * @param id Group id. + * @param other_id Group id to receive immunity to. + * @noreturn + */ +native SetAdmGroupImmuneFrom(GroupId:id, GroupId:other_id); + +/** + * Returns the number of specific group immunities. + * + * @param id Group id. + * @return Number of group immunities. + */ +native GetAdmGroupImmuneCount(GroupId:id); + +/** + * Returns a group that this group is immune to given an index. + * + * @param id Group id. + * @param number Index from 0 to N-1, from GetAdmGroupImmuneCount(). + * @return GroupId that this group is immune to, or INVALID_GROUP_ID on failure. + */ +native GroupId:GetAdmGroupImmuneFrom(GroupId:id, number); + +/** + * Adds a group-specific override type. + * + * @param id Group id. + * @param name String containing command name (case sensitive). + * @param type Override type (specific command or group). + * @param rule Override allow/deny setting. + * @noreturn + */ +native AddAdmGroupCmdOverride(GroupId:id, const String:name[], OverrideType:type, OverrideRule:rule); + +/** + * Retrieves a group-specific command override. + * + * @param id Group id. + * @param name String containing command name (case sensitive). + * @param type Override type (specific command or group). + * @param rule Optional pointer to store allow/deny setting. + * @return True if an override exists, false otherwise. + */ +native bool:GetAdmGroupCmdOverride(GroupId:id, const String:name[], OverrideType:type, &OverrideRule:rule); + +/** + * Registers an authentication identity type. You normally never need to call this except for + * very specific systems. + * + * @param codename Codename to use for your authentication type. + * @noreturn + */ +native RegisterAuthIdentType(const String:name[]); + +/** + * Creates a new admin entry in the permissions cache. + * + * @param name Name for this entry (does not have to be unique). + * Specify an empty string for an anonymous admin. + */ +native AdminId:CreateAdmin(const String:name[]=""); + +/** + * Retrieves an admin's user name as made with CreateAdmin(). + * + * @note This function can return UTF-8 strings, and will safely chop UTF-8 strings. + * + * @param id AdminId of the admin. + * @param name String buffer to store name. + * @param maxlength Maximum size of string buffer. + * @return Number of bytes written. + */ +native GetAdminUsername(AdminId:id, const String:name[], maxlength); + +/** + * Binds an admin to an identity for fast lookup later on. The bind must be unique. + * + * @param id AdminId of the admin. + * @param auth Auth method to use, predefined or from RegisterAuthIdentType(). + * @param ident String containing the arbitrary, unique identity. + * @return True on success, false if the auth method was not found, + * or ident was already taken. + */ +native bool:BindAdminIdentity(AdminId:id, const String:auth[], const String:ident[]); + +/** + * Sets whether or not a flag is enabled on an admin. + * + * @param id AdminId index of the admin. + * @param flag Admin flag to use. + * @param enabled True to enable, false to disable. + * @noreturn + */ +native SetAdminFlag(AdminId:id, AdminFlag:flag, bool:enabled); + +/** + * Returns whether or not a flag is enabled on an admin. + * + * @param id AdminId index of the admin. + * @param flag Admin flag to use. + * @param mode Access mode to check. + * @return True if enabled, false otherwise. + */ +native bool:GetAdminFlag(AdminId:id, AdminFlag:flag, AdmAccessMode:mode=Access_Effective); + +/** + * Returns the bitstring of access flags on an admin. + * + * @param id AdminId index of the admin. + * @param mode Access mode to use. + * @return A bitstring containing which flags are enabled. + */ +native GetAdminFlags(AdminId:id, AdmAccessMode:mode); + +/** + * Adds a group to an admin's inherited group list. Any flags the group has + * will be added to the admin's effective flags. + * + * @param id AdminId index of the admin. + * @param gid GroupId index of the group. + * @return True on success, false on invalid input or duplicate membership. + */ +native bool:AdminInheritGroup(AdminId:id, GroupId:gid); + +/** + * Returns the number of groups this admin is a member of. + * + * @param id AdminId index of the admin. + * @return Number of groups this admin is a member of. + */ +native GetAdminGroupCount(AdminId:id); + +/** + * Returns group information from an admin. + * + * @param id AdminId index of the admin. + * @param index Group number to retrieve, from 0 to N-1, where N + * is the value of GetAdminGroupCount(id). + * @param name Buffer to store the group's name. + * Note: This will safely chop UTF-8 strings. + * @param maxlength Maximum size of the output name buffer. + * @return A GroupId index and a name pointer, or + * INVALID_GROUP_ID and NULL if an error occurred. +*/ +native GroupId:GetAdminGroup(AdminId:id, index, const String:name[], maxlength); + +/** + * Sets a password on an admin. + * + * @param id AdminId index of the admin. + * @param passwd String containing the password. + * @noreturn + */ +native SetAdminPassword(AdminId:id, const String:password[]); + +/** + * Gets an admin's password. + * + * @param id AdminId index of the admin. + * @param name Optional buffer to store the admin's password. + * @param maxlength Maximum size of the output name buffer. + * Note: This will safely chop UTF-8 strings. + * @return True if there was a password set, false otherwise. + */ +native bool:GetAdminPassword(AdminId:id, const String:buffer[]="", maxlength=0); + +/** + * Attempts to find an admin by an auth method and an identity. + * + * @param auth Auth method to try. + * @param identity Identity string to look up. + * @return An AdminId index if found, INVALID_ADMIN_ID otherwise. + */ +native AdminId:FindAdminByIdentity(const String:auth[], const String:identity[]); + +/** + * Removes an admin entry from the cache. + * + * @note This will remove any bindings to a specific user. + * + * @param id AdminId index to remove/invalidate. + * @return True on success, false otherwise. + */ +native bool:RemoveAdmin(AdminId:id); + +/** + * Converts a flag bit string to a bit array. + * + * @param bits Bit string containing the flags. + * @param array Array to write the flags to. Enabled flags will be 'true'. + * @param maxSize Maximum number of flags the array can store. + * @return Number of flags written. + */ +native FlagBitsToBitArray(bits, bool:array[], maxSize); + +/** + * Converts a flag array to a bit string. + * + * @param array Array containing true or false for each AdminFlag. + * @param maxSize Maximum size of the flag array. + * @return A bit string composed of the array bits. + */ +native FlagBitArrayToBits(const bool:array[], maxSize); + +/** + * Converts an array of flags to bits. + * + * @param array Array containing flags that are enabled. + * @param numFlags Number of flags in the array. + * @return A bit string composed of the array flags. + */ +native FlagArrayToBits(const AdminFlag:array[], numFlags); + +/** + * Converts a bit string to an array of flags. + * + * @param bits Bit string containing the flags. + * @param array Output array to write flags. + * @param maxSize Maximum size of the flag array. + * @return Number of flags written. + */ +native FlagBitsToArray(bits, AdminFlag:array[], maxSize); + +/** + * Finds a flag by its string name. + * + * @param name Flag name (like "kick"), case sensitive. + * @param flag Variable to store flag in. + * @return True on success, false if not found. + */ +native bool:FindFlagByName(const String:name[], &AdminFlag:flag); + +/** + * Finds a flag by a given character. + * + * @param c Flag ASCII character/token. + * @param flag Variable to store flag in. + * @return True on success, false if not found. + */ +native bool:FindFlagByChar(c, &AdminFlag:flag); + +/** + * Converts a string of flag characters to a bit string. + * + * @param flags Flag ASCII string. + * @param numchars Optional variable to store the number of bytes read. + * @return Bit string of ADMFLAG values. + */ +native ReadFlagString(const String:flags[], &numchars=0); + +/** + * Tests whether one admin can target another. + * + * The hueristics for this check are as follows: + * 0. If the targeting AdminId is INVALID_ADMIN_ID, targeting fails. + * 1. If the targeted AdminId is INVALID_ADMIN_ID, targeting succeeds. + * 2. If the targeted AdminId is the same as the targeting AdminId, + * (self) targeting succeeds. + * 3. If the targeting admin is root, targeting succeeds. + * 4. If the targeted admin has access higher (as interpreted by + * (sm_immunity_mode) than the targeting admin, then targeting fails. + * 5. If the targeted admin has specific immunity from the + * targeting admin via group immunities, targeting fails. + * 6. Targeting succeeds. + * + * @param admin Admin doing the targetting (may be INVALID_ADMIN_ID). + * @param target Target admin (may be INVALID_ADMIN_ID). + * @return True if targetable, false if immune. + */ +native CanAdminTarget(AdminId:admin, AdminId:target); + +/** + * Creates an admin auth method. This does not need to be called more than once + * per method, ever. + * + * @param method Name of the authentication method. + * @return True on success, false on failure. + */ +native bool:CreateAuthMethod(const String:method[]); + +/** + * Sets a group's immunity level. + * + * @param gid Group Id. + * @param level Immunity level value. + * @return Old immunity level value. + */ +native SetAdmGroupImmunityLevel(GroupId:gid, level); + +/** + * Gets a group's immunity level (defaults to 0). + * + * @param gid Group Id. + * @return Immunity level value. + */ +native GetAdmGroupImmunityLevel(GroupId:gid); + +/** + * Sets an admin's immunity level. + * + * @param id Admin Id. + * @param level Immunity level value. + * @return Old immunity level value. + */ +native SetAdminImmunityLevel(AdminId:id, level); + +/** + * Gets an admin's immunity level. + * + * @param id Admin Id. + * @return Immunity level value. + */ +native GetAdminImmunityLevel(AdminId:id); + +/** + * Converts a flag to its single bit. + * + * @param flag Flag to convert. + * @return Bit representation of the flag. + */ +stock FlagToBit(AdminFlag:flag) +{ + return (1<<_:flag); +} + +/** + * Converts a bit to an AdminFlag. + * + * @param bit Bit to convert. + * @param flag Stores the converted flag by reference. + * @return True on success, false otherwise. + */ +stock bool:BitToFlag(bit, &AdminFlag:flag) +{ + new AdminFlag:array[1]; + + if (FlagBitsToArray(bit, array, 1)) + { + flag = array[0]; + return true; + } + + return false; +} + diff --git a/src/include/adminmenu.inc b/src/include/adminmenu.inc new file mode 100644 index 0000000..9385055 --- /dev/null +++ b/src/include/adminmenu.inc @@ -0,0 +1,153 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: adminmenu.inc 1838 2008-01-04 16:05:26Z dvander $ + */ + +#if defined _adminmenu_included + #endinput +#endif +#define _adminmenu_included + +/* Decide whether topmenus should be required */ +#if !defined REQUIRE_PLUGIN + #if defined REQUIRE_EXTENSIONS + #define TEMP_REQUIRE_EXTENSIONS + #undef REQUIRE_EXTENSIONS +#endif + +#include + +/* Restore old REQUIRE_EXTENSIONS value if necessary */ +#if defined TEMP_REQUIRE_EXTENSIONS + #define REQUIRE_EXTENSIONS + #undef TEMP_REQUIRE_EXTENSIONS +#endif + +/** Category for player commands. */ +#define ADMINMENU_PLAYERCOMMANDS "PlayerCommands" +/** Category for server commands. */ +#define ADMINMENU_SERVERCOMMANDS "ServerCommands" +/** Category for voting commands. */ +#define ADMINMENU_VOTINGCOMMANDS "VotingCommands" + +/** + * Called when the admin menu is created and 3rd party plugins can grab + * the Handle or add categories. + * + * @param topmenu Handle to the admin menu's TopMenu. + * @noreturn + */ +forward OnAdminMenuCreated(Handle:topmenu); + +/** + * Called when the admin menu is ready to have items added. + * + * @param topmenu Handle to the admin menu's TopMenu. + * @noreturn + */ +forward OnAdminMenuReady(Handle:topmenu); + +/** + * Retrieves the Handle to the admin top menu. + * + * @return Handle to the admin menu's TopMenu, + * or INVALID_HANDLE if not created yet. + */ +native Handle:GetAdminTopMenu(); + +/** + * Adds targets to an admin menu. + * + * Each client is displayed as: name (userid) + * Each item contains the userid as a string for its info. + * + * @param menu Menu Handle. + * @param source_client Source client, or 0 to ignore immunity. + * @param in_game_only True to only select in-game players. + * @param alive_only True to only select alive players. + * @return Number of clients added. + */ +native AddTargetsToMenu(Handle:menu, + source_client, + bool:in_game_only=true, + bool:alive_only=false); + +/** + * Adds targets to an admin menu. + * + * Each client is displayed as: name (userid) + * Each item contains the userid as a string for its info. + * + * @param menu Menu Handle. + * @param source_client Source client, or 0 to ignore immunity. + * @param flags COMMAND_FILTER flags from commandfilters.inc. + * @return Number of clients added. + */ +native AddTargetsToMenu2(Handle:menu, source_client, flags); + +/** + * Re-displays the admin menu to a client after selecting an item. + * Auto-aborts if the Handle is invalid. + * + * @param topmenu TopMenu Handle. + * @param client Client index. + * @return True on success, false on failure. + */ +stock bool:RedisplayAdminMenu(Handle:topmenu, client) +{ + if (topmenu == INVALID_HANDLE) + { + return false; + } + + return DisplayTopMenu(topmenu, client, TopMenuPosition_LastCategory); +} + +/* DO NOT EDIT BELOW THIS LINE */ + +public SharedPlugin:__pl_adminmenu = +{ + name = "adminmenu", + file = "adminmenu.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public __pl_adminmenu_SetNTVOptional() +{ + MarkNativeAsOptional("GetAdminTopMenu"); + MarkNativeAsOptional("AddTargetsToMenu"); + MarkNativeAsOptional("AddTargetsToMenu2"); +} +#endif diff --git a/src/include/adt.inc b/src/include/adt.inc new file mode 100644 index 0000000..185395c --- /dev/null +++ b/src/include/adt.inc @@ -0,0 +1,39 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: adt.inc 2093 2008-04-24 10:25:27Z damagedsoul $ + */ + +#if defined _adt_included + #endinput +#endif +#define _adt_included + +#include +#include diff --git a/src/include/adt_array.inc b/src/include/adt_array.inc new file mode 100644 index 0000000..65c8231 --- /dev/null +++ b/src/include/adt_array.inc @@ -0,0 +1,283 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: adt_array.inc 2093 2008-04-24 10:25:27Z damagedsoul $ + */ + +#if defined _adt_array_included + #endinput +#endif +#define _adt_array_included + +/** + * Given a maximum string size (including the null terminator), + * returns the number of cells required to fit that string. + * + * @param size Number of bytes. + * @return Minimum number of cells required to fit the byte count. + */ +stock ByteCountToCells(size) +{ + if (!size) + { + return 1; + } + + return (size + 3) / 4; +} + +/** + * Creates a dynamic global cell array. While slower than a normal array, + * it can be used globally AND dynamically, which is otherwise impossible. + * + * The contents of the array are uniform; i.e. storing a string at index X + * and then retrieving it as an integer is NOT the same as StringToInt()! + * The "blocksize" determines how many cells each array slot has; it cannot + * be changed after creation. + * + * @param blocksize The number of cells each member of the array can + * hold. For example, 32 cells is equivalent to: + * new Array[X][32] + * @param startsize Initial size of the array. Note that data will + * NOT be auto-intialized. + * @return New Handle to the array object. + */ +native Handle:CreateArray(blocksize=1, startsize=0); + +/** + * Clears an array of all entries. This is the same as ResizeArray(0). + * + * @param array Array Handle. + * @noreturn + * @error Invalid Handle. + */ +native ClearArray(Handle:array); + +/** + * Clones an array, returning a new handle with the same size and data. This should NOT + * be confused with CloneHandle. This is a completely new handle with the same data but + * no relation to the original. You MUST close it. + * + * @param array Array handle to be cloned + * @return New handle to the cloned array object + * @error Invalid Handle + */ + native Handle:CloneArray(Handle:array); + +/** + * Resizes an array. If the size is smaller than the current size, + * the array is truncated. + * + * @param array Array Handle. + * @param newsize New size. + * @noreturn + * @error Invalid Handle or out of memory. + */ +native bool:ResizeArray(Handle:array, newsize); + +/** + * Returns the array size. + * + * @param array Array Handle. + * @return Number of elements in the array. + * @error Invalid Handle. + */ +native GetArraySize(Handle:array); + +/** + * Pushes a value onto the end of an array, adding a new index. + * + * This may safely be used even if the array has a blocksize + * greater than 1. + * + * @param array Array Handle. + * @param value Value to push. + * @return Index of the new entry. + * @error Invalid Handle or out of memory. + */ +native PushArrayCell(Handle:array, any:value); + +/** + * Pushes a string onto the end of an array, truncating it + * if it is too big. + * + * @param array Array Handle. + * @param value String to push. + * @return Index of the new entry. + * @error Invalid Handle or out of memory. + */ +native PushArrayString(Handle:array, const String:value[]); + +/** + * Pushes an array of cells onto the end of an array. The cells + * are pushed as a block (i.e. the entire array sits at the index), + * rather than pushing each cell individually. + * + * @param array Array Handle. + * @param values Block of values to copy. + * @param size If not set, the number of elements copied from the array + * will be equal to the blocksize. If set higher than the + * blocksize, the operation will be truncated. + * @return Index of the new entry. + * @error Invalid Handle or out of memory. + */ +native PushArrayArray(Handle:array, const any:values[], size=-1); + +/** + * Retrieves a cell value from an array. + * + * @param array Array Handle. + * @param index Index in the array. + * @param block Optionally specify which block to read from + * (useful if the blocksize > 0). + * @param asChar Optionally read as a byte instead of a cell. + * @return Value read. + * @error Invalid Handle, invalid index, or invalid block. + */ +native any:GetArrayCell(Handle:array, index, block=0, bool:asChar=false); + +/** + * Retrieves a string value from an array. + * + * @param array Array Handle. + * @param index Index in the array. + * @param buffer Buffer to copy to. + * @param maxlength Maximum size of the buffer. + * @return Number of characters copied. + * @error Invalid Handle or invalid index. + */ +native GetArrayString(Handle:array, index, String:buffer[], maxlength); + +/** + * Retrieves an array of cells from an array. + * + * @param array Array Handle. + * @param index Index in the array. + * @param buffer Buffer to store the array in. + * @param size If not set, assumes the buffer size is equal to the + * blocksize. Otherwise, the size passed is used. + * @return Number of cells copied. + * @error Invalid Handle or invalid index. + */ +native GetArrayArray(Handle:array, index, any:buffer[], size=-1); + +/** + * Sets a cell value in an array. + * + * @param array Array Handle. + * @param index Index in the array. + * @param value Cell value to set. + * @param block Optionally specify which block to write to + * (useful if the blocksize > 0). + * @param asChar Optionally set as a byte instead of a cell. + * @noreturn + * @error Invalid Handle, invalid index, or invalid block. + */ +native any:SetArrayCell(Handle:array, index, any:value, block=0, bool:asChar=false); + +/** + * Sets a string value in an array. + * + * @param array Array Handle. + * @param index Index in the array. + * @param value String value to set. + * @return Number of characters copied. + * @error Invalid Handle or invalid index. + */ +native SetArrayString(Handle:array, index, const String:buffer[]); + +/** + * Sets an array of cells in an array. + * + * @param array Array Handle. + * @param index Index in the array. + * @param buffer Array to copy. + * @param size If not set, assumes the buffer size is equal to the + * blocksize. Otherwise, the size passed is used. + * @return Number of cells copied. + * @error Invalid Handle or invalid index. + */ +native SetArrayArray(Handle:array, index, const any:values[], size=-1); + +/** + * Shifts an array up. All array contents after and including the given + * index are shifted up by one, and the given index is then "free." + * After shifting, the contents of the given index is undefined. + * + * @param array Array Handle. + * @param index Index in the array to shift up from. + * @noreturn + * @error Invalid Handle or invalid index. + */ +native ShiftArrayUp(Handle:array, index); + +/** + * Removes an array index, shifting the entire array down from that position + * on. For example, if item 8 of 10 is removed, the last 3 items will then be + * (6,7,8) instead of (7,8,9), and all indexes before 8 will remain unchanged. + * + * @param array Array Handle. + * @param index Index in the array to remove at. + * @noreturn + * @error Invalid Handle or invalid index. + */ +native RemoveFromArray(Handle:array, index); + +/** + * Swaps two items in the array. + * + * @param array Array Handle. + * @param index1 First index. + * @param index2 Second index. + * @noreturn + * @error Invalid Handle or invalid index. + */ +native SwapArrayItems(Handle:array, index1, index2); + +/** + * Returns the index for the first occurance of the provided string. If the string + * cannot be located, -1 will be returned. + * + * @param array Array Handle. + * @param item String to search for + * @return Array index, or -1 on failure + * @error Invalid Handle + */ +native FindStringInArray(Handle:array, const String:item[]); + +/** + * Returns the index for the first occurance of the provided value. If the value + * cannot be located, -1 will be returned. + * + * @param array Array Handle. + * @param item Value to search for + * @return Array index, or -1 on failure + * @error Invalid Handle + */ +native FindValueInArray(Handle:array, any:item); diff --git a/src/include/adt_trie.inc b/src/include/adt_trie.inc new file mode 100644 index 0000000..37fcee1 --- /dev/null +++ b/src/include/adt_trie.inc @@ -0,0 +1,158 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: adt_trie.inc 2093 2008-04-24 10:25:27Z damagedsoul $ + */ + +#if defined _adt_trie_included + #endinput +#endif +#define _adt_trie_included + +/** + * Creates a Trie structure. A trie is a data storage object that maps any value to a + * string of text. It features very fast lookup and deletion, but grows very slow for + * insertion once tens of thousands of items are added. + * + * Keys in Tries are unique. That is, each key may only have one value. Unlike arrays, + * Tries cannot be iterated right now. Since the contents are known to be unique, to + * work around this, you can use ADT Arrays to store a list of keys known to be in a + * Trie. + * + * @return New Trie Handle, which must be freed via CloseHandle(). + */ +native Handle:CreateTrie(); + +/** + * Sets a value in a Trie, either inserting a new entry or replacing an old one. + * + * @param trie Trie Handle. + * @param key Key string. + * @param value Value to store at this key. + * @param replace If false, operation will fail if the key is already set. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:SetTrieValue(Handle:trie, const String:key[], any:value, bool:replace=true); + +/** + * Sets an array value in a Trie, either inserting a new entry or replacing an old one. + * + * @param trie Trie Handle. + * @param key Key string. + * @param array Array to store. + * @param num_items Number of items in the array. + * @param replace If false, operation will fail if the key is already set. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:SetTrieArray(Handle:trie, const String:key[], const any:array[], num_items, bool:replace=true); + +/** + * Sets a string value in a Trie, either inserting a new entry or replacing an old one. + * + * @param trie Trie Handle. + * @param key Key string. + * @param array Array to store. + * @param num_items Number of items in the array. + * @param replace If false, operation will fail if the key is already set. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:SetTrieString(Handle:trie, const String:key[], const String:value[], bool:replace=true); + +/** + * Retrieves a value in a Trie. + * + * @param trie Trie Handle. + * @param key Key string. + * @param val Variable to store value. + * @return True on success. False if the key is not set, or the key is set + * as an array or string (not a value). + * @error Invalid Handle. + */ +native bool:GetTrieValue(Handle:trie, const String:key[], &any:value); + +/** + * Retrieves an array in a Trie. + * + * @param trie Trie Handle. + * @param key Key string. + * @param array Buffer to store array. + * @param max_size Maximum size of array buffer. + * @param size Optional parameter to store the number of elements written to the buffer. + * @return True on success. False if the key is not set, or the key is set + * as a value or string (not an array). + * @error Invalid Handle. + */ +native bool:GetTrieArray(Handle:trie, const String:key[], any:array[], max_size, &size=0); + +/** + * Retrieves a string in a Trie. + * + * @param trie Trie Handle. + * @param key Key string. + * @param value Buffer to store value. + * @param max_size Maximum size of string buffer. + * @param size Optional parameter to store the number of bytes written to the buffer. + * @return True on success. False if the key is not set, or the key is set + * as a value or array (not a string). + * @error Invalid Handle. + */ +native bool:GetTrieString(Handle:trie, const String:key[], String:value[], max_size, &size=0); + +/** + * Removes a key entry from a Trie. + * + * @param trie Trie Handle. + * @param key Key string. + * @return True on success, false if the value was never set. + * @error Invalid Handle. + */ +native RemoveFromTrie(Handle:trie, const String:key[]); + +/** + * Clears all entries from a Trie. + * + * @param trie Trie Handle. + * @error Invalid Handle. + */ +native ClearTrie(Handle:trie); + +/** + * Retrieves the number of elements in a trie. + * + * Note that trie items are not enumerable/iteratable. If you need to + * retrieve the elements in a trie, store its keys in an ADT Array. + * + * @param trie Trie Handle. + * @return Number of elements in the trie. + * @error Invalid Handle. + */ +native GetTrieSize(Handle:trie); diff --git a/src/include/banning.inc b/src/include/banning.inc new file mode 100644 index 0000000..c3431eb --- /dev/null +++ b/src/include/banning.inc @@ -0,0 +1,157 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: banning.inc 1456 2007-09-21 14:56:18Z dvander $ + */ + +#if defined _banning_included + #endinput +#endif +#define _banning_included + +#define BANFLAG_AUTO (1<<0) /**< Auto-detects whether to ban by steamid or IP */ +#define BANFLAG_IP (1<<1) /**< Always ban by IP address */ +#define BANFLAG_AUTHID (1<<2) /**< Always ban by authstring (for BanIdentity) if possible */ +#define BANFLAG_NOKICK (1<<3) /**< Does not kick the client */ + +/** + * Called for calls to BanClient() with a non-empty command. + * + * @param client Client being banned. + * @param time Time the client is being banned for (0 = permanent). + * @param flags One if AUTHID or IP will be enabled. If AUTO is also + * enabled, it means Core autodetected which to use. + * @param reason Reason passed via BanClient(). + * @param kick_message Kick message passed via BanClient(). + * @param command Command string to identify the ban source. + * @param source Source value passed via BanClient(). + * @return Plugin_Handled to block the actual server banning. + * Kicking will still occur. + */ +forward Action:OnBanClient(client, + time, + flags, + const String:reason[], + const String:kick_message[], + const String:command[], + any:source); + +/** + * Called for calls to BanIdentity() with a non-empty command. + * + * @param identity Identity string being banned (authstring or ip). + * @param time Time the client is being banned for (0 = permanent). + * @param flags Ban flags (only IP or AUTHID are valid here). + * @param reason Reason passed via BanIdentity(). + * @param command Command string to identify the ban source. + * @param source Source value passed via BanIdentity(). + * @return Plugin_Handled to block the actual server banning. + */ +forward Action:OnBanIdentity(const String:identity[], + time, + flags, + const String:reason[], + const String:command[], + any:source); + +/** + * Called for calls to RemoveBan() with a non-empty command. + * + * @param identity Identity string being banned (authstring or ip). + * @param flags Ban flags (only IP or AUTHID are valid here). + * @param command Command string to identify the ban source. + * @param source Source value passed via BanIdentity(). + * @return Plugin_Handled to block the actual server banning. + */ +forward Action:OnRemoveBan(const String:identity[], + flags, + const String:command[], + any:source); + +/** + * Bans a client. + * + * @param client Client being banned. + * @param time Time (in minutes) to ban (0 = permanent). + * @param flags Flags for controlling the ban mechanism. If AUTHID + * is set and no AUTHID is available, the ban will fail + * unless AUTO is also flagged. + * @param reason Reason to ban the client for. + * @param kick_message Message to display to the user when kicking. + * @param command Command string to identify the source. If this is left + * empty, then the OnBanClient forward will not be called. + * @param source A source value that could be interpreted as a player + * index of any sort (not actually checked by Core). + * @return True on success, false on failure. + * @error Invalid client index or client not in game. + */ +native bool:BanClient(client, + time, + flags, + const String:reason[], + const String:kick_message[]="", + const String:command[]="", + any:source=0); + +/** + * Bans an identity (either an IP address or auth string). + * + * @param identity String to ban (ip or authstring). + * @param time Time to ban for (0 = permanent). + * @param flags Flags (only IP and AUTHID are valid flags here). + * @param reason Ban reason string. + * @param command Command string to identify the source. If this is left + * empty, then the OnBanIdentity forward will not be called. + * @param source A source value that could be interpreted as a player + * index of any sort (not actually checked by Core). + * @return True on success, false on failure. + */ +native bool:BanIdentity(const String:identity[], + time, + flags, + const String:reason[], + const String:command[]="", + any:source=0); + +/** + * Removes a ban that was written to the server (either in memory or on disk). + * + * @param identity String to unban (ip or authstring). + * @param flags Flags (only IP and AUTHID are valid flags here). + * @param command Command strnig to identify the source. If this is left + * empty, then OnRemoveBan will not be called. + * @param source A source value that could be interpreted as a player + * index of any sort (not actually checked by Core). + * @return True on success, false on failure. + */ +native bool:RemoveBan(const String:identity[], + flags, + const String:command[]="", + any:source=0); + diff --git a/src/include/bitbuffer.inc b/src/include/bitbuffer.inc new file mode 100644 index 0000000..b8741d3 --- /dev/null +++ b/src/include/bitbuffer.inc @@ -0,0 +1,325 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: bitbuffer.inc 2038 2008-04-11 17:22:19Z damagedsoul $ + */ + +#if defined _bitbuffer_included + #endinput +#endif +#define _bitbuffer_included + +/** + * Writes a single bit to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param bit Bit to write (true for 1, false for 0). + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteBool(Handle:bf, bool:bit); + +/** + * Writes a byte to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param byte Byte to write (value will be written as 8bit). + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteByte(Handle:bf, byte); + +/** + * Writes a byte to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param chr Character to write. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteChar(Handle:bf, chr); + +/** + * Writes a 16bit integer to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param num Integer to write (value will be written as 16bit). + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteShort(Handle:bf, num); + +/** + * Writes a 16bit unsigned integer to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param num Integer to write (value will be written as 16bit). + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteWord(Handle:bf, num); + +/** + * Writes a normal integer to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param num Integer to write (value will be written as 32bit). + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteNum(Handle:bf, num); + +/** + * Writes a floating point number to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param num Number to write. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteFloat(Handle:bf, Float:num); + +/** + * Writes a string to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param string Text string to write. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteString(Handle:bf, const String:string[]); + +/** + * Writes an entity to a writable bitbuffer (bf_write). + * @note This is a wrapper around BfWriteShort(). + * + * @param bf bf_write handle to write to. + * @param ent Entity index to write. + * @noreturn + * @error Invalid or incorrect Handle, or invalid entity. + */ +native BfWriteEntity(Handle:bf, ent); + +/** + * Writes a bit angle to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param angle Angle to write. + * @param numBits Optional number of bits to use. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteAngle(Handle:bf, Float:angle, numBits=8); + +/** + * Writes a coordinate to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param coord Coordinate to write. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteCoord(Handle:bf, Float:coord); + +/** + * Writes a 3D vector of coordinates to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param coord Coordinate array to write. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteVecCoord(Handle:bf, Float:coord[3]); + +/** + * Writes a 3D normal vector to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param vec Vector to write. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteVecNormal(Handle:bf, Float:vec[3]); + +/** + * Writes a 3D angle vector to a writable bitbuffer (bf_write). + * + * @param bf bf_write handle to write to. + * @param angles Angle vector to write. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfWriteAngles(Handle:bf, Float:angles[3]); + +/** + * Reads a single bit from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Bit value read. + * @error Invalid or incorrect Handle. + */ +native bool:BfReadBool(Handle:bf); + +/** + * Reads a byte from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Byte value read (read as 8bit). + * @error Invalid or incorrect Handle. + */ +native BfReadByte(Handle:bf); + +/** + * Reads a character from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Character value read. + * @error Invalid or incorrect Handle. + */ +native BfReadChar(Handle:bf); + +/** + * Reads a 16bit integer from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Integer value read (read as 16bit). + * @error Invalid or incorrect Handle. + */ +native BfReadShort(Handle:bf); + +/** + * Reads a 16bit unsigned integer from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Integer value read (read as 16bit). + * @error Invalid or incorrect Handle. + */ +native BfReadWord(Handle:bf); + +/** + * Reads a normal integer to a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Integer value read (read as 32bit). + * @error Invalid or incorrect Handle. + */ +native BfReadNum(Handle:bf); + +/** + * Reads a floating point number from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Floating point value read. + * @error Invalid or incorrect Handle. + */ +native Float:BfReadFloat(Handle:bf); + +/** + * Reads a string from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @param buffer Destination string buffer. + * @param maxlength Maximum length of output string buffer. + * @param line If true the buffer will be copied until it reaches a '\n' or a null terminator. + * @return Number of bytes written to the buffer. If the bitbuffer stream overflowed, + * that is, had no terminator before the end of the stream, then a negative + * number will be returned equal to the number of characters written to the + * buffer minus 1. The buffer will be null terminated regardless of the + * return value. + * @error Invalid or incorrect Handle. + */ +native BfReadString(Handle:bf, String:buffer[], maxlength, bool:line=false); + +/** + * Reads an entity from a readable bitbuffer (bf_read). + * @note This is a wrapper around BfReadShort(). + * + * @param bf bf_read handle to read from. + * @return Entity index read. + * @error Invalid or incorrect Handle. + */ +native BfReadEntity(Handle:bf); + +/** + * Reads a bit angle from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @param numBits Optional number of bits to use. + * @return Angle read. + * @error Invalid or incorrect Handle. + */ +native Float:BfReadAngle(Handle:bf, numBits=8); + +/** + * Reads a coordinate from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Coordinate read. + * @error Invalid or incorrect Handle. + */ +native Float:BfReadCoord(Handle:bf); + +/** + * Reads a 3D vector of coordinates from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @param coord Destination coordinate array. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfReadVecCoord(Handle:bf, Float:coord[3]); + +/** + * Reads a 3D normal vector from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @param vec Destination vector array. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfReadVecNormal(Handle:bf, Float:vec[3]); + +/** + * Reads a 3D angle vector from a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @param angles Destination angle vector. + * @noreturn + * @error Invalid or incorrect Handle. + */ +native BfReadAngles(Handle:bf, Float:angles[3]); + +/** + * Returns the number of bytes left in a readable bitbuffer (bf_read). + * + * @param bf bf_read handle to read from. + * @return Number of bytes left unread. + * @error Invalid or incorrect Handle. + */ +native BfGetNumBytesLeft(Handle:bf); diff --git a/src/include/clients.inc b/src/include/clients.inc new file mode 100644 index 0000000..eb59667 --- /dev/null +++ b/src/include/clients.inc @@ -0,0 +1,684 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: clients.inc 2362 2008-07-06 00:44:56Z dvander $ + */ + +#if defined _clients_included + #endinput +#endif +#define _clients_included + +/** + * Network flow directions. + */ +enum NetFlow +{ + NetFlow_Outgoing = 0, /**< Outgoing traffic */ + NetFlow_Incoming, /**< Incoming traffic */ + NetFlow_Both, /**< Both values added together */ +}; + +#define MAXPLAYERS 64 /**< Maximum number of players that can be in server */ +#define MAX_NAME_LENGTH 32 /**< Maximum buffer required to store a client name */ + +/** + * Called on client connection. + * + * @param client Client index. + * @param rejectmsg Buffer to store the rejection message when the connection is refused. + * @param maxlen Maximum number of characters for rejection buffer. + * @return True to validate client's connection, false to refuse it. + */ +forward bool:OnClientConnect(client, String:rejectmsg[], maxlen); + +/** + * Called when a client is entering the game. + * + * Whether a client has a steamid is undefined until OnClientAuthorized + * is called, which may occur either before or after OnClientPutInServer. + * Similarly, use OnClientPostAdminCheck() if you need to verify whether + * connecting players are admins. + * + * GetClientCount() will include clients as they are passed through this + * function, as clients are already in game at this point. + * + * @param client Client index. + * @noreturn + */ +forward OnClientPutInServer(client); + +/** + * Called when a client is disconnecting from the server. + * + * @param client Client index. + * @noreturn + */ +forward OnClientDisconnect(client); + +/** + * Called when a client is disconnected from the server. + * + * @param client Client index. + * @noreturn + */ +forward OnClientDisconnect_Post(client); + +/** + * Called when a client is sending a command. + * + * @param client Client index. + * @param args Number of arguments. + * @noreturn + */ +forward Action:OnClientCommand(client, args); + +/** + * Called whenever the client's settings are changed. + * + * @param client Client index. + * @noreturn + */ +forward OnClientSettingsChanged(client); + +/** + * Called when a client receives a Steam ID. The state of a client's + * authorization via Steam or as an admin is not guaranteed here. + * Use OnClientAuthorized() or OnClientPostAdminCheck() for those, + * respectively. + * + * This is called by bots, but the ID will be "BOT". + * + * @param client Client index. + * @param auth Client auth string. + * @noreturn + */ +forward OnClientAuthorized(client, const String:auth[]); + +/** + * Called once a client is authorized and fully in-game, but + * before admin checks are done. This can be used to override + * the default admin checks for a client. You should only use + * this for overriding; use OnClientPostAdminCheck() instead + * if you want notification. + * + * Note: If handled/blocked, PostAdminCheck must be signalled + * manually via NotifyPostAdminCheck(). + * + * This callback is gauranteed to occur on all clients, and always + * after each OnClientPutInServer() call. + * + * @param client Client index. + * @return Plugin_Handled to block admin checks. + */ +forward Action:OnClientPreAdminCheck(client); + +/** + * Called directly before OnClientPostAdminCheck() as a method to + * alter administrative permissions before plugins perform final + * post-connect operations. + * + * In general, do not use this function unless you are specifically + * attempting to change access permissions. Use OnClientPostAdminCheck() + * instead if you simply want to perform post-connect authorization + * routines. + * + * See OnClientPostAdminCheck() for more information. + * + * @param client Client index. + * @noreturn + */ +forward OnClientPostAdminFilter(client); + +/** + * Called once a client is authorized and fully in-game, and + * after all post-connection authorizations have been performed. + * + * This callback is gauranteed to occur on all clients, and always + * after each OnClientPutInServer() call. + * + * @param client Client index. + * @noreturn + */ +forward OnClientPostAdminCheck(client); + +/** + * Returns the maximum number of clients allowed on the server. This may + * return 0 if called before OnMapStart(), and thus should not be called + * in OnPluginStart(). + * + * @return Maximum number of clients allowed. + */ +native GetMaxClients(); + +/** + * Returns the client count put in the server. + * + * @param inGameOnly If false connecting players are also counted. + * @return Client count in the server. + */ +native GetClientCount(bool:inGameOnly=true); + +/** + * Returns the client's name. + * + * @param client Player index. + * @param name Buffer to store the client's name. + * @param maxlen Maximum length of string buffer (includes NULL terminator). + * @return True on success, false otherwise. + * @error If the client is not connected an error will be thrown. + */ +native bool:GetClientName(client, String:name[], maxlen); + +/** + * Retrieves a client's IP address. + * + * @param client Player index. + * @param name Buffer to store the client's ip address. + * @param maxlen Maximum length of string buffer (includes NULL terminator). + * @param remport Remove client's port from the ip string (true by default). + * @return True on success, false otherwise. + * @error If the client is not connected or the index is invalid. + */ +native bool:GetClientIP(client, String:ip[], maxlen, bool:remport=true); + +/** + * Retrieves a client's authentication string (SteamID). + * + * @param client Player index. + * @param auth Buffer to store the client's auth string. + * @param maxlen Maximum length of string buffer (includes NULL terminator). + * @return True on success, false otherwise. + * @error If the client is not connected or the index is invalid. + */ +native bool:GetClientAuthString(client, String:auth[], maxlen); + +/** + * Retrieves a client's user id, which is an index incremented for every client + * that joins the server. + * + * @param client Player index. + * @return User id of the client. + * @error If the client is not connected or the index is invalid. + */ +native GetClientUserId(client); + +/** + * Returns if a certain player is connected. + * + * @param client Player index. + * @return True if player is connected to the server, false otherwise. + */ +native bool:IsClientConnected(client); + +/** + * Returns if a certain player has entered the game. + * + * @param client Player index (index does not have to be connected). + * @return True if player has entered the game, false otherwise. + * @error Invalid client index. + */ +native bool:IsClientInGame(client); + +/** + * Returns if a client is in the "kick queue" (i.e. the client will be kicked + * shortly and thus they should not appear as valid). + * + * @param client Player index (must be connected). + * @return True if in the kick queue, false otherwise. + * @error Invalid client index. + */ +native bool:IsClientInKickQueue(client); + +/** + * Backwards compatibility stock - use IsClientInGame + * @deprecated Renamed to IsClientInGame + */ +#pragma deprecated Use IsClientInGame() instead +stock bool:IsPlayerInGame(client) +{ + return IsClientInGame(client); +} + +/** + * Returns if a certain player has been authenticated. + * + * @param client Player index. + * @return True if player has been authenticated, false otherwise. + */ +native bool:IsClientAuthorized(client); + +/** + * Returns if a certain player is a fake client. + * + * @param client Player index. + * @return True if player is a fake client, false otherwise. + */ +native bool:IsFakeClient(client); + +/** + * Returns if a certain player is an observer/spectator. + * + * @param client Player index. + * @return True if player is an obverser, false otherwise. + */ +native bool:IsClientObserver(client); + +/** + * Returns if the client is alive or dead. + * + * Note: This function was originally in SDKTools and was moved to core. + * + * @param client Player's index. + * @return True if the client is alive, false otherwise. + * @error Invalid client index, client not in game, or no mod support. + */ +native bool:IsPlayerAlive(client); + +/** + * Retrieves values from client replicated keys. + * + * @param client Player's index. + * @param key Key string. + * @param value Buffer to store value. + * @param maxlen Maximum length of valve (UTF-8 safe). + * @return True on success, false otherwise. + * @error Invalid client index, or client not connected. + */ +native bool:GetClientInfo(client, const String:key[], String:value[], maxlen); + +/** + * Retrieves a client's team index. + * + * @param client Player's index. + * @return Team index the client is on (mod specific). + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientTeam(client); + +/** + * Sets a client's AdminId. + * + * @param client Player's index. + * @param id AdminId to set. INVALID_ADMIN_ID removes admin permissions. + * @param temp True if the id should be freed on disconnect. + * @noreturn + * @error Invalid client index, client not connected, or bogus AdminId. + */ +native SetUserAdmin(client, AdminId:id, bool:temp=false); + +/** + * Retrieves a client's AdminId. + * + * @param client Player's index. + * @return AdminId of the client, or INVALID_ADMIN_ID if none. + * @error Invalid client index, or client not connected. + */ +native AdminId:GetUserAdmin(client); + +/** + * Sets access flags on a client. If the client is not an admin, + * a temporary, anonymous AdminId is given. + * + * @param client Player's index. + * @param ... Flags to set on the client. + * @noreturn + * @error Invalid client index, or client not connected. + */ +native AddUserFlags(client, AdminFlag:...); + +/** + * Removes flags from a client. If the client is not an admin, + * this has no effect. + * + * @param client Player's index. + * @param ... Flags to remove from the client. + * @noreturn + * @error Invalid client index, or client not connected. + */ +native RemoveUserFlags(client, AdminFlag:...); + +/** + * Sets access flags on a client using bits instead of flags. If the + * client is not an admin, and flags not 0, a temporary, anonymous AdminId is given. + * + * @param client Player's index. + * @param flags Bitstring of flags to set on client. + * @noreturn + */ +native SetUserFlagBits(client, flags); + +/** + * Returns client access flags. If the client is not an admin, + * the result is always 0. + * + * @param client Player's index. + * @return Flags + * @error Invalid client index, or client not connected. + */ +native GetUserFlagBits(client); + +/** + * Returns whether a user can target another user. + * This is a helper function for CanAdminTarget. + * + * @param client Player's index. + * @param target Target player's index. + * @return True if target is targettable by the player, false otherwise. + * @error Invalid or unconnected player indexers. + */ +native bool:CanUserTarget(client, target); + +/** + * Runs through the Core-defined admin authorization checks on a player. + * Has no effect if the player is already an admin. + * + * Note: This function is based on the internal cache only. + * + * @param client Client index. + * @return True if access was changed, false if it did not. + * @error Invalid client index or client not in-game AND authorized. + */ +native bool:RunAdminCacheChecks(client); + +/** + * Signals that a player has completed post-connection admin checks. + * Has no effect if the player has already had this event signalled. + * + * Note: This must be sent even if no admin id was assigned. + * + * @param client Client index. + * @noreturn + * @error Invalid client index or client not in-game AND authorized. + */ +native NotifyPostAdminCheck(client); + +/** + * Creates a fake client. + * + * @param name Name to use. + * @return Client index on success, 0 otherwise. + */ +native CreateFakeClient(const String:name[]); + +/** + * Sets a convar value on a fake client. + * + * @param client Client index. + * @param cvar ConVar name. + * @param value ConVar value. + * @noreturn + * @error Invalid client index, client not connected, + * or client not a fake client. + */ +native SetFakeClientConVar(client, const String:cvar[], const String:value[]); + +/** + * Returns the client's health. + * + * @param client Player's index. + * @return Health value. + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientHealth(client); + +/** + * Returns the client's model name. + * + * @param client Player's index. + * @param model Buffer to store the client's model name. + * @param maxlen Maximum length of string buffer (includes NULL terminator). + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientModel(client, String:model[], maxlen); + +/** + * Returns the client's weapon name. + * + * @param client Player's index. + * @param weapon Buffer to store the client's weapon name. + * @param maxlen Maximum length of string buffer (includes NULL terminator). + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientWeapon(client, String:weapon[], maxlen); + +/** + * Returns the client's max size vector. + * + * @param client Player's index. + * @param vec Destination vector to store the client's max size. + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientMaxs(client, Float:vec[3]); + +/** + * Returns the client's min size vector. + * + * @param client Player's index. + * @param vec Destination vector to store the client's min size. + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientMins(client, Float:vec[3]); + +/** + * Returns the client's position angle. + * + * @param client Player's index. + * @param ang Destination vector to store the client's position angle. + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientAbsAngles(client, Float:ang[3]); + +/** + * Returns the client's origin vector. + * + * @param client Player's index. + * @param vec Destination vector to store the client's origin vector. + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientAbsOrigin(client, Float:vec[3]); + +/** + * Returns the client's armor. + * + * @param client Player's index. + * @return Armor value. + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientArmor(client); + +/** + * Returns the client's death count. + * + * @param client Player's index. + * @return Death count. + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientDeaths(client); + +/** + * Returns the client's frag count. + * + * @param client Player's index. + * @return Frag count. + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientFrags(client); + +/** + * Returns the client's send data rate in bytes/sec. + * + * @param client Player's index. + * @return Data rate. + * @error Invalid client index, client not in game, or fake client. + */ +native GetClientDataRate(client); + +/** + * Returns if a client is timing out + * + * @param client Player's index. + * @return True if client is timing out, false otherwise. + * @error Invalid client index, client not in game, or fake client. + */ +native bool:IsClientTimingOut(client); + +/** + * Returns the client's connection time in seconds. + * + * @param client Player's index. + * @return Connection time. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetClientTime(client); + +/** + * Returns the client's current latency (RTT), more accurate than GetAvgLatency but jittering. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Latency. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetClientLatency(client, NetFlow:flow); + +/** + * Returns the client's average packet latency in seconds. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Average latency. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetClientAvgLatency(client, NetFlow:flow); + +/** + * Returns the client's average packet loss, values go from 0 to 1 (for percentages). + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Average packet loss. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetClientAvgLoss(client, NetFlow:flow); + +/** + * Returns the client's average packet choke, values go from 0 to 1 (for percentages). + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Average packet choke. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetClientAvgChoke(client, NetFlow:flow); + +/** + * Returns the client's data flow in bytes/sec. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Data flow. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetClientAvgData(client, NetFlow:flow); + +/** + * Returns the client's average packet frequency in packets/sec. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Packet frequency. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetClientAvgPackets(client, NetFlow:flow); + +/** + * Translates an userid index to the real player index. + * + * @param userid Userid value. + * @return Client value. + * @error Returns 0 if invalid userid. + */ +native GetClientOfUserId(userid); + +/** + * Disconnects a client from the server as soon as the next frame starts. + * + * Note: Originally, KickClient() was immediate. The delay was introduced + * because despite warnings, plugins were using it in ways that would crash. + * The new safe version can break cases that rely on immediate disconnects, + * but ensures that plugins do not accidentally cause crashes. + * + * If you need immediate disconnects, use KickClientEx(). + * + * Note: IsClientInKickQueue() will return true before the kick occurs. + * + * @param client Client index. + * @param format Optional formatting rules for disconnect reason. + * Note that a period is automatically appended to the string by the engine. + * @param ... Variable number of format parameters. + * @noreturn + * @error Invalid client index, or client not connected. + */ +native KickClient(client, const String:format[]="", any:...); + +/** + * Immediately disconnects a client from the server. + * + * Kicking clients from certain events or callbacks may cause crashes. If in + * doubt, create a short (0.1 second) timer to kick the client in the next + * available frame. + * + * @param client Client index. + * @param format Optional formatting rules for disconnect reason. + * Note that a period is automatically appended to the string by the engine. + * @param ... Variable number of format parameters. + * @noreturn + * @error Invalid client index, or client not connected. + */ +native KickClientEx(client, const String:format[]="", any:...); + +/** + * Changes a client's team through the mod's generic team changing function. + * On CS:S, this will kill the player. + * + * @param client Client index. + * @param team Mod-specific team index. + * @noreturn + * @error Invalid client index, client not connected, or lack of + * mod support. + */ +native ChangeClientTeam(client, team); + diff --git a/src/include/commandfilters.inc b/src/include/commandfilters.inc new file mode 100644 index 0000000..f00a654 --- /dev/null +++ b/src/include/commandfilters.inc @@ -0,0 +1,134 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: commandfilters.inc 2080 2008-04-19 00:44:11Z dvander $ + */ + +#if defined _commandfilters_included + #endinput +#endif +#define _commandfilters_included + +#define MAX_TARGET_LENGTH 64 + +#define COMMAND_FILTER_ALIVE (1<<0) /**< Only allow alive players */ +#define COMMAND_FILTER_DEAD (1<<1) /**< Only filter dead players */ +#define COMMAND_FILTER_CONNECTED (1<<2) /**< Allow players not fully in-game */ +#define COMMAND_FILTER_NO_IMMUNITY (1<<3) /**< Ignore immunity rules */ +#define COMMAND_FILTER_NO_MULTI (1<<4) /**< Do not allow multiple target patterns */ +#define COMMAND_FILTER_NO_BOTS (1<<5) /**< Do not allow bots to be targetted */ + +#define COMMAND_TARGET_NONE 0 /**< No target was found */ +#define COMMAND_TARGET_NOT_ALIVE -1 /**< Single client is not alive */ +#define COMMAND_TARGET_NOT_DEAD -2 /**< Single client is not dead */ +#define COMMAND_TARGET_NOT_IN_GAME -3 /**< Single client is not in game */ +#define COMMAND_TARGET_IMMUNE -4 /**< Single client is immune */ +#define COMMAND_TARGET_EMPTY_FILTER -5 /**< A multi-filter (such as @all) had no targets */ +#define COMMAND_TARGET_NOT_HUMAN -6 /**< Target was not human */ +#define COMMAND_TARGET_AMBIGUOUS -7 /**< Partial name had too many targets */ + +/** + * Processes a generic command target string, and resolves it to a list + * of clients or one client, based on filtering rules and a pattern. + * + * Note that you should use LoadTranslations("common.phrases") in OnPluginStart(), + * as that file is guaranteed to contain all of the translatable phrases that + * ProcessTargetString() will return. + * + * @param pattern Pattern to find clients against. + * @param admin Admin performing the action, or 0 if the server. + * @param targets Array to hold targets. + * @param max_targets Maximum size of the targets array. + * @param filter_flags Filter flags. + * @param target_name Buffer to store the target name. + * @param tn_maxlength Maximum length of the target name buffer. + * @param tn_is_ml OUTPUT: Will be true if the target name buffer is an ML phrase, + * false if it is a normal string. + * @return If a multi-target pattern was used, the number of clients found + * is returned. If a single-target pattern was used, 1 is returned + * if one valid client is found. Otherwise, a COMMAND_TARGET reason + * for failure is returned. + */ +native ProcessTargetString(const String:pattern[], + admin, + targets[], + max_targets, + filter_flags, + String:target_name[], + tn_maxlength, + &bool:tn_is_ml); + +/** + * Replies to a client with a given message describing a targetting + * failure reason. + * + * Note: The translation phrases are found in common.phrases.txt. + * + * @param client Client index, or 0 for server. + * @param reason COMMAND_TARGET reason. + * @noreturn + */ +stock ReplyToTargetError(client, reason) +{ + switch (reason) + { + case COMMAND_TARGET_NONE: + { + ReplyToCommand(client, "[SM] %t", "No matching client"); + } + case COMMAND_TARGET_NOT_ALIVE: + { + ReplyToCommand(client, "[SM] %t", "Target must be alive"); + } + case COMMAND_TARGET_NOT_DEAD: + { + ReplyToCommand(client, "[SM] %t", "Target must be dead"); + } + case COMMAND_TARGET_NOT_IN_GAME: + { + ReplyToCommand(client, "[SM] %t", "Target is not in game"); + } + case COMMAND_TARGET_IMMUNE: + { + ReplyToCommand(client, "[SM] %t", "Unable to target"); + } + case COMMAND_TARGET_EMPTY_FILTER: + { + ReplyToCommand(client, "[SM] %t", "No matching clients"); + } + case COMMAND_TARGET_NOT_HUMAN: + { + ReplyToCommand(client, "[SM] %t", "Cannot target bot"); + } + case COMMAND_TARGET_AMBIGUOUS: + { + ReplyToCommand(client, "[SM] %t", "More than one client matched"); + } + } +} diff --git a/src/include/console.inc b/src/include/console.inc new file mode 100644 index 0000000..578054a --- /dev/null +++ b/src/include/console.inc @@ -0,0 +1,779 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: console.inc 2482 2008-09-02 03:58:23Z dvander $ + */ + +#if defined _console_included + #endinput +#endif +#define _console_included + +#define INVALID_FCVAR_FLAGS (-1) + +/** + * Console variable bound values used with Get/SetConVarBounds() + */ +enum ConVarBounds +{ + ConVarBound_Upper = 0, + ConVarBound_Lower +}; + +/** + * Console variable query helper values. + */ +enum QueryCookie +{ + QUERYCOOKIE_FAILED = 0, +}; + +/** + * Reply sources for commands. + */ +enum ReplySource +{ + SM_REPLY_TO_CONSOLE = 0, + SM_REPLY_TO_CHAT = 1, +}; + +/** + * Console variable query result values. + */ +enum ConVarQueryResult +{ + ConVarQuery_Okay = 0, /**< Retrieval of client convar value was successful. */ + ConVarQuery_NotFound, /**< Client convar was not found. */ + ConVarQuery_NotValid, /**< A console command with the same name was found, but there is no convar. */ + ConVarQuery_Protected /**< Client convar was found, but it is protected. The server cannot retrieve its value. */ +}; + +/** + * @section Flags for console commands and console variables. The descriptions + * for each constant come directly from the Source SDK. + */ +#define FCVAR_NONE 0 /**< The default, no flags at all */ +#define FCVAR_UNREGISTERED (1<<0) /**< If this is set, don't add to linked list, etc. */ +#define FCVAR_LAUNCHER (1<<1) /**< Defined by launcher. */ +#define FCVAR_GAMEDLL (1<<2) /**< Defined by the game DLL. */ +#define FCVAR_CLIENTDLL (1<<3) /**< Defined by the client DLL. */ +#define FCVAR_MATERIAL_SYSTEM (1<<4) /**< Defined by the material system. */ +#define FCVAR_PROTECTED (1<<5) /**< It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value. */ +#define FCVAR_SPONLY (1<<6) /**< This cvar cannot be changed by clients connected to a multiplayer server. */ +#define FCVAR_ARCHIVE (1<<7) /**< Set to cause it to be saved to vars.rc */ +#define FCVAR_NOTIFY (1<<8) /**< Notifies players when changed. */ +#define FCVAR_USERINFO (1<<9) /**< Changes the client's info string. */ +#define FCVAR_PRINTABLEONLY (1<<10) /**< This cvar's string cannot contain unprintable characters (e.g., used for player name, etc.) */ +#define FCVAR_UNLOGGED (1<<11) /**< If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log */ +#define FCVAR_NEVER_AS_STRING (1<<12) /**< Never try to print that cvar. */ +#define FCVAR_REPLICATED (1<<13) /**< Server setting enforced on clients. */ +#define FCVAR_CHEAT (1<<14) /**< Only useable in singleplayer / debug / multiplayer & sv_cheats */ +#define FCVAR_STUDIORENDER (1<<15) /**< Defined by the studiorender system. */ +#define FCVAR_DEMO (1<<16) /**< Record this cvar when starting a demo file. */ +#define FCVAR_DONTRECORD (1<<17) /**< Don't record these command in demo files. */ +#define FCVAR_PLUGIN (1<<18) /**< Defined by a 3rd party plugin. */ +#define FCVAR_DATACACHE (1<<19) /**< Defined by the datacache system. */ +#define FCVAR_TOOLSYSTEM (1<<20) /**< Defined by an IToolSystem library */ +#define FCVAR_FILESYSTEM (1<<21) /**< Defined by the file system. */ +#define FCVAR_NOT_CONNECTED (1<<22) /**< Cvar cannot be changed by a client that is connected to a server. */ +#define FCVAR_SOUNDSYSTEM (1<<23) /**< Defined by the soundsystem library. */ +#define FCVAR_ARCHIVE_XBOX (1<<24) /**< Cvar written to config.cfg on the Xbox. */ +#define FCVAR_INPUTSYSTEM (1<<25) /**< Defined by the inputsystem DLL. */ +#define FCVAR_NETWORKSYSTEM (1<<26) /**< Defined by the network system. */ +#define FCVAR_VPHYSICS (1<<27) /**< Defined by vphysics. */ + +/** + * @endsection + */ + +/** + * Executes a server command as if it were on the server console (or RCON) + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ +native ServerCommand(const String:format[], any:...); + +/** + * Inserts a server command at the beginning of the server command buffer. + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ +native InsertServerCommand(const String:format[], any:...); + +/** + * Executes every command in the server's command buffer, rather than once per frame. + * + * @noreturn + */ +native ServerExecute(); + +/** + * Executes a client command. Note that this will not work on clients unless + * they have cl_restrict_server_commands set to 0. + * + * @param client Index of the client. + * @param fmt Format of the client command. + * @param ... Format parameters/ + * @noreturn + * @error Invalid client index, or client not connected. + */ +native ClientCommand(client, const String:fmt[], any:...); + +/** + * Executes a client command on the server without being networked. + * + * FakeClientCommand() overwrites the command tokenization buffer. This can + * cause undesired effects because future calls to GetCmdArg* will return + * data from the FakeClientCommand(), not the parent command. If you are in + * a hook where this matters (for example, a "say" hook), you should use + * FakeClientCommandEx() instead. + * + * @param client Index of the client. + * @param fmt Format of the client command. + * @param ... Format parameters + * @noreturn + * @error Invalid client index, or client not connected. + */ +native FakeClientCommand(client, const String:fmt[], any:...); + +/** + * Executes a client command on the server without being networked. The + * execution of the client command is delayed by one frame to prevent any + * re-entrancy issues that might surface with FakeClientCommand(). + * + * @param client Index of the client. + * @param fmt Format of the client command. + * @param ... Format parameters + * @noreturn + * @error Invalid client index, or client not connected. + */ +native FakeClientCommandEx(client, const String:fmt[], any:...); + +/** + * Sends a message to the server console. + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ +native PrintToServer(const String:format[], any:...); + +/** + * Sends a message to a client's console. + * + * @param client Client index. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error If the client is not connected an error will be thrown. + */ +native PrintToConsole(client, const String:format[], any:...); + +/** + * 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, PrintToChat() is used. + * + * @param client Client index, or 0 for server. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error If the client is not connected or invalid. + */ +native ReplyToCommand(client, const String:format[], any:...); + +/** + * Returns the current reply source of a command. + * + * @return ReplySource value. + */ +native ReplySource:GetCmdReplySource(); + +/** + * Sets the current reply source of a command. + * + * Only use this if you know what you are doing. You should save the old value + * and restore it once you are done. + * + * @param source New ReplySource value. + * @return Old ReplySource value. + */ +native ReplySource:SetCmdReplySource(ReplySource:source); + +/** + * Returns whether the current say hook is a chat trigger. + * + * This function is only meaningful inside say or say_team hooks. + * + * @return True if a chat trigger, false otherwise. + */ +native bool:IsChatTrigger(); + +/** + * 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. + * + * @param client Client index doing the action, or 0 for server. + * @param tag Tag to prepend to the message. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +native ShowActivity2(client, const String:tag[], const String:format[], any:...); + +/** + * 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. + * + * @param client Client index doing the action, or 0 for server. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +native ShowActivity(client, const String:format[], any:...); + +/** + * Same as ShowActivity(), except the tag parameter is used instead of + * "[SM] " (note that you must supply any spacing). + * + * @param client Client index doing the action, or 0 for server. + * @param tag Tag to display with. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +native ShowActivityEx(client, const String:tag[], const String:format[], any:...); + +/** + * Called when a server-only command is invoked. + * + * @params args Number of arguments that were in the argument string. + * @return An Action value. Not handling the command + * means that Source will report it as "not found." + */ +functag SrvCmd Action:public(args); + +/** + * Creates a server-only console command, or hooks an already existing one. + * + * Server commands are case sensitive. + * + * @param cmd Name of the command to hook or create. + * @param callback A function to use as a callback for when the command is invoked. + * @param description Optional description to use for command creation. + * @param flags Optional flags to use for command creation. + * @noreturn + * @error Command name is the same as an existing convar. + */ +native RegServerCmd(const String:cmd[], SrvCmd:callback, const String:description[]="", flags=0); + +/** + * Called when a generic console command is invoked. + * + * @param client Index of the client, or 0 from the server. + * @param args Number of arguments that were in the argument string. + * @return An Action value. Not handling the command + * means that Source will report it as "not found." + */ +functag ConCmd Action:public(client, args); + +/** + * Creates a console command, or hooks an already existing one. + * + * Console commands are case sensitive. However, if the command already exists in the game, + * the a client may enter the command in any case. SourceMod corrects for this automatically, + * and you should only hook the "real" version of the command. + * + * @param cmd Name of the command to hook or create. + * @param callback A function to use as a callback for when the command is invoked. + * @param description Optional description to use for command creation. + * @param flags Optional flags to use for command creation. + * @noreturn + * @error Command name is the same as an existing convar. + */ +native RegConsoleCmd(const String:cmd[], ConCmd:callback, const String:description[]="", flags=0); + +/** + * Creates a console command as an administrative command. If the command does not exist, + * it is created. When this command is invoked, the access rights of the player are + * automatically checked before allowing it to continue. + * + * Admin commands are case sensitive from both the client and server. + * + * @param cmd String containing command to register. + * @param callback A function to use as a callback for when the command is invoked. + * @param adminflags Administrative flags (bitstring) to use for permissions. + * @param description Optional description to use for help. + * @param group String containing the command group to use. If empty, + * the plugin's filename will be used instead. + * @param flags Optional console flags. + * @noreturn + * @error Command name is the same as an existing convar. + */ +native RegAdminCmd(const String:cmd[], + ConCmd:callback, + adminflags, + const String:description[]="", + const String:group[]="", + flags=0); + +/** + * Returns the number of arguments from the current console or server command. + * @note Unlike the HL2 engine call, this does not include the command itself. + * + * @return Number of arguments to the current command. + */ +native GetCmdArgs(); + +/** + * Retrieves a command argument given its index, from the current console or + * server command. + * @note Argument indexes start at 1; 0 retrieves the command name. + * + * @param argnum Argument number to retrieve. + * @param buffer Buffer to use for storing the string. + * @param maxlength Maximum length of the buffer. + * @return Length of string written to buffer. + */ +native GetCmdArg(argnum, String:buffer[], maxlength); + +/** + * Retrieves the entire command argument string in one lump from the current + * console or server command. + * + * @param buffer Buffer to use for storing the string. + * @param maxlength Maximum length of the buffer. + * @return Length of string written to buffer. + */ +native GetCmdArgString(String:buffer[], maxlength); + +/** + * Creates a new console variable. + * + * @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. + */ +native Handle:CreateConVar(const String:name[], const String:defaultValue[], const String:description[]="", flags=0, bool:hasMin=false, Float:min=0.0, bool:hasMax=false, Float:max=0.0); + +/** + * Searches for a console variable. + * + * @param name Name of convar to find. + * @return A handle to the convar if it is found. INVALID_HANDLE otherwise. + */ +native Handle:FindConVar(const String:name[]); + +/** + * Called when a console variable's value is changed. + * + * @param convar Handle to the convar that was changed. + * @param oldValue String containing the value of the convar before it was changed. + * @param newValue String containing the new value of the convar. + * @noreturn + */ +functag ConVarChanged public(Handle:convar, const String:oldValue[], const String:newValue[]); + +/** + * Creates a hook for when a console variable's value is changed. + * + * @param convar Handle to the convar. + * @param callback An OnConVarChanged function pointer. + * @noreturn + * @error Invalid or corrupt Handle or invalid callback function. + */ +native HookConVarChange(Handle:convar, ConVarChanged:callback); + +/** + * Removes a hook for when a console variable's value is changed. + * + * @param convar Handle to the convar. + * @param callback An OnConVarChanged function pointer. + * @noreturn + * @error Invalid or corrupt Handle, invalid callback function, or no active hook on convar. + */ +native UnhookConVarChange(Handle:convar, ConVarChanged:callback); + +/** + * Returns the boolean value of a console variable. + * + * @param convar Handle to the convar. + * @return The boolean value of the convar. + * @error Invalid or corrupt Handle. + */ +native bool:GetConVarBool(Handle:convar); + +/** + * Sets the boolean value of a console variable. + * + * @param convar Handle to the convar. + * @param value New boolean value. + * @param replicate If set to true, the new convar value will be set on all clients. + * This will only work if the convar has the FCVAR_REPLICATED flag + * and actually exists on clients. + * @param notify If set to true, clients will be notified that the convar has changed. + * This will only work if the convar has the FCVAR_NOTIFY flag. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetConVarBool(Handle:convar, bool:value, bool:replicate=false, bool:notify=false); + +/** + * Returns the integer value of a console variable. + * + * @param convar Handle to the convar. + * @return The integer value of the convar. + * @error Invalid or corrupt Handle. + */ +native GetConVarInt(Handle:convar); + +/** + * Sets the integer value of a console variable. + * + * @param convar Handle to the convar. + * @param value New integer value. + * @param replicate If set to true, the new convar value will be set on all clients. + * This will only work if the convar has the FCVAR_REPLICATED flag + * and actually exists on clients. + * @param notify If set to true, clients will be notified that the convar has changed. + * This will only work if the convar has the FCVAR_NOTIFY flag. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetConVarInt(Handle:convar, value, bool:replicate=false, bool:notify=false); + +/** + * Returns the floating point value of a console variable. + * + * @param convar Handle to the convar. + * @return The floating point value of the convar. + * @error Invalid or corrupt Handle. + */ +native Float:GetConVarFloat(Handle:convar); + +/** + * Sets the floating point value of a console variable. + * + * @param convar Handle to the convar. + * @param value New floating point value. + * @param replicate If set to true, the new convar value will be set on all clients. + * This will only work if the convar has the FCVAR_REPLICATED flag + * and actually exists on clients. + * @param notify If set to true, clients will be notified that the convar has changed. + * This will only work if the convar has the FCVAR_NOTIFY flag. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetConVarFloat(Handle:convar, Float:value, bool:replicate=false, bool:notify=false); + +/** + * Retrieves the string value of a console variable. + * + * @param convar Handle to the convar. + * @param value Buffer to store the value of the convar. + * @param maxlength Maximum length of string buffer. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native GetConVarString(Handle:convar, String:value[], maxlength); + +/** + * Sets the string value of a console variable. + * + * @param convar Handle to the convar. + * @param value New string value. + * @param replicate If set to true, the new convar value will be set on all clients. + * This will only work if the convar has the FCVAR_REPLICATED flag + * and actually exists on clients. + * @param notify If set to true, clients will be notified that the convar has changed. + * This will only work if the convar has the FCVAR_NOTIFY flag. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetConVarString(Handle:convar, const String:value[], bool:replicate=false, bool:notify=false); + +/** + * Resets the console variable to its default value. + * + * @param convar Handle to the convar. + * @param replicate If set to true, the new convar value will be set on all clients. + * This will only work if the convar has the FCVAR_REPLICATED flag + * and actually exists on clients. + * @param notify If set to true, clients will be notified that the convar has changed. + * This will only work if the convar has the FCVAR_NOTIFY flag. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native ResetConVar(Handle:convar, bool:replicate=false, bool:notify=false); + +/** + * Returns the bitstring of flags on a console variable. + * + * @param convar Handle to the convar. + * @return A bitstring containing the FCVAR_* flags that are enabled. + * @error Invalid or corrupt Handle. + */ +native GetConVarFlags(Handle:convar); + +/** + * Sets the bitstring of flags on a console variable. + * + * @param convar Handle to the convar. + * @param flags A bitstring containing the FCVAR_* flags to enable. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetConVarFlags(Handle:convar, flags); + +/** + * Retrieves the specified bound of a console variable. + * + * @param convar Handle to the convar. + * @param type Type of bound to retrieve, ConVarBound_Lower or ConVarBound_Upper. + * @param value By-reference cell to store the specified floating point bound value. + * @return True if the convar has the specified bound set, false otherwise. + * @error Invalid or corrupt Handle. + */ +native bool:GetConVarBounds(Handle:convar, ConVarBounds:type, &Float:value); + +/** + * Sets the specified bound of a console variable. + * + * @param convar Handle to the convar. + * @param type Type of bound to set, ConVarBound_Lower or ConVarBound_Upper + * @param set If set to true, convar will use specified bound. If false, bound will be removed. + * @param value Floating point value to use as the specified bound. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetConVarBounds(Handle:convar, ConVarBounds:type, bool:set, Float:value=0.0); + +/** + * Retrieves the name of a console variable. + * + * @param convar Handle to the convar. + * @param value Buffer to store the name of the convar. + * @param maxlength Maximum length of string buffer. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native GetConVarName(Handle:convar, const String:name[], maxlength); + +funcenum ConVarQueryFinished +{ + /** + * Called when a query to retrieve a client's console variable has finished. + * + * @param cookie Unique identifier of query. + * @param client Player index. + * @param result Result of query that tells one whether or not query was successful. + * See ConVarQueryResult enum for more details. + * @param convarName Name of client convar that was queried. + * @param convarValue Value of client convar that was queried if successful. This will be "" if it was not. + * @param value Value that was passed when query was started. + * @noreturn + */ + public(QueryCookie:cookie, client, ConVarQueryResult:result, const String:cvarName[], const String:cvarValue[], any:value), + + /** + * Called when a query to retrieve a client's console variable has finished. + * + * @param cookie Unique identifier of query. + * @param client Player index. + * @param result Result of query that tells one whether or not query was successful. + * See ConVarQueryResult enum for more details. + * @param convarName Name of client convar that was queried. + * @param convarValue Value of client convar that was queried if successful. This will be "" if it was not. + * @noreturn + */ + public(QueryCookie:cookie, client, ConVarQueryResult:result, const String:cvarName[], const String:cvarValue[]) +}; + +/** + * Starts a query to retrieve the value of a client's console variable. + * + * @param client Player index. + * @param name Name of client convar to query. + * @param callback A function to use as a callback when the query has finished. + * @param value Optional value to pass to the callback function. + * @return A cookie that uniquely identifies the query. + * Returns QUERYCOOKIE_FAILED on failure, such as when used on a bot. + */ +native QueryCookie:QueryClientConVar(client, const String:cvarName[], ConVarQueryFinished:callback, any:value=0); + +/** + * Gets a command iterator. Must be freed with CloseHandle(). + * + * @return A new command iterator. + */ +native Handle:GetCommandIterator(); + +/** + * Reads a command iterator, then advances to the next command if any. + * Only SourceMod specific commands are returned. + * + * @param iter Command iterator Handle. + * @param name Name buffer. + * @param nameLen Name buffer size. + * @param eflags Effective default flags of a command. + * @param desc Command description buffer. + * @param descLen Command description buffer size. + * @return True on success, false if there are no more commands. + */ +native bool:ReadCommandIterator(Handle:iter, + String:name[], + nameLen, + &eflags=0, + String:desc[]="", + descLen=0); + +/** + * Returns whether a client has access to a given command string. The string + * can also be any override string, as overrides can be independent of + * commands. This important feature essentially allows you to create custom + * flags using the override system. + * + * @param client Client index. + * @param command Command name. If the command is not found, the default + * flags are used. + * @param flags Flag string to use as a default, if the command or override + * is not found. + * @param override_only If true, SourceMod will not attempt to find a matching + * command, and it will only use the default flags specified. + * Otherwise, SourceMod will ignore the default flags if + * there is a matching admin command. + * @return True if the client has access, false otherwise. + */ +native bool:CheckCommandAccess(client, + const String:command[], + flags, + bool:override_only=false); + +/** + * Returns true if the supplied character is valid in a ConVar name. + * + * @param c Character to validate. + * @return True is valid for ConVars, false otherwise + */ +stock bool:IsValidConVarChar(c) +{ + return (c == '_' || IsCharAlpha(c) || IsCharNumeric(c)); +} + +/** + * Returns the bitstring of flags of a command. + * + * @param name Name of the command. + * @return A bitstring containing the FCVAR_* flags that are enabled + * or INVALID_FCVAR_FLAGS if command not found. + */ +native GetCommandFlags(const String:name[]); + +/** + * Sets the bitstring of flags of a command. + * + * @param name Name of the command. + * @param flags A bitstring containing the FCVAR_* flags to enable. + * @return True on success, otherwise false. + */ +native bool:SetCommandFlags(const String:name[], flags); + +/** + * Starts a ConCommandBase search, traversing the list of ConVars and + * ConCommands. If a Handle is returned, the next entry must be read + * via FindNextConCommand(). The order of the list is undefined. + * + * @param buffer Buffer to store entry name. + * @param max_size Maximum size of the buffer. + * @param isCommand Variable to store whether the entry is a command. + * If it is not a command, it is a ConVar. + * @param flags Variable to store entry flags. + * @param description Buffer to store the description, empty if no description present. + * @param descrmax_size Maximum size of the description buffer. + * @return On success, a ConCmdIter Handle is returned, which + * can be read via FindNextConCommand(), and must be + * closed via CloseHandle(). Additionally, the output + * parameters will be filled with information of the + * first ConCommandBase entry. + * On failure, INVALID_HANDLE is returned, and the + * contents of outputs is undefined. + */ +native Handle:FindFirstConCommand(String:buffer[], max_size, &bool:isCommand, &flags=0, String:description[]="", descrmax_size=0); + +/** + * Reads the next entry in a ConCommandBase iterator. + * + * @param search ConCmdIter Handle to search. + * @param buffer Buffer to store entry name. + * @param max_size Maximum size of the buffer. + * @param isCommand Variable to store whether the entry is a command. + * If it is not a command, it is a ConVar. + * @param flags Variable to store entry flags. + * @param description Buffer to store the description, empty if no description present. + * @param descrmax_size Maximum size of the description buffer. + * @return On success, the outputs are filled, the iterator is + * advanced to the next entry, and true is returned. + * If no more entries exist, false is returned, and the + * contents of outputs is undefined. + */ +native bool:FindNextConCommand(Handle:search, String:buffer[], max_size, &bool:isCommand, &flags=0, String:description[]="", descrmax_size=0); + +/** + * Replicates a convar value to a specific client. This does not change the actual convar value. + * + * @param client Client index + * @param convar ConVar handle + * @param value String value to send + * @return True on success, false on failure + * @error Invalid client index, client not in game, or client is fake + */ +native bool:SendConVarValue(client, Handle:convar, const String:value[]); diff --git a/src/include/core.inc b/src/include/core.inc new file mode 100644 index 0000000..76b792f --- /dev/null +++ b/src/include/core.inc @@ -0,0 +1,141 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: core.inc 2243 2008-06-02 05:04:02Z dvander $ + */ + +#if defined _core_included + #endinput +#endif +#define _core_included + +#include + +#define SOURCEMOD_PLUGINAPI_VERSION 3 +struct PlVers +{ + version, + String:filevers[], + String:date[], + String:time[] +}; + +/** + * Function helper values. + */ +enum Function +{ + INVALID_FUNCTION = -1, +}; + +/** + * Specifies what to do after a hook completes. + */ +enum Action +{ + Plugin_Continue = 0, /**< Continue with the original action */ + Plugin_Changed = 1, /**< Inputs or outputs have been overridden with new values */ + Plugin_Handled = 3, /**< Handle the action at the end (don't call it) */ + Plugin_Stop = 4, /**< Immediately stop the hook chain and handle the original */ +}; + +/** + * Specifies identity types. + */ +enum Identity +{ + Identity_Core = 0, + Identity_Extension = 1, + Identity_Plugin = 2 +}; + +public PlVers:__version = +{ + version = SOURCEMOD_PLUGINAPI_VERSION, + filevers = SOURCEMOD_VERSION, + date = __DATE__, + time = __TIME__ +}; + +/** + * Plugin status values. + */ +enum PluginStatus +{ + Plugin_Running=0, /**< Plugin is running */ + /* All states below are "temporarily" unexecutable */ + Plugin_Paused, /**< Plugin is loaded but paused */ + Plugin_Error, /**< Plugin is loaded but errored/locked */ + /* All states below do not have all natives */ + Plugin_Loaded, /**< Plugin has passed loading and can be finalized */ + Plugin_Failed, /**< Plugin has a fatal failure */ + Plugin_Created, /**< Plugin is created but not initialized */ + Plugin_Uncompiled, /**< Plugin is not yet compiled by the JIT */ + Plugin_BadLoad, /**< Plugin failed to load */ +}; + +/** + * Plugin information properties. + */ +enum PluginInfo +{ + PlInfo_Name, /**< Plugin name */ + PlInfo_Author, /**< Plugin author */ + PlInfo_Description, /**< Plugin description */ + PlInfo_Version, /**< Plugin verison */ + PlInfo_URL, /**< Plugin URL */ +}; + +/** + * Defines how an extension must expose itself for autoloading. + */ +struct Extension +{ + const String:name[], /**< Short name */ + const String:file[], /**< Default file name */ + bool:autoload, /**< Whether or not to auto-load */ + bool:required, /**< Whether or not to require */ +}; + +/** + * Defines how a plugin must expose itself for native requiring. + */ +struct SharedPlugin +{ + const String:name[], /**< Short name */ + const String:file[], /**< File name */ + bool:required, /**< Whether or not to require */ +}; + +public Float:NULL_VECTOR[3]; /**< Pass this into certain functions to act as a C++ NULL */ +public const String:NULL_STRING[1]; /**< pass this into certain functions to act as a C++ NULL */ + +#define AUTOLOAD_EXTENSIONS +#define REQUIRE_EXTENSIONS +#define REQUIRE_PLUGIN diff --git a/src/include/cstrike.inc b/src/include/cstrike.inc new file mode 100644 index 0000000..eb5ad84 --- /dev/null +++ b/src/include/cstrike.inc @@ -0,0 +1,88 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: cstrike.inc 1828 2007-12-24 20:41:33Z pred $ + */ + +#if defined _cstrike_included + #endinput +#endif +#define _cstrike_included + +#define CS_TEAM_NONE 0 /**< No team yet. */ +#define CS_TEAM_SPECTATOR 1 /**< Spectators. */ +#define CS_TEAM_T 2 /**< Terrorists. */ +#define CS_TEAM_CT 3 /**< Counter-Terrorists. */ + +#define CS_SLOT_PRIMARY 0 /**< Primary weapon slot. */ +#define CS_SLOT_SECONDARY 1 /**< Secondary weapon slot. */ +#define CS_SLOT_GRENADE 3 /**< Grenade slot (will only return one grenade). */ +#define CS_SLOT_C4 4 /**< C4 slot. */ + +/** + * Respawns a player. + * + * @param client Player's index. + * @noreturn + * @error Invalid client index, client not in game. + */ +native CS_RespawnPlayer(client); + +/** + * Switches the player's team. + * + * @param client Player's index. + * @param team Team index. + * @noreturn + * @error Invalid client index, client not in game. + */ +native CS_SwitchTeam(client, team); + +/** + * Do not edit below this line! + */ +public Extension:__ext_cstrike = +{ + name = "cstrike", + file = "games/game.cstrike.ext", + autoload = 0, +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_cstrike_SetNTVOptional() +{ + MarkNativeAsOptional("CS_RespawnPlayer"); + MarkNativeAsOptional("CS_SwitchTeam"); +} +#endif diff --git a/src/include/datapack.inc b/src/include/datapack.inc new file mode 100644 index 0000000..a289081 --- /dev/null +++ b/src/include/datapack.inc @@ -0,0 +1,142 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: datapack.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _datapack_included + #endinput +#endif +#define _datapack_included + +/** + * Creates a new data pack. + * + * @return A Handle to the data pack. Must be closed with CloseHandle(). + */ +native Handle:CreateDataPack(); + +/** + * Packs a normal cell into a data pack. + * + * @param pack Handle to the data pack. + * @param cell Cell to add. + * @noreturn + * @error Invalid handle. + */ +native WritePackCell(Handle:pack, cell); + +/** + * Packs a float into a data pack. + * + * @param pack Handle to the data pack. + * @param val Float to add. + * @noreturn + * @error Invalid handle. + */ +native WritePackFloat(Handle:pack, Float:val); + +/** + * Packs a string into a data pack. + * + * @param pack Handle to the data pack. + * @param str String to add. + * @noreturn + * @error Invalid handle. + */ +native WritePackString(Handle:pack, const String:str[]); + +/** + * Reads a cell from a data pack. + * + * @param pack Handle to the data pack. + * @return Cell value. + * @error Invalid handle, or bounds error. + */ +native ReadPackCell(Handle:pack); + +/** + * Reads a float from a data pack. + * + * @param pack Handle to the data pack. + * @return Float value. + * @error Invalid handle, or bounds error. + */ +native Float:ReadPackFloat(Handle:pack); + +/** + * Reads a string from a data pack. + * + * @param pack Handle to the data pack. + * @param buffer Destination string buffer. + * @param maxlen Maximum length of output string buffer. + * @noreturn + * @error Invalid handle, or bounds error. + */ +native ReadPackString(Handle:pack, String:buffer[], maxlen); + +/** + * Resets the position in a data pack. + * + * @param pack Handle to the data pack. + * @param clear If true, clears the contained data. + * @noreturn + * @error Invalid handle. + */ +native ResetPack(Handle:pack, bool:clear=false); + +/** + * Returns the read or write position in a data pack. + * + * @param pack Handle to the data pack. + * @return Numerical position in the data pack. + * @error Invalid handle. + */ +native GetPackPosition(Handle:pack); + +/** + * Sets the read/write position in a data pack. + * + * @param pack Handle to the data pack. + * @param position New position to set. + * @noreturn + * @error Invalid handle, or position is beyond the pack bounds. + */ +native SetPackPosition(Handle:pack, position); + +/** + * Returns whether or not a specified number of bytes from the data pack + * position to the end can be read. + * + * @param pack Handle to the data pack. + * @param bytes Number of bytes to simulate reading. + * @return True if can be read, false otherwise. + * @error Invalid handle. + */ +native bool:IsPackReadable(Handle:pack, bytes); diff --git a/src/include/dbi.inc b/src/include/dbi.inc new file mode 100644 index 0000000..cc60e94 --- /dev/null +++ b/src/include/dbi.inc @@ -0,0 +1,633 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: dbi.inc 1918 2008-03-02 18:01:49Z dvander $ + */ + +#if defined _dbi_included + #endinput +#endif +#define _dbi_included + +/** + * @handle Driver + * + * Contains information about an SQL driver. + */ + +/** + * @handle Database + * + * Contains information about a database connection. + */ + +/** + * @handle Query + * + * Contains information about an active query and its + * result sets. + */ + +/** + * @handle Statement : Query + * + * Extends a Query Handle and can be used as a Query Handle. + * Statement Handles are for prepared queries and contain + * their own function for binding parameters. Statement + * Handles can be used instead of database Handles in a few + * select functions. + */ + +/** + * Describes a database field fetch status. + */ +enum DBResult +{ + DBVal_Error = 0, /**< Column number/field is invalid. */ + DBVal_TypeMismatch = 1, /**< You cannot retrieve this data with this type. */ + DBVal_Null = 2, /**< Field has no data (NULL) */ + DBVal_Data = 3, /**< Field has data */ +}; + +/** + * Describes binding types. + */ +enum DBBindType +{ + DBBind_Int = 0, /**< Bind an integer. */ + DBBind_Float = 1, /**< Bind a float. */ + DBBind_String = 2, /**< Bind a string. */ +}; + +/** + * Threading priority level. + */ +enum DBPriority +{ + DBPrio_High = 0, /**< High priority. */ + DBPrio_Normal = 1, /**< Normal priority. */ + DBPrio_Low = 2, /**< Low priority. */ +}; + +/** + * Creates an SQL connection from a named configuration. + * + * @param confname Named configuration. + * @param persistent True to re-use a previous persistent connection if + * possible, false otherwise. + * @param error Error buffer. + * @param maxlength Maximum length of the error buffer. + * @return A database connection Handle, or INVALID_HANDLE on failure. + */ +native Handle:SQL_Connect(const String:confname[], bool:persistent, String:error[], maxlength); + +/** + * Creates a default SQL connection. + * + * @param error Error buffer. + * @param maxlength Maximum length of the error buffer. + * @param persistent True to re-use a previous persistent connection + * if possible, false otherwise. + * @return A database connection Handle, or INVALID_HANDLE on failure. + */ +stock Handle:SQL_DefConnect(String:error[], maxlength, bool:persistent=true) +{ + return SQL_Connect("default", persistent, error, maxlength); +} + +/** + * Creates an SQL connection from specific parameters. + * + * @param driver Driver Handle, or INVALID_HANDLE for default. + * @param host Host name. + * @param user User name. + * @param pass User password. + * @param database Database name. + * @param error Error buffer. + * @param maxlength Maximum length of the error buffer. + * @param persistent True to re-use a previous persistent connection + * if possible, false otherwise. + * @param port Optional port to specify. + * @param maxTimeout Maximum timeout in seconds if applicable. + * @return A database connection Handle, or INVALID_HANDLE on failure. + * @error Invalid driver Handle other than INVALID_HANDLE. + */ +native Handle:SQL_ConnectEx(Handle:driver, + const String:host[], + const String:user[], + const String:pass[], + const String:database[], + String:error[], + maxlength, + bool:persistent=true, + port=0, + maxTimeout=0); + +/** + * Returns if a named configuration is present in databases.cfg. + * + * @param name Configuration name. + * @return True if it exists, false otherwise. + */ +native bool:SQL_CheckConfig(const String:name[]); + +/** + * Returns a driver Handle from a name string. + * + * If the driver is not found, SourceMod will attempt + * to load an extension named dbi..ext.[dll|so]. + * + * @param name Driver identification string, or an empty + * string to return the default driver. + * @return Driver Handle, or INVALID_HANDLE on failure. + */ +native Handle:SQL_GetDriver(const String:name[]=""); + +/** + * Reads the driver of an opened database. + * + * @param database Database Handle. + * @param ident Option buffer to store the identification string. + * @param ident_length Maximum length of the buffer. + * @return Driver Handle. + */ +native Handle:SQL_ReadDriver(Handle:database, String:ident[]="", ident_length=0); + +/** + * Retrieves a driver's identification string. + * + * Example: "mysql", "sqlite" + * + * @param driver Driver Handle, or INVALID_HANDLE for the default driver. + * @param ident Identification string buffer. + * @param maxlength Maximum length of the buffer. + * @noreturn + * @error Invalid Handle other than INVALID_HANDLE. + */ +native SQL_GetDriverIdent(Handle:driver, String:ident[], maxlength); + +/** + * Retrieves a driver's product string. + * + * Example: "MySQL", "SQLite" + * + * @param driver Driver Handle, or INVALID_HANDLE for the default driver. + * @param product Product string buffer. + * @param maxlength Maximum length of the buffer. + * @noreturn + * @error Invalid Handle other than INVALID_HANDLE. + */ +native SQL_GetDriverProduct(Handle:driver, String:product[], maxlength); + +/** + * Returns the number of affected rows from the last query. + * + * @param hndl A database OR statement Handle. + * @return Number of rows affected by the last query. + * @error Invalid database or statement Handle. + */ +native SQL_GetAffectedRows(Handle:hndl); + +/** + * Returns the last query's insertion id. + * + * @param hndl A database OR statement Handle. + * @return Last query's insertion id. + * @error Invalid database or statement Handle. + */ +native SQL_GetInsertId(Handle:hndl); + +/** + * Returns the error reported by the last query. + * + * @param hndl A database OR statement Handle. + * @param error Error buffer. + * @param maxlength Maximum length of the buffer. + * @return True if there was an error, false otherwise. + * @error Invalid database or statement Handle. + */ +native bool:SQL_GetError(Handle:hndl, String:error[], maxlength); + +/** + * Escapes a database string for literal insertion. This is not needed + * for binding strings in prepared statements. + * + * Generally, database strings are inserted into queries enclosed in + * single quotes ('). If user input has a single quote in it, the + * quote needs to be escaped. This function ensures that any unsafe + * characters are safely escaped according to the database engine and + * the database's character set. + * + * @param hndl A database Handle. + * @param string String to quote. + * @param buffer Buffer to store quoted string in. + * @param maxlength Maximum length of the buffer. + * @param written Optionally returns the number of bytes written. + * @return True on success, false if buffer is not big enough. + * The buffer must be at least 2*strlen(string)+1. + * @error Invalid database or statement Handle. + */ +native bool:SQL_EscapeString(Handle:database, + const String:string[], + String:buffer[], + maxlength, + &written=0); + +/** + * This is a backwards compatibility stock. You should use SQL_EscapeString() + * instead, as this function will probably be deprecated in SourceMod 1.1. + */ +stock bool:SQL_QuoteString(Handle:database, + const String:string[], + String:buffer[], + maxlength, + &written=0) +{ + return SQL_EscapeString(database, string, buffer, maxlength, written); +} + +/** + * Executes a query and ignores the result set. + * + * @param database A database Handle. + * @param query Query string. + * @param len Optional parameter to specify the query length, in + * bytes. This can be used to send binary queries that + * have a premature terminator. + * @return True if query succeeded, false otherwise. Use + * SQL_GetError to find the last error. + * @error Invalid database Handle. + */ +native bool:SQL_FastQuery(Handle:database, const String:query[], len=-1); + +/** + * Executes a simple query and returns a new query Handle for + * receiving the results. + * + * @param database A database Handle. + * @param query Query string. + * @param len Optional parameter to specify the query length, in + * bytes. This can be used to send binary queries that + * have a premature terminator. + * @return A new Query Handle on success, INVALID_HANDLE + * otherwise. The Handle must be freed with CloseHandle(). + * @error Invalid database Handle. + */ +native Handle:SQL_Query(Handle:database, const String:query[], len=-1); + +/** + * Creates a new prepared statement query. Prepared statements can + * be executed any number of times. They can also have placeholder + * parameters, similar to variables, which can be bound safely and + * securely (for example, you do not need to quote bound strings). + * + * Statement handles will work in any function that accepts a Query handle. + * + * @param database A database Handle. + * @param query Query string. + * @param error Error buffer. + * @param maxlength Maximum size of the error buffer. + * @return A new statement Handle on success, INVALID_HANDLE + * otherwise. The Handle must be freed with CloseHandle(). + * @error Invalid database Handle. + */ +native Handle:SQL_PrepareQuery(Handle:database, const String:query[], String:error[], maxlength); + +/** + * Advances to the next set of results. + * + * In some SQL implementations, multiple result sets can exist on one query. + * This is possible in MySQL with simple queries when executing a CALL + * query. If this is the case, all result sets must be processed before + * another query is made. + * + * @param query A query Handle. + * @return True if there was another result set, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_FetchMoreResults(Handle:query); + +/** + * Returns whether or not a result set exists. This will + * return true even if 0 results were returned, but false + * on queries like UPDATE, INSERT, or DELETE. + * + * @param query A query (or statement) Handle. + * @return True if there is a result set, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_HasResultSet(Handle:query); + +/** + * Retrieves the number of rows in the last result set. + * + * @param query A query (or statement) Handle. + * @return Number of rows in the current result set. + * @error Invalid query Handle. + */ +native SQL_GetRowCount(Handle:query); + +/** + * Retrieves the number of fields in the last result set. + * + * @param query A query (or statement) Handle. + * @return Number of fields in the current result set. + * @error Invalid query Handle. + */ +native SQL_GetFieldCount(Handle:query); + +/** + * Retrieves the name of a field by index. + * + * @param query A query (or statement) Handle. + * @param field Field number (starting from 0). + * @param name Name buffer. + * @param maxlength Maximum length of the name buffer. + * @noreturn + * @error Invalid query Handle, invalid field index, or + * no current result set. + */ +native SQL_FieldNumToName(Handle:query, field, String:name[], maxlength); + +/** + * Retrieves a field index by name. + * + * @param query A query (or statement) Handle. + * @param name Name of the field (case sensitive). + * @param field Variable to store field index in. + * @return True if found, false if not found. + * @error Invalid query Handle or no current result set. + */ +native bool:SQL_FieldNameToNum(Handle:query, const String:name[], &field); + +/** + * Fetches a row from the current result set. This must be + * successfully called before any results are fetched. + * + * If this function fails, SQL_MoreResults() can be used to + * tell if there was an error or the result set is finished. + * + * @param query A query (or statement) Handle. + * @return True if a row was fetched, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_FetchRow(Handle:query); + +/** + * Returns if there are more rows. + * + * @param query A query (or statement) Handle. + * @return True if there are more rows, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_MoreRows(Handle:query); + +/** + * Rewinds a result set back to the first result. + * + * @param query A query (or statement) Handle. + * @return True on success, false otherwise. + * @error Invalid query Handle or no current result set. + */ +native bool:SQL_Rewind(Handle:query); + +/** + * Fetches a string from a field in the current row of a result set. + * If the result is NULL, an empty string will be returned. A NULL + * check can be done with the result parameter, or SQL_IsFieldNull(). + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @param buffer String buffer. + * @param maxlength Maximum size of the string buffer. + * @param result Optional variable to store the status of the return value. + * @return Number of bytes written. + * @error Invalid query Handle or field index, invalid + * type conversion requested from the database, + * or no current result set. + */ +native SQL_FetchString(Handle:query, field, String:buffer[], maxlength, &DBResult:result=DBVal_Error); + +/** + * Fetches a float from a field in the current row of a result set. + * If the result is NULL, a value of 0.0 will be returned. A NULL + * check can be done with the result parameter, or SQL_IsFieldNull(). + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @param result Optional variable to store the status of the return value. + * @return A float value. + * @error Invalid query Handle or field index, invalid + * type conversion requested from the database, + * or no current result set. + */ +native Float:SQL_FetchFloat(Handle:query, field, &DBResult:result=DBVal_Error); + +/** + * Fetches an integer from a field in the current row of a result set. + * If the result is NULL, a value of 0 will be returned. A NULL + * check can be done with the result parameter, or SQL_IsFieldNull(). + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @param result Optional variable to store the status of the return value. + * @return An integer value. + * @error Invalid query Handle or field index, invalid + * type conversion requested from the database, + * or no current result set. + */ +native SQL_FetchInt(Handle:query, field, &DBResult:result=DBVal_Error); + +/** + * Returns whether a field's data in the current row of a result set is + * NULL or not. NULL is an SQL type which means "no data." + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @return True if data is NULL, false otherwise. + * @error Invalid query Handle or field index, or no + * current result set. + */ +native bool:SQL_IsFieldNull(Handle:query, field); + +/** + * Returns the length of a field's data in the current row of a result + * set. This only needs to be called for strings to determine how many + * bytes to use. Note that the return value does not include the null + * terminator. + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @return Number of bytes for the field's data size. + * @error Invalid query Handle or field index or no + * current result set. + */ +native SQL_FetchSize(Handle:query, field); + +/** + * Binds a parameter in a prepared statement to a given integer value. + * + * @param statement A statement (prepared query) Handle. + * @param param The parameter index (starting from 0). + * @param number The number to bind. + * @param signed True to bind the number as signed, false to + * bind it as unsigned. + * @noreturn + * @error Invalid statement Handle or parameter index, or + * SQL error. + */ +native SQL_BindParamInt(Handle:statement, param, number, bool:signed=true); + +/** + * Binds a parameter in a prepared statement to a given float value. + * + * @param statement A statement (prepared query) Handle. + * @param param The parameter index (starting from 0). + * @param float The float number to bind. + * @noreturn + * @error Invalid statement Handle or parameter index, or + * SQL error. + */ +native SQL_BindParamFloat(Handle:statement, param, Float:value); + +/** + * Binds a parameter in a prepared statement to a given string value. + * + * @param statement A statement (prepared query) Handle. + * @param param The parameter index (starting from 0). + * @param value The string to bind. + * @param copy Whether or not SourceMod should copy the value + * locally if necessary. If the string contents + * won't change before calling SQL_Execute(), this + * can be set to false for optimization. + * @noreturn + * @error Invalid statement Handle or parameter index, or + * SQL error. + */ +native SQL_BindParamString(Handle:statement, param, const String:value[], bool:copy); + +/** + * Executes a prepared statement. All parameters must be bound beforehand. + * + * @param statement A statement (prepared query) Handle. + * @return True on success, false on failure. + * @error Invalid statement Handle. + */ +native bool:SQL_Execute(Handle:statement); + +/** + * Locks a database so threading operations will not interrupt. + * + * If you are using a database Handle for both threading and non-threading, + * this MUST be called before doing any set of non-threading DB operations. + * Otherwise you risk corrupting the database driver's memory or network + * connection. + * + * Leaving a lock on a database and then executing a threaded query results + * in a dead lock! Make sure to call SQL_UnlockDatabase()! + * + * If the lock cannot be acquired, the main thread will pause until the + * threaded operation has concluded. + * + * @param database A database Handle. + * @noreturn + * @error Invalid database Handle. + */ +native SQL_LockDatabase(Handle:database); + +/** + * Unlocks a database so threading operations may continue. + * + * @param database A database Handle. + * @noreturn + * @error Invalid database Handle. + */ +native SQL_UnlockDatabase(Handle:database); + +/** + * General callback for threaded SQL stuff. + * + * @param db Parent object of the Handle (or INVALID_HANDLE if none). + * @param hndl Handle to the child object (or INVALID_HANDLE if none). + * @param error Error string if there was an error. The error could be + * empty even if an error condition exists, so it is important + * to check the actual Handle value instead. + * @param data Data passed in via the original threaded invocation. + * @param + */ +functag SQLTCallback public(Handle:owner, Handle:hndl, const String:error[], any:data); + +/** + * Tells whether two database handles both point to the same database + * connection. + * + * @param hndl1 First database Handle. + * @param hndl2 Second database Handle. + * @return True if the Handles point to the same + * connection, false otherwise. + * @error Invalid Handle. + */ +native bool:SQL_IsSameConnection(Handle:hndl1, Handle:hndl2); + +/** + * Connects to a database via a thread. This can be used instead of + * SQL_Connect() if you wish for non-blocking functionality. + * + * It is not necessary to use this to use threaded queries. However, if you + * don't (or you mix threaded/non-threaded queries), you should see + * SQL_LockDatabase(). + * + * @param callback Callback; new Handle will be in hndl, owner is the driver. + * If no driver was found, the owner is INVALID_HANDLE. + * @param name Database name. + * @noreturn + */ +native SQL_TConnect(SQLTCallback:callback, const String:name[]="default", any:data=0); + +/** + * Executes a simple query via a thread. The query Handle is passed through + * the callback. + * + * The database Handle returned through the callback is always a new Handle, + * and if necessary, SQL_IsSameConnection() should be used to test against + * other conenctions. + * + * The query Handle returned through the callback is temporary and destroyed + * at the end of the callback. If you need to hold onto it, use CloneHandle(). + * + * @param database A database Handle. + * @param callback Callback; database is in "owner" and the query Handle + * is passed in "hndl". + * @param query Query string. + * @param data Extra data value to pass to the callback. + * @param prio Priority queue to use. + * @noreturn + * @error Invalid database Handle. + */ +native SQL_TQuery(Handle:database, SQLTCallback:callback, const String:query[], any:data=0, DBPriority:prio=DBPrio_Normal); diff --git a/src/include/entity.inc b/src/include/entity.inc new file mode 100644 index 0000000..26eaa96 --- /dev/null +++ b/src/include/entity.inc @@ -0,0 +1,677 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: entity.inc 1890 2008-02-22 16:51:15Z dvander $ + */ + +#if defined _entity_included + #endinput +#endif +#define _entity_included + +/** + * Property types for entities. + */ +enum PropType +{ + Prop_Send = 0, /**< This property is networked. */ + Prop_Data = 1, /**< This property is for save game data fields. */ +}; + +/** + * @section For more information on these, see the HL2SDK (public/edict.h) + */ +#define FL_EDICT_CHANGED (1<<0) /**< Game DLL sets this when the entity state changes + Mutually exclusive with FL_EDICT_PARTIAL_CHANGE. */ +#define FL_EDICT_FREE (1<<1) /**< this edict if free for reuse */ +#define FL_EDICT_FULL (1<<2) /**< this is a full server entity */ +#define FL_EDICT_FULLCHECK (0<<0) /**< call ShouldTransmit() each time, this is a fake flag */ +#define FL_EDICT_ALWAYS (1<<3) /**< always transmit this entity */ +#define FL_EDICT_DONTSEND (1<<4) /**< don't transmit this entity */ +#define FL_EDICT_PVSCHECK (1<<5) /**< always transmit entity, but cull against PVS */ +#define FL_EDICT_PENDING_DORMANT_CHECK (1<<6) +#define FL_EDICT_DIRTY_PVS_INFORMATION (1<<7) +#define FL_FULL_EDICT_CHANGED (1<<8) + +enum PropFieldType +{ + PropField_Unsupported, /**< The type is unsupported. */ + PropField_Integer, /**< Valid for SendProp and Data fields */ + PropField_Float, /**< Valid for SendProp and Data fields */ + PropField_Entity, /**< Valid for Data fields only (SendProp shows as int) */ + PropField_Vector, /**< Valid for SendProp and Data fields */ + PropField_String, /**< Valid for SendProp and Data fields */ + PropField_String_T, /**< Valid for Data fields. Read only. + Note that the size of a string_t is dynamic, and + thus FindDataMapOffs() will return the constant size + of the string_t container (which is 32 bits right now). + */ +}; + +/** + * @endsection + */ + +/** + * Returns the maximum number of entities. + * + * @return Maximum number of entities. + */ +native GetMaxEntities(); + +/** + * Returns the number of entities in the server. + * + * @return Number of entities in the server. + */ +native GetEntityCount(); + +/** + * Returns whether or not an entity is valid. Returns false + * if there is no matching CBaseEntity for this edict index. + * + * @param edict Index of the entity/edict. + * @return True if valid, false otherwise. + */ +native bool:IsValidEntity(edict); + +/** + * Returns whether or not an edict index is valid. + * + * @param edict Index of the edict. + * @return True if valid, false otherwise. + */ +native bool:IsValidEdict(edict); + +/** + * Returns whether or not an entity is a valid networkable edict. + * + * @param edict Index of the edict. + * @return True if networkable, false if invalid or not networkable. + */ +native bool:IsEntNetworkable(edict); + +/** + * Creates a new edict (the basis of a networkable entity) + * + * @return Index of the edict, 0 on failure. + */ +native CreateEdict(); + +/** + * Removes an edict from the world. + * + * @param edict Index of the edict. + * @noreturn + * @error Invalid edict index. + */ +native RemoveEdict(edict); + +/** + * Returns the flags on an edict. These are not the same as entity flags. + * + * @param edict Index of the entity. + * @return Edict flags. + * @error Invalid edict index. + */ +native GetEdictFlags(edict); + +/** + * Sets the flags on an edict. These are not the same as entity flags. + * + * @param edict Index of the entity. + * @param flags Flags to set. + * @noreturn + * @error Invalid edict index. + */ +native SetEdictFlags(edict, flags); + +/** + * Retrieves an edict classname. + * + * @param edict Index of the entity. + * @param clsname Buffer to store the classname. + * @param maxlength Maximum length of the buffer. + * @return True on success, false if there is no classname set. + */ +native bool:GetEdictClassname(edict, String:clsname[], maxlength); + +/** + * Retrieves an entity's networkable serverclass name. + * This is not the same as the classname and is used for networkable state changes. + * + * @param edict Index of the entity. + * @param clsname Buffer to store the serverclass name. + * @param maxlength Maximum lnegth of the buffer. + * @return True on success, false if the edict is not networkable. + * @error Invalid edict index. + */ +native bool:GetEntityNetClass(edict, String:clsname[], maxlength); + +/** + * @section Entity offset functions + * + * Offsets should be specified in byte distance from the CBaseEntity + * structure, not short (double byte) or integer (four byte) multiples. + * It is somewhat common practice to use offsets aligned to their final + * type, and thus make sure you are not falling to this error in SourceMod. + * For example, if your "integer-aligned" offset was 119, your byte-aligned + * offset is 119*4, or 476. + + * Specifying incorrect offsets or the incorrect data type for an offset + * can have fatal consequences. If you are hardcoding offsets, and the + * layout of CBaseEntity does not match, you can easily crash the server. + * + * The reasonable bounds for offsets is greater than or equal to 0 and + * below 32768. Offsets out of these bounds will throw an error. However, + * this does not represent any real range, it is simply a sanity check for + * illegal values. Any range outside of the CBaseEntity structure's private + * size will cause undefined behaviour or even crash. + */ + +/** + * Marks an entity as state changed. This can be useful if you set an offset + * and wish for it to be immediately changed over the network. By default this + * is not done for offset setting functions. + * + * @param edict Index to the edict. + * @param offset Offset to mark as changed. If 0, + * the entire edict is marked as changed. + * @noreturn + * @error Invalid entity or offset out of bounds. + */ +native ChangeEdictState(edict, offset = 0); + +/** + * Peeks into an entity's object data and retrieves the integer value at + * the given offset. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param size Number of bytes to read (valid values are 1, 2, or 4). + * @return Value at the given memory location. + * @error Invalid entity or offset out of reasonable bounds. + */ +native GetEntData(entity, offset, size=4); + +/** + * Peeks into an entity's object data and sets the integer value at + * the given offset. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param size Number of bytes to write (valid values are 1, 2, or 4). + * @param changeState If true, change will be sent over the network. + * @return Value at the given memory location. + * @error Invalid entity or offset out of reasonable bounds. + * @noreturn + */ +native SetEntData(entity, offset, any:value, size=4, bool:changeState=false); + +/** + * Peeks into an entity's object data and retrieves the float value at + * the given offset. + * + * @param entity Edict index. + * @param offset Offset to use. + * @return Value at the given memory location. + * @error Invalid entity or offset out of reasonable bounds. + */ +native Float:GetEntDataFloat(entity, offset); + +/** + * Peeks into an entity's object data and sets the integer value at + * the given offset. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param changeState If true, change will be sent over the network. + * @return Value at the given memory location. + * @error Invalid entity or offset out of reasonable bounds. + * @noreturn + */ +native SetEntDataFloat(entity, offset, Float:value, bool:changeState=false); + +/** + * This function is deprecated. Use GetEntDataEnt2 instead, for + * reasons explained in the notes. + * + * Note: This function returns 0 on failure, which may be misleading, + * as the number 0 is also used for the world entity index. + * + * Note: This function makes no attempt to validate the returned + * entity, and in fact, it could be garbage or completely unexpected. + * + * @param entity Edict index. + * @param offset Offset to use. + * @return Entity index at the given location, or 0 if none. + * @error Invalid entity or offset out of reasonable bounds. + */ +native GetEntDataEnt(entity, offset); + +/** + * This function is deprecated. Use GetEntDataEnt2 instead, for + * reasons explained in the notes. + * + * Note: This function uses 0 as an indicator to unset data, but + * 0 is also the world entity index. Thus, the a property cannot + * be set to the world entity using this native. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param other Entity index to set, or 0 to clear. + * @param changeState If true, change will be sent over the network. + * @noreturn + * @error Invalid entity or offset out of reasonable bounds. + */ +native SetEntDataEnt(entity, offset, other, bool:changeState=false); + +/** + * Peeks into an entity's object data and retrieves the entity index + * at the given offset. + * + * Note: This will only work on offsets that are stored as "entity + * handles" (which usually looks like m_h* in properties). These + * are not SourceMod Handles, but internal Source structures. + * + * @param entity Edict index. + * @param offset Offset to use. + * @return Entity index at the given location. If there is no entity, + * or the stored entity is invalid, then -1 is returned. + * @error Invalid input entity, or offset out of reasonable bounds. + */ +native GetEntDataEnt2(entity, offset); + +/** + * Peeks into an entity's object data and sets the entity index at the + * given offset. + * + * Note: This will only work on offsets that are stored as "entity + * handles" (which usually looks like m_h* in properties). These + * are not SourceMod Handles, but internal Source structures. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param other Entity index to set, or -1 to clear. + * @param changeState If true, change will be sent over the network. + * @noreturn + * @error Invalid input entity, or offset out of reasonable bounds. + */ +native SetEntDataEnt2(entity, offset, other, bool:changeState=false); + +/** + * Peeks into an entity's object data and retrieves the vector at the + * given offset. + * @note Both a Vector and a QAngle are three floats. This is a + * convenience function and will work with both types. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param vec Vector buffer to store data in. + * @noreturn + * @error Invalid entity or offset out of reasonable bounds. + */ +native GetEntDataVector(entity, offset, Float:vec[3]); + +/** + * Peeks into an entity's object data and sets the vector at the given + * offset. + * @note Both a Vector and a QAngle are three floats. This is a + * convenience function and will work with both types. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param vec Vector to set. + * @param changeState If true, change will be sent over the network. + * @noreturn + * @error Invalid entity or offset out of reasonable bounds. + */ +native SetEntDataVector(entity, offset, const Float:vec[3], bool:changeState=false); + +/** + * Peeks into an entity's object data and retrieves the string at + * the given offset. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param buffer Destination string buffer. + * @param maxlen Maximum length of output string buffer. + * @return Number of non-null bytes written. + * @error Invalid entity or offset out of reasonable bounds. + */ +native GetEntDataString(entity, offset, String:buffer[], maxlen); + +/** + * Peeks into an entity's object data and sets the string at + * the given offset. + * + * @param entity Edict index. + * @param offset Offset to use. + * @param buffer String to set. + * @param maxlen Maximum length of bytes to write. + * @param changeState If true, change will be sent over the network. + * @return Number of non-null bytes written. + * @error Invalid entity or offset out of reasonable bounds. + */ +native SetEntDataString(entity, offset, const String:buffer[], maxlen, bool:changeState=false); + +/** + * @endsection + */ + +/** + * Given a ServerClass name, finds a networkable send property offset. + * This information is cached for future calls. + * + * Note, this function may return offsets that do not work! + * If a property is nested beneath a parent object, the resulting offset + * will be invalid for direct use with data functions. Therefore, you + * should use FindSendPropInfo() instead. An example of such a property is + * CTFPlayer::DT_LocalPlayer.m_nDisguiseClass on Team Fortress. + * + * @param cls Classname. + * @param prop Property name. + * @return An offset, or -1 on failure. + */ +native FindSendPropOffs(const String:cls[], const String:prop[]); + +/** + * Given a ServerClass name, finds a networkable send property offset. + * This information is cached for future calls. + * + * Note: This function will correctly compute nested offsets, unlike + * FindSendPropOffs(). YOU SHOULD NOT use this function to self-compute + * nested offsets. For example, it is okay to add indexes for arrays, + * but not to add DT_LocalPlayer to m_nDisguiseClass. + * + * @param cls Classname. + * @param prop Property name. + * @param type Optional parameter to store the type. + * @param num_bits Optional parameter to store the number of bits the field + * uses, if applicable (otherwise 0 is stored). The number + * of bits varies for integers and floats, and is always 0 + * for strings. + * @param local_offset Optional parameter to store the local offset, as + * FindSendPropOffs() would return. + * @return On success, returns an absolutely computed offset. + * If no offset is available, 0 is returned. + * If the property is not found, -1 is returned. + */ +native FindSendPropInfo(const String:cls[], + const String:prop[], + &PropFieldType:type=PropFieldType:0, + &num_bits=0, + &local_offset=0); + +/** + * Given an entity, finds a datamap property offset. + * This information is cached for future calls. + * + * @param entity Entity index. + * @param prop Property name. + * @param type Optional parameter to store the type. + * @param num_bits Optional parameter to store the number of bits the field + * uses. The bit count will either be 1 (for boolean) or + * divisible by 8 (including 0 if unknown). + * @return An offset, or -1 on failure. + */ +native FindDataMapOffs(entity, + const String:prop[], + &PropFieldType:type=PropFieldType:0, + &num_bits=0); + +/** + * Wrapper function for finding a send property for a particular entity. + * + * @param ent Entity index. + * @param prop Property name. + * @param actual Defaults to false for backwards compatibility. + * If true, the newer FindSendPropInfo() function + * is used instead. + * @return An offset, or -1 on failure. + */ +stock GetEntSendPropOffs(ent, const String:prop[], bool:actual=false) +{ + decl String:cls[64]; + + if (!GetEntityNetClass(ent, cls, sizeof(cls))) + { + return -1; + } + + if (actual) + { + return FindSendPropInfo(cls, prop); + } + else + { + return FindSendPropOffs(cls, prop); + } +} + +/** + * Retrieves an integer value from an entity's property. + * + * This function is considered safer and more robust over GetEntData, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @param size Number of bytes to write (valid values are 1, 2, or 4). + * This value is auto-detected, and the size parameter is + * only used as a fallback in case detection fails. + * @return Value at the given property offset. + * @error Invalid entity or property not found. + */ +native GetEntProp(entity, PropType:type, const String:prop[], size=4); + +/** + * Sets an integer value in an entity's property. + * + * This function is considered safer and more robust over SetEntData, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @param size Number of bytes to write (valid values are 1, 2, or 4). + * This value is auto-detected, and the size parameter is + * only used as a fallback in case detection fails. + * @error Invalid entity or offset out of reasonable bounds. + * @noreturn + */ +native SetEntProp(entity, PropType:type, const String:prop[], any:value, size=4); + +/** + * Retrieves a float value from an entity's property. + * + * This function is considered safer and more robust over GetEntDataFloat, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @return Value at the given property offset. + * @error Invalid entity or offset out of reasonable bounds. + */ +native Float:GetEntPropFloat(entity, PropType:type, const String:prop[]); + +/** + * Sets a float value in an entity's property. + * + * This function is considered safer and more robust over SetEntDataFloat, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @param value Value to set. + * @noreturn + * @error Invalid entity or offset out of reasonable bounds. + */ +native SetEntPropFloat(entity, PropType:type, const String:prop[], Float:value); + +/** + * Retrieves an entity index from an entity's property. + * + * This function is considered safer and more robust over GetEntDataEnt*, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @return Entity index at the given property. + * If there is no entity, or the entity is not valid, + * then -1 is returned. + * @error Invalid entity or offset out of reasonable bounds. + */ +native GetEntPropEnt(entity, PropType:type, const String:prop[]); + +/** + * Sets an entity index in an entity's property. + * + * This function is considered safer and more robust over SetEntDataEnt*, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @param other Entity index to set, or -1 to unset. + * @noreturn + * @error Invalid entity or offset out of reasonable bounds. + */ +native SetEntPropEnt(entity, PropType:type, const String:prop[], other); + +/** + * Retrieves a vector of floats from an entity, given a named network property. + * + * This function is considered safer and more robust over GetEntDataVector, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @param vec Vector buffer to store data in. + * @noreturn + * @error Invalid entity, property not found, or property not + * actually a vector data type. + */ +native GetEntPropVector(entity, PropType:type, const String:prop[], Float:vec[3]); + +/** + * Sets a vector of floats in an entity, given a named network property. + * + * This function is considered safer and more robust over SetEntDataVector, + * because it performs strict offset checking and typing rules. There is a + * very minor performance hit from this. + * + * @param entity Entity/edict index. + * @param type Property type. + * @param prop Property name. + * @param vec Vector to set. + * @noreturn + * @error Invalid entity, property not found, or property not + * actually a vector data type. + */ +native SetEntPropVector(entity, PropType:type, const String:prop[], const Float:vec[3]); + +/** + * Gets a network property as a string. + * + * @param entity Edict index. + * @param type Property type. + * @param prop Property to use. + * @param buffer Destination string buffer. + * @param maxlen Maximum length of output string buffer. + * @return Number of non-null bytes written. + * @error Invalid entity, offset out of reasonable bounds, or property is not a valid string. + */ +native GetEntPropString(entity, PropType:type, const String:prop[], String:buffer[], maxlen); + +/** + * Sets a network property as a string. + * + * This cannot set property fields of type PropField_String_T (such as "m_target"). + * To set such fields, you should use DispatchKeyValue() from SDKTools. + * + * @param entity Edict index. + * @param type Property type. + * @param prop Property to use. + * @param buffer String to set. + * @return Number of non-null bytes written. + * @error Invalid entity, offset out of reasonable bounds, or property is not a valid string. + */ +native SetEntPropString(entity, PropType:type, const String:prop[], const String:buffer[]); + +/** + * Copies an array of cells from an entity at a given offset. + * + * @param entity Entity index. + * @param offset Offset to use. + * @param array Array to read into. + * @param arraySize Number of values to read. + * @param dataSize Size of each value in bytes (1, 2, or 4). + * @noreturn + * @error Invalid entity or offset out of reasonable bounds. + */ +stock GetEntDataArray(entity, offset, array[], arraySize, dataSize=4) +{ + for (new i=0; i. + * + * 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 . + * + * Version: $Id: entity_prop_stocks.inc 1943 2008-03-16 23:16:34Z dvander $ + */ + +#if defined _entity_prop_stocks_included + #endinput +#endif +#define _entity_prop_stocks_included + +enum MoveType +{ + MOVETYPE_NONE = 0, /**< never moves */ + MOVETYPE_ISOMETRIC, /**< For players */ + MOVETYPE_WALK, /**< Player only - moving on the ground */ + MOVETYPE_STEP, /**< gravity, special edge handling -- monsters use this */ + MOVETYPE_FLY, /**< No gravity, but still collides with stuff */ + MOVETYPE_FLYGRAVITY, /**< flies through the air + is affected by gravity */ + MOVETYPE_VPHYSICS, /**< uses VPHYSICS for simulation */ + MOVETYPE_PUSH, /**< no clip to world, push and crush */ + MOVETYPE_NOCLIP, /**< No gravity, no collisions, still do velocity/avelocity */ + MOVETYPE_LADDER, /**< Used by players only when going onto a ladder */ + MOVETYPE_OBSERVER, /**< Observer movement, depends on player's observer mode */ + MOVETYPE_CUSTOM, /**< Allows the entity to describe its own physics */ +}; + +enum RenderMode +{ + RENDER_NORMAL, /**< src */ + RENDER_TRANSCOLOR, /**< c*a+dest*(1-a) */ + RENDER_TRANSTEXTURE, /**< src*a+dest*(1-a) */ + RENDER_GLOW, /**< src*a+dest -- No Z buffer checks -- Fixed size in screen space */ + RENDER_TRANSALPHA, /**< src*srca+dest*(1-srca) */ + RENDER_TRANSADD, /**< src*a+dest */ + RENDER_ENVIRONMENTAL, /**< not drawn, used for environmental effects */ + RENDER_TRANSADDFRAMEBLEND, /**< use a fractional frame value to blend between animation frames */ + RENDER_TRANSALPHAADD, /**< src + dest*(1-a) */ + RENDER_WORLDGLOW, /**< Same as kRenderGlow but not fixed size in screen space */ + RENDER_NONE, /**< Don't render. */ +}; + +enum RenderFx +{ + RENDERFX_NONE = 0, + RENDERFX_PULSE_SLOW, + RENDERFX_PULSE_FAST, + RENDERFX_PULSE_SLOW_WIDE, + RENDERFX_PULSE_FAST_WIDE, + RENDERFX_FADE_SLOW, + RENDERFX_FADE_FAST, + RENDERFX_SOLID_SLOW, + RENDERFX_SOLID_FAST, + RENDERFX_STROBE_SLOW, + RENDERFX_STROBE_FAST, + RENDERFX_STROBE_FASTER, + RENDERFX_FLICKER_SLOW, + RENDERFX_FLICKER_FAST, + RENDERFX_NO_DISSIPATION, + RENDERFX_DISTORT, /**< Distort/scale/translate flicker */ + RENDERFX_HOLOGRAM, /**< kRenderFxDistort + distance fade */ + RENDERFX_EXPLODE, /**< Scale up really big! */ + RENDERFX_GLOWSHELL, /**< Glowing Shell */ + RENDERFX_CLAMP_MIN_SCALE, /**< Keep this sprite from getting very small (SPRITES only!) */ + RENDERFX_ENV_RAIN, /**< for environmental rendermode, make rain */ + RENDERFX_ENV_SNOW, /**< " " " , make snow */ + RENDERFX_SPOTLIGHT, /**< TEST CODE for experimental spotlight */ + RENDERFX_RAGDOLL, /**< HACKHACK: TEST CODE for signalling death of a ragdoll character */ + RENDERFX_PULSE_FAST_WIDER, + RENDERFX_MAX +}; + +// These defines are for client button presses. +#define IN_ATTACK (1 << 0) +#define IN_JUMP (1 << 1) +#define IN_DUCK (1 << 2) +#define IN_FORWARD (1 << 3) +#define IN_BACK (1 << 4) +#define IN_USE (1 << 5) +#define IN_CANCEL (1 << 6) +#define IN_LEFT (1 << 7) +#define IN_RIGHT (1 << 8) +#define IN_MOVELEFT (1 << 9) +#define IN_MOVERIGHT (1 << 10) +#define IN_ATTACK2 (1 << 11) +#define IN_RUN (1 << 12) +#define IN_RELOAD (1 << 13) +#define IN_ALT1 (1 << 14) +#define IN_ALT2 (1 << 15) +#define IN_SCORE (1 << 16) // Used by client.dll for when scoreboard is held down +#define IN_SPEED (1 << 17) // Player is holding the speed key +#define IN_WALK (1 << 18) // Player holding walk key +#define IN_ZOOM (1 << 19) // Zoom key for HUD zoom +#define IN_WEAPON1 (1 << 20) // weapon defines these bits +#define IN_WEAPON2 (1 << 21) // weapon defines these bits +#define IN_BULLRUSH (1 << 22) +#define IN_GRENADE1 (1 << 23) // grenade 1 +#define IN_GRENADE2 (1 << 24) // grenade 2 + +// CBaseEntity::m_fFlags +// PLAYER SPECIFIC FLAGS FIRST BECAUSE WE USE ONLY A FEW BITS OF NETWORK PRECISION +#define FL_ONGROUND (1 << 0) // At rest / on the ground +#define FL_DUCKING (1 << 1) // Player flag -- Player is fully crouched +#define FL_WATERJUMP (1 << 2) // player jumping out of water +#define FL_ONTRAIN (1 << 3) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. +#define FL_INRAIN (1 << 4) // Indicates the entity is standing in rain +#define FL_FROZEN (1 << 5) // Player is frozen for 3rd person camera +#define FL_ATCONTROLS (1 << 6) // Player can't move, but keeps key inputs for controlling another entity +#define FL_CLIENT (1 << 7) // Is a player +#define FL_FAKECLIENT (1 << 8) // Fake client, simulated server side; don't send network messages to them +// NOTE if you move things up, make sure to change this value +#define PLAYER_FLAG_BITS 9 +// NON-PLAYER SPECIFIC (i.e., not used by GameMovement or the client .dll ) -- Can still be applied to players, though +#define FL_INWATER (1 << 9) // In water +#define FL_FLY (1 << 10) // Changes the SV_Movestep() behavior to not need to be on ground +#define FL_SWIM (1 << 11) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) +#define FL_CONVEYOR (1 << 12) +#define FL_NPC (1 << 13) +#define FL_GODMODE (1 << 14) +#define FL_NOTARGET (1 << 15) +#define FL_AIMTARGET (1 << 16) // set if the crosshair needs to aim onto the entity +#define FL_PARTIALGROUND (1 << 17) // not all corners are valid +#define FL_STATICPROP (1 << 18) // Eetsa static prop! +#define FL_GRAPHED (1 << 19) // worldgraph has this ent listed as something that blocks a connection +#define FL_GRENADE (1 << 20) +#define FL_STEPMOVEMENT (1 << 21) // Changes the SV_Movestep() behavior to not do any processing +#define FL_DONTTOUCH (1 << 22) // Doesn't generate touch functions, generates Untouch() for anything it was touching when this flag was set +#define FL_BASEVELOCITY (1 << 23) // Base velocity has been applied this frame (used to convert base velocity into momentum) +#define FL_WORLDBRUSH (1 << 24) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) +#define FL_OBJECT (1 << 25) // Terrible name. This is an object that NPCs should see. Missiles, for example. +#define FL_KILLME (1 << 26) // This entity is marked for death -- will be freed by game DLL +#define FL_ONFIRE (1 << 27) // You know... +#define FL_DISSOLVING (1 << 28) // We're dissolving! +#define FL_TRANSRAGDOLL (1 << 29) // In the process of turning into a client side ragdoll. +#define FL_UNBLOCKABLE_BY_PLAYER (1 << 30) // pusher that can't be blocked by the player +// END m_fFlags #defines + +/** + * Get an entity's flags. + * + * @param entity Entity index. + * @return Entity's flags, see m_fFlag defines above + * @error Invalid entity index, or lack of mod compliance. + */ +stock GetEntityFlags(entity) +{ + return GetEntProp(entity, Prop_Data, "m_fFlags"); +} + +/** + * Gets an entity's movetype. + * + * @param entity Entity index. + * @return Movetype, see enum above. + * @error Invalid entity index, or lack of mod compliance. + */ +stock MoveType:GetEntityMoveType(entity) +{ + new offset = GetEntSendPropOffs(entity, "movetype"); + return MoveType:GetEntData(entity, offset, 1); +} + +/** + * Sets an entity's movetype. + * + * @param entity Entity index. + * @param mt Movetype, see enum above. + * @noreturn + * @error Invalid entity index, or lack of mod compliance. + */ +stock SetEntityMoveType(entity, MoveType:mt) +{ + new offset = GetEntSendPropOffs(entity, "movetype"); + SetEntData(entity, offset, mt, 1, true); +} + +/** + * Gets an entity's render mode. + * + * @param entity Entity index. + * @return RenderMode value. + * @error Invalid entity index, or lack of mod compliance. + */ +stock RenderMode:GetEntityRenderMode(entity) +{ + return RenderMode:GetEntProp(entity, Prop_Send, "m_nRenderMode", 1); +} + +/** + * Sets an entity's render mode. + * + * @param entity Entity index. + * @param mode RenderMode value. + * @noreturn + * @error Invalid entity index, or lack of mod compliance. + */ +stock SetEntityRenderMode(entity, RenderMode:mode) +{ + SetEntProp(entity, Prop_Send, "m_nRenderMode", mode, 1); +} + +/** + * Gets an entity's render Fx. + * + * @param entity Entity index. + * @return RenderFx value. + * @error Invalid entity index, or lack of mod compliance. + */ +stock RenderFx:GetEntityRenderFx(entity) +{ + return RenderFx:GetEntProp(entity, Prop_Send, "m_nRenderFX", 1); +} + +/** + * Sets an entity's render Fx. + * + * @param entity Entity index. + * @param fx RenderFx value. + * @noreturn + * @error Invalid entity index, or lack of mod compliance. + */ +stock SetEntityRenderFx(entity, RenderFx:fx) +{ + SetEntProp(entity, Prop_Send, "m_nRenderFX", fx, 1); +} + +/** + * Sets an entity's color. + * + * @param entity Entity index + * @param r Amount of red (0-255) + * @param g Amount of green (0-255) + * @param b Amount of blue (0-255) + * @param a Amount of alpha (0-255) + * @noreturn + * @error Invalid entity index, or lack of mod compliance. + */ +stock SetEntityRenderColor(entity, r=255, g=255, b=255, a=255) +{ + new offset = GetEntSendPropOffs(entity, "m_clrRender"); + SetEntData(entity, offset, r, 1, true); + SetEntData(entity, offset + 1, g, 1, true); + SetEntData(entity, offset + 2, b, 1, true); + SetEntData(entity, offset + 3, a, 1, true); +} + +/* GuessSDKVersion */ + +/** + * Gets an entity's gravity. + * + * @param entity Entity index. + * @return Entity's m_flGravity value. + * @error Invalid entity index, or lack of mod compliance. + */ +stock Float:GetEntityGravity(entity) +{ + return GetEntPropFloat(entity, Prop_Data, "m_flGravity"); +} + +/** + * Sets an entity's gravity. + * + * @param entity Entity index. + * @param amount Gravity to set (default = 1.0, half = 0.5, double = 2.0). + * @noreturn + * @error Invalid entity index, or lack of mod compliance. + */ +stock SetEntityGravity(entity, Float:amount) +{ + SetEntPropFloat(entity, Prop_Data, "m_flGravity", amount); +} + +/** + * Sets an entity's health + * + * @param entity Entity index. + * @param amount Health amount. + * @noreturn + * @error Invalid entity index, or lack of mod compliance. + */ +stock SetEntityHealth(entity, amount) +{ + SetEntProp(entity, Prop_Send, "m_iHealth", amount); +} + +/** + * Get's a users current pressed buttons + * + * @param client Client index + * @return Bitsum of buttons + * @error Invalid client index, client not in game, + * or lack of mod compliance. + */ +stock GetClientButtons(client) +{ + return GetEntProp(client, Prop_Data, "m_nButtons"); +} diff --git a/src/include/events.inc b/src/include/events.inc new file mode 100644 index 0000000..d643c65 --- /dev/null +++ b/src/include/events.inc @@ -0,0 +1,243 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: events.inc 1857 2008-01-19 20:58:10Z dvander $ + */ + +#if defined _events_included + #endinput +#endif +#define _events_included + +/** + * Event hook modes determining how hooking should be handled + */ +enum EventHookMode +{ + EventHookMode_Pre, /**< Hook callback fired before event is fired */ + EventHookMode_Post, /**< Hook callback fired after event is fired */ + EventHookMode_PostNoCopy /**< Hook callback fired after event is fired, but event data won't be copied */ +}; + +/** + * Hook function types for events. + */ +funcenum EventHook +{ + /** + * Called when a game event is fired. + * + * @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking + * this event has set the hook mode EventHookMode_PostNoCopy. + * @param name String containing the name of the event. + * @param dontBroadcast True if event was not broadcast to clients, false otherwise. + * @return Ignored for post hooks. Plugin_Handled will block event if hooked as pre. + */ + Action:public(Handle:event, const String:name[], bool:dontBroadcast), + /** + * Called when a game event is fired. + * + * @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking + * this event has set the hook mode EventHookMode_PostNoCopy. + * @param name String containing the name of the event. + * @param dontBroadcast True if event was not broadcast to clients, false otherwise. + * @noreturn + */ + public(Handle:event, const String:name[], bool:dontBroadcast), +}; + +/** + * Creates a hook for when a game event is fired. + * + * @param name Name of event. + * @param callback An EventHook function pointer. + * @param mode Optional EventHookMode determining the type of hook. + * @noreturn + * @error Invalid event name or invalid callback function. + */ +native HookEvent(const String:name[], EventHook:callback, EventHookMode:mode=EventHookMode_Post); + +/** + * Creates a hook for when a game event is fired. + * + * @param name Name of event. + * @param callback An EventHook function pointer. + * @param mode Optional EventHookMode determining the type of hook. + * @return True if event exists and was hooked successfully, false otherwise. + * @error Invalid callback function. + */ +native bool:HookEventEx(const String:name[], EventHook:callback, EventHookMode:mode=EventHookMode_Post); + +/** + * Removes a hook for when a game event is fired. + * + * @param name Name of event. + * @param callback An EventHook function pointer. + * @param mode Optional EventHookMode determining the type of hook. + * @noreturn + * @error Invalid callback function or no active hook for specified event. + */ +native UnhookEvent(const String:name[], EventHook:callback, EventHookMode:mode=EventHookMode_Post); + +/** + * Creates a game event to be fired later. + * + * The Handle should not be closed via CloseHandle(). It must be closed via + * FireEvent() or CancelCreatedEvent(). + * + * @param name Name of event. + * @param force If set to true, this forces the event to be created even if it's not being hooked. + * Note that this will not force it if the event doesn't exist at all. + * @return Handle to event. INVALID_HANDLE is returned if the event doesn't exist or isn't + being hooked (unless force is true). + */ +native Handle:CreateEvent(const String:name[], bool:force=false); + +/** + * Fires a game event. + * + * This function closes the event Handle after completing. + * + * @param event Handle to the event. + * @param dontBroadcast Optional boolean that determines if event should be broadcast to clients. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native FireEvent(Handle:event, bool:dontBroadcast=false); + +/** + * Cancels a previously created game event that has not been fired. + * + * @param event Handled to the event. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native CancelCreatedEvent(Handle:event); + +/** + * Returns the boolean value of a game event's key. + * + * @param event Handle to the event. + * @param key Name of event key. + * @return The boolean value of the specfied event key. + * @error Invalid or corrupt Handle. + */ +native bool:GetEventBool(Handle:event, const String:key[]); + +/** + * Sets the boolean value of a game event's key. + * + * @param event Handle to the event. + * @param key Name of event key. + * @param value New boolean value. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetEventBool(Handle:event, const String:key[], bool:value); + +/** + * Returns the integer value of a game event's key. + * + * @param event Handle to the event. + * @param key Name of event key. + * @return The integer value of the specfied event key. + * @error Invalid or corrupt Handle. + */ +native GetEventInt(Handle:event, const String:key[]); + +/** + * Sets the integer value of a game event's key. + * + * Integer value refers to anything that can be reduced to an integer. + * The various size specifiers, such as "byte" and "short" are still + * integers, and only refer to how much data will actually be sent + * over the network (if applicable). + * + * @param event Handle to the event. + * @param key Name of event key. + * @param value New integer value. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetEventInt(Handle:event, const String:key[], value); + +/** + * Returns the floating point value of a game event's key. + * + * @param event Handle to the event. + * @param key Name of event key. + * @return The floating point value of the specfied event key. + * @error Invalid or corrupt Handle. + */ +native Float:GetEventFloat(Handle:event, const String:key[]); + +/** + * Sets the floating point value of a game event's key. + * + * @param event Handle to the event. + * @param key Name of event key. + * @param value New floating point value. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetEventFloat(Handle:event, const String:key[], Float:value); + +/** + * Retrieves the string value of a game event's key. + * + * @param event Handle to the event. + * @param key Name of event key. + * @param value Buffer to store the value of the specified event key. + * @param maxlength Maximum length of string buffer. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native GetEventString(Handle:event, const String:key[], String:value[], maxlength); + +/** + * Sets the string value of a game event's key. + * + * @param event Handle to the event. + * @param key Name of event key. + * @param value New string value. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SetEventString(Handle:event, const String:key[], const String:value[]); + +/** + * Retrieves the name of a game event. + * + * @param event Handle to the event. + * @param value Buffer to store the name of the event. + * @param maxlength Maximum length of string buffer. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native GetEventName(Handle:event, String:name[], maxlength); diff --git a/src/include/files.inc b/src/include/files.inc new file mode 100644 index 0000000..d462d69 --- /dev/null +++ b/src/include/files.inc @@ -0,0 +1,388 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: files.inc 1860 2008-01-22 17:04:59Z dvander $ + */ + +#if defined _files_included + #endinput +#endif +#define _files_included + +/** + * @global All paths in SourceMod natives are relative to the mod folder + * unless otherwise noted. + * + * Most functions in SourceMod (at least, ones that deal with direct + * file manipulation) will support an alternate path specification. + * + * If the path starts with the string "file://" and the PathType is + * not relative, then the "file://" portion is stripped off, and the + * rest of the path is used without any modification (except for + * correcting slashes). This can be used to override the path + * builder to supply alternate absolute paths. Examples: + * + * file://C:/Temp/file.txt + * file:///tmp/file.txt + */ + +/** + * File inode types. + */ +enum FileType +{ + FileType_Unknown = 0, /* Unknown file type (device/socket) */ + FileType_Directory = 1, /* File is a directory */ + FileType_File = 2, /* File is a file */ +}; + +/** + * File time modes. + */ +enum FileTimeMode +{ + FileTime_LastAccess = 0, /* Last access (does not work on FAT) */ + FileTime_Created = 1, /* Creation (does not work on FAT) */ + FileTime_LastChange = 2, /* Last modification */ +}; + +#define PLATFORM_MAX_PATH 256 /**< Maximum path length. */ + +#define SEEK_SET 0 /**< Seek from start. */ +#define SEEK_CUR 1 /**< Seek from current position. */ +#define SEEK_END 2 /**< Seek from end position. */ + +/** + * Path types. + */ +enum PathType +{ + Path_SM, /**< SourceMod root folder */ +}; + +/** + * Builds a path relative to the SourceMod folder. This should be used instead of + * directly referencing addons/sourcemod, in case users change the name of their + * folder layout. + * + * @param type Type of path to build as the base. + * @param buffer Buffer to store the path. + * @param maxlength Maximum length of buffer. + * @param fmt Format string. + * @param ... Format arguments. + * @return Number of bytes written to buffer (not including null terminator). + */ +native BuildPath(PathType:type, String:buffer[], maxlength, const String:fmt[], any:...); + +/** + * Opens a directory/folder for contents enumeration. + * + * @note Directories are closed with CloseHandle(). + * @note Directories Handles can be cloned. + * @note OpenDirectory() supports the "file://" notation. + * + * @param path Path to open. + * @return A Handle to the directory, INVALID_HANDLE on open error. + */ +native Handle:OpenDirectory(const String:path[]); + +/** + * Reads the current directory entry as a local filename, then moves to the next file. + * + * @note Contents of buffers are undefined when returning false. + * @note Both the '.' and '..' automatic directory entries will be retrieved for Windows and Linux. + * + * @param dir Handle to a directory. + * @param buffer String buffer to hold directory name. + * @param maxlength Maximum size of string buffer. + * @param type Optional variable to store the file type. + * @return True on success, false if there are no more files to read. + * @error Invalid or corrupt Handle. + */ +native bool:ReadDirEntry(Handle:dir, String:buffer[], maxlength, &FileType:type=FileType_Unknown); + +/** + * Opens a file. + * + * @note Files are closed with CloseHandle(). + * @note File Handles can be cloned. + * @note OpenFile() supports the "file://" notation. + * + * @param file File to open. + * @param mode Open mode. + * @return A Handle to the file, INVALID_HANDLE on open error. + */ +native Handle:OpenFile(const String:file[], const String:mode[]); + +/** + * Deletes a file. + * + * @param path Path of the file to delete. + * @return True on success, false otherwise. + */ +native bool:DeleteFile(const String:path[]); + +/** + * Reads a line from a text file. + * + * @param hndl Handle to the file. + * @param buffer String buffer to hold the line. + * @param maxlength Maximum size of string buffer. + * @return True on success, false otherwise. + */ +native bool:ReadFileLine(Handle:hndl, String:buffer[], maxlength); + +/** + * Reads binary data from a file. + * + * @param hndl Handle to the file. + * @param items Array to store each item read. + * @param num_items Number of items to read into the array. + * @param size Size of each element, in bytes, to be read. + * Valid sizes are 1, 2, or 4. + * @return Number of elements read, or -1 on error. + */ +native ReadFile(Handle:hndl, items[], num_items, size); + +/** + * Reads a UTF8 or ANSI string from a file. + * + * @param hndl Handle to the file. + * @param buffer Buffer to store the string. + * @param max_size Maximum size of the string buffer. + * @param stop If true, reading will stop once max_size-1 bytes have + * been read. If false, reading will stop once a NUL + * terminator is reached. The buffer will simply be + * terminated in either case, the difference is in how + * the far the file position is changed. + * @return Number of characters written to the buffer, or -1 + * if an error was encountered. + * @error Invalid Handle, or read_count > max_size. + */ +native ReadFileString(Handle:hndl, String:buffer[], max_size, read_count=-1); + +/** + * Writes binary data to a file. + * + * @param hndl Handle to the file. + * @param items Array of items to write. The data is read directly. + * That is, in 1 or 2-byte mode, the lower byte(s) in + * each cell are used directly, rather than performing + * any casts from a 4-byte number to a smaller number. + * @param num_items Number of items in the array. + * @param size Size of each item in the array in bytes. + * Valid sizes are 1, 2, or 4. + * @return True on success, false on error. + * @error Invalid Handle. + */ +native bool:WriteFile(Handle:hndl, const items[], num_items, size); + +/** + * Writes a binary string to a file. + * + * @param hndl Handle to th efile. + * @param buffer String to write. + * @param term True to append NUL terminator, false otherwise. + * @return True on success, false on error. + * @error Invalid Handle. + */ +native bool:WriteFileString(Handle:hndl, const String:buffer[], bool:term); + +/** + * Writes a line of text to a text file. A newline is automatically appended. + * + * @param hndl Handle to the file. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @return True on success, false otherwise. + * @error Invalid Handle. + */ +native bool:WriteFileLine(Handle:hndl, const String:format[], any:...); + +/** + * Reads a single binary cell from a file. + * + * @param hndl Handle to the file. + * @param data Variable to store the data read. + * @param size Size of the data to read in bytes. Valid + * sizes are 1, 2, or 4 bytes. + * @return Number of elements read (max 1), or -1 on error. + * @error Invalid Handle. + */ +stock ReadFileCell(Handle:hndl, &data, size) +{ + new array[1], ret; + + if ((ret = ReadFile(hndl, array, 1, size)) == 1) + { + data = array[0]; + } + + return ret; +} + +/** + * Writes a single binary cell to a file. + * + * @param hndl Handle to the file. + * @param data Cell to write to the file. + * @param size Size of the data to read in bytes. Valid + * sizes are 1, 2, or 4 bytes. If the size + * is less than 4 bytes, the data is truncated + * rather than casted. That is, only the lower + * bits will be read. + * @return True on success, false on error. + * @error Invalid Handle. + */ +stock bool:WriteFileCell(Handle:hndl, data, size) +{ + new array[1]; + + array[0] = data; + + return WriteFile(hndl, array, 1, size); +} + +/** + * Tests if the end of file has been reached. + * + * @param file Handle to the file. + * @return True if end of file has been reached, false otherwise. + * @error Invalid Handle. + */ +native bool:IsEndOfFile(Handle:file); + +/** + * Sets the file position indicator. + * + * @param file Handle to the file. + * @param position Position relative to what is specified in whence. + * @param where SEEK_ constant value of where to see from. + * @return True on success, false otherwise. + * @error Invalid Handle. + */ +native bool:FileSeek(Handle:file, position, where); + +/** + * Get current position in the file. + * + * @param file Handle to the file. + * @return Value for the file position indicator. + * @error Invalid Handle. + */ +native FilePosition(Handle:file); + +/** + * Checks if a file exists. + * + * @param path Path to the file. + * @param use_valve_fs If true, the Valve file system will be used instead. + * This can be used to check for the existance of files + * inside GCFs or the game cache, rather than solely files + * that are on disk. + * @return True if the file exists, false otherwise. + */ +native bool:FileExists(const String:path[], bool:use_valve_fs=false); + +/** + * Renames a file. + * + * @param newpath New path to the file. + * @param oldpath Path to the existing file. + * @return True on success, false otherwise. + */ +native bool:RenameFile(const String:newpath[], const String:oldpath[]); + +/** + * Checks if a directory exists. + * + * @param path Path to the directory. + * @return True if the directory exists, false otherwise. + */ +native bool:DirExists(const String:path[]); + +/** + * Get the file size in bytes. + * + * @param path Path to the file. + * @return File size in bytes, -1 if file not found. + */ +native FileSize(const String:path[]); + +/** + * Flushes a file's buffered output; any buffered output + * is immediately written to the file. + * + * @param file Handle to the file. + * @return True on success, false on failure. + */ +native FlushFile(Handle:file); + +/** + * Removes a directory. + * @note On most Operating Systems you cannot remove a directory which has files inside it. + * + * @param path Path to the directory. + * @return True on success, false otherwise. + */ +native bool:RemoveDir(const String:path[]); + +/** + * Returns a file timestamp as a unix timestamp. + * + * @param file File name. + * @param tmode Time mode. + * @return Time value, or -1 on failure. + */ +native GetFileTime(const String:file[], FileTimeMode:tmode); + +/** + * Same as LogToFile(), except uses an open file Handle. The file must + * be opened in text appending mode. + * + * @param hndl Handle to the file. + * @param message Message format. + * @param ... Message format parameters. + * @noreturn + * @error Invalid Handle. + */ +native LogToOpenFile(Handle:hndl, const String:message[], any:...); + +/** + * Same as LogToFileEx(), except uses an open file Handle. The file must + * be opened in text appending mode. + * + * @param hndl Handle to the file. + * @param message Message format. + * @param ... Message format parameters. + * @noreturn + * @error Invalid Handle. + */ +native LogToOpenFileEx(Handle:hndl, const String:message[], any:...); + diff --git a/src/include/float.inc b/src/include/float.inc new file mode 100644 index 0000000..1e44a7d --- /dev/null +++ b/src/include/float.inc @@ -0,0 +1,415 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: float.inc 1942 2008-03-16 23:08:56Z dvander $ + */ + +#if defined _float_included + #endinput +#endif +#define _float_included + +/** + * Converts an integer into a floating point value. + * + * @param value Integer to convert. + * @return Floating point value. + */ +native Float:float(value); + +/** + * Multiplies two floats together. + * + * @param oper1 First value. + * @param oper2 Second value. + * @return oper1*oper2. + */ +native Float:FloatMul(Float:oper1, Float:oper2); + +/** + * Divides the dividend by the divisor. + * + * @param dividend First value. + * @param divisor Second value. + * @return dividend/divisor. + */ +native Float:FloatDiv(Float:dividend, Float:divisor); + +/** + * Adds two floats together. + * + * @param oper1 First value. + * @param oper2 Second value. + * @return oper1+oper2. + */ +native Float:FloatAdd(Float:oper1, Float:oper2); + +/** + * Subtracts oper2 from oper1. + * + * @param oper1 First value. + * @param oper2 Second value. + * @return oper1-oper2. + */ +native Float:FloatSub(Float:oper1, Float:oper2); + +/** + * Returns the decimal part of a float. + * + * @param value Input value. + * @return Decimal part. + */ +native Float:FloatFraction(Float:value); + +/** + * Rounds a float to the closest integer to zero. + * + * @param value Input value to be rounded. + * @return Rounded value. + */ +native RoundToZero(Float:value); + +/** + * Rounds a float to the next highest integer value. + * + * @param value Input value to be rounded. + * @return Rounded value. + */ +native RoundToCeil(Float:value); + +/** + * Rounds a float to the next lowest integer value. + * + * @param value Input value to be rounded. + * @return Rounded value. + */ +native RoundToFloor(Float:value); + +/** + * Standard IEEE rounding. + * + * @param value Input value to be rounded. + * @return Rounded value. + */ +native RoundToNearest(Float:value); + +/** + * Compares two floats. + * + * @param fOne First value. + * @param fTwo Second value. + * @return Returns 1 if the first argument is greater than the second argument. + * Returns -1 if the first argument is smaller than the second argument. + * Returns 0 if both arguments are equal. + */ +native FloatCompare(Float:fOne, Float:fTwo); + +/** + * Returns the square root of the input value, equivalent to floatpower(value, 0.5). + * + * @param value Input value. + * @return Square root of the value. + */ +native Float:SquareRoot(Float:value); + +/** + * Returns the value raised to the power of the exponent. + * + * @param value Value to be raised. + * @param exponent Value to raise the base. + * @return value^exponent. + */ +native Float:Pow(Float:value, Float:exponent); + +/** + * Returns the value of raising the input by e. + * + * @param value Input value. + * @return exp(value). + */ +native Float:Exponential(Float:value); + +/** + * Returns the logarithm of any base specified. + * + * @param value Input value. + * @param base Logarithm base to use, default is 10. + * @return log(value)/log(base). + */ +native Float:Logarithm(Float:value, Float:base=10.0); + +/** + * Returns the sine of the argument. + * + * @param value Input value in radians. + * @return sin(value). + */ +native Float:Sine(Float:value); + +/** + * Returns the cosine of the argument. + * + * @param value Input value in radians. + * @return cos(value). + */ +native Float:Cosine(Float:value); + +/** + * Returns the tangent of the argument. + * + * @param value Input value in radians. + * @return tan(value). + */ +native Float:Tangent(Float:value); + +/** + * Returns an absolute value. + * + * @param value Input value. + * @return Absolute value of the input. + */ +native Float:FloatAbs(Float:value); + +/** + * Returns the arctangent of the input value. + * + * @param angle Input value. + * @return atan(value) in radians. + */ +native Float:ArcTangent(Float:angle); + +/** + * Returns the arccosine of the input value. + * + * @param angle Input value. + * @return acos(value) in radians. + */ +native Float:ArcCosine(Float:angle); + +/** + * Returns the arcsine of the input value. + * + * @param angle Input value. + * @return asin(value) in radians. + */ +native Float:ArcSine(Float:angle); + +/** + * Returns the arctangent2 of the input values. + * + * @param x Horizontal value. + * @param y Vertical value. + * @return atan2(value) in radians. + */ +native Float:ArcTangent2(Float:x, Float:y); + +/** + * Rounds a floating point number using the "round to nearest" algorithm. + * + * @param value Floating point value to round. + * @return The value rounded to the nearest integer. + */ +stock RoundFloat(Float:value) +{ + return RoundToNearest(value); +} + +/** + * User defined operators. + * + */ +#pragma rational Float + +native Float:operator*(Float:oper1, Float:oper2) = FloatMul; +native Float:operator/(Float:oper1, Float:oper2) = FloatDiv; +native Float:operator+(Float:oper1, Float:oper2) = FloatAdd; +native Float:operator-(Float:oper1, Float:oper2) = FloatSub; + +stock Float:operator++(Float:oper) +{ + return oper+1.0; +} + +stock Float:operator--(Float:oper) +{ + return oper-1.0; +} + +stock Float:operator-(Float:oper) +{ + return oper^Float:((-1)^((-1)/2)); /* IEEE values are sign/magnitude */ +} + +stock Float:operator*(Float:oper1, oper2) +{ + return FloatMul(oper1, float(oper2)); /* "*" is commutative */ +} + +stock Float:operator/(Float:oper1, oper2) +{ + return FloatDiv(oper1, float(oper2)); +} + +stock Float:operator/(oper1, Float:oper2) +{ + return FloatDiv(float(oper1), oper2); +} + +stock Float:operator+(Float:oper1, oper2) +{ + return FloatAdd(oper1, float(oper2)); /* "+" is commutative */ +} + +stock Float:operator-(Float:oper1, oper2) +{ + return FloatSub(oper1, float(oper2)); +} + +stock Float:operator-(oper1, Float:oper2) +{ + return FloatSub(float(oper1), oper2); +} + +stock bool:operator==(Float:oper1, Float:oper2) +{ + return FloatCompare(oper1, oper2) == 0; +} + +stock bool:operator==(Float:oper1, oper2) +{ + return FloatCompare(oper1, float(oper2)) == 0; /* "==" is commutative */ +} + +stock bool:operator!=(Float:oper1, Float:oper2) +{ + return FloatCompare(oper1, oper2) != 0; +} + +stock bool:operator!=(Float:oper1, oper2) +{ + return FloatCompare(oper1, float(oper2)) != 0; /* "==" is commutative */ +} + +stock bool:operator>(Float:oper1, Float:oper2) +{ + return FloatCompare(oper1, oper2) > 0; +} + +stock bool:operator>(Float:oper1, oper2) +{ + return FloatCompare(oper1, float(oper2)) > 0; +} + +stock bool:operator>(oper1, Float:oper2) +{ + return FloatCompare(float(oper1), oper2) > 0; +} + +stock bool:operator>=(Float:oper1, Float:oper2) +{ + return FloatCompare(oper1, oper2) >= 0; +} + +stock bool:operator>=(Float:oper1, oper2) +{ + return FloatCompare(oper1, float(oper2)) >= 0; +} + +stock bool:operator>=(oper1, Float:oper2) +{ + return FloatCompare(float(oper1), oper2) >= 0; +} + +stock bool:operator<(Float:oper1, Float:oper2) +{ + return FloatCompare(oper1, oper2) < 0; +} + +stock bool:operator<(Float:oper1, oper2) +{ + return FloatCompare(oper1, float(oper2)) < 0; +} + +stock bool:operator<(oper1, Float:oper2) +{ + return FloatCompare(float(oper1), oper2) < 0; +} + +stock bool:operator<=(Float:oper1, Float:oper2) +{ + return FloatCompare(oper1, oper2) <= 0; +} + +stock bool:operator<=(Float:oper1, oper2) +{ + return FloatCompare(oper1, float(oper2)) <= 0; +} + +stock bool:operator<=(oper1, Float:oper2) +{ + return FloatCompare(float(oper1), oper2) <= 0; +} + +stock bool:operator!(Float:oper) +{ + return (_:oper & ((-1)/2)) == 0; /* -1 = all bits to 1; /2 = remove most significant bit (sign) + works on both 32bit and 64bit systems; no constant required */ +} + +/** + * Forbidden operators. + * + */ +forward operator%(Float:oper1, Float:oper2); +forward operator%(Float:oper1, oper2); +forward operator%(oper1, Float:oper2); + +#define FLOAT_PI 3.1415926535897932384626433832795 + +/** + * Converts degrees to radians. + * + * @param angle Degrees. + * @return Radians. + */ +stock Float:DegToRad(Float:angle) +{ + return (angle*FLOAT_PI)/180; +} + +/** + * Converts degrees to radians. + * + * @param angle Radians. + * @return Degrees. + */ +stock Float:RadToDeg(Float:angle) +{ + return (angle*180)/FLOAT_PI; +} diff --git a/src/include/functions.inc b/src/include/functions.inc new file mode 100644 index 0000000..4d8048a --- /dev/null +++ b/src/include/functions.inc @@ -0,0 +1,489 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: functions.inc 2360 2008-07-06 00:01:40Z dvander $ + */ + +#define SP_PARAMFLAG_BYREF (1<<0) /**< Internal use only. */ + +/** + * Describes the various ways to pass parameters to functions or forwards. + */ +enum ParamType +{ + Param_Any = 0, /**< Any data type can be pushed */ + Param_Cell = (1<<1), /**< Only basic cells can be pushed */ + Param_Float = (2<<1), /**< Only floats can be pushed */ + Param_String = (3<<1)|SP_PARAMFLAG_BYREF, /**< Only strings can be pushed */ + Param_Array = (4<<1)|SP_PARAMFLAG_BYREF, /**< Only arrays can be pushed */ + Param_VarArgs = (5<<1), /**< Same as "..." in plugins, anything can be pushed, but it will always be byref */ + Param_CellByRef = (1<<1)|SP_PARAMFLAG_BYREF, /**< Only a cell by reference can be pushed */ + Param_FloatByRef = (2<<1)|SP_PARAMFLAG_BYREF /**< Only a float by reference can be pushed */ +}; + +/** + * Defines how a forward iterates through plugin functions. + */ +enum ExecType +{ + ET_Ignore = 0, /**< Ignore all return values, return 0 */ + ET_Single = 1, /**< Only return the last exec, ignore all others */ + ET_Event = 2, /**< Acts as an event with the Actions defined in core.inc, no mid-Stops allowed, returns highest */ + ET_Hook = 3 /**< Acts as a hook with the Actions defined in core.inc, mid-Stops allowed, returns highest */ +}; + +/** + * @section Flags that are used with Call_PushArrayEx() and Call_PushStringEx() + */ + +#define SM_PARAM_COPYBACK (1<<0) /**< Copy an array/reference back after call */ + +#define SM_PARAM_STRING_UTF8 (1<<0) /**< String should be UTF-8 handled */ +#define SM_PARAM_STRING_COPY (1<<1) /**< String should be copied into the plugin */ +#define SM_PARAM_STRING_BINARY (1<<2) /**< Treat the string as a binary string */ + +/** + * @endsection + */ + +/** + * @section Error codes + */ +#define SP_ERROR_NONE 0 /**< No error occurred */ +#define SP_ERROR_FILE_FORMAT 1 /**< File format unrecognized */ +#define SP_ERROR_DECOMPRESSOR 2 /**< A decompressor was not found */ +#define SP_ERROR_HEAPLOW 3 /**< Not enough space left on the heap */ +#define SP_ERROR_PARAM 4 /**< Invalid parameter or parameter type */ +#define SP_ERROR_INVALID_ADDRESS 5 /**< A memory address was not valid */ +#define SP_ERROR_NOT_FOUND 6 /**< The object in question was not found */ +#define SP_ERROR_INDEX 7 /**< Invalid index parameter */ +#define SP_ERROR_STACKLOW 8 /**< Nnot enough space left on the stack */ +#define SP_ERROR_NOTDEBUGGING 9 /**< Debug mode was not on or debug section not found */ +#define SP_ERROR_INVALID_INSTRUCTION 10 /**< Invalid instruction was encountered */ +#define SP_ERROR_MEMACCESS 11 /**< Invalid memory access */ +#define SP_ERROR_STACKMIN 12 /**< Stack went beyond its minimum value */ +#define SP_ERROR_HEAPMIN 13 /**< Heap went beyond its minimum value */ +#define SP_ERROR_DIVIDE_BY_ZERO 14 /**< Division by zero */ +#define SP_ERROR_ARRAY_BOUNDS 15 /**< Array index is out of bounds */ +#define SP_ERROR_INSTRUCTION_PARAM 16 /**< Instruction had an invalid parameter */ +#define SP_ERROR_STACKLEAK 17 /**< A native leaked an item on the stack */ +#define SP_ERROR_HEAPLEAK 18 /**< A native leaked an item on the heap */ +#define SP_ERROR_ARRAY_TOO_BIG 19 /**< A dynamic array is too big */ +#define SP_ERROR_TRACKER_BOUNDS 20 /**< Tracker stack is out of bounds */ +#define SP_ERROR_INVALID_NATIVE 21 /**< Native was pending or invalid */ +#define SP_ERROR_PARAMS_MAX 22 /**< Maximum number of parameters reached */ +#define SP_ERROR_NATIVE 23 /**< Error originates from a native */ +#define SP_ERROR_NOT_RUNNABLE 24 /**< Function or plugin is not runnable */ +#define SP_ERROR_ABORTED 25 /**< Function call was aborted */ + +/** + * @endsection + */ + +/** + * Gets a function id from a function name. + * + * @param plugin Handle of the plugin that contains the function. + Pass INVALID_HANDLE to search in the calling plugin. + * @param name Name of the function. + * @return Function id or INVALID_FUNCTION if not found. + * @error Invalid or corrupt plugin handle. + */ +native Function:GetFunctionByName(Handle:plugin, const String:name[]); + +/** + * Creates a global forward. + * + * @note The name used to create the forward is used as its public function in all target plugins. + * @note This is ideal for global, static forwards that are never changed. + * @note Global forwards cannot be cloned. + * @note Use CloseHandle() to destroy these. + * + * @param name Name of public function to use in forward. + * @param type Execution type to be used. + * @param ... Variable number of parameter types (up to 32). + * @return Handle to new global forward. + * @error More than 32 paramater types passed. + */ +native Handle:CreateGlobalForward(const String:name[], ExecType:type, ParamType:...); + +/** + * Creates a private forward. + * + * @note No functions are automatically added. Use AddToForward() to do this. + * @note Private forwards can be cloned. + * @note Use CloseHandle() to destroy these. + * + * @param type Execution type to be used. + * @param ... Variable number of parameter types (up to 32). + * @return Handle to new private forward. + * @error More than 32 paramater types passed. + */ +native Handle:CreateForward(ExecType:type, ParamType:...); + +/** + * Returns the number of functions in a global or private forward's call list. + * + * @param fwd Handle to global or private forward. + * @return Number of functions in forward. + * @error Invalid or corrupt forward handle. + */ +native GetForwardFunctionCount(Handle:fwd); + +/** + * Adds a function to a private forward's call list. + * + * @note Cannot be used during an incompleted call. + * + * @param fwd Handle to private forward. + * @param plugin Handle of the plugin that contains the function. + * Pass INVALID_HANDLE to specify the calling plugin. + * @param func Function to add to forward. + * @return True on success, false otherwise. + * @error Invalid or corrupt private forward handle, invalid or corrupt plugin handle, or invalid function. + */ +native bool:AddToForward(Handle:fwd, Handle:plugin, Function:func); + +/** + * Removes a function from a private forward's call list. + * + * @note Only removes one instance. + * @note Functions will be removed automatically if their parent plugin is unloaded. + * + * @param fwd Handle to private forward. + * @param plugin Handle of the plugin that contains the function. + * Pass INVALID_HANDLE to specify the calling plugin. + * @param func Function to remove from forward. + * @return True on success, false otherwise. + * @error Invalid or corrupt private forward handle, invalid or corrupt plugin handle, or invalid function. + */ +native bool:RemoveFromForward(Handle:fwd, Handle:plugin, Function:func); + +/** + * Removes all instances of a plugin from a private forward's call list. + * + * @note Functions will be removed automatically if their parent plugin is unloaded. + * + * @param fwd Handle to private forward. + * @param plugin Handle of the plugin to remove instances of. + * Pass INVALID_HANDLE to specify the calling plugin. + * @return Number of functions removed from forward. + * @error Invalid or corrupt private forward handle or invalid or corrupt plugin handle. + */ +native RemoveAllFromForward(Handle:fwd, Handle:plugin); + +/** + * Starts a call to functions in a forward's call list. + * + * @note Cannot be used during an incompleted call. + * + * @param fwd Handle to global or private forward. + * @noreturn + * @error Invalid or corrupt forward handle or called before another call has completed. + */ +native Call_StartForward(Handle:fwd); + +/** + * Starts a call to a function. + * + * @note Cannot be used during an incompleted call. + * + * @param plugin Handle of the plugin that contains the function. + * Pass INVALID_HANDLE to specify the calling plugin. + * @param func Function to call. + * @noreturn + * @error Invalid or corrupt plugin handle, invalid function, or called before another call has completed. + */ +native Call_StartFunction(Handle:plugin, Function:func); + +/** + * Pushes a cell onto the current call. + * + * @note Cannot be used before a call has been started. + * + * @param value Cell value to push. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushCell(any:value); + +/** + * Pushes a cell by reference onto the current call. + * + * @note Cannot be used before a call has been started. + * + * @param value Cell reference to push. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushCellRef(&any:value); + +/** + * Pushes a float onto the current call. + * + * @note Cannot be used before a call has been started. + * + * @param value Floating point value to push. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushFloat(Float:value); + +/** + * Pushes a float by reference onto the current call. + * + * @note Cannot be used before a call has been started. + * + * @param value Floating point reference to push. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushFloatRef(&Float:value); + +/** + * Pushes an array onto the current call. + * + * @note Changes to array are not copied back to caller. Use PushArrayEx() to do this. + * @note Cannot be used before a call has been started. + * + * @param value Array to push. + * @param size Size of array. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushArray(const any:value[], size); + +/** + * Pushes an array onto the current call. + * + * @note Cannot be used before a call has been started. + * + * @param value Array to push. + * @param size Size of array. + * @param cpflags Whether or not changes should be copied back to the input array. + * See SP_PARAM_* constants for details. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushArrayEx(any:value[], size, cpflags); + +/** + * Pushes a string onto the current call. + * + * @note Changes to string are not copied back to caller. Use PushStringEx() to do this. + * @note Cannot be used before a call has been started. + * + * @param value String to push. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushString(const String:value[]); + +/** + * Pushes a string onto the current call. + * + * @note Cannot be used before a call has been started. + * + * @param value String to push. + * @param length Length of string buffer. + * @param szflags Flags determining how string should be handled. + * See SP_PARAM_STRING_* constants for details. + * The default (0) is to push ASCII. + * @param cpflags Whether or not changes should be copied back to the input array. + * See SP_PARAM_* constants for details. + * @noreturn + * @error Called before a call has been started. + */ +native Call_PushStringEx(String:value[], length, szflags, cpflags); + +/** + * Completes a call to a function or forward's call list. + * + * @note Cannot be used before a call has been started. + * + * @param result Return value of function or forward's call list. + * @return SP_ERROR_NONE on success, any other integer on failure. + * @error Called before a call has been started. + */ +native Call_Finish(&any:result=0); + +/** + * Cancels a call to a function or forward's call list. + * + * @note Cannot be used before a call has been started. + * + * @noreturn + * @error Called before a call has been started. + */ +native Call_Cancel(); + +/** + * Defines a native function. + * + * It is not necessary to validate the parameter count + * + * @param plugin Handle of the calling plugin. + * @param numParams Number of parameters passed to the native. + * @return Value for the native call to return. + */ +functag NativeCall public(Handle:plugin, numParams); + +/** + * Creates a dynamic native. This should only be called in AskPluginLoad(), or + * else you risk not having your native shared with other plugins. + * + * @param name Name of the dynamic native; must be unique amongst + * all other registered dynamic natives. + * @param func Function to use as the dynamic native. + * @noreturn + */ +native CreateNative(const String:name[], NativeCall:func); + +/** + * Throws an error in the calling plugin of a native, instead of your own plugin. + * + * @param error Error code to use. + * @param fmt Error message format. + * @param ... Format arguments. + */ +native ThrowNativeError(error, const String:fmt[], any:...); + +/** + * Retrieves the string length from a native parameter string. This is useful + * fetching the entire string using dynamic arrays. + * @note If this function succeeds, Get/SetNativeString will also succeed. + * + * @param param Parameter number, starting from 1. + * @param length Stores the length of the string. + * @return SP_ERROR_NONE on success, any other integer on failure. + * @error Invalid parameter number or calling from a non-native function. + */ +native GetNativeStringLength(param, &length); + +/** + * Retrieves a string from a native parameter. + * @note Output conditions are undefined on failure. + * + * @param param Parameter number, starting from 1. + * @param buffer Buffer to store the string in. + * @param maxlength Maximum length of the buffer. + * @param bytes Optionally store the number of bytes written. + * @return SP_ERROR_NONE on success, any other integer on failure. + * @error Invalid parameter number or calling from a non-native function. + */ +native GetNativeString(param, String:buffer[], maxlength, &bytes=0); + +/** + * Sets a string in a native parameter. + * @note Output conditions are undefined on failure. + * + * @param param Parameter number, starting from 1. + * @param source Source string to use. + * @param maxlength Maximum number of bytes to write. + * @param utf8 If false, string will not be written + * with UTF8 safety. + * @param bytes Optionally store the number of bytes written. + * @return SP_ERROR_NONE on success, any other integer on failure. + * @error Invalid parameter number or calling from a non-native function. + */ +native SetNativeString(param, const String:source[], maxlength, bool:utf8=true, &bytes=0); + +/** + * Gets a cell from a native parameter. + * + * @param param Parameter number, starting from 1. + * @return Cell value at the parameter number. + * @error Invalid parameter number or calling from a non-native function. + */ +native any:GetNativeCell(param); + +/** + * Gets a cell from a native parameter, by reference. + * + * @param param Parameter number, starting from 1. + * @return Cell value at the parameter number. + * @error Invalid parameter number or calling from a non-native function. + */ +native any:GetNativeCellRef(param); + +/** + * Sets a cell from a native parameter, by reference. + * + * @param param Parameter number, starting from 1. + * @param value Cell value at the parameter number to set by reference. + * @noreturn + * @error Invalid parameter number or calling from a non-native function. + */ +native SetNativeCellRef(param, any:value); + +/** + * Gets an array from a native parameter (always by reference). + * + * @param param Parameter number, starting from 1. + * @param local Local array to copy into. + * @param size Maximum size of local array. + * @return SP_ERROR_NONE on success, anything else on failure. + * @error Invalid parameter number or calling from a non-native function. + */ +native GetNativeArray(param, any:local[], size); + +/** + * Copies a local array into a native parameter array (always by reference). + * + * @param param Parameter number, starting from 1. + * @param local Local array to copy from. + * @param size Size of the local array to copy. + * @return SP_ERROR_NONE on success, anything else on failure. + * @error Invalid parameter number or calling from a non-native function. + */ +native SetNativeArray(param, const any:local[], size); + +/** + * Formats a string using parameters from a native. + * + * @note All parameter indexes start at 1. + * @note If the input and output buffers overlap, the contents + * of the output buffer at the end is undefined. + * + * @param out_param Output parameter number to write to. If 0, out_string is used. + * @param fmt_param Format parameter number. If 0, fmt_string is used. + * @param vararg_param First variable parameter number. + * @param out_len Output string buffer maximum length (always required). + * @param written Optionally stores the number of bytes written. + * @param out_string Output string buffer to use if out_param is not used. + * @param fmt_string Format string to use if fmt_param is not used. + * @return SP_ERROR_NONE on success, anything else on failure. + */ +native FormatNativeString(out_param, + fmt_param, + vararg_param, + out_len, + &written=0, + String:out_string[]="", + const String:fmt_string[]=""); diff --git a/src/include/geoip.inc b/src/include/geoip.inc new file mode 100644 index 0000000..8048bc9 --- /dev/null +++ b/src/include/geoip.inc @@ -0,0 +1,102 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: geoip.inc 1579 2007-10-15 04:27:56Z dvander $ + */ + +#if defined _geoip_included + #endinput +#endif +#define _geoip_included + +#include + +/** + * @section IP addresses can contain ports, the ports will be stripped out. + */ + +/** + * Gets the two character country code from an IP address. (US, CA, etc) + * + * @param ip Ip to determine the country code. + * @param ccode Destination string buffer to store the code. + * @return True on success, false if no country found. + */ +native bool:GeoipCode2(const String:ip[], String:ccode[3]); + +/** + * Gets the three character country code from an IP address. (USA, CAN, etc) + * + * @param ip Ip to determine the country code. + * @param ccode Destination string buffer to store the code. + * @return True on success, false if no country found. + */ +native bool:GeoipCode3(const String:ip[], String:ccode[4]); + +/** + * Gets the full country name. (max length of output string is 45) + * + * @param ip Ip to determine the country code. + * @param ccode Destination string buffer to store the country name. + * @param len Maximum length of output string buffer. + * @return True on success, false if no country found. + */ +native bool:GeoipCountry(const String:ip[], String:name[], maxlength); + +/** + * @endsection + */ + +/** + * Do not edit below this line! + */ +public Extension:__ext_geoip = +{ + name = "GeoIP", + file = "geoip.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_geoip_SetNTVOptional() +{ + MarkNativeAsOptional("GeoipCode2"); + MarkNativeAsOptional("GeoipCode3"); + MarkNativeAsOptional("GeoipCountry"); +} +#endif diff --git a/src/include/hacks.inc b/src/include/hacks.inc new file mode 100644 index 0000000..6145213 --- /dev/null +++ b/src/include/hacks.inc @@ -0,0 +1,199 @@ +#if defined _hacks_included + #endinput +#endif +#define _hacks_included + +#include + +// Version: 1.3.0.0 + +#define Hacks_Continue -54321 + +enum Hacks_HookType +{ + HACKS_HTYPE_SPAWN=0, + HACKS_HTYPE_TRACEATTACK, + HACKS_HTYPE_ONTAKEDAMAGE, + HACKS_HTYPE_EVENT_KILLED, + HACKS_HTYPE_STARTTOUCH, + HACKS_HTYPE_TOUCH, + HACKS_HTYPE_ENDTOUCH, + HACKS_HTYPE_UPDATEONREMOVE, + HACKS_HTYPE_WEAPON_CANUSE, + HACKS_HTYPE_WEAPON_DROP, + HACKS_HTYPE_WEAPON_CANSWITCHTO, + HACKS_HTYPE_COMMITSUICIDE, + HACKS_HTYPE_IMPULSECOMMANDS, + HACKS_HTYPE_PLAYERRUNCOMMAND +}; + +enum Hacks_CallType +{ + HACKS_CTYPE_ENTITY=0, + HACKS_CTYPE_COLLISIONPROP, + HACKS_CTYPE_PHYSICSOBJECT, + HACKS_CTYPE_EVENTQUEUE, + HACKS_CTYPE_GAMERULES +}; + +enum Hacks_ReturnType +{ + HACKS_RTYPE_VOID=0, + HACKS_RTYPE_INT, + HACKS_RTYPE_FLOAT, + HACKS_RTYPE_EDICT, + HACKS_RTYPE_ENTITY +}; + +enum Hacks_Param +{ + HACKS_PARAM_NULL=0, + HACKS_PARAM_INT, + HACKS_PARAM_FLOAT, + HACKS_PARAM_STRING, + HACKS_PARAM_VECTOR, // Pointer + HACKS_PARAM_QANGLE, // Pointer + HACKS_PARAM_EDICT, + HACKS_PARAM_ENTITY, + HACKS_PARAM_PHYSICSOBJECT +}; + +/** + * Callback for a hooked function. + * + * @param entity Index of the entity that was hooked. + * @param arg1 + * @param arg2 + * @param arg3 + * @param arg4 + * @param arg5 + * @return Hacks_Continue, other = Supercede the + * function and return the value. + */ +functag Hacks_HookFunc public(entity, arg1, arg2, arg3, arg4, arg5); + +/** + * Hooks a virtual function. + * + * @param entity Index of the entity. + * @param type Type of the function, see Hacks_HookType. + * @param handler Your function that will be called on hooking. + * @param post True for post operation, false for pre operation. + * @return HookID, that's needed to unhook. + */ +native Hacks_Hook(entity, Hacks_HookType:type, Hacks_HookFunc:func, bool:post=false); + +/** + * Unhooks a virtual function. + * + * @param hookid The returned hookid from Hacks_Hook. + * @noreturn + */ +native Hacks_Unhook(hookid); + +/** + * Scans for a function in the memory. + * + * @param name Name of the function (e.g. "CBaseAnimating::Ignite"). + * @param signature Signature to scan for (Windows). 2A = wildcard + * (e.g. "56 8B 74 24 0C 83 FE FF 57 8B 7C 24 0C 74 25 8B") + * @param symbol Symbol to scan for (Linux). + * @return CallID, that's needed to call the function. + */ +native Hacks_Find(const String:name[], const String:signature[], const String:symbol[]); + +/** + * Calls a previously scanned function. + * + * @param entity Index of the entity. + * @param callid The returned callid from Hacks_Find. + * @param calltype Type to call, see Hacks_CallType. + * @param returntype Return type, see Hacks_ReturnType. + * @param[x] argtype Datatype of argument, see Hacks_Param. + * @param[y] arg Argument for function. + * @return If there was a failure in the function, it will return -1, + * otherwise see Hacks_ReturnType. + */ +native Hacks_Call(entity, callid, Hacks_CallType:calltype, Hacks_ReturnType:returntype, any:...); + +/** + * Calls a virtual function by its offset. + * + * @param entity Index of the entity. + * @param windows Windows offset. + * @param linux Linux offset. + * @param calltype Type to call, see Hacks_CallType. + * @param returntype Return type, see Hacks_ReturnType. + * @param[x] argtype Datatype of argument, see Hacks_Param. + * @param[y] arg Argument for function. + * @return If there was a failure in the function, it will return -1, + * otherwise see Hacks_ReturnType. + */ +native Hacks_Call_Offset(entity, windows, linux, Hacks_CallType:calltype, Hacks_ReturnType:returntype, any:...); + +/** + * Calls a previously scanned function, but without an entity. + * + * @param callid The returned callid from Hacks_Find. + * @param returntype Return type, see Hacks_ReturnType. + * @param[x] argtype Datatype of argument, see Hacks_Param. + * @param[y] arg Argument for function. + * @return If there was a failure in the function, it will return -1, + * otherwise see Hacks_ReturnType. + */ +native Hacks_Call_NoEntity(callid, Hacks_ReturnType:returntype, any:...); + +/** + * Adds a file to be force downloaded (cleared every map change). + * + * @param ... Each argument = One file that will be added. + * @noreturn + */ +native Hacks_AddDownload(const String:...); + +/** + * Creates a patch that you can use with Hacks_Patch/Hacks_Unpatch + * + * @param callid The returned callid from Hacks_Find. + * @param windows_bytes New Bytes (eg "C2 08 00") + * @param windows_offset Offset (addr to patch = func_addr + offset) + * @param linux_bytes + * @param linux_offset + * @return PatchID, that's needed to patch/unpatch. + */ +native Hacks_CreatePatch(callid, const String:windows_bytes[], windows_offset, const String:linux_bytes[], linux_offset); + +/** + * Writes new bytes to a given address + * + * @param patchid The returned patchid from Hacks_CreatePatch. + * @noreturn + */ +native Hacks_Patch(patchid); + +/** + * Writes the original bytes to a given address + * + * @param patchid The returned patchid from Hacks_CreatePatch. + * @noreturn + */ +native Hacks_Unpatch(patchid); + +/** + * Do not edit below this line! + */ +public Extension:__ext_hacks = +{ + name = "Hacks Extension", + file = "hacks.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; \ No newline at end of file diff --git a/src/include/halflife.inc b/src/include/halflife.inc new file mode 100644 index 0000000..6d832e3 --- /dev/null +++ b/src/include/halflife.inc @@ -0,0 +1,544 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: halflife.inc 1921 2008-03-02 23:54:38Z dvander $ + */ + +#if defined _halflife_included + #endinput +#endif +#define _halflife_included + +#define SOURCE_SDK_UNKNOWN 0 /**< Could not determine the engine version */ +#define SOURCE_SDK_ORIGINAL 10 /**< Original Source engine (still used by "The Ship") */ +#define SOURCE_SDK_EPISODE1 20 /**< SDK+Engine released after Episode 1 */ +#define SOURCE_SDK_EPISODE2 30 /**< Engine released after Episode 2 (no SDK yet) */ + +#define MOTDPANEL_TYPE_TEXT 0 /**< Treat msg as plain text */ +#define MOTDPANEL_TYPE_INDEX 1 /**< Msg is auto determined by the engine */ +#define MOTDPANEL_TYPE_URL 2 /**< Treat msg as an URL link */ +#define MOTDPANEL_TYPE_FILE 3 /**< Treat msg as a filename to be openned */ + +enum DialogType +{ + DialogType_Msg = 0, /**< just an on screen message */ + DialogType_Menu, /**< an options menu */ + DialogType_Text, /**< a richtext dialog */ + DialogType_Entry, /**< an entry box */ + DialogType_AskConnect /**< ask the client to connect to a specified IP */ +}; + +/** + * Logs a generic message to the HL2 logs. + * + * @param format String format. + * @param ... Format arguments. + * @noreturn + */ +native LogToGame(const String:format[], any:...); + +/** + * Sets the seed value for the global Half-Life 2 Random Stream. + * + * @param seed Seed value. + * @noreturn + */ +native SetRandomSeed(seed); + +/** + * Returns a random floating point number from the Half-Life 2 Random Stream. + * + * @param fMin Minimum random bound. + * @param fMax Maximum random bound. + * @return A random number between (inclusive) fMin and fMax. + */ +native Float:GetRandomFloat(Float:fMin=0.0, Float:fMax=1.0); + +/** + * Returns a random number from the Half-Life 2 Random Stream. + * + * @param nmin Minimum random bound. + * @param nmax Maximum random bound. + * @return A random number between (inclusive) nmin and nmax. + */ +native GetRandomInt(nmin, nmax); + +/** + * Returns whether a map is valid or not. + * + * @param Map name, excluding .bsp extension. + * @return True if valid, false otherwise. + */ +native bool:IsMapValid(const String:map[]); + +/** + * Returns whether the server is dedicated. + * + * @return True if dedicated, false otherwise. + */ +native bool:IsDedicatedServer(); + +/** + * Returns a high-precision time value for profiling the engine. + * + * @return A floating point time value. + */ +native Float:GetEngineTime(); + +/** + * Returns the game time based on the game tick. + * + * @return Game tick time. + */ +native Float:GetGameTime(); + +/** + * Returns the game description from the mod. + * + * @param buffer Buffer to store the description. + * @param maxlength Maximum size of the buffer. + * @param original If true, retrieves the original game description, + * ignoring any potential hooks from plugins. + * @return Number of bytes written to the buffer (UTF-8 safe). + */ +native GetGameDescription(String:buffer[], maxlength, bool:original=false); + +/** + * Returns the name of the game's directory. + * + * @param buffer Buffer to store the directory name. + * @param maxlength Maximum size of the buffer. + * + * return Number of bytes written to the buffer (UTF-8 safe). + */ +native GetGameFolderName(String:buffer[], maxlength); + +/** + * Returns the current map name. + * + * @param buffer Buffer to store map name. + * @param maxlength Maximum length of buffer. + * @return Number of bytes written (UTF-8 safe). + */ +native GetCurrentMap(String:buffer[], maxlength); + +/** + * Precaches a given model. + * + * @param model Name of the model to precache. + * @param preload If preload is true the file will be precached before level startup. + * @return Returns the model index, 0 for error. + */ +native PrecacheModel(const String:model[], bool:preload=false); + +/** + * Precaches a given sentence file. + * + * @param file Name of the sentence file to precache. + * @param preload If preload is true the file will be precached before level startup. + * @return Returns a sentence file index. + */ +native PrecacheSentenceFile(const String:file[], bool:preload=false); + +/** + * Precaches a given decal. + * + * @param decal Name of the decal to precache. + * @param preload If preload is true the file will be precached before level startup. + * @return Returns a decal index. + */ +native PrecacheDecal(const String:decal[], bool:preload=false); + +/** + * Precaches a given generic file. + * + * @param generic Name of the generic file to precache. + * @param preload If preload is true the file will be precached before level startup. + * @return Returns a generic file index. + */ +native PrecacheGeneric(const String:generic[], bool:preload=false); + +/** + * Returns if a given model is precached. + * + * @param model Name of the model to check. + * @return True if precached, false otherwise. + */ +native bool:IsModelPrecached(const String:model[]); + +/** + * Returns if a given decal is precached. + * + * @param decal Name of the decal to check. + * @return True if precached, false otherwise. + */ +native bool:IsDecalPrecached(const String:decal[]); + +/** + * Returns if a given generic file is precached. + * + * @param decal Name of the generic file to check. + * @return True if precached, false otherwise. + */ +native bool:IsGenericPrecached(const String:generic[]); + +/** + * Precaches a given sound. + * + * @param sound Name of the sound to precache. + * @param preload If preload is true the file will be precached before level startup. + * @return True if successfully precached, false otherwise. + */ +native bool:PrecacheSound(const String:sound[], bool:preload=false); + +/** + * Returns if a given sound is precached. + * + * @param sound Name of the sound to check. + * @return True if precached, false otherwise. + */ +native bool:IsSoundPrecached(const String:sound[]); + +/** + * Creates different types of ingame messages. + * + * @param client Index of the client. + * @param kv KeyValues handle to set the menu keys and options. (Check iserverplugin.h for more information). + * @param type Message type to display ingame. + * @noreturn + * @error Invalid client index, or client not connected. + */ +native CreateDialog(client, Handle:kv, DialogType:type); + +/** + * Guesses the SDK version a mod was compiled against. If nothing + * specific is known about the game, the engine version is used instead. + * + * The return values are guaranteed to increase chronologically (that is, + * a later release will have a higher value). + * + * @return SOURCE_SDK version code. + */ +native GuessSDKVersion(); + +/** + * Prints a message to a specific client in the chat area. + * + * @param client Client index. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error If the client is not connected an error will be thrown. + */ +native PrintToChat(client, const String:format[], any:...); + +/** + * Prints a message to all clients in the chat area. + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ +stock PrintToChatAll(const String:format[], any:...) +{ + new maxClients = GetMaxClients(); + decl String:buffer[192]; + + for (new i = 1; i <= maxClients; i++) + { + if (IsClientInGame(i)) + { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), format, 2); + PrintToChat(i, "%s", buffer); + } + } +} + +/** + * Prints a message to a specific client in the center of the screen. + * + * @param client Client index. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error If the client is not connected an error will be thrown. + */ +native PrintCenterText(client, const String:format[], any:...); + +/** + * Prints a message to all clients in the center of the screen. + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ +stock PrintCenterTextAll(const String:format[], any:...) +{ + new maxClients = GetMaxClients(); + decl String:buffer[192]; + + for (new i = 1; i <= maxClients; i++) + { + if (IsClientInGame(i)) + { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), format, 2); + PrintCenterText(i, "%s", buffer); + } + } +} + +/** + * Prints a message to a specific client with a hint box. + * + * @param client Client index. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error If the client is not connected an error will be thrown. + */ +native PrintHintText(client, const String:format[], any:...); + +/** + * Prints a message to all clients with a hint box. + * + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + */ +stock PrintHintTextToAll(const String:format[], any:...) +{ + new maxClients = GetMaxClients(); + decl String:buffer[192]; + + for (new i = 1; i <= maxClients; i++) + { + if (IsClientInGame(i)) + { + SetGlobalTransTarget(i); + VFormat(buffer, sizeof(buffer), format, 2); + PrintHintText(i, "%s", buffer); + } + } +} + +/** + * Shows a VGUI panel to a specific client. + * + * @param client Client index. + * @param name Panel type name (Check viewport_panel_names.h to see a list of + * some panel names). + * @param Kv KeyValues handle with all the data for the panel setup (Depends + * on the panel type and may be unused). + * @param show True to show the panel, or false to remove it from the client screen. + * @noreturn + * @error If the client is not connected an error will be thrown. + */ +native ShowVGUIPanel(client, const String:name[], Handle:Kv=INVALID_HANDLE, bool:show=true); + +/** + * Creates a HUD synchronization object. This object is used to automatically assign and + * re-use channels for a set of messages. + * + * The HUD has a hardcoded number of channels (usually 6) for displaying + * text. You can use any channel for any area of the screen. Text on + * different channels can overlap, but text on the same channel will + * erase the old text first. This overlapping and overwriting gets problematic. + * + * A HUD synchronization object automatically selects channels for you based on + * the following heuristics: + * - If channel X was last used by the object, and hasn't been modified again, + * channel X gets re-used. + * - Otherwise, a new channel is chosen based on the least-recently-used channel. + * + * This ensures that if you display text on a sync object, that the previous text + * displayed on it will always be cleared first. This is because your new text + * will either overwrite the old text on the same channel, or because another + * channel has already erased your text. + * + * Note that messages can still overlap if they are on different synchronization + * objects, or they are displayed to manual channels. + * + * These are particularly useful for displaying repeating or refreshing HUD text, in + * addition to displaying multiple message sets in one area of the screen (for example, + * center-say messages that may pop up randomly that you don't want to overlap each + * other). + * + * @return New HUD synchronization object. + * The Handle can be closed with CloseHandle(). + * If HUD text is not supported on this mod, then + * INVALID_HANDLE is returned. + */ +native Handle:CreateHudSynchronizer(); + +/** + * Sets the HUD parameters for drawing text. These parameters are stored + * globally, although nothing other than this function and SetHudTextParamsEx + * modify them. + * + * You must call this function before drawing text. If you are drawing + * text to multiple clients, you can set the parameters once, since + * they won't be modified. However, as soon as you pass control back + * to other plugins, you must reset the parameters next time you draw. + * + * @param x x coordinate, from 0 to 1. -1.0 is the center. + * @param y y coordinate, from 0 to 1. -1.0 is the center. + * @param holdTime Number of seconds to hold the text. + * @param r Red color value. + * @param g Green color value. + * @param b Blue color value. + * @param a Alpha transparency value. + * @param effect 0/1 causes the text to fade in and fade out. + * 2 causes the text to flash[?]. + * @param fxTime Duration of chosen effect (may not apply to all effects). + * @param fadeIn Number of seconds to spend fading in. + * @param fadeOut Number of seconds to spend fading out. + * @noreturn + */ +native SetHudTextParams(Float:x, Float:y, Float:holdTime, r, g, b, a, effect = 0, + Float:fxTime=6.0, Float:fadeIn=0.1, Float:fadeOut=0.2); + +/** + * Sets the HUD parameters for drawing text. These parameters are stored + * globally, although nothing other than this function and SetHudTextParams + * modify them. + * + * This is the same as SetHudTextParams(), except it lets you set the alternate + * color for when effects require it. + * + * @param x x coordinate, from 0 to 1. -1.0 is the center. + * @param y y coordinate, from 0 to 1. -1.0 is the center. + * @param holdTime Number of seconds to hold the text. + * @param color1 First color set, array values being [red, green, blue, alpha] + * @param color2 Second color set, array values being [red, green, blue, alpha] + * @param effect 0/1 causes the text to fade in and fade out. + * 2 causes the text to flash[?]. + * @param fxTime Duration of chosen effect (may not apply to all effects). + * @param fadeIn Number of seconds to spend fading in. + * @param fadeOut Number of seconds to spend fading out. + * @noreturn + */ +native SetHudTextParamsEx(Float:x, Float:y, Float:holdTime, color1[4], + color2[4]={255,255,255,0}, effect = 0, Float:fxTime=6.0, + Float:fadeIn=0.1, Float:fadeOut=0.2); + +/** + * Shows a synchronized HUD message to a client. + * + * As of this writing, only TF, HL2MP, and SourceForts support HUD Text. + * + * @param client Client index to send the message to. + * @param sync Synchronization object. + * @param message Message text or formatting rules. + * @param ... Message formatting parameters. + * @return -1 on failure, anything else on success. + * This function fails if the mod does not support it. + * @error Client not in-game, or sync object not valid. + */ +native ShowSyncHudText(client, Handle:sync, const String:message[], any:...); + +/** + * Clears the text on a synchronized HUD channel. + * + * This is not the same as sending "" because it guarantees that it won't + * overwrite text on another channel. For example, consider the scenario: + * + * 1. Your synchronized message goes to channel 3. + * 2. Someone else's non-synchronized message goes to channel 3. + * + * If you were to simply send "" on your synchronized message, + * then someone else's text could be overwritten. + * + * @param client Client index to send the message to. + * @param sync Synchronization object. + * @noreturn + * @error Client not in-game, or sync object not valid. + */ +native ClearSyncHud(client, Handle:sync); + +/** + * Shows a HUD message to a client on the given channel. + * + * As of this writing, only TF, HL2MP, and SourceForts support HUD Text. + * + * @param client Client index to send the message to. + * @param channel A channel number. + * If -1, then a channel will automatically be selected + * based on the least-recently-used channel. If the + * channel is any other number, it will be modulo'd with + * the channel count to get a final channel number. + * @param message Message text or formatting rules. + * @param ... Message formatting parameters. + * @return -1 on failure (lack of mod support). + * Any other return value is the channel number that was + * used to render the text. + */ +native ShowHudText(client, channel, const String:message[], any:...); + +/** + * Shows a MOTD panel to a specific client. + * + * @param client Client index. + * @param title Title of the panel (printed on the top border of the window). + * @param msg Contents of the panel, it can be treated as an url, filename or plain text + * depending on the type parameter (WARNING: msg has to be 192 bytes maximum!) + * @param type Determines the way to treat the message body of the panel. + * @noreturn + * @error If the client is not connected an error will be thrown. + */ +stock ShowMOTDPanel(client, const String:title[], const String:msg[], type=MOTDPANEL_TYPE_INDEX) +{ + decl String:num[3]; + new Handle:Kv = CreateKeyValues("data"); + IntToString(type, num, sizeof(num)); + + KvSetString(Kv, "title", title); + KvSetString(Kv, "type", num); + KvSetString(Kv, "msg", msg); + ShowVGUIPanel(client, "info", Kv); + CloseHandle(Kv); +} + +/** + * Displays a panel asking the client to connect to a specified IP. + * + * @param client Client index. + * @param time Duration to hold the panel on the client's screen. + * @param ip Destionation IP. + * @noreturn + */ +stock DisplayAskConnectBox(client, Float:time, const String:ip[]) +{ + new Handle:Kv = CreateKeyValues("data"); + KvSetFloat(Kv, "time", time); + KvSetString(Kv, "title", ip); + CreateDialog(client, Kv, DialogType_AskConnect); + CloseHandle(Kv); +} diff --git a/src/include/handles.inc b/src/include/handles.inc new file mode 100644 index 0000000..19a8b6c --- /dev/null +++ b/src/include/handles.inc @@ -0,0 +1,96 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: handles.inc 1771 2007-12-05 15:38:45Z dvander $ + */ + +#if defined _handles_included + #endinput +#endif +#define _handles_included + +/** + * Handle helper macros. + */ +enum Handle +{ + INVALID_HANDLE = 0, +}; + + +/** + * Closes a Handle. If the handle has multiple copies open, + * it is not destroyed unless all copies are closed. + * + * @note Closing a Handle has a different meaning for each Handle type. Make + * sure you read the documentation on whatever provided the Handle. + * + * @param hndl Handle to close. + * @return True if successful, false if not closeable. + * @error Invalid handles will cause a run time error. + */ +native bool:CloseHandle(Handle:hndl); + +/** + * Clones a Handle. When passing handles in between plugins, caching handles + * can result in accidental invalidation when one plugin releases the Handle, or is its owner + * is unloaded from memory. To prevent this, the Handle may be "cloned" with a new owner. + * + * @note Usually, you will be cloning Handles for other plugins. This means that if you clone + * the Handle without specifying the new owner, it will assume the identity of your original calling + * plugin, which is not very useful. You should either specify that the receiving plugin should + * clone the handle on its own, or you should explicitly clone the Handle using the receiving plugin's + * identity Handle. + * + * @param hndl Handle to clone/duplicate. + * @param plugin Optional Handle to another plugin to mark as the new owner. + * If no owner is passed, the owner becomes the calling plugin. + * @return Handle on success, INVALID_HANDLE if not cloneable. + * @error Invalid handles will cause a run time error. + */ +native Handle:CloneHandle(Handle:hndl, Handle:plugin=INVALID_HANDLE); + +/** + * Do not use this function. Returns if a Handle and its contents + * are readable, whereas INVALID_HANDLE only checks for the absence + * of a Handle. + * + * This function is intended only for tests where the validity of a + * Handle can absolutely not be known. + * + * Do not use this to check the return values of functions, or to + * check if timers should be closed (except in very rare cases). + * This function is for very specific usage and using it for general + * purpose routines can and will hide very subtle bugs. + * + * @param hndl Handle to test for validity. + * @return True if handle is valid, false otherwise. + */ +#pragma deprecated Do not use this function. +native bool:IsValidHandle(Handle:hndl); diff --git a/src/include/helpers.inc b/src/include/helpers.inc new file mode 100644 index 0000000..841601a --- /dev/null +++ b/src/include/helpers.inc @@ -0,0 +1,294 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: helpers.inc 1895 2008-02-22 21:01:08Z dvander $ + */ + +#if defined _helpers_included + #endinput +#endif +#define _helpers_included + +/** + * Formats a user's info as log text. This is usually not needed because + * %L can be used to auto-format client information into a string. + * + * @param client Client index. + * @param buffer Buffer for text. + * @param maxlength Maximum length of text. + */ +stock FormatUserLogText(client, String:buffer[], maxlength) +{ + decl String:auth[32]; + decl String:name[40]; + + new userid = GetClientUserId(client); + if (!GetClientAuthString(client, auth, sizeof(auth))) + { + strcopy(auth, sizeof(auth), "UNKNOWN"); + } + if (!GetClientName(client, name, sizeof(name))) + { + strcopy(name, sizeof(name), "UNKNOWN"); + } + + /** Currently, no team stuff ... */ + + Format(buffer, maxlength, "\"%s<%d><%s><>\"", name, userid, auth); +} + +/** + * Returns plugin handle from plugin filename. + * + * @param filename Filename of the plugin to search for. + * @return Handle to plugin if found, INVALID_HANDLE otherwise. + */ +stock Handle:FindPluginByFile(const String:filename[]) +{ + decl String:buffer[256]; + + new Handle:iter = GetPluginIterator(); + new Handle:pl; + + while (MorePlugins(iter)) + { + pl = ReadPlugin(iter); + + GetPluginFilename(pl, buffer, sizeof(buffer)); + if (strcmp(buffer, filename, false) == 0) + { + CloseHandle(iter); + return pl; + } + } + + CloseHandle(iter); + + return INVALID_HANDLE; +} + +/** + * @deprecated Use FindTarget() or ProcessTargetString(). + */ +#pragma deprecated Use FindTarget() or ProcessTargetString() +stock SearchForClients(const String:pattern[], clients[], maxClients) +{ + new maxclients = GetMaxClients(); + new total = 0; + + if (maxClients == 0) + { + return 0; + } + + if (pattern[0] == '#') + { + new input = StringToInt(pattern[1]); + if (!input) + { + decl String:name[65] + for (new i=1; i<=maxclients; i++) + { + if (!IsClientInGame(i)) + { + continue; + } + GetClientName(i, name, sizeof(name)); + if (strcmp(name, pattern, false) == 0) + { + clients[0] = i; + return 1; + } + } + } else { + new client = GetClientOfUserId(input); + if (client) + { + clients[0] = client; + return 1; + } + } + } + + decl String:name[65] + for (new i=1; i<=maxclients; i++) + { + if (!IsClientInGame(i)) + { + continue; + } + GetClientName(i, name, sizeof(name)); + if (StrContains(name, pattern, false) != -1) + { + clients[total++] = i; + if (total >= maxClients) + { + break; + } + } + } + + return total; +} + +/** + * Wraps ProcessTargetString() and handles producing error messages for + * bad targets. + * + * @param client Client who issued command + * @param target Client's target argument + * @param nobots Optional. Set to true if bots should NOT be targetted + * @param immunity Optional. Set to false to ignore target immunity. + * @return Index of target client, or -1 on error. + */ +stock FindTarget(client, const String:target[], bool:nobots = false, bool:immunity = true) +{ + decl String:target_name[MAX_TARGET_LENGTH]; + decl target_list[1], target_count, bool:tn_is_ml; + + new flags = COMMAND_FILTER_NO_MULTI; + if (nobots) + { + flags |= COMMAND_FILTER_NO_BOTS; + } + if (!immunity) + { + flags |= COMMAND_FILTER_NO_IMMUNITY; + } + + if ((target_count = ProcessTargetString( + target, + client, + target_list, + 1, + flags, + target_name, + sizeof(target_name), + tn_is_ml)) > 0) + { + return target_list[0]; + } + else + { + ReplyToTargetError(client, target_count); + return -1; + } +} + +/** + * This function is no longer supported. It has been replaced with ReadMapList(), + * which uses a more unified caching and configuration mechanism. This function also + * has a bug where if the cvar contents changes, the fileTime change won't be recognized. + * + * Loads a specified array with maps. The maps will be either loaded from mapcyclefile, or if supplied + * a cvar containing a file name. If the file in the cvar is bad, it will use mapcyclefile. The fileTime + * parameter is used to store a timestamp of the file. If specified, the file will only be reloaded if it + * has changed. + * + * @param array Valid array handle, should be created with CreateArray(33) or larger. + * @param fileTime Variable containing the "last changed" time of the file. Used to avoid needless reloading. + * @param fileCvar CVAR set to the file to be loaded. Optional. + * @return Number of maps loaded or 0 if in error. + */ +#pragma deprecated Use ReadMapList() instead. + stock LoadMaps(Handle:array, &fileTime = 0, Handle:fileCvar = INVALID_HANDLE) + { + decl String:mapPath[256], String:mapFile[64]; + new bool:fileFound = false; + + if (fileCvar != INVALID_HANDLE) + { + GetConVarString(fileCvar, mapFile, 64); + BuildPath(Path_SM, mapPath, sizeof(mapPath), mapFile); + fileFound = FileExists(mapPath); + } + + if (!fileFound) + { + new Handle:mapCycleFile = FindConVar("mapcyclefile"); + GetConVarString(mapCycleFile, mapPath, sizeof(mapPath)); + fileFound = FileExists(mapPath); + } + + if (!fileFound) + { + LogError("Failed to find a file to load maps from. No maps loaded."); + ClearArray(array); + + return 0; + } + + // If the file hasn't changed, there's no reason to reload + // all of the maps. + new newTime = GetFileTime(mapPath, FileTime_LastChange); + if (fileTime == newTime) + { + return GetArraySize(array); + } + + fileTime = newTime; + + ClearArray(array); + + new Handle:file = OpenFile(mapPath, "rt"); + if (file == INVALID_HANDLE) + { + LogError("Could not open file: %s", mapPath); + + return 0; + } + + LogMessage("Loading maps from file: %s", mapPath); + + decl String:buffer[64], len; + while (!IsEndOfFile(file) && ReadFileLine(file, buffer, sizeof(buffer))) + { + TrimString(buffer); + + if ((len = StrContains(buffer, ".bsp", false)) != -1) + { + buffer[len] = '\0'; + } + + if (buffer[0] == '\0' || !IsValidConVarChar(buffer[0]) || !IsMapValid(buffer)) + { + continue; + } + + if (FindStringInArray(array, buffer) != -1) + { + continue; + } + + PushArrayString(array, buffer); + } + + CloseHandle(file); + return GetArraySize(array); +} diff --git a/src/include/keyvalues.inc b/src/include/keyvalues.inc new file mode 100644 index 0000000..9e07883 --- /dev/null +++ b/src/include/keyvalues.inc @@ -0,0 +1,429 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: keyvalues.inc 1740 2007-12-01 00:37:47Z faluco $ + */ + +#if defined _keyvalues_included + #endinput +#endif +#define _keyvalues_included + +/** + * KeyValue data value types + */ +enum KvDataTypes +{ + KvData_None = 0, /**< Type could not be identified, or no type */ + KvData_String, /**< String value */ + KvData_Int, /**< Integer value */ + KvData_Float, /**< Floating point value */ + KvData_Ptr, /**< Pointer value (sometimes called "long") */ + KvData_WString, /**< Wide string value */ + KvData_Color, /**< Color value */ + KvData_UInt64, /**< Large integer value */ + /* --- */ + KvData_NUMTYPES, +}; + +/** + * Creates a new KeyValues structure. The Handle must always be closed. + * + * @param name Name of the root section. + * @param firstKey If non-empty, specifies the first key value. + * @param firstValue If firstKey is non-empty, specifies the first key's value. + * @return A Handle to a new KeyValues structure. + */ +native Handle:CreateKeyValues(const String:name[], const String:firstkey[]="", const String:firstValue[]=""); + +/** + * Sets a string value of a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param value String value. + * @noreturn + * @error Invalid Handle. + */ +native KvSetString(Handle:kv, const String:key[], const String:value[]); + +/** + * Sets an integer value of a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param value Value number. + * @noreturn + * @error Invalid Handle. + */ +native KvSetNum(Handle:kv, const String:key[], value); + +/** + * Sets a large integer value of a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param value Large integer value (0=High bits, 1=Low bits) + * @noreturn + * @error Invalid Handle. + */ +native KvSetUInt64(Handle:kv, const String:key[], const value[2]); + +/** + * Sets a floating point value of a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param value Floating point value. + * @noreturn + * @error Invalid Handle. + */ +native KvSetFloat(Handle:kv, const String:key[], Float:value); + +/** + * Sets a set of color values of a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param r Red value. + * @param g Green value. + * @param b Blue value. + * @param a Alpha value. + * @noreturn + * @error Invalid Handle. + */ +native KvSetColor(Handle:kv, const String:key[], r, g, b, a=0); + +/** + * Sets a vector value of a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param vec Vector value. + * @noreturn + * @error Invalid Handle. + */ +native KvSetVector(Handle:kv, const String:key[], const Float:vec[3]); + +/** + * Retrieves a string value from a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param value Buffer to store key value in. + * @param maxlength Maximum length of the value buffer. + * @param defvalue Optional default value to use if the key is not found. + * @noreturn + * @error Invalid Handle. + */ +native KvGetString(Handle:kv, const String:key[], String:value[], maxlength, const String:defvalue[]=""); + +/** + * Retrieves an integer value from a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param defvalue Optional default value to use if the key is not found. + * @return Integer value of the key. + * @error Invalid Handle. + */ +native KvGetNum(Handle:kv, const String:key[], defvalue=0); + +/** + * Retrieves a floating point value from a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param defvalue Optional default value to use if the key is not found. + * @return Floating point value of the key. + * @error Invalid Handle. + */ +native Float:KvGetFloat(Handle:kv, const String:key[], Float:defvalue=0.0); + +/** + * Retrieves a set of color values from a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param r Red value, set by reference. + * @param g Green value, set by reference. + * @param b Blue value, set by reference. + * @param a Alpha value, set by reference. + * @noreturn + * @error Invalid Handle. + */ +native KvGetColor(Handle:kv, const String:key[], &r, &g, &b, &a); + +/** + * Retrieves a large integer value from a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param value Array to represent the large integer. + * @param defvalue Optional default value to use if the key is not found. + * @noreturn + * @error Invalid Handle. + */ +native KvGetUInt64(Handle:kv, const String:key[], value[2], defvalue[2]={0,0}); + +/** + * Retrieves a vector value from a KeyValues key. + * + * @param kv KeyValues Handle. + * @param key Name of the key, or NULL_STRING. + * @param vec Destination vector to store the value in. + * @param defvalue Optional default value to use if the key is not found. + * @noreturn + * @error Invalid Handle. + */ +native KvGetVector(Handle:kv, const String:key[], Float:vec[3], const Float:defvalue[3]={0.0, 0.0, 0.0}); + +/** + * Sets the current position in the KeyValues tree to the given key. + * + * @param kv KeyValues Handle. + * @param key Name of the key. + * @param create If true, and the key does not exist, it will be created. + * @return True if the key exists, false if it does not and was not created. + */ +native bool:KvJumpToKey(Handle:kv, const String:key[], bool:create=false); + +/** + * Sets the current position in the KeyValues tree to the given key. + * + * @param kv KeyValues Handle. + * @param id KeyValues id. + * @return True if the key exists, false if it does not. + */ +native bool:KvJumpToKeySymbol(Handle:kv, id); + +/** + * Sets the current position in the KeyValues tree to the first sub key. + * This native adds to the internal traversal stack. + * + * @param kv KeyValues Handle. + * @param keyOnly If false, non-keys will be traversed (values). + * @return True on success, false if there was no first sub key. + * @error Invalid Handle. + */ +native bool:KvGotoFirstSubKey(Handle:kv, bool:keyOnly=true); + +/** + * Sets the current position in the KeyValues tree to the next sub key. + * This native does NOT add to the internal traversal stack, and thus + * KvGoBack() is not needed for each successive call to this function. + * + * @param kv KeyValues Handle. + * @param keyOnly If false, non-keys will be traversed (values). + * @return True on success, false if there was no next sub key. + * @error Invalid Handle. + */ +native bool:KvGotoNextKey(Handle:kv, bool:keyOnly=true); + +/** + * Saves the current position in the traversal stack onto the traversal + * stack. This can be useful if you wish to use KvGotoNextKey() and + * have the previous key saved for backwards traversal. + * + * @param kv KeyValues Handle. + * @noreturn + * @error Invalid Handle. + */ +native KvSavePosition(Handle:kv); + +/** + * Removes the given key from the current position. + * + * @param kv KeyValues Handle. + * @param key Name of the key. + * @return True on success, false if key did not exist. + * @error Invalid Handle. + */ +native bool:KvDeleteKey(Handle:kv, const String:key[]); + +/** + * Removes the current sub-key and attempts to set the position + * to the sub-key after the removed one. If no such sub-key exists, + * the position will be the parent key in the traversal stack. + * Given the sub-key having position "N" in the traversal stack, the + * removal will always take place from position "N-1." + * + * @param kv KeyValues Handle. + * @return 1 if removal succeeded and there was another key. + * 0 if the current node was not contained in the + * previous node, or no previous node exists. + * -1 if removal succeeded and there were no more keys, + * thus the state is as if KvGoBack() was called. + * @error Invalid Handle. + */ +native KvDeleteThis(Handle:kv); + +/** + * Jumps back to the previous position. Returns false if there are no + * previous positions (i.e., at the root node). This should be called + * once for each successful Jump call, in order to return to the top node. + * This function pops one node off the internal traversal stack. + * + * @param kv KeyValues Handle. + * @return True on success, false if there is no higher node. + * @error Invalid Handle. + */ +native bool:KvGoBack(Handle:kv); + +/** + * Sets the position back to the top node, emptying the entire node + * traversal history. This can be used instead of looping KvGoBack() + * if recursive iteration is not important. + * + * @param kv KeyValues Handle. + * @noreturn + * @error Invalid Handle. + */ +native KvRewind(Handle:kv); + +/** + * Retrieves the current section name. + * + * @param kv KeyValues Handle. + * @param section Buffer to store the section name. + * @param maxlength Maximum length of the name buffer. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:KvGetSectionName(Handle:kv, String:section[], maxlength); + +/** + * Sets the current section name. + * + * @param kv KeyValues Handle. + * @param section Section name. + * @noreturn + * @error Invalid Handle. + */ +native KvSetSectionName(Handle:kv, const String:section[]); + +/** + * Returns the data type at a key. + * + * @param kv KeyValues Handle. + * @param key Key name. + * @return KvDataType value of the key. + * @error Invalid Handle. + */ +native KvDataTypes:KvGetDataType(Handle:kv, const String:key[]); + +/** + * Converts a KeyValues tree to a file. The tree is dumped + * from the current position. + * + * @param kv KeyValues Handle. + * @param file File to dump write to. + * @return True on success, false otherwise. + * @error Invalid Handle. + */ +native bool:KeyValuesToFile(Handle:kv, const String:file[]); + +/** + * Converts a file to a KeyValues tree. The file is read into + * the current position of the tree. + * + * @param kv KeyValues Handle. + * @param file File to read from. + * @return True on success, false otherwise. + * @error Invalid Handle. + */ +native bool:FileToKeyValues(Handle:kv, const String:file[]); + +/** + * Sets whether or not the KeyValues parser will read escape sequences. + * For example, \n would be read as a literal newline. This defaults + * to false for new KeyValues structures. + * + * @param kv KeyValues Handle. + * @param useEscapes Whether or not to read escape sequences. + * @noreturn + * @error Invalid Handle. + */ +native KvSetEscapeSequences(Handle:kv, bool:useEscapes); + +/** + * Returns the position in the jump stack; I.e. the number of calls + * required for KvGoBack to return to the root node. If at the root node, + * 0 is returned. + * + * @param kv KeyValues Handle. + * @return Number of non-root nodes in the jump stack. + * @error Invalid Handle. + */ +native KvNodesInStack(Handle:kv); + +/** + * Makes a new copy of all subkeys in the origin KeyValues to + * the destination KeyValues. + * NOTE: All KeyValues are processed from the current location not the root one. + * + * @param origin Origin KeyValues Handle. + * @param dest Destination KeyValues Handlee. + * @noreturn + * @error Invalid Handle. + */ +native KvCopySubkeys(Handle:origin, Handle:dest); + +/** + * Finds a KeyValues name by id. + * + * @param kv KeyValues Handle. + * @param id KeyValues id. + * @param name Buffer to store the name. + * @param maxlength Maximum length of the value buffer. + * @return True on success, false if id not found. + * @error Invalid Handle. + */ +native bool:KvFindKeyById(Handle:kv, id, String:name[], maxlength); + +/** + * Finds a KeyValues id inside a KeyValues tree. + * + * @param kv KeyValues Handle. + * @param key Key name. + * @param id Id of the found KeyValue. + * @return True on success, false if key not found. + * @error Invalid Handle. + */ +native bool:KvGetNameSymbol(Handle:kv, const String:key[], &id); + +/** + * Retrieves the current section id. + * + * @param kv KeyValues Handle. + * @param id Id of the current section. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:KvGetSectionSymbol(Handle:kv, &id); diff --git a/src/include/lang.inc b/src/include/lang.inc new file mode 100644 index 0000000..3186d40 --- /dev/null +++ b/src/include/lang.inc @@ -0,0 +1,96 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: lang.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _lang_included + #endinput +#endif +#define _lang_included + +#define LANG_SERVER 0 /**< Translate using the server's language */ + +/** + * Loads a translation file for the plugin calling this native. + * If no extension is specified, .txt is assumed. + * + * @param path Translation file. + * @noreturn + */ +native LoadTranslations(const String:file[]); + +/** + * Sets the global language target. This is useful for creating functions + * that will be compatible with the %t format specifier. Note that invalid + * indexes can be specified but the error will occur during translation, + * not during this function call. + * + * @param client Client index or LANG_SERVER. + * @noreturn + */ +native SetGlobalTransTarget(client); + +/** + * Retrieves the language number of a client. + * Currently this simply returns the server language index. + * + * @param client Client index. + * @return Language number client is using. + * @error Invalid client index or client not in game. + */ +native GetClientLanguage(client); + +/** + * Retrieves the server's language. + * + * @return Language number server is using. + */ +native GetServerLanguage(); + +/** + * Returns the number of languages known in languages.cfg. + * + * @return Language count. + */ +native GetLanguageCount(); + +/** + * Retrieves info about a given language number. + * + * @param language Language number. + * @param code Language code buffer (2-3 characters usually). + * @param codeLen Maximum length of the language code buffer. + * @param name Language name buffer. + * @param nameLen Maximum length of the language name buffer. + * @noreturn + * @error Invalid language number. + */ +native GetLanguageInfo(language, String:code[]="", codeLen=0, String:name[]="", nameLen=0); + diff --git a/src/include/logging.inc b/src/include/logging.inc new file mode 100644 index 0000000..569c3c5 --- /dev/null +++ b/src/include/logging.inc @@ -0,0 +1,153 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: logging.inc 1920 2008-03-02 19:08:27Z dvander $ + */ + +#if defined _sm_logging_included + #endinput +#endif +#define _sm_logging_included + +/** + * Logs a plugin message to the SourceMod logs. The log message will be + * prefixed by the plugin's logtag (filename). + * + * @param format String format. + * @param ... Format arguments. + * @noreturn + */ +native LogMessage(const String:format[], any:...); + +/** + * Logs a message to the SourceMod logs without any plugin logtag. This is + * useful for re-routing messages from other plugins, for example, messages + * from LogAction(). + * + * @param format String format. + * @param ... Format arguments. + * @noreturn + */ +native LogMessageEx(const String:format[], any:...); + +/** + * Logs a message to any file. The log message will be in the normal + * SourceMod format, with the plugin logtag prepended. + * + * @param file File to write the log message in. + * @param format String format. + * @param ... Format arguments. + * @noreturn + * @error File could not be opened/written. + */ +native LogToFile(const String:file[], const String:format[], any:...); + +/** + * Same as LogToFile(), except no plugin logtag is prepended. + * + * @param file File to write the log message in. + * @param format String format. + * @param ... Format arguments. + * @noreturn + * @error File could not be opened/written. + */ +native LogToFileEx(const String:file[], const String:format[], any:...); + +/** + * Logs an action from a command or event whereby interception and routing may + * be important. This is intended to be a logging version of ShowActivity(). + * + * @param client Client performing the action, 0 for server, or -1 if not + * applicable. + * @param target Client being targetted, or -1 if not applicable. + * @param message Message format. + * @param ... Message formatting parameters. + * @noreturn + */ +native LogAction(client, target, const String:message[], any:...); + +/** + * Logs a plugin error message to the SourceMod logs. + * + * @param format String format. + * @param ... Format arguments. + * @noreturn + */ +native LogError(const String:format[], any:...); + +/** + * Called when an action is going to be logged. + * + * @param source Handle to the object logging the action, or INVALID_HANDLE + * if Core is logging the action. + * @param ident Type of object logging the action (plugin, ext, or core). + * @param client Client the action is from; 0 for server, -1 if not applicable. + * @param target Client the action is targetting, or -1 if not applicable. + * @param message Message that is being logged. + * @return Plugin_Continue will cause Core to defaulty log the message. + * Plugin_Handled will stop Core from logging the message. + * Plugin_Stop is the same as Handled, but prevents any other + * plugins from handling the message. + */ +forward Action:OnLogAction(Handle:source, + Identity:ident, + client, + target, + const String:message[]); + +/** + * Called when a game log message is received. + * + * Any Log*() functions called within this callback will not recursively + * pass through. That is, they will log directly, bypassing this callback. + * + * Note that this does not capture log messages from the engine. It only + * captures log messages being sent from the game/mod itself. + * + * @param message Message contents. + * @return Plugin_Handled or Plugin_Stop will prevent the message + * from being written to the log file. + */ +functag GameLogHook Action:public(const String:message[]); + +/** + * Adds a game log hook. + * + * @param hook Hook function. + * @noreturn + */ +native AddGameLogHook(GameLogHook:hook); + +/** + * Removes a game log hook. + * + * @param hook Hook function. + * @noreturn + */ +native RemoveGameLogHook(GameLogHook:hook); diff --git a/src/include/market.inc b/src/include/market.inc new file mode 100644 index 0000000..64eeff0 --- /dev/null +++ b/src/include/market.inc @@ -0,0 +1,62 @@ +/** + * ==================== + * Market + * File: market.inc + * Version: 1.2 + * Author: Greyscale + * ==================== + */ + +/** + * Used to send the market to a client + * @param client Client index + * @param title Title of the market menu + * @param rebuy Text to display for the "Rebuy" option + * @noreturn + */ +native Market_Send(client, const String:title[], const String:rebuy[]); + +/** + * Used to send the market to a client + * @param weapondid ID of the weapon whose info will be returned + * @param display The display name of the weapon + * @param weapon The entity name of the weapon + * @param price The price of the weapon + * @return True if weaponid exists, false if not + */ +native bool:Market_GetWeaponIDInfo(const String:weaponid[], String:display[], String:weapon[], &price); + +/** + * Called when a player makes a selection from the menu + * @param client Client index + * @param weaponid Returns the ID of the weapon selected ("rebuy" is returned when player selected rebuy) + * @return True to allow purchase, false to block + */ +forward bool:Market_OnWeaponSelected(client, String:weaponid[]); + +/** + * Called after a player makes a selection from the menu + * @param client Client index + * @param allowed True if selection was allowed and false if it was blocked + * noreturn + */ +forward Market_PostOnWeaponSelected(client, &bool:allowed); + + + +public SharedPlugin:__pl_market = +{ + name = "market", + file = "market.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +public __pl_market_SetNTVOptional() +{ + MarkNativeAsOptional("Market_Send"); + MarkNativeAsOptional("Market_GetWeaponIDInfo"); +} \ No newline at end of file diff --git a/src/include/menus.inc b/src/include/menus.inc new file mode 100644 index 0000000..700ac51 --- /dev/null +++ b/src/include/menus.inc @@ -0,0 +1,787 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: menus.inc 1777 2007-12-06 12:26:59Z dvander $ + */ + +#if defined _menus_included + #endinput +#endif +#define _menus_included + +/** + * Low-level drawing style of the menu. + */ +enum MenuStyle +{ + MenuStyle_Default = 0, /**< The "default" menu style for the mod */ + MenuStyle_Valve = 1, /**< The Valve provided menu style (Used on HL2DM) */ + MenuStyle_Radio = 2, /**< The simpler menu style commonly used on CS:S */ +}; + +/** + * Different actions for the menu "pump" callback + */ +enum MenuAction +{ + MenuAction_Start = (1<<0), /**< A menu has been started (nothing passed) */ + MenuAction_Display = (1<<1), /**< A menu is about to be displayed (param1=client, param2=MenuPanel Handle) */ + MenuAction_Select = (1<<2), /**< An item was selected (param1=client, param2=item) */ + MenuAction_Cancel = (1<<3), /**< The menu was cancelled (param1=client, param2=reason) */ + MenuAction_End = (1<<4), /**< A menu display has fully ended. + param1 is the MenuEnd reason, and if it's MenuEnd_Cancelled, then + param2 is the MenuCancel reason from MenuAction_Cancel. + */ + MenuAction_VoteEnd = (1<<5), /**< (VOTE ONLY): A vote sequence has succeeded (param1=chosen item) + This is not called if SetVoteResultCallback has been used on the menu. */ + MenuAction_VoteStart = (1<<6), /**< (VOTE ONLY): A vote sequence has started (nothing passed) */ + MenuAction_VoteCancel = (1<<7), /**< (VOTE ONLY): A vote sequence has been cancelled (param1=reason) */ + MenuAction_DrawItem = (1<<8), /**< An item is being drawn; return the new style (param1=client, param2=item) */ + MenuAction_DisplayItem = (1<<9),/**< Item text is being drawn to the display (param1=client, param2=item) + To change the text, use RedrawMenuItem(). + If you do so, return its return value. Otherwise, return 0. + */ +}; + +/** Default menu actions */ +#define MENU_ACTIONS_DEFAULT MenuAction_Select|MenuAction_Cancel|MenuAction_End +/** All menu actions */ +#define MENU_ACTIONS_ALL MenuAction:0xFFFFFFFF + +#define MENU_NO_PAGINATION 0 /**< Menu should not be paginated (10 items max) */ +#define MENU_TIME_FOREVER 0 /**< Menu should be displayed as long as possible */ + +#define ITEMDRAW_DEFAULT (0) /**< Item should be drawn normally */ +#define ITEMDRAW_DISABLED (1<<0) /**< Item is drawn but not selectable */ +#define ITEMDRAW_RAWLINE (1<<1) /**< Item should be a raw line, without a slot */ +#define ITEMDRAW_NOTEXT (1<<2) /**< No text should be drawn */ +#define ITEMDRAW_SPACER (1<<3) /**< Item should be drawn as a spacer, if possible */ +#define ITEMDRAW_IGNORE ((1<<1)|(1<<2)) /**< Item should be completely ignored (rawline + notext) */ +#define ITEMDRAW_CONTROL (1<<4) /**< Item is control text (back/next/exit) */ + +#define MENUFLAG_BUTTON_EXIT (1<<0) /**< Menu has an "exit" button (default if paginated) */ +#define MENUFLAG_BUTTON_EXITBACK (1<<1) /**< Menu has an "exit back" button */ +#define MENUFLAG_NO_SOUND (1<<2) /**< Menu will not have any select sounds */ + +#define VOTEINFO_CLIENT_INDEX 0 /**< Client index */ +#define VOTEINFO_CLIENT_ITEM 1 /**< Item the client selected, or -1 for none */ +#define VOTEINFO_ITEM_INDEX 0 /**< Item index */ +#define VOTEINFO_ITEM_VOTES 1 /**< Number of votes for the item */ + +/** + * Reasons a menu can be cancelled (MenuAction_Cancel). + */ +enum +{ + MenuCancel_Disconnected = -1, /**< Client dropped from the server */ + MenuCancel_Interrupted = -2, /**< Client was interrupted with another menu */ + MenuCancel_Exit = -3, /**< Client exited via "exit" */ + MenuCancel_NoDisplay = -4, /**< Menu could not be displayed to the client */ + MenuCancel_Timeout = -5, /**< Menu timed out */ + MenuCancel_ExitBack = -6, /**< Client selected "exit back" on a paginated menu */ +}; + +/** + * Reasons a vote can be cancelled (MenuAction_VoteCancel). + */ +enum +{ + VoteCancel_Generic = -1, /**< Vote was generically cancelled. */ + VoteCancel_NoVotes = -2, /**< Vote did not receive any votes. */ +}; + +/** + * Reasons a menu ended (MenuAction_End). + */ +enum +{ + MenuEnd_Selected = 0, /**< Menu item was selected */ + MenuEnd_VotingDone = -1, /**< Voting finished */ + MenuEnd_VotingCancelled = -2, /**< Voting was cancelled */ + MenuEnd_Cancelled = -3, /**< Menu was cancelled (reason in param2) */ + MenuEnd_Exit = -4, /**< Menu was cleanly exited via "exit" */ + MenuEnd_ExitBack = -5, /**< Menu was cleanly exited via "back" */ +}; + +/** + * Describes a menu's source + */ +enum MenuSource +{ + MenuSource_None = 0, /**< No menu is being displayed */ + MenuSource_External = 1, /**< External menu */ + MenuSource_Normal = 2, /**< A basic menu is being displayed */ + MenuSource_RawPanel = 3, /**< A display is active, but it is not tied to a menu */ +}; + +/** + * Called when a menu action is completed. + * + * @param menu The menu being acted upon. + * @param action The action of the menu. + * @param param1 First action parameter (usually the client). + * @param param2 Second action parameter (usually the item). + * @noreturn + */ +functag MenuHandler public(Handle:menu, MenuAction:action, param1, param2); + +/** + * Creates a new, empty menu using the default style. + * + * @param handler Function which will receive menu actions. + * @param actions Optionally set which actions to receive. Select, + * Cancel, and End will always be received regardless + * of whether they are set or not. They are also + * the only default actions. + * @return A new menu Handle. + */ +native Handle:CreateMenu(MenuHandler:handler, MenuAction:actions=MENU_ACTIONS_DEFAULT); + +/** + * Displays a menu to a client. + * + * @param menu Menu Handle. + * @param client Client index. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false on failure. + * @error Invalid Handle or client not in game. + */ +native bool:DisplayMenu(Handle:menu, client, time); + +/** + * Displays a menu to a client, starting from the given item. + * + * @param menu Menu Handle. + * @param client Client index. + * @param first_item First item to begin drawing from. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false on failure. + * @error Invalid Handle or client not in game. + */ +native bool:DisplayMenuAtItem(Handle:menu, client, first_item, time); + +/** + * Appends a new item to the end of a menu. + * + * @param menu Menu Handle. + * @param info Item information string. + * @param display Default item display string. + * @param style Drawing style flags. Anything other than DEFAULT or + * DISABLED will be completely ignored when paginating. + * @return True on success, false on failure. + * @error Invalid Handle or item limit reached. + */ +native AddMenuItem(Handle:menu, + const String:info[], + const String:display[], + style=ITEMDRAW_DEFAULT); + +/** + * Inserts an item into the menu before a certain position; the new item will + * be at the given position and all next items pushed forward. + * + * @param menu Menu Handle. + * @param position Position, starting from 0. + * @param info Item information string. + * @param display Default item display string. + * @param style Drawing style flags. Anything other than DEFAULT or + * DISABLED will be completely ignored when paginating. + * @return True on success, false on failure. + * @error Invalid Handle or menu position. + */ +native bool:InsertMenuItem(Handle:menu, + position, + const String:info[], + const String:display[], + style=ITEMDRAW_DEFAULT); + +/** + * Removes an item from the menu. + * + * @param menu Menu Handle. + * @param position Position, starting from 0. + * @return True on success, false on failure. + * @error Invalid Handle or menu position. + */ +native bool:RemoveMenuItem(Handle:menu, position); + +/** + * Removes all items from a menu. + * + * @param menu Menu Handle. + * @noreturn + * @error Invalid Handle or menu position. + */ +native RemoveAllMenuItems(Handle:menu); + +/** + * Retrieves information about a menu item. + * + * @param menu Menu Handle. + * @param position Position, starting from 0. + * @param infoBuf Info buffer. + * @param infoBufLen Maximum length of the info buffer. + * @param style By-reference variable to store drawing flags. + * @param dispBuf Display buffer. + * @param dispBufLen Maximum length of the display buffer. + * @return True on success, false if position is invalid. + * @error Invalid Handle. + */ +native bool:GetMenuItem(Handle:menu, + position, + String:infoBuf[], + infoBufLen, + &style=0, + String:dispBuf[]="", + dispBufLen=0); + +/** + * Returns the first item on the page of a currently selected menu. + * + * This is only valid inside a MenuAction_Select callback. + * + * @return First item number on the page the client was viewing + * before selecting the item in the callback. This can + * be used to re-display the menu from the original + * position. + * @error Not called from inside a MenuAction_Select callback. + */ +native GetMenuSelectionPosition(); + +/** + * Returns the number of items in a menu. + * + * @param menu Menu Handle. + * @return Number of items in the menu. + * @error Invalid Handle. + */ +native GetMenuItemCount(Handle:menu); + +/** + * Sets whether the menu should be paginated or not. + * + * If itemsPerPage is MENU_NO_PAGINATION, and the exit button flag is set, + * then the exit button flag is removed. It can be re-applied if desired. + * + * @param menu Handle to the menu. + * @param itemsPerPage Number of items per page, or MENU_NO_PAGINATION. + * @return True on success, false if pagination is too high or + * low. + * @error Invalid Handle. + */ +native bool:SetMenuPagination(Handle:menu, itemsPerPage); + +/** + * Returns a menu's pagination setting. + * + * @param menu Handle to the menu. + * @return Pagination setting. + * @error Invalid Handle. + */ +native GetMenuPagination(Handle:menu); + +/** + * Returns a menu's MenuStyle Handle. The Handle + * is global and cannot be freed. + * + * @param menu Handle to the menu. + * @return Handle to the menu's draw style. + * @error Invalid Handle. + */ +native Handle:GetMenuStyle(Handle:menu); + +/** + * Sets the menu's default title/instruction message. + * + * @param menu Menu Handle. + * @param fmt Message string format + * @param ... Message string arguments. + * @noreturn + * @error Invalid Handle. + */ +native SetMenuTitle(Handle:menu, const String:fmt[], any:...); + +/** + * Returns the text of a menu's title. + * + * @param menu Menu Handle. + * @param buffer Buffer to store title. + * @param maxlength Maximum length of the buffer. + * @return Number of bytes written. + * @error Invalid Handle/ + */ +native GetMenuTitle(Handle:menu, String:buffer[], maxlength); + +/** + * Creates a raw MenuPanel based off the menu's style. + * The Handle must be freed with CloseHandle(). + * + * @return A new MenuPanel Handle. + * @error Invalid Handle. + */ +native Handle:CreatePanelFromMenu(Handle:menu); + +/** + * Returns whether or not the menu has an exit button. + * By default, menus have an exit button. + * + * @param menu Menu Handle. + * @return True if the menu has an exit button; false otherwise. + * @error Invalid Handle. + */ +native bool:GetMenuExitButton(Handle:menu); + +/** + * Sets whether or not the menu has an exit button. By default, paginated menus + * have an exit button. + * + * If a menu's pagination is changed to MENU_NO_PAGINATION, and the pagination + * was previously a different value, then the Exit button status is changed to + * false. It must be explicitly re-enabled afterwards. + * + * If a non-paginated menu has an exit button, then at most 9 items will be + * displayed. + * + * @param menu Menu Handle. + * @param button True to enable the button, false to remove it. + * @return True if allowed; false on failure. + * @error Invalid Handle. + */ +native bool:SetMenuExitButton(Handle:menu, bool:button); + +/** + * Returns whether or not the menu has an "exit back" button. By default, + * menus do not have an exit back button. + * + * Exit Back buttons appear as "Back" on page 1 of paginated menus and have + * functionality defined by the user in MenuEnd_ExitBack. + * + * @param menu Menu Handle. + * @return True if the menu has an exit back button; false otherwise. + * @error Invalid Handle. + */ +native bool:GetMenuExitBackButton(Handle:menu); + +/** + * Sets whether or not the menu has an "exit back" button. By default, menus + * do not have an exit back button. + * + * Exit Back buttons appear as "Back" on page 1 of paginated menus and have + * functionality defined by the user in MenuEnd_ExitBack. + * + * @param menu Menu Handle. + * @param button True to enable the button, false to remove it. + * @error Invalid Handle. + */ +native SetMenuExitBackButton(Handle:menu, bool:button); + + +/** + * Cancels a menu from displaying on all clients. While the + * cancellation is in progress, this menu cannot be re-displayed + * to any clients. + * + * The menu may still exist on the client's screen after this command. + * This simply verifies that the menu is not being used anywhere. + * + * If any vote is in progress on a menu, it will be cancelled. + * + * @param menu Menu Handle. + * @noreturn + * @error Invalid Handle. + */ +native CancelMenu(Handle:menu); + +/** + * Retrieves a menu's option flags. + * + * @param menu Menu Handle. + * @return A bitstring of MENUFLAG bits. + * @error Invalid Handle. + */ +native GetMenuOptionFlags(Handle:menu); + +/** + * Sets a menu's option flags. + * + * If a certain bit is not supported, it will be stripped before being set. + * See SetMenuExitButton() for information on Exit buttons. + * See SetMenuExitBackButton() for information on Exit Back buttons. + * + * @param menu Menu Handle. + * @param flags A new bitstring of MENUFLAG bits. + * @noreturn + * @error Invalid Handle. + */ +native SetMenuOptionFlags(Handle:menu, flags); + +/** + * Returns whether a vote is in progress. + * + * @param menu Deprecated; no longer used. + * @return True if a vote is in progress, false otherwise. + */ +native bool:IsVoteInProgress(Handle:menu=INVALID_HANDLE); + +/** + * Cancels the vote in progress. + * + * @noreturn + * @error If no vote is in progress. + */ +native CancelVote(); + +/** + * Broadcasts a menu to a list of clients. The most selected item will be + * returned through MenuAction_End. On a tie, a random item will be returned + * from a list of the tied items. + * + * Note that MenuAction_VoteEnd and MenuAction_VoteStart are both + * default callbacks and do not need to be enabled. + * + * @param menu Menu Handle. + * @param clients Array of clients to broadcast to. + * @param numClients Number of clients in the array. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false if this menu already has a vote session + * in progress. + * @error Invalid Handle, or a vote is already in progress. + */ +native bool:VoteMenu(Handle:menu, clients[], numClients, time); + +/** + * Sends a vote menu to all clients. See VoteMenu() for more information. + * + * @param menu Menu Handle. + * @param time Maximum time to leave menu on the screen. + * @return True on success, false if this menu already has a vote session + * in progress. + * @error Invalid Handle. + */ +stock VoteMenuToAll(Handle:menu, time) +{ + new num = GetMaxClients(); + new total; + decl players[num]; + + for (new i=1; i<=num; i++) + { + if (!IsClientInGame(i)) + { + continue; + } + players[total++] = i; + } + + return VoteMenu(menu, players, total, time); +} +/** + * Callback for when a vote has ended and results are available. + * + * @param menu The menu being voted on. + * @param num_votes Number of votes tallied in total. + * @param num_clients Number of clients who could vote. + * @param client_info Array of clients. Use VOTEINFO_CLIENT_ defines. + * @param num_items Number of unique items that were selected. + * @param item_info Array of items, sorted by count. Use VOTEINFO_ITEM + * defines. + * @noreturn + */ +functag VoteHandler public(Handle:menu, + num_votes, + num_clients, + const client_info[][2], + num_items, + const item_info[][2]); + +/** + * Sets an advanced vote handling callback. If this callback is set, + * MenuAction_VoteEnd will not be called. + * + * @param menu Menu Handle. + * @param callback Callback function. + * @noreturn + * @error Invalid Handle or callback. + */ +native SetVoteResultCallback(Handle:menu, VoteHandler:callback); + +/** + * Returns the number of seconds you should "wait" before displaying + * a publicly invocable menu. This number is the time remaining until + * (last_vote + sm_vote_delay). + * + * @return Number of seconds to wait, or 0 for none. + */ +native CheckVoteDelay(); + +/** + * Returns a style's global Handle. + * + * @param style Menu Style. + * @return A Handle, or INVALID_HANDLE if not found or unusable. + */ +native Handle:GetMenuStyleHandle(MenuStyle:style); + +/** + * Creates a MenuPanel from a MenuStyle. Panels are used for drawing raw + * menus without any extra helper functions. The Handle must be closed + * with CloseHandle(). + * + * @param hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @return A new MenuPanel Handle. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native Handle:CreatePanel(Handle:hStyle=INVALID_HANDLE); + +/** + * Creates a Menu from a MenuStyle. The Handle must be closed with + * CloseHandle(). + * + * @parma hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @param handler Function which will receive menu actions. + * @param actions Optionally set which actions to receive. Select, + * Cancel, and End will always be received regardless + * of whether they are set or not. They are also + * the only default actions. + * @return A new menu Handle. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native Handle:CreateMenuEx(Handle:hStyle=INVALID_HANDLE, MenuHandler:handler, MenuAction:actions=MENU_ACTIONS_DEFAULT); + +/** + * Returns whether a client is viewing a menu. + * + * @param client Client index. + * @param hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @return A MenuSource value. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native MenuSource:GetClientMenu(client, Handle:hStyle=INVALID_HANDLE); + +/** + * Cancels a menu on a client. This will only affect non-external menus. + * + * @param hstyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @param client Client index. + * @param autoIgnore If true, no menus can be re-drawn on the client during + * the cancellation process. + * @return True if a menu was cancelled, false otherwise. + */ +native bool:CancelClientMenu(client, bool:autoIgnore=false, Handle:hStyle=INVALID_HANDLE); + +/** + * Returns a style's maximum items per page. + * + * @param hStyle MenuStyle Handle, or INVALID_HANDLE to use the default style. + * @return Maximum items per page. + * @error Invalid Handle other than INVALID_HANDLE. + */ +native GetMaxPageItems(Handle:hStyle=INVALID_HANDLE); + +/** + * Returns a MenuPanel's parent style. + * + * @param panel A MenuPanel Handle. + * @return The MenuStyle Handle that created the panel. + * @error Invalid Handle. + */ +native Handle:GetPanelStyle(Handle:panel); + +/** + * Sets the panel's title. + * + * @param panel A MenuPanel Handle. + * @param title Text to set as the title. + * @param onlyIfEmpty If true, the title will only be set if no title is set. + * @noreturn + * @error Invalid Handle. + */ +native Handle:SetPanelTitle(Handle:panel, const String:text[], bool:onlyIfEmpty=false); + +/** + * Draws an item on a panel. If the item takes up a slot, the position + * is returned. + * + * @param panel A MenuPanel Handle. + * @param text Display text to use. If not a raw line, + * the style may automatically add color markup. + * No numbering or newlines are needed. + * @param style ITEMDRAW style flags. + * @return A slot position, or 0 if item was a rawline or could not be drawn. + * @error Invalid Handle. + */ +native DrawPanelItem(Handle:panel, const String:text[], style=ITEMDRAW_DEFAULT); + +/** + * Draws a raw line of text on a panel, without any markup other than a newline. + * + * @param panel A MenuPanel Handle, or INVALID_HANDLE if inside a + * MenuAction_DisplayItem callback. + * @param text Display text to use. + * @return True on success, false if raw lines are not supported. + * @error Invalid Handle. + */ +native DrawPanelText(Handle:panel, const String:text[]); + +/** + * Returns whether or not the given drawing flags are supported by + * the menu style. + * + * @param panel A MenuPanel Handle. + * @param style ITEMDRAW style flags. + * @return True if item is drawable, false otherwise. + * @error Invalid Handle. + */ +native CanPanelDrawFlags(Handle:panel, style); + +/** + * Sets the selectable key map of a panel. This is not supported by + * all styles (only by Radio, as of this writing). + * + * @param keys An integer where each bit N allows key + * N+1 to be selected. If no keys are selectable, + * then key 0 (bit 9) is automatically set. + * @return True if supported, false otherwise. + */ +native bool:SetPanelKeys(Handle:panel, keys); + +/** + * Sends a panel to a client. Unlike full menus, the handler + * function will only receive the following actions, both of + * which will have INVALID_HANDLE for a menu, and the client + * as param1. + * + * MenuAction_Select (param2 will be the key pressed) + * MenuAction_Cancel (param2 will be the reason) + * + * Also, if the menu fails to display, no callbacks will be called. + * + * @param panel A MenuPanel Handle. + * @param client A client to draw to. + * @param handler The MenuHandler function to catch actions with. + * @param time Time to hold the menu for. + * @return True on success, false on failure. + * @error Invalid Handle. + */ +native bool:SendPanelToClient(Handle:panel, client, MenuHandler:handler, time); + +/** + * @brief Returns the amount of text the menu can still hold. If this is + * limit is reached or overflowed, the text is silently truncated. + * + * Radio menus: Currently 511 characters (512 bytes). + * Valve menus: Currently -1 (no meaning). + * + * @param panel A MenuPanel Handle. + * @return Number of characters that the menu can still hold, + * or -1 if there is no known limit. + * @error Invalid Handle. + */ +native GetPanelTextRemaining(Handle:panel); + +/** + * @brief Returns the current key position. + * + * @param panel A MenuPanel Handle. + * @return Current key position starting at 1. + * @error Invalid Handle. + */ +native GetPanelCurrentKey(Handle:panel); + +/** + * @brief Sets the next key position. This cannot be used + * to traverse backwards. + * + * @param panel A MenuPanel Handle. + * @param key Key that is greater or equal to + * GetPanelCurrentKey(). + * @return True on success, false otherwise. + * @error Invalid Handle. + */ +native bool:SetPanelCurrentKey(Handle:panel, key); + +/** + * @brief Redraws menu text from inside a MenuAction_DisplayItem callback. + * + * @param text Menu text to draw. + * @return Item position; must be returned via the callback. + */ +native RedrawMenuItem(const String:text[]); + +/** + * This function is provided for legacy code only. Some older plugins may use + * network messages instead of the panel API. This function wraps the panel + * API for eased portability into the SourceMod menu system. + * + * This function is only usable with the Radio Menu style. You do not need to + * split up your menu into multiple packets; SourceMod will break the string + * up internally. + * + * @param client Client index. + * @param str Full menu string as would be passed over the network. + * @param time Time to hold the menu for. + * @param keys Selectable key bitstring. + * @param handler Optional handler function, with the same rules as + * SendPanelToClient(). + * @return True on success, false on failure. + * @error Invalid client index, or radio menus not supported. + */ +native bool:InternalShowMenu(client, const String:str[], time, keys=-1, MenuHandler:handler=MenuHandler:-1); + +/** + * Retrieves voting information from MenuAction_VoteEnd. + * + * @param param2 Second parameter of MenuAction_VoteEnd. + * @param winningVotes Number of votes received by the winning option. + * @param totalVotes Number of total votes received. + * @noreturn + */ +stock GetMenuVoteInfo(param2, &winningVotes, &totalVotes) +{ + winningVotes = param2 & 0xFFFF; + totalVotes = param2 >> 16; +} + +/** + * Quick stock to determine whether voting is allowed. This doesn't let you + * fine-tune a reason for not voting, so it's not recommended for lazily + * telling clients that voting isn't allowed. + * + * @return True if voting is allowed, false if voting is in progress + * or the cooldown is active. + */ +stock bool:IsNewVoteAllowed() +{ + if (IsVoteInProgress() || CheckVoteDelay() != 0) + { + return false; + } + + return true; +} diff --git a/src/include/profiler.inc b/src/include/profiler.inc new file mode 100644 index 0000000..a6f6c9e --- /dev/null +++ b/src/include/profiler.inc @@ -0,0 +1,77 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: profiler.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _profiler_included + #endinput +#endif +#define _profiler_included + +/** + * ONLY AVAILABLE ON WINDOWS RIGHT NOW K. + */ + +/** + * Creates a new profile object. The Handle must be freed + * using CloseHandle(). + * + * @return Handle to the profiler object. + */ +native Handle:CreateProfiler(); + +/** + * Starts profiling. + * + * @param prof Profiling object. + * @noreturn + * @error Invalid Handle. + */ +native StartProfiling(Handle:prof); + +/** + * Stops profiling. + * + * @param prof Profiling object. + * @noreturn + * @error Invalid Handle or profiling was never started. + */ +native StopProfiling(Handle:prof); + +/** + * Returns the amount of high-precision time in seconds + * that passed during the profiler's last start/stop + * cycle. + * + * @param prof Profiling object. + * @return Time elapsed in seconds. + * @error Invalid Handle. + */ +native Float:GetProfilerTime(Handle:prof); diff --git a/src/include/regex.inc b/src/include/regex.inc new file mode 100644 index 0000000..53ed2bd --- /dev/null +++ b/src/include/regex.inc @@ -0,0 +1,173 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: regex.inc 2086 2008-04-19 10:34:56Z pred $ + */ + +#if defined _regex_included + #endinput +#endif +#define _regex_included + +/** + * @section Flags for compiling regex expressions. These come directly from the + * pcre library and can be used in MatchRegex and CompileRegex. + */ +#define PCRE_CASELESS 0x00000001 /* Ignore Case */ +#define PCRE_MULTILINE 0x00000002 /* Multilines (affects ^ and $ so that they match the start/end of a line rather than matching the start/end of the string). */ +#define PCRE_DOTALL 0x00000004 /* Single line (affects . so that it matches any character, even new line characters). */ +#define PCRE_EXTENDED 0x00000008 /* Pattern extension (ignore whitespace and # comments). */ +#define PCRE_UNGREEDY 0x00000200 /* Invert greediness of quantifiers */ +#define PCRE_UTF8 0x00000800 /* Use UTF-8 Chars */ +#define PCRE_NO_UTF8_CHECK 0x00002000 /* Do not check the pattern for UTF-8 validity (only relevant if PCRE_UTF8 is set) */ + + +/** + * Regex expression error codes. + */ +enum RegexError +{ + REGEX_ERROR_NONE = 0, /* No error */ + REGEX_ERROR_NOMATCH = -1, /* No match was found */ + REGEX_ERROR_NULL = -2, + REGEX_ERROR_BADOPTION = -3, + REGEX_ERROR_BADMAGIC = -4, + REGEX_ERROR_UNKNOWN_OPCODE = -5, + REGEX_ERROR_NOMEMORY = -6, + REGEX_ERROR_NOSUBSTRING = -7, + REGEX_ERROR_MATCHLIMIT = -8, + REGEX_ERROR_CALLOUT = -9, /* Never used by PCRE itself */ + REGEX_ERROR_BADUTF8 = -10, + REGEX_ERROR_BADUTF8_OFFSET = -11, + REGEX_ERROR_PARTIAL = -12, + REGEX_ERROR_BADPARTIAL = -13, + REGEX_ERROR_INTERNAL = -14, + REGEX_ERROR_BADCOUNT = -15, + REGEX_ERROR_DFA_UITEM = -16, + REGEX_ERROR_DFA_UCOND = -17, + REGEX_ERROR_DFA_UMLIMIT = -18, + REGEX_ERROR_DFA_WSSIZE = -19, + REGEX_ERROR_DFA_RECURSE = -20, + REGEX_ERROR_RECURSIONLIMIT = -21, + REGEX_ERROR_NULLWSLIMIT = -22, /* No longer actually used */ + REGEX_ERROR_BADNEWLINE = -23 +}; + +/** + * Precompile a regular expression. Use this if you intend on using the + * same expression multiple times. Pass the regex handle returned here to + * MatchRegex to check for matches. + * + * @param pattern The regular expression pattern. + * @param flags General flags for the regular expression. + * @param error Error message encountered, if applicable. + * @param maxLen Maximum string length of the error buffer. + * @param errcode Regex type error code encountered, if applicable. + * @return Valid regex handle on success, INVALID_HANDLE on failure. + */ +native Handle:CompileRegex(const String:pattern[], flags = 0, String:error[]="", maxLen = 0, &RegexError:errcode = REGEX_ERROR_NONE); + +/** + * Matches a string against a pre-compiled regular expression pattern. + * + * @param str The string to check. + * @param regex Regex Handle from CompileRegex() + * @param ret Error code, if applicable. + * @return Number of substrings found or -1 on failure. + * + * @note Use the regex handle passed to this function to extract + * matches with GetRegexSubString(). + */ +native MatchRegex(Handle:regex, const String:str[], &RegexError:ret = REGEX_ERROR_NONE); + +/** + * Returns a matched substring from a regex handle. + * Substring ids start at 0 and end at substrings-1, where substrings is the number returned + * by MatchRegex + * + * @param regex The regex handle to extract data from. + * @param str_id The index of the expression to get - starts at 0, and ends at substrings - 1. + * @param buffer The buffer to set to the matching substring. + * @param maxLen The maximum string length of the buffer. + * @return True if a substring was found, False on fail/error + */ +native bool:GetRegexSubString(Handle:regex, str_id, String:buffer[], maxlen); + +/** + * Matches a string against a regular expression pattern. + * + * @note If you intend on using the same regular expression pattern + * multiple times, consider using CompileRegex and MatchRegex + * instead of making this function reparse the expression each time. + * + * @param str The string to check. + * @param pattern The regular expression pattern. + * @param flags General flags for the regular expression. + * @param error Error message, if applicable. + * @param maxLen Maximum length of the error buffer. + * @return Number of substrings found or -1 on failure. + */ +stock SimpleRegexMatch(const String:str[], const String:pattern[], flags = 0, String:error[]="", maxLen = 0) +{ + new Handle:regex = CompileRegex(pattern, flags, error, maxLen); + + if (regex == INVALID_HANDLE) + { + return -1; + } + + new substrings = MatchRegex(regex, str); + + CloseHandle(regex); + + return substrings; +} + +/** + * @endsection + */ + +/** + * Do not edit below this line! + */ +public Extension:__ext_regex = +{ + name = "Regex Extension", + file = "regex.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; diff --git a/src/include/sdktools.inc b/src/include/sdktools.inc new file mode 100644 index 0000000..176721d --- /dev/null +++ b/src/include/sdktools.inc @@ -0,0 +1,212 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools.inc 1990 2008-04-06 05:40:11Z dvander $ + */ + +#if defined _sdktools_included + #endinput +#endif +#define _sdktools_included + +#include +#include +#include +#if !defined SDKTOOLS_DISABLE_SOUNDAPI +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +enum SDKCallType +{ + SDKCall_Static, /**< Static call */ + SDKCall_Entity, /**< CBaseEntity call */ + SDKCall_Player, /**< CBasePlayer call */ + SDKCall_GameRules, /**< CGameRules call */ + SDKCall_EntityList, /**< CGlobalEntityList call */ +}; + +enum SDKLibrary +{ + SDKLibrary_Server, /**< server.dll/server_i486.so */ + SDKLibrary_Engine, /**< engine.dll/engine_*.so */ +}; + +enum SDKFuncConfSource +{ + SDKConf_Virtual = 0, /**< Read a virtual index from the Offsets section */ + SDKConf_Signature = 1, /**< Read a signature from the Signatures section */ +}; + +enum SDKType +{ + SDKType_CBaseEntity, /**< CBaseEntity (always as pointer) */ + SDKType_CBasePlayer, /**< CBasePlayer (always as pointer) */ + SDKType_Vector, /**< Vector (pointer, byval, or byref) */ + SDKType_QAngle, /**< QAngles (pointer, byval, or byref) */ + SDKType_PlainOldData, /**< Integer/generic data <=32bit (any) */ + SDKType_Float, /**< Float (any) */ + SDKType_Edict, /**< edict_t (always as pointer) */ + SDKType_String, /**< NULL-terminated string (always as pointer) */ + SDKType_Bool, /**< Boolean (any) */ +}; + +enum SDKPassMethod +{ + SDKPass_Pointer, /**< Pass as a pointer */ + SDKPass_Plain, /**< Pass as plain data */ + SDKPass_ByValue, /**< Pass an object by value */ + SDKPass_ByRef, /**< Pass an object by reference */ +}; + +#define VDECODE_FLAG_ALLOWNULL (1<<0) /**< Allow NULL for pointers */ +#define VDECODE_FLAG_ALLOWNOTINGAME (1<<1) /**< Allow players not in game */ +#define VDECODE_FLAG_ALLOWWORLD (1<<2) /**< Allow World entity */ +#define VDECODE_FLAG_BYREF (1<<3) /**< Floats/ints by reference */ + +#define VENCODE_FLAG_COPYBACK (1<<0) /**< Copy back data once done */ + +/** + * Starts the preparation of an SDK call. + * + * @param type Type of function call this will be. + * @noreturn + */ +native StartPrepSDKCall(SDKCallType:type); + +/** + * Sets the virtual index of the SDK call if it is virtual. + * + * @param vtblidx Virtual table index. + * @noreturn + */ +native PrepSDKCall_SetVirtual(vtblidx); + +/** + * Finds an address in a library and sets it as the address to use for the SDK call. + * + * @param lib Library to use. + * @param signature Binary data to search for in the library. If it starts with '@', + * the bytes parameter is ignored and the signature is interpreted + * as a symbol lookup in the library. + * @param bytes Number of bytes in the binary search string. + * @return True on success, false if nothing was found. + */ +native bool:PrepSDKCall_SetSignature(SDKLibrary:lib, const String:signature[], bytes); + +/** + * Finds an address or virtual function index in a GameConfig file and sets it as + * the calling information for the SDK call. + * + * @param gameconf GameConfig Handle, or INVALID_HANDLE to use sdktools.games.txt. + * @param source Whether to look in Offsets or Signatures. + * @param name Name of the property to find. + * @return True on success, false if nothing was found. + */ +native bool:PrepSDKCall_SetFromConf(Handle:gameconf, SDKFuncConfSource:source, const String:name[]); + +/** + * Sets the return information of an SDK call. Do not call this if there is no return data. + * This must be called if there is a return value (i.e. it is not necessarily safe to ignore + * the data). + * + * @param type Data type to convert to/from. + * @param pass How the data is passed in C++. + * @param decflags Flags on decoding from the plugin to C++. + * @param encflags Flags on encoding from C++ to the plugin. + * @noreturn + */ +native PrepSDKCall_SetReturnInfo(SDKType:type, SDKPassMethod:pass, decflags=0, encflags=0); + +/** + * Adds a parameter to the calling convention. This should be called in normal ascending order. + * + * @param type Data type to convert to/from. + * @param pass How the data is passed in C++. + * @param decflags Flags on decoding from the plugin to C++. + * @param encflags Flags on encoding from C++ to the plugin. + * @noreturn + */ +native PrepSDKCall_AddParameter(SDKType:type, SDKPassMethod:pass, decflags=0, encflags=0); + +/** + * Finalizes an SDK call preparation and returns the resultant Handle. + * + * @return A new SDKCall Handle on success, or INVALID_HANDLE on failure. + */ +native Handle:EndPrepSDKCall(); + +/** + * Calls an SDK function with the given parameters. + * + * If the call type is Entity or Player, the index MUST ALWAYS be the FIRST parameter passed. + * If the call type is GameRules, then nothing special needs to be passed. + * If the return value is a Vector or QAngles, the SECOND parameter must be a Float[3]. + * If the return value is a string, the THIRD parameter must be a String buffer, and the + * FOURTH parameter must be the maximum length. + * All parameters must be passed after the above is followed. Failure to follow these + * rules will result in crashes or wildly unexpected behavior! + * + * If the return value is a float or integer, the return value will be this value. + * If the return value is a CBaseEntity, CBasePlayer, or edict, the return value will + * always be the entity index, or -1 for NULL. + * + * @param call SDKCall Handle. + * @param ... Call Parameters. + * @return Simple return value, if any. + * @error Invalid Handle or internal decoding error. + */ +native any:SDKCall(Handle:call, any:...); + +#include + +/** + * Do not edit below this line! + */ +public Extension:__ext_sdktools = +{ + name = "SDKTools", + file = "sdktools.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; diff --git a/src/include/sdktools_engine.inc b/src/include/sdktools_engine.inc new file mode 100644 index 0000000..1c44dae --- /dev/null +++ b/src/include/sdktools_engine.inc @@ -0,0 +1,69 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_engine.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _sdktools_engine_included + #endinput +#endif +#define _sdktools_engine_included + +#define MAX_LIGHTSTYLES 64 + +/** + * Sets a client's "viewing entity." + * + * @param client Client index. + * @param entity Entity index. + * @noreturn + * @error Invalid client or entity, lack of mod support, or client not in + * game. + */ +native SetClientViewEntity(client, entity); + +/** + * Sets a light style. + * + * @param style Light style (from 0 to MAX_LIGHTSTYLES-1) + * @param value Light value string (see world.cpp/light.cpp in dlls) + * @noreturn + * @error Light style index is out of range. + */ +native SetLightStyle(style, const String:value[]); + +/** + * Returns the client's eye position. + * + * @param client Player's index. + * @param pos Destination vector to store the client's eye position. + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native GetClientEyePosition(client, Float:pos[3]); diff --git a/src/include/sdktools_entinput.inc b/src/include/sdktools_entinput.inc new file mode 100644 index 0000000..ad66d10 --- /dev/null +++ b/src/include/sdktools_entinput.inc @@ -0,0 +1,116 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_entinput.inc 1504 2007-09-28 19:56:19Z faluco $ + */ + +#if defined _sdktools_entinput_included + #endinput +#endif +#define _sdktools_entinput_included + +/** + * Invokes a named input method on an entity. + * + * After completion (successful or not), the current global variant is re-initalized. + * + * @param dest Destination entity index. + * @param input Input action. + * @param activator Entity index which initiated the sequence of actions (-1 for a NULL entity). + * @param caller Entity index from which this event is sent (-1 for a NULL entity). + * @param outputid Unknown. + * @return True if successful otherwise false. + * @error Invalid entity index or no mod support. + */ +native bool:AcceptEntityInput(dest, const String:input[], activator=-1, caller=-1, outputid=0); + +/** + * Sets a bool value in the global variant object. + * + * @param val Input value. + * @noreturn + */ +native SetVariantBool(bool:val); + +/** + * Sets a string in the global variant object. + * + * @param str Input string. + * @noreturn + */ +native SetVariantString(const String:str[]); + +/** + * Sets an integer value in the global variant object. + * + * @param val Input value. + * @noreturn + */ +native SetVariantInt(val); + +/** + * Sets a floating point value in the global variant object. + * + * @param val Input value. + * @noreturn + */ +native SetVariantFloat(Float:val); + +/** + * Sets a 3D vector in the global variant object. + * + * @param vec Input vector. + * @noreturn + */ +native SetVariantVector3D(const Float:vec[3]); + +/** + * Sets a 3D position vector in the global variant object. + * + * @param vec Input position vector. + * @noreturn + */ +native SetVariantPosVector3D(const Float:vec[3]); + +/** + * Sets a color in the global variant object. + * + * @param color Input color. + * @noreturn + */ +native SetVariantColor(const color[4]); + +/** + * Sets an entity in the global variant object. + * + * @param Entity index. + * @noreturn + * @error Invalid entity index. + */ +native SetVariantEntity(entity); diff --git a/src/include/sdktools_entoutput.inc b/src/include/sdktools_entoutput.inc new file mode 100644 index 0000000..350ae7f --- /dev/null +++ b/src/include/sdktools_entoutput.inc @@ -0,0 +1,91 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_entoutput.inc 1990 2008-04-06 05:40:11Z dvander $ + */ + +#if defined _sdktools_entoutput_included + #endinput +#endif +#define _sdktools_entoutput_included + +/** + * Called when an entity output is fired. + * + * @param output Name of the output that fired. + * @param caller Entity index of the caller. + * @param activator Entity index of the activator. + * @param delay Delay in seconds? before the event gets fired. + */ +functag EntityOutput public(const String:output[], caller, activator, Float:delay); + +/** + * Add an entity output hook on a entity classname + * + * @param classname The classname to hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @noreturn + * @error Entity Outputs disabled. + */ +native HookEntityOutput(const String:classname[], const String:output[], EntityOutput:callback); + +/** + * Remove an entity output hook. + * @param classname The classname to hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @return True on success, false if no valid hook was found. + * @error Entity Outputs disabled. + */ +native bool:UnhookEntityOutput(const String:classname[], const String:output[], EntityOutput:callback); + +/** + * Add an entity output hook on a single entity instance + * + * @param entity The entity on which to add a hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @param once Only fire this hook once and then remove itself. + * @noreturn + * @error Entity Outputs disabled or Invalid Entity index. + */ +native HookSingleEntityOutput(entity, const String:output[], EntityOutput:callback , bool:once=false); + +/** + * Remove a single entity output hook. + * + * @param entity The entity on which to remove the hook. + * @param output The output name to hook. + * @param callback An EntityOutput function pointer. + * @return True on success, false if no valid hook was found. + * @error Entity Outputs disabled or Invalid Entity index. + */ +native bool:UnhookSingleEntityOutput(entity, const String:output[], EntityOutput:callback); + diff --git a/src/include/sdktools_functions.inc b/src/include/sdktools_functions.inc new file mode 100644 index 0000000..ccb65bb --- /dev/null +++ b/src/include/sdktools_functions.inc @@ -0,0 +1,310 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_functions.inc 2069 2008-04-16 23:29:37Z dvander $ + */ + +#if defined _sdktools_functions_included + #endinput +#endif +#define _sdktools_functions_included + +/** + * Removes a player's item. + * + * @param client Client index. + * @param item CBaseCombatWeapon entity index. + * @return True on success, false otherwise. + * @error Invalid client or entity, lack of mod support, or client not in + * game. + */ +native bool:RemovePlayerItem(client, item); + +/** + * Gives a named item to a player. + * + * @param client Client index. + * @param item Item classname (such as weapon_ak47). + * @param iSubType Unknown. + * @return Entity index on success, or -1 on failure. + * @error Invalid client or client not in game, or lack of mod support. + */ +native GivePlayerItem(client, const String:item[], iSubType=0); + +/** + * Returns the weapon in a player's slot. + * + * @param client Client index. + * @param slot Slot index (mod specific). + * @return Entity index on success, -1 if no weapon existed. + * @error Invalid client or client not in game, or lack of mod support. + */ +native GetPlayerWeaponSlot(client, slot); + +/** + * Ignites an entity on fire. + * + * @param entity Entity index. + * @param time Number of seconds to set on fire. + * @param npc True to only affect NPCs. + * @param size Unknown. + * @param level Unknown. + * @noreturn + * @error Invalid entity or client not in game, or lack of mod support. + */ +native IgniteEntity(entity, Float:time, bool:npc=false, Float:size=0.0, bool:level=false); + +/** + * Extinguishes a player that is on fire. + * + * @param entity Entity index. + * @noreturn + * @error Invalid entity or client not in game, or lack of mod support. + */ +native ExtinguishEntity(client); + +/** + * Teleports an entity. + * + * @param entity Client index. + * @param origin New origin, or NULL_VECTOR for no change. + * @param angles New angles, or NULL_VECTOR for no change. + * @param velocity New velocity, or NULL_VECTOR for no change. + * @noreturn + * @error Invalid entity or client not in game, or lack of mod support. + */ +native TeleportEntity(entity, const Float:origin[3], const Float:angles[3], const Float:velocity[3]); + +/** + * Forces a player to commit suicide. + * + * @param client Client index. + * @noreturn + * @error Invalid client or client not in game, or lack of mod support. + */ +native ForcePlayerSuicide(client); + +/** + * Slaps a player in a random direction. + * + * @param client Client index. + * @param health Health to subtract. + * @param sound False to disable the sound effects. + * @noreturn + * @error Invalid client or client not in game, or lack of mod support. + */ +native SlapPlayer(client, health=5, bool:sound=true); + +/** + * Searches for an entity by classname. + * + * @param startEnt The entity index after which to begin searching from. + * Use -1 to start from the first entity. + * @param classname Classname of the entity to find. + * @return Entity index >= 0 if found, -1 otherwise. + * @error Lack of mod support. + */ +native FindEntityByClassname(startEnt, const String:classname[]); + +/** + * Returns the client's eye angles. + * + * @param client Player's index. + * @param ang Destination vector to store the client's eye angles. + * @return True on success, false on failure. + * @error Invalid client index, client not in game, or no mod support. + */ +native bool:GetClientEyeAngles(client, Float:ang[3]); + +/** + * Creates an entity by string name, but does not spawn it (see DispatchSpawn). + * If ForceEdictIndex is not -1, then it will use the edict by that index. If the index is + * invalid or there is already an edict using that index, it will error out. + * + * @param classname Entity classname. + * @param ForceEdictIndex Edict index used by the created entity. + * @return Entity index on success, or -1 on failure. + * @error Invalid edict index, or no mod support. + */ +native CreateEntityByName(const String:classname[], ForceEdictIndex=-1); + +/** + * Spawns an entity into the game. + * + * @param entity Entity index of the created entity. + * @return True on success, false otherwise. + * @error Invalid entity index, or no mod support. + */ +native bool:DispatchSpawn(entity); + +/** + * Dispatches a KeyValue into given entity using a string value. + * + * @param entity Destination entity index. + * @param keyName Name of the key. + * @param value String value. + * @return True on success, false otherwise. + * @error Invalid entity index, or no mod support. + */ +native bool:DispatchKeyValue(entity, const String:keyName[], const String:value[]); + +/** + * Dispatches a KeyValue into given entity using a floating point value. + * + * @param entity Destination entity index. + * @param keyName Name of the key. + * @param value Floating point value. + * @return True on success, false otherwise. + * @error Invalid entity index, or no mod support. + */ +native bool:DispatchKeyValueFloat(entity, const String:keyName[], Float:value); + +/** + * Dispatches a KeyValue into given entity using a vector value. + * + * @param entity Destination entity index. + * @param keyName Name of the key. + * @param vec Vector value. + * @return True on success, false otherwise. + * @error Invalid entity index, or no mod support. + */ +native bool:DispatchKeyValueVector(entity, const String:keyName[], const Float:vector[3]); + +/** + * Returns the entity a client is aiming at. + * + * @param client Client performing the aiming. + * @param only_clients True to exclude all entities but clients. + * @return Entity index being aimed at. + * -1 if no entity is being aimed at. + * -2 if the function is not supported. + * @error Invalid client index or client not in game. + */ +native GetClientAimTarget(client, bool:only_clients=true); + +/** + * Returns the total number of teams in a game. + * Note: This native should not be called before OnMapStart. + * + * @return Total number of teams. + */ +native GetTeamCount(); + +/** + * Retrieves the team name based on a team index. + * Note: This native should not be called before OnMapStart. + * + * @param index Team index. + * @param name Buffer to store string in. + * @param maxlength Maximum length of string buffer. + * @noreturn + * @error Invalid team index. + */ +native GetTeamName(index, String:name[], maxlength); + +/** + * Returns the score of a team based on a team index. + * Note: This native should not be called before OnMapStart. + * + * @param index Team index. + * @return Score. + * @error Invalid team index. + */ +native GetTeamScore(index); + +/** + * Sets the score of a team based on a team index. + * Note: This native should not be called before OnMapStart. + * + * @param index Team index. + * @param value New score value. + * @return Score. + * @error Invalid team index. + */ +native SetTeamScore(index, value); + +/** + * Retrieves the number of players in a certain team. + * Note: This native should not be called before OnMapStart. + * + * @param index Team index. + * @return Number of players in the team. + * @error Invalid team index. + */ +native GetTeamClientCount(index); + +/** + * Sets the model to a given entity. + * + * @param entity Entity index. + * @param model Model name. + * @noreturn + * @error Invalid entity index, or no mod support. + */ +native SetEntityModel(entity, const String:model[]); + +/** + * Retrieves the decal file name associated to a given client. + * + * @param client Player's index. + * @param hex Buffer to store the logo filename. + * @param maxlength Maximum length of string buffer. + * @return True on success, otherwise false. + * @error Invalid client or client not in game. + */ +native bool:GetPlayerDecalFile(client, String:hex[], maxlength); + +/** + * Returns the average server network traffic in bytes/sec. + * + * @param in Buffer to store the input traffic velocity. + * @param out Buffer to store the output traffic velocity. + * @noreturn + */ +native GetServerNetStats(&Float:in, &Float:out); + +/** + * Equip's a player's weapon. + * + * @param client Client index. + * @param item CBaseCombatWeapon entity index. + * @noreturn + * @error Invalid client or entity, lack of mod support, or client not in + * game. + */ +native EquipPlayerWeapon(client, weapon); + +/** + * Activates an entity (CBaseAnimating::Activate) + * + * @param entity Entity index. + * @noreturn + * @error Invalid entity or lack of mod support. + */ +native ActivateEntity(entity); diff --git a/src/include/sdktools_sound.inc b/src/include/sdktools_sound.inc new file mode 100644 index 0000000..5688325 --- /dev/null +++ b/src/include/sdktools_sound.inc @@ -0,0 +1,442 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_sound.inc 1934 2008-03-12 05:21:56Z dvander $ + */ + +#if defined _sdktools_sound_included + #endinput +#endif +#define _sdktools_sound_included + +/** + * Sound should be from the target client. + */ +#define SOUND_FROM_PLAYER -2 + +/** + * Sound should be from the listen server player. + */ +#define SOUND_FROM_LOCAL_PLAYER -1 + +/** + * Sound is from the world. + */ +#define SOUND_FROM_WORLD 0 + +/** + * Sound channels. + */ +enum +{ + SNDCHAN_REPLACE = -1, /**< Unknown */ + SNDCHAN_AUTO = 0, /**< Auto */ + SNDCHAN_WEAPON = 1, /**< Weapons */ + SNDCHAN_VOICE = 2, /**< Voices */ + SNDCHAN_ITEM = 3, /**< Items */ + SNDCHAN_BODY = 4, /**< Player? */ + SNDCHAN_STREAM = 5, /**< "Stream channel from the static or dynamic area" */ + SNDCHAN_STATIC = 6, /**< "Stream channel from the static area" */ + SNDCHAN_VOICE_BASE = 7, /**< "Channel for network voice data" */ + SNDCHAN_USER_BASE = 135 /**< Anything >= this is allocated to game code */ +}; + +/** + * Sound flags for the sound emitter system. + */ +enum +{ + SND_NOFLAGS= 0, /**< Nothing */ + SND_CHANGEVOL = 1, /**< Change sound volume */ + SND_CHANGEPITCH = 2, /**< Change sound pitch */ + SND_STOP = 3, /**< Stop the sound */ + SND_SPAWNING = 4, /**< Used in some cases for ambients */ + SND_DELAY = 5, /**< Sound has an initial delay */ + SND_STOPLOOPING = 6, /**< Stop looping all sounds on the entity */ + SND_SPEAKER = 7, /**< Being played by a mic through a speaker */ + SND_SHOULDPAUSE = 8, /**< Pause if game is paused */ +}; + +/** + * Various predefined sound levels in dB. + */ +enum +{ + SNDLEVEL_NONE = 0, /**< None */ + SNDLEVEL_RUSTLE = 20, /**< Rustling leaves */ + SNDLEVEL_WHISPER = 25, /**< Whispering */ + SNDLEVEL_LIBRARY = 30, /**< In a library */ + SNDLEVEL_FRIDGE = 45, /**< Refridgerator */ + SNDLEVEL_HOME = 50, /**< Average home (3.9 attn) */ + SNDLEVEL_CONVO = 60, /**< Normal conversation (2.0 attn) */ + SNDLEVEL_DRYER = 60, /**< Clothes dryer */ + SNDLEVEL_DISHWASHER = 65, /**< Dishwasher/washing machine (1.5 attn) */ + SNDLEVEL_CAR = 70, /**< Car or vacuum cleaner (1.0 attn) */ + SNDLEVEL_NORMAL = 75, /**< Normal sound level */ + SNDLEVEL_TRAFFIC = 75, /**< Busy traffic (0.8 attn) */ + SNDLEVEL_MINIBIKE = 80, /**< Mini-bike, alarm clock (0.7 attn) */ + SNDLEVEL_SCREAMING = 90, /**< Screaming child (0.5 attn) */ + SNDLEVEL_TRAIN = 100, /**< Subway train, pneumatic drill (0.4 attn) */ + SNDLEVEL_HELICOPTER = 105, /**< Helicopter */ + SNDLEVEL_SNOWMOBILE = 110, /**< Snow mobile */ + SNDLEVEL_AIRCRAFT = 120, /**< Auto horn, aircraft */ + SNDLEVEL_RAIDSIREN = 130, /**< Air raid siren */ + SNDLEVEL_GUNFIRE = 140, /**< Gunshot, jet engine (0.27 attn) */ + SNDLEVEL_ROCKET = 180, /**< Rocket launching (0.2 attn) */ +}; + +#define SNDVOL_NORMAL 1.0 /**< Normal volume */ +#define SNDPITCH_NORMAL 100 /**< Normal pitch */ +#define SNDPITCH_LOW 95 /**< A low pitch */ +#define SNDPITCH_HIGH 120 /**< A high pitch */ +#define SNDATTN_NONE 0.0 /**< No attenuation */ +#define SNDATTN_NORMAL 0.8 /**< Normal attenuation */ +#define SNDATTN_STATIC 1.25 /**< Static attenuation? */ +#define SNDATTN_RICOCHET 1.5 /**< Ricochet effect */ +#define SNDATTN_IDLE 2.0 /**< Idle attenuation? */ + +/** + * Prefetches a sound. + * + * @param name Sound file name relative to the "sounds" folder. + * @noreturn + */ +native PrefetchSound(const String:nane[]); + +/** + * This function is not known to work, and may crash. You should + * not use it. It is provided for backwards compatibility only. + * + * @param name Sound file name relative to the "sounds" folder. + * @return Duration in seconds. + */ +#pragma deprecated Does not work, may crash. +native Float:GetSoundDuration(const String:name[]); + +/** + * Emits an ambient sound. + * + * @param name Sound file name relative to the "sounds" folder. + * @param pos Origin of sound. + * @param entity Entity index to associate sound with. + * @param level Sound level (from 0 to 255). + * @param flags Sound flags. + * @param vol Volume (from 0.0 to 1.0). + * @param pitch Pitch (from 0 to 255). + * @param delay Play delay. + * @noreturn + */ +native EmitAmbientSound(const String:name[], + const Float:pos[3], + entity = SOUND_FROM_WORLD, + level = SNDLEVEL_NORMAL, + flags = SND_NOFLAGS, + Float:vol = SNDVOL_NORMAL, + pitch = SNDPITCH_NORMAL, + Float:delay = 0.0); + +/** + * Fades a client's volume level toward silence or a given percentage. + * + * @param client Client index. + * @param percent Fade percentage. + * @param outtime Fade out time, in seconds. + * @param holdtime Hold time, in seconds. + * @param intime Fade in time, in seconds. + * @noreturn + * @error Invalid client index or client not in game. + */ +native FadeClientVolume(client, Float:percent, Float:outtime, Float:holdtime, Float:intime); + +/** + * Stops a sound. + * + * @param entity Entity index. + * @param channel Channel number. + * @param name Sound file name relative to the "sounds" folder. + * @noreturn + */ +native StopSound(entity, channel, const String:name[]); + +/** + * Emits a sound to a list of clients. + * + * @param clients Array of client indexes. + * @param numClients Number of clients in the array. + * @param sample Sound file name relative to the "sounds" folder. + * @param entity Entity to emit from. + * @param channel Channel to emit with. + * @param level Sound level. + * @param flags Sound flags. + * @param volume Sound volume. + * @param pitch Sound pitch. + * @param speakerentity Unknown. + * @param origin Sound origin. + * @param dir Sound direction. + * @param updatePos Unknown (updates positions?) + * @param soundtime Alternate time to play sound for. + * @param ... Optional list of Float[3] arrays to specify additional origins. + * @noreturn + * @error Invalid client index. + */ +native EmitSound(const clients[], + numClients, + const String:sample[], + entity = SOUND_FROM_PLAYER, + channel = SNDCHAN_AUTO, + level = SNDLEVEL_NORMAL, + flags = SND_NOFLAGS, + Float:volume = SNDVOL_NORMAL, + pitch = SNDPITCH_NORMAL, + speakerentity = -1, + const Float:origin[3] = NULL_VECTOR, + const Float:dir[3] = NULL_VECTOR, + bool:updatePos = true, + Float:soundtime = 0.0, + any:...); + +/** + * Emits a sentence to a list of clients. + * + * @param clients Array of client indexes. + * @param numClients Number of clients in the array. + * @param sentence Sentence index (from PrecacheSenteFile). + * @param entity Entity to emit from. + * @param channel Channel to emit with. + * @param level Sound level. + * @param flags Sound flags. + * @param volume Sound volume. + * @param pitch Sound pitch. + * @param speakerentity Unknown. + * @param origin Sound origin. + * @param dir Sound direction. + * @param updatePos Unknown (updates positions?) + * @param soundtime Alternate time to play sound for. + * @param ... Optional list of Float[3] arrays to specify additional origins. + * @noreturn + * @error Invalid client index. + */ +native EmitSentence(const clients[], + numClients, + sentence, + entity, + channel = SNDCHAN_AUTO, + level = SNDLEVEL_NORMAL, + flags = SND_NOFLAGS, + Float:volume = SNDVOL_NORMAL, + pitch = SNDPITCH_NORMAL, + speakerentity = -1, + const Float:origin[3] = NULL_VECTOR, + const Float:dir[3] = NULL_VECTOR, + bool:updatePos = true, + Float:soundtime = 0.0, + any:...); + +/** + * Called when an ambient sound is about to be emitted to one or more clients. + * + * NOTICE: all parameters can be overwritten to modify the default behavior. + * + * @param sample Sound file name relative to the "sounds" folder. + * @param entity Entity index associated to the sound. + * @param volume Volume (from 0.0 to 1.0). + * @param level Sound level (from 0 to 255). + * @param pitch Pitch (from 0 to 255). + * @param pos Origin of sound. + * @param flags Sound flags. + * @param delay Play delay. + * @return Plugin_Continue to allow the sound to be played, Plugin_Stop to block it, + * Plugin_Changed when any parameter has been modified. + */ +functag AmbientSHook Action:public(String:sample[PLATFORM_MAX_PATH], &entity, &Float:volume, &level, &pitch, Float:pos[3], &flags, &Float:delay); + +/** + * Called when a sound is going to be emitted to one or more clients. + * NOTICE: all params can be overwritten to modify the default behaviour. + * + * @param clients Array of client indexes. + * @param numClients Number of clients in the array (modify this value if you add/remove elements from the client array). + * @param sample Sound file name relative to the "sounds" folder. + * @param entity Entity emitting the sound. + * @param channel Channel emitting the sound. + * @param volume Sound volume. + * @param level Sound level. + * @param pitch Sound pitch. + * @param flags Sound flags. + * @return Plugin_Continue to allow the sound to be played, Plugin_Stop to block it, + * Plugin_Changed when any parameter has been modified. + */ +functag NormalSHook Action:public(clients[64], &numClients, String:sample[PLATFORM_MAX_PATH], &entity, &channel, &Float:volume, &level, &pitch, &flags); + +/** + * Hooks all played ambient sounds. + * + * @param hook Function to use as a hook. + * @noreturn + * @error Invalid function hook. + */ +native AddAmbientSoundHook(AmbientSHook:hook); + +/** + * Hooks all played normal sounds. + * + * @param hook Function to use as a hook. + * @noreturn + * @error Invalid function hook. + */ +native AddNormalSoundHook(NormalSHook:hook); + +/** + * Unhooks all played ambient sounds. + * + * @param hook Function used for the hook. + * @noreturn + * @error Invalid function hook. + */ +native RemoveAmbientSoundHook(AmbientSHook:hook); + +/** + * Unhooks all played normal sounds. + * + * @param hook Function used for the hook. + * @noreturn + * @error Invalid function hook. + */ +native RemoveNormalSoundHook(NormalSHook:hook); + +/** + * Wrapper to emit sound to one client. + * + * @param client Client index. + * @param sample Sound file name relative to the "sounds" folder. + * @param entity Entity to emit from. + * @param channel Channel to emit with. + * @param level Sound level. + * @param flags Sound flags. + * @param volume Sound volume. + * @param pitch Sound pitch. + * @param speakerentity Unknown. + * @param origin Sound origin. + * @param dir Sound direction. + * @param updatePos Unknown (updates positions?) + * @param soundtime Alternate time to play sound for. + * @noreturn + * @error Invalid client index. + */ +stock EmitSoundToClient(client, + const String:sample[], + entity = SOUND_FROM_PLAYER, + channel = SNDCHAN_AUTO, + level = SNDLEVEL_NORMAL, + flags = SND_NOFLAGS, + Float:volume = SNDVOL_NORMAL, + pitch = SNDPITCH_NORMAL, + speakerentity = -1, + const Float:origin[3] = NULL_VECTOR, + const Float:dir[3] = NULL_VECTOR, + bool:updatePos = true, + Float:soundtime = 0.0) +{ + new clients[1]; + clients[0] = client; + /* Save some work for SDKTools and remove SOUND_FROM_PLAYER references */ + entity = (entity == SOUND_FROM_PLAYER) ? client : entity; + EmitSound(clients, 1, sample, entity, channel, + level, flags, volume, pitch, speakerentity, + origin, dir, updatePos, soundtime); +} + +/** + * Wrapper to emit sound to all clients. + * + * @param sample Sound file name relative to the "sounds" folder. + * @param entity Entity to emit from. + * @param channel Channel to emit with. + * @param level Sound level. + * @param flags Sound flags. + * @param volume Sound volume. + * @param pitch Sound pitch. + * @param speakerentity Unknown. + * @param origin Sound origin. + * @param dir Sound direction. + * @param updatePos Unknown (updates positions?) + * @param soundtime Alternate time to play sound for. + * @noreturn + * @error Invalid client index. + */ +stock EmitSoundToAll(const String:sample[], + entity = SOUND_FROM_PLAYER, + channel = SNDCHAN_AUTO, + level = SNDLEVEL_NORMAL, + flags = SND_NOFLAGS, + Float:volume = SNDVOL_NORMAL, + pitch = SNDPITCH_NORMAL, + speakerentity = -1, + const Float:origin[3] = NULL_VECTOR, + const Float:dir[3] = NULL_VECTOR, + bool:updatePos = true, + Float:soundtime = 0.0) +{ + new maxClients = GetMaxClients(); + new clients[maxClients]; + new total = 0; + + for (new i=1; i<=maxClients; i++) + { + if (IsClientInGame(i)) + { + clients[total++] = i; + } + } + + if (!total) + { + return; + } + + EmitSound(clients, total, sample, entity, channel, + level, flags, volume, pitch, speakerentity, + origin, dir, updatePos, soundtime); +} + +/** + * Converts an attenuation value to a sound level. + * This function is from the HL2SDK. + * + * @param attn Attenuation value. + * @return Integer sound level. + */ +stock ATTN_TO_SNDLEVEL(Float:attn) +{ + if (attn > 0.0) + { + return RoundFloat(50.0 + (20.0 / attn)); + } + return 0; +} diff --git a/src/include/sdktools_stocks.inc b/src/include/sdktools_stocks.inc new file mode 100644 index 0000000..ec25166 --- /dev/null +++ b/src/include/sdktools_stocks.inc @@ -0,0 +1,76 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_stocks.inc 1622 2007-10-21 00:16:46Z dvander $ + */ + +#if defined _sdktools_stocks_included + #endinput +#endif +#define _sdktools_stocks_included + +/** + * Given a partial team name, attempts to find a matching team. + * + * The search is performed case insensitively and only against the + * first N characters of the team names, where N is the number of + * characters in the search pattern. + * + * @param name Partial or full team name. + * @return A valid team index on success. + * -1 if no team matched. + * -2 if more than one team matched. + */ +stock FindTeamByName(const String:name[]) +{ + new name_len = strlen(name); + new num_teams = GetTeamCount(); + decl String:team_name[32]; + new found_team = -1 + + for (new i = 0; i < num_teams; i++) + { + GetTeamName(i, team_name, sizeof(team_name)); + + if (strncmp(team_name, name, name_len) == 0) + { + if (found_team >= 0) + { + return -2; + } + else + { + found_team = i; + } + } + } + + return found_team; +} + diff --git a/src/include/sdktools_stringtables.inc b/src/include/sdktools_stringtables.inc new file mode 100644 index 0000000..4eec397 --- /dev/null +++ b/src/include/sdktools_stringtables.inc @@ -0,0 +1,180 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_stringtables.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _sdktools_stringtables_included + #endinput +#endif +#define _sdktools_stringtables_included + +#define INVALID_STRING_TABLE -1 /**< An invalid string table index */ +#define INVALID_STRING_INDEX -1 /**< An invalid string index in a table */ + +/** + * Searches for a string table. + * + * @param name Name of string table to find. + * @return A string table index number if found, INVALID_STRING_TABLE otherwise. + */ +native FindStringTable(const String:name[]); + +/** + * Returns the number of string tables that currently exist. + * + * @return Number of string tables that currently exist. + */ +native GetNumStringTables(); + +/** + * Returns the number of strings that currently exist in a given string table. + * + * @param tableidx A string table index. + * @return Number of strings that currently exist. + * @error Invalid string table index. + */ +native GetStringTableNumStrings(tableidx); + +/** + * Returns the maximum number of strings that are allowed in a given string table. + * + * @param tableidx A string table index. + * @return Maximum number of strings allowed. + * @error Invalid string table index. + */ +native GetStringTableMaxStrings(tableidx); + +/** + * Retrieves the name of a string table. + * + * @param tableidx A string table index. + * @param name Buffer to store the name of the string table. + * @param maxlength Maximum length of string buffer. + * @return Number of bytes written to the buffer (UTF-8 safe). + * @error Invalid string table index. + */ +native GetStringTableName(tableidx, String:name[], maxlength); + +/** + * Searches for the index of a given string in a string table. + * + * @param tableidx A string table index. + * @param string String to find. + * @return String index if found, INVALID_STRING_INDEX otherwise. + * @error Invalid string table index. + */ +native FindStringIndex(tableidx, const String:str[]); + +/** + * Retrieves the string at a given index of a string table. + * + * @param tableidx A string table index. + * @param stringidx A string index. + * @param name Buffer to store the string value. + * @param maxlength Maximum length of string buffer. + * @return Number of bytes written to the buffer (UTF-8 safe). + * @error Invalid string table index or string index. + */ +native ReadStringTable(tableidx, stringIdx, String:str[], maxlength); + +/** + * Returns the length of the user data associated with a given string index. + * + * @param tableidx A string table index. + * @param stringidx A string index. + * @return Length of user data. This will be 0 if there is no user data. + * @error Invalid string table index or string index. + */ +native GetStringTableDataLength(tableidx, stringidx); + +/** + * Retrieves the user data associated with a given string index. + * + * @param tableidx A string table index. + * @param stringidx A string index. + * @param userdata Buffer to store the user data. This will be set to "" if there is no user data. + * @param maxlength Maximum length of string buffer. + * @return Number of bytes written to the buffer (UTF-8 safe). + * @error Invalid string table index or string index. + */ +native GetStringTableData(tableidx, stringIdx, String:userdata[], maxlength); + +/** + * Sets the user data associated with a given string index. + * + * @param tableidx A string table index. + * @param stringidx A string index. + * @param userdata User data string that will be set. + * @param length Length of user data string. This should include the null terminator. + * @return Number of bytes written to the buffer (UTF-8 safe). + * @error Invalid string table index or string index. + */ +native SetStringTableData(tableidx, stringIdx, const String:userdata[], length); + +/** + * Adds a string to a given string table. + * + * @param tableidx A string table index. + * @param string String to add. + * @param userdata An optional user data string. + * @param length Length of user data string. This should include the null terminator. + * If set to -1, then user data will be not be altered if the specified string + * already exists in the string table. + */ +native AddToStringTable(tableidx, const String:str[], const String:userdata[]="", length=-1); + +/** + * Locks or unlocks the network string tables. + * + * @param lock Determines whether network string tables should be locked. + * True means the tables should be locked for writing; false means unlocked. + * @return Previous lock state. + */ +native bool:LockStringTables(bool:lock); + +/** + * Adds a file to the downloadables network string table. + * This forces a client to download the file if they do not already have it. + * + * @param filename File that will be added to downloadables table. + */ +stock AddFileToDownloadsTable(const String:filename[]) +{ + static table = INVALID_STRING_TABLE; + + if (table == INVALID_STRING_TABLE) + { + table = FindStringTable("downloadables"); + } + + new bool:save = LockStringTables(false); + AddToStringTable(table, filename); + LockStringTables(save); +} diff --git a/src/include/sdktools_tempents.inc b/src/include/sdktools_tempents.inc new file mode 100644 index 0000000..acea043 --- /dev/null +++ b/src/include/sdktools_tempents.inc @@ -0,0 +1,229 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_tempents.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _sdktools_tempents_included + #endinput +#endif +#define _sdktools_tempents_included + +/** + * Called when a temp entity is going to be sent. + * + * @param te_name TE name. + * @param Players Array containing target player indexes. + * @param numClients Number of players in the array. + * @param delay Delay in seconds to send the TE. + * @return Plugin_Continue to allow the transmission of the TE, Plugin_Stop to block it. + */ +functag TEHook Action:public(const String:te_name[], const Players[], numClients, Float:delay); + +/** + * Hooks a temp entity. + * + * @param te_name TE name to hook. + * @param hook Function to use as a hook. + * @noreturn + * @error Temp Entity name not available or invalid function hook. + */ +native AddTempEntHook(const String:te_name[], TEHook:hook); + +/** + * Removes a temp entity hook. + * + * @param te_name TE name to unhook. + * @param hook Function used for the hook. + * @noreturn + * @error Temp Entity name not available or invalid function hook. + */ +native RemoveTempEntHook(const String:te_name[], TEHook:hook); + +/** + * Starts a temp entity transmission. + * + * @param te_name TE name. + * @noreturn + * @error Temp Entity name not available. + */ +native TE_Start(const String:te_name[]); + +/** + * Checks if a certain TE property exists. + * + * @param prop Property to use. + * @return True if the property exists, otherwise false. + */ +native bool:TE_IsValidProp(const String:prop[]); + +/** + * Sets an integer value in the current temp entity. + * + * @param prop Property to use. + * @param value Integer value to set. + * @noreturn + * @error Property not found. + */ +native TE_WriteNum(const String:prop[], value); + +/** + * Reads an integer value in the current temp entity. + * + * @param prop Property to use. + * @return Property value. + * @error Property not found. + */ +native TE_ReadNum(const String:prop[]); + +/** + * Sets a floating point number in the current temp entity. + * + * @param prop Property to use. + * @param value Floating point number to set. + * @noreturn + * @error Property not found. + */ +native TE_WriteFloat(const String:prop[], Float:value); + +/** + * Reads a floating point number in the current temp entity. + * + * @param prop Property to use. + * @noreturn Property value. + * @error Property not found. + */ +native Float:TE_ReadFloat(const String:prop[]); + +/** + * Sets a vector in the current temp entity. + * + * @param prop Property to use. + * @param vector Vector to set. + * @noreturn + * @error Property not found. + */ +native TE_WriteVector(const String:prop[], const Float:vector[3]); + +/** + * Reads a vector in the current temp entity. + * + * @param prop Property to use. + * @param vector Vector to read. + * @noreturn + * @error Property not found. + */ +native TE_ReadVector(const String:prop[], Float:vector[3]); + +/** + * Sets a QAngle in the current temp entity. + * + * @param prop Property to use. + * @param angles Angles to set. + * @return True on success, otherwise false. + * @error Property not found. + */ +native TE_WriteAngles(const String:prop[], const Float:angles[3]); + +/** + * Sets an array of floats in the current temp entity. + * + * @param prop Property to use. + * @param array Array of values to copy. + * @param arraySize Number of values to copy. + * @return True on success, otherwise false. + * @error Property not found. + */ +native TE_WriteFloatArray(const String:prop[], const Float:array[], arraySize); + +/** + * Sends the current temp entity to one or more clients. + * + * @param clients Array containing player indexes to broadcast to. + * @param numClients Number of players in the array. + * @param delay Delay in seconds to send the TE. + * @noreturn + * @error Invalid client index or client not in game. + */ +native TE_Send(clients[], numClients, Float:delay=0.0); + +/** + * Sets an encoded entity index in the current temp entity. + * (This is usually used for m_nStartEntity and m_nEndEntity). + * + * @param prop Property to use. + * @param value Value to set. + * @noreturn + * @error Property not found. + */ +stock TE_WriteEncodedEnt(const String:prop[], value) +{ + new encvalue = (value & 0x0FFF) | ((1 & 0xF)<<12); + return TE_WriteNum(prop, encvalue); +} + +/** + * Broadcasts the current temp entity to all clients. + * @note See TE_Start(). + * + * @param delay Delay in seconds to send the TE. + * @noreturn + */ +stock TE_SendToAll(Float:delay=0.0) +{ + new maxClients = GetMaxClients(); + new total = 0; + new clients[maxClients]; + for (new i=1; i<=maxClients; i++) + { + if (IsClientInGame(i)) + { + clients[total++] = i; + } + } + return TE_Send(clients, total, delay); +} + +/** + * Sends the current TE to only a client. + * @note See TE_Start(). + * + * @param client Client to send to. + * @param delay Delay in seconds to send the TE. + * @noreturn + * @error Invalid client index or client not in game. + */ +stock TE_SendToClient(client, Float:delay=0.0) +{ + new players[1]; + + players[0] = client; + + return TE_Send(players, 1, delay); +} diff --git a/src/include/sdktools_tempents_stocks.inc b/src/include/sdktools_tempents_stocks.inc new file mode 100644 index 0000000..6730cac --- /dev/null +++ b/src/include/sdktools_tempents_stocks.inc @@ -0,0 +1,458 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_tempents_stocks.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _te_stocks_included + #endinput +#endif +#define _te_stocks_included + +/** + * @section TE Explosion flags. + */ +#define TE_EXPLFLAG_NONE 0x0 /**< all flags clear makes default Half-Life explosion */ +#define TE_EXPLFLAG_NOADDITIVE 0x1 /**< sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) */ +#define TE_EXPLFLAG_NODLIGHTS 0x2 /**< do not render dynamic lights */ +#define TE_EXPLFLAG_NOSOUND 0x4 /**< do not play client explosion sound */ +#define TE_EXPLFLAG_NOPARTICLES 0x8 /**< do not draw particles */ +#define TE_EXPLFLAG_DRAWALPHA 0x10 /**< sprite will be drawn alpha */ +#define TE_EXPLFLAG_ROTATE 0x20 /**< rotate the sprite randomly */ +#define TE_EXPLFLAG_NOFIREBALL 0x40 /**< do not draw a fireball */ +#define TE_EXPLFLAG_NOFIREBALLSMOKE 0x80 /**< do not draw smoke with the fireball */ + +/** + * @endsection + */ + +/** + * @section TE Beam flags. + */ +#define FBEAM_STARTENTITY 0x00000001 +#define FBEAM_ENDENTITY 0x00000002 +#define FBEAM_FADEIN 0x00000004 +#define FBEAM_FADEOUT 0x00000008 +#define FBEAM_SINENOISE 0x00000010 +#define FBEAM_SOLID 0x00000020 +#define FBEAM_SHADEIN 0x00000040 +#define FBEAM_SHADEOUT 0x00000080 +#define FBEAM_ONLYNOISEONCE 0x00000100 /**< Only calculate our noise once */ +#define FBEAM_NOTILE 0x00000200 +#define FBEAM_USE_HITBOXES 0x00000400 /**< Attachment indices represent hitbox indices instead when this is set. */ +#define FBEAM_STARTVISIBLE 0x00000800 /**< Has this client actually seen this beam's start entity yet? */ +#define FBEAM_ENDVISIBLE 0x00001000 /**< Has this client actually seen this beam's end entity yet? */ +#define FBEAM_ISACTIVE 0x00002000 +#define FBEAM_FOREVER 0x00004000 +#define FBEAM_HALOBEAM 0x00008000 /**< When drawing a beam with a halo, don't ignore the segments and endwidth */ + +/** + * @endsection + */ + +/** + * Sets up a sparks effect. + * + * @param pos Position of the sparks. + * @param dir Direction of the sparks. + * @param Magnitude Sparks size. + * @param TrailLength Trail lenght of the sparks. + * @noreturn + */ +stock TE_SetupSparks(const Float:pos[3], const Float:dir[3], Magnitude, TrailLength) +{ + TE_Start("Sparks"); + TE_WriteVector("m_vecOrigin[0]", pos); + TE_WriteVector("m_vecDir", dir); + TE_WriteNum("m_nMagnitude", Magnitude); + TE_WriteNum("m_nTrailLength", TrailLength); +} + +/** + * Sets up a smoke effect. + * + * @param pos Position of the smoke. + * @param Model Precached model index. + * @param Scale Scale of the smoke. + * @param Framerate Frame rate of the smoke. + * @noreturn + */ +stock TE_SetupSmoke(const Float:pos[3], Model, Float:Scale, FrameRate) +{ + TE_Start("Smoke"); + TE_WriteVector("m_vecOrigin", pos); + TE_WriteNum("m_nModelIndex", Model); + TE_WriteFloat("m_fScale", Scale); + TE_WriteNum("m_nFrameRate", FrameRate); +} + +/** + * Sets up a dust cloud effect. + * + * @param pos Position of the dust. + * @param dir Direction of the dust. + * @param Size Dust cloud size. + * @param Speed Dust cloud speed. + * @noreturn + */ +stock TE_SetupDust(const Float:pos[3], const Float:dir[3], Float:Size, Float:Speed) +{ + TE_Start("Dust"); + TE_WriteVector("m_vecOrigin[0]", pos); + TE_WriteVector("m_vecDirection", dir); + TE_WriteFloat("m_flSize", Size); + TE_WriteFloat("m_flSpeed", Speed); +} + +/** + * Sets up a muzzle flash effect. + * + * @param pos Position of the muzzle flash. + * @param angles Rotation angles of the muzzle flash. + * @param Scale Scale of the muzzle flash. + * @param Type Muzzle flash type to render (Mod specific). + * @noreturn + */ +stock TE_SetupMuzzleFlash(const Float:pos[3], const Float:angles[3], Float:Scale, Type) +{ + TE_Start("MuzzleFlash"); + TE_WriteVector("m_vecOrigin", pos); + TE_WriteVector("m_vecAngles", angles); + TE_WriteFloat("m_flScale", Scale); + TE_WriteNum("m_nType", Type); +} + +/** + * Sets up a metal sparks effect. + * + * @param pos Position of the metal sparks. + * @param dir Direction of the metal sparks. + * @noreturn + */ +stock TE_SetupMetalSparks(const Float:pos[3], const Float:dir[3]) +{ + TE_Start("Metal Sparks"); + TE_WriteVector("m_vecPos", pos); + TE_WriteVector("m_vecDir", dir); +} + +/** + * Sets up an energy splash effect. + * + * @param pos Position of the energy splash. + * @param dir Direction of the energy splash. + * @param Explosive Makes the effect explosive. + * @noreturn + */ +stock TE_SetupEnergySplash(const Float:pos[3], const Float:dir[3], bool:Explosive) +{ + TE_Start("Energy Splash"); + TE_WriteVector("m_vecPos", pos); + TE_WriteVector("m_vecDir", dir); + TE_WriteNum("m_bExplosive", Explosive); +} + +/** + * Sets up an armor ricochet effect. + * + * @param pos Position of the armor ricochet. + * @param dir Directon of the armor ricochet. + * @noreturn + */ +stock TE_SetupArmorRicochet(const Float:pos[3], const Float:dir[3]) +{ + TE_Start("Armor Ricochet"); + TE_WriteVector("m_vecPos", pos); + TE_WriteVector("m_vecDir", dir); +} + +/** + * Sets up a glowing sprite effect. + * + * @param pos Position of the sprite. + * @param Model Precached model index. + * @param Life Time duration of the sprite. + * @param Size Sprite size. + * @param Brightness Sprite brightness. + * @noreturn + */ +stock TE_SetupGlowSprite(const Float:pos[3], Model, Float:Life, Float:Size, Brightness) +{ + TE_Start("GlowSprite"); + TE_WriteVector("m_vecOrigin", pos); + TE_WriteNum("m_nModelIndex", Model); + TE_WriteFloat("m_fScale", Size); + TE_WriteFloat("m_fLife", Life); + TE_WriteNum("m_nBrightness", Brightness); +} + +/** + * Sets up a explosion effect. + * + * @param pos Explosion position. + * @param Model Precached model index. + * @param Scale Explosion scale. + * @param Framerate Explosion frame rate. + * @param Flags Explosion flags. + * @param Radius Explosion radius. + * @param Magnitude Explosion size. + * @param normal Normal vector to the explosion. + * @param MaterialType Exploded material type. + * @noreturn + */ +stock TE_SetupExplosion(const Float:pos[3], Model, Float:Scale, Framerate, Flags, Radius, Magnitude, const Float:normal[3]={0.0, 0.0, 1.0}, MaterialType='C') +{ + TE_Start("Explosion"); + TE_WriteVector("m_vecOrigin[0]", pos); + TE_WriteVector("m_vecNormal", normal); + TE_WriteNum("m_nModelIndex", Model); + TE_WriteFloat("m_fScale", Scale); + TE_WriteNum("m_nFrameRate", Framerate); + TE_WriteNum("m_nFlags", Flags); + TE_WriteNum("m_nRadius", Radius); + TE_WriteNum("m_nMagnitude", Magnitude); + TE_WriteNum("m_chMaterialType", MaterialType); +} + +/** + * Sets up a blood sprite effect. + * + * @param pos Position of the sprite. + * @param dir Sprite direction. + * @param color Color array (r, g, b, a). + * @param Size Sprite size. + * @param SprayModel Precached model index. + * @param BloodDropModel Precached model index. + * @noreturn + */ +stock TE_SetupBloodSprite(const Float:pos[3], const Float:dir[3], const color[4], Size, SprayModel, BloodDropModel) +{ + TE_Start("Blood Sprite"); + TE_WriteVector("m_vecOrigin", pos); + TE_WriteVector("m_vecDirection", dir); + TE_WriteNum("r", color[0]); + TE_WriteNum("g", color[1]); + TE_WriteNum("b", color[2]); + TE_WriteNum("a", color[3]); + TE_WriteNum("m_nSize", Size); + TE_WriteNum("m_nSprayModel", SprayModel); + TE_WriteNum("m_nDropModel", BloodDropModel); +} + +/** + * Sets up a beam ring point effect. + * + * @param center Center position of the ring. + * @param Start_Radius Initial ring radius. + * @param End_Radius Final ring radius. + * @param ModelIndex Precached model index. + * @param HaloIndex Precached model index. + * @param StartFrame Initital frame to render. + * @param FrameRate Ring frame rate. + * @param Life Time duration of the ring. + * @param Width Beam width. + * @param Amplitude Beam amplitude. + * @param color Color array (r, g, b, a). + * @param Speed Speed of the beam. + * @param Flags Beam flags. + * @noreturn + */ +stock TE_SetupBeamRingPoint(const Float:center[3], Float:Start_Radius, Float:End_Radius, ModelIndex, HaloIndex, StartFrame, + FrameRate, Float:Life, Float:Width, Float:Amplitude, const Color[4], Speed, Flags) +{ + TE_Start("BeamRingPoint"); + TE_WriteVector("m_vecCenter", center); + TE_WriteFloat("m_flStartRadius", Start_Radius); + TE_WriteFloat("m_flEndRadius", End_Radius); + 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", Width); + TE_WriteFloat("m_fAmplitude", Amplitude); + TE_WriteNum("r", Color[0]); + TE_WriteNum("g", Color[1]); + TE_WriteNum("b", Color[2]); + TE_WriteNum("a", Color[3]); + TE_WriteNum("m_nSpeed", Speed); + TE_WriteNum("m_nFlags", Flags); + TE_WriteNum("m_nFadeLength", 0); +} + +/** + * 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 HaloIndex Precached model index. + * @param StartFrame Initital frame to render. + * @param FrameRate Beam frame rate. + * @param Life Time duration of the beam. + * @param Width Initial beam width. + * @param EndWidth Final beam width. + * @param FadeLength Beam fade time duration. + * @param Amplitude Beam amplitude. + * @param color Color array (r, g, b, a). + * @param Speed Speed of the beam. + * @noreturn + */ +stock TE_SetupBeamPoints(const Float:start[3], const Float:end[3], ModelIndex, HaloIndex, StartFrame, FrameRate, Float:Life, + Float:Width, Float:EndWidth, FadeLength, Float:Amplitude, const Color[4], Speed) +{ + 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", Color[0]); + TE_WriteNum("g", Color[1]); + TE_WriteNum("b", Color[2]); + TE_WriteNum("a", Color[3]); + TE_WriteNum("m_nSpeed", Speed); + TE_WriteNum("m_nFadeLength", FadeLength); +} + +/** + * Sets up an entity to entity laser effect. + * + * @param StartEntity Entity index from where the beam starts. + * @param EndEntity Entity index from where the beam ends. + * @param ModelIndex Precached model index. + * @param HaloIndex Precached model index. + * @param StartFrame Initital frame to render. + * @param FrameRate Beam frame rate. + * @param Life Time duration of the beam. + * @param Width Initial beam width. + * @param EndWidth Final beam width. + * @param FadeLength Beam fade time duration. + * @param Amplitude Beam amplitude. + * @param color Color array (r, g, b, a). + * @param Speed Speed of the beam. + * @noreturn + */ +stock TE_SetupBeamLaser(StartEntity, EndEntity, ModelIndex, HaloIndex, StartFrame, FrameRate, Float:Life, + Float:Width, Float:EndWidth, FadeLength, Float:Amplitude, const Color[4], Speed) +{ + TE_Start("BeamLaser"); + TE_WriteEncodedEnt("m_nStartEntity", StartEntity); + TE_WriteEncodedEnt("m_nEndEntity", EndEntity); + 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", Color[0]); + TE_WriteNum("g", Color[1]); + TE_WriteNum("b", Color[2]); + TE_WriteNum("a", Color[3]); + TE_WriteNum("m_nSpeed", Speed); + TE_WriteNum("m_nFadeLength", FadeLength); +} + +/** + * Sets up a beam ring effect. + * + * @param StartEntity Entity index from where the ring starts. + * @param EndEntity Entity index from where the ring ends. + * @param ModelIndex Precached model index. + * @param HaloIndex Precached model index. + * @param StartFrame Initital frame to render. + * @param FrameRate Ring frame rate. + * @param Life Time duration of the ring. + * @param Width Beam width. + * @param Amplitude Beam amplitude. + * @param color Color array (r, g, b, a). + * @param Speed Speed of the beam. + * @param Flags Beam flags. + * @noreturn + */ +stock TE_SetupBeamRing(StartEntity, EndEntity, ModelIndex, HaloIndex, StartFrame, FrameRate, Float:Life, Float:Width, Float:Amplitude, const Color[4], Speed, Flags) +{ + TE_Start("BeamRing"); + TE_WriteEncodedEnt("m_nStartEntity", StartEntity); + TE_WriteEncodedEnt("m_nEndEntity", EndEntity); + 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", Width); + TE_WriteFloat("m_fAmplitude", Amplitude); + TE_WriteNum("r", Color[0]); + TE_WriteNum("g", Color[1]); + TE_WriteNum("b", Color[2]); + TE_WriteNum("a", Color[3]); + TE_WriteNum("m_nSpeed", Speed); + TE_WriteNum("m_nFadeLength", 0); + TE_WriteNum("m_nFlags", Flags); +} + +/** + * Sets up a follow beam effect. + * + * @param EntIndex Entity index from where the beam starts. + * @param ModelIndex Precached model index. + * @param HaloIndex Precached model index. + * @param Life Time duration of the beam. + * @param Width Initial beam width. + * @param EndWidth Final beam width. + * @param FadeLength Beam fade time duration. + * @param color Color array (r, g, b, a). + * @noreturn + */ +stock TE_SetupBeamFollow(EntIndex, ModelIndex, HaloIndex, Float:Life, Float:Width, Float:EndWidth, FadeLength, const Color[4]) +{ + TE_Start("BeamFollow"); + TE_WriteEncodedEnt("m_iEntIndex", EntIndex); + TE_WriteNum("m_nModelIndex", ModelIndex); + TE_WriteNum("m_nHaloIndex", HaloIndex); + TE_WriteNum("m_nStartFrame", 0); + TE_WriteNum("m_nFrameRate", 0); + TE_WriteFloat("m_fLife", Life); + TE_WriteFloat("m_fWidth", Width); + TE_WriteFloat("m_fEndWidth", EndWidth); + TE_WriteNum("m_nFadeLength", FadeLength); + TE_WriteNum("r", Color[0]); + TE_WriteNum("g", Color[1]); + TE_WriteNum("b", Color[2]); + TE_WriteNum("a", Color[3]); +} diff --git a/src/include/sdktools_trace.inc b/src/include/sdktools_trace.inc new file mode 100644 index 0000000..06eb955 --- /dev/null +++ b/src/include/sdktools_trace.inc @@ -0,0 +1,367 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_trace.inc 2208 2008-05-29 03:50:36Z dvander $ + */ + +#if defined _sdktools_trace_included + #endinput +#endif +#define _sdktools_trace_included + +#define CONTENTS_EMPTY 0 /**< No contents. */ +#define CONTENTS_SOLID 0x1 /**< an eye is never valid in a solid . */ +#define CONTENTS_WINDOW 0x2 /**< translucent, but not watery (glass). */ +#define CONTENTS_AUX 0x4 +#define CONTENTS_GRATE 0x8 /**< alpha-tested "grate" textures. Bullets/sight pass through, but solids don't. */ +#define CONTENTS_SLIME 0x10 +#define CONTENTS_WATER 0x20 +#define CONTENTS_MIST 0x40 +#define CONTENTS_OPAQUE 0x80 /**< things that cannot be seen through (may be non-solid though). */ +#define LAST_VISIBLE_CONTENTS 0x80 +#define ALL_VISIBLE_CONTENTS (LAST_VISIBLE_CONTENTS | (LAST_VISIBLE_CONTENTS-1)) +#define CONTENTS_TESTFOGVOLUME 0x100 +#define CONTENTS_UNUSED5 0x200 +#define CONTENTS_UNUSED6 0x4000 +#define CONTENTS_TEAM1 0x800 /**< per team contents used to differentiate collisions. */ +#define CONTENTS_TEAM2 0x1000 /**< between players and objects on different teams. */ +#define CONTENTS_IGNORE_NODRAW_OPAQUE 0x2000 /**< ignore CONTENTS_OPAQUE on surfaces that have SURF_NODRAW. */ +#define CONTENTS_MOVEABLE 0x4000 /**< hits entities which are MOVETYPE_PUSH (doors, plats, etc) */ +#define CONTENTS_AREAPORTAL 0x8000 /**< remaining contents are non-visible, and don't eat brushes. */ +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +/** + * @section currents can be added to any other contents, and may be mixed + */ +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +/** + * @endsection + */ + +#define CONTENTS_ORIGIN 0x1000000 /**< removed before bsping an entity. */ +#define CONTENTS_MONSTER 0x2000000 /**< should never be on a brush, only in game. */ +#define CONTENTS_DEBRIS 0x4000000 +#define CONTENTS_DETAIL 0x8000000 /**< brushes to be added after vis leafs. */ +#define CONTENTS_TRANSLUCENT 0x10000000 /**< auto set if any surface has trans. */ +#define CONTENTS_LADDER 0x20000000 +#define CONTENTS_HITBOX 0x40000000 /**< use accurate hitboxes on trace. */ + +/** + * @section Trace masks. + */ +#define MASK_ALL (0xFFFFFFFF) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE) /**< everything that is normally solid */ +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE) /**< everything that blocks player movement */ +#define MASK_NPCSOLID (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE) /**< blocks npc movement */ +#define MASK_WATER (CONTENTS_WATER|CONTENTS_MOVEABLE|CONTENTS_SLIME) /**< water physics in these contents */ +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_OPAQUE) /**< everything that blocks line of sight for AI, lighting, etc */ +#define MASK_OPAQUE_AND_NPCS (MASK_OPAQUE|CONTENTS_MONSTER) /**< everything that blocks line of sight for AI, lighting, etc, but with monsters added. */ +#define MASK_VISIBLE (MASK_OPAQUE|CONTENTS_IGNORE_NODRAW_OPAQUE) /**< everything that blocks line of sight for players */ +#define MASK_VISIBLE_AND_NPCS (MASK_OPAQUE_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE) /**< everything that blocks line of sight for players, but with monsters added. */ +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEBRIS|CONTENTS_HITBOX) /**< bullets see these as solid */ +#define MASK_SHOT_HULL (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEBRIS|CONTENTS_GRATE) /**< non-raycasted weapons see this as solid (includes grates) */ +#define MASK_SHOT_PORTAL (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW) /**< hits solids (not grates) and passes through everything else */ +#define MASK_SOLID_BRUSHONLY (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_GRATE) /**< everything normally solid, except monsters (world+brush only) */ +#define MASK_PLAYERSOLID_BRUSHONLY (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_PLAYERCLIP|CONTENTS_GRATE) /**< everything normally solid for player movement, except monsters (world+brush only) */ +#define MASK_NPCSOLID_BRUSHONLY (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTERCLIP|CONTENTS_GRATE) /**< everything normally solid for npc movement, except monsters (world+brush only) */ +#define MASK_NPCWORLDSTATIC (CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_MONSTERCLIP|CONTENTS_GRATE) /**< just the world, used for route rebuilding */ +#define MASK_SPLITAREAPORTAL (CONTENTS_WATER|CONTENTS_SLIME) /**< These are things that can split areaportals */ + +/** + * @endsection + */ + +enum RayType +{ + RayType_EndPoint, /**< The trace ray will go from the start position to the end position. */ + RayType_Infinite /**< The trace ray will go from the start position to infinity using a direction vector. */ +}; + +funcenum TraceEntityFilter +{ + /** + * Called on entity filtering. + * + * @param entity Entity index. + * @param contentsMask Contents Mask. + * @return True to allow the current entity to be hit, otherwise false. + */ + bool:public(entity, contentsMask), + + /** + * Called on entity filtering. + * + * @param entity Entity index. + * @param contentsMask Contents Mask. + * @param data Data value, if used. + * @return True to allow the current entity to be hit, otherwise false. + */ + bool:public(entity, contentsMask, any:data), +}; + +/** + * Get the contents mask and the entity index at the given position. + * + * @param pos World position to test. + * @param entindex Entity index found at the given position (by reference). + * @return Contents mask. + */ +native TR_GetPointContents(const Float:pos[3], &entindex=-1); + +/** + * Get the point contents testing only the given entity index. + * + * @param entindex Entity index to test. + * @param pos World position. + * @return Contents mask. + */ +native TR_GetPointContentsEnt(entindex, const Float:pos[3]); + +/** + * Starts up a new trace ray using a global trace result. + * + * @param pos Starting position of the ray. + * @param vec Depending on RayType, it will be used as the + * ending point, or the direction angle. + * @param flags Trace flags. + * @param rtype Method to calculate the ray direction. + * @noreturn + */ +native TR_TraceRay(const Float:pos[3], + const Float:vec[3], + flags, + RayType:rtype); + +/** + * Starts up a new trace hull using a global trace result. + * + * @param pos Starting position of the ray. + * @param vec Ending position of the ray. + * @param mins Hull minimum size. + * @param maxs Hull maximum size. + * @param flags Trace flags. + * @noreturn + */ +native TR_TraceHull(const Float:pos[3], + const Float:vec[3], + const Float:mins[3], + const Float:maxs[3], + flags); + +/** + * Starts up a new trace ray using a global trace result and a customized + * trace ray filter. + * + * Calling TR_Trace*Filter or TR_Trace*FilterEx from inside a filter + * function is currently not allowed and may not work. + * + * @param pos Starting position of the ray. + * @param vec Depending on RayType, it will be used as the ending + * point, or the direction angle. + * @param flags Trace flags. + * @param rtype Method to calculate the ray direction. + * @param filter Function to use as a filter. + * @param data Arbitrary data value to pass through to the filter + * function. + * @noreturn + */ +native TR_TraceRayFilter(const Float:pos[3], + const Float:vec[3], + flags, + RayType:rtype, + TraceEntityFilter:filter, + any:data=0); + +/** + * Starts up a new trace hull using a global trace result and a customized + * trace ray filter. + * + * Calling TR_Trace*Filter or TR_Trace*FilterEx from inside a filter + * function is currently not allowed and may not work. + * + * @param pos Starting position of the ray. + * @param vec Depending on RayType, it will be used as the ending + * point, or the direction angle. + * @param mins Hull minimum size. + * @param maxs Hull maximum size. + * @param flags Trace flags. + * @param filter Function to use as a filter. + * @param data Arbitrary data value to pass through to the filter + * function. + * @noreturn + */ +native TR_TraceHullFilter(const Float:pos[3], + const Float:vec[3], + const Float:mins[3], + const Float:maxs[3], + flags, + TraceEntityFilter:filter, + any:data=0); + +/** + * Starts up a new trace ray using a new trace result. + * + * @param pos Starting position of the ray. + * @param vec Depending on RayType, it will be used as the ending + * point, or the direction angle. + * @param flags Trace flags. + * @param rtype Method to calculate the ray direction. + * @return Ray trace handle, which must be closed via CloseHandle(). + */ +native Handle:TR_TraceRayEx(const Float:pos[3], + const Float:vec[3], + flags, + RayType:rtype); + +/** + * Starts up a new trace hull using a new trace result. + * + * @param pos Starting position of the ray. + * @param vec Ending position of the ray. + * @param mins Hull minimum size. + * @param maxs Hull maximum size. + * @param flags Trace flags. + * @return Ray trace handle, which must be closed via CloseHandle(). + */ +native Handle:TR_TraceHullEx(const Float:pos[3], + const Float:vec[3], + const Float:mins[3], + const Float:maxs[3], + flags); + +/** + * Starts up a new trace ray using a new trace result and a customized + * trace ray filter. + * + * Calling TR_Trace*Filter or TR_TraceRay*Ex from inside a filter + * function is currently not allowed and may not work. + * + * @param pos Starting position of the ray. + * @param vec Depending on RayType, it will be used as the ending + * point, or the direction angle. + * @param flags Trace flags. + * @param rtype Method to calculate the ray direction. + * @param filter Function to use as a filter. + * @param data Arbitrary data value to pass through to the filter function. + * @return Ray trace handle, which must be closed via CloseHandle(). + */ +native Handle:TR_TraceRayFilterEx(const Float:pos[3], + const Float:vec[3], + flags, + RayType:rtype, + TraceEntityFilter:filter, + any:data=0); + +/** + * Starts up a new trace hull using a new trace result and a customized + * trace ray filter. + * + * Calling TR_Trace*Filter or TR_Trace*FilterEx from inside a filter + * function is currently not allowed and may not work. + * + * @param pos Starting position of the ray. + * @param vec Ending position of the ray. + * @param mins Hull minimum size. + * @param maxs Hull maximum size. + * @param flags Trace flags. + * @param filter Function to use as a filter. + * @param data Arbitrary data value to pass through to the filter function. + * @return Ray trace handle, which must be closed via CloseHandle(). + */ +native Handle:TR_TraceHullFilterEx(const Float:pos[3], + const Float:vec[3], + const Float:mins[3], + const Float:maxs[3], + flags, + TraceEntityFilter:filter, + any:data=0); + +/** + * Returns the time fraction from a trace result (1.0 means no collision). + * + * @param hndl A trace Handle, or INVALID_HANDLE to use a global trace result. + * @return Time fraction value of the trace. + * @error Invalid Handle. + */ +native Float:TR_GetFraction(Handle:hndl=INVALID_HANDLE); + +/** + * Returns the collision position of a trace result. + * + * @param pos Vector buffer to store data in. + * @param hndl A trace Handle, or INVALID_HANDLE to use a global trace result. + * @noreturn + * @error Invalid Handle. + */ +native TR_GetEndPosition(Float:pos[3], Handle:hndl=INVALID_HANDLE); + +/** + * Returns the entity index that collided with the trace. + * + * @param hndl A trace Handle, or INVALID_HANDLE to use a global trace result. + * @return Entity index or -1 for no collision. + * @error Invalid Handle. + */ +native TR_GetEntityIndex(Handle:hndl=INVALID_HANDLE); + +/** + * Returns if there was any kind of collision along the trace ray. + * + * @param hndl A trace Handle, or INVALID_HANDLE to use a global trace result. + * @return True if any collision found, otherwise false. + * @error Invalid Handle. + */ +native bool:TR_DidHit(Handle:hndl=INVALID_HANDLE); + +/** + * Returns in which body hit group the trace collided if any. + * + * @param hndl A trace Handle, or INVALID_HANDLE to use a global trace result. + * @return Body hit group. + * @error Invalid Handle. + */ +native TR_GetHitGroup(Handle:hndl=INVALID_HANDLE); + +/** + * Find the normal vector to the collison plane of a trace. + * + * @param hndl A trace Handle, or INVALID_HANDLE to use a global trace result. + * @param normal Vector buffer to store the vector normal to the collision plane + * @noreturn + * @error Invalid Handle + */ +native TR_GetPlaneNormal(Handle:hndl, Float:normal[3]); + diff --git a/src/include/sdktools_voice.inc b/src/include/sdktools_voice.inc new file mode 100644 index 0000000..2cf33bb --- /dev/null +++ b/src/include/sdktools_voice.inc @@ -0,0 +1,92 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sdktools_voice.inc 2163 2008-05-21 03:31:27Z dvander $ + */ + +#if defined _sdktools_voice_included + #endinput +#endif +#define _sdktools_voice_included + +/** + * @section voice flags. + */ +#define VOICE_NORMAL 0 /**< Allow the client to listen and speak normally. */ +#define VOICE_MUTED 1 /**< Mutes the client from speaking to everyone. */ +#define VOICE_SPEAKALL 2 /**< Allow the client to speak to everyone. */ +#define VOICE_LISTENALL 4 /**< Allow the client to listen to everyone. */ +#define VOICE_TEAM 8 /**< Allow the client to always speak to team, even when dead. */ +#define VOICE_LISTENTEAM 16 /**< Allow the client to always hear teammates, including dead ones. */ + +#define LISTEN_DEFAULT 0 /**< Default action (flags or game) is taken */ +#define LISTEN_NO 1 /**< Receiver cannot hear sender */ +#define LISTEN_YES 2 /**< Receiver can hear sender */ + +/** + * @endsection + */ + +/** + * Set the client listening flags. + * + * @param client The client index + * @param flags The voice flags + * @noreturn + */ +native SetClientListeningFlags(client, flags); + +/** + * Retrieve the client current listening flags. + * + * @param client The client index + * @return The current voice flags + */ +native GetClientListeningFlags(client); + +/** + * Set the receiver's ability to listen to the sender. + * + * @param receiver The listener client index. + * @param sender The sender client index. + * @param control A LISTEN_ constant describing the voice relationship. + * @noreturn + * @error If either client index is invalid or not connected. + */ +native bool:SetClientListening(receiver, sender, control); + +/** + * Retrieves if a receiver can listen to the sender. + * + * @param receiver The listener client index. + * @param sender The sender client index. + * @return A LISTEN_ constant describing the voice relationship. + * @error If either client index is invalid or not connected. + */ +native bool:GetClientListening(receiver, sender); diff --git a/src/include/sorting.inc b/src/include/sorting.inc new file mode 100644 index 0000000..fe21511 --- /dev/null +++ b/src/include/sorting.inc @@ -0,0 +1,176 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sorting.inc 1923 2008-03-03 06:28:41Z pred $ + */ + + +#if defined _sorting_included + #endinput +#endif +#define _sorting_included + +/** + * Contains sorting orders. + */ +enum SortOrder +{ + Sort_Ascending = 0, /**< Ascending order */ + Sort_Descending = 1, /**< Descending order */ + Sort_Random = 2 /**< Random order */ +}; + +/** + * Data types for ADT Array Sorts + */ +enum SortType +{ + Sort_Integer = 0, + Sort_Float, + Sort_String, +}; + +/** + * Sorts an array of integers. + * + * @param array Array of integers to sort in-place. + * @param array_size Size of the array. + * @param order Sorting order to use. + * @noreturn + */ +native SortIntegers(array[], array_size, SortOrder:order = Sort_Ascending); + +/** + * Sorts an array of float point numbers. + * + * @param array Array of floating point numbers to sort in-place. + * @param array_size Size of the array. + * @param order Sorting order to use. + * @noreturn + */ +native SortFloats(Float:array[], array_size, SortOrder:order = Sort_Ascending); + +/** + * Sorts an array of strings. + * + * @param array Array of strings to sort in-place. + * @param array_size Size of the array. + * @param order Sorting order to use. + * @noreturn + */ +native SortStrings(String:array[][], num_strings, SortOrder:order = Sort_Ascending); + +/** + * Sort comparison function for 1D array elements. + * @note You may need to use explicit tags in order to use data properly. + * + * @param elem1 First element to compare. + * @param elem2 Second element to compare. + * @param array Array that is being sorted (order is undefined). + * @param hndl Handle optionally passed in while sorting. + * @return -1 if first should go before second + * 0 if first is equal to second + * 1 if first should go after second + */ +functag SortFunc1D public(elem1, elem2, const array[], Handle:hndl); + +/** + * Sorts a custom 1D array. You must pass in a comparison function. + * + * @param array Array to sort. + * @param array_size Size of the array to sort. + * @param sortfunc Sort function. + * @param hndl Optional Handle to pass through the comparison calls. + * @noreturn + */ +native SortCustom1D(array[], array_size, SortFunc1D:sortfunc, Handle:hndl=INVALID_HANDLE); + +/** + * Sort comparison function for 2D array elements (sub-arrays). + * @note You may need to use explicit tags in order to use data properly. + * + * @param elem1 First array to compare. + * @param elem2 Second array to compare. + * @param array Array that is being sorted (order is undefined). + * @param hndl Handle optionally passed in while sorting. + * @return -1 if first should go before second + * 0 if first is equal to second + * 1 if first should go after second + */ +funcenum SortFunc2D +{ + public(array[], array[], const array[][], Handle:hndl), + public(String:array[], String:array[], const String:array[][], Handle:hndl), +}; + +/** + * Sorts a custom 2D array. You must pass in a comparison function. + * + * @param array Array to sort. + * @param array_size Size of the major array to sort (first index, outermost). + * @param sortfunc Sort comparison function to use. + * @param hndl Optional Handle to pass through the comparison calls. + * @noreturn + */ +native SortCustom2D(array[][], array_size, SortFunc2D:sortfunc, Handle:hndl=INVALID_HANDLE); + +/** + * Sort an ADT Array. Specify the type as Integer, Float, or String. + * + * @param array Array Handle to sort + * @param order Sort order to use, same as other sorts. + * @param type Data type stored in the ADT Array + * @noreturn + */ +native SortADTArray(Handle:array, SortOrder:order, SortType:type); + +/** + * Sort comparison function for ADT Array elements. Function provides you with + * indexes currently being sorted, use ADT Array functions to retrieve the + * index values and compare. + * + * @param index1 First index to compare. + * @param index2 Second index to compare. + * @param array Array that is being sorted (order is undefined). + * @param hndl Handle optionally passed in while sorting. + * @return -1 if first should go before second + * 0 if first is equal to second + * 1 if first should go after second + */ +functag SortFuncADTArray public(index1, index2, Handle:array, Handle:hndl); + +/** + * Custom sorts an ADT Array. You must pass in a comparison function. + * + * @param array Array Handle to sort + * @param sortfunc Sort comparision function to use + * @param hndl Optional Handle to pass through the comparison calls. + * @noreturn + */ +native SortADTArrayCustom(Handle:array, SortFuncADTArray:sortfunc, Handle:hndl=INVALID_HANDLE); \ No newline at end of file diff --git a/src/include/sourcebans.inc b/src/include/sourcebans.inc new file mode 100644 index 0000000..1a55da0 --- /dev/null +++ b/src/include/sourcebans.inc @@ -0,0 +1,31 @@ +#if defined _sourcebans_included + #endinput +#endif +#define _sourcebans_included + +public SharedPlugin:__pl_sourcebans = +{ + name = "SourceBans", + file = "sourcebans.smx", + required = 0 +}; + + +public __pl_sourcebans_SetNTVOptional() +{ + MarkNativeAsOptional("SBBanPlayer"); +} + + +/********************************************************* + * Ban Player from server + * + * @param client The client index of the admin who is banning the client + * @param target The client index of the player to ban + * @param time The time to ban the player for (in minutes, 0 = permanent) + * @param reason The reason to ban the player from the server + * @noreturn + *********************************************************/ +native SBBanPlayer(client, target, time, String:reason[]); + +//Yarr! diff --git a/src/include/sourcemod.inc b/src/include/sourcemod.inc new file mode 100644 index 0000000..7a55072 --- /dev/null +++ b/src/include/sourcemod.inc @@ -0,0 +1,554 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: sourcemod.inc 2212 2008-05-29 03:55:31Z dvander $ + */ + +#if defined _sourcemod_included + #endinput +#endif +#define _sourcemod_included + +/** + * Plugin public information. + */ +struct Plugin +{ + const String:name[], /**< Plugin Name */ + const String:description[], /**< Plugin Description */ + const String:author[], /**< Plugin Author */ + const String:version[], /**< Plugin Version */ + const String:url[], /**< Plugin URL */ +}; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Declare this as a struct in your plugin to expose its information. + * Example: + * + * public Plugin:myinfo = + * { + * name = "My Plugin", + * //etc + * }; + */ +public Plugin:myinfo; + +/** + * Called when the plugin is fully initialized and all known external references + * are resolved. This is only called once in the lifetime of the plugin, and is + * paired with OnPluginEnd(). + * + * If any run-time error is thrown during this callback, the plugin will be marked + * as failed. + * + * It is not necessary to close any handles or remove hooks in this function. + * SourceMod guarantees that plugin shutdown automatically and correctly releases + * all resources. + * + * @noreturn + */ +forward OnPluginStart(); + +/** + * Called before OnPluginStart, in case the plugin wants to check for load failure. + * This is called even if the plugin type is "private." Any natives from modules are + * not available at this point. Thus, this forward should only be used for explicit + * pre-emptive things, such as adding dynamic natives, or setting certain types of load filters. + * + * @note It is not safe to call externally resolved natives until OnPluginStart(). + * @note Any sort of RTE in this function will cause the plugin to fail loading. + * @note You MUST remember to return SOMETHING here. The act of not returning is + * equivalent to returning 0 (false). If you forgot to return, it will + * cause your plugin to not load with a very strange error message. + * + * @param myself Handle to the plugin. + * @param late Whether or not the plugin was loaded "late" (after map load). + * @param error Error message buffer in case load failed. + * @param err_max Maximum number of characters for error message buffer. + * @return True if load success, false otherwise. + */ +forward bool:AskPluginLoad(Handle:myself, bool:late, String:error[], err_max); + +/** + * Called when the plugin is about to be unloaded. + * + * @noreturn + */ +forward OnPluginEnd(); + +/** + * Called when the plugin's pause status is changing. + * + * @param pause True if the plugin is being paused, false otherwise. + * @noreturn + */ +forward OnPluginPauseChange(bool:pause); + +/** + * Called before every server frame. Note that you should avoid + * doing expensive computations here, and you should declare large + * local arrays using 'decl' instead of 'new'. + */ +forward OnGameFrame(); + +/** + * Called when the map is loaded. + * + * @note This used to be OnServerLoad(), which is now deprecated. + * Plugins still using the old forward will work. + */ +forward OnMapStart(); + +/** + * Called right before a map ends. + */ +forward OnMapEnd(); + +/** + * Called when the map has loaded, servercfgfile (server.cfg) has been + * executed, and all plugin configs are done executing. This is the best + * place to initialize plugin functions which are based on cvar data. + * + * @note This will always be called once and only once per map. It will be + * called after OnMapStart(). + * + * @noreturn + */ +forward OnConfigsExecuted(); + +/** + * This is called once, right after OnMapStart() but any time before + * OnConfigsExecuted(). It is called after the "exec sourcemod.cfg" + * command and all AutoExecConfig() exec commands have been added to + * the ServerCommand() buffer. + * + * If you need to load per-map settings that override default values, + * adding commands to the ServerCommand() buffer here will guarantee + * that they're set before OnConfigsExecuted(). + * + * Unlike OnMapStart() and OnConfigsExecuted(), this is not called on + * late loads that occur after OnMapStart(). + * + * @noreturn + */ +forward OnAutoConfigsBuffered(); + +/** + * @deprecated Use OnConfigsExecuted() instead. + */ +#pragma deprecated Use OnConfigsExecuted() instead +forward OnServerCfg(); + +/** + * Called after all plugins have been loaded. This is called once for + * every plugin. If a plugin late loads, it will be called immediately + * after OnPluginStart(). + */ +forward OnAllPluginsLoaded(); + +/** + * Returns the calling plugin's Handle. + * + * @return Handle of the calling plugin. + */ +native Handle:GetMyHandle(); + +/** + * Returns an iterator that can be used to search through plugins. + * + * @return Handle to iterate with. Must be closed via + * CloseHandle(). + * @error Invalid Handle. + */ +native Handle:GetPluginIterator(); + +/** + * Returns whether there are more plugins available in the iterator. + * + * @param iter Handle to the plugin iterator. + * @return True on more plugins, false otherwise. + * @error Invalid Handle. + */ +native bool:MorePlugins(Handle:iter); + +/** + * Returns the current plugin in the iterator and advances the iterator. + * + * @param iter Handle to the plugin iterator. + * @return Current plugin the iterator is at, before + * the iterator is advanced. + * @error Invalid Handle. + */ +native Handle:ReadPlugin(Handle:iter); + +/** + * Returns a plugin's status. + * + * @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin). + * @return Status code for the plugin. + * @error Invalid Handle. + */ +native PluginStatus:GetPluginStatus(Handle:plugin); + +/** + * Retrieves a plugin's file name relative to the plugins folder. + * + * @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin). + * @param buffer Buffer to the store the file name. + * @param maxlength Maximum length of the name buffer. + * @noreturn + * @error Invalid Handle. + */ +native GetPluginFilename(Handle:plugin, String:buffer[], maxlength); + +/** + * Retrieves whether or not a plugin is being debugged. + * + * @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin). + * @return True if being debugged, false otherwise. + * @error Invalid Handle. + */ +native bool:IsPluginDebugging(Handle:hndl); + +/** + * Retrieves a plugin's public info. + * + * @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin). + * @param info Plugin info property to retrieve. + * @param buffer Buffer to store info in. + * @param maxlength Maximum length of buffer. + * @return True on success, false if property is not available. + * @error Invalid Handle. + */ +native bool:GetPluginInfo(Handle:plugin, PluginInfo:info, String:buffer[], maxlength); + +/** + * Finds a plugin by its order in the list from the "plugins list" server + * "sm" command. You should not use this function to loop through all plugins, + * use the iterator instead. Looping through all plugins using this native + * is O(n^2), whereas using the iterator is O(n). + * + * @param order_num Number of the plugin as it appears in "sm plugins list". + * @return Plugin Handle on success, INVALID_HANDLE if no plugin + * matches the given number. + */ +native Handle:FindPluginByNumber(order_num); + +/** + * Causes the plugin to enter a failed state. An error will be thrown and + * the plugin will be paused until it is unloaded or reloaded. + * + * For backwards compatibility, if no extra arguments are passed, no + * formatting is applied. If one or more additional arguments is passed, + * the string is formatted using Format(). If any errors are encountered + * during formatting, both the format specifier string and an additional + * error message are written. + * + * This function does not return, and no further code in the plugin is + * executed. + * + * @param string Format specifier string. + * @param ... Formatting arguments. + * @noreturn + * @error Always throws SP_ERROR_ABORT. + */ +native SetFailState(const String:string[], any:...); + +/** + * Aborts the current callback and throws an error. This function + * does not return in that no code is executed following it. + * + * @param format String format. + * @param ... Format arguments. + * @noreturn + * @error Always! + */ +native ThrowError(const String:fmt[], any:...); + +/** + * Gets the system time as a unix timestamp. + * + * @param bigStamp Optional array to store the 64bit timestamp in. + * @return 32bit timestamp (number of seconds since unix epoch). + */ +native GetTime(bigStamp[2]={0,0}); + +/** + * Produces a date and/or time string value for a timestamp. + * + * See this URL for valid parameters: + * http://cplusplus.com/reference/clibrary/ctime/strftime.html + * + * @param buffer Destination string buffer. + * @param maxlength Maximum length of output string buffer. + * @param format Formatting rules (passing NULL_STRING will use the rules defined in sm_datetime_format). + * @param stamp Optional time stamp. + * @noreturn + * @error Buffer too small or invalid time format. + */ +native FormatTime(String:buffer[], maxlength, const String:format[], stamp=-1); + +/** + * Loads a game config file. + * + * @param file File to load. The path must be relative to the 'gamedata' folder under the config folder + * and the extension should be omitted. + * @return A handle to the game config file or INVALID_HANDLE in failure. + */ +native Handle:LoadGameConfigFile(const String:file[]); + +/** + * Returns an offset value. + * + * @param gc Game config handle. + * @param key Key to retrieve from the offset section. + * @return An offset, or -1 on failure. + */ +native GameConfGetOffset(Handle:gc, const String:key[]); + +/** + * Gets the value of a key from the "Keys" section. + * + * @param gc Game config handle. + * @param key Key to retrieve from the Keys section. + * @param buffer Destination string buffer. + * @param maxlen Maximum length of output string buffer. + * @return True if key existed, false otherwise. + */ +native bool:GameConfGetKeyValue(Handle:gc, const String:key[], String:buffer[], maxlen); + +/** + * Returns the operating system's "tick count," which is a number of + * milliseconds since the operating system loaded. This can be used + * for basic benchmarks. + * + * @return Tick count in milliseconds. + */ +native GetSysTickCount(); + +/** + * Specifies that the given config file should be executed after plugin load. + * OnConfigsExecuted() will not be called until the config file has executed, + * but it will be called if the execution fails. + * + * @param autoCreate If true, and the config file does not exist, such a config + * file will be automatically created and populated with + * information from the plugin's registered cvars. + * @param name Name of the config file, excluding the .cfg extension. + * If empty, is assumed. + * @param folder Folder under cfg/ to use. By default this is "sourcemod." + * @noreturn + */ +native AutoExecConfig(bool:autoCreate=true, const String:name[]="", const String:folder[]="sourcemod"); + +/** + * Sets a native as optional, such that if it is unloaded, removed, + * or otherwise non-existent, the plugin will still work. Calling + * removed natives results in a run-time error. + * + * @param name Native name. + * @noreturn + */ +native MarkNativeAsOptional(const String:name[]); + +/** + * Registers a library name for identifying as a dependency to + * other plugins. + * + * @param name Library name. + * @noreturn + */ +native RegPluginLibrary(const String:name[]); + +/** + * Returns whether a library exists. This function should be considered + * expensive; it should only be called on plugin to determine availability + * of resources. Use OnLibraryAdded()/OnLibraryRemoved() to detect changes + * in optional resources. + * + * @param name Library name of a plugin or extension. + * @return True if exists, false otherwise. + */ +native bool:LibraryExists(const String:name[]); + +/** + * Returns the status of an extension, by filename. + * + * @param name Extension name (like "sdktools.ext"). + * @param error Optional error message buffer. + * @param maxlength Length of optional error message buffer. + * @return -2 if the extension was not found. + * -1 if the extension was found but failed to load. + * 0 if the extension loaded but reported an error. + * 1 if the extension is running without error. + */ +native GetExtensionFileStatus(const String:name[], String:error[]="", maxlength=0); + +/** + * Called after a library is added that the current plugin references + * optionally. A library is either a plugin name or extension name, as + * exposed via its include file. + * + * @param name Library name. + */ +forward OnLibraryAdded(const String:name[]); + +/** + * Called right before a library is removed that the current plugin references + * optionally. A library is either a plugin name or extension name, as + * exposed via its include file. + * + * @param name Library name. + */ +forward OnLibraryRemoved(const String:name[]); + +#define MAPLIST_FLAG_MAPSFOLDER (1<<0) /**< On failure, use all maps in the maps folder. */ +#define MAPLIST_FLAG_CLEARARRAY (1<<1) /**< If an input array is specified, clear it before adding. */ +#define MAPLIST_FLAG_NO_DEFAULT (1<<2) /**< Do not read "default" or "mapcyclefile" on failure. */ + +/** + * Loads a map list to an ADT Array. + * + * A map list is a list of maps from a file. SourceMod allows easy configuration of + * maplists through addons/sourcemod/configs/maplists.cfg. Each entry is given a + * name and a file (for example, "rtv" => "rtv.cfg"), or a name and a redirection + * (for example, "rtv" => "default"). This native will read a map list entry, + * cache the file, and return the list of maps it holds. + * + * Serial change numbers are used to identify if a map list has changed. Thus, if + * you pass a serial change number and it's equal to what SourceMod currently knows + * about the map list, then SourceMod won't reparse the file. + * + * If the maps end up being read from the maps folder (MAPLIST_FLAG_MAPSFOLDER), they + * are automatically sorted in alphabetical, ascending order. + * + * Arrays created by this function are temporary and must be freed via CloseHandle(). + * Modifying arrays created by this function will not affect future return values or + * or the contents of arrays returned to other plugins. + * + * @param array Array to store the map list. If INVALID_HANDLE, a new blank + * array will be created. The blocksize should be at least 16; + * otherwise results may be truncated. Items are added to the array + * as strings. The array is never checked for duplicates, and it is + * not read beforehand. Only the serial number is used to detect + * changes. + * @param serial Serial number to identify last known map list change. If -1, the + * the value will not be checked. If the map list has since changed, + * the serial is updated (even if -1 was passed). If there is an error + * finding a valid maplist, then the serial is set to -1. + * @param str Config name, or "default" for the default map list. Config names + * should be somewhat descriptive. For example, the admin menu uses + * a config name of "admin menu". The list names can be configured + * by users in addons/sourcemod/configs/maplists.cfg. + * @param flags MAPLIST_FLAG flags. + * @return On failure: + * INVALID_HANDLE is returned, the serial is set to -1, and the input + * array (if any) is left unchanged. + * On no change: + INVALID_HANDLE is returned, the serial is unchanged, and the input + array (if any) is left unchanged. + * On success: + * A valid array Handle is returned, containing at least one map string. + * If an array was passed, the return value is equal to the passed Array + * Handle. If the passed array was not cleared, it will have grown by at + * least one item. The serial number is updated to a positive number. + * @error Invalid array Handle that is not INVALID_HANDLE. + */ +native Handle:ReadMapList(Handle:array=INVALID_HANDLE, + &serial=-1, + const String:str[]="default", + flags=MAPLIST_FLAG_CLEARARRAY); + +/** + * Makes a compatibility binding for map lists. For example, if a function previously used + * "clam.cfg" for map lists, this function will insert a "fake" binding to "clam.cfg" that + * will be overridden if it's in the maplists.cfg file. + * + * @param name Configuration name that would be used with ReadMapList(). + * @param file Default file to use. + * @noreturn + */ +native SetMapListCompatBind(const String:name[], const String:file[]); + +/** + * Called when a client has sent chat text. This must return either true or + * false to indicate that a client is or is not spamming the server. + * + * The return value is a hint only. Core or another plugin may decide + * otherwise. + * + * @param client Client index. The server (0) will never be passed. + * @return True if client is spamming the server, false otherwise. + */ +forward bool:OnClientFloodCheck(client); + +/** + * Called after a client's flood check has been computed. This can be used + * by antiflood algorithms to decay/increase flooding weights. + * + * Since the result from "OnClientFloodCheck" isn't guaranteed to be the + * final result, it is generally a good idea to use this to play with other + * algorithms nicely. + * + * @param client Client index. The server (0) will never be passed. + * @param blocked True if client flooded last "say", false otherwise. + * @noreturn + */ +forward OnClientFloodResult(client, bool:blocked); + +#include +#include +#include + diff --git a/src/include/string.inc b/src/include/string.inc new file mode 100644 index 0000000..8d57b3f --- /dev/null +++ b/src/include/string.inc @@ -0,0 +1,557 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: string.inc 2206 2008-05-29 03:46:42Z dvander $ + */ + +#if defined _string_included + #endinput +#endif +#define _string_included + +/** + * @global Unless otherwise noted, all string functions which take in a + * writable buffer and maximum length should have the null terminator INCLUDED + * in the length. This means that this is valid: + * strcopy(string, sizeof(string), ...) + */ + +/** + * Calculates the length of a string. + * + * @param str String to check. + * @return Number of valid character bytes in the string. + */ +native strlen(const String:str[]); + +/** + * Tests whether a string is found inside another string. + * + * @param str String to search in. + * @param substr Substring to find inside the original string. + * @param caseSensitive If true (default), search is case sensitive. + * If false, search is case insensitive. + * @return -1 on failure (no match found). Any other value + * indicates a position in the string where the match starts. + */ +native StrContains(const String:str[], const String:substr[], bool:caseSensitive=true); + +/** + * Compares two strings lexographically. + * + * @param str1 First string (left). + * @param str2 Second string (right). + * @param caseSensitive If true (default), comparison is case sensitive. + * If false, comparison is case insensitive. + * @return -1 if str1 < str2 + * 0 if str1 == str2 + * 1 if str1 > str2 + */ +native strcmp(const String:str1[], const String:str2[], bool:caseSensitive=true); + +/** + * Compares two strings parts lexographically. + * + * @param str1 First string (left). + * @param str2 Second string (right). + * @param num Number of characters to compare. + * @param caseSensitive If true (default), comparison is case sensitive. + * If false, comparison is case insensitive. + * @return -1 if str1 < str2 + * 0 if str1 == str2 + * 1 if str1 > str2 + */ +native strncmp(const String:str1[], const String:str2[], num, bool:caseSensitive=true); + +/** + * Backwards compatible stock - StrCompare is now strcmp + * @deprecated Renamed to strcmp + */ +#pragma deprecated Use strcmp() instead +stock StrCompare(const String:str1[], const String:str2[], bool:caseSensitive=true) +{ + return strcmp(str1, str2, caseSensitive); +} + +/** + * Returns whether two strings are equal. + * + * @param str1 First string (left). + * @param str2 Second string (right). + * @param caseSensitive If true (default), comparison is case sensitive. + * If false, comparison is case insensitive. + * @return True if equal, false otherwise. + */ +stock bool:StrEqual(const String:str1[], const String:str2[], bool:caseSensitive=true) +{ + return (strcmp(str1, str2, caseSensitive) == 0); +} + +/** + * Copies one string to another string. + * @note If the destination buffer is too small to hold the source string, the + * destination will be truncated. + * + * @param dest Destination string buffer to copy to. + * @param destlen Destination buffer length (includes null terminator). + * @param source Source string buffer to copy from. + * @return Number of cells written. + */ +native strcopy(String:dest[], destLen, const String:source[]); + +/** + * Backwards compatibility stock - use strcopy + * @deprecated Renamed to strcopy + */ +#pragma deprecated Use strcopy() instead +stock StrCopy(String:dest[], destLen, const String:source[]) +{ + return strcopy(dest, destLen, source); +} + +/** + * Formats a string according to the SourceMod format rules (see documentation). + * + * @param buffer Destination string buffer. + * @param maxlength Maximum length of output string buffer. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @return Number of cells written. + */ +native Format(String:buffer[], maxlength, const String:format[], any:...); + +/** + * Formats a string according to the SourceMod format rules (see documentation). + * @note This is the same as Format(), except none of the input buffers can + * overlap the same memory as the output buffer. Since this security + * check is removed, it is slightly faster. + * + * @param buffer Destination string buffer. + * @param maxlength Maximum length of output string buffer. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @return Number of cells written. + */ +native FormatEx(String:buffer[], maxlength, const String:format[], any:...); + +/** + * Formats a string according to the SourceMod format rules (see documentation). + * @note This is the same as Format(), except it grabs parameters from a + * parent parameter stack, rather than a local. This is useful for + * implementing your own variable argument functions. + * + * @param buffer Destination string buffer. + * @param maxlength Maximum length of output string buffer. + * @param format Formatting rules. + * @param varpos Argument number which contains the '...' symbol. + * Note: Arguments start at 1. + * @return Number of bytes written. + */ +native VFormat(String:buffer[], maxlength, const String:format[], varpos); + +/** + * Converts a string to an integer. + * + * @param str String to convert. + * @param nBase Numerical base to use. 10 is default. + * @return Integer conversion of string, or 0 on failure. + */ +native StringToInt(const String:str[], nBase=10); + +/** + * Converts a string to an integer with some more options. + * + * @param str String to convert. + * @param result Variable to store the result in. + * @param nBase Numerical base to use. 10 is default. + * @return Number of characters consumed. + */ +native StringToIntEx(const String:str[], &result, nBase=10); + +/** + * Converts an integer to a string. + * + * @param num Integer to convert. + * @param str Buffer to store string in. + * @param maxlength Maximum length of string buffer. + * @return Number of cells written to buffer. + */ +native IntToString(num, String:str[], maxlength); + +/** + * Converts a string to a floating point number. + * + * @param str String to convert to a foat. + * @return Floating point result, or 0.0 on error. + */ +native Float:StringToFloat(const String:str[]); + +/** + * Converts a string to a floating point number with some more options. + * + * @param str String to convert to a foat. + * @param result Variable to store result in. + * @return Number of characters consumed. + */ +native StringToFloatEx(const String:str[], &Float:result); + +/** + * Converts a floating point number to a string. + * + * @param num Floating point number to convert. + * @param str Buffer to store string in. + * @param maxlength Maximum length of string buffer. + * @return Number of cells written to buffer. + */ +native FloatToString(Float:num, String:str[], maxlength); + +/** + * Finds the first "argument" in a string; either a set of space + * terminated characters, or a fully quoted string. After the + * argument is found, whitespace is read until the next portion + * of the string is reached. If nothing remains, -1 is returned. + * Otherwise, the index to the first character is returned. + * + * @param source Source input string. + * @param arg Stores argument read from string. + * @param argLen Maximum length of argument buffer. + * @return Index to next piece of string, or -1 if none. + */ +native BreakString(const String:source[], String:arg[], argLen); + +/** + * Backwards compatibility stock - use BreakString + * @deprecated Renamed to BreakString. + */ +#pragma deprecated Use BreakString() instead +stock StrBreak(const String:source[], String:arg[], argLen) +{ + return BreakString(source, arg, argLen); +} + +/** + * Removes whitespace characters from the beginning and end of a string. + * + * @param str The string to trim. + * @return Number of bytes written (UTF-8 safe). + */ +native TrimString(String:str[]); + +/** + * Returns text in a string up until a certain character sequence is reached. + * + * @param source Source input string. + * @param split A string which specifies a search point to break at. + * @param part Buffer to store string part. + * @param partLen Maximum length of the string part buffer. + * @return -1 if no match was found; otherwise, an index into source + * marking the first index after the searched text. The + * index is always relative to the start of the input string. + */ +native SplitString(const String:source[], const String:split[], String:part[], partLen); + +/** + * Given a string, replaces all occurrences of a search string with a + * replacement string. + * + * @param text String to perform search and replacements on. + * @param maxlength Maximum length of the string buffer. + * @param search String to search for. + * @param replace String to replace the search string with. + * @return Number of replacements that were performed. + */ +native ReplaceString(String:text[], maxlength, const String:search[], const String:replace[]); + +/** + * Given a string, replaces the first occurrence of a search string with a + * replacement string. + * + * @param text String to perform search and replacements on. + * @param maxlength Maximum length of the string buffer. + * @param search String to search for. + * @param replace String to replace the search string with. + * @param searchLen If higher than -1, its value will be used instead of + * a strlen() call on the search parameter. + * @param replaceLen If higher than -1, its value will be used instead of + * a strlen() call on the replace parameter. + * @return Index into the buffer (relative to the start) from where + * the last replacement ended, or -1 if no replacements were + * made. + */ +native ReplaceStringEx(String:text[], maxlength, const String:search[], const String:replace[], searchLen=-1, replaceLen=-1); + +/** + * Returns the number of bytes a character is using. This is + * for multi-byte characters (UTF-8). For normal ASCII characters, + * this will return 1. + * + * @param source Source input string. + * @return Number of bytes the current character uses. + */ +native GetCharBytes(const String:source[]); + +/** + * Returns whether a character is an ASCII alphabet character. + * + * @note Multi-byte characters will always return false. + * + * @param char Character to test. + * @return True if character is alphabetical, otherwise false. + */ +native bool:IsCharAlpha(chr); + +/** + * Returns whether a character is numeric. + * + * @note Multi-byte characters will always return false. + * + * @param char Character to test. + * @return True if character is numeric, otherwise false. + */ +native bool:IsCharNumeric(chr); + +/** + * Returns whether a character is whitespace. + * + * @note Multi-byte characters will always return false. + * + * @param char Character to test. + * @return True if character is whitespace, otherwise false. + */ +native bool:IsCharSpace(chr); + +/** + * Returns if a character is multi-byte or not. + * + * @param char Character to test. + * @return 0 for a normal 7-bit ASCII character, + * otherwise number of bytes in multi-byte character. + */ +native IsCharMB(chr); + +/** + * Returns whether an alphabetic character is uppercase. + * + * @note Multi-byte characters will always return false. + * + * @param char Character to test. + * @return True if character is uppercase, otherwise false. + */ +native bool:IsCharUpper(chr); + +/** + * Returns whether an alphabetic character is lowercase. + * + * @note Multi-byte characters will always return false. + * + * @param char Character to test. + * @return True if character is lowercase, otherwise false. + */ +native bool:IsCharLower(chr); + +/** + * Strips a quote pair off a string if it exists. That is, the following + * replace rule is applied once: $"(.*)"^ -> $\1^ + * + * Note that the leading and trailing quotes will only be removed if both + * exist. Otherwise, the string is left unmodified. This function should + * be considered O(k) (all characters get shifted down). + * + * @param text String to modify (in place). + * @return True if string was modified, false if there was no + * set of quotes. + */ +native bool:StripQuotes(String:text[]); + +/** + * Returns an uppercase character to a lowercase character. + * + * @param chr Characer to convert. + * @return Lowercase character on success, + * no change on failure. + */ +stock CharToUpper(chr) +{ + if (IsCharLower(chr)) + { + return (chr & ~(1<<5)); + } + return chr; +} + +/** + * Returns a lowercase character to an uppercase character. + * + * @param chr Characer to convert. + * @return Uppercase character on success, + * no change on failure. + */ +stock CharToLower(chr) +{ + if (IsCharUpper(chr)) + { + return (chr | (1<<5)); + } + return chr; +} + +/** + * Finds the first occurrence of a character in a string. + * + * @param str String. + * @param c Character to search for. + * @param reverse False (default) to search forward, true to search + * backward. + * @return The index of the first occurrence of the character + * in the string, or -1 if the character was not found. + */ +stock FindCharInString(const String:str[], c, bool:reverse = false) +{ + new i, len + + len = strlen(str); + + if (!reverse) + { + for (i = 0; i < len; i++) + { + if (str[i] == c) + { + return i; + } + } + } + else + { + for (i = len - 1; i >= 0; i--) + { + if (str[i] == c) + { + return i; + } + } + } + + return -1; +} + +/** + * Concatenates one string onto another. + * + * @param buffer String to append to. + * @param maxlength Maximum length of entire buffer. + * @param source Source string to concatenate. + * @return Number of bytes written. + */ +stock StrCat(String:buffer[], maxlength, const String:source[]) +{ + new len = strlen(buffer); + if (len >= maxlength) + { + return 0; + } + + return Format(buffer[len], maxlength-len, "%s", source); +} + +/** + * Breaks a string into pieces and stores each piece into an array of buffers. + * + * @param text The string to split. + * @param split The string to use as a split delimiter. + * @param buffers An array of string buffers (2D array). + * @param maxStrings Number of string buffers (first dimension size). + * @param maxStringLength Maximum length of each string buffer. + * @return Number of strings retrieved. + */ +stock ExplodeString(const String:text[], const String:split[], String:buffers[][], maxStrings, maxStringLength) +{ + new reloc_idx, idx, total; + + if (maxStrings < 1 || split[0] == '\0') + { + return 0; + } + + while ((idx = SplitString(text[reloc_idx], split, buffers[total], maxStringLength)) != -1) + { + reloc_idx += idx; + if (text[reloc_idx] == '\0') + { + break; + } + if (++total >= maxStrings) + { + return total; + } + } + + if (text[reloc_idx] != '\0' && total <= maxStrings - 1) + { + strcopy(buffers[total++], maxStringLength, text[reloc_idx]); + } + + return total; +} + +/** + * Joins an array of strings into one string, with a "join" string inserted in + * between each given string. This function complements ExplodeString. + * + * @param strings An array of strings. + * @param numStrings Number of strings in the array. + * @param join The join string to insert between each string. + * @param buffer Output buffer to write the joined string to. + * @param maxLength Maximum length of the output buffer. + * @return Number of bytes written to the output buffer. + */ +stock ImplodeStrings(const String:strings[][], numStrings, const String:join[], String:buffer[], maxLength) +{ + new total, length, part_length; + new join_length = strlen(join); + for (new i=0; i. + * + * 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 . + * + * Version: $Id: textparse.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _textparse_included + #endinput +#endif +#define _textparse_included + + +/******************************** + * Everything below describes the SMC Parse, or "SourceMod Configuration" format. + * This parser is entirely event based. You must hook events to receive data. + * The file format itself is nearly identical to Valve's KeyValues format. + ********************************/ + +/** + * Parse result directive. + */ +enum SMCResult +{ + SMCParse_Continue, /**< Continue parsing */ + SMCParse_Halt, /**< Stop parsing here */ + SMCParse_HaltFail /**< Stop parsing and return failure */ +}; + +/** + * Parse error codes. + */ +enum SMCError +{ + SMCError_Okay = 0, /**< No error */ + SMCError_StreamOpen, /**< Stream failed to open */ + SMCError_StreamError, /**< The stream died... somehow */ + SMCError_Custom, /**< A custom handler threw an error */ + SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ + SMCError_InvalidSection2, /**< A section was declared without any header */ + SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ + SMCError_InvalidSection4, /**< A section ending has no matching beginning */ + SMCError_InvalidSection5, /**< A section beginning has no matching ending */ + SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ + SMCError_TokenOverflow, /**< The token buffer overflowed */ + SMCError_InvalidProperty1, /**< A property was declared outside of any section */ +}; + +/** + * Creates a new SMC file format parser. This is used to set parse hooks. + * + * @return A new Handle to an SMC Parse structure. + */ +native Handle:SMC_CreateParser(); + +/** + * Parses an SMC file. + * + * @param smc A Handle to an SMC Parse structure. + * @param file A string containing the file path. + * @param line An optional by reference cell to store the last line number read. + * @param col An optional by reference cell to store the last column number read. + * @return An SMCParseError result. + * @error Invalid or corrupt Handle. + */ +native SMCError:SMC_ParseFile(Handle:smc, const String:file[], &line=0, &col=0); + +/** + * Gets an error string for an SMCError code. + * @note SMCError_Okay returns false. + * @note SMCError_Custom (which is thrown on SMCParse_HaltFail) returns false. + * + * @param error The SMCParseError code. + * @param buffer A string buffer for the error (contents undefined on failure). + * @param buf_max The maximum size of the buffer. + * @return True on success, false otherwise. + */ +native bool:SMC_GetErrorString(SMCError:error, String:buffer[], buf_max); + +/** + * Called when parsing is started. + * + * @param smc The SMC Parse Handle. + * @noreturn + */ +functag SMC_ParseStart public(Handle:smc); + +/** + * Sets the SMC_ParseStart function of a parse Handle. + * + * @param smc Handle to an SMC Parse. + * @param func SMC_ParseStart function. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SMC_SetParseStart(Handle:smc, SMC_ParseStart:func); + +/** + * Called when parsing is halted. + * + * @param smc The SMC Parse Handle. + * @param halted True if abnormally halted, false otherwise. + * @param failed True if parsing failed, false otherwise. + * @noreturn + */ +functag SMC_ParseEnd public(Handle:smc, bool:halted, bool:failed); + +/** + * Sets the SMC_ParseEnd of a parse handle. + * + * @param smc Handle to an SMC Parse. + * @param func SMC_ParseEnd function. + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SMC_SetParseEnd(Handle:smc, SMC_ParseEnd:func); + +/** + * Called when the parser is entering a new section or sub-section. + * @note Enclosing quotes are always stripped. + * + * @param smc The SMC Parse Handle. + * @param name String containing section name. + * @param opt_quotes True if the section name was quote-enclosed in the file. + * @return An SMCResult action to take. + */ +functag SMC_NewSection SMCResult:public(Handle:smc, const String:name[], bool:opt_quotes); + +/** + * Called when the parser finds a new key/value pair. + * @note Enclosing quotes are always stripped. + * + * @param smc The SMC Parse Handle. + * @param key String containing key name. + * @param value String containing value name. + * @param key_quotes Whether or not the key was enclosed in quotes. + * @param value_quotes Whether or not the value was enclosed in quotes. + * @return An SMCResult action to take. + */ +functag SMC_KeyValue SMCResult:public(Handle:smc, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes); + +/** + * Called when the parser finds the end of the current section. + * + * @param smc The SMC Parse Handle. + * @return An SMCResult action to take. + */ +functag SMC_EndSection SMCResult:public(Handle:smc); + +/** + * Sets the three main reader functions. + * + * @param smc An SMC parse Handle. + * @param ns An SMC_NewSection function pointer. + * @param kv An SMC_KeyValue function pointer. + * @param es An SMC_EndSection function pointer. + * @noreturn + */ +native SMC_SetReaders(Handle:smc, SMC_NewSection:ns, SMC_KeyValue:kv, SMC_EndSection:es); + +/** + * Called whenever a raw line is read. + * + * @param smc The SMC Parse Handle. + * @param line A string containing the raw line from the file. + * @param lineno The line number it occurs on. + * @return An SMCResult action to take. + */ +functag SMC_RawLine SMCResult:public(Handle:smc, const String:line[], lineno); + +/** + * Sets a raw line reader on an SMC parser Handle. + * + * @param smc Handle to an SMC Parse. + * @param func SMC_RawLine function. + * @noreturn + */ +native SMC_SetRawLine(Handle:smc, SMC_RawLine:func); diff --git a/src/include/tf2.inc b/src/include/tf2.inc new file mode 100644 index 0000000..6b868fa --- /dev/null +++ b/src/include/tf2.inc @@ -0,0 +1,121 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: tf2.inc 2215 2008-05-29 04:06:16Z dvander $ + */ + +#if defined _tf2_included + #endinput +#endif +#define _tf2_included + +enum TFClassType +{ + TFClass_Unknown = 0, + TFClass_Scout, + TFClass_Sniper, + TFClass_Soldier, + TFClass_DemoMan, + TFClass_Medic, + TFClass_Heavy, + TFClass_Pyro, + TFClass_Spy, + TFClass_Engineer +}; + +enum TFTeam +{ + TFTeam_Unassigned = 0, + TFTeam_Spectator = 1, + TFTeam_Red = 2, + TFTeam_Blue = 3 +}; + + +/** + * Respawns a client + * + * @param client Player's index. + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native TF2_RespawnPlayer(client); + +/** + * Disguises a client to the given model and team. Only has an effect on spies. + * + * Note: This only starts the disguise process and a delay occurs before the spy is fully disguised + * + * @param client Player's index. + * @param team Team to disguise the player as (only TFTeam_Red and TFTeam_Blue have an effect) + * @param class TFClassType class to disguise the player as + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native TF2_DisguisePlayer(client, TFTeam:team, TFClassType:class); + +/** + * Removes the current disguise from a client. Only has an effect on spies. + * + * @param client Player's index. + * @noreturn + * @error Invalid client index, client not in game, or no mod support. + */ +native TF2_RemovePlayerDisguise(client); + +/** + * Retrieves the entity index of the CPlayerResource entity + * + * @return The current resource entity index. + */ +native TF2_GetResourceEntity(); + +/** + * Finds the TFClassType for a given class name. + * + * @param classname A classname string such as "sniper" or "demoman" + * @return A TFClassType constant. + */ +native TFClassType:TF2_GetClass(const String:classname[]); + +/** + * Do not edit below this line! + */ +public Extension:__ext_tf2 = +{ + name = "TF2 Tools", + file = "game.tf2.ext", + autoload = 1, +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + diff --git a/src/include/tf2_stocks.inc b/src/include/tf2_stocks.inc new file mode 100644 index 0000000..f659248 --- /dev/null +++ b/src/include/tf2_stocks.inc @@ -0,0 +1,219 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: tf2_stocks.inc 2236 2008-05-31 22:30:57Z damagedsoul $ + */ + +#if defined _tf2_stocks_included + #endinput +#endif +#define _tf2_stocks_included + +#include +#include + +enum TFResourceType +{ + TFResource_Ping, + TFResource_Score, + TFResource_Deaths, + TFResource_TotalScore, + TFResource_Captures, + TFResource_Defenses, + TFResource_Dominations, + TFResource_Revenge, + TFResource_BuildingsDestroyed, + TFResource_Headshots, + TFResource_Backstabs, + TFResource_HealPoints, + TFResource_Invulns, + TFResource_Teleports, + TFResource_ResupplyPoints, + TFResource_KillAssists, + TFResource_MaxHealth, + TFResource_PlayerClass +}; + +static const String:TFResourceNames[TFResourceType][] = +{ + "m_iPing", + "m_iScore", + "m_iDeaths", + "m_iTotalScore", + "m_iCaptures", + "m_iDefenses", + "m_iDominations", + "m_iRevenge", + "m_iBuildingsDestroyed", + "m_iHeadshots", + "m_iBackstabs", + "m_iHealPoints", + "m_iInvulns", + "m_iTeleports", + "m_iResupplyPoints", + "m_iKillAssists", + "m_iMaxHealth", + "m_iPlayerClass" +}; + +/** + * Get's a Clients current class. + * + * @param client Player's index. + * @param class TFClassType to change to. + * @noreturn + * @error Invalid client index. + */ +stock TFClassType:TF2_GetPlayerClass(client) +{ + return TFClassType:GetEntProp(client, Prop_Send, "m_iClass"); +} + +/** + * Set's a Clients class. + * + * Note: If setting player class in a player spawn hook weapons should be set to false. + * + * @param client Player's index. + * @param class TFClassType class symbol. + * @param weapons This paramater is ignored. + * @param persistant If true changes the players desired class so the change stays after death. + * @noreturn + * @error Invalid client index. + */ +stock TF2_SetPlayerClass(client, TFClassType:class, bool:weapons=true, bool:persistant=true) +{ + SetEntProp(client, Prop_Send, "m_iClass", _:class); + + if (persistant) + { + SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:class); + } +} + +/** + * Retrieves client data from the resource entity + * + * @param client Player's index. + * @param type ResourceType constant + * @return Value or -1 on failure. + * @error Invalid client index, client not in game or failed to find resource entity. + */ +stock TF2_GetPlayerResourceData(client, TFResourceType:type) +{ + if (!IsClientConnected(client)) + { + return -1; + } + + new offset = FindSendPropInfo("CTFPlayerResource", TFResourceNames[type]); + + if (offset < 1) + { + return -1; + } + + new entity = TF2_GetResourceEntity(); + + if (entity == -1) + { + return -1; + } + + return GetEntData(entity, offset + (client*4)); +} + +/** + * Sets client data in the resource entity + * + * Note: The game overwrites these values every frame, so changing them will have very little effect. + * + * @param client Player's index. + * @param type ResourceType constant + * @param value Value to set. + * @return Value or -1 on failure. + * @error Invalid client index, client not in game or failed to find resource entity. + */ +stock bool:TF2_SetPlayerResourceData(client, TFResourceType:type, any:value) +{ + if (!IsClientConnected(client)) + { + return false; + } + + new offset = FindSendPropInfo("CTFPlayerResource", TFResourceNames[type]); + + if (offset < 1) + { + return false; + } + + new entity = TF2_GetResourceEntity(); + + if (entity == -1) + { + return false; + } + + SetEntData(entity, offset + (client*4), value); + + return true; +} + +/** + * Removes all weapons from a client's weapon slot + * + * @param client Player's index. + * @param slot Slot index (0-5) + * @noreturn + * @error Invalid client, invalid slot or lack of mod support + */ +stock TF2_RemoveWeaponSlot(client, slot) +{ + new weaponIndex; + while ((weaponIndex = GetPlayerWeaponSlot(client, slot)) != -1) + { + RemovePlayerItem(client, weaponIndex); + RemoveEdict(weaponIndex); + } +} + +/** + * Removes all weapons from a client + * + * @param client Player's index. + * @noreturn + */ +stock TF2_RemoveAllWeapons(client) +{ + for (new i = 0; i <= 5; i++) + { + TF2_RemoveWeaponSlot(client, i); + } +} diff --git a/src/include/timers.inc b/src/include/timers.inc new file mode 100644 index 0000000..1b4ac74 --- /dev/null +++ b/src/include/timers.inc @@ -0,0 +1,208 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: timers.inc 2213 2008-05-29 03:59:00Z dvander $ + */ + +#if defined _timers_included + #endinput +#endif +#define _timers_included + +#include + +#define TIMER_REPEAT (1<<0) /**< Timer will repeat until it returns Plugin_Stop */ +#define TIMER_FLAG_NO_MAPCHANGE (1<<1) /**< Timer will not carry over mapchanges */ +#define TIMER_HNDL_CLOSE (1<<9) /**< Timer will automatically call CloseHandle() on its value when finished */ + +/** + * Any of the following prototypes will work for a timed function. + */ +funcenum Timer +{ + /** + * Called when the timer interval has elapsed. + * + * @param timer Handle to the timer object. + * @param hndl Handle passed when the timer was created. + * @return Plugin_Stop to stop a repeating timer, any other value for + * default behavior. + */ + Action:public(Handle:timer, Handle:hndl), + + /** + * Called when the timer interval has elapsed. + * + * @param timer Handle to the timer object. + * @param value Value passed when the timer was created. + * @return Plugin_Stop to stop a repeating timer, any other value for + * default behavior. + */ + Action:public(Handle:timer, any:value), + + /** + * Called when the timer interval has elapsed. + * + * @param timer Handle to the timer object. + * @return Plugin_Stop to stop a repeating timer, any other value for + * default behavior. + */ + Action:public(Handle:timer), +}; + +/** + * Creates a basic timer. Calling CloseHandle() on a timer will end the timer. + * + * @param interval Interval from the current game time to execute the given function. + * @param func Function to execute once the given interval has elapsed. + * @param value Handle or value to give to the timer function. + * @param flags Flags to set (such as repeatability or auto-Handle closing). + * @return Handle to the timer object. You do not need to call CloseHandle(). + * If the timer could not be created, INVALID_HANDLE will be returned. + */ +native Handle:CreateTimer(Float:interval, Timer:func, any:value=INVALID_HANDLE, flags=0); + +/** + * Kills a timer. Use this instead of CloseHandle() if you need more options. + * + * @param autoClose If autoClose is true, the timer's value will be + * closed as a handle if TIMER_HNDL_CLOSE was not specified. + * @noreturn + */ +native KillTimer(Handle:timer, bool:autoClose=false); + +/** + * Manually triggers a timer so its function will be called. + * + * @param timer Timer Handle to trigger. + * @param reset If reset is true, the elapsed time counter is reset + * so the full interval must pass again. + * @noreturn + */ +native TriggerTimer(Handle:timer, bool:reset=false); + +/** + * Returns the simulated game time. + * + * This time is internally maintained by SourceMod and is based on the game + * tick count and tick rate. Unlike GetGameTime(), it will increment past + * map changes and while no players are connected. Unlike GetEngineTime(), + * it will not increment based on the system clock (i.e. it is still bound + * to the ticking process). + * + * @return Time based on the game tick count. + */ +native Float:GetTickedTime(); + +/** + * Returns an estimate of the time left before the map ends. If the server + * has not processed any frames yet (i.e. no players have joined the map yet), + * then the time left returned will always be infinite. + * + * @param timeleft Variable to store the time, in seconds. If the + * value is less than 0, the time limit is infinite. + * @return True if the operation is supported, false otherwise. + */ +native bool:GetMapTimeLeft(&timeleft); + +/** + * Retrieves the current map time limit. If the server has not processed any + * frames yet (i.e. no players have joined the map yet), then the time limit + * returned will always be 0. + * + * @param time Set to the number of total seconds in the map time + * limit, or 0 if there is no time limit set. + * @return True on success, false if operation is not supported. + */ +native bool:GetMapTimeLimit(&time); + +/** + * Extends the map time limit in a way that will notify all plugins. + * + * @param time Number of seconds to extend map time limit by. + * The number can be negative to decrease the time limit. + * If 0, the map will be set to have no time limit. + * @return True on success, false if operation is not supported. + */ +native bool:ExtendMapTimeLimit(time); + +/** + * Returns the number of seconds in between game server ticks. + * + * Note: A tick, in this context, is a frame. + * + * @return Number of seconds in between ticks. + */ +native Float:GetTickInterval(); + +/** + * Notification that the map's time left has changed via a change in the time + * limit or a change in the game rules (such as mp_restartgame). This is useful + * for plugins trying to create timers based on the time left in the map. + * + * Calling ExtendMapTimeLimit() from here, without proper precaution, will + * cause infinite recursion. + * + * If the operation is not supported, this will never be called. + + * If the server has not yet processed any frames (i.e. no players have joined + * the map yet), then this will be called once the server begins ticking, even + * if there is no time limit set. + */ +forward OnMapTimeLeftChanged(); + +/** + * Returns whether or not the server is processing frames or not. + * + * The server does not process frames until at least one client joins the game. + * Once the first player has in, even if that player, leaves, the server's + * timers and entities will work. + * + * @return True if the server is ticking, false otherwise. + */ +native bool:IsServerProcessing(); + +/** + * Creates a timer associated with a new data pack, and returns the datapack. + * @note The datapack is automatically freed when the timer ends. + * @note The position of the datapack is not reset or changed for the timer function. + * + * @param interval Interval from the current game time to execute the given function. + * @param func Function to execute once the given interval has elapsed. + * @param data The newly created datapack is passed though this by-reference parameter. + * @param flags Timer flags. + * @return Handle to the timer object. You do not need to call CloseHandle(). + */ +stock Handle:CreateDataTimer(Float:interval, Timer:func, &Handle:data, flags=0) +{ + data = CreateDataPack(); + flags |= TIMER_HNDL_CLOSE; + return CreateTimer(interval, func, data, flags); +} + diff --git a/src/include/topmenus.inc b/src/include/topmenus.inc new file mode 100644 index 0000000..44776a5 --- /dev/null +++ b/src/include/topmenus.inc @@ -0,0 +1,290 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: topmenus.inc 2086 2008-04-19 10:34:56Z pred $ + */ + +#if defined _topmenus_included + #endinput +#endif +#define _topmenus_included + +#include + +/** + * Actions a top menu will take on an object. + */ +enum TopMenuAction +{ + /** + * An option is being drawn for a menu (or for sorting purposes). + * + * INPUT : TopMenu Handle, object ID, client index. + * OUTPUT: Buffer for rendering, maxlength of buffer. + */ + TopMenuAction_DisplayOption = 0, + + /** + * The title of a menu is being drawn for a given object. + * + * Note: The Object ID will be INVALID_TOPMENUOBJECT if drawing the + * root title. Otherwise, the Object ID is a category. + * + * INPUT : TopMenu Handle, object ID, client index. + * OUTPUT: Buffer for rendering, maxlength of buffer. + */ + TopMenuAction_DisplayTitle = 1, + + /** + * A menu option has been selected. + * + * The Object ID will always be an item (not a category). + * + * INPUT : TopMenu Handle, object ID, client index. + */ + TopMenuAction_SelectOption = 2, + + /** + * A menu option is being drawn and its flags can be overridden. + * + * INPUT : TopMenu Handle, object ID, client index. + * OUTPUT: The first byte of the 'buffer' string should be set + * to the desired flags. By default, it will contain + * ITEMDRAW_DEFAULT. + */ + TopMenuAction_DrawOption = 3, + + /** + * Called when an object is being removed from the menu. + * This can be used to clean up data stored in the info string. + * + * INPUT : TopMenu Handle, object ID. + */ + TopMenuAction_RemoveObject = 4, +}; + +/** + * Top menu object types. + */ +enum TopMenuObjectType +{ + TopMenuObject_Category = 0, /**< Category (sub-menu branching from root) */ + TopMenuObject_Item = 1 /**< Item on a sub-menu */ +}; + +/** + * Top menu starting positions for display. + */ +enum TopMenuPosition +{ + TopMenuPosition_Start = 0, /**< Start/root of the menu */ + TopMenuPosition_LastRoot = 1, /**< Last position in the root menu */ + TopMenuPosition_LastCategory = 3, /**< Last position in their last category */ +}; + +/** + * Top menu object tag for type checking. + */ +enum TopMenuObject +{ + INVALID_TOPMENUOBJECT = 0, +}; + +/** + * TopMenu callback prototype. + * + * @param topmenu Handle to the TopMenu. + * @param action TopMenuAction being performed. + * @param object_id The object ID (if used). + * @param param Extra parameter (if used). + * @param buffer Output buffer (if used). + * @param maxlength Output buffer (if used). + * @noreturn + */ +functag TopMenuHandler public(Handle:topmenu, + TopMenuAction:action, + TopMenuObject:object_id, + param, + String:buffer[], + maxlength); + +/** + * Creates a TopMenu. + * + * @param handler Handler to use for drawing the root title. + * @return A new TopMenu Handle, or INVALID_HANDLE on failure. + */ +native Handle:CreateTopMenu(TopMenuHandler:handler); + +/** + * Re-sorts the items in a TopMenu via a configuration file. + * + * The format of the configuration file should be a Valve Key-Values + * formatted file that SourceMod can parse. There should be one root + * section, and one sub-section for each category. Each sub-section's + * name should match the category name. + * + * Each sub-section may only contain key/value pairs in the form of: + * key: "item" + * value: Name of the item as passed to AddToTopMenu(). + * + * The TopMenu will draw items in the order declared in the configuration + * file. If items do not appear in the configuration file, they are sorted + * per-player based on how the handler function renders for that player. + * These items appear after the configuration sorted items. + * + * @param topmenu TopMenu Handle. + * @param file File path. + * @param error Error buffer. + * @param maxlength Maximum size of the error buffer. + * Error buffer will be filled with a + * zero-terminated string if false is + * returned. + * @return True on success, false on failure. + * @error Invalid TopMenu Handle. + */ +native bool:LoadTopMenuConfig(Handle:topmenu, const String:file[], String:error[], maxlength); + +/** + * Adds an object to a TopMenu. + * + * @param topmenu TopMenu Handle. + * @param name Object name (MUST be unique). + * @param type Object type. + * @param handler Handler for object. + * @param cmdname Command name (for access overrides). + * @param flags Default access flags. + * @param parent Parent object ID, or INVALID_TOPMENUOBJECT for none. + * Items must have a category parent. + * Categories must not have a parent. + * @param info_string Arbitrary storage (max 255 bytes). + * @return A new TopMenuObject ID, or INVALID_TOPMENUOBJECT on + * failure. + * @error Invalid TopMenu Handle. + */ +native TopMenuObject:AddToTopMenu(Handle:topmenu, + const String:name[], + TopMenuObjectType:type, + TopMenuHandler:handler, + TopMenuObject:parent, + const String:cmdname[]="", + flags=0, + const String:info_string[]=""); + +/** + * Retrieves the info string of a top menu item. + * + * @param topmenu TopMenu Handle. + * @param object TopMenuObject ID. + * @param buffer Buffer to store info string. + * @param maxlength Maximum size of info string. + * @return Number of bytes written, not including the + * null terminator. + * @error Invalid TopMenu Handle or TopMenuObject ID. + */ +native GetTopMenuInfoString(Handle:topmenu, TopMenuObject:parent, String:buffer[], maxlength); + +/** + * Retrieves the name string of a top menu item. + * + * @param topmenu TopMenu Handle. + * @param object TopMenuObject ID. + * @param buffer Buffer to store info string. + * @param maxlength Maximum size of info string. + * @return Number of bytes written, not including the + * null terminator. + * @error Invalid TopMenu Handle or TopMenuObject ID. + */ +native GetTopMenuObjName(Handle:topmenu, TopMenuObject:object, String:buffer[], maxlength); + +/** + * Removes an object from a TopMenu. + * + * Plugins' objects are automatically removed all TopMenus when the given + * plugin unloads or pauses. In the case of unpausing, all items are restored. + * + * @param topmenu TopMenu Handle. + * @param object TopMenuObject ID. + * @noreturn + * @error Invalid TopMenu Handle. + */ +native RemoveFromTopMenu(Handle:topmenu, TopMenuObject:object); + +/** + * Displays a TopMenu to a client. + * + * @param topmenu TopMenu Handle. + * @param client Client index. + * @param position Position to display from. + * @return True on success, false on failure. + * @error Invalid TopMenu Handle or client not in game. + */ +native bool:DisplayTopMenu(Handle:topmenu, client, TopMenuPosition:position); + +/** + * Finds a category's object ID in a TopMenu. + * + * @param topmenu TopMenu Handle. + * @param name Object's unique name. + * @return TopMenuObject ID on success, or + * INVALID_TOPMENUOBJECT on failure. + * @error Invalid TopMenu Handle. + */ +native TopMenuObject:FindTopMenuCategory(Handle:topmenu, const String:name[]); + +/** + * Do not edit below this line! + */ +public Extension:__ext_topmenus = +{ + name = "TopMenus", + file = "topmenus.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_topmenus_SetNTVOptional() +{ + MarkNativeAsOptional("CreateTopMenu"); + MarkNativeAsOptional("LoadTopMenuConfig"); + MarkNativeAsOptional("AddToTopMenu"); + MarkNativeAsOptional("RemoveFromTopMenu"); + MarkNativeAsOptional("DisplayTopMenu"); + MarkNativeAsOptional("FindTopMenuCategory"); +} +#endif diff --git a/src/include/usermessages.inc b/src/include/usermessages.inc new file mode 100644 index 0000000..7d0c1b8 --- /dev/null +++ b/src/include/usermessages.inc @@ -0,0 +1,200 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: usermessages.inc 1943 2008-03-16 23:16:34Z dvander $ + */ + +#if defined _eventsmsgs_included + #endinput +#endif +#define _eventsmsgs_included + +/** + * UserMsg helper values. + */ +enum UserMsg +{ + INVALID_MESSAGE_ID = -1, +}; + +/** + * @section Message Flags. + */ +#define USERMSG_RELIABLE (1<<2) /**< Message will be set on the reliable stream */ +#define USERMSG_INITMSG (1<<3) /**< Message will be considered to be an initmsg */ +#define USERMSG_BLOCKHOOKS (1<<7) /**< Prevents the message from triggering SourceMod and Metamod hooks */ + +/** + * @endsection + */ + +/** + * Returns the ID of a given message, or -1 on failure. + * + * @param msg String containing message name (case sensitive). + * @return A message index, or INVALID_MESSAGE_ID on failure. + */ +native UserMsg:GetUserMessageId(const String:msg[]); + +/** + * Retrieves the name of a message by ID. + * + * @param msg_id Message index. + * @param msg Buffer to store the name of the message. + * @param maxlength Maximum length of string buffer. + * @return True if message index is valid, false otherwise. + */ +native bool:GetUserMessageName(UserMsg:msg_id, String:msg[], maxlength); + +/** + * Starts a usermessage (network message). + * @note Only one message can be active at a time. + * @note It is illegal to send any message while a non-intercept hook is in progress. + * + * @param msgname Message name to start. + * @param clients Array containing player indexes to broadcast to. + * @param numClients Number of players in the array. + * @param flags Optional flags to set. + * @return A handle to a bf_write bit packing structure, or + * INVALID_HANDLE on failure. + * @error Invalid message name or unable to start a message. + */ +native Handle:StartMessage(String:msgname[], clients[], numClients, flags=0); + +/** + * Starts a usermessage (network message). + * @note Only one message can be active at a time. + * @note It is illegal to send any message while a non-intercept hook is in progress. + * + * @param msg Message index to start. + * @param clients Array containing player indexes to broadcast to. + * @param numClients Number of players in the array. + * @param flags Optional flags to set. + * @return A handle to a bf_write bit packing structure, or + * INVALID_HANDLE on failure. + * @error Invalid message name or unable to start a message. + */ +native Handle:StartMessageEx(UserMsg:msg, clients[], numClients, flags=0); + +/** + * Ends a previously started user message (network message). + * + * @noreturn + */ +native EndMessage(); + +/** + * Called when a message is hooked + * + * @param msg_id Message index. + * @param bf Handle to the input bit buffer of the message. + * @param players Array containing player indexes. + * @param playersNum Number of players in the array. + * @param reliable True if message is reliable, false otherwise. + * @param init True if message is an initmsg, false otherwise. + * @return Ignored for normal hooks. For intercept hooks, Plugin_Handled + * blocks the message from being sent, and Plugin_Continue + * resumes normal functionality. + */ +functag MsgHook Action:public(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init); + +/** + * Called when a message is finished sending. + * + * @param msg_id Message index. + */ +functag MsgSentNotify public(UserMsg:msg_id); + +/** + * Hooks a user message. + * + * @param msg_id Message index. + * @param hook Function to use as a hook. + * @param intercept If intercept is true, message will be fully intercepted, + * allowing the user to block the message. Otherwise, + * the hook is normal and ignores the return value. + * @param notify Notification function. + * @noreturn + * @error Invalid message index. + */ +native HookUserMessage(UserMsg:msg_id, MsgHook:hook, bool:intercept=false, MsgSentNotify:notify=MsgSentNotify:-1); + +/** + * Removes one usermessage hook. + * + * @param msg_id Message index. + * @param hook Function used for the hook. + * @param intercept Specifies whether the hook was an intercept hook or not. + * @noreturn + * @error Invalid message index. + */ +native UnhookUserMessage(UserMsg:msg_id, MsgHook:hook, bool:intercept=false); + +/** + * Starts a usermessage (network message) that broadcasts to all clients. + * @note See StartMessage or StartMessageEx(). + * + * @param msgname Message name to start. + * @param flags Optional flags to set. + * @return A handle to a bf_write bit packing structure, or + * INVALID_HANDLE on failure. + */ +stock Handle:StartMessageAll(String:msgname[], flags=0) +{ + new maxClients = GetMaxClients(); + new total = 0; + new clients[maxClients]; + for (new i=1; i<=maxClients; i++) + { + if (IsClientConnected(i)) + { + clients[total++] = i; + } + } + return StartMessage(msgname, clients, total, flags); +} + +/** + * Starts a simpler usermessage (network message) for one client. + * @note See StartMessage or StartMessageEx(). + * + * @param msgname Message name to start. + * @param client Client to send to. + * @param flags Optional flags to set. + * @return A handle to a bf_write bit packing structure, or + * INVALID_HANDLE on failure. + */ +stock Handle:StartMessageOne(String:msgname[], client, flags=0) +{ + new players[1]; + + players[0] = client; + + return StartMessage(msgname, players, 1, flags); +} diff --git a/src/include/vector.inc b/src/include/vector.inc new file mode 100644 index 0000000..da8f9a7 --- /dev/null +++ b/src/include/vector.inc @@ -0,0 +1,188 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: vector.inc 1336 2007-08-15 06:19:30Z damagedsoul $ + */ + +#if defined _vector_included + #endinput +#endif +#define _vector_included + +/** + * Calculates a vector's length. + * + * @param vec Vector. + * @param squared If true, the result will be squared (for optimization). + * @return Vector length (magnitude). + */ +native Float:GetVectorLength(const Float:vec[3], bool:squared=false); + +/** + * Calculates the distance between two vectors. + * + * @param vec1 First vector. + * @param vec2 Second vector. + * @param squared If true, the result will be squared (for optimization). + * @return Vector distance. + */ +native Float:GetVectorDistance(const Float:vec1[3], const Float:vec2[3], bool:squared=false); + +/** + * Calculates the dot product of two vectors. + * + * @param vec1 First vector. + * @param vec2 Second vector. + * @return Dot product of the two vectors. + */ +native Float:GetVectorDotProduct(const Float:vec1[3], const Float:vec2[3]); + +/** + * Computes the cross product of two vectors. Any input array can be the same + * as the output array. + * + * @param vec1 First vector. + * @param vec2 Second vector. + * @param result Resultant vector. + * @noreturn + */ +native GetVectorCrossProduct(const Float:vec1[3], const Float:vec2[3], Float:result[3]); + +/** + * Normalizes a vector. The input array can be the same as the output array. + * + * @param vec Vector. + * @param result Resultant vector. + * @return Vector length. + */ +native Float:NormalizeVector(const Float:vec[3], Float:result[3]); + +/** + * Returns vectors in the direction of an angle. + * + * @param angle Angle. + * @param fwd Forward vector buffer or NULL_VECTOR. + * @param right Right vector buffer or NULL_VECTOR. + * @param up Up vector buffer or NULL_VECTOR. + * @noreturn + */ +native GetAngleVectors(const Float:angle[3], Float:fwd[3], Float:right[3], Float:up[3]); + +/** + * Returns angles from a vector. + * + * @param vec Vector. + * @param angle Angle buffer. + * @noreturn + */ +native GetVectorAngles(const Float:vec[3], Float:angle[3]); + +/** + * Returns direction vectors from a vector. + * + * @param vec Vector. + * @param right Right vector buffer or NULL_VECTOR. + * @param up Up vector buffer or NULL_VECTOR. + * @noreturn + */ +native GetVectorVectors(const Float:vec[3], Float:right[3], Float:up[3]); + +/** + * Adds two vectors. It is safe to use either input buffer as an output + * buffer. + * + * @param vec1 First vector. + * @param vec2 Second vector. + * @param result Result buffer. + * @noreturn + */ +stock AddVectors(const Float:vec1[3], const Float:vec2[3], Float:result[3]) +{ + result[0] = vec1[0] + vec2[0]; + result[1] = vec1[1] + vec2[1]; + result[2] = vec1[2] + vec2[2]; +} + +/** + * Subtracts a vector from another vector. It is safe to use either input + * buffer as an output buffer. + * + * @param vec1 First vector. + * @param vec2 Second vector to subtract from first. + * @param result Result buffer. + * @noreturn + */ +stock SubtractVectors(const Float:vec1[3], const Float:vec2[3], Float:result[3]) +{ + result[0] = vec1[0] - vec2[0]; + result[1] = vec1[1] - vec2[1]; + result[2] = vec1[2] - vec2[2]; +} + +/** + * Scales a vector. + * + * @param vec Vector. + * @param scale Scale value. + * @noreturn + */ +stock ScaleVector(Float:vec[3], Float:scale) +{ + vec[0] *= scale; + vec[1] *= scale; + vec[2] *= scale; +} + +/** + * Negatives a vector. + * + * @param vec Vector. + * @noreturn + */ +stock NegateVector(Float:vec[3]) +{ + vec[0] = -vec[0]; + vec[1] = -vec[1]; + vec[2] = -vec[2]; +} + +/** + * Builds a vector from two points by subtracting the points. + * + * @param pt1 First point (to be subtracted from the second). + * @param pt2 Second point. + * @param output Output vector buffer. + * @noreturn + */ +stock MakeVectorFromPoints(const Float:pt1[3], const Float:pt2[3], Float:output[3]) +{ + output[0] = pt2[0] - pt1[0]; + output[1] = pt2[1] - pt1[1]; + output[2] = pt2[2] - pt1[2]; +} diff --git a/src/include/version.inc b/src/include/version.inc new file mode 100644 index 0000000..ee3a51f --- /dev/null +++ b/src/include/version.inc @@ -0,0 +1,42 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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 . + * + * 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 . + * + * Version: $Id: version.inc 2534 2008-09-14 05:13:21Z dvander $ + */ + +#if defined _version_included + #endinput +#endif +#define _version_included + +#define SOURCEMOD_V_MAJOR 1 /**< SourceMod Major version */ +#define SOURCEMOD_V_MINOR 0 /**< SourceMod Minor version */ +#define SOURCEMOD_V_RELEASE 4 /**< SourceMod Release version */ + +#define SOURCEMOD_VERSION "1.0.4" /**< SourceMod version string (major.minor.release.build) */ diff --git a/src/include/zr.inc b/src/include/zr.inc new file mode 100644 index 0000000..fa656b6 --- /dev/null +++ b/src/include/zr.inc @@ -0,0 +1,42 @@ +/** + * ==================== + * Zombie:Reloaded + * File: zr.inc + * Author: Greyscale + * ==================== + */ + +/** + * Used to check if a player is a zombie. + * @param client Client index. + * @return True if the player is a zombie, and false if human. + */ +native bool:ZR_IsClientZombie(client); + +/** + * Returns the client's zombie class. + * @param client Client index. + * @return The client's zombie class. + */ +native ZR_GetClientZClass(client); + +/** + * Returns if the zombie has spawned or not. + * @return Returns true, if the first zombie has spawned during the round, false if not. + */ +native bool:ZR_HasZombieSpawned(); + +/** + * Called when a player turns into a zombie. + * @param client Client index. + * @param mother True if zombie is a mother, false if zombified by another zombie. + */ +forward ZR_Zombify(client, bool:mother); + +/** + * Called when a player changes their zombie class. + * @param client Client index. + * @param oldclass The client's old class. + * @param newclass The client's new class. + */ +forward ZR_OnZClassChanged(client, oldclass, newclass); \ No newline at end of file diff --git a/src/zombiereloaded.sp b/src/zombiereloaded.sp new file mode 100644 index 0000000..51e591d --- /dev/null +++ b/src/zombiereloaded.sp @@ -0,0 +1,209 @@ +/** + * ==================== + * Zombie:Reloaded + * File: zombiereloaded.sp + * Author: Greyscale + * ==================== + */ + +#pragma semicolon 1 +#include +#include +#include +#include + +#undef REQUIRE_PLUGIN +#include + +#define VERSION "2.5.1" + +#include "zr/zombiereloaded" +#include "zr/global" +#include "zr/cvars" +#include "zr/translation" +#include "zr/offsets" +#include "zr/ambience" +#include "zr/classes" +#include "zr/models" +#include "zr/overlays" +#include "zr/zombie" +#include "zr/menu" +#include "zr/sayhooks" +#include "zr/weaponrestrict" +#include "zr/damagecontrol" +#include "zr/commands" +#include "zr/event" + +public Plugin:myinfo = +{ + name = "Zombie:Reloaded", + author = "Greyscale", + description = "Infection/survival style gameplay", + version = VERSION, + url = "" +}; + +public bool:AskPluginLoad(Handle:myself, bool:late, String:error[], err_max) +{ + CreateGlobals(); + + return true; +} + +public OnPluginStart() +{ + LoadTranslations("common.phrases.txt"); + LoadTranslations("zombiereloaded.phrases.txt"); + + // ====================================================================== + + ZR_PrintToServer("Plugin loading"); + + // ====================================================================== + + HookEvents(); + HookChatCmds(); + CreateCvars(); + HookCvars(); + CreateCommands(); + HookCommands(); + FindOffsets(); + SetupGameData(); + InitWeaponRestrict(); + InitDmgControl(); + + // ====================================================================== + + market = LibraryExists("market"); + + // ====================================================================== + + CreateConVar("gs_zombiereloaded_version", VERSION, "[ZR] Current version of this plugin", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_UNLOGGED|FCVAR_DONTRECORD|FCVAR_REPLICATED|FCVAR_NOTIFY); + + // ====================================================================== + + ZR_PrintToServer("Plugin loaded"); +} + +public OnPluginEnd() +{ + ZREnd(); +} + +public OnLibraryRemoved(const String:name[]) +{ + if (StrEqual(name, "market")) + { + market = false; + } +} + +public OnLibraryAdded(const String:name[]) +{ + if (StrEqual(name, "market")) + { + market = true; + } +} + +public OnMapStart() +{ + MapChangeCleanup(); + + LoadModelData(); + LoadDownloadData(); +} + +public OnConfigsExecuted() +{ + FindMapSky(); + + LoadClassData(); + LoadAmbienceData(); + + decl String:mapconfig[PLATFORM_MAX_PATH]; + + GetCurrentMap(mapconfig, sizeof(mapconfig)); + Format(mapconfig, sizeof(mapconfig), "sourcemod/zombiereloaded/%s.cfg", mapconfig); + + decl String:path[PLATFORM_MAX_PATH]; + Format(path, sizeof(path), "cfg/%s", mapconfig); + + if (FileExists(path)) + { + ServerCommand("exec %s", mapconfig); + } +} + +public OnClientPutInServer(client) +{ + pClass[client] = 0; + gBlockMotherInfect[client] = false; + + bZVision[client] = !IsFakeClient(client); + + new bool:zhp = GetConVarBool(gCvars[CVAR_ZHP_DEFAULT]); + dispHP[client] = zhp; + + ClientHookUse(client); + ClientHookAttack(client); + + FindClientDXLevel(client); + + for (new x = 0; x < MAXTIMERS; x++) + { + tHandles[client][x] = INVALID_HANDLE; + } +} + +public OnClientDisconnect(client) +{ + ClientUnHookUse(client); + ClientUnHookAttack(client); + + PlayerLeft(client); + + for (new x = 0; x < MAXTIMERS; x++) + { + if (tHandles[client][x] != INVALID_HANDLE) + { + CloseHandle(tHandles[client][x]); + tHandles[client][x] = INVALID_HANDLE; + } + } +} + +MapChangeCleanup() +{ + ClearArray(restrictedWeapons); + + tRound = INVALID_HANDLE; + tInfect = INVALID_HANDLE; + tAmbience = INVALID_HANDLE; +} + +ZREnd() +{ + TerminateRound(3.0, Game_Commencing); + + UnhookCvars(); + UnhookEvents(); + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (!IsClientInGame(x)) + { + continue; + } + + for (new y = 0; y < MAXTIMERS; y++) + { + if (tHandles[x][y] != INVALID_HANDLE) + { + CloseHandle(tHandles[x][y]); + tHandles[x][y] = INVALID_HANDLE; + } + } + } +} \ No newline at end of file diff --git a/src/zr/ambience.inc b/src/zr/ambience.inc new file mode 100644 index 0000000..91f3a61 --- /dev/null +++ b/src/zr/ambience.inc @@ -0,0 +1,97 @@ +/** + * ==================== + * Zombie:Reloaded + * File: ambience.inc + * Author: Greyscale + * ==================== + */ + +new bool:soundValid = false; + +new Handle:tAmbience = INVALID_HANDLE; + +LoadAmbienceData() +{ + new bool:ambience = GetConVarBool(gCvars[CVAR_AMBIENCE]); + if (!ambience) + { + return; + } + + decl String:sound[64]; + GetConVarString(gCvars[CVAR_AMBIENCE_FILE], sound, sizeof(sound)); + Format(sound, sizeof(sound), "sound/%s", sound); + + soundValid = FileExists(sound, true); + + if (soundValid) + { + AddFileToDownloadsTable(sound); + } + else + { + ZR_LogMessage("Ambient sound load failed", sound); + } +} + +RestartAmbience() +{ + if (tAmbience != INVALID_HANDLE) + { + CloseHandle(tAmbience); + } + + CreateTimer(0.0, AmbienceLoop, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action:AmbienceLoop(Handle:timer) +{ + new bool:ambience = GetConVarBool(gCvars[CVAR_AMBIENCE]); + + if (!ambience || !soundValid) + { + return; + } + + decl String:sound[64]; + GetConVarString(gCvars[CVAR_AMBIENCE_FILE], sound, sizeof(sound)); + + EmitAmbience(sound); + + new Float:delay = GetConVarFloat(gCvars[CVAR_AMBIENCE_LENGTH]); + tAmbience = CreateTimer(delay, AmbienceLoop, _, TIMER_FLAG_NO_MAPCHANGE); +} + +StopAmbience() +{ + new bool:ambience = GetConVarBool(gCvars[CVAR_AMBIENCE]); + + if (!ambience) + { + return; + } + + decl String:sound[64]; + GetConVarString(gCvars[CVAR_AMBIENCE_FILE], sound, sizeof(sound)); + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (!IsClientInGame(x)) + { + continue; + } + + StopSound(x, SNDCHAN_AUTO, sound); + } +} + +EmitAmbience(const String:sound[]) +{ + PrecacheSound(sound); + + StopAmbience(); + + new Float:volume = GetConVarFloat(gCvars[CVAR_AMBIENCE_VOLUME]); + EmitSoundToAll(sound, SOUND_FROM_PLAYER, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_NOFLAGS, volume, SNDPITCH_NORMAL, -1, NULL_VECTOR, NULL_VECTOR, true, 0.0); +} \ No newline at end of file diff --git a/src/zr/classes.inc b/src/zr/classes.inc new file mode 100644 index 0000000..b9cc736 --- /dev/null +++ b/src/zr/classes.inc @@ -0,0 +1,295 @@ +/** + * ==================== + * Zombie:Reloaded + * File: classes.inc + * Author: Greyscale + * ==================== + */ + +enum ZR_ClassOptions +{ + String:data_name[64], + String:data_model[256], + String:data_menu_description[256], + String:data_zvision[256], + data_health, + Float:data_speed, + Float:data_jump_distance, + Float:data_jump_height, + Float:data_knockback, + bool:data_nvgs[8], + data_fov, + bool:data_regen, + data_regen_health, + Float:data_regen_interval, + bool:data_napalm, + Float:data_napalm_time, + bool:data_nofalldamage, + data_kill_bonus, + data_infect_health +} + +#define MAXCLASSES 20 + +new Handle:kvClasses = INVALID_HANDLE; + +new arrayClasses[MAXCLASSES][ZR_ClassOptions]; +new classCount; + +LoadClassData() +{ + if (kvClasses != INVALID_HANDLE) + { + CloseHandle(kvClasses); + } + + kvClasses = CreateKeyValues("classes"); + + decl String:path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "configs/zr/classes.txt"); + + if (!FileToKeyValues(kvClasses, path)) + { + SetFailState("\"%s\" missing from server", path); + } + + KvRewind(kvClasses); + if (!KvGotoFirstSubKey(kvClasses)) + { + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + SetConVarBool(gCvars[CVAR_CLASSES], false); + + ZR_LogMessage("Class auto-disable", path); + } + } + + decl String:name[64]; + decl String:model[256]; + decl String:menu_description[256]; + decl String:zvision[256]; + + classCount = 0; + + do + { + KvGetString(kvClasses, "name", name, sizeof(name)); + strcopy(arrayClasses[classCount][data_name], 64, name); + + KvGetString(kvClasses, "model", model, sizeof(model)); + strcopy(arrayClasses[classCount][data_model], 256, model); + + KvGetString(kvClasses, "menu_description", menu_description, sizeof(menu_description)); + strcopy(arrayClasses[classCount][data_menu_description], 256, menu_description); + + decl String:cvar_zvision[256]; + GetConVarString(gCvars[CVAR_ZOMBIE_ZVISION], cvar_zvision, sizeof(cvar_zvision)); + + KvGetString(kvClasses, "zvision", zvision, sizeof(zvision), cvar_zvision); + strcopy(arrayClasses[classCount][data_zvision], 256, zvision); + + arrayClasses[classCount][data_health] = KvGetNum(kvClasses, "health"), GetConVarInt(gCvars[CVAR_ZOMBIE_HEALTH]); + arrayClasses[classCount][data_speed] = KvGetFloat(kvClasses, "speed"), GetConVarFloat(gCvars[CVAR_ZOMBIE_SPEED]); + arrayClasses[classCount][data_jump_distance] = KvGetFloat(kvClasses, "jump_distance"), GetConVarFloat(gCvars[CVAR_ZOMBIE_JUMP_DISTANCE]); + arrayClasses[classCount][data_jump_height] = KvGetFloat(kvClasses, "jump_height"), GetConVarFloat(gCvars[CVAR_ZOMBIE_JUMP_HEIGHT]); + arrayClasses[classCount][data_knockback] = KvGetFloat(kvClasses, "knockback"), GetConVarFloat(gCvars[CVAR_ZOMBIE_KNOCKBACK]); + arrayClasses[classCount][data_nvgs] = bool:KvGetNum(kvClasses, "nvgs"), GetConVarBool(gCvars[CVAR_ZOMBIE_NVGS]); + arrayClasses[classCount][data_fov] = KvGetNum(kvClasses, "fov"), GetConVarInt(gCvars[CVAR_ZOMBIE_FOV]); + arrayClasses[classCount][data_regen] = bool:KvGetNum(kvClasses, "regen"), GetConVarBool(gCvars[CVAR_ZOMBIE_REGEN]); + arrayClasses[classCount][data_regen_health] = KvGetNum(kvClasses, "regen_health"), GetConVarInt(gCvars[CVAR_ZOMBIE_REGEN_HEALTH]); + arrayClasses[classCount][data_regen_interval] = KvGetFloat(kvClasses, "regen_interval"), GetConVarFloat(gCvars[CVAR_ZOMBIE_REGEN_INTERVAL]); + arrayClasses[classCount][data_napalm] = bool:KvGetNum(kvClasses, "napalm"), GetConVarBool(gCvars[CVAR_ZOMBIE_NAPALM]); + arrayClasses[classCount][data_napalm_time] = KvGetFloat(kvClasses, "napalm_time"), GetConVarFloat(gCvars[CVAR_ZOMBIE_NAPALM_TIME]); + arrayClasses[classCount][data_nofalldamage] = bool:KvGetNum(kvClasses, "nofalldamage"), GetConVarBool(gCvars[CVAR_ZOMBIE_NOFALLDAMAGE]); + arrayClasses[classCount][data_kill_bonus] = KvGetNum(kvClasses, "kill_bonus"), GetConVarInt(gCvars[CVAR_ZOMBIE_KILL_BONUS]); + arrayClasses[classCount][data_infect_health] = KvGetNum(kvClasses, "infect_health"), GetConVarInt(gCvars[CVAR_ZOMBIE_INFECT_HEALTH]); + + classCount++; + } while (KvGotoNextKey(kvClasses)); +} + +GetClassName(classindex, String:name[], maxlen) +{ + strcopy(name, maxlen, arrayClasses[classindex][data_name]); +} + +GetClassModel(classindex, String:model[], maxlen) +{ + strcopy(model, maxlen, arrayClasses[classindex][data_model]); +} + +GetClassMenuDescription(classindex, String:menudescription[], maxlen) +{ + strcopy(menudescription, maxlen, arrayClasses[classindex][data_menu_description]); +} + +GetClassZVision(classindex, String:zvision[], maxlen) +{ + strcopy(zvision, maxlen, arrayClasses[classindex][data_zvision]); +} + +GetClassHealth(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_health]; + } + + return GetConVarInt(gCvars[CVAR_ZOMBIE_HEALTH]); +} + +Float:GetClassSpeed(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_speed]; + } + + return GetConVarFloat(gCvars[CVAR_ZOMBIE_SPEED]); +} + +Float:GetClassJumpDistance(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_jump_distance]; + } + + return GetConVarFloat(gCvars[CVAR_ZOMBIE_JUMP_DISTANCE]); +} + +Float:GetClassJumpHeight(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_jump_height]; + } + + return GetConVarFloat(gCvars[CVAR_ZOMBIE_JUMP_HEIGHT]); +} + +Float:GetClassKnockback(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_knockback]; + } + + return GetConVarFloat(gCvars[CVAR_ZOMBIE_KNOCKBACK]); +} + +bool:GetClassNVGs(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_nvgs]; + } + + return GetConVarBool(gCvars[CVAR_ZOMBIE_NVGS]); +} + +GetClassFOV(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_fov]; + } + + return GetConVarInt(gCvars[CVAR_ZOMBIE_FOV]); +} + +bool:GetClassRegen(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_regen]; + } + + return GetConVarBool(gCvars[CVAR_ZOMBIE_REGEN]); +} + +GetClassRegenHealth(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_regen_health]; + } + + return GetConVarInt(gCvars[CVAR_ZOMBIE_REGEN_HEALTH]); +} + +Float:GetClassRegenInterval(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_regen_interval]; + } + + return GetConVarFloat(gCvars[CVAR_ZOMBIE_REGEN_INTERVAL]); +} + +bool:GetClassNapalm(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_napalm]; + } + + return GetConVarBool(gCvars[CVAR_ZOMBIE_NAPALM]); +} + +Float:GetClassNapalmTime(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_napalm_time]; + } + + return GetConVarFloat(gCvars[CVAR_ZOMBIE_NAPALM_TIME]); +} + +bool:GetClassNoFallDamage(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_nofalldamage]; + } + + return GetConVarBool(gCvars[CVAR_ZOMBIE_NOFALLDAMAGE]); +} + +GetClassKillBonus(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_kill_bonus]; + } + + return GetConVarInt(gCvars[CVAR_ZOMBIE_KILL_BONUS]); +} + +GetClassInfectHealth(classindex) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + return arrayClasses[classindex][data_infect_health]; + } + + return GetConVarInt(gCvars[CVAR_ZOMBIE_INFECT_HEALTH]); +} \ No newline at end of file diff --git a/src/zr/commands.inc b/src/zr/commands.inc new file mode 100644 index 0000000..93dfc54 --- /dev/null +++ b/src/zr/commands.inc @@ -0,0 +1,118 @@ +/** + * ==================== + * Zombie:Reloaded + * File: commands.inc + * Author: Greyscale + * ==================== + */ + +CreateCommands() +{ + RegAdminCmd("zr_infect", Command_Infect, ADMFLAG_GENERIC, "Infects the specified player"); + RegAdminCmd("zr_spawn", Command_Respawn, ADMFLAG_GENERIC, "Respawns the specified player following auto-respawning rules"); + + RegAdminCmd("zr_restrict", Command_Restrict, ADMFLAG_GENERIC, "Restrict a specified weapon"); + RegAdminCmd("zr_unrestrict", Command_UnRestrict, ADMFLAG_GENERIC, "Unrestrict a specified weapon"); +} + +public Action:Command_Infect(client, argc) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (argc < 1 || !enabled) + { + return Plugin_Handled; + } + + decl String:arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + decl String:target_name[MAX_TARGET_LENGTH]; + new targets[MAXPLAYERS]; + new bool:tn_is_ml; + + new tcount = ProcessTargetString(arg1, client, targets, MAXPLAYERS, COMMAND_FILTER_ALIVE, target_name, sizeof(target_name), tn_is_ml); + if (tcount <= 0) + { + ReplyToTargetError(client, tcount); + return Plugin_Handled; + } + + for (new x = 0; x < tcount; x++) + { + Zombify(targets[x], 0); + } + + return Plugin_Handled; +} + +public Action:Command_Respawn(client, argc) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (argc < 1 || !enabled) + { + return Plugin_Handled; + } + + decl String:arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + decl String:target_name[MAX_TARGET_LENGTH]; + new targets[MAXPLAYERS]; + new bool:tn_is_ml; + + new tcount = ProcessTargetString(arg1, client, targets, MAXPLAYERS, COMMAND_FILTER_DEAD, target_name, sizeof(target_name), tn_is_ml); + if (tcount <= 0) + { + ReplyToTargetError(client, tcount); + return Plugin_Handled; + } + + for (new x = 0; x < tcount; x++) + { + RespawnPlayer(targets[x]); + } + + return Plugin_Handled; +} + +public Action:Command_Restrict(client, argc) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (argc < 1 || !enabled) + { + return Plugin_Handled; + } + + decl String:arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + new WepRestrictQuery:output = RestrictWeapon(arg1); + + if (output == Existing) + { + ZR_ReplyToCommand(client, "Weapon already restricted", arg1); + } + + return Plugin_Handled; +} + +public Action:Command_UnRestrict(client, argc) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (argc < 1 || !enabled) + { + return Plugin_Handled; + } + + decl String:arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + + new WepRestrictQuery:output = UnRestrictWeapon(arg1); + + if (output == Invalid) + { + ZR_ReplyToCommand(client, "Weapon invalid", arg1); + } + + return Plugin_Handled; +} \ No newline at end of file diff --git a/src/zr/cvars.inc b/src/zr/cvars.inc new file mode 100644 index 0000000..5e5b3ec --- /dev/null +++ b/src/zr/cvars.inc @@ -0,0 +1,191 @@ +/** + * ==================== + * Zombie:Reloaded + * File: cvars.inc + * Author: Greyscale + * ==================== + */ + +enum ZRSettings +{ + Handle:CVAR_ENABLE, + Handle:CVAR_ALLOW_PLAYER_TEAM, + Handle:CVAR_AMBIENCE, + Handle:CVAR_AMBIENCE_FILE, + Handle:CVAR_AMBIENCE_LENGTH, + Handle:CVAR_AMBIENCE_VOLUME, + Handle:CVAR_EMITSOUNDS, + Handle:CVAR_CLASSES, + Handle:CVAR_CLASSES_SPAWN, + Handle:CVAR_CLASSES_RANDOM, + Handle:CVAR_ZOMBIE_HEALTH, + Handle:CVAR_ZOMBIE_SPEED, + Handle:CVAR_ZOMBIE_JUMP_DISTANCE, + Handle:CVAR_ZOMBIE_JUMP_HEIGHT, + Handle:CVAR_ZOMBIE_KNOCKBACK, + Handle:CVAR_ZOMBIE_NVGS, + Handle:CVAR_ZOMBIE_FOV, + Handle:CVAR_ZOMBIE_REGEN, + Handle:CVAR_ZOMBIE_REGEN_HEALTH, + Handle:CVAR_ZOMBIE_REGEN_INTERVAL, + Handle:CVAR_ZOMBIE_NAPALM, + Handle:CVAR_ZOMBIE_NAPALM_TIME, + Handle:CVAR_ZOMBIE_NOFALLDAMAGE, + Handle:CVAR_ZOMBIE_KILL_BONUS, + Handle:CVAR_ZOMBIE_INFECT_HEALTH, + Handle:CVAR_ZOMBIE_ZVISION, + Handle:CVAR_ZVISION_REDISPLAY, + Handle:CVAR_ZVISION_ALLOW_DISABLE, + Handle:CVAR_DARK, + Handle:CVAR_DARK_LEVEL, + Handle:CVAR_DARK_SKY, + Handle:CVAR_MOTHER_ZOMBIE_RATIO, + Handle:CVAR_MOTHER_ZOMBIE_RESPAWN, + Handle:CVAR_RESPAWN, + Handle:CVAR_RESPAWN_TEAM, + Handle:CVAR_RESPAWN_DELAY, + Handle:CVAR_SUICIDE, + Handle:CVAR_SPAWN_MIN, + Handle:CVAR_SPAWN_MAX, + Handle:CVAR_PROTECT, + Handle:CVAR_CONSECUTIVE_INFECT, + Handle:CVAR_OVERLAYS, + Handle:CVAR_OVERLAYS_HUMAN, + Handle:CVAR_OVERLAYS_ZOMBIE, + Handle:CVAR_ZMARKET_BUYZONE, + Handle:CVAR_ZSPAWN, + Handle:CVAR_ZTELE, + Handle:CVAR_ZTELE_LIMIT, + Handle:CVAR_ZSTUCK, + Handle:CVAR_ZHP, + Handle:CVAR_ZHP_DEFAULT, + Handle:CVAR_CASHFILL, + Handle:CVAR_CASHAMOUNT, + Handle:CVAR_INFECT_FIREBALL, + Handle:CVAR_INFECT_SMOKE, + Handle:CVAR_INFECT_SPARKS, + Handle:CVAR_INFECT_SOUND, + Handle:CVAR_INFECT_ESPLASH, + Handle:CVAR_INFECT_SHAKE, + Handle:CVAR_INFECT_SHAKE_AMP, + Handle:CVAR_INFECT_SHAKE_FREQUENCY, + Handle:CVAR_INFECT_SHAKE_DURATION +} + +new gCvars[ZRSettings]; + +CreateCvars() +{ + gCvars[CVAR_ENABLE] = CreateConVar("zr_enable", "1", "Enable zombie gameplay (0: Disable)"); + gCvars[CVAR_ALLOW_PLAYER_TEAM] = CreateConVar("zr_allow_player_team", "0", "This will allow the player_team event to be fired on first team join, enable when using mani model menu (0: Disable)"); + gCvars[CVAR_AMBIENCE] = CreateConVar("zr_ambience", "1", "Enable creepy ambience to be played throughout the game (0: Disable)"); + gCvars[CVAR_AMBIENCE_FILE] = CreateConVar("zr_ambience_file", "ambient/zr/zr_ambience.mp3", "Path to ambient sound file that will be played throughout the game, when zr_ambience is 1"); + gCvars[CVAR_AMBIENCE_LENGTH] = CreateConVar("zr_ambience_length", "60.0", "The length, in seconds, of the ambient sound file"); + gCvars[CVAR_AMBIENCE_VOLUME] = CreateConVar("zr_ambience_volume", "0.6", "Volume of ambient sounds when zr_ambience is 1 (0.0: Unhearable, 1.0: Max volume)"); + gCvars[CVAR_EMITSOUNDS] = CreateConVar("zr_emitsounds", "50", "How often a zombie emits a sound, in seconds (0: Disable)"); + gCvars[CVAR_CLASSES] = CreateConVar("zr_classes", "1", "Enable zombie classes"); + gCvars[CVAR_CLASSES_SPAWN] = CreateConVar("zr_classes_spawn", "0", "Classmenu is re-displayed every spawn (0: Disable)"); + gCvars[CVAR_CLASSES_RANDOM] = CreateConVar("zr_classes_random", "0", "A random class is assigned to each player every round (0: Disable)"); + gCvars[CVAR_ZOMBIE_HEALTH] = CreateConVar("zr_zombie_health", "5000", "The default health of a zombie"); + gCvars[CVAR_ZOMBIE_SPEED] = CreateConVar("zr_zombie_speed", "350", "How fast zombies travel (300: Default speed, 600: Double speed)"); + gCvars[CVAR_ZOMBIE_JUMP_DISTANCE] = CreateConVar("zr_zombie_jump_distance", "0.1", "How far the zombie jumps, (0: Regular jump distance)"); + gCvars[CVAR_ZOMBIE_JUMP_HEIGHT] = CreateConVar("zr_zombie_jump_height", "10.0", "How high a zombie jumps (0: Regular jump height)"); + gCvars[CVAR_ZOMBIE_KNOCKBACK] = CreateConVar("zr_zombie_knockback", "4", "How far zombies are pushed back when shot (1: Default)"); + gCvars[CVAR_ZOMBIE_NVGS] = CreateConVar("zr_zombie_nvgs", "0", "Zombies will receive nightvision and it will automatically be enabled (0: No night vision, recommended if using zvision)"); + gCvars[CVAR_ZOMBIE_FOV] = CreateConVar("zr_zombie_fov", "110", "The field of vision of zombies (90: Default vision)"); + gCvars[CVAR_ZOMBIE_REGEN] = CreateConVar("zr_zombie_regen", "0", "Zombies will regenerate health"); + gCvars[CVAR_ZOMBIE_REGEN_HEALTH] = CreateConVar("zr_zombie_regen_health", "1", "How much health is regenerated when zr_zombie_regen is 1"); + gCvars[CVAR_ZOMBIE_REGEN_INTERVAL] = CreateConVar("zr_zombie_regen_interval", "5", "How often, in seconds, a zombie regenerates health when zr_zombie_regen is 1"); + gCvars[CVAR_ZOMBIE_NAPALM] = CreateConVar("zr_zombie_napalm", "1", "Turns grenades into napalm grenades that light zombies on fire (0: Disable)"); + gCvars[CVAR_ZOMBIE_NAPALM_TIME] = CreateConVar("zr_zombie_napalm_time", "20", "How long the zombie burns when zr_napalm is 1"); + gCvars[CVAR_ZOMBIE_NOFALLDAMAGE] = CreateConVar("zr_zombie_nofalldamage", "0", "Zombies wont be hurt from falling (0: Disable)"); + gCvars[CVAR_ZOMBIE_KILL_BONUS] = CreateConVar("zr_zombie_kill_bonus", "2", "How many additional kills are rewarded to the killer of the zombie"); + gCvars[CVAR_ZOMBIE_INFECT_HEALTH] = CreateConVar("zr_zombie_infect_health", "100", "How much health a zombie gains when infecting a human (0: Disable)"); + gCvars[CVAR_ZOMBIE_ZVISION] = CreateConVar("zr_zombie_zvision", "overlays/zr/zvision", "Overlay to be shown on all zombies' screen on infection (Leave empty to disable)"); + gCvars[CVAR_ZVISION_REDISPLAY] = CreateConVar("zr_zvision_redisplay", "0.2", "Frequency, in seconds, to display zvision on the client's screen (Never go below 0.1, 0.2 seems safe)"); + gCvars[CVAR_ZVISION_ALLOW_DISABLE] = CreateConVar("zr_zvision_allow_disable", "1", "Allow users to disable ZVision with their nightvision key (0: Disable)"); + gCvars[CVAR_DARK] = CreateConVar("zr_dark", "0", "Default value for darkening maps, most dislike this feature (0: Disable)"); + gCvars[CVAR_DARK_LEVEL] = CreateConVar("zr_dark_level", "a", "The darkness of the map, a being the darkest, z being extremely bright when zr_dark is 1 (n: Default)"); + gCvars[CVAR_DARK_SKY] = CreateConVar("zr_dark_sky", "sky_borealis01", "The sky the map will have when zr_dark is 1"); + gCvars[CVAR_MOTHER_ZOMBIE_RATIO] = CreateConVar("zr_mother_zombie_ratio", "5", "For every 'x' number of humans, there will be 1 zombie (0: Always only 1 mother zombie)"); + gCvars[CVAR_MOTHER_ZOMBIE_RESPAWN] = CreateConVar("zr_mother_zombie_respawn", "0", "First zombie(s) will be teleported back to spawn when infected (0: Disable)"); + gCvars[CVAR_RESPAWN] = CreateConVar("zr_respawn", "0", "When player is killed, player will respawn"); + gCvars[CVAR_RESPAWN_TEAM] = CreateConVar("zr_respawn_team", "zombie", "Which team to respawn player as (Choices: zombie, human)"); + gCvars[CVAR_RESPAWN_DELAY] = CreateConVar("zr_respawn_delay", "1", "How long to wait after death to respawn, in seconds"); + gCvars[CVAR_SUICIDE] = CreateConVar("zr_suicide", "1", "Stops players from suiciding"); + gCvars[CVAR_SPAWN_MIN] = CreateConVar("zr_spawn_min", "30", "Minimum time a player is picked to be zombie after the round starts, in seconds"); + gCvars[CVAR_SPAWN_MAX] = CreateConVar("zr_spawn_max", "50", "Maximum time a player is picked to be zombie after the round starts, in seconds"); + gCvars[CVAR_PROTECT] = CreateConVar("zr_protect", "10", "Players that join late will be protected for this long, in seconds (0: Disable)"); + gCvars[CVAR_CONSECUTIVE_INFECT] = CreateConVar("zr_consecutive_infect", "0", "Allow player to be randomly chosen twice in a row to be a mother zombie (0: Disable)"); + gCvars[CVAR_OVERLAYS] = CreateConVar("zr_overlays", "1", "Will show overlays that tell who the winner of the round was (0: Disable)"); + gCvars[CVAR_OVERLAYS_HUMAN] = CreateConVar("zr_overlays_human", "overlays/zr/humans_win", "The overlay shown to tell everyone that humans won when zr_overlays is 1"); + gCvars[CVAR_OVERLAYS_ZOMBIE] = CreateConVar("zr_overlays_zombie", "overlays/zr/zombies_win", "The overlay shown to tell everyone that zombies won when zr_overlays is 1"); + gCvars[CVAR_ZMARKET_BUYZONE] = CreateConVar("zr_zmarket_buyzone", "1", "Must be in buyzone to access !zmarket, if Market is installed (0: Can be used anywhere)"); + gCvars[CVAR_ZSPAWN] = CreateConVar("zr_zspawn", "1", "Allow players to spawn if they just joined the game (0: Disable)"); + gCvars[CVAR_ZTELE] = CreateConVar("zr_ztele", "1", "Allows zombies who get stuck to teleport back to spawn (0: Disable)"); + gCvars[CVAR_ZTELE_LIMIT] = CreateConVar("zr_ztele_limit", "1", "Max amount of teleports per round when zr_ztele is 1 (0: Unlimited)"); + gCvars[CVAR_ZSTUCK] = CreateConVar("zr_zstuck", "1", "Allow players that are stuck together to get unstuck (0: Disable)"); + gCvars[CVAR_ZHP] = CreateConVar("zr_zhp", "1", "Allows clients to enable/disable zombie health display (1: On, 0: Off)"); + gCvars[CVAR_ZHP_DEFAULT] = CreateConVar("zr_zhp_default", "1", "The default value of zombie health display to new clients (1: On, 0: Off)"); + gCvars[CVAR_CASHFILL] = CreateConVar("zr_cashfill", "1", "Enable the mod to set the players cash to zr_cashamount (0: Disabled)"); + gCvars[CVAR_CASHAMOUNT] = CreateConVar("zr_cashamount", "12000", "How much money players will have when they spawn when zr_cashfill is 1"); + gCvars[CVAR_INFECT_FIREBALL] = CreateConVar("zr_infect_fireball", "1", "A fireball is created when a player is infected ( 0: Disable)"); + gCvars[CVAR_INFECT_SMOKE] = CreateConVar("zr_infect_smoke", "1", "A puff of smoke is created when a player is infected (0: Disable)"); + gCvars[CVAR_INFECT_SPARKS] = CreateConVar("zr_infect_sparks", "1", "Sparks are emitted when a player is infected (0: Disable)"); + gCvars[CVAR_INFECT_SOUND] = CreateConVar("zr_infect_sound", "npc/fast_zombie/fz_scream1.wav", "Sound played from from player on infection (Leave blank to disable)"); + gCvars[CVAR_INFECT_ESPLASH] = CreateConVar("zr_infect_esplash", "1", "An energy splash is emitted when player is infected ( 0: Disable)"); + gCvars[CVAR_INFECT_SHAKE] = CreateConVar("zr_infect_shake", "1", "Player's screen is shaken on infection (0: Disable)"); + gCvars[CVAR_INFECT_SHAKE_AMP] = CreateConVar("zr_infect_shake_amp", "15.0", "Amplitude of the shake, when zr_infect_shake is 1"); + gCvars[CVAR_INFECT_SHAKE_FREQUENCY] = CreateConVar("zr_infect_shake_frequency", "1.0", "Frequency of the shake, when zr_infect_shake is 1"); + gCvars[CVAR_INFECT_SHAKE_DURATION] = CreateConVar("zr_infect_shake_duration", "5.0", "Duration of the shake, when zr_infect_shake is 1"); + + HookConVarChange(gCvars[CVAR_ENABLE], EnableHook); + + AutoExecConfig(true, "zombiereloaded", "sourcemod/zombiereloaded"); +} + +HookCvars() +{ + SetConVarBool(FindConVar("mp_autoteambalance"), false); + SetConVarInt(FindConVar("mp_limitteams"), 0); + + HookConVarChange(FindConVar("mp_autoteambalance"), AutoTeamBalanceHook); + HookConVarChange(FindConVar("mp_limitteams"), LimitTeamsHook); + + HookConVarChange(FindConVar("mp_restartgame"), RestartGameHook); +} + +UnhookCvars() +{ + UnhookConVarChange(FindConVar("mp_autoteambalance"), AutoTeamBalanceHook); + UnhookConVarChange(FindConVar("mp_limitteams"), LimitTeamsHook); + + UnhookConVarChange(FindConVar("mp_restartgame"), RestartGameHook); +} + +public EnableHook(Handle:convar, const String:oldValue[], const String:newValue[]) +{ + new bool:enable = bool:StringToInt(newValue); + + if (enable) + { + HookEvents(); + HookCvars(); + + TerminateRound(3.0, Game_Commencing); + } + else + { + ZREnd(); + } +} + +public AutoTeamBalanceHook(Handle:convar, const String:oldValue[], const String:newValue[]) +{ + SetConVarBool(FindConVar("mp_autoteambalance"), false); +} + +public LimitTeamsHook(Handle:convar, const String:oldValue[], const String:newValue[]) +{ + SetConVarInt(FindConVar("mp_limitteams"), 0); +} \ No newline at end of file diff --git a/src/zr/damagecontrol.inc b/src/zr/damagecontrol.inc new file mode 100644 index 0000000..a6f8bef --- /dev/null +++ b/src/zr/damagecontrol.inc @@ -0,0 +1,178 @@ +/** + * ==================== + * Zombie:Reloaded + * File: damagecontrol.inc + * Author: Greyscale + * ==================== + */ + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_DROWN (1 << 14) // Drowning + +enum ZRHooks +{ + Hook_TraceAttack, + Hook_OnTakeDamage +} + +new gHooks[MAXPLAYERS+1][ZRHooks]; + +InitDmgControl() +{ + RegConsoleCmd("kill", Attempt_Suicide); + RegConsoleCmd("jointeam", Attempt_Suicide); + RegConsoleCmd("spectate", Attempt_Suicide); +} + +ClientHookAttack(client) +{ + gHooks[client][Hook_TraceAttack] = Hacks_Hook(client, HACKS_HTYPE_TRACEATTACK, TraceAttack, false); + gHooks[client][Hook_OnTakeDamage] = Hacks_Hook(client, HACKS_HTYPE_ONTAKEDAMAGE, OnTakeDamage, false); +} + +ClientUnHookAttack(client) +{ + Hacks_Unhook(gHooks[client][Hook_TraceAttack]); + Hacks_Unhook(gHooks[client][Hook_OnTakeDamage]); +} + +public TraceAttack(client, inflictor, attacker, damage, hitbox, hitgroup) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + + if (!attacker || !IsClientInGame(attacker) || !enabled) + { + return Hacks_Continue; + } + + if (IsPlayerZombie(client) && IsPlayerZombie(attacker)) + { + return 0; + } + + if (IsPlayerHuman(client) && IsPlayerHuman(attacker)) + { + return 0; + } + + return Hacks_Continue; +} + +public OnTakeDamage(client, inflictor, attacker, damage, damagetype, ammotype) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (!enabled) + { + return Hacks_Continue; + } + + decl String:classname[64]; + GetEdictClassname(inflictor, classname, sizeof(classname)); + if (StrContains(classname, "trigger") > -1) + { + return Hacks_Continue; + } + + if (damagetype & DMG_FALL) + { + if (!IsPlayerZombie(client)) + { + return Hacks_Continue; + } + + new bool:blockfalldamage = GetClassNoFallDamage(pClass[client]); + if (!blockfalldamage) + { + return Hacks_Continue; + } + + return 0; + } + + if (damagetype & DMG_BLAST) + { + if (!IsPlayerHuman(client) || !IsClientInGame(attacker)) + { + return Hacks_Continue; + } + + return 0; + } + + if (damagetype & DMG_BULLET) + { + if (!client || !IsClientInGame(client)) + { + return Hacks_Continue; + } + + if (!attacker || !IsClientInGame(attacker)) + { + return Hacks_Continue; + } + + if (IsPlayerZombie(client) && IsPlayerHuman(attacker)) + { + return Hacks_Continue; + } + + if (IsPlayerHuman(client) && IsPlayerZombie(attacker)) + { + new health = GetClientHealth(client); + SetEntityHealth(client, health + damage); + + return Hacks_Continue; + } + + return 0; + } + + return Hacks_Continue; +} + +public Action:Attempt_Suicide(client, argc) +{ + if (!client) + { + return Plugin_Continue; + } + + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (!enabled) + { + return Plugin_Continue; + } + + new bool:suicide = GetConVarBool(gCvars[CVAR_SUICIDE]); + if (!suicide) + { + return Plugin_Continue; + } + + decl String:cmd[16]; + GetCmdArg(0, cmd, sizeof(cmd)); + + if (!IsPlayerZombie(client) && StrEqual(cmd, "spectate", false)) + { + return Plugin_Continue; + } + + if (!IsPlayerZombie(client) && !GetConVarBool(gCvars[CVAR_RESPAWN])) + { + return Plugin_Continue; + } + + if (!IsPlayerAlive(client)) + { + return Plugin_Continue; + } + + ZR_ReplyToCommand(client, "Suicide text"); + + return Plugin_Handled; +} \ No newline at end of file diff --git a/src/zr/event.inc b/src/zr/event.inc new file mode 100644 index 0000000..bc96752 --- /dev/null +++ b/src/zr/event.inc @@ -0,0 +1,465 @@ +/** + * ==================== + * Zombie:Reloaded + * File: events.inc + * Author: Greyscale + * ==================== + */ + +HookEvents() +{ + HookEvent("round_start", RoundStart); + HookEvent("round_freeze_end", RoundFreezeEnd); + HookEvent("round_end", RoundEnd); + HookEvent("player_team", PlayerTeam, EventHookMode_Pre); + HookEvent("player_spawn", PlayerSpawn); + HookEvent("player_hurt", PlayerHurt); + HookEvent("player_death", PlayerDeath); + HookEvent("player_jump", PlayerJump); +} + +UnhookEvents() +{ + UnhookEvent("round_start", RoundStart); + UnhookEvent("round_freeze_end", RoundFreezeEnd); + UnhookEvent("round_end", RoundEnd); + UnhookEvent("player_team", PlayerTeam, EventHookMode_Pre); + UnhookEvent("player_spawn", PlayerSpawn); + UnhookEvent("player_hurt", PlayerHurt); + UnhookEvent("player_death", PlayerDeath); + UnhookEvent("player_jump", PlayerJump); +} + +public Action:RoundStart(Handle:event, const String:name[], bool:dontBroadcast) +{ + ChangeLightStyle(); + + RestartAmbience(); + + RefreshList(); + + if (tRound != INVALID_HANDLE) + { + CloseHandle(tRound); + tRound = INVALID_HANDLE; + } + + if (tInfect != INVALID_HANDLE) + { + CloseHandle(tInfect); + tInfect = INVALID_HANDLE; + } + + + ZR_PrintToChat(0, "Round objective"); +} + +public Action:RoundFreezeEnd(Handle:event, const String:name[], bool:dontBroadcast) +{ + RemoveObjectives(); + + if (tRound != INVALID_HANDLE) + { + CloseHandle(tRound); + } + + new Float:roundlen = GetConVarFloat(FindConVar("mp_roundtime")) * 60.0; + tRound = CreateTimer(roundlen, RoundOver, _, TIMER_FLAG_NO_MAPCHANGE); + + if (tInfect != INVALID_HANDLE) + { + CloseHandle(tInfect); + } + + new Float:min = GetConVarFloat(gCvars[CVAR_SPAWN_MIN]); + new Float:max = GetConVarFloat(gCvars[CVAR_SPAWN_MAX]); + new Float:randlen = GetRandomFloat(min, max); + tInfect = CreateTimer(randlen, MotherZombie, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action:RoundEnd(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (tRound != INVALID_HANDLE) + { + CloseHandle(tRound); + tRound = INVALID_HANDLE; + } + + if (tInfect != INVALID_HANDLE) + { + CloseHandle(tInfect); + tInfect = INVALID_HANDLE; + } + + zombieSpawned = false; + + new maxplayers = GetMaxClients(); + for (new x = 1; x<= maxplayers; x++) + { + if (!IsClientInGame(x)) + { + continue; + } + + gZombie[x] = false; + + new bool:consecutive_infect = GetConVarBool(gCvars[CVAR_CONSECUTIVE_INFECT]); + gBlockMotherInfect[x] = consecutive_infect ? false : motherZombie[x]; + } + + BalanceTeams(); + + new reason = GetEventInt(event, "reason"); + + if (reason == CTs_PreventEscape) + { + ShowOverlays(5.0, Human); + } + else if (reason == Terrorists_Escaped) + { + ShowOverlays(5.0, Zombie); + } +} + +public Action:PlayerTeam(Handle:event, const String:name[], bool:dontBroadcast) +{ + new index = GetClientOfUserId(GetEventInt(event, "userid")); + new team = GetEventInt(event, "team"); + + if (team == 1) + { + gZombie[index] = false; + motherZombie[index] = false; + } + + new bool:allow_player_team = GetConVarBool(gCvars[CVAR_ALLOW_PLAYER_TEAM]); + if (allow_player_team && !IsPlayerInList(index)) + { + return Plugin_Continue; + } + + return Plugin_Handled; +} + +public Action:PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast) +{ + new index = GetClientOfUserId(GetEventInt(event, "userid")); + + for (new x = 0; x < MAXTIMERS; x++) + { + if (x == TRESPAWN) + { + continue; + } + + if (tHandles[index][x] != INVALID_HANDLE) + { + CloseHandle(tHandles[index][x]); + tHandles[index][x] = INVALID_HANDLE; + } + } + + gZombie[index] = false; + motherZombie[index] = false; + + SetPlayerFOV(index, 90); + + ClientCommand(index, "r_screenoverlay \"\""); + + new team = GetClientTeam(index); + if (team != CS_TEAM_T && team != CS_TEAM_CT) + { + return; + } + + new bool:cashfill = GetConVarBool(gCvars[CVAR_CASHFILL]); + if (cashfill) + { + new cash = GetConVarInt(gCvars[CVAR_CASHAMOUNT]); + SetPlayerMoney(index, cash); + } + + teleCount[index] = 0; + GetClientAbsOrigin(index, spawnLoc[index]); + + NightVisionOn(index, false); + NightVision(index, false); + + if (pNextClass[index] != -1) + { + Call_StartForward(hOnZClassChanged); + Call_PushCell(index); + Call_PushCell(pClass[index]); + Call_PushCell(pNextClass[index]); + Call_Finish(); + + pClass[index] = pNextClass[index]; + pNextClass[index] = -1; + } + + pProtect[index] = false; + if (zombieSpawned) + { + if (team == CS_TEAM_T) + { + CS_SwitchTeam(index, CS_TEAM_CT); + CS_RespawnPlayer(index); + } + + new protect = GetConVarInt(gCvars[CVAR_PROTECT]); + if (protect > 0) + { + decl String:respawnteam[32]; + GetConVarString(gCvars[CVAR_RESPAWN_TEAM], respawnteam, sizeof(respawnteam)); + + if (!StrEqual(respawnteam, "zombie", false)) + { + pProtect[index] = true; + + ZR_PrintToChat(index, "Spawn protection begin", protect); + ZR_PrintCenterText(index, "Spawn protection begin", protect); + + if (tHandles[index][TPROTECT] != INVALID_HANDLE) + { + CloseHandle(tHandles[index][TPROTECT]); + } + + tHandles[index][TPROTECT] = CreateTimer(float(protect), EndProtect, index, TIMER_FLAG_NO_MAPCHANGE); + } + } + } + + new bool:randomclass = GetConVarBool(gCvars[CVAR_CLASSES_RANDOM]); + + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + new bool:showmenu = GetConVarBool(gCvars[CVAR_CLASSES_SPAWN]); + if (showmenu && !randomclass) + { + ClassMenu(index); + } + } + + ZR_PrintToChat(index, "!zmenu reminder"); + + decl String:steamid[16]; + GetClientAuthString(index, steamid, sizeof(steamid)); + if (StrEqual(steamid, "BOT") || randomclass) + { + new class = GetRandomInt(0, classCount - 1); + + Call_StartForward(hOnZClassChanged); + Call_PushCell(index); + Call_PushCell(pClass[index]); + Call_PushCell(class); + Call_Finish(); + + pClass[index] = class; + + decl String:classname[32]; + GetClassName(class, classname, sizeof(classname)); + + ZR_PrintToChat(index, "Auto-assign", classname); + } +} + +public Action:PlayerHurt(Handle:event, const String:name[], bool:dontBroadcast) +{ + new index = GetClientOfUserId(GetEventInt(event, "userid")); + new attacker = GetClientOfUserId(GetEventInt(event, "attacker")); + + new dmg = GetEventInt(event, "dmg_health"); + + decl String:weapon[32]; + GetEventString(event, "weapon", weapon, sizeof(weapon)); + + if (attacker != 0) + { + if (IsPlayerHuman(index) && IsPlayerZombie(attacker)) + { + if (StrEqual(weapon, "knife")) + { + Zombify(index, attacker); + } + } + else if (IsPlayerHuman(attacker)) + { + new Float:knockback = GetClassKnockback(pClass[index]); + + new Float:clientloc[3]; + new Float:attackerloc[3]; + + GetClientAbsOrigin(index, clientloc); + + if (!StrEqual(weapon, "hegrenade")) + { + GetClientAbsOrigin(attacker, attackerloc); + + new bool:shotgun = (StrEqual(weapon, "m3") || StrEqual(weapon, "xm1014")); + + KnockBack(index, clientloc, attackerloc, knockback, dmg, shotgun); + } + else + { + new Float:heLoc[3]; + FindExplodingGrenade(heLoc); + + KnockBack(index, clientloc, heLoc, knockback, dmg, true); + } + } + } + + if (!IsPlayerZombie(index)) + { + return; + } + + if (GetRandomInt(1, 5) == 1) + { + decl String:sound[64]; + new randsound = GetRandomInt(1, 6); + + Format(sound, sizeof(sound), "npc/zombie/zombie_pain%d.wav", randsound); + + PrecacheSound(sound); + EmitSoundToAll(sound, index); + } + + new bool:napalm = GetClassNapalm(pClass[index]); + if (napalm) + { + if (StrEqual(weapon, "hegrenade", false)) + { + new Float:napalm_time = GetClassNapalmTime(pClass[index]); + IgniteEntity(index, napalm_time); + } + } + + UpdateHPDisplay(index); + + if (GetClassRegen(pClass[index])) + { + if (tHandles[index][TREGEN] == INVALID_HANDLE) + { + new Float:interval = GetClassRegenInterval(pClass[index]); + tHandles[index][TREGEN] = CreateTimer(interval, Regenerate, index, TIMER_REPEAT); + } + } +} + +FindExplodingGrenade(Float:heLoc[3]) +{ + decl String:classname[64]; + + new maxentities = GetMaxEntities(); + for (new x = GetMaxClients(); x <= maxentities; x++) + { + if (IsValidEdict(x)) + { + GetEdictClassname(x, classname, sizeof(classname)); + if (StrEqual(classname, "hegrenade_projectile")) + { + new takedamage = GetEntProp(x, Prop_Data, "m_takedamage"); + if (takedamage == 0) + { + GetEntPropVector(x, Prop_Send, "m_vecOrigin", heLoc); + return x; + } + } + } + } + + return -1; +} + +public Action:PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast) +{ + new index = GetClientOfUserId(GetEventInt(event, "userid")); + new attacker = GetClientOfUserId(GetEventInt(event, "attacker")); + + SetPlayerFOV(index, DEFAULT_FOV); + + ExtinguishEntity(index); + + decl String:weapon[32]; + + GetEventString(event, "weapon", weapon, sizeof(weapon)); + if (StrEqual(weapon, "zombie_claws_of_death", false)) + { + if (index) + { + AddPlayerDeath(index, 1); + } + + if (attacker) + { + AddPlayerScore(attacker, 1); + + new healthgain = GetClassInfectHealth(pClass[attacker]); + new health = GetClientHealth(attacker); + + SetEntityHealth(attacker, health + healthgain); + + UpdateHPDisplay(attacker); + } + } + else + { + if (IsPlayerZombie(index)) + { + decl String:sound[64]; + + new randsound = GetRandomInt(1, 3); + Format(sound, sizeof(sound), "npc/zombie/zombie_die%d.wav", randsound); + + PrecacheSound(sound); + EmitSoundToAll(sound, index); + + if (attacker) + { + new bonus = GetClassKillBonus(pClass[index]); + AddPlayerScore(attacker, bonus); + } + } + + for (new x = 0; x < MAXTIMERS; x++) + { + if (tHandles[index][x] != INVALID_HANDLE) + { + CloseHandle(tHandles[index][x]); + tHandles[index][x] = INVALID_HANDLE; + } + } + + new bool:respawn = GetConVarBool(gCvars[CVAR_RESPAWN]); + if (respawn) + { + new Float:delay = GetConVarFloat(gCvars[CVAR_RESPAWN_DELAY]); + tHandles[index][TRESPAWN] = CreateTimer(delay, RespawnTimer, index, TIMER_FLAG_NO_MAPCHANGE); + } + } + + new ZTeam:team = IsRoundOver(); + if (team == Neither) + { + ClientCommand(index, "r_screenoverlay \"\""); + + return; + } + + RoundWin(team); +} + +public Action:PlayerJump(Handle:event, const String:name[], bool:dontBroadcast) +{ + new index = GetClientOfUserId(GetEventInt(event, "userid")); + + if (IsPlayerZombie(index)) + { + new Float:distance = GetClassJumpDistance(pClass[index]); + new Float:height = GetClassJumpHeight(pClass[index]); + + JumpBoost(index, distance, height); + } +} \ No newline at end of file diff --git a/src/zr/global.inc b/src/zr/global.inc new file mode 100644 index 0000000..e29d3c9 --- /dev/null +++ b/src/zr/global.inc @@ -0,0 +1,36 @@ +/** + * ==================== + * Zombie:Reloaded + * File: global.inc + * Author: Greyscale + * ==================== + */ + +new Handle:hZombify = INVALID_HANDLE; +new Handle:hOnZClassChanged = INVALID_HANDLE; + +CreateGlobals() +{ + CreateNative("ZR_IsClientZombie", Native_IsClientZombie); + CreateNative("ZR_GetClientZClass", Native_GetClientZClass); + CreateNative("ZR_HasZombieSpawned", Native_HasZombieSpawned); + + hZombify = CreateGlobalForward("ZR_Zombify", ET_Ignore, Param_Cell, Param_Cell); + hOnZClassChanged = CreateGlobalForward("ZR_OnZClassChanged", ET_Ignore, Param_Cell, Param_Cell, Param_Cell); +} + +public Native_IsClientZombie(Handle:plugin, argc) +{ + return gZombie[GetNativeCell(1)]; +} + +public Native_GetClientZClass(Handle:plugin, argc) +{ + new class = GetNativeCell(1); + return pClass[class]; +} + +public Native_HasZombieSpawned(Handle:plugin, argc) +{ + return zombieSpawned; +} \ No newline at end of file diff --git a/src/zr/menu.inc b/src/zr/menu.inc new file mode 100644 index 0000000..e69d059 --- /dev/null +++ b/src/zr/menu.inc @@ -0,0 +1,175 @@ +/** + * ==================== + * Zombie:Reloaded + * File: menu.sp + * Author: Greyscale + * ==================== + */ + +MainMenu(client) +{ + new Handle:menu_main = CreateMenu(MainMenuHandle); + + SetGlobalTransTarget(client); + + SetMenuTitle(menu_main, "%t\n ", "!zmenu title"); + + decl String:zmenu[128]; + decl String:zclass[128]; + decl String:zmarket[128]; + decl String:zspawn[128]; + decl String:ztele[128]; + decl String:zstuck[128]; + decl String:zhp[128]; + + Format(zmenu, sizeof(zmenu), "%t", "!zmenu menu"); + Format(zclass, sizeof(zclass), "%t", "!zmenu class"); + Format(zmarket, sizeof(zmarket), "%t", "!zmenu market"); + Format(zspawn, sizeof(zspawn), "%t", "!zmenu spawn"); + Format(ztele, sizeof(ztele), "%t", "!zmenu tele"); + Format(zstuck, sizeof(zstuck), "%t", "!zmenu stuck"); + Format(zhp, sizeof(zhp), "%t (%d HP)", "!zmenu hp", GetClientHealth(client)); + + AddMenuItem(menu_main, "zmenu", zmenu, ITEMDRAW_DISABLED); + AddMenuItem(menu_main, "zclass", zclass); + + if (market) + { + AddMenuItem(menu_main, "zmarket", zmarket); + } + else + { + AddMenuItem(menu_main, "zmarket", zmarket, ITEMDRAW_DISABLED); + } + + AddMenuItem(menu_main, "zspawn", zspawn); + AddMenuItem(menu_main, "ztele", ztele); + AddMenuItem(menu_main, "zstuck", zstuck); + AddMenuItem(menu_main, "zhp", zhp); + + DisplayMenu(menu_main, client, MENU_TIME_FOREVER); +} + +public MainMenuHandle(Handle:menu_main, MenuAction:action, client, slot) +{ + if (action == MenuAction_Select) + { + switch(slot) + { + case 1: + { + if (!ZClass(client)) + { + MainMenu(client); + } + } + case 2: + { + if (!ZMarket(client)) + { + MainMenu(client); + } + } + case 3: + { + ZSpawn(client); + MainMenu(client); + } + case 4: + { + ZTele(client); + MainMenu(client); + } + case 5: + { + ZStuck(client); + MainMenu(client); + } + case 6: + { + ZHP(client); + MainMenu(client); + } + } + } + if (action == MenuAction_End) + { + CloseHandle(menu_main); + } +} + +ClassMenu(client) +{ + new Handle:menu_classes = CreateMenu(ClassMenuHandle); + + decl String:menu_description[256]; + decl String:display[288]; // 32 + 256 + + SetGlobalTransTarget(client); + + SetMenuTitle(menu_classes, "%t\n ", "!zclass title"); + + for (new x = 0; x < classCount; x++) + { + GetClassName(x, display, sizeof(display)); + GetClassMenuDescription(x, menu_description, sizeof(menu_description)); + + if (pNextClass[client] == -1) + { + if (x == pClass[client]) + { + Format(display, sizeof(display), "%s (current)", display); + } + } + else if (x == pNextClass[client]) + { + Format(display, sizeof(display), "%s (current)", display); + } + + Format(display, sizeof(display), "%s\n %s", display, menu_description); + + AddMenuItem(menu_classes, "", display); + } + + SetMenuExitBackButton(menu_classes, true); + + DisplayMenu(menu_classes, client, MENU_TIME_FOREVER); +} + +public ClassMenuHandle(Handle:menu_classes, MenuAction:action, client, slot) +{ + if (action == MenuAction_Select) + { + if (IsPlayerHuman(client) || !IsPlayerAlive(client)) + { + Call_StartForward(hOnZClassChanged); + Call_PushCell(client); + Call_PushCell(pClass[client]); + Call_PushCell(slot); + Call_Finish(); + + pClass[client] = slot; + } + else + { + pNextClass[client] = slot; + } + + decl String:name[64]; + GetClassName(slot, name, sizeof(name)); + + ZR_PrintToChat(client, "Class select", name); + } + + if (action == MenuAction_Cancel) + { + if (slot == MenuCancel_ExitBack) + { + MainMenu(client); + } + } + else if (action == MenuAction_End) + { + CloseHandle(menu_classes); + } +} \ No newline at end of file diff --git a/src/zr/models.inc b/src/zr/models.inc new file mode 100644 index 0000000..631f865 --- /dev/null +++ b/src/zr/models.inc @@ -0,0 +1,139 @@ +/** + * ==================== + * Zombie:Reloaded + * File: models.inc + * Author: Greyscale + * ==================== + */ + +new String:modelSuffix[8][16] = {".dx80.vtx", ".dx90.vtx", ".mdl", ".phy", ".sw.vtx", ".vvd", ".xbox", ".xbox.vtx"}; + +new Handle:arrayModels = INVALID_HANDLE; + +FileLinesToArray(Handle:array, const Handle:file) +{ + ClearArray(array); + + decl String:line[128]; + + while(!IsEndOfFile(file) && ReadFileLine(file, line, sizeof(line))) + { + if (StrContains(line, ";") == -1) + { + if (StrContains(line, "//") > -1) + { + SplitString(line, "//", line, sizeof(line)); + } + TrimString(line); + + if (!StrEqual(line, "", false)) + { + PushArrayString(array, line); + } + } + } +} + +LoadModelData() +{ + decl String:path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "configs/zr/models.txt"); + + if (arrayModels != INVALID_HANDLE) + { + CloseHandle(arrayModels); + } + + arrayModels = CreateArray(256, 0); + + new Handle:fileModels = OpenFile(path, "r"); + if (fileModels == INVALID_HANDLE) + { + SetFailState("\"%s\" missing from server", path); + } + + FileLinesToArray(arrayModels, fileModels); + + if (!GetArraySize(arrayModels)) + { + SetFailState("No models listed in models.txt, please add some models then restart"); + } + + decl String:model[256]; + decl String:modelpath[256]; + + new modelsize = GetArraySize(arrayModels); + for (new x = 0; x < modelsize; x++) + { + for (new y = 0; y < 8; y++) + { + GetArrayString(arrayModels, x, model, sizeof(model)); + Format(modelpath, sizeof(modelpath), "%s%s", model, modelSuffix[y]); + if (FileExists(modelpath)) + { + AddFileToDownloadsTable(modelpath); + } + } + } + + CloseHandle(fileModels); +} + +LoadDownloadData() +{ + decl String:path[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, path, sizeof(path), "configs/zr/downloads.txt"); + + new Handle:fileDownloads = OpenFile(path, "r"); + + if (fileDownloads == INVALID_HANDLE) + { + SetFailState("\"%s\" missing from server", path); + } + + new Handle:arrayDownloads = CreateArray(256, 0); + + FileLinesToArray(arrayDownloads, fileDownloads); + + decl String:file[256]; + + new downloadsize = GetArraySize(arrayDownloads); + for (new x = 0; x < downloadsize; x++) + { + GetArrayString(arrayDownloads, x, file, sizeof(file)); + if (FileExists(file)) + { + AddFileToDownloadsTable(file); + } + else + { + ZR_LogMessage("File load failed", file); + } + } + + CloseHandle(fileDownloads); + CloseHandle(arrayDownloads); +} + +ApplyZombieModel(client) +{ + decl String:modelpath[256]; + + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (classes) + { + GetClassModel(pClass[client], modelpath, sizeof(modelpath)); + if (!StrEqual(modelpath, "default", false)) + { + SetPlayerModel(client, modelpath); + return; + } + } + + new randmodel = GetRandomInt(0, GetArraySize(arrayModels) - 1); + GetArrayString(arrayModels, randmodel, modelpath, sizeof(modelpath)); + Format(modelpath, sizeof(modelpath), "%s.mdl", modelpath); + + SetPlayerModel(client, modelpath); +} + diff --git a/src/zr/offsets.inc b/src/zr/offsets.inc new file mode 100644 index 0000000..646ed77 --- /dev/null +++ b/src/zr/offsets.inc @@ -0,0 +1,190 @@ +/** + * ==================== + * Zombie:Reloaded + * File: offsets.inc + * Author: Greyscale + * ==================== + */ + +new offsBaseVelocity; +new offsGetVelocity0; +new offsGetVelocity1; +new offsGetVelocity2; +new offsSpeed; +new offsNVG; +new offsNVGOn; +new offsCollision; +new offsMoney; +new offsFOV; +new offsBuyZone; + +new Handle:hGameConf = INVALID_HANDLE; +new Handle:hRemoveAllItems = INVALID_HANDLE; +new Handle:hTerminateRound = INVALID_HANDLE; + +FindOffsets() +{ + offsBaseVelocity = FindSendPropInfo("CBasePlayer", "m_vecBaseVelocity"); + if (offsBaseVelocity == -1) + { + SetFailState("Couldn't find \"m_vecBaseVelocity\"!"); + } + + offsGetVelocity0 = FindSendPropInfo("CBasePlayer", "m_vecVelocity[0]"); + if (offsGetVelocity0 == -1) + { + SetFailState("Couldn't find \"m_vecVelocity[0]\"!"); + } + + offsGetVelocity1 = FindSendPropInfo("CBasePlayer", "m_vecVelocity[1]"); + if (offsGetVelocity1 == -1) + { + SetFailState("Couldn't find \"m_vecVelocity[1]\"!"); + } + + offsGetVelocity2 = FindSendPropInfo("CBasePlayer", "m_vecVelocity[2]"); + if (offsGetVelocity2 == -1) + { + SetFailState("Couldn't find \"m_vecVelocity[2]\"!"); + } + + offsSpeed = FindSendPropInfo("CCSPlayer", "m_flLaggedMovementValue"); + if (offsSpeed == -1) + { + SetFailState("Couldn't find \"m_flLaggedMovementValue\"!"); + } + + offsNVG = FindSendPropInfo("CCSPlayer", "m_bHasNightVision"); + if (offsNVG == -1) + { + SetFailState("Couldn't find \"m_bHasNightVision\"!"); + } + + offsNVGOn = FindSendPropInfo("CCSPlayer", "m_bNightVisionOn"); + if (offsNVGOn == -1) + { + SetFailState("Couldn't find \"m_bNightVisionOn\"!"); + } + + offsCollision = FindSendPropInfo("CBaseEntity", "m_CollisionGroup"); + if (offsCollision == -1) + { + SetFailState("Couldn't find \"m_CollisionGroup\"!"); + } + + offsMoney = FindSendPropInfo("CCSPlayer", "m_iAccount"); + if (offsMoney == -1) + { + SetFailState("Couldn't find \"m_iAccount\"!"); + } + + offsFOV = FindSendPropInfo("CBasePlayer", "m_iDefaultFOV"); + if (offsFOV == -1) + { + SetFailState("Couldn't find \"m_iDefaultFOV\"!"); + } + + offsBuyZone = FindSendPropInfo("CCSPlayer", "m_bInBuyZone"); + if (offsBuyZone == -1) + { + SetFailState("Couldn't find \"m_bInBuyZone\"!"); + } +} + +SetupGameData() +{ + hGameConf = LoadGameConfigFile("plugin.zombiereloaded"); + + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "RemoveAllItems"); + hRemoveAllItems = EndPrepSDKCall(); + + StartPrepSDKCall(SDKCall_GameRules); + PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "TerminateRound"); + PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + hTerminateRound = EndPrepSDKCall(); +} + +SetPlayerVelocity(client, const Float:vec[3]) +{ + SetEntDataVector(client, offsBaseVelocity, vec, true); +} + +GetPlayerVelocity(client, Float:vel[3]) +{ + vel[0] = GetEntDataFloat(client, offsGetVelocity0); + vel[1] = GetEntDataFloat(client, offsGetVelocity1); + vel[2] = GetEntDataFloat(client, offsGetVelocity2); +} + +SetPlayerSpeed(client, Float:speed) +{ + new Float:newspeed = speed / 300.0; + SetEntDataFloat(client, offsSpeed, newspeed, true); +} + +NightVision(client, bool:enable) +{ + SetEntData(client, offsNVG, enable, 1, true); +} + +NightVisionOn(client, bool:enable) +{ + SetEntData(client, offsNVGOn, enable, 1, true); +} + +NoCollide(client, bool:nocollide) +{ + if (nocollide) + { + SetEntData(client, offsCollision, 2, 1, true); + } + else + { + SetEntData(client, offsCollision, 5, 1, true); + } +} + +SetPlayerMoney(client, amount) +{ + SetEntData(client, offsMoney, amount, 4, true); +} + +SetPlayerFOV(client, fov) +{ + SetEntData(client, offsFOV, fov, 1, true); +} + +bool:IsClientInBuyZone(client) +{ + return bool:GetEntData(client, offsBuyZone); +} + +AddPlayerScore(client, amount) +{ + new frags = GetEntProp(client, Prop_Data, "m_iFrags"); + SetEntProp(client, Prop_Data, "m_iFrags", frags + amount); +} + +AddPlayerDeath(client, amount) +{ + new deaths = GetEntProp(client, Prop_Data, "m_iDeaths"); + SetEntProp(client, Prop_Data, "m_iDeaths", deaths + amount); +} + +RemoveAllPlayersWeapons(client) +{ + SDKCall(hRemoveAllItems, client); +} + +TerminateRound(Float:delay, reason) +{ + SDKCall(hTerminateRound, delay, reason); +} + +SetPlayerModel(client, const String:model[]) +{ + PrecacheModel(model); + SetEntityModel(client, model); +} \ No newline at end of file diff --git a/src/zr/overlays.inc b/src/zr/overlays.inc new file mode 100644 index 0000000..fa54c36 --- /dev/null +++ b/src/zr/overlays.inc @@ -0,0 +1,47 @@ +/** + * ==================== + * Zombie:Reloaded + * File: overlays.inc + * Author: Greyscale + * ==================== + */ + +ShowOverlays(Float:time, ZTeam:winner) +{ + new bool:overlays = GetConVarBool(gCvars[CVAR_OVERLAYS]); + if (overlays) + { + decl String:overlay[64]; + if (winner == Human) + { + GetConVarString(gCvars[CVAR_OVERLAYS_HUMAN], overlay, sizeof(overlay)); + } + else if (winner == Zombie) + { + GetConVarString(gCvars[CVAR_OVERLAYS_ZOMBIE], overlay, sizeof(overlay)); + } + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (IsClientInGame(x)) + { + DisplayClientOverlay(x, overlay); + } + } + + CreateTimer(time, KillOverlays); + } +} + +public Action:KillOverlays(Handle:timer) +{ + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (IsClientInGame(x)) + { + ClientCommand(x, "r_screenoverlay \"\""); + } + } +} \ No newline at end of file diff --git a/src/zr/sayhooks.inc b/src/zr/sayhooks.inc new file mode 100644 index 0000000..7212e95 --- /dev/null +++ b/src/zr/sayhooks.inc @@ -0,0 +1,346 @@ +/** + * ==================== + * Zombie:Reloaded + * File: sayhooks.inc + * Author: Greyscale + * ==================== + */ + +HookChatCmds() +{ + RegConsoleCmd("say", SayCommand); + RegConsoleCmd("say_team", SayCommand); +} + +public Action:SayCommand(client, argc) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (!client || !enabled) + { + return Plugin_Continue; + } + + decl String:args[192]; + + GetCmdArgString(args, sizeof(args)); + ReplaceString(args, sizeof(args), "\"", ""); + + if (StrEqual(args, "!zmenu", false)) + { + MainMenu(client); + return Plugin_Handled; + } + + else if (StrEqual(args, "!zclass", false)) + { + ZClass(client); + return Plugin_Handled; + } + + else if (StrEqual(args, "!zmarket", false)) + { + ZMarket(client); + return Plugin_Handled; + } + + else if (StrEqual(args, "!zspawn", false)) + { + ZSpawn(client); + return Plugin_Handled; + } + + else if (StrEqual(args, "!ztele", false)) + { + ZTele(client); + return Plugin_Handled; + } + + else if (StrEqual(args, "!zstuck", false)) + { + ZStuck(client); + return Plugin_Handled; + } + + else if (StrEqual(args, "!zhp", false)) + { + ZHP(client); + return Plugin_Handled; + } + + return Plugin_Continue; +} + +bool:ZClass(client) +{ + new bool:classes = GetConVarBool(gCvars[CVAR_CLASSES]); + if (!classes) + { + ZR_PrintToChat(client, "Feature is disabled"); + + return false; + } + + new bool:randomclass = GetConVarBool(gCvars[CVAR_CLASSES_RANDOM]); + if (randomclass) + { + ZR_PrintToChat(client, "Random class is enabled"); + + return false; + } + + ClassMenu(client); + + return true; +} + +bool:ZMarket(client) +{ + if (!market) + { + ZR_PrintToChat(client, "Feature is disabled"); + + return false; + } + + if (!IsPlayerAlive(client)) + { + ZR_PrintToChat(client, "Must be alive"); + + return false; + } + + new bool:buyzone = GetConVarBool(gCvars[CVAR_ZMARKET_BUYZONE]); + if (!IsClientInBuyZone(client) && buyzone) + { + ZR_PrintCenterText(client, "Market out of buyzone"); + + return false; + } + + SetGlobalTransTarget(client); + + decl String:title[64]; + decl String:rebuy[64]; + + Format(title, sizeof(title), "%t\n ", "Market title"); + Format(rebuy, sizeof(rebuy), "%t\n ", "Market rebuy"); + + Market_Send(client, title, rebuy); + + return true; +} + +public bool:Market_OnWeaponSelected(client, String:weaponid[]) +{ + if (!weaponid[0] || !IsPlayerAlive(client)) + { + return false; + } + + if (IsPlayerZombie(client)) + { + ZR_PrintToChat(client, "Zombie cant use weapon"); + + return false; + } + + if (StrEqual(weaponid, "rebuy")) + { + return true; + } + + decl String:display[64]; + decl String:weapon[32]; + new price; + + if (!Market_GetWeaponIDInfo(weaponid, display, weapon, price)) + { + return false; + } + + ReplaceString(weapon, sizeof(weapon), "weapon_", ""); + + if (IsWeaponRestricted(weapon)) + { + ZR_PrintToChat(client, "Weapon is restricted", weapon); + + return false; + } + + new bool:buyzone = GetConVarBool(gCvars[CVAR_ZMARKET_BUYZONE]); + if (!IsClientInBuyZone(client) && buyzone) + { + ZR_PrintCenterText(client, "Market out of buyzone"); + + return false; + } + + return true; +} + +public Market_PostOnWeaponSelected(client, &bool:allowed) +{ + if (!allowed) + { + return; + } + + ZMarket(client); +} + +ZSpawn(client) +{ + new bool:spawn = GetConVarBool(gCvars[CVAR_ZSPAWN]); + if (!spawn) + { + ZR_PrintToChat(client, "Feature is disabled"); + + return; + } + + new team = GetClientTeam(client); + if (team != CS_TEAM_T && team != CS_TEAM_CT) + { + return; + } + + if (IsPlayerAlive(client) || IsPlayerInList(client)) + { + return; + } + + RespawnPlayer(client); + + AddPlayerToList(client); +} + +ZTele(client) +{ + new bool:tele = GetConVarBool(gCvars[CVAR_ZTELE]); + if (!tele) + { + ZR_PrintToChat(client, "Feature is disabled"); + + return; + } + + if (!IsPlayerZombie(client) && zombieSpawned) + { + ZR_PrintToChat(client, "!ztele humans restricted"); + + return; + } + + if (!IsPlayerAlive(client) || tHandles[client][TTELE] != INVALID_HANDLE) + { + return; + } + + new count = teleCount[client]; + new limit = GetConVarInt(gCvars[CVAR_ZTELE_LIMIT]); + + if (limit > 0) + { + if (count < limit) + { + tHandles[client][TTELE] = CreateTimer(3.0, Teleport, client, TIMER_FLAG_NO_MAPCHANGE); + teleCount[client]++; + ZR_PrintToChat(client, "!ztele amount", limit - teleCount[client]); + } + else + { + ZR_PrintToChat(client, "!ztele limit reached"); + } + } + else + { + tHandles[client][TTELE] = CreateTimer(3.0, Teleport, client, TIMER_FLAG_NO_MAPCHANGE); + ZR_PrintToChat(client, "!ztele amount unlimited"); + } +} + +public Action:Teleport(Handle:timer, any:index) +{ + TeleportEntity(index, spawnLoc[index], NULL_VECTOR, NULL_VECTOR); + + tHandles[index][TTELE] = INVALID_HANDLE; +} + +ZStuck(client) +{ + new bool:stuck = GetConVarBool(gCvars[CVAR_ZTELE]); + if (!stuck) + { + ZR_PrintToChat(client, "Feature is disabled"); + + return; + } + + if (!IsPlayerAlive(client)) + { + return; + } + + new Float:clientloc[3]; + new Float:stuckloc[3]; + + GetClientAbsOrigin(client, clientloc); + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (!IsClientInGame(x) || !IsPlayerAlive(x)) + { + continue; + } + + GetClientAbsOrigin(x, stuckloc); + if (GetVectorDistance(clientloc, stuckloc) <= 60) + { + NoCollide(x, true); + CreateTimer(0.5, CollisionOn, x, TIMER_FLAG_NO_MAPCHANGE); + } + } + + new Float:x = GetRandomFloat(-150.0, 150.0); + new Float:y = GetRandomFloat(-150.0, 150.0); + + new Float:nudge[3]; + + nudge[0] = x; + nudge[1] = y; + + SetPlayerVelocity(client, nudge); +} + +public Action:CollisionOn(Handle:timer, any:index) +{ + if (!IsClientInGame(index)) + { + return; + } + + NoCollide(index, false); +} + +ZHP(client) +{ + new bool:zhp = GetConVarBool(gCvars[CVAR_ZHP]); + if (!zhp) + { + ZR_PrintToChat(client, "Feature is disabled"); + + return; + } + + if (dispHP[client]) + { + ZR_PrintToChat(client, "!zhp disabled"); + } + else + { + ZR_PrintToChat(client, "!zhp enabled"); + UpdateHPDisplay(client); + } + + dispHP[client] = !dispHP[client]; +} \ No newline at end of file diff --git a/src/zr/translation.inc b/src/zr/translation.inc new file mode 100644 index 0000000..a808552 --- /dev/null +++ b/src/zr/translation.inc @@ -0,0 +1,118 @@ +/** + * ==================== + * Zombie:Reloaded + * File: translations.inc + * Author: Greyscale + * ==================== + */ + +FormatTextString(String:text[], maxlen) +{ + Format(text, maxlen, "@green[%t] @default%s", "ZR", text); + + ReplaceString(text, maxlen, "@default","\x01"); + ReplaceString(text, maxlen, "@lgreen","\x03"); + ReplaceString(text, maxlen, "@green","\x04"); +} + +stock ZR_PrintToChat(client, any:...) +{ + decl String:phrase[192]; + + if (client) + { + SetGlobalTransTarget(client); + + VFormat(phrase, sizeof(phrase), "%t", 2); + FormatTextString(phrase, sizeof(phrase)); + + PrintToChat(client, phrase); + } + else + { + SetGlobalTransTarget(client); + + VFormat(phrase, sizeof(phrase), "%t", 2); + FormatTextString(phrase, sizeof(phrase)); + + PrintToServer(phrase); + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (IsClientInGame(x)) + { + SetGlobalTransTarget(x); + + VFormat(phrase, sizeof(phrase), "%t", 2); + FormatTextString(phrase, sizeof(phrase)); + + PrintToChat(x, phrase); + } + } + } +} + +stock ZR_PrintCenterText(client, any:...) +{ + SetGlobalTransTarget(client); + + decl String:phrase[192]; + + VFormat(phrase, sizeof(phrase), "%t", 2); + + PrintCenterText(client, phrase); +} + +stock ZR_HudHint(client, any:...) +{ + SetGlobalTransTarget(client); + + decl String:phrase[192]; + + VFormat(phrase, sizeof(phrase), "%t", 2); + + new Handle:hHintText = StartMessageOne("HintText", client); + if (hHintText != INVALID_HANDLE) + { + BfWriteByte(hHintText, -1); + BfWriteString(hHintText, phrase); + EndMessage(); + } +} + +stock ZR_PrintToServer(any:...) +{ + SetGlobalTransTarget(LANG_SERVER); + + decl String:phrase[192]; + decl String:buffer[192]; + + VFormat(phrase, sizeof(phrase), "%t", 1); + Format(buffer, sizeof(buffer), "[%t] %s", "ZR", phrase); + + PrintToServer(buffer); +} + +stock ZR_LogMessage(any:...) +{ + SetGlobalTransTarget(LANG_SERVER); + + decl String:phrase[192]; + + VFormat(phrase, sizeof(phrase), "%t", 1); + + LogMessage(phrase); +} + +stock ZR_ReplyToCommand(client, any:...) +{ + decl String:phrase[192]; + + SetGlobalTransTarget(client); + + VFormat(phrase, sizeof(phrase), "%t", 2); + FormatTextString(phrase, sizeof(phrase)); + + ReplyToCommand(client, phrase); +} \ No newline at end of file diff --git a/src/zr/weaponrestrict.inc b/src/zr/weaponrestrict.inc new file mode 100644 index 0000000..669f9b0 --- /dev/null +++ b/src/zr/weaponrestrict.inc @@ -0,0 +1,261 @@ +/** + * ==================== + * Zombie:Reloaded + * File: weaponrestrict.inc + * Author: Greyscale + * ==================== + */ + +new Handle:restrictedWeapons = INVALID_HANDLE; + +new gRestrict[MAXPLAYERS+1]; + +enum WepRestrictQuery +{ + Successful, /** Weapon (un)restrict query was successful */ + Invalid, /** Weapon invalid */ + Existing, /** Already restricted */ +} + +InitWeaponRestrict() +{ + RegConsoleCmd("buy", BuyHook); + + restrictedWeapons = CreateArray(32, 0); +} + +ClientHookUse(client) +{ + gRestrict[client] = Hacks_Hook(client, HACKS_HTYPE_WEAPON_CANUSE, Weapon_CanUse, false); +} + +ClientUnHookUse(client) +{ + Hacks_Unhook(gRestrict[client]); +} + +public Action:BuyHook(client, argc) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (!enabled) + { + return Plugin_Continue; + } + + if (IsPlayerHuman(client)) + { + decl String:weapon[64]; + GetCmdArg(1, weapon, sizeof(weapon)); + + ReplaceString(weapon, sizeof(weapon), "weapon_", ""); + + if (IsWeaponRestricted(weapon)) + { + ZR_PrintToChat(client, "Weapon is restricted", weapon); + return Plugin_Handled; + } + } + else + { + ZR_PrintToChat(client, "Zombie cant use weapon"); + return Plugin_Handled; + } + + return Plugin_Continue; +} + +WepRestrictQuery:RestrictWeapon(const String:weapon[]) +{ + if (IsWeaponGroup(weapon)) + { + RestrictWeaponGroup(weapon); + + ZR_PrintToChat(0, "Weapon group has been restricted", weapon); + + return Successful; + } + + if (!IsWeaponRestricted(weapon)) + { + PushArrayString(restrictedWeapons, weapon); + + ZR_PrintToChat(0, "Weapon has been restricted", weapon); + + return Successful; + } + + return Existing; +} + +RestrictWeaponGroup(const String:group[]) +{ + if (StrEqual(group, "pistols", false)) + { + PushArrayString(restrictedWeapons, "glock"); + PushArrayString(restrictedWeapons, "usp"); + PushArrayString(restrictedWeapons, "p228"); + PushArrayString(restrictedWeapons, "deagle"); + PushArrayString(restrictedWeapons, "elite"); + PushArrayString(restrictedWeapons, "fiveseven"); + } + else if (StrEqual(group, "shotguns", false)) + { + PushArrayString(restrictedWeapons, "m3"); + PushArrayString(restrictedWeapons, "xm1014"); + } + else if (StrEqual(group, "smgs", false)) + { + PushArrayString(restrictedWeapons, "tmp"); + PushArrayString(restrictedWeapons, "mac10"); + PushArrayString(restrictedWeapons, "mp5navy"); + PushArrayString(restrictedWeapons, "ump45"); + PushArrayString(restrictedWeapons, "p90"); + } + else if (StrEqual(group, "rifles", false)) + { + PushArrayString(restrictedWeapons, "galil"); + PushArrayString(restrictedWeapons, "famas"); + PushArrayString(restrictedWeapons, "ak47"); + PushArrayString(restrictedWeapons, "m4a1"); + PushArrayString(restrictedWeapons, "sg552"); + PushArrayString(restrictedWeapons, "bullpup"); + } + else if (StrEqual(group, "snipers", false)) + { + PushArrayString(restrictedWeapons, "scout"); + PushArrayString(restrictedWeapons, "sg550"); + PushArrayString(restrictedWeapons, "g3sg1"); + PushArrayString(restrictedWeapons, "awp"); + } +} + +WepRestrictQuery:UnRestrictWeapon(const String:weapon[]) +{ + if (IsWeaponGroup(weapon)) + { + UnRestrictWeaponGroup(weapon); + + ZR_PrintToChat(0, "Weapon group has been unrestricted", weapon); + + return Successful; + } + + new index = GetRestrictedWeaponIndex(weapon); + + if (index > -1) + { + RemoveFromArray(restrictedWeapons, index); + + ZR_PrintToChat(0, "Weapon has been unrestricted", weapon); + + return Successful; + } + + return Invalid; +} + +UnRestrictWeaponGroup(const String:group[]) +{ + if (StrEqual(group, "pistols", false)) + { + UnRestrictWeapon("glock"); + UnRestrictWeapon("usp"); + UnRestrictWeapon("p228"); + UnRestrictWeapon("deagle"); + UnRestrictWeapon("elite"); + UnRestrictWeapon("fiveseven"); + } + else if (StrEqual(group, "shotguns", false)) + { + UnRestrictWeapon("m3"); + UnRestrictWeapon("xm1014"); + } + else if (StrEqual(group, "smgs", false)) + { + UnRestrictWeapon("tmp"); + UnRestrictWeapon("mac10"); + UnRestrictWeapon("mp5navy"); + UnRestrictWeapon("ump45"); + UnRestrictWeapon("p90"); + } + else if (StrEqual(group, "rifles", false)) + { + UnRestrictWeapon("galil"); + UnRestrictWeapon("famas"); + UnRestrictWeapon("ak47"); + UnRestrictWeapon("m4a1"); + UnRestrictWeapon("sg552"); + UnRestrictWeapon("bullpup"); + } + else if (StrEqual(group, "snipers", false)) + { + UnRestrictWeapon("scout"); + UnRestrictWeapon("sg550"); + UnRestrictWeapon("g3sg1"); + UnRestrictWeapon("awp"); + } +} + +bool:IsWeaponRestricted(const String:weapon[]) +{ + for (new x = 0; x < GetArraySize(restrictedWeapons); x++) + { + decl String:restrictedweapon[32]; + GetArrayString(restrictedWeapons, x, restrictedweapon, sizeof(restrictedweapon)); + + if (StrEqual(weapon, restrictedweapon, false)) + { + return true; + } + } + + return false; +} + +GetRestrictedWeaponIndex(const String:weapon[]) +{ + for (new x = 0; x < GetArraySize(restrictedWeapons); x++) + { + decl String:restrictedweapon[32]; + GetArrayString(restrictedWeapons, x, restrictedweapon, sizeof(restrictedweapon)); + ReplaceString(restrictedweapon, sizeof(restrictedweapon), "weapon_", ""); + + if (StrEqual(weapon, restrictedweapon, false)) + { + return x; + } + } + + return -1; +} + +bool:IsWeaponGroup(const String:weapon[]) +{ + return (StrEqual(weapon, "pistols", false) || StrEqual(weapon, "shotguns", false) || StrEqual(weapon, "smgs", false) || StrEqual(weapon, "rifles", false) || StrEqual(weapon, "snipers", false)); +} + +public Weapon_CanUse(client, weapon, dummy1, dummy2, dummy3, dummy4) +{ + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (!enabled) + { + return Hacks_Continue; + } + + new String:weaponname[32]; + GetEdictClassname(weapon, weaponname, sizeof(weaponname)); + + ReplaceString(weaponname, sizeof(weaponname), "weapon_", ""); + + if (IsWeaponRestricted(weaponname)) + { + return 0; + } + + if (IsPlayerZombie(client) && !StrEqual(weaponname, "knife")) + { + return 0; + } + + return Hacks_Continue; +} \ No newline at end of file diff --git a/src/zr/zombie.inc b/src/zr/zombie.inc new file mode 100644 index 0000000..210ccf7 --- /dev/null +++ b/src/zr/zombie.inc @@ -0,0 +1,887 @@ +/** + * ==================== + * Zombie:Reloaded + * File: zombie.inc + * Author: Greyscale + * ==================== + */ + +#define EXP_NODAMAGE 1 +#define EXP_REPEATABLE 2 +#define EXP_NOFIREBALL 4 +#define EXP_NOSMOKE 8 +#define EXP_NODECAL 16 +#define EXP_NOSPARKS 32 +#define EXP_NOSOUND 64 +#define EXP_RANDOMORIENTATION 128 +#define EXP_NOFIREBALLSMOKE 256 +#define EXP_NOPARTICLES 512 +#define EXP_NODLIGHTS 1024 +#define EXP_NOCLAMPMIN 2048 +#define EXP_NOCLAMPMAX 4096 + +new String:skyname[32]; + +HookCommands() +{ + RegConsoleCmd("nightvision", Command_NightVision); +} + +public Action:Command_NightVision(client, argc) +{ + new bool:allow_disable = GetConVarBool(gCvars[CVAR_ZVISION_ALLOW_DISABLE]); + if (!allow_disable) + { + return; + } + + new bool:enabled = GetConVarBool(gCvars[CVAR_ENABLE]); + if (!enabled) + { + return; + } + + if (!IsPlayerZombie(client)) + { + return; + } + + bZVision[client] = !bZVision[client]; + + if (bZVision[client]) + { + StartZVision(client); + } + else + { + StopZVision(client); + } +} + +FindMapSky() +{ + GetConVarString(FindConVar("sv_skyname"), skyname, sizeof(skyname)); +} + +ChangeLightStyle() +{ + new bool:dark = GetConVarBool(gCvars[CVAR_DARK]); + if (dark) + { + decl String:darkness[2]; + decl String:sky[32]; + + GetConVarString(gCvars[CVAR_DARK_LEVEL], darkness, sizeof(darkness)); + GetConVarString(gCvars[CVAR_DARK_SKY], sky, sizeof(sky)); + + SetLightStyle(0, darkness); + SetConVarString(FindConVar("sv_skyname"), sky, true, false); + } + else + { + SetLightStyle(0, "n"); + SetConVarString(FindConVar("sv_skyname"), skyname, true, false); + } +} + +public RestartGameHook(Handle:convar, const String:oldValue[], const String:newValue[]) +{ + SetConVarInt(FindConVar("mp_restartgame"), 0); + TerminateRound(StringToFloat(newValue), Round_Draw); +} + +public Action:MotherZombie(Handle:timer) +{ + RefreshList(); + + new size = GetArraySize(pList); + new immune = 0; + + for (new x = 0; x < size; x++) + { + new index = GetArrayCell(pList, x); + + if (gBlockMotherInfect[index]) + { + immune++; + } + + if (!IsPlayerAlive(index) || IsPlayerZombie(index)) + { + continue; + } + + CS_SwitchTeam(index, CS_TEAM_CT); + } + + if (!(size - immune)) + { + tInfect = INVALID_HANDLE; + + return; + } + + new randclient; + new ratio = GetConVarInt(gCvars[CVAR_MOTHER_ZOMBIE_RATIO]); + + if (ratio <= 0) + { + do + { + randclient = RandomPlayerFromList(); + } while (!IsPlayerAlive(randclient) || gBlockMotherInfect[randclient]); + + Zombify_Mother(randclient); + } + else + { + new mothercount = RoundToCeil(float(size) / ratio); + + for (new x = 0; x < mothercount; x++) + { + do + { + randclient = RandomPlayerFromList(); + } while (IsPlayerZombie(randclient) || !IsPlayerAlive(randclient) || gBlockMotherInfect[randclient]); + + Zombify_Mother(randclient); + } + } + + tInfect = INVALID_HANDLE; + + zombieSpawned = true; +} + +Zombify_Mother(client) +{ + Call_StartForward(hZombify); + Call_PushCell(client); + Call_PushCell(true); + Call_Finish(); + + gZombie[client] = true; + motherZombie[client] = true; + + ApplyZombieHealth(client,true); + + ApplyZombieSpeed(client); + + CS_SwitchTeam(client, CS_TEAM_T); + + RemoveAllPlayersWeapons(client); + GivePlayerItem(client, "weapon_knife"); + + ApplyZombieNightVision(client); + + ApplyZombieFOV(client); + + ApplyZombieModel(client); + + InfectionEffects(client); + + ZR_PrintToChat(client, "You are a zombie"); + + if (bZVision[client]) + { + StartZVision(client); + } + + new bool:mother_zombie_respawn = GetConVarBool(gCvars[CVAR_MOTHER_ZOMBIE_RESPAWN]); + if (mother_zombie_respawn) + { + TeleportEntity(client, spawnLoc[client], NULL_VECTOR, NULL_VECTOR); + } + + new Float:interval = GetConVarFloat(gCvars[CVAR_EMITSOUNDS]); + if (tHandles[client][TMOAN] != INVALID_HANDLE) + { + CloseHandle(tHandles[client][TMOAN]); + } + tHandles[client][TMOAN] = CreateTimer(interval, ZombieMoanTimer, client, TIMER_REPEAT); + + if (tHandles[client][TPROTECT] != INVALID_HANDLE) + { + TriggerTimer(tHandles[client][TPROTECT]); + tHandles[client][TPROTECT] = INVALID_HANDLE; + } + + if (tHandles[client][TZHP] != INVALID_HANDLE) + { + CloseHandle(tHandles[client][TZHP]); + tHandles[client][TZHP] = INVALID_HANDLE; + } + + new bool:zhp = GetConVarBool(gCvars[CVAR_ZHP]); + if (zhp) + { + UpdateHPDisplay(client); + + tHandles[client][TZHP] = CreateTimer(5.0, ZHPTimer, client, TIMER_REPEAT); + } +} + +Zombify(client, attacker) +{ + if (attacker != 0) + { + new Handle:event = CreateEvent("player_death"); + if (event != INVALID_HANDLE) + { + SetEventInt(event, "userid", GetClientUserId(client)); + SetEventInt(event, "attacker", GetClientUserId(attacker)); + SetEventString(event, "weapon", "zombie_claws_of_death"); + FireEvent(event, false); + } + } + + Call_StartForward(hZombify); + Call_PushCell(client); + Call_PushCell(false); + Call_Finish(); + + gZombie[client] = true; + + ApplyZombieHealth(client, false); + + ApplyZombieSpeed(client); + + RemoveAllPlayersWeapons(client); + GivePlayerItem(client, "weapon_knife"); + + ApplyZombieNightVision(client); + + ApplyZombieFOV(client); + + ApplyZombieModel(client); + + if (bZVision[client]) + { + StartZVision(client); + } + + InfectionEffects(client); + + new ZTeam:team = IsRoundOver(); + RoundWin(team); + + CS_SwitchTeam(client, CS_TEAM_T); + + new Float:interval = GetConVarFloat(gCvars[CVAR_EMITSOUNDS]); + if (tHandles[client][TMOAN] != INVALID_HANDLE) + { + CloseHandle(tHandles[client][TMOAN]); + } + tHandles[client][TMOAN] = CreateTimer(interval, ZombieMoanTimer, client, TIMER_REPEAT); + + if (tHandles[client][TPROTECT] != INVALID_HANDLE) + { + TriggerTimer(tHandles[client][TPROTECT]); + tHandles[client][TPROTECT] = INVALID_HANDLE; + } + + if (tHandles[client][TZHP] != INVALID_HANDLE) + { + CloseHandle(tHandles[client][TZHP]); + tHandles[client][TZHP] = INVALID_HANDLE; + } + + new bool:zhp = GetConVarBool(gCvars[CVAR_ZHP]); + if (zhp) + { + UpdateHPDisplay(client); + + tHandles[client][TZHP] = CreateTimer(5.0, ZHPTimer, client, TIMER_REPEAT); + } + + AntiStick(attacker, client); +} + +InfectionEffects(client) +{ + new Float:clientloc[3]; + new Float:direction[3] = {0.0, 0.0, 0.0}; + + GetClientAbsOrigin(client, clientloc); + clientloc[2] += 30; + + decl String:sound[128]; + GetConVarString(gCvars[CVAR_INFECT_SOUND], sound, sizeof(sound)); + if (sound[0]) + { + PrecacheSound(sound); + EmitSoundToAll(sound, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); + } + + new bool:esplash = GetConVarBool(gCvars[CVAR_INFECT_ESPLASH]); + if (esplash) + { + TE_SetupEnergySplash(clientloc, direction, true); + TE_SendToAll(); + } + + new explosion = CreateEntityByName("env_explosion"); + + if (explosion != -1) + { + new flags = GetEntProp(explosion, Prop_Data, "m_spawnflags"); + flags = flags | EXP_NODAMAGE | EXP_NODECAL; + + new bool:fireball = GetConVarBool(gCvars[CVAR_INFECT_FIREBALL]); + if (!fireball) + { + flags = flags | EXP_NOFIREBALL; + } + + new bool:smoke = GetConVarBool(gCvars[CVAR_INFECT_SMOKE]); + if (!smoke) + { + flags = flags | EXP_NOSMOKE; + } + + new bool:sparks = GetConVarBool(gCvars[CVAR_INFECT_SPARKS]); + if (!sparks) + { + flags = flags | EXP_NOSPARKS; + } + + SetEntProp(explosion, Prop_Data, "m_spawnflags", flags); + + DispatchSpawn(explosion); + + PrecacheModel("materials/sprites/xfireball3.vmt"); + + DispatchKeyValueVector(explosion, "origin", clientloc); + DispatchKeyValue(explosion, "fireballsprite", "materials/sprites/xfireball3.vmt"); + + AcceptEntityInput(explosion, "Explode"); + } + + new bool:shake = GetConVarBool(gCvars[CVAR_INFECT_SHAKE]); + if (shake) + { + new Handle:hShake = StartMessageOne("Shake", client); + if (hShake != INVALID_HANDLE) + { + BfWriteByte(hShake, 0); + BfWriteFloat(hShake, GetConVarFloat(gCvars[CVAR_INFECT_SHAKE_AMP])); + BfWriteFloat(hShake, GetConVarFloat(gCvars[CVAR_INFECT_SHAKE_FREQUENCY])); + BfWriteFloat(hShake, GetConVarFloat(gCvars[CVAR_INFECT_SHAKE_DURATION])); + + EndMessage(); + } + } +} + +ApplyZombieHealth(client, bool:mother) +{ + new health = GetClassHealth(pClass[client]); + + if (mother) + { + health *= 2; + } + + SetEntityHealth(client, health); +} + +ApplyZombieSpeed(client) +{ + new Float:speed = GetClassSpeed(pClass[client]); + SetPlayerSpeed(client, speed); +} + +ApplyZombieNightVision(client) +{ + new bool:nvgs = GetClassNVGs(pClass[client]); + NightVision(client, nvgs); + NightVisionOn(client, nvgs); +} + +ApplyZombieFOV(client) +{ + new fov = GetClassFOV(pClass[client]); + SetPlayerFOV(client, fov); +} + +KnockBack(client, const Float:clientloc[3], const Float:attackerloc[3], Float:power, dmg, bool:boost) +{ + if (!IsPlayerZombie(client)) + { + return; + } + + new Float:vector[3]; + + MakeVectorFromPoints(attackerloc, clientloc, vector); + NormalizeVector(vector, vector); + + vector[0] *= power * (float(dmg) * 1.5); + vector[1] *= power * (float(dmg) * 1.5); + vector[2] *= power * (float(dmg) * 1.5); + + if (boost) + { + ScaleVector(vector, 4.0); + } + + SetPlayerVelocity(client, vector); +} + +JumpBoost(client, Float:distance, Float:height) +{ + new Float:vel[3]; + + GetPlayerVelocity(client, vel); + + vel[0] *= distance; + vel[1] *= distance; + vel[2] = height; + + SetPlayerVelocity(client, vel); +} + +PlayerLeft(client) +{ + if (!IsClientInGame(client)) + { + return; + } + + new ZTeam:team = IsRoundOver(); + if (team == Zombie) + { + RoundWin(team); + return; + } + + if (!IsPlayerAlive(client) || !IsPlayerZombie(client)) + { + return; + } + + new zombiecount = GetZTeamCount(Zombie); + if (zombiecount > 1) + { + return; + } + + new count = GetTeamClientCount(CS_TEAM_CT); + if (count <= 1) + { + return; + } + + new Handle:aClients = CreateArray(); + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (!IsClientInGame(x) || !IsPlayerAlive(x) || client == x || GetClientTeam(x) != CS_TEAM_CT || gBlockMotherInfect[x]) + { + continue; + } + + PushArrayCell(aClients, x); + } + + new size = GetArraySize(aClients); + if (!size) + { + return; + } + + new randclient = GetArrayCell(aClients, GetRandomInt(0, size-1)); + Zombify_Mother(randclient); + + ZR_PrintToChat(randclient, "Zombie replacement"); + + CloseHandle(aClients); +} + +GetZTeamCount(ZTeam:team) +{ + new count = 0; + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (!IsClientInGame(x) || !IsPlayerAlive(x)) + { + continue; + } + + new ZTeam:pTeam = GetPlayerZTeam(x); + if (pTeam == team) + { + count++; + } + } + + return count; +} + +ZTeam:IsRoundOver() +{ + new bool:zombies = false; + new bool:humans = false; + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (!IsClientInGame(x) || !IsPlayerAlive(x)) + { + continue; + } + + if (IsPlayerZombie(x)) + { + zombies = true; + } + else + { + humans = true; + } + } + + if (zombies && !humans) + { + return Zombie; + } + + if (humans && !zombies) + { + if (zombieSpawned) + { + return Human; + } + } + + return Neither; +} + +RoundWin(ZTeam:team) +{ + if (team == Human) + { + TerminateRound(5.0, CTs_PreventEscape); + } + else if (team == Zombie) + { + TerminateRound(5.0, Terrorists_Escaped); + } +} + +BalanceTeams() +{ + new count = 0; + new cPlayers[MAXPLAYERS]; + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (!IsClientInGame(x) || GetClientTeam(x) <= 1) + { + continue; + } + + CS_SwitchTeam(x, CS_TEAM_T); + cPlayers[count++] = x; + } + + for (new x = 0; x < count; x++) + { + if (!IsClientInGame(cPlayers[x]) || GetClientTeam(cPlayers[x]) <= 1) + { + continue; + } + + CS_SwitchTeam(cPlayers[x], CS_TEAM_CT); + x++; + } +} + +RemoveObjectives() +{ + decl String:classname[64]; + + new maxentities = GetMaxEntities(); + for (new x = 0; x <= maxentities; x++) + { + if(!IsValidEdict(x)) + { + continue; + } + + GetEdictClassname(x, classname, sizeof(classname)); + if( StrEqual(classname, "func_bomb_target") || + StrEqual(classname, "func_hostage_rescue") || + StrEqual(classname, "c4") || + StrEqual(classname, "hostage_entity")) + { + RemoveEdict(x); + } + } +} + +AntiStick(attacker, client) +{ + if (!attacker || !IsClientInGame(attacker)) + { + return; + } + + new Float:vector[3]; + + new Float:attackerloc[3]; + new Float:clientloc[3]; + + GetClientAbsOrigin(attacker, attackerloc); + GetClientAbsOrigin(client, clientloc); + + MakeVectorFromPoints(attackerloc, clientloc, vector); + + NormalizeVector(vector, vector); + ScaleVector(vector, -160.0); + + TeleportEntity(attacker, NULL_VECTOR, NULL_VECTOR, vector); +} + +StartZVision(client) +{ + if (tHandles[client][TZVISION] != INVALID_HANDLE) + { + CloseHandle(tHandles[client][TZVISION]); + } + + new bool:zvision = ZVision(client); + if (zvision) + { + new Float:redisplay = GetConVarFloat(gCvars[CVAR_ZVISION_REDISPLAY]); + tHandles[client][TZVISION] = CreateTimer(redisplay, ZVisionTimer, client, TIMER_REPEAT); + } +} + +StopZVision(client) +{ + if (tHandles[client][TZVISION] != INVALID_HANDLE) + { + CloseHandle(tHandles[client][TZVISION]); + tHandles[client][TZVISION] = INVALID_HANDLE; + } + + ClientCommand(client, "r_screenoverlay \"\""); +} + +bool:ZVision(client) +{ + if (IsFakeClient(client)) + { + return false; + } + + decl String:zvision[64]; + GetClassZVision(pClass[client], zvision, sizeof(zvision)); + + if (zvision[0]) + { + DisplayClientOverlay(client, zvision); + + return true; + } + + return false; +} + +public Action:ZVisionTimer(Handle:timer, any:index) +{ + if (!IsClientInGame(index) || !IsPlayerZombie(index)) + { + tHandles[index][TZVISION] = INVALID_HANDLE; + + return Plugin_Stop; + } + + ZVision(index); + + return Plugin_Continue; +} + +ZombieMoan(client) +{ + decl String:sound[64]; + + new randsound = GetRandomInt(1, 14); + Format(sound, sizeof(sound), "npc/zombie/zombie_voice_idle%d.wav", randsound); + + PrecacheSound(sound); + EmitSoundToAll(sound, client, SNDCHAN_AUTO, SNDLEVEL_SCREAMING); +} + +public Action:ZombieMoanTimer(Handle:timer, any:index) +{ + if (!IsClientInGame(index) || !IsPlayerZombie(index)) + { + tHandles[index][TMOAN] = INVALID_HANDLE; + + return Plugin_Stop; + } + + ZombieMoan(index); + + return Plugin_Continue; +} + +public Action:Regenerate(Handle:timer, any:index) +{ + if (!IsClientInGame(index) || !IsPlayerZombie(index)) + { + tHandles[index][TREGEN] = INVALID_HANDLE; + + return Plugin_Stop; + } + + new health = GetClassRegenHealth(pClass[index]); + new maxhealth = GetClassHealth(pClass[index]); + + if (motherZombie[index]) + { + maxhealth *= 2; + } + + new newhealth = GetClientHealth(index) + health; + if (newhealth > maxhealth) + { + newhealth = maxhealth; + + tHandles[index][TREGEN] = INVALID_HANDLE; + + return Plugin_Stop; + } + + SetEntityHealth(index, newhealth); + + UpdateHPDisplay(index); + + return Plugin_Continue; +} + +UpdateHPDisplay(client) +{ + new bool:zhp = GetConVarBool(gCvars[CVAR_ZHP]); + if (!zhp) + { + return; + } + + if (!IsPlayerZombie(client) || !dispHP[client]) + { + return; + } + + new health = GetClientHealth(client); + if (health < 0) + { + health = 0; + } + + ZR_HudHint(client, "Display HP", health); +} + +public Action:ZHPTimer(Handle:timer, any:index) +{ + if (!IsClientInGame(index)) + { + tHandles[index][TZHP] = INVALID_HANDLE; + + return Plugin_Stop; + } + + UpdateHPDisplay(index); + + return Plugin_Continue; +} + +public Action:EndProtect(Handle:timer, any:index) +{ + if (!IsClientInGame(index)) + { + tHandles[index][TPROTECT] = INVALID_HANDLE; + + return; + } + + pProtect[index] = false; + + if (IsPlayerHuman(index)) + { + ZR_PrintCenterText(index, "Spawn protection end"); + } + + tHandles[index][TPROTECT] = INVALID_HANDLE; +} + +RespawnPlayer(client) +{ + if (!IsClientInGame(client)) + { + return; + } + + CS_RespawnPlayer(client); + + if (!zombieSpawned) + { + return; + } + + decl String:team[32]; + GetConVarString(gCvars[CVAR_RESPAWN_TEAM], team, sizeof(team)); + + if (StrEqual(team, "zombie", false)) + { + Zombify(client, 0); + } +} + +public Action:RespawnTimer(Handle:timer, any:index) +{ + new team = GetClientTeam(index); + if (!IsClientInGame(index) || IsPlayerAlive(index) || team != CS_TEAM_T && team != CS_TEAM_CT) + { + tHandles[index][TZHP] = INVALID_HANDLE; + + return; + } + + RespawnPlayer(index); + + tHandles[index][TRESPAWN] = INVALID_HANDLE; +} + +public Action:RoundOver(Handle:timer) +{ + tRound = INVALID_HANDLE; + + RoundWin(Human); +} + +bool:IsPlayerZombie(client) +{ + return gZombie[client]; +} + +bool:IsPlayerHuman(client) +{ + return !gZombie[client]; +} + +ZTeam:GetPlayerZTeam(client) +{ + if (IsPlayerZombie(client)) + { + return Zombie; + } + + return Human; +} \ No newline at end of file diff --git a/src/zr/zombiereloaded.inc b/src/zr/zombiereloaded.inc new file mode 100644 index 0000000..1e8a1fa --- /dev/null +++ b/src/zr/zombiereloaded.inc @@ -0,0 +1,177 @@ +/** + * ==================== + * Zombie:Reloaded + * File: zombiereloaded.inc + * Author: Greyscale + * ==================== + */ + +enum ZTeam +{ + Neither, /** Round is not over */ + Zombie, /** Round is over because zombies win */ + Human, /** Round is over because humans wins */ +} + +#define Target_Bombed 1 // Target Successfully Bombed! +#define VIP_Escaped 2 // The VIP has escaped! +#define VIP_Assassinated 3 // VIP has been assassinated! +#define Terrorists_Escaped 4 // The terrorists have escaped! +#define CTs_PreventEscape 5 // The CT's have prevented most of the terrorists from escaping! +#define Escaping_Terrorists_Neutralized 6 // Escaping terrorists have all been neutralized! +#define Bomb_Defused 7 // The bomb has been defused! +#define CTs_Win 8 // Counter-Terrorists Win! +#define Terrorists_Win 9 // Terrorists Win! +#define Round_Draw 10 // Round Draw! +#define All_Hostages_Rescued 11 // All Hostages have been rescued! +#define Target_Saved 12 // Target has been saved! +#define Hostages_Not_Rescued 13 // Hostages have not been rescued! +#define Terrorists_Not_Escaped 14 // Terrorists have not escaped! +#define VIP_Not_Escaped 15 // VIP has not escaped! +#define Game_Commencing 16 // Game Commencing! + +#define DXLEVEL_MIN 90 + +#define DEFAULT_FOV 90 + +new bool:market; + +new dxLevel[MAXPLAYERS+1]; + +new bool:zombieSpawned; +new bool:motherZombie[MAXPLAYERS+1]; +new bool:gZombie[MAXPLAYERS+1]; +new bool:gBlockMotherInfect[MAXPLAYERS+1]; +new bool:bZVision[MAXPLAYERS+1]; +new bool:dispHP[MAXPLAYERS+1]; +new bool:pProtect[MAXPLAYERS+1]; + +new pClass[MAXPLAYERS+1]; +new pNextClass[MAXPLAYERS+1]; + +new teleCount[MAXPLAYERS+1]; +new Float:spawnLoc[MAXPLAYERS+1][3]; + +new Handle:tRound = INVALID_HANDLE; +new Handle:tInfect = INVALID_HANDLE; + +new Handle:pList = INVALID_HANDLE; + +#define MAXTIMERS 7 + +#define TMOAN 0 +#define TREGEN 1 +#define TTELE 2 +#define TZHP 3 +#define TPROTECT 4 +#define TRESPAWN 5 +#define TZVISION 6 + +new Handle:tHandles[MAXPLAYERS+1][MAXTIMERS]; + +new QueryCookie:mat_dxlevel; + +FindClientDXLevel(client) +{ + if (IsFakeClient(client)) + { + return; + } + + mat_dxlevel = QueryClientConVar(client, "mat_dxlevel", DXLevelClientQuery); +} + +public DXLevelClientQuery(QueryCookie:cookie, client, ConVarQueryResult:result, const String:cvarName[], const String:cvarValue[]) +{ + if (cookie != mat_dxlevel) + { + return; + } + + dxLevel[client] = 0; + + if (result != ConVarQuery_Okay) + { + return; + } + + dxLevel[client] = StringToInt(cvarValue); +} + +DisplayClientOverlay(client, const String:overlay[]) +{ + if (!dxLevel[client]) + { + FindClientDXLevel(client); + + return; + } + + if (dxLevel[client] >= DXLEVEL_MIN) + { + ClientCommand(client, "r_screenoverlay \"%s\"", overlay); + } + else + { + ZR_PrintCenterText(client, "DX90 not supported", dxLevel[client], DXLEVEL_MIN); + } +} + +RefreshList() +{ + ClearList(); + + pList = CreateArray(); + + new maxplayers = GetMaxClients(); + for (new x = 1; x <= maxplayers; x++) + { + if (IsClientInGame(x)) + { + new team = GetClientTeam(x); + if (team == CS_TEAM_T || team == CS_TEAM_CT) + { + PushArrayCell(pList, x); + } + } + } +} + +AddPlayerToList(client) +{ + if (pList != INVALID_HANDLE) + { + PushArrayCell(pList, client); + } +} + +ClearList() +{ + if (pList != INVALID_HANDLE) + { + ClearArray(pList); + } +} + +RandomPlayerFromList() +{ + if (pList != INVALID_HANDLE) + { + new size = GetArraySize(pList); + new index = GetRandomInt(0, size - 1); + + return GetArrayCell(pList, index); + } + + return -1; +} + +bool:IsPlayerInList(client) +{ + if (pList != INVALID_HANDLE) + { + return (FindValueInArray(pList, client) != -1); + } + + return false; +}