├── .gitattributes ├── .gitignore ├── README.md ├── gamedata └── tf2.attributes.txt └── scripting ├── include └── tf2attributes.inc ├── tf2attributes.sp └── tf2attributes_example.sp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | *.publishproj 131 | 132 | # NuGet Packages Directory 133 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 134 | #packages/ 135 | 136 | # Windows Azure Build Output 137 | csx 138 | *.build.csdef 139 | 140 | # Windows Store app package directory 141 | AppPackages/ 142 | 143 | # Others 144 | sql/ 145 | *.Cache 146 | ClientBin/ 147 | [Ss]tyle[Cc]op.* 148 | ~$* 149 | *~ 150 | *.dbmdl 151 | *.[Pp]ublish.xml 152 | *.pfx 153 | *.publishsettings 154 | 155 | # RIA/Silverlight projects 156 | Generated_Code/ 157 | 158 | # Backup & report files from converting an old project file to a newer 159 | # Visual Studio version. Backup files are not needed, because we have git ;-) 160 | _UpgradeReport_Files/ 161 | Backup*/ 162 | UpgradeLog*.XML 163 | UpgradeLog*.htm 164 | 165 | # SQL Server files 166 | App_Data/*.mdf 167 | App_Data/*.ldf 168 | 169 | ############# 170 | ## Windows detritus 171 | ############# 172 | 173 | # Windows image file caches 174 | Thumbs.db 175 | ehthumbs.db 176 | 177 | # Folder config file 178 | Desktop.ini 179 | 180 | # Recycle Bin used on file shares 181 | $RECYCLE.BIN/ 182 | 183 | # Mac crap 184 | .DS_Store 185 | 186 | 187 | ############# 188 | ## Python 189 | ############# 190 | 191 | *.py[cod] 192 | 193 | # Packages 194 | *.egg 195 | *.egg-info 196 | dist/ 197 | build/ 198 | eggs/ 199 | parts/ 200 | var/ 201 | sdist/ 202 | develop-eggs/ 203 | .installed.cfg 204 | 205 | # Installer logs 206 | pip-log.txt 207 | 208 | # Unit test / coverage reports 209 | .coverage 210 | .tox 211 | 212 | #Translations 213 | *.mo 214 | 215 | #Mr Developer 216 | .mr.developer.cfg 217 | 218 | *.smx 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > As of 2024-04-18 this fork is no longer compatible with current releases of Team Fortress 3 | > 2, and I have no intent of keeping gamedata synchronized across both. Please grab releases 4 | > from [FlaminSarge's repository][flaminsarge]. 5 | 6 | # tf2attributes 7 | 8 | TF2Attributes SourceMod plugin 9 | 10 | https://forums.alliedmods.net/showthread.php?t=210221 11 | 12 | ## Differences from FlaminSarge's upstream 13 | 14 | This repository has been merged upstream, so there are no significant feature differences. 15 | Feel free to [download the latest version from there][flaminsarge]. I've been added as a 16 | collaborator there; stabilized features will eventually make their way there. 17 | 18 | ~~This repository will remain for any experimental changes.~~ 19 | 20 | [flaminsarge]: https://github.com/FlaminSarge/tf2attributes 21 | 22 | ## Installing or migrating 23 | 24 | This fork is forward compatible with FlaminSarge/tf2attributes — all plugins compiled for 25 | their version should continue to work with this one. The installation instructions remain the 26 | same. 27 | 28 | 1. Download all the non-source code files in [the latest release][]. 29 | 2. Copy `tf2attributes.smx` to `addons/sourcemod/plugins/`. 30 | 3. Copy `tf2.attributes.txt` to `addons/sourcemod/gamedata/`. 31 | 4. If you're a developer, copy `tf2attributes.inc` to `addons/sourcemod/scripting/include/` 32 | (or the appropriate path for your compiler toolchain / project). 33 | 34 | [the latest release]: https://github.com/nosoop/tf2attributes/releases 35 | -------------------------------------------------------------------------------- /gamedata/tf2.attributes.txt: -------------------------------------------------------------------------------- 1 | "Games" 2 | { 3 | /* Team Fortress 2 */ 4 | "tf" 5 | { 6 | "Offsets" 7 | { 8 | "CAttributeManager::OnAttributeValuesChanged" 9 | { 10 | "windows" "13" 11 | "linux" "14" 12 | "mac" "14" 13 | } 14 | "CAttributeManager::ApplyAttributeStringWrapper" 15 | { 16 | // linux uses a signature 17 | "windows" "15" 18 | } 19 | "ISchemaAttributeTypeBase::BConvertStringToEconAttributeValue" 20 | { 21 | "windows" "4" 22 | "linux" "5" 23 | } 24 | "ISchemaAttributeTypeBase::InitializeNewEconAttributeValue" 25 | { 26 | "windows" "7" 27 | "linux" "8" 28 | } 29 | "ISchemaAttributeTypeBase::UnloadEconAttributeValue" 30 | { 31 | "windows" "8" 32 | "linux" "9" 33 | } 34 | "ISchemaAttributeTypeBase::BSupportsGame..." 35 | { 36 | // "ISchemaAttributeTypeBase::BSupportsGameplayModificationAndNetworking()" 37 | "windows" "10" 38 | "linux" "11" 39 | } 40 | } 41 | "Signatures" 42 | { 43 | "CEconItemSchema::GetItemDefinition" //(int), returns CEconItemDefinition* 44 | { 45 | "library" "server" 46 | "windows" "\x55\x8B\xEC\x56\x8B\xF1\x8D\x45\x08\x57\x50\x8D\x8E\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x85\xC0" 47 | "linux" "@_ZN15CEconItemSchema17GetItemDefinitionEi" 48 | "mac" "@_ZN15CEconItemSchema17GetItemDefinitionEi" 49 | } 50 | "CEconItemView::GetSOCData" //(), returns CEconItem* 51 | { 52 | "library" "server" 53 | "windows" "\x56\x8B\xF1\x8B\x46\x2A\x85\xC0\x75\x2A\xE8\x2A\x2A\x2A\x2A\xFF\x76\x20\x8B\xC8\x8B\x10\xFF\x52\x44\x85\xC0\x74\x2A\xFF\x76\x14\x8B\xC8\xFF\x76\x10\xE8\x2A\x2A\x2A\x2A\x5E" 54 | "linux" "@_ZNK13CEconItemView10GetSOCDataEv" 55 | "mac" "@_ZNK13CEconItemView10GetSOCDataEv" 56 | } 57 | "GEconItemSchema" //static? 58 | { 59 | "library" "server" 60 | "windows" "\xE8\x2A\x2A\x2A\x2A\x83\xC0\x04\xC3" 61 | "linux" "@_Z15GEconItemSchemav" 62 | "mac" "@_Z15GEconItemSchemav" 63 | } 64 | "CEconItemSchema::GetAttributeDefinition" //(int), returns CEconItemAttributeDefinition* 65 | { 66 | "library" "server" 67 | "windows" "\x55\x8B\xEC\x83\xEC\x2A\x53\x56\x8B\xD9\x8D\x2A\x2A\x57" 68 | "linux" "@_ZN15CEconItemSchema22GetAttributeDefinitionEi" 69 | "mac" "@_ZN15CEconItemSchema22GetAttributeDefinitionEi" 70 | } 71 | "CEconItemSchema::GetAttributeDefinitionByName" //(const char*), returns CEconItemAttributeDefinition* 72 | { 73 | "library" "server" 74 | "windows" "\x55\x8B\xEC\x83\xEC\x14\x53\x8B\x5D\x08\x56\x57\x8B\xF9\x85\xDB" 75 | "linux" "@_ZN15CEconItemSchema28GetAttributeDefinitionByNameEPKc" 76 | "mac" "@_ZN15CEconItemSchema28GetAttributeDefinitionByNameEPKc" 77 | } 78 | "CAttributeList::RemoveAttribute" //(CEconItemAttributeDefinition*), returns CEconItemAttributeDefinition* 79 | { 80 | "library" "server" 81 | "windows" "\x55\x8B\xEC\x51\x53\x8B\xD9\x56\x33\xF6\x8B\x43\x10\x89\x45\xFC\x85\xC0\x7E\x2A\x57\x33\xFF" 82 | "linux" "@_ZN14CAttributeList15RemoveAttributeEPK28CEconItemAttributeDefinition" 83 | "mac" "@_ZN14CAttributeList15RemoveAttributeEPK28CEconItemAttributeDefinition" 84 | } 85 | "CAttributeList::SetRuntimeAttributeValue" //(CEconItemAttributeDefinition*, float), returns void 86 | { 87 | "library" "server" 88 | "windows" "\x55\x8B\xEC\x83\xEC\x2A\x33\x2A\x53\x8B\xD9\x56\x57\x8B\x2A\x2A\x8B\x2A\x2A" 89 | "linux" "@_ZN14CAttributeList24SetRuntimeAttributeValueEPK28CEconItemAttributeDefinitionf" 90 | "mac" "@_ZN14CAttributeList24SetRuntimeAttributeValueEPK28CEconItemAttributeDefinitionf" 91 | } 92 | "CAttributeList::GetAttributeByID" //(int), returns CEconAttribute* 93 | { 94 | "library" "server" 95 | "windows" "\x55\x8B\xEC\x51\x8B\xC1\x53\x56\x33\xF6\x89\x45\xFC\x8B\x58\x10" 96 | "linux" "@_ZNK14CAttributeList16GetAttributeByIDEi" 97 | "mac" "@_ZNK14CAttributeList16GetAttributeByIDEi" 98 | } 99 | "CAttributeList::DestroyAllAttributes" //(), returns int 100 | { 101 | "library" "server" 102 | "windows" "\x56\x8B\xF1\x83\x7E\x10\x00\x74\x2A\xC7\x46\x10\x00\x00\x00\x00" 103 | "linux" "@_ZN14CAttributeList20DestroyAllAttributesEv" 104 | "mac" "@_ZN14CAttributeList20DestroyAllAttributesEv" 105 | } 106 | "CAttributeManager::AttribHookValue" 107 | { 108 | // (float value, string_t attrClass, CBaseEntity* ent, CUtlVector *reentrant, bool const_str) 109 | // called in unique x-ref to "ubercharge_ammo" on Windows 110 | "library" "server" 111 | "linux" "@_ZN17CAttributeManager15AttribHookValueIfEET_S1_PKcPK11CBaseEntityP10CUtlVectorIPS4_10CUtlMemoryIS8_iEEb" 112 | "windows" "\x55\x8B\xEC\x83\xEC\x0C\x8B\x0D\x2A\x2A\x2A\x2A\x53\x56\x57\x33\xF6\x33\xFF\x89\x75\xF4\x89\x7D\xF8\x8B\x41\x08\x85\xC0\x74\x2A\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x6A\x6B" 113 | } 114 | "CAttributeManager::AttribHookValue" 115 | { 116 | // (int value, string_t attrClass, CBaseEntity* ent, CUtlVector *reentrant, bool const_str) 117 | // called in unique x-ref to "mod_max_primary_clip_override" on Windows 118 | "library" "server" 119 | "linux" "@_ZN17CAttributeManager15AttribHookValueIiEET_S1_PKcPK11CBaseEntityP10CUtlVectorIPS4_10CUtlMemoryIS8_iEEb" 120 | "windows" "\x55\x8B\xEC\x83\xEC\x10\x8B\x0D\x2A\x2A\x2A\x2A\x53\x56\x57\x33\xFF\x33\xDB\x89\x7D\xF0\x89\x5D\xF4\x8B\x41\x08\x85\xC0\x74\x2A\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x68\x2A\x2A\x2A\x2A\x6A\x6B" 121 | } 122 | "CAttributeManager::ApplyAttributeStringWrapper" 123 | { 124 | // uses a hidden pointer, which ends up looking something like this monstrosity: 125 | // (string_t* returnValue, CAttributeManager* this, string_t input, CBaseEntity* initiator, string_t classname, CUtlVector* entityList), returns string_t 126 | // windows uses a (mostly) standard calling convention so we use the vtable call for that 127 | "library" "server" 128 | "linux" "@_ZN17CAttributeManager27ApplyAttributeStringWrapperE8string_tP11CBaseEntityS0_P10CUtlVectorIS2_10CUtlMemoryIS2_iEE" 129 | } 130 | "CTFPlayer::AddCustomAttribute" //(const char*, float, float), returns void 131 | { 132 | "library" "server" 133 | "windows" "\x55\x8B\xEC\xF3\x0F\x10\x4D\x10\x83\xEC\x10" 134 | "linux" "@_ZN9CTFPlayer18AddCustomAttributeEPKcff" 135 | "mac" "@_ZN9CTFPlayer18AddCustomAttributeEPKcff" 136 | } 137 | "CTFPlayer::RemoveCustomAttribute" //(const char*), returns void 138 | { 139 | // called with x-ref string "hidden maxhealth non buffed" 140 | "library" "server" 141 | "windows" "\x55\x8B\xEC\x83\xEC\x10\x53\x56\x57\xFF\x75\x08" 142 | "linux" "@_ZN9CTFPlayer21RemoveCustomAttributeEPKc" 143 | "mac" "@_ZN9CTFPlayer21RemoveCustomAttributeEPKc" 144 | } 145 | "CopyStringAttributeValueToCharPointerOutput" //(CAttribute_String*, char**), returns void 146 | { 147 | // called from CAttributeIterator_GetTypedAttributeValue::OnIterateAttributeValue 148 | // which on Windows has a unique bytesig `55 8B EC 56 8B F1 8B 46 04 3B 45 08 75 ? FF 76 08` 149 | "library" "server" 150 | "windows" "\x55\x8B\xEC\x8B\x45\x08\x8B\x48\x10" 151 | "linux" "@_Z43CopyStringAttributeValueToCharPointerOutputPK17CAttribute_StringPPKc" 152 | "mac" "@_Z43CopyStringAttributeValueToCharPointerOutputPK17CAttribute_StringPPKc" 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /scripting/include/tf2attributes.inc: -------------------------------------------------------------------------------- 1 | #if defined _tf2attributes_included 2 | #endinput 3 | #endif 4 | #define _tf2attributes_included 5 | 6 | /** 7 | * Sets an attribute's value on an entity, adding it if it isn't on the entity. 8 | * 9 | * @param iEntity Entity index to set the attribute on. Must have m_AttributeList. 10 | * @param strAttrib Name of the attribute, as from the "name" key in items_game. 11 | * @param flValue Value to set the attribute to 12 | * 13 | * @return True if the attribute was added successfully, false if entity does not have m_AttributeList. 14 | * @error Invalid entity index or attribute name passed. 15 | */ 16 | native bool TF2Attrib_SetByName(int iEntity, const char[] strAttrib, float flValue); 17 | 18 | /** 19 | * Sets an attribute's value on an entity, adding it if it isn't on the entity. 20 | * 21 | * @param iEntity Entity index to set the attribute on. Must have m_AttributeList. 22 | * @param iDefIndex Definition index of the attribute, as from the number on the attribute entry in items_game. 23 | * @param flValue Value to set the attribute to 24 | * 25 | * @return True if the attribute was added successfully, false if entity does not have m_AttributeList. 26 | * @error Invalid entity index or attribute name passed. 27 | */ 28 | native bool TF2Attrib_SetByDefIndex(int iEntity, int iDefIndex, float flValue); 29 | 30 | /** 31 | * Parses the attribute name and value strings and applies it on the entity. This parses 32 | * numeric and string attributes. 33 | * 34 | * If you use this on a non-numeric attribute, make sure that only the server reads off of that 35 | * attribute. Non-primitive values aren't replicated correctly between the client and the 36 | * server; the client will read garbage and may crash! 37 | * 38 | * @param iEntity Entity index to set the attribute on. Must have m_AttributeList. 39 | * @param strAttrib Name of the attribute, as from the "name" key in items_game. 40 | * @param strValue Value to set the attribute to. 41 | * 42 | * @return True if the attribute was added successfully, false if the attribute name was invalid. 43 | * @error Invalid entity index or entity does not have m_AttributeList. 44 | */ 45 | native bool TF2Attrib_SetFromStringValue(int iEntity, const char[] strAttrib, const char[] strValue); 46 | 47 | /** 48 | * Returns the address of an attribute on an entity. 49 | * 50 | * @param iEntity Entity index to get attribute from. Must have m_AttributeList. 51 | * @param strAttrib Name of the attribute, as from the "name" key in items_game. 52 | * 53 | * @return Address of the attribute on the entity, or Address_Null if the attribute does not exist on the entity. 54 | * @error Invalid entity index or attribute name passed. 55 | */ 56 | native Address TF2Attrib_GetByName(int iEntity, const char[] strAttrib); 57 | 58 | /** 59 | * Returns the address of an attribute (by attribute index) on an entity. 60 | * 61 | * @param iEntity Entity index to get attribute from. Must have m_AttributeList. 62 | * @param iDefIndex Definition index of the attribute, as from the number on the attribute entry in items_game. 63 | * 64 | * @return Address of the attribute on the entity, or Address_Null if the attribute does not exist on the entity. 65 | * @error Invalid entity index or attribute index passed. 66 | */ 67 | native Address TF2Attrib_GetByDefIndex(int iEntity, int iDefIndex); 68 | 69 | /** 70 | * Removes an attribute from an entity. 71 | * 72 | * @param iEntity Entity index to remove attribute from. Must have m_AttributeList. 73 | * @param strAttrib Name of the attribute, as from the "name" key in items_game. 74 | * 75 | * @return True if the SDKCall was made, false if entity had invalid address or m_AttributeList missing. 76 | * @error Invalid entity index or attribute name passed. 77 | */ 78 | native bool TF2Attrib_RemoveByName(int iEntity, const char[] strAttrib); 79 | 80 | /** 81 | * Removes an attribute from an entity. 82 | * 83 | * @param iEntity Entity index to remove attribute from. Must have m_AttributeList. 84 | * @param iDefIndex Definition index of the attribute, as from the number on the attribute entry in items_game. 85 | * 86 | * @return True if the SDKCall was made, false if entity had invalid address or m_AttributeList missing. 87 | * @error Invalid entity index or attribute index passed. 88 | */ 89 | native bool TF2Attrib_RemoveByDefIndex(int iEntity, int iDefIndex); 90 | 91 | /** 92 | * Removes all attributes from an entity. 93 | * 94 | * @param iEntity Entity index to remove attribute from. Must have m_AttributeList. 95 | * 96 | * @return True if the SDKCall was made, false if entity had invalid address or m_AttributeList missing. 97 | * @error Invalid entity index passed. 98 | */ 99 | native bool TF2Attrib_RemoveAll(int iEntity); 100 | 101 | /** 102 | * Clears and presumably rebuilds the attribute cache for an entity, 'refreshing' attributes. 103 | * Call this after making changes to an attribute with any of the TF2Attrib_Set*(Address pAttrib, arg) natives below. 104 | * You may also need to call this on the entity's m_hOwnerEntity if it is a weapon or wearable. 105 | * You do NOT need to call this after calls to TF2Attrib_SetByName, TF2Attrib_Remove, and TF2Attrib_RemoveAll. 106 | * 107 | * @param iEntity Entity index to remove attribute from. Must have m_AttributeList. 108 | * 109 | * @return True if the SDKCall was made, false if entity had invalid address or m_AttributeList missing. 110 | * @error Invalid entity index passed. 111 | */ 112 | native bool TF2Attrib_ClearCache(int iEntity); 113 | 114 | /** 115 | * Sets the value of m_iAttributeDefinitionIndex (the attribute ID) on an attribute. 116 | * Warning, this changes what GetByName/ID and SetByName 'see' as the name of the attribute, 117 | * but will only change attribute's effects if TF2Attrib_ClearCache is called on the entity with the attribute after. 118 | * 119 | * @param pAttrib Address of the attribute. 120 | * @param iDefIndex Value to set m_iAttributeDefinitionIndex to. 121 | * 122 | * @noreturn 123 | */ 124 | native void TF2Attrib_SetDefIndex(Address pAttrib, int iDefIndex); 125 | 126 | /** 127 | * Returns the value of m_iAttributeDefinitionIndex (the attribute ID) on an attribute. 128 | * 129 | * @param pAttrib Address of the attribute. 130 | * 131 | * @return The integer value of m_iAttributeDefinitionIndex on the attribute. 132 | */ 133 | native int TF2Attrib_GetDefIndex(Address pAttrib); 134 | 135 | /** 136 | * Sets the value of m_flValue on an attribute. 137 | * 138 | * @param pAttrib Address of the attribute. 139 | * @param flValue Value to set m_flValue to. 140 | * 141 | * @noreturn 142 | */ 143 | native void TF2Attrib_SetValue(Address pAttrib, float flValue); 144 | 145 | /** 146 | * Returns the value of m_flValue on an attribute. 147 | * 148 | * @param pAttrib Address of the attribute. 149 | * 150 | * @return The floating point value of m_flValue on the attribute. 151 | */ 152 | native float TF2Attrib_GetValue(Address pAttrib); 153 | 154 | /** 155 | * Returns the string data from its raw value representation (a CAttribute_String instance). 156 | * 157 | * WARNING: This dereferences the input value! Feeding it values that aren't CAttribute_String pointers will result in unexpected behavior, potentially crashing the server. 158 | * In the case where you only want the currently active value, use TF2Attrib_HookValueString instead. 159 | * 160 | * @param pRawValue Raw attribute value. You can get this value with either TF2Attrib_GetValue, TF2Attrib_GetSOCAttribs, or TF2Attrib_GetStaticAttribs. 161 | * @param buffer Buffer to store the resulting string to. 162 | * @param maxlen Maximum length of the buffer. 163 | * 164 | * @return Number of bytes written. 165 | */ 166 | native int TF2Attrib_UnsafeGetStringValue(any pRawValue, char[] buffer, int maxlen); 167 | 168 | /** 169 | * Sets the value of m_nRefundableCurrency on an attribute. 170 | * 171 | * @param pAttrib Address of the attribute. 172 | * @param nCurrency Value to set m_nRefundableCurrency to. 173 | * 174 | * @noreturn 175 | */ 176 | native void TF2Attrib_SetRefundableCurrency(Address pAttrib, int nCurrency); 177 | 178 | /** 179 | * Returns the value of m_nRefundableCurrency on an attribute. 180 | * 181 | * @param pAttrib Address of the attribute. 182 | * 183 | * @return The (unsigned) integer value of m_nRefundableCurrency on the attribute. 184 | */ 185 | native int TF2Attrib_GetRefundableCurrency(Address pAttrib); 186 | 187 | /** 188 | * Returns an array containing the attributes (as indices) present on an entity. 189 | * 190 | * @param iEntity Entity index to get attribute list from. Must have m_AttributeList. 191 | * @param iDefIndices Array of attribute definition indices found on the entity. 192 | * @param iMaxLen Max length of the iDefIndices array, default 20. Does not affect the return value. 193 | * 194 | * @return The number of attributes found on the entity's attribute list (max 20 as of Oct 2017), or -1 if some error happened. 195 | * @error Invalid iEntity or iMaxLen. 196 | */ 197 | native int TF2Attrib_ListDefIndices(int iEntity, int[] iDefIndices, int iMaxLen=20); 198 | 199 | /** 200 | * Returns arrays containing the static attributes and their values present on an item definition. 201 | * 202 | * @param iItemDefIndex Item definition index (e.g. 7 for Shovel) to get static attribute list from. 203 | * @param iAttribIndices Array of attribute definition indices found on the item definition. 204 | * @param flAttribValues Array of attribute values found on the item definition, corresponding to the indices. 205 | * @param iMaxLen Max length of the two arrays passed in, default 16. Does not affect the return value. 206 | * 207 | * @return The number of attributes found on the item definition's static attribute list (max 16 as of June 2017), or -1 if no schema or item definition found. 208 | * @error Invalid iMaxLen, or gamedata for this function failed to load. 209 | */ 210 | native int TF2Attrib_GetStaticAttribs(int iItemDefIndex, int[] iAttribIndices, float[] flAttribValues, int iMaxLen=16); 211 | 212 | /** 213 | * Returns arrays containing the item server (SOC) attributes and their values present on an item definition. 214 | * 215 | * @param iEntity Entity index to get the item server attribute list from. 216 | * @param iAttribIndices Array of attribute definition indices found. 217 | * @param flAttribValues Array of attribute values found, corresponding to the indices. 218 | * @param iMaxLen Max length of the two arrays passed in, default 16. Does not affect the return value. 219 | * 220 | * @return The number of attributes found on the item's SOC attribute list (max 16 as of June 2017), or -1 if some error happened. 221 | * @error Invalid iEntity or iMaxLen, or gamedata for this function failed to load. 222 | */ 223 | native int TF2Attrib_GetSOCAttribs(int iEntity, int[] iAttribIndices, float[] flAttribValues, int iMaxLen=16); 224 | 225 | /** 226 | * Gets whether an attribute is stored as an integer or as a float. 227 | * Use TF2Attrib_SetValue(attribute, view_as(intValue)) on attributes that store values as ints 228 | * to avoid compiler tag mismatch warnings. 229 | * 230 | * @param iDefIndex Index of the attribute (as returned by TF2Attrib_GetDefIndex()). 231 | * 232 | * @return True if attribute value is supposed to be an int, false if float. 233 | */ 234 | native bool TF2Attrib_IsIntegerValue(int iDefIndex); 235 | 236 | /** 237 | * Returns true if an attribute with the specified name exists. 238 | * 239 | * @param strAttrib Name of the attribute, as from the "name" key in items_game. 240 | * 241 | * @return True if the attribute exists, false otherwise. 242 | */ 243 | native bool TF2Attrib_IsValidAttributeName(const char[] strAttrib); 244 | 245 | /** 246 | * Adds a custom, potentially temporary attribute to a player. 247 | * 248 | * @param client Client index to set the attribute on. 249 | * @param strAttrib Name of the attribute, as from the "name" key in items_game. 250 | * @param flValue Value to set m_flValue to. 251 | * @param flDuration Duration of the attribute. If less than 0, the attribute will not be automatically removed. 252 | * 253 | * @noreturn 254 | */ 255 | native void TF2Attrib_AddCustomPlayerAttribute(int client, const char[] strAttrib, float flValue, float flDuration = -1.0); 256 | 257 | /** 258 | * Removes a previously applied custom attribute on a player. 259 | * 260 | * @param client Client index to remove the attribute from. 261 | * @param strAttrib Name of the attribute, as from the "name" key in items_game. 262 | * 263 | * @noreturn 264 | */ 265 | native void TF2Attrib_RemoveCustomPlayerAttribute(int client, const char[] strAttrib); 266 | 267 | /** 268 | * Applies a transformation to the given initial value, following the rules according to the given attribute class. 269 | * 270 | * @param flInitial Initial float value. 271 | * @param attrClass The attribute class, as from the "attribute_class" key in items_game. 272 | * @param iEntity The entity that should be checked. Checking players also checks their equipped items. 273 | * 274 | * @return Transformed initial value. 275 | */ 276 | native float TF2Attrib_HookValueFloat(float flInitial, const char[] attrClass, int iEntity); 277 | 278 | /** 279 | * Applies a transformation to the given initial value, following the rules according to the given attribute class. 280 | * 281 | * @param nInitial Initial integer value. 282 | * @param attrClass The attribute class, as from the "attribute_class" key in items_game. 283 | * @param iEntity The entity that should be checked. Checking players also checks their equipped items. 284 | * 285 | * @return Transformed initial value. 286 | */ 287 | native int TF2Attrib_HookValueInt(int nInitial, const char[] attrClass, int iEntity); 288 | 289 | /** 290 | * Applies a transformation to the given initial value, following the rules according to the given attribute class. 291 | * 292 | * @param initial Initial string value. (This appears to only be returned if the entity doesn't have the attribute.) 293 | * @param attrClass The attribute class, as from the "attribute_class" key in items_game. 294 | * @param iEntity The entity that should be checked. Checking players also checks their equipped items. 295 | * @param buffer Transformed initial value. 296 | * @param maxlen Buffer size. 297 | * 298 | * @return Number of bytes written. 299 | */ 300 | native int TF2Attrib_HookValueString(const char[] initial, const char[] attrClass, int iEntity, char[] buffer, int maxlen); 301 | 302 | /** 303 | * Gets whether the plugin loaded without ANY errors. 304 | * For the purpose of allowing dependencies to ignore the plugin if this returns false. 305 | * Check in OnAllPluginsLoaded() or something. I dunno. 306 | * 307 | * @return True if no errors while loading, false otherwise. 308 | */ 309 | native bool TF2Attrib_IsReady(); 310 | 311 | public SharedPlugin __pl_tf2attributes = 312 | { 313 | name = "tf2attributes", 314 | file = "tf2attributes.smx", 315 | #if defined REQUIRE_PLUGIN 316 | required = 1, 317 | #else 318 | required = 0, 319 | #endif 320 | }; 321 | 322 | #if !defined REQUIRE_PLUGIN 323 | public void __pl_tf2attributes_SetNTVOptional() 324 | { 325 | MarkNativeAsOptional("TF2Attrib_SetByName"); 326 | MarkNativeAsOptional("TF2Attrib_SetByDefIndex"); 327 | MarkNativeAsOptional("TF2Attrib_GetByName"); 328 | MarkNativeAsOptional("TF2Attrib_GetByDefIndex"); 329 | MarkNativeAsOptional("TF2Attrib_RemoveByName"); 330 | MarkNativeAsOptional("TF2Attrib_RemoveByDefIndex"); 331 | MarkNativeAsOptional("TF2Attrib_RemoveAll"); 332 | MarkNativeAsOptional("TF2Attrib_ClearCache"); 333 | MarkNativeAsOptional("TF2Attrib_SetDefIndex"); 334 | MarkNativeAsOptional("TF2Attrib_GetDefIndex"); 335 | MarkNativeAsOptional("TF2Attrib_SetValue"); 336 | MarkNativeAsOptional("TF2Attrib_GetValue"); 337 | MarkNativeAsOptional("TF2Attrib_SetRefundableCurrency"); 338 | MarkNativeAsOptional("TF2Attrib_GetRefundableCurrency"); 339 | MarkNativeAsOptional("TF2Attrib_ListDefIndices"); 340 | MarkNativeAsOptional("TF2Attrib_GetStaticAttribs"); 341 | MarkNativeAsOptional("TF2Attrib_GetSOCAttribs"); 342 | MarkNativeAsOptional("TF2Attrib_ListDefIndices"); 343 | MarkNativeAsOptional("TF2Attrib_IsIntegerValue"); 344 | MarkNativeAsOptional("TF2Attrib_IsValidAttributeName"); 345 | MarkNativeAsOptional("TF2Attrib_AddCustomPlayerAttribute"); 346 | MarkNativeAsOptional("TF2Attrib_RemoveCustomPlayerAttribute"); 347 | MarkNativeAsOptional("TF2Attrib_HookValueFloat"); 348 | MarkNativeAsOptional("TF2Attrib_HookValueInt"); 349 | 350 | MarkNativeAsOptional("TF2Attrib_IsReady"); 351 | } 352 | #endif 353 | -------------------------------------------------------------------------------- /scripting/tf2attributes.sp: -------------------------------------------------------------------------------- 1 | #pragma semicolon 1 2 | 3 | #include 4 | #include 5 | 6 | #pragma newdecls required 7 | 8 | #define PLUGIN_NAME "[TF2] TF2Attributes" 9 | #define PLUGIN_AUTHOR "FlaminSarge" 10 | #define PLUGIN_VERSION "1.3.3@nosoop-1.7.1.1" 11 | #define PLUGIN_CONTACT "http://forums.alliedmods.net/showthread.php?t=210221" 12 | #define PLUGIN_DESCRIPTION "Functions to add/get attributes for TF2 players/items" 13 | 14 | public Plugin myinfo = { 15 | name = PLUGIN_NAME, 16 | author = PLUGIN_AUTHOR, 17 | description = PLUGIN_DESCRIPTION, 18 | version = PLUGIN_VERSION, 19 | url = PLUGIN_CONTACT 20 | }; 21 | 22 | // "counts as assister is some kind of pet this update is going to be awesome" is 73 characters. Valve... Valve. 23 | #define MAX_ATTRIBUTE_NAME_LENGTH 128 24 | #define MAX_ATTRIBUTE_VALUE_LENGTH PLATFORM_MAX_PATH 25 | 26 | Handle hSDKGetItemDefinition; 27 | Handle hSDKGetSOCData; 28 | Handle hSDKSchema; 29 | Handle hSDKGetAttributeDef; 30 | Handle hSDKGetAttributeDefByName; 31 | Handle hSDKSetRuntimeValue; 32 | Handle hSDKGetAttributeByID; 33 | Handle hSDKOnAttribValuesChanged; 34 | Handle hSDKRemoveAttribute; 35 | Handle hSDKDestroyAllAttributes; 36 | Handle hSDKAddCustomAttribute; 37 | Handle hSDKRemoveCustomAttribute; 38 | Handle hSDKAttributeHookFloat; 39 | Handle hSDKAttributeHookInt; 40 | 41 | // these two are mutually exclusive 42 | Handle hSDKAttributeApplyStringWrapperWindows; 43 | Handle hSDKAttributeApplyStringWrapperLinux; 44 | 45 | Handle hSDKAttributeValueInitialize; 46 | Handle hSDKAttributeTypeCanBeNetworked; 47 | Handle hSDKAttributeValueFromString; 48 | Handle hSDKAttributeValueUnload; 49 | Handle hSDKAttributeValueUnloadByRef; 50 | Handle hSDKCopyStringAttributeToCharPointer; 51 | 52 | // caches attribute name to definition instance 53 | StringMap g_AttributeDefinitionMapping; 54 | 55 | // caches string_t instances from AllocPooledString 56 | StringMap g_AllocPooledStringCache; 57 | 58 | /** 59 | * since the game doesn't free heap-allocated non-GC attributes, we're taking on that 60 | * responsibility 61 | */ 62 | enum struct HeapAttributeValue { 63 | Address m_pAttributeValue; 64 | int m_iAttributeDefinitionIndex; 65 | 66 | void Destroy() { 67 | Address pAttrDef = GetAttributeDefinitionByID(this.m_iAttributeDefinitionIndex); 68 | UnloadAttributeRawValue(pAttrDef, this.m_pAttributeValue); 69 | } 70 | } 71 | ArrayList g_ManagedAllocatedValues; 72 | 73 | static bool g_bPluginReady = false; 74 | public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { 75 | char game[8]; 76 | GetGameFolderName(game, sizeof(game)); 77 | 78 | if (strncmp(game, "tf", 2, false) != 0) { 79 | strcopy(error, err_max, "Plugin only available for TF2 and possibly TF2Beta"); 80 | return APLRes_Failure; 81 | } 82 | 83 | CreateNative("TF2Attrib_SetByName", Native_SetAttrib); 84 | CreateNative("TF2Attrib_SetByDefIndex", Native_SetAttribByID); 85 | CreateNative("TF2Attrib_SetFromStringValue", Native_SetAttribStringByName); 86 | CreateNative("TF2Attrib_GetByName", Native_GetAttrib); 87 | CreateNative("TF2Attrib_GetByDefIndex", Native_GetAttribByID); 88 | CreateNative("TF2Attrib_RemoveByName", Native_Remove); 89 | CreateNative("TF2Attrib_RemoveByDefIndex", Native_RemoveByID); 90 | CreateNative("TF2Attrib_RemoveAll", Native_RemoveAll); 91 | CreateNative("TF2Attrib_SetDefIndex", Native_SetID); 92 | CreateNative("TF2Attrib_GetDefIndex", Native_GetID); 93 | CreateNative("TF2Attrib_SetValue", Native_SetVal); 94 | CreateNative("TF2Attrib_GetValue", Native_GetVal); 95 | CreateNative("TF2Attrib_UnsafeGetStringValue", Native_GetStringVal); 96 | CreateNative("TF2Attrib_SetRefundableCurrency", Native_SetCurrency); 97 | CreateNative("TF2Attrib_GetRefundableCurrency", Native_GetCurrency); 98 | CreateNative("TF2Attrib_ClearCache", Native_ClearCache); 99 | CreateNative("TF2Attrib_ListDefIndices", Native_ListIDs); 100 | CreateNative("TF2Attrib_GetStaticAttribs", Native_GetStaticAttribs); 101 | CreateNative("TF2Attrib_GetSOCAttribs", Native_GetSOCAttribs); 102 | CreateNative("TF2Attrib_IsIntegerValue", Native_IsIntegerValue); 103 | CreateNative("TF2Attrib_IsValidAttributeName", Native_IsValidAttributeName); 104 | CreateNative("TF2Attrib_AddCustomPlayerAttribute", Native_AddCustomAttribute); 105 | CreateNative("TF2Attrib_RemoveCustomPlayerAttribute", Native_RemoveCustomAttribute); 106 | CreateNative("TF2Attrib_HookValueFloat", Native_HookValueFloat); 107 | CreateNative("TF2Attrib_HookValueInt", Native_HookValueInt); 108 | CreateNative("TF2Attrib_HookValueString", Native_HookValueString); 109 | CreateNative("TF2Attrib_IsReady", Native_IsReady); 110 | 111 | //unused, backcompat I guess? 112 | CreateNative("TF2Attrib_SetInitialValue", Native_DeprecatedPropertyAccess); 113 | CreateNative("TF2Attrib_GetInitialValue", Native_DeprecatedPropertyAccess); 114 | CreateNative("TF2Attrib_SetIsSetBonus", Native_DeprecatedPropertyAccess); 115 | CreateNative("TF2Attrib_GetIsSetBonus", Native_DeprecatedPropertyAccess); 116 | 117 | RegPluginLibrary("tf2attributes"); 118 | return APLRes_Success; 119 | } 120 | 121 | public int Native_IsReady(Handle plugin, int numParams) { 122 | return g_bPluginReady; 123 | } 124 | 125 | public void OnPluginStart() { 126 | Handle hGameConf = LoadGameConfigFile("tf2.attributes"); 127 | if (!hGameConf) { 128 | SetFailState("Could not locate gamedata file tf2.attributes.txt for TF2Attributes, pausing plugin"); 129 | } 130 | 131 | StartPrepSDKCall(SDKCall_Raw); 132 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CEconItemSchema::GetItemDefinition"); 133 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 134 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //Returns address of CEconItemDefinition 135 | hSDKGetItemDefinition = EndPrepSDKCall(); 136 | if (!hSDKGetItemDefinition) { 137 | SetFailState("Could not initialize call to CEconItemSchema::GetItemDefinition"); 138 | } 139 | 140 | StartPrepSDKCall(SDKCall_Raw); 141 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CEconItemView::GetSOCData"); 142 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //Returns address of CEconItem 143 | hSDKGetSOCData = EndPrepSDKCall(); 144 | if (!hSDKGetSOCData) { 145 | SetFailState("Could not initialize call to CEconItemView::GetSOCData"); 146 | } 147 | 148 | StartPrepSDKCall(SDKCall_Static); 149 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "GEconItemSchema"); 150 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //Returns address of CEconItemSchema 151 | hSDKSchema = EndPrepSDKCall(); 152 | if (!hSDKSchema) { 153 | SetFailState("Could not initialize call to GEconItemSchema"); 154 | } 155 | 156 | StartPrepSDKCall(SDKCall_Raw); 157 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CEconItemSchema::GetAttributeDefinition"); 158 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 159 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //Returns address of a CEconItemAttributeDefinition 160 | hSDKGetAttributeDef = EndPrepSDKCall(); 161 | if (!hSDKGetAttributeDef) { 162 | SetFailState("Could not initialize call to CEconItemSchema::GetAttributeDefinition"); 163 | } 164 | 165 | StartPrepSDKCall(SDKCall_Raw); 166 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CEconItemSchema::GetAttributeDefinitionByName"); 167 | PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); 168 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //Returns address of a CEconItemAttributeDefinition 169 | hSDKGetAttributeDefByName = EndPrepSDKCall(); 170 | if (!hSDKGetAttributeDefByName) { 171 | SetFailState("Could not initialize call to CEconItemSchema::GetAttributeDefinitionByName"); 172 | } 173 | 174 | StartPrepSDKCall(SDKCall_Raw); 175 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CAttributeList::RemoveAttribute"); 176 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 177 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //not a clue what this return is 178 | hSDKRemoveAttribute = EndPrepSDKCall(); 179 | if (!hSDKRemoveAttribute) { 180 | SetFailState("Could not initialize call to CAttributeList::RemoveAttribute"); 181 | } 182 | 183 | StartPrepSDKCall(SDKCall_Raw); 184 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CAttributeList::SetRuntimeAttributeValue"); 185 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 186 | PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain); 187 | //PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); 188 | //Apparently there's no return, so avoid setting return info, but the 'return' is nonzero if the attribute is added successfully 189 | //Just a note, the above SDKCall returns ((entindex + 4) * 4) | 0xA000), and you can AND it with 0x1FFF to get back the entindex if you want, though it's pointless) 190 | //I don't know any other specifics, such as if the highest 3 bits actually matter 191 | //And I don't know what happens when you hit ent index 2047 192 | 193 | hSDKSetRuntimeValue = EndPrepSDKCall(); 194 | if (!hSDKSetRuntimeValue) { 195 | SetFailState("Could not initialize call to CAttributeList::SetRuntimeAttributeValue"); 196 | } 197 | 198 | StartPrepSDKCall(SDKCall_Raw); 199 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CAttributeList::DestroyAllAttributes"); 200 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); 201 | hSDKDestroyAllAttributes = EndPrepSDKCall(); 202 | if (!hSDKDestroyAllAttributes) { 203 | SetFailState("Could not initialize call to CAttributeList::DestroyAllAttributes"); 204 | } 205 | 206 | StartPrepSDKCall(SDKCall_Raw); 207 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CAttributeList::GetAttributeByID"); 208 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 209 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); //Returns address of a CEconItemAttribute 210 | hSDKGetAttributeByID = EndPrepSDKCall(); 211 | if (!hSDKGetAttributeByID) { 212 | SetFailState("Could not initialize call to CAttributeList::GetAttributeByID"); 213 | } 214 | 215 | StartPrepSDKCall(SDKCall_Raw); 216 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "CAttributeManager::OnAttributeValuesChanged"); 217 | hSDKOnAttribValuesChanged = EndPrepSDKCall(); 218 | if (!hSDKOnAttribValuesChanged) { 219 | SetFailState("Could not initialize call to CAttributeManager::OnAttributeValuesChanged"); 220 | } 221 | 222 | StartPrepSDKCall(SDKCall_Player); 223 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CTFPlayer::AddCustomAttribute"); 224 | PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); 225 | PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain); 226 | PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain); 227 | hSDKAddCustomAttribute = EndPrepSDKCall(); 228 | if (!hSDKAddCustomAttribute) { 229 | SetFailState("Could not initialize call to CTFPlayer::AddCustomAttribute"); 230 | } 231 | 232 | StartPrepSDKCall(SDKCall_Player); 233 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CTFPlayer::RemoveCustomAttribute"); 234 | PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); 235 | hSDKRemoveCustomAttribute = EndPrepSDKCall(); 236 | if (!hSDKRemoveCustomAttribute) { 237 | SetFailState("Could not initialize call to CTFPlayer::AddCustomAttribute"); 238 | } 239 | 240 | StartPrepSDKCall(SDKCall_Static); 241 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CAttributeManager::AttribHookValue"); 242 | PrepSDKCall_SetReturnInfo(SDKType_Float, SDKPass_Plain); 243 | PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain); // initial value 244 | PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); // attribute class 245 | PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); // CBaseEntity* entity 246 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // CUtlVector, set to nullptr 247 | PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain); // bool const_string 248 | hSDKAttributeHookFloat = EndPrepSDKCall(); 249 | if (!hSDKAttributeHookFloat) { 250 | SetFailState("Could not initialize call to CAttributeManager::AttribHookValue"); 251 | } 252 | 253 | StartPrepSDKCall(SDKCall_Static); 254 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CAttributeManager::AttribHookValue"); 255 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); 256 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // initial value 257 | PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); // attribute class 258 | PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); // CBaseEntity* entity 259 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // CUtlVector, set to nullptr 260 | PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain); // bool const_string 261 | hSDKAttributeHookInt = EndPrepSDKCall(); 262 | if (!hSDKAttributeHookInt) { 263 | SetFailState("Could not initialize call to CAttributeManager::AttribHookValue"); 264 | } 265 | 266 | // linux signature. this uses a hidden pointer passed in before `this` on the stack 267 | // so we'll do our best with static since SM doesn't support that calling convention 268 | // no subclasses override this virtual function so we'll just call it directly 269 | StartPrepSDKCall(SDKCall_Static); 270 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CAttributeManager::ApplyAttributeStringWrapper"); 271 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); // return string_t 272 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer); // return value 273 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // thisptr 274 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // string_t initial value 275 | PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); // initator entity (should contain thisptr) 276 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // string_t attribute class 277 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // CUtlVector, set to nullptr 278 | hSDKAttributeApplyStringWrapperLinux = EndPrepSDKCall(); 279 | 280 | if (!hSDKAttributeApplyStringWrapperLinux) { 281 | // windows vcall. this one also uses a hidden pointer, but it's passed as the first param 282 | // `this` remains unchanged so we can still use a vcall 283 | StartPrepSDKCall(SDKCall_Raw); 284 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "CAttributeManager::ApplyAttributeStringWrapper"); 285 | PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); // return string_t 286 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer); // return value too 287 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // string_t initial value 288 | PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); // CBaseEntity* entity 289 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // string_t attribute class 290 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // CUtlVector, set to nullptr 291 | hSDKAttributeApplyStringWrapperWindows = EndPrepSDKCall(); 292 | } 293 | 294 | if (!hSDKAttributeApplyStringWrapperWindows && !hSDKAttributeApplyStringWrapperLinux) { 295 | SetFailState("Could not initialize call to CAttributeManager::ApplyAttributeStringWrapper"); 296 | } 297 | 298 | StartPrepSDKCall(SDKCall_Raw); // CEconItemAttribute* 299 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, 300 | "ISchemaAttributeTypeBase::InitializeNewEconAttributeValue"); 301 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); // CAttributeDefinition* 302 | hSDKAttributeValueInitialize = EndPrepSDKCall(); 303 | if (!hSDKAttributeValueInitialize) { 304 | SetFailState("Could not initialize call to ISchemaAttributeTypeBase::InitializeNewEconAttributeValue"); 305 | } 306 | 307 | StartPrepSDKCall(SDKCall_Raw); // attr_type 308 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, 309 | "ISchemaAttributeTypeBase::BSupportsGame..."); // 64 chars ought to be enough for anyone -- dvander, probably 310 | PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); 311 | hSDKAttributeTypeCanBeNetworked = EndPrepSDKCall(); 312 | if (!hSDKAttributeTypeCanBeNetworked) { 313 | SetFailState("Could not initialize call to ISchemaAttributeTypeBase::BSupportsGameplayModificationAndNetworking"); 314 | } 315 | 316 | StartPrepSDKCall(SDKCall_Raw); 317 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, 318 | "ISchemaAttributeTypeBase::BConvertStringToEconAttributeValue"); 319 | PrepSDKCall_SetReturnInfo(SDKType_Bool, SDKPass_Plain); 320 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 321 | PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); 322 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 323 | PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain); 324 | hSDKAttributeValueFromString = EndPrepSDKCall(); 325 | if (!hSDKAttributeValueFromString) { 326 | SetFailState("Could not initialize call to ISchemaAttributeTypeBase::BConvertStringToEconAttributeValue"); 327 | } 328 | 329 | StartPrepSDKCall(SDKCall_Raw); 330 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, 331 | "ISchemaAttributeTypeBase::UnloadEconAttributeValue"); 332 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 333 | hSDKAttributeValueUnload = EndPrepSDKCall(); 334 | if (!hSDKAttributeValueUnload) { 335 | SetFailState("Could not initialize call to ISchemaAttributeTypeBase::UnloadEconAttributeValue"); 336 | } 337 | 338 | StartPrepSDKCall(SDKCall_Raw); 339 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, 340 | "ISchemaAttributeTypeBase::UnloadEconAttributeValue"); 341 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer); 342 | hSDKAttributeValueUnloadByRef = EndPrepSDKCall(); 343 | if (!hSDKAttributeValueUnloadByRef) { 344 | SetFailState("Could not initialize call to ISchemaAttributeTypeBase::UnloadEconAttributeValue"); 345 | } 346 | 347 | StartPrepSDKCall(SDKCall_Static); 348 | PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, 349 | "CopyStringAttributeValueToCharPointerOutput"); 350 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); 351 | PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Pointer, VDECODE_FLAG_ALLOWNULL, VENCODE_FLAG_COPYBACK); // char**, variable contains char* on return 352 | hSDKCopyStringAttributeToCharPointer = EndPrepSDKCall(); 353 | if (!hSDKCopyStringAttributeToCharPointer) { 354 | SetFailState("Could not initialize call to CopyStringAttributeValueToCharPointerOutput"); 355 | } 356 | 357 | CreateConVar("tf2attributes_version", PLUGIN_VERSION, "TF2Attributes version number", FCVAR_NOTIFY); 358 | 359 | g_bPluginReady = true; 360 | 361 | delete hGameConf; 362 | 363 | g_ManagedAllocatedValues = new ArrayList(sizeof(HeapAttributeValue)); 364 | g_AttributeDefinitionMapping = new StringMap(); 365 | 366 | g_AllocPooledStringCache = new StringMap(); 367 | } 368 | 369 | public void OnPluginEnd() { 370 | /** 371 | * We don't need to do remove-on-entities on map end since their runtime lists will be gone, 372 | * but we do need to remove them when the plugin is unloaded / reloaded, since we manage 373 | * runtime non-networked attributes ourselves and they don't outlive the plugin. 374 | */ 375 | RemoveNonNetworkedRuntimeAttributesOnEntities(); 376 | DestroyManagedAllocatedValues(); 377 | } 378 | 379 | /** 380 | * Free up all attribute values that we allocated ourselves. 381 | */ 382 | public void OnMapEnd() { 383 | DestroyManagedAllocatedValues(); 384 | 385 | // because attribute injection's a thing now, we invalidate our internal mappings 386 | // in case everything changes during the next map 387 | g_AttributeDefinitionMapping.Clear(); 388 | 389 | // pooled strings might get purged only between map changes 390 | g_AllocPooledStringCache.Clear(); 391 | } 392 | 393 | /* native bool TF2Attrib_IsIntegerValue(int iDefIndex); */ 394 | public int Native_IsIntegerValue(Handle plugin, int numParams) { 395 | int iDefIndex = GetNativeCell(1); 396 | 397 | Address pEconItemAttributeDefinition = GetAttributeDefinitionByID(iDefIndex); 398 | if (!pEconItemAttributeDefinition) { 399 | return ThrowNativeError(1, "Attribute index %d is invalid", iDefIndex); 400 | } 401 | 402 | return LoadFromAddressOffset(pEconItemAttributeDefinition, 0x0E, NumberType_Int8); 403 | } 404 | 405 | static int GetStaticAttribs(Address pItemDef, int[] iAttribIndices, int[] iAttribValues, int size = 16) { 406 | AssertValidAddress(pItemDef); 407 | 408 | // 0x1C = CEconItemDefinition.m_Attributes (type CUtlVector) 409 | // 0x1C = (...) m_Attributes.m_Memory.m_pMemory (m_Attributes + 0x00) 410 | // 0x28 = (...) m_Attributes.m_Size (m_Attributes + 0x0C) 411 | int iNumAttribs = LoadFromAddressOffset(pItemDef, 0x28, NumberType_Int32); 412 | if (!iNumAttribs) { 413 | return 0; 414 | } 415 | 416 | Address pAttribList = DereferencePointer(pItemDef, .offset = 0x1C); 417 | 418 | // Read static_attrib_t (size 0x08) entries from contiguous block of memory 419 | for (int i = 0; i < iNumAttribs && i < size; i++) { 420 | Address pStaticAttrib = pAttribList + view_as
(i * 0x08); 421 | iAttribIndices[i] = LoadFromAddress(pStaticAttrib, NumberType_Int16); 422 | iAttribValues[i] = LoadFromAddressOffset(pStaticAttrib, 0x04, NumberType_Int32); 423 | } 424 | return iNumAttribs; 425 | } 426 | 427 | /* native int TF2Attrib_GetStaticAttribs(int iItemDefIndex, int[] iAttribIndices, float[] flAttribValues, int iMaxLen=16); */ 428 | public int Native_GetStaticAttribs(Handle plugin, int numParams) { 429 | int iItemDefIndex = GetNativeCell(1); 430 | int size = 16; 431 | if (numParams >= 4) { 432 | size = GetNativeCell(4); 433 | } 434 | 435 | if (size <= 0) { 436 | return ThrowNativeError(SP_ERROR_NATIVE, "Array size must be greater than 0 (currently %d)", size); 437 | } 438 | 439 | Address pSchema = GetItemSchema(); 440 | if (!pSchema) { 441 | return -1; 442 | } 443 | 444 | Address pItemDef = SDKCall(hSDKGetItemDefinition, pSchema, iItemDefIndex); 445 | AssertValidAddress(pItemDef); 446 | 447 | int[] iAttribIndices = new int[size]; int[] iAttribValues = new int[size]; 448 | int iCount = GetStaticAttribs(pItemDef, iAttribIndices, iAttribValues, size); 449 | SetNativeArray(2, iAttribIndices, size); 450 | SetNativeArray(3, iAttribValues, size); //cast to float on inc side 451 | return iCount; 452 | } 453 | 454 | static int GetSOCAttribs(int iEntity, int[] iAttribIndices, int[] iAttribValues, int size = 16) { 455 | if (size <= 0) { 456 | return -1; 457 | } 458 | Address pEconItemView = GetEntityEconItemView(iEntity); 459 | if (!pEconItemView) { 460 | return -1; 461 | } 462 | 463 | // pEconItem may be null if the item doesn't have SOC data (i.e., not from the item server) 464 | Address pEconItem = SDKCall(hSDKGetSOCData, pEconItemView); 465 | if (!pEconItem) { 466 | return 0; 467 | } 468 | 469 | // 0x34 = CEconItem.m_pAttributes (type CUtlVector*, possibly null) 470 | Address pCustomData = DereferencePointer(pEconItem, .offset = 0x34); 471 | if (pCustomData) { 472 | AssertValidAddress(pCustomData); 473 | 474 | // 0x0C = (...) m_pAttributes->m_Size (m_pAttributes + 0x0C) 475 | // 0x00 = (...) m_pAttributes->m_Memory.m_pMemory (m_pAttributes + 0x00) 476 | int iCount = LoadFromAddressOffset(pCustomData, 0x0C, NumberType_Int32); 477 | if (!iCount) { 478 | // abort early if the attribute list is empty -- we might deref garbage otherwise 479 | return 0; 480 | } 481 | 482 | Address pCustomDataArray = DereferencePointer(pCustomData); 483 | 484 | // Read static_attrib_t (size 0x08) entries from contiguous block of memory 485 | for (int i = 0; i < iCount && i < size; ++i) { 486 | Address pSOCAttribEntry = pCustomDataArray + view_as
(i * 0x08); 487 | 488 | iAttribIndices[i] = LoadFromAddress(pSOCAttribEntry, NumberType_Int16); 489 | iAttribValues[i] = LoadFromAddressOffset(pSOCAttribEntry, 0x04, NumberType_Int32); 490 | } 491 | return iCount; 492 | } 493 | 494 | //(CEconItem+0x27 & 0b100 & 0xFF) != 0 495 | bool hasInternalAttribute = !!(LoadFromAddressOffset(pEconItem, 0x27, NumberType_Int8) & 0b100); 496 | if (hasInternalAttribute) { 497 | iAttribIndices[0] = LoadFromAddressOffset(pEconItem, 0x2C, NumberType_Int16); 498 | iAttribValues[0] = LoadFromAddressOffset(pEconItem, 0x30, NumberType_Int32); 499 | return 1; 500 | } 501 | return 0; 502 | } 503 | 504 | /* native int TF2Attrib_GetSOCAttribs(int iEntity, int[] iAttribIndices, float[] flAttribValues, int iMaxLen=16); */ 505 | public int Native_GetSOCAttribs(Handle plugin, int numParams) { 506 | int iEntity = GetNativeCell(1); 507 | int size = 16; 508 | if (numParams >= 4) { 509 | size = GetNativeCell(4); 510 | } 511 | 512 | if (size <= 0) { 513 | return ThrowNativeError(SP_ERROR_NATIVE, "Array size must be greater than 0 (currently %d)", size); 514 | } 515 | 516 | if (!IsValidEntity(iEntity)) { 517 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(iEntity), iEntity); 518 | } 519 | 520 | //maybe move some address stuff to here from the stock, but for now it's okay 521 | int[] iAttribIndices = new int[size]; int[] iAttribValues = new int[size]; 522 | int iCount = GetSOCAttribs(iEntity, iAttribIndices, iAttribValues, size); 523 | SetNativeArray(2, iAttribIndices, size); 524 | SetNativeArray(3, iAttribValues, size); //cast to float on inc side 525 | return iCount; 526 | } 527 | 528 | /* native bool TF2Attrib_SetByName(int iEntity, char[] strAttrib, float flValue); */ 529 | public int Native_SetAttrib(Handle plugin, int numParams) { 530 | int entity = GetNativeCell(1); 531 | if (!IsValidEntity(entity)) { 532 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 533 | } 534 | 535 | char strAttrib[MAX_ATTRIBUTE_NAME_LENGTH]; 536 | GetNativeString(2, strAttrib, sizeof(strAttrib)); 537 | float flVal = GetNativeCell(3); 538 | 539 | Address pEntAttributeList = GetEntityAttributeList(entity); 540 | if (!pEntAttributeList) { 541 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 542 | } 543 | 544 | Address pAttribDef = GetAttributeDefinitionByName(strAttrib); 545 | if (!pAttribDef) { 546 | return ThrowNativeError(SP_ERROR_NATIVE, "Attribute name '%s' is invalid", strAttrib); 547 | } 548 | 549 | SDKCall(hSDKSetRuntimeValue, pEntAttributeList, pAttribDef, flVal); 550 | return true; 551 | } 552 | 553 | /* native bool TF2Attrib_SetByDefIndex(int iEntity, int iDefIndex, float flValue); */ 554 | public int Native_SetAttribByID(Handle plugin, int numParams) { 555 | int entity = GetNativeCell(1); 556 | if (!IsValidEntity(entity)) { 557 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 558 | } 559 | 560 | int iAttrib = GetNativeCell(2); 561 | float flVal = GetNativeCell(3); 562 | 563 | Address pEntAttributeList = GetEntityAttributeList(entity); 564 | if (!pEntAttributeList) { 565 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 566 | } 567 | 568 | Address pAttribDef = GetAttributeDefinitionByID(iAttrib); 569 | if (!pAttribDef) { 570 | return ThrowNativeError(SP_ERROR_NATIVE, "Attribute index %d is invalid", iAttrib); 571 | } 572 | 573 | SDKCall(hSDKSetRuntimeValue, pEntAttributeList, pAttribDef, flVal); 574 | return true; 575 | } 576 | 577 | /* native bool TF2Attrib_SetFromStringValue(int iEntity, const char[] strAttrib, const char[] strValue); */ 578 | public int Native_SetAttribStringByName(Handle plugin, int numParams) { 579 | int entity = GetNativeCell(1); 580 | if (!IsValidEntity(entity)) { 581 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 582 | } 583 | 584 | Address pEntAttributeList = GetEntityAttributeList(entity); 585 | if (!pEntAttributeList) { 586 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 587 | } 588 | 589 | char strAttrib[MAX_ATTRIBUTE_NAME_LENGTH], strAttribVal[MAX_ATTRIBUTE_VALUE_LENGTH]; 590 | GetNativeString(2, strAttrib, sizeof(strAttrib)); 591 | GetNativeString(3, strAttribVal, sizeof(strAttribVal)); 592 | 593 | int attrdef; 594 | if (!GetAttributeDefIndexByName(strAttrib, attrdef)) { 595 | // we don't throw on nonexistent attributes here; we return false and let the caller handle that 596 | return false; 597 | } 598 | 599 | // allocate a CEconItemAttribute instance in an entity's runtime attribute list 600 | Address pEconItemAttribute = FindOrAllocateEconItemAttribute(entity, attrdef); 601 | if (!InitializeAttributeValue(attrdef, pEconItemAttribute, strAttribVal)) { 602 | return false; 603 | } 604 | 605 | ClearAttributeCache(entity); 606 | return true; 607 | } 608 | 609 | /* native Address TF2Attrib_GetByName(int iEntity, char[] strAttrib); */ 610 | public int Native_GetAttrib(Handle plugin, int numParams) { 611 | // There is a CAttributeList::GetByName, wonder why this is being done instead... 612 | int entity = GetNativeCell(1); 613 | if (!IsValidEntity(entity)) { 614 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 615 | } 616 | 617 | char strAttrib[MAX_ATTRIBUTE_NAME_LENGTH]; 618 | GetNativeString(2, strAttrib, sizeof(strAttrib)); 619 | 620 | Address pEntAttributeList = GetEntityAttributeList(entity); 621 | if (!pEntAttributeList) { 622 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 623 | } 624 | 625 | int iDefIndex; 626 | if (!GetAttributeDefIndexByName(strAttrib, iDefIndex)) { 627 | return ThrowNativeError(SP_ERROR_NATIVE, "Attribute name '%s' is invalid", strAttrib); 628 | } 629 | return SDKCall(hSDKGetAttributeByID, pEntAttributeList, iDefIndex); 630 | } 631 | 632 | /* native Address TF2Attrib_GetByDefIndex(int iEntity, int iDefIndex); */ 633 | public int Native_GetAttribByID(Handle plugin, int numParams) { 634 | int entity = GetNativeCell(1); 635 | if (!IsValidEntity(entity)) { 636 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 637 | } 638 | 639 | int iDefIndex = GetNativeCell(2); 640 | 641 | Address pEntAttributeList = GetEntityAttributeList(entity); 642 | if (!pEntAttributeList) { 643 | return 0; 644 | } 645 | 646 | return SDKCall(hSDKGetAttributeByID, pEntAttributeList, iDefIndex); 647 | } 648 | 649 | /* native bool TF2Attrib_RemoveByName(int iEntity, char[] strAttrib); */ 650 | public int Native_Remove(Handle plugin, int numParams) { 651 | int entity = GetNativeCell(1); 652 | if (!IsValidEntity(entity)) { 653 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 654 | } 655 | 656 | char strAttrib[MAX_ATTRIBUTE_NAME_LENGTH]; 657 | GetNativeString(2, strAttrib, sizeof(strAttrib)); 658 | 659 | Address pEntAttributeList = GetEntityAttributeList(entity); 660 | if (!pEntAttributeList) { 661 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 662 | } 663 | 664 | Address pAttribDef = GetAttributeDefinitionByName(strAttrib); 665 | if (!pAttribDef) { 666 | return ThrowNativeError(SP_ERROR_NATIVE, "Attribute name '%s' is invalid", strAttrib); 667 | } 668 | 669 | SDKCall(hSDKRemoveAttribute, pEntAttributeList, pAttribDef); //Not a clue what the return is here, but it's probably a clone of the attrib being removed 670 | return true; 671 | } 672 | 673 | /* native bool TF2Attrib_RemoveByDefIndex(int iEntity, int iDefIndex); */ 674 | public int Native_RemoveByID(Handle plugin, int numParams) { 675 | int entity = GetNativeCell(1); 676 | if (!IsValidEntity(entity)) { 677 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 678 | } 679 | 680 | int iAttrib = GetNativeCell(2); 681 | 682 | Address pEntAttributeList = GetEntityAttributeList(entity); 683 | if (!pEntAttributeList) { 684 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 685 | } 686 | 687 | Address pAttribDef = GetAttributeDefinitionByID(iAttrib); 688 | if (!pAttribDef) { 689 | return ThrowNativeError(SP_ERROR_NATIVE, "Attribute index %d is invalid", iAttrib); 690 | } 691 | 692 | SDKCall(hSDKRemoveAttribute, pEntAttributeList, pAttribDef); //Not a clue what the return is here, but it's probably a clone of the attrib being removed 693 | return true; 694 | } 695 | 696 | /* native bool TF2Attrib_RemoveAll(int iEntity); */ 697 | public int Native_RemoveAll(Handle plugin, int numParams) { 698 | int entity = GetNativeCell(1); 699 | if (!IsValidEntity(entity)) { 700 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 701 | } 702 | 703 | Address pEntAttributeList = GetEntityAttributeList(entity); 704 | if (!pEntAttributeList) { 705 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 706 | } 707 | 708 | SDKCall(hSDKDestroyAllAttributes, pEntAttributeList); //disregard the return (Valve does!) 709 | return true; 710 | } 711 | 712 | /* native void TF2Attrib_SetDefIndex(Address pAttrib, int iDefIndex); */ 713 | public int Native_SetID(Handle plugin, int numParams) { 714 | Address pAttrib = GetNativeCell(1); 715 | int iDefIndex = GetNativeCell(2); 716 | StoreToAddressOffset(pAttrib, 0x04, iDefIndex, NumberType_Int16); 717 | } 718 | 719 | /* native int TF2Attrib_GetDefIndex(Address pAttrib); */ 720 | public int Native_GetID(Handle plugin, int numParams) { 721 | Address pAttrib = GetNativeCell(1); 722 | return LoadFromAddressOffset(pAttrib, 0x04, NumberType_Int16); 723 | } 724 | 725 | /* native void TF2Attrib_SetValue(Address pAttrib, float flValue); */ 726 | public int Native_SetVal(Handle plugin, int numParams) { 727 | Address pAttrib = GetNativeCell(1); 728 | int flVal = GetNativeCell(2); //It's a float but avoiding tag mismatch warnings from StoreToAddress 729 | StoreToAddressOffset(pAttrib, 0x08, flVal, NumberType_Int32); 730 | } 731 | 732 | /* native float TF2Attrib_GetValue(Address pAttrib); */ 733 | public int Native_GetVal(Handle plugin, int numParams) { 734 | Address pAttrib = GetNativeCell(1); 735 | return LoadFromAddressOffset(pAttrib, 0x08, NumberType_Int32); 736 | } 737 | 738 | /* TF2Attrib_UnsafeGetStringValue(any pRawValue, char[] buffer, int maxlen); */ 739 | public int Native_GetStringVal(Handle plugin, int numParams) { 740 | Address pRawValue = GetNativeCell(1); 741 | 742 | int maxlen = GetNativeCell(3), length; 743 | char[] buffer = new char[maxlen]; 744 | 745 | ReadStringAttributeValue(pRawValue, buffer, maxlen); 746 | SetNativeString(2, buffer, maxlen, .bytes = length); 747 | return length; 748 | } 749 | 750 | /* native void TF2Attrib_SetRefundableCurrency(Address pAttrib, int nCurrency); */ 751 | public int Native_SetCurrency(Handle plugin, int numParams) { 752 | Address pAttrib = GetNativeCell(1); 753 | int nCurrency = GetNativeCell(2); 754 | StoreToAddressOffset(pAttrib, 0x0C, nCurrency, NumberType_Int32); 755 | } 756 | 757 | /* native int TF2Attrib_GetRefundableCurrency(Address pAttrib); */ 758 | public int Native_GetCurrency(Handle plugin, int numParams) { 759 | Address pAttrib = GetNativeCell(1); 760 | return LoadFromAddressOffset(pAttrib, 0x0C, NumberType_Int32); 761 | } 762 | 763 | public int Native_DeprecatedPropertyAccess(Handle plugin, int numParams) { 764 | return ThrowNativeError(SP_ERROR_NATIVE, "Property associated with native function no longer exists"); 765 | } 766 | 767 | static bool ClearAttributeCache(int entity) { 768 | if (entity <= 0 || !IsValidEntity(entity)) { 769 | return false; 770 | } 771 | 772 | Address pAttributeManager = GetEntityAttributeManager(entity); 773 | if (!pAttributeManager) { 774 | return false; 775 | } 776 | 777 | SDKCall(hSDKOnAttribValuesChanged, pAttributeManager); 778 | return true; 779 | } 780 | 781 | /* native bool TF2Attrib_ClearCache(int iEntity); */ 782 | public int Native_ClearCache(Handle plugin, int numParams) { 783 | int entity = GetNativeCell(1); 784 | if (!IsValidEntity(entity)) { 785 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 786 | } 787 | return ClearAttributeCache(entity); 788 | } 789 | 790 | /* native int TF2Attrib_ListDefIndices(int iEntity, int[] iDefIndices, int iMaxLen=20); */ 791 | public int Native_ListIDs(Handle plugin, int numParams) { 792 | int entity = GetNativeCell(1); 793 | int size = 20; 794 | if (numParams >= 3) { 795 | size = GetNativeCell(3); 796 | } 797 | 798 | if (size <= 0) { 799 | return ThrowNativeError(SP_ERROR_NATIVE, "Array size must be greater than 0 (currently %d)", size); 800 | } 801 | 802 | if (!IsValidEntity(entity)) { 803 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", EntIndexToEntRef(entity), entity); 804 | } 805 | 806 | Address pAttributeList = GetEntityAttributeList(entity); 807 | if (!pAttributeList) { 808 | return ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) does not have property m_AttributeList", EntIndexToEntRef(entity), entity); 809 | } 810 | 811 | // 0x10 = CAttributeList.m_Attributes.m_Size (m_Attributes + 0x0C) 812 | int iNumAttribs = LoadFromAddressOffset(pAttributeList, 0x10, NumberType_Int32); 813 | if (!iNumAttribs) { 814 | return 0; 815 | } 816 | 817 | // 0x04 = CAttributeList.m_Attributes (type CUtlVector) 818 | // 0x04 = CAttributeList.m_Attributes.m_Memory.m_pMemory 819 | Address pAttribListData = DereferencePointer(pAttributeList, .offset = 0x04); 820 | AssertValidAddress(pAttribListData); 821 | 822 | int[] iAttribIndices = new int[size]; 823 | 824 | // Read CEconItemAttribute (size 0x10) entries from contiguous block of memory 825 | for (int i = 0; i < iNumAttribs && i < size; i++) { 826 | Address pAttributeEntry = pAttribListData + view_as
(i * 0x10); 827 | iAttribIndices[i] = LoadFromAddressOffset(pAttributeEntry, 0x04, NumberType_Int16); 828 | } 829 | SetNativeArray(2, iAttribIndices, size); 830 | return iNumAttribs; 831 | } 832 | 833 | /* native bool TF2Attrib_IsValidAttributeName(const char[] strAttrib); */ 834 | public int Native_IsValidAttributeName(Handle plugin, int numParams) { 835 | char strAttrib[MAX_ATTRIBUTE_NAME_LENGTH]; 836 | GetNativeString(1, strAttrib, sizeof(strAttrib)); 837 | 838 | return GetAttributeDefinitionByName(strAttrib)? true : false; 839 | } 840 | 841 | /* native void TF2Attrib_AddCustomPlayerAttribute(int client, const char[] strAttrib, float flValue, float flDuration = -1.0); */ 842 | public int Native_AddCustomAttribute(Handle plugin, int numParams) { 843 | char strAttrib[MAX_ATTRIBUTE_NAME_LENGTH]; 844 | 845 | int client = GetNativeCell(1); 846 | GetNativeString(2, strAttrib, sizeof(strAttrib)); 847 | 848 | if (!GetAttributeDefinitionByName(strAttrib)) { 849 | return ThrowNativeError(SP_ERROR_NATIVE, "Attribute name '%s' is invalid", strAttrib); 850 | } 851 | 852 | float flValue = GetNativeCell(3); 853 | float flDuration = GetNativeCell(4); 854 | 855 | SDKCall(hSDKAddCustomAttribute, client, strAttrib, flValue, flDuration); 856 | return 0; 857 | } 858 | 859 | public int Native_RemoveCustomAttribute(Handle plugin, int numParams) { 860 | char strAttrib[MAX_ATTRIBUTE_NAME_LENGTH]; 861 | 862 | int client = GetNativeCell(1); 863 | GetNativeString(2, strAttrib, sizeof(strAttrib)); 864 | 865 | if (!GetAttributeDefinitionByName(strAttrib)) { 866 | return ThrowNativeError(SP_ERROR_NATIVE, "Attribute name '%s' is invalid", strAttrib); 867 | } 868 | 869 | SDKCall(hSDKRemoveCustomAttribute, client, strAttrib); 870 | return 0; 871 | } 872 | 873 | /* native float TF2Attrib_HookValueFloat(float flInitial, const char[] attrClass, int iEntity); */ 874 | public int Native_HookValueFloat(Handle plugin, int numParams) { 875 | /** 876 | * CAttributeManager::AttribHookValue(float value, string_t attr_class, 877 | * CBaseEntity const* entity, CUtlVector reentrantList, 878 | * bool is_const_str); 879 | * 880 | * `value` is the value that is returned after modifiers based on `attr_class`. 881 | * `reentrantList` seems to be a list of entities to ignore? 882 | * `is_const_str` is true iff the `attr_class` is hardcoded 883 | * (i.e., it's at a fixed location) -- this is never true from a plugin 884 | * This determines if the game uses AllocPooledString_StaticConstantStringPointer 885 | * (when is_const_str == true) or AllocPooledString (false). 886 | */ 887 | float initial = GetNativeCell(1); 888 | 889 | int buflen; 890 | GetNativeStringLength(2, buflen); 891 | char[] attrClass = new char[++buflen]; 892 | GetNativeString(2, attrClass, buflen); 893 | 894 | int entity = GetNativeCell(3); 895 | 896 | return SDKCall(hSDKAttributeHookFloat, initial, attrClass, entity, 897 | Address_Null, false); 898 | } 899 | 900 | /* native float TF2Attrib_HookValueInt(int nInitial, const char[] attrClass, int iEntity); */ 901 | public int Native_HookValueInt(Handle plugin, int numParams) { 902 | int initial = GetNativeCell(1); 903 | 904 | int buflen; 905 | GetNativeStringLength(2, buflen); 906 | char[] attrClass = new char[++buflen]; 907 | GetNativeString(2, attrClass, buflen); 908 | 909 | int entity = GetNativeCell(3); 910 | 911 | return SDKCall(hSDKAttributeHookInt, initial, attrClass, entity, 912 | Address_Null, false); 913 | } 914 | 915 | /* native void TF2Attrib_HookValueString(const char[] initial, const char[] attrClass, 916 | int iEntity, char[] buffer, int maxlen); */ 917 | public int Native_HookValueString(Handle plugin, int numParams) { 918 | int buflen; 919 | 920 | GetNativeStringLength(1, buflen); 921 | char[] inputValue = new char[++buflen]; 922 | GetNativeString(1, inputValue, buflen); 923 | 924 | GetNativeStringLength(2, buflen); 925 | char[] attrClass = new char[++buflen]; 926 | GetNativeString(2, attrClass, buflen); 927 | 928 | int entity = GetNativeCell(3); 929 | 930 | // string needs to be pooled for caching purposes 931 | Address pInput = AllocPooledString(inputValue); 932 | Address pAttrClass = AllocPooledString(attrClass); 933 | 934 | buflen = GetNativeCell(5); 935 | char[] output = new char[buflen]; 936 | 937 | Address pOutput; 938 | if (hSDKAttributeApplyStringWrapperWindows) { 939 | // windows version; hidden ptr pushes params, `this` still in correct register 940 | Address result; 941 | pOutput = SDKCall(hSDKAttributeApplyStringWrapperWindows, 942 | GetEntityAttributeManager(entity), result, pInput, entity, pAttrClass, 943 | Address_Null); 944 | } else if (hSDKAttributeApplyStringWrapperLinux) { 945 | // linux version; hidden ptr moves the stack and this forward 946 | Address result; 947 | pOutput = SDKCall(hSDKAttributeApplyStringWrapperLinux, result, 948 | GetEntityAttributeManager(entity), pInput, entity, pAttrClass, Address_Null); 949 | } 950 | 951 | // read from the output string_t 952 | LoadStringFromAddress(DereferencePointer(pOutput), output, buflen); 953 | 954 | int written; 955 | SetNativeString(4, output, buflen, .bytes = written); 956 | return written; 957 | } 958 | 959 | /* helper functions */ 960 | 961 | static Address GetItemSchema() { 962 | return SDKCall(hSDKSchema); 963 | } 964 | 965 | static Address GetEntityEconItemView(int entity) { 966 | int iCEIVOffset = GetEntSendPropOffs(entity, "m_Item", true); 967 | if (iCEIVOffset > 0) { 968 | return GetEntityAddress(entity) + view_as
(iCEIVOffset); 969 | } 970 | return Address_Null; 971 | } 972 | 973 | /** 974 | * Returns the m_AttributeList offset. This does not correspond to the CUtlVector instance 975 | * (which is offset by 0x04). 976 | */ 977 | static Address GetEntityAttributeList(int entity) { 978 | int offsAttributeList = GetEntSendPropOffs(entity, "m_AttributeList", true); 979 | if (offsAttributeList > 0) { 980 | return GetEntityAddress(entity) + view_as
(offsAttributeList); 981 | } 982 | return Address_Null; 983 | } 984 | 985 | static Address GetAttributeDefinitionByName(const char[] name) { 986 | Address cachedResult; 987 | if (g_AttributeDefinitionMapping.GetValue(name, cachedResult)) { 988 | return cachedResult; 989 | } 990 | 991 | Address pSchema = GetItemSchema(); 992 | if (!pSchema) { 993 | return Address_Null; 994 | } 995 | cachedResult = SDKCall(hSDKGetAttributeDefByName, pSchema, name); 996 | g_AttributeDefinitionMapping.SetValue(name, cachedResult); 997 | return cachedResult; 998 | } 999 | 1000 | static Address GetAttributeDefinitionByID(int id) { 1001 | Address pSchema = GetItemSchema(); 1002 | if (!pSchema) { 1003 | return Address_Null; 1004 | } 1005 | return SDKCall(hSDKGetAttributeDef, pSchema, id); 1006 | } 1007 | 1008 | /** 1009 | * Returns true if an attribute with the specified name exists, storing the definition index 1010 | * to the given by-ref `iDefIndex` argument. 1011 | */ 1012 | static bool GetAttributeDefIndexByName(const char[] name, int &iDefIndex) { 1013 | Address pAttribDef = GetAttributeDefinitionByName(name); 1014 | if (!pAttribDef) { 1015 | return false; 1016 | } 1017 | 1018 | iDefIndex = LoadFromAddressOffset(pAttribDef, 0x04, NumberType_Int16); 1019 | return true; 1020 | } 1021 | 1022 | static Address GetEntityAttributeManager(int entity) { 1023 | Address pAttributeList = GetEntityAttributeList(entity); 1024 | if (!pAttributeList) { 1025 | return Address_Null; 1026 | } 1027 | 1028 | Address pAttributeManager = DereferencePointer(pAttributeList, .offset = 0x18); 1029 | AssertValidAddress(pAttributeManager); 1030 | return pAttributeManager; 1031 | } 1032 | 1033 | /** 1034 | * Returns the address of a CEconItemAttribute instance on an entity with the given attribute 1035 | * definition index, allocating it if one doesn't already exist. For networked attributes this 1036 | * is zero-initialized; heap-based attributes are considered uninitialized. 1037 | */ 1038 | static Address FindOrAllocateEconItemAttribute(int entity, int attrdef) { 1039 | Address pAttrDef = GetAttributeDefinitionByID(attrdef); 1040 | if (!pAttrDef) { 1041 | return Address_Null; 1042 | } 1043 | 1044 | Address pAttributeList = GetEntityAttributeList(entity); 1045 | if (!pAttributeList) { 1046 | // we checked this before, but just in case refactors happen... 1047 | return Address_Null; 1048 | } 1049 | 1050 | Address pEconItemAttribute = SDKCall(hSDKGetAttributeByID, pAttributeList, attrdef); 1051 | if (!pEconItemAttribute) { 1052 | SDKCall(hSDKSetRuntimeValue, pAttributeList, pAttrDef, 0.0); 1053 | pEconItemAttribute = SDKCall(hSDKGetAttributeByID, pAttributeList, attrdef); 1054 | } 1055 | return pEconItemAttribute; 1056 | } 1057 | 1058 | /** 1059 | * Initializes the space occupied by a given CEconItemAttribute pointer, parsing and allocating 1060 | * the raw value based on the attribute's underlying type. This should correctly parse numeric 1061 | * and string values. 1062 | */ 1063 | static bool InitializeAttributeValue(int attrdef, Address pEconItemAttribute, const char[] value) { 1064 | Address pAttrDef = GetAttributeDefinitionByID(attrdef); 1065 | if (!pAttrDef) { 1066 | return false; 1067 | } 1068 | 1069 | Address pDefType = DereferencePointer(pAttrDef + view_as
(0x08)); 1070 | Address pAttributeValue = pEconItemAttribute + view_as
(0x08); 1071 | 1072 | if (!IsNetworkedRuntimeAttribute(pDefType)) { 1073 | // reusing any existing matching attribute value strings 1074 | Address rawAttributeValue = GetHeapManagedAttributeString(attrdef, value); 1075 | if (rawAttributeValue) { 1076 | StoreToAddress(pAttributeValue, view_as(rawAttributeValue), NumberType_Int32); 1077 | return true; 1078 | } 1079 | 1080 | /** 1081 | * initialize raw value; any existing values present in the CEconItemAttribute* are trashed 1082 | * 1083 | * that is okay -- tf2attributes is the only one managing heap-allocated values, and 1084 | * it holds its own reference to the value for freeing later 1085 | * 1086 | * we don't attempt to free any existing attribute value mid-game as we don't know if 1087 | * the value is present in multiple places (no refcounts!) 1088 | */ 1089 | SDKCall(hSDKAttributeValueInitialize, pDefType, pAttributeValue); 1090 | 1091 | // add to our managed values 1092 | // this definitely works for heap, not sure if it works for inline 1093 | HeapAttributeValue attribute; 1094 | attribute.m_iAttributeDefinitionIndex = attrdef; 1095 | attribute.m_pAttributeValue = DereferencePointer(pAttributeValue); 1096 | 1097 | g_ManagedAllocatedValues.PushArray(attribute); 1098 | } 1099 | 1100 | if (!SDKCall(hSDKAttributeValueFromString, pDefType, pAttrDef, value, pAttributeValue, true)) { 1101 | // we couldn't parse the attribute value, abort 1102 | return false; 1103 | } 1104 | return true; 1105 | } 1106 | 1107 | /** 1108 | * Returns the address of an existing instance for the given attribute definition and string 1109 | * value, if it exists. 1110 | */ 1111 | static Address GetHeapManagedAttributeString(int attrdef, const char[] value) { 1112 | /** 1113 | * we restrict it to strings as we don't have a way to determine equality on non-string 1114 | * attributes. 1115 | */ 1116 | if (!IsAttributeString(attrdef)) { 1117 | return Address_Null; 1118 | } 1119 | 1120 | for (int i, n = g_ManagedAllocatedValues.Length; i < n; i++) { 1121 | HeapAttributeValue existingAttribute; 1122 | g_ManagedAllocatedValues.GetArray(i, existingAttribute, sizeof(existingAttribute)); 1123 | 1124 | if (existingAttribute.m_iAttributeDefinitionIndex != attrdef) { 1125 | continue; 1126 | } 1127 | 1128 | char attributeString[PLATFORM_MAX_PATH]; 1129 | ReadStringAttributeValue(existingAttribute.m_pAttributeValue, attributeString, sizeof(attributeString)); 1130 | if (StrEqual(attributeString, value)) { 1131 | return existingAttribute.m_pAttributeValue; 1132 | } 1133 | } 1134 | return Address_Null; 1135 | } 1136 | 1137 | /** 1138 | * Returns true if the given attribute type can (normally) be networked. 1139 | * We make the assumption that non-networked attributes have to be heap / inline allocated. 1140 | */ 1141 | static bool IsNetworkedRuntimeAttribute(Address pDefType) { 1142 | return SDKCall(hSDKAttributeTypeCanBeNetworked, pDefType); 1143 | } 1144 | 1145 | /** 1146 | * Unloads the attribute in a given CEconItemAttribute instance. 1147 | */ 1148 | #pragma unused UnloadAttributeValue 1149 | static void UnloadAttributeValue(Address pAttrDef, Address pEconItemAttribute) { 1150 | Address pDefType = DereferencePointer(pAttrDef + view_as
(0x08)); 1151 | Address pAttributeValue = pEconItemAttribute + view_as
(0x08); 1152 | 1153 | SDKCall(hSDKAttributeValueUnload, pDefType, pAttributeValue); 1154 | } 1155 | 1156 | /** 1157 | * Unloads the given raw attribute value. 1158 | */ 1159 | static void UnloadAttributeRawValue(Address pAttrDef, Address pAttributeValue) { 1160 | Address pAttributeDataUnion = pAttributeValue; 1161 | Address pDefType = DereferencePointer(pAttrDef + view_as
(0x08)); 1162 | SDKCall(hSDKAttributeValueUnloadByRef, pDefType, pAttributeDataUnion); 1163 | } 1164 | 1165 | /** 1166 | * Returns true if the given attribute definition index is a string. 1167 | */ 1168 | static bool IsAttributeString(int attrdef) { 1169 | Address pAttrDef = GetAttributeDefinitionByID(attrdef); 1170 | Address pKnownStringAttribDef = GetAttributeDefinitionByName("cosmetic taunt sound"); 1171 | return pAttrDef && pKnownStringAttribDef 1172 | && DereferencePointer(pAttrDef, 0x08) == DereferencePointer(pKnownStringAttribDef, 0x08); 1173 | } 1174 | 1175 | /** 1176 | * Reads the contents of a CAttribute_String raw value. 1177 | */ 1178 | static int ReadStringAttributeValue(Address pRawValue, char[] buffer, int maxlen) { 1179 | /** 1180 | * Linux, Windows, and Mac differ slightly on how the std::string is laid out. 1181 | * 1182 | * For the Linux binary, the first member is a char* containing the contents of the string. 1183 | * Deref that and call it a day. 1184 | * 1185 | * Windows implements it as a union where it's either a `char[16]` or a `char*, size_t @ 0x14`. 1186 | * Check if the size_t is less than 16, then read the inline string or deref the char* depending on the results. 1187 | * 1188 | * Mac implements it as either a `bool, char[]` or `bool, char* @ 0x8`. 1189 | * 1190 | * I'm too lazy to reimplement the platform-specific bits; we're going to use sigs for this. 1191 | */ 1192 | Address pString; 1193 | SDKCall(hSDKCopyStringAttributeToCharPointer, pRawValue, pString); 1194 | return LoadStringFromAddress(pString, buffer, maxlen); 1195 | } 1196 | 1197 | /** 1198 | * Iterates over entities and removes any attributes that aren't networked (that is, 1199 | * allocated on the heap). 1200 | * 1201 | * We must do this before we unload ourselves, otherwise the game will crash trying to look up 1202 | * the heap runtime attributes we managed. 1203 | */ 1204 | static void RemoveNonNetworkedRuntimeAttributesOnEntities() { 1205 | // remove heap-based attributes from any existing entities so they don't use-after-free 1206 | int entity = -1; 1207 | while ((entity = FindEntityByClassname(entity, "*")) != -1) { 1208 | // iterate runtime attribute list and remove string attributes 1209 | // implementation straight from TF2Attrib_ListDefIndices, go over there for details 1210 | Address pAttributeList = GetEntityAttributeList(entity); 1211 | if (!pAttributeList) { 1212 | continue; 1213 | } 1214 | 1215 | // hold attribute defs pointing to heaped attributes so we don't mutate the runtime 1216 | // attribute list while we iterate over it - according to the CUtlVector docs the list 1217 | // can be realloc'd when an element is removed 1218 | 1219 | // the runtime attribute list can be any size, the current limit of 20 is on networked 1220 | ArrayList heapedAttribDefs = new ArrayList(); 1221 | 1222 | int iNumAttribs = LoadFromAddressOffset(pAttributeList, 0x10, NumberType_Int32); 1223 | if (!iNumAttribs) { 1224 | continue; 1225 | } 1226 | 1227 | Address pAttribListData = DereferencePointer(pAttributeList, .offset = 0x04); 1228 | 1229 | // we know there are attributes; make sure our contiguous memory is valid 1230 | AssertValidAddress(pAttribListData); 1231 | 1232 | for (int i = 0; i < iNumAttribs; i++) { 1233 | Address pAttributeEntry = pAttribListData + view_as
(i * 0x10); 1234 | int attrdef = LoadFromAddressOffset(pAttributeEntry, 0x04, NumberType_Int16); 1235 | 1236 | Address pAttrDef = GetAttributeDefinitionByID(attrdef); 1237 | if (!pAttrDef) { 1238 | // this shouldn't happen, but just in case 1239 | continue; 1240 | } 1241 | 1242 | Address pDefType = DereferencePointer(pAttrDef + view_as
(0x08)); 1243 | if (IsNetworkedRuntimeAttribute(pDefType)) { 1244 | continue; 1245 | } 1246 | 1247 | any rawValue = LoadFromAddressOffset(pAttributeEntry, 0x08, NumberType_Int32); 1248 | 1249 | // allow plugins to `TF2Attrib_Set*()` their own instances undisturbed by only 1250 | // processing attributes that we're aware of 1251 | if (IsAttributeValueInHeap(rawValue)) { 1252 | // we should be passing around pAttrDef instead, 1253 | // but I want the nice display printout 1254 | heapedAttribDefs.Push(attrdef); 1255 | } 1256 | } 1257 | 1258 | while (heapedAttribDefs.Length) { 1259 | int attrdef = heapedAttribDefs.Get(0); 1260 | heapedAttribDefs.Erase(0); 1261 | 1262 | Address pAttribDef = GetAttributeDefinitionByID(attrdef); 1263 | 1264 | PrintToServer("[tf2attributes] " 1265 | ... "Removing heap-allocated attribute index %d from entity %d", 1266 | attrdef, entity); 1267 | 1268 | SDKCall(hSDKRemoveAttribute, pAttributeList, pAttribDef); 1269 | } 1270 | delete heapedAttribDefs; 1271 | 1272 | ClearAttributeCache(entity); 1273 | } 1274 | } 1275 | 1276 | /** 1277 | * Frees our heap-allocated managed attribute values so they don't leak. 1278 | * This happens on map change (where runtime attributes are invalidated) and when the plugin is 1279 | * unloaded. 1280 | */ 1281 | void DestroyManagedAllocatedValues() { 1282 | while (g_ManagedAllocatedValues.Length) { 1283 | HeapAttributeValue attribute; 1284 | g_ManagedAllocatedValues.GetArray(0, attribute, sizeof(attribute)); 1285 | 1286 | attribute.Destroy(); 1287 | 1288 | g_ManagedAllocatedValues.Erase(0); 1289 | } 1290 | } 1291 | 1292 | bool IsAttributeValueInHeap(any rawValue) { 1293 | for (int i, n = g_ManagedAllocatedValues.Length; i < n; i++) { 1294 | HeapAttributeValue a; 1295 | g_ManagedAllocatedValues.GetArray(i, a, sizeof(a)); 1296 | 1297 | if (a.m_pAttributeValue == rawValue) { 1298 | return true; 1299 | } 1300 | } 1301 | return false; 1302 | } 1303 | 1304 | /** 1305 | * Inserts a string into the game's string pool. This uses the same implementation that is in 1306 | * SourceMod's core: 1307 | * 1308 | * https://github.com/alliedmodders/sourcemod/blob/b14c18ee64fc822dd6b0f5baea87226d59707d5a/core/HalfLife2.cpp#L1415-L1423 1309 | */ 1310 | stock Address AllocPooledString(const char[] value) { 1311 | Address pValue; 1312 | if (g_AllocPooledStringCache.GetValue(value, pValue)) { 1313 | return pValue; 1314 | } 1315 | 1316 | int ent = FindEntityByClassname(-1, "worldspawn"); 1317 | if (!IsValidEntity(ent)) { 1318 | return Address_Null; 1319 | } 1320 | int offset = FindDataMapInfo(ent, "m_iName"); 1321 | if (offset <= 0) { 1322 | return Address_Null; 1323 | } 1324 | Address pOrig = view_as
(GetEntData(ent, offset)); 1325 | DispatchKeyValue(ent, "targetname", value); 1326 | pValue = view_as
(GetEntData(ent, offset)); 1327 | SetEntData(ent, offset, pOrig); 1328 | 1329 | g_AllocPooledStringCache.SetValue(value, pValue); 1330 | return pValue; 1331 | } 1332 | 1333 | stock int LoadFromAddressOffset(Address addr, int offset, NumberType size) { 1334 | return LoadFromAddress(addr + view_as
(offset), size); 1335 | } 1336 | 1337 | stock void StoreToAddressOffset(Address addr, int offset, int data, NumberType size) { 1338 | StoreToAddress(addr + view_as
(offset), data, size); 1339 | } 1340 | 1341 | stock Address DereferencePointer(Address addr, int offset = 0) { 1342 | return view_as
(LoadFromAddressOffset(addr, offset, NumberType_Int32)); 1343 | } 1344 | 1345 | stock int LoadStringFromAddress(Address addr, char[] buffer, int maxlen, 1346 | bool &bIsNullPointer = false) { 1347 | if (!addr) { 1348 | bIsNullPointer = true; 1349 | return 0; 1350 | } 1351 | 1352 | int c; 1353 | char ch; 1354 | do { 1355 | ch = view_as(LoadFromAddress(addr + view_as
(c), NumberType_Int8)); 1356 | buffer[c] = ch; 1357 | } while (ch && ++c < maxlen - 1); 1358 | return c; 1359 | } 1360 | 1361 | /** 1362 | * Runtime assertion that we're receiving valid addresses. 1363 | * If we're not, something has gone terribly wrong and we might need to update. 1364 | */ 1365 | stock void AssertValidAddress(Address pAddress) { 1366 | static Address Address_MinimumValid = view_as
(0x10000); 1367 | if (pAddress == Address_Null) { 1368 | ThrowError("Received invalid address (NULL)"); 1369 | } 1370 | if (unsigned_compare(view_as(pAddress), view_as(Address_MinimumValid)) < 0) { 1371 | ThrowError("Received invalid address (%08x)", pAddress); 1372 | } 1373 | } 1374 | 1375 | stock int unsigned_compare(int a, int b) { 1376 | if (a == b) { 1377 | return 0; 1378 | } 1379 | if ((a >>> 31) == (b >>> 31)) { 1380 | return ((a & 0x7FFFFFFF) > (b & 0x7FFFFFFF)) ? 1 : -1; 1381 | } 1382 | return ((a >>> 31) > (b >>> 31)) ? 1 : -1; 1383 | } 1384 | /* 1385 | struct CEconItemAttributeDefinition 1386 | { 1387 | WORD index, //4 1388 | WORD blank, 1389 | DWORD type, //8 1390 | BYTE hidden, //12 1391 | BYTE force_output_description, //13 1392 | BYTE stored_as_integer, //14 1393 | BYTE instance_data, //15 1394 | BYTE is_set_bonus, //16 1395 | BYTE blank, 1396 | BYTE blank, 1397 | BYTE blank, 1398 | DWORD is_user_generated, //20 1399 | DWORD effect_type, //24 1400 | DWORD description_format, //28 1401 | DWORD description_string, //32 1402 | DWORD armory_desc, //36 1403 | DWORD name, //40 1404 | DWORD attribute_class, //44 1405 | BYTE can_affect_market_name, //48 1406 | BYTE can_affect_recipe_component_name, //49 1407 | BYTE blank, 1408 | BYTE blank, 1409 | DWORD apply_tag_to_item_definition, //52 1410 | DWORD unknown 1411 | 1412 | };*/ 1413 | /*class CEconItemAttribute 1414 | { 1415 | public: 1416 | void *m_pVTable; //0 1417 | 1418 | uint16 m_iAttributeDefinitionIndex; //4 1419 | float m_flValue; //8 1420 | int32 m_nRefundableCurrency; //12 1421 | -----removed float m_flInitialValue; //12 1422 | -----removed bool m_bSetBonus; //20 1423 | }; 1424 | and +24 is still attribute manager 1425 | */ 1426 | -------------------------------------------------------------------------------- /scripting/tf2attributes_example.sp: -------------------------------------------------------------------------------- 1 | #pragma semicolon 1 2 | #pragma newdecls required 3 | 4 | #include 5 | #include 6 | 7 | #define MAX_RUNTIME_ATTRIBUTES 20 8 | 9 | Address lastAddr[MAXPLAYERS + 1]; 10 | 11 | public void OnPluginStart() 12 | { 13 | RegAdminCmd("sm_addattrib", Command_AddAttrib, ADMFLAG_ROOT); 14 | RegAdminCmd("sm_addwepatt", Command_AddWepAttrib, ADMFLAG_ROOT); 15 | RegAdminCmd("sm_remattrib", Command_RemAttrib, ADMFLAG_ROOT); 16 | RegAdminCmd("sm_remwepatt", Command_RemWepAttrib, ADMFLAG_ROOT); 17 | RegAdminCmd("sm_remallatt", Command_RemAllAttrib, ADMFLAG_ROOT); 18 | RegAdminCmd("sm_remallwepatt", Command_RemAllWepAttrib, ADMFLAG_ROOT); 19 | RegAdminCmd("sm_getattrib", Command_GetAttrByName, ADMFLAG_ROOT); 20 | // RegAdminCmd("sm_getattrid", Command_GetAttrByID, ADMFLAG_ROOT); 21 | RegAdminCmd("sm_getattrs", Command_GetAttrs, ADMFLAG_ROOT); 22 | // RegAdminCmd("sm_attrset", SetValueStuff, ADMFLAG_ROOT); //Definitely unsafe as all hell 23 | LoadTranslations("common.phrases"); 24 | } 25 | public void OnMapStart() 26 | { 27 | for (int client = 0; client <= MaxClients; client++) 28 | lastAddr[client] = Address_Null; 29 | } 30 | public Action Command_RemAttrib(int client, int args) 31 | { 32 | char arg1[32]; 33 | char arg2[32]; 34 | if (args < 2) 35 | { 36 | ReplyToCommand(client, "[SM] Usage: sm_remattrib "); 37 | arg1 = "@me"; 38 | return Plugin_Handled; 39 | } 40 | 41 | GetCmdArg(1, arg1, sizeof(arg1)); 42 | GetCmdArg(2, arg2, sizeof(arg2)); 43 | bool bydefidx = false; 44 | if (arg2[0] == '#') 45 | { 46 | strcopy(arg2, sizeof(arg2), arg2[1]); 47 | bydefidx = true; 48 | } 49 | 50 | /** 51 | * target_name - stores the noun identifying the target(s) 52 | * target_list - array to store clients 53 | * target_count - variable to store number of clients 54 | * tn_is_ml - stores whether the noun must be translated 55 | */ 56 | char target_name[MAX_TARGET_LENGTH]; 57 | int target_list[MAXPLAYERS]; 58 | int target_count; 59 | bool tn_is_ml; 60 | 61 | if (arg1[0] == '#' && arg1[1] == '#') //'##entindex' instead of target 62 | { 63 | target_list[0] = StringToInt(arg1[2]); 64 | target_count = 1; 65 | strcopy(target_name, sizeof(target_name), arg1[2]); 66 | tn_is_ml = false; 67 | } 68 | else 69 | if ((target_count = ProcessTargetString( 70 | arg1, 71 | client, 72 | target_list, 73 | MAXPLAYERS, 74 | COMMAND_FILTER_CONNECTED, 75 | target_name, 76 | sizeof(target_name), 77 | tn_is_ml)) <= 0) 78 | { 79 | /* This function replies to the admin with a failure message */ 80 | ReplyToTargetError(client, target_count); 81 | return Plugin_Handled; 82 | } 83 | 84 | for (int i = 0; i < target_count; i++) 85 | { 86 | if (IsValidEntity(target_list[i])) 87 | { 88 | if (bydefidx) 89 | TF2Attrib_RemoveByDefIndex(target_list[i], StringToInt(arg2)); 90 | else 91 | TF2Attrib_RemoveByName(target_list[i], arg2); 92 | } 93 | } 94 | if (tn_is_ml) 95 | ReplyToCommand(client, "[SM] Removed attrib '%s' from %t", arg2, target_name); 96 | else 97 | ReplyToCommand(client, "[SM] Removed attrib '%s' from %s", arg2, target_name); 98 | return Plugin_Handled; 99 | } 100 | public Action Command_RemWepAttrib(int client, int args) 101 | { 102 | char arg1[32]; 103 | char arg2[32]; 104 | if (args < 2) 105 | { 106 | ReplyToCommand(client, "[SM] Usage: sm_remwepatt "); 107 | arg1 = "@me"; 108 | return Plugin_Handled; 109 | } 110 | 111 | GetCmdArg(1, arg1, sizeof(arg1)); 112 | GetCmdArg(2, arg2, sizeof(arg2)); 113 | bool bydefidx = false; 114 | if (arg2[0] == '#') 115 | { 116 | strcopy(arg2, sizeof(arg2), arg2[1]); 117 | bydefidx = true; 118 | } 119 | /** 120 | * target_name - stores the noun identifying the target(s) 121 | * target_list - array to store clients 122 | * target_count - variable to store number of clients 123 | * tn_is_ml - stores whether the noun must be translated 124 | */ 125 | char target_name[MAX_TARGET_LENGTH]; 126 | int target_list[MAXPLAYERS]; 127 | int target_count; 128 | bool tn_is_ml; 129 | 130 | if ((target_count = ProcessTargetString( 131 | arg1, 132 | client, 133 | target_list, 134 | MAXPLAYERS, 135 | COMMAND_FILTER_ALIVE, 136 | target_name, 137 | sizeof(target_name), 138 | tn_is_ml)) <= 0) 139 | { 140 | /* This function replies to the admin with a failure message */ 141 | ReplyToTargetError(client, target_count); 142 | return Plugin_Handled; 143 | } 144 | for (int i = 0; i < target_count; i++) 145 | { 146 | if (IsValidClient(target_list[i])) 147 | { 148 | int wep = GetEntPropEnt(target_list[i], Prop_Send, "m_hActiveWeapon"); 149 | if (IsValidEntity(wep)) 150 | (bydefidx ? TF2Attrib_RemoveByDefIndex(wep, StringToInt(arg2)) : TF2Attrib_RemoveByName(wep, arg2)); 151 | } 152 | } 153 | if (tn_is_ml) 154 | ReplyToCommand(client, "[SM] Removed attrib '%s' from active wep of %t", arg2, target_name); 155 | else 156 | ReplyToCommand(client, "[SM] Removed attrib '%s' from active wep of %s", arg2, target_name); 157 | return Plugin_Handled; 158 | } 159 | public Action Command_RemAllAttrib(int client, int args) 160 | { 161 | char arg1[32]; 162 | if (args < 1) 163 | { 164 | ReplyToCommand(client, "[SM] Usage: sm_remallatt "); 165 | arg1 = "@me"; 166 | } 167 | else GetCmdArg(1, arg1, sizeof(arg1)); 168 | 169 | 170 | /** 171 | * target_name - stores the noun identifying the target(s) 172 | * target_list - array to store clients 173 | * target_count - variable to store number of clients 174 | * tn_is_ml - stores whether the noun must be translated 175 | */ 176 | char target_name[MAX_TARGET_LENGTH]; 177 | int target_list[MAXPLAYERS]; 178 | int target_count; 179 | bool tn_is_ml; 180 | 181 | if (arg1[0] == '#' && arg1[1] == '#') //'##entindex' instead of target 182 | { 183 | target_list[0] = StringToInt(arg1[2]); 184 | target_count = 1; 185 | strcopy(target_name, sizeof(target_name), arg1[2]); 186 | tn_is_ml = false; 187 | } 188 | else 189 | if ((target_count = ProcessTargetString( 190 | arg1, 191 | client, 192 | target_list, 193 | MAXPLAYERS, 194 | COMMAND_FILTER_CONNECTED, 195 | target_name, 196 | sizeof(target_name), 197 | tn_is_ml)) <= 0) 198 | { 199 | /* This function replies to the admin with a failure message */ 200 | ReplyToTargetError(client, target_count); 201 | return Plugin_Handled; 202 | } 203 | for (int i = 0; i < target_count; i++) 204 | { 205 | if (IsValidEntity(target_list[i])) 206 | { 207 | TF2Attrib_RemoveAll(target_list[i]); 208 | } 209 | } 210 | if (tn_is_ml) 211 | ReplyToCommand(client, "[SM] Removed allattrib from %t", target_name); 212 | else 213 | ReplyToCommand(client, "[SM] Removed allattrib from %s", target_name); 214 | return Plugin_Handled; 215 | } 216 | public Action Command_RemAllWepAttrib(int client, int args) 217 | { 218 | char arg1[32]; 219 | if (args < 1) 220 | { 221 | ReplyToCommand(client, "[SM] Usage: sm_remallwepatt "); 222 | arg1 = "@me"; 223 | } 224 | else GetCmdArg(1, arg1, sizeof(arg1)); 225 | 226 | /** 227 | * target_name - stores the noun identifying the target(s) 228 | * target_list - array to store clients 229 | * target_count - variable to store number of clients 230 | * tn_is_ml - stores whether the noun must be translated 231 | */ 232 | char target_name[MAX_TARGET_LENGTH]; 233 | int target_list[MAXPLAYERS]; 234 | int target_count; 235 | bool tn_is_ml; 236 | 237 | if ((target_count = ProcessTargetString( 238 | arg1, 239 | client, 240 | target_list, 241 | MAXPLAYERS, 242 | COMMAND_FILTER_ALIVE, 243 | target_name, 244 | sizeof(target_name), 245 | tn_is_ml)) <= 0) 246 | { 247 | /* This function replies to the admin with a failure message */ 248 | ReplyToTargetError(client, target_count); 249 | return Plugin_Handled; 250 | } 251 | for (int i = 0; i < target_count; i++) 252 | { 253 | if (IsValidClient(target_list[i])) 254 | { 255 | int wep = GetEntPropEnt(target_list[i], Prop_Send, "m_hActiveWeapon"); 256 | if (IsValidEntity(wep)) 257 | TF2Attrib_RemoveAll(wep); 258 | } 259 | } 260 | if (tn_is_ml) 261 | ReplyToCommand(client, "[SM] Removed allattrib from active wep of %t", target_name); 262 | else 263 | ReplyToCommand(client, "[SM] Removed allattrib from active wep of %s", target_name); 264 | return Plugin_Handled; 265 | } 266 | public Action Command_AddAttrib(int client, int args) 267 | { 268 | char arg1[32]; 269 | char arg2[128]; 270 | char arg3[32]; 271 | if (args < 3) 272 | { 273 | ReplyToCommand(client, "[SM] Usage: sm_addattrib [pass as int]"); 274 | return Plugin_Handled; 275 | } 276 | 277 | GetCmdArg(1, arg1, sizeof(arg1)); 278 | GetCmdArg(2, arg2, sizeof(arg2)); 279 | GetCmdArg(3, arg3, sizeof(arg3)); 280 | bool passint = false; 281 | if (args > 3) passint = true; 282 | float val = (passint ? (view_as(StringToInt(arg3))) : StringToFloat(arg3)); 283 | bool bydefidx = false; 284 | if (arg2[0] == '#') 285 | { 286 | strcopy(arg2, sizeof(arg2), arg2[1]); 287 | bydefidx = true; 288 | } 289 | /** 290 | * target_name - stores the noun identifying the target(s) 291 | * target_list - array to store clients 292 | * target_count - variable to store number of clients 293 | * tn_is_ml - stores whether the noun must be translated 294 | */ 295 | char target_name[MAX_TARGET_LENGTH]; 296 | int target_list[MAXPLAYERS]; 297 | int target_count; 298 | bool tn_is_ml; 299 | 300 | if (arg1[0] == '#' && arg1[1] == '#') //'##entindex' instead of target 301 | { 302 | target_list[0] = StringToInt(arg1[2]); 303 | target_count = 1; 304 | strcopy(target_name, sizeof(target_name), arg1[2]); 305 | tn_is_ml = false; 306 | } 307 | else 308 | if ((target_count = ProcessTargetString( 309 | arg1, 310 | client, 311 | target_list, 312 | MAXPLAYERS, 313 | COMMAND_FILTER_CONNECTED, 314 | target_name, 315 | sizeof(target_name), 316 | tn_is_ml)) <= 0) 317 | { 318 | /* This function replies to the admin with a failure message */ 319 | ReplyToTargetError(client, target_count); 320 | return Plugin_Handled; 321 | } 322 | for (int i = 0; i < target_count; i++) 323 | { 324 | if (IsValidEntity(target_list[i])) 325 | { 326 | bool result = false; 327 | if (bydefidx) 328 | result = TF2Attrib_SetByDefIndex(target_list[i], StringToInt(arg2), val); 329 | else 330 | result = TF2Attrib_SetByName(target_list[i], arg2, val); 331 | if (target_count == 1) 332 | { 333 | ReplyToCommand(client, "[SM] AddAttrib returned %d", result); 334 | } 335 | } 336 | } 337 | if (tn_is_ml) 338 | ReplyToCommand(client, "[SM] Added attrib '%s' val %s to %t%s", arg2, arg3, target_name, passint ? " as int" : ""); 339 | else 340 | ReplyToCommand(client, "[SM] Added attrib '%s' val %s to %s%s", arg2, arg3, target_name, passint ? " as int" : ""); 341 | return Plugin_Handled; 342 | } 343 | public Action Command_AddWepAttrib(int client, int args) 344 | { 345 | char arg1[32]; 346 | char arg2[128]; 347 | char arg3[32]; 348 | if (args < 3) 349 | { 350 | ReplyToCommand(client, "[SM] Usage: sm_addattrib [pass as int]"); 351 | return Plugin_Handled; 352 | } 353 | 354 | GetCmdArg(1, arg1, sizeof(arg1)); 355 | GetCmdArg(2, arg2, sizeof(arg2)); 356 | GetCmdArg(3, arg3, sizeof(arg3)); 357 | bool passint = false; 358 | if (args > 3) passint = true; 359 | float val = (passint ? (view_as(StringToInt(arg3))) : StringToFloat(arg3)); 360 | bool bydefidx = false; 361 | if (arg2[0] == '#') 362 | { 363 | strcopy(arg2, sizeof(arg2), arg2[1]); 364 | bydefidx = true; 365 | } 366 | /** 367 | * target_name - stores the noun identifying the target(s) 368 | * target_list - array to store clients 369 | * target_count - variable to store number of clients 370 | * tn_is_ml - stores whether the noun must be translated 371 | */ 372 | char target_name[MAX_TARGET_LENGTH]; 373 | int target_list[MAXPLAYERS]; 374 | int target_count; 375 | bool tn_is_ml; 376 | 377 | if ((target_count = ProcessTargetString( 378 | arg1, 379 | client, 380 | target_list, 381 | MAXPLAYERS, 382 | COMMAND_FILTER_ALIVE, 383 | target_name, 384 | sizeof(target_name), 385 | tn_is_ml)) <= 0) 386 | { 387 | /* This function replies to the admin with a failure message */ 388 | ReplyToTargetError(client, target_count); 389 | return Plugin_Handled; 390 | } 391 | for (int i = 0; i < target_count; i++) 392 | { 393 | if (IsValidClient(target_list[i])) 394 | { 395 | int wep = GetEntPropEnt(target_list[i], Prop_Send, "m_hActiveWeapon"); 396 | if (!IsValidEntity(wep)) continue; 397 | bool result = (bydefidx ? TF2Attrib_SetByDefIndex(wep, StringToInt(arg2), val) : TF2Attrib_SetByName(wep, arg2, val)); 398 | if (target_count == 1) 399 | { 400 | ReplyToCommand(client, "[SM] AddAttrib wep returned %d", result); 401 | } 402 | } 403 | } 404 | if (tn_is_ml) 405 | ReplyToCommand(client, "[SM] Added attrib '%s' val %s to active wep of %t%s", arg2, arg3, target_name, passint ? " as int" : ""); 406 | else 407 | ReplyToCommand(client, "[SM] Added attrib '%s' val %s to active wep of %s%s", arg2, arg3, target_name, passint ? " as int" : ""); 408 | return Plugin_Handled; 409 | } 410 | public Action Command_GetAttrByName(int client, int args) 411 | { 412 | char arg1[32]; 413 | char arg2[128]; 414 | char arg3[32]; 415 | if (args < 2) 416 | { 417 | ReplyToCommand(client, "[SM] Usage: sm_getattrib [p/w]"); 418 | return Plugin_Handled; 419 | } 420 | 421 | GetCmdArg(1, arg1, sizeof(arg1)); 422 | GetCmdArg(2, arg2, sizeof(arg2)); 423 | bool bydefidx = false; 424 | if (arg2[0] == '#') 425 | { 426 | strcopy(arg2, sizeof(arg2), arg2[1]); 427 | bydefidx = true; 428 | } 429 | if (args > 2) GetCmdArg(3, arg3, sizeof(arg3)); 430 | else arg3 = "p"; 431 | bool usePlayer = arg3[0] != 'w'; 432 | int target = -1; 433 | if (arg1[0] == '#' && arg1[1] == '#') //'##entindex' instead of target 434 | { 435 | target = StringToInt(arg1[2]); 436 | } 437 | else target = FindTarget(client, arg1, false, false); 438 | if (!IsValidEntity(target)) return Plugin_Handled; 439 | 440 | int wep = target; 441 | if (!usePlayer) 442 | { 443 | wep = GetEntPropEnt(target, Prop_Send, "m_hActiveWeapon"); 444 | if (!IsValidEntity(wep)) 445 | { 446 | usePlayer = true; 447 | wep = target; 448 | } 449 | } 450 | 451 | Address pAttrib = (bydefidx ? TF2Attrib_GetByDefIndex(wep, StringToInt(arg2)) : TF2Attrib_GetByName(wep, arg2)); 452 | lastAddr[client] = pAttrib; 453 | if (!IsValidAddress(view_as
(pAttrib))) 454 | { 455 | ReplyToCommand(client, "[SM] GetAttrib got null attrib '%s' on %s%d", arg2, usePlayer ? "" : "active wep of ", target);//, target); 456 | return Plugin_Handled; 457 | } 458 | float result = TF2Attrib_GetValue(pAttrib); 459 | int idx = TF2Attrib_GetDefIndex(pAttrib); 460 | if (TF2Attrib_IsIntegerValue(idx)) result = float(view_as(result)); 461 | float init;// = TF2Attrib_GetInitialValue(pAttrib); 462 | if (TF2Attrib_IsIntegerValue(idx)) init = float(view_as(init)); 463 | ReplyToCommand(client, "[SM] GetAttrib got: %d %d ; %.3f, %.3f, %d, %d for attrib '%s' on %s%d", view_as(pAttrib), idx, result, init, TF2Attrib_GetRefundableCurrency(pAttrib), 0 /*TF2Attrib_GetIsSetBonus(pAttrib)*/, arg2, usePlayer ? "" : "active wep of ", target);//, target); 464 | return Plugin_Handled; 465 | } 466 | public Action Command_GetAttrByID(int client, int args) 467 | { 468 | char arg1[32]; 469 | char arg2[128]; 470 | char arg3[32]; 471 | if (args < 2) 472 | { 473 | ReplyToCommand(client, "[SM] Usage: sm_getattrib [p/w]"); 474 | return Plugin_Handled; 475 | } 476 | 477 | GetCmdArg(1, arg1, sizeof(arg1)); 478 | GetCmdArg(2, arg2, sizeof(arg2)); 479 | int index = StringToInt(arg2); 480 | if (args > 2) GetCmdArg(3, arg3, sizeof(arg3)); 481 | else arg3 = "p"; 482 | bool usePlayer = arg3[0] != 'w'; 483 | int target = -1; 484 | if (arg1[0] == '#' && arg1[1] == '#') //'##entindex' instead of target 485 | { 486 | target = StringToInt(arg1[2]); 487 | } 488 | else target = FindTarget(client, arg1, false, false); 489 | if (!IsValidEntity(target)) return Plugin_Handled; 490 | 491 | int wep = target; 492 | if (!usePlayer) 493 | { 494 | wep = GetEntPropEnt(target, Prop_Send, "m_hActiveWeapon"); 495 | if (!IsValidEntity(wep)) 496 | { 497 | usePlayer = true; 498 | wep = target; 499 | } 500 | } 501 | 502 | Address pAttrib = TF2Attrib_GetByDefIndex(wep, index); 503 | lastAddr[client] = pAttrib; 504 | if (!IsValidAddress(view_as
(pAttrib))) 505 | { 506 | ReplyToCommand(client, "[SM] GetAttrib got null attrib '%d' on %s%d", index, usePlayer ? "" : "active wep of ", target);//, target); 507 | return Plugin_Handled; 508 | } 509 | float result = TF2Attrib_GetValue(pAttrib); 510 | int idx = TF2Attrib_GetDefIndex(pAttrib); 511 | if (TF2Attrib_IsIntegerValue(idx)) result = float(view_as(result)); 512 | float init;// = TF2Attrib_GetInitialValue(pAttrib); 513 | if (TF2Attrib_IsIntegerValue(idx)) init = float(view_as(init)); 514 | ReplyToCommand(client, "[SM] GetAttrib got: %08X %d ; %.3f, %.3f, %d, %d for attrib '%s' on %s%d", view_as(pAttrib), idx, result, init, TF2Attrib_GetRefundableCurrency(pAttrib), 0 /*TF2Attrib_GetIsSetBonus(pAttrib)*/, arg2, usePlayer ? "" : "active wep of ", target);//, target); 515 | return Plugin_Handled; 516 | } 517 | public Action Command_GetAttrs(int client, int args) 518 | { 519 | char arg1[64]; 520 | char arg2[32]; 521 | if (args < 1) 522 | { 523 | ReplyToCommand(client, "[SM] Usage: sm_getattrs [p/w]"); 524 | return Plugin_Handled; 525 | } 526 | 527 | GetCmdArg(1, arg1, sizeof(arg1)); 528 | arg2 = "p"; 529 | if (args > 1) 530 | { 531 | GetCmdArg(2, arg2, sizeof(arg2)); 532 | } 533 | bool usePlayer = arg2[0] != 'w'; 534 | int target = -1; 535 | if (arg1[0] == '#' && arg1[1] == '#') //'##entindex' instead of target 536 | { 537 | target = StringToInt(arg1[2]); 538 | } 539 | else target = FindTarget(client, arg1, false, false); 540 | if (!IsValidEntity(target)) return Plugin_Handled; 541 | 542 | int wep = target; 543 | if (!usePlayer) 544 | { 545 | wep = GetEntPropEnt(target, Prop_Send, "m_hActiveWeapon"); 546 | if (!IsValidEntity(wep)) 547 | { 548 | usePlayer = true; 549 | wep = target; 550 | } 551 | } 552 | 553 | int attriblist[MAX_RUNTIME_ATTRIBUTES]; 554 | arg1 = ""; 555 | int count = TF2Attrib_ListDefIndices(wep, attriblist, sizeof(attriblist)); 556 | ReplyToCommand(client, "[SM] ListDefIndices: Got %d attributes on %s%d", count, usePlayer ? "" : "active wep of ", target); 557 | if (count > sizeof(attriblist)) 558 | { 559 | ReplyToCommand(client, "Max expected was %d", sizeof(attriblist)); 560 | } 561 | for (int i = 0; i < count && i < sizeof(attriblist); i++) 562 | { 563 | Format(arg1, sizeof(arg1), "%s %d", arg1, attriblist[i]); 564 | } 565 | TrimString(arg1); 566 | 567 | ReplyToCommand(client, "Runtime: [%s]", arg1); 568 | if (!usePlayer) 569 | { 570 | float valuelist[MAX_RUNTIME_ATTRIBUTES]; 571 | int count_static = TF2Attrib_GetSOCAttribs(wep, attriblist, valuelist, sizeof(attriblist)); 572 | if (count_static > 0) 573 | { 574 | ReplyToCommand(client, "SOC:"); 575 | } 576 | for (int i = 0; i < count_static; i++) 577 | { 578 | ReplyToCommand(client, "%d: %.3f %d", attriblist[i], valuelist[i], view_as(valuelist[i])); 579 | } 580 | int iDefIndex = GetEntProp(wep, Prop_Send, "m_iItemDefinitionIndex"); 581 | count_static = TF2Attrib_GetStaticAttribs(iDefIndex, attriblist, valuelist); 582 | if (count_static > 0) 583 | { 584 | ReplyToCommand(client, "Static:"); 585 | } 586 | for (int i = 0; i < count_static; i++) 587 | { 588 | ReplyToCommand(client, "%d: %.3f %d", attriblist[i], valuelist[i], view_as(valuelist[i])); 589 | } 590 | } 591 | return Plugin_Handled; 592 | } 593 | public Action SetValueStuff(int client, int args) 594 | { 595 | char arg1[32]; 596 | char arg2[32]; 597 | char arg3[32]; 598 | if (args < 3) 599 | { 600 | ReplyToCommand(client, "[SM] Usage: sm_attrset
"); 601 | return Plugin_Handled; 602 | } 603 | GetCmdArg(1, arg1, sizeof(arg1)); 604 | GetCmdArg(2, arg2, sizeof(arg2)); 605 | GetCmdArg(3, arg3, sizeof(arg3)); 606 | Address addr = view_as
(StringToInt(arg1)); 607 | if (addr != lastAddr[client] || !IsValidAddress(addr)) 608 | { 609 | ReplyToCommand(client, "[SM] Unsafe address"); 610 | return Plugin_Handled; 611 | } 612 | int type = StringToInt(arg2); 613 | switch (type) 614 | { 615 | case 1: TF2Attrib_SetDefIndex(addr, StringToInt(arg3)); 616 | case 2: TF2Attrib_SetValue(addr, StringToFloat(arg3)); 617 | // case 3: TF2Attrib_SetInitialValue(addr, StringToFloat(arg3)); 618 | case 3: TF2Attrib_SetRefundableCurrency(addr, StringToInt(arg3)); 619 | // case 5: TF2Attrib_SetIsSetBonus(addr, !!StringToInt(arg3)); 620 | } 621 | ReplyToCommand(client, "[SM] Set %d on %d to %s", type, addr, arg3); 622 | return Plugin_Handled; 623 | } 624 | stock bool IsValidClient(int client) 625 | { 626 | if (client <= 0 || client > MaxClients) return false; 627 | return IsClientInGame(client); 628 | } 629 | //TODO Stop using Address_MinimumValid once verified that logic still works without it 630 | stock bool IsValidAddress(Address pAddress) 631 | { 632 | static Address Address_MinimumValid = view_as
(0x10000); 633 | if (pAddress == Address_Null) 634 | return false; 635 | return unsigned_compare(view_as(pAddress), view_as(Address_MinimumValid)) >= 0; 636 | } 637 | stock int unsigned_compare(int a, int b) { 638 | if (a == b) 639 | return 0; 640 | if ((a >>> 31) == (b >>> 31)) 641 | return ((a & 0x7FFFFFFF) > (b & 0x7FFFFFFF)) ? 1 : -1; 642 | return ((a >>> 31) > (b >>> 31)) ? 1 : -1; 643 | } 644 | --------------------------------------------------------------------------------