Skip to content

Commit c11de01

Browse files
committed
Add support for accessing some playercond info
1 parent ee2abab commit c11de01

File tree

3 files changed

+259
-2
lines changed

3 files changed

+259
-2
lines changed

gamedata/tf2.utils.nosoop.txt

+50
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
{
33
"tf"
44
{
5+
"Addresses"
6+
{
7+
"&TF_COND_LAST"
8+
{
9+
"signature" "CTFPlayerShared::RemoveAllCond()"
10+
"linux"
11+
{
12+
"offset" "37"
13+
}
14+
"windows"
15+
{
16+
"offset" "147"
17+
}
18+
}
19+
}
520
"Offsets"
621
{
722
// virtual offsets
@@ -57,6 +72,34 @@
5772
"linux" "3540"
5873
"windows" "3520"
5974
}
75+
"CTFPlayerShared::m_ConditionData"
76+
{
77+
"linux" "8"
78+
"windows" "8"
79+
}
80+
"CTFPlayerShared::m_flBurnDuration"
81+
{
82+
// after unique xref to string "mult_wpn_burntime"
83+
"linux" "520"
84+
"windows" "520"
85+
}
86+
87+
"TFCondInfo_t::m_flDuration"
88+
{
89+
"linux" "8"
90+
"windows" "8"
91+
}
92+
"TFCondInfo_t::m_hProvider"
93+
{
94+
"linux" "12"
95+
"windows" "12"
96+
}
97+
98+
"sizeof(TFCondInfo_t)"
99+
{
100+
"linux" "20"
101+
"windows" "20"
102+
}
60103
}
61104
"Signatures"
62105
{
@@ -88,6 +131,13 @@
88131
"linux" "@_ZN15CTFPlayerShared18GetMaxBuffedHealthEbb"
89132
"windows" "\x55\x8B\xEC\x83\xEC\x08\x56\x8B\xF1\x57\x8B\x8E\x2A\x01\x00\x00"
90133
}
134+
"CTFPlayerShared::RemoveAllCond()"
135+
{
136+
// first non-virtual call after semi-unique xref to "Player.Spawn"
137+
"library" "server"
138+
"linux" "@_ZN15CTFPlayerShared13RemoveAllCondEv"
139+
"windows" "\x55\x8B\xEC\x51\x53\x56\x57\x8B\xF9\x8D\x8F\xE0\x00\x00\x00"
140+
}
91141
"PointInRespawnRoom()"
92142
{
93143
"library" "server"

scripting/include/tf2utils.inc

+53
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,59 @@ native int TF2Util_GetPlayerMaxAmmo(int client, int iAmmoIndex,
4848
native int TF2Util_GetPlayerMaxHealth(int client, bool bIgnoreAttributes = false,
4949
bool bIgnoreOverheal = false);
5050

51+
/**
52+
* Returns the number of conditions currently available in the game. This value will be one
53+
* higher than the last valid condition.
54+
*/
55+
native int TF2Util_GetConditionCount();
56+
57+
/**
58+
* Returns the remaining time on a player condition, or 0.0 if the condition is not active.
59+
*
60+
* This is just a wrapper around the internal condition list; some conditions may not accurately
61+
* report their duration here (such as burning).
62+
*/
63+
native float TF2Util_GetPlayerConditionDuration(int client, TFCond cond);
64+
65+
/**
66+
* Sets the remaining time on a player condition without reapplying the condition.
67+
*
68+
* @error Condition is not active.
69+
*/
70+
native void TF2Util_SetPlayerConditionDuration(int client, TFCond cond, float duration);
71+
72+
/**
73+
* Returns the entity providing a player condition, or INVALID_ENT_REFERENCE if the condition is
74+
* not active. 0 is returned for "no inflictor".
75+
*
76+
* (This is the entity described as the "inflictor" for `TF2_AddCondition`.)
77+
*/
78+
native int TF2Util_GetPlayerConditionProvider(int client, TFCond cond);
79+
80+
/**
81+
* Sets the entity providing a player condition without reapplying the condition.
82+
*
83+
* (This is the entity described as the "inflictor" for `TF2_AddCondition`.)
84+
*
85+
* @error Condition is not active.
86+
*/
87+
native void TF2Util_SetPlayerConditionProvider(int client, TFCond cond, int inflictor);
88+
89+
/**
90+
* Returns the remaining burn time on a given player. By default this decreases at a fixed rate
91+
* over time; the rate may change based on fire resistances (such as while cloaked).
92+
*
93+
* If the player is not burning, 0.0 is returned.
94+
*/
95+
native float TF2Util_GetPlayerBurnDuration(int client);
96+
97+
/**
98+
* Updates the remaining burn time on a given player. This does not ignite players.
99+
*
100+
* @error Player is not already burning.
101+
*/
102+
native void TF2Util_SetPlayerBurnDuration(int client, float duration);
103+
51104
/**
52105
* Returns the player's shoot position.
53106
*/

scripting/tf2utils.sp

+156-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
#pragma newdecls required
88

99
#include <sdkhooks>
10-
#include <sdktools>
10+
#include <tf2_stocks>
1111

1212
#include <stocksoup/memory>
1313

14-
#define PLUGIN_VERSION "0.11.0"
14+
#define PLUGIN_VERSION "0.12.0"
1515
public Plugin myinfo = {
1616
name = "TF2 Utils",
1717
author = "nosoop",
@@ -42,6 +42,16 @@ Handle g_SDKCallPointInRespawnRoom;
4242

4343
Address offs_CTFPlayer_hMyWearables;
4444

45+
Address offs_CTFPlayerShared_flBurnDuration;
46+
Address offs_CTFPlayerShared_ConditionData;
47+
48+
Address offs_TFCondInfo_flDuration;
49+
Address offs_TFCondInfo_hProvider;
50+
51+
int sizeof_TFCondInfo;
52+
53+
int g_nConditions;
54+
4555
public APLRes AskPluginLoad2(Handle self, bool late, char[] error, int maxlen) {
4656
RegPluginLibrary("nosoop_tf2utils");
4757

@@ -50,6 +60,14 @@ public APLRes AskPluginLoad2(Handle self, bool late, char[] error, int maxlen) {
5060
CreateNative("TF2Util_GetPlayerMaxHealth", Native_GetMaxHealth);
5161
CreateNative("TF2Util_GetPlayerMaxAmmo", Native_GetMaxAmmo);
5262

63+
CreateNative("TF2Util_GetPlayerConditionCount", Native_GetPlayerConditionCount);
64+
CreateNative("TF2Util_GetPlayerConditionDuration", Native_GetPlayerConditionDuration);
65+
CreateNative("TF2Util_SetPlayerConditionDuration", Native_SetPlayerConditionDuration);
66+
CreateNative("TF2Util_GetPlayerConditionProvider", Native_GetPlayerConditionProvider);
67+
CreateNative("TF2Util_SetPlayerConditionProvider", Native_SetPlayerConditionProvider);
68+
CreateNative("TF2Util_GetPlayerBurnDuration", Native_GetPlayerBurnDuration);
69+
CreateNative("TF2Util_SetPlayerBurnDuration", Native_SetPlayerBurnDuration);
70+
5371
CreateNative("TF2Util_IsEntityWearable", Native_IsEntityWearable);
5472
CreateNative("TF2Util_GetPlayerWearable", Native_GetPlayerWearable);
5573
CreateNative("TF2Util_GetPlayerWearableCount", Native_GetPlayerWearableCount);
@@ -161,6 +179,38 @@ public void OnPluginStart() {
161179
"CTFPlayer::m_hMyWearables");
162180
}
163181

182+
// TODO: use FindSendPropInfo("CTFPlayer", "m_ConditionData")
183+
if (offs_CTFPlayerShared_ConditionData <= Address_Null) {
184+
offs_CTFPlayerShared_ConditionData = GameConfGetAddressOffset(hGameConf,
185+
"CTFPlayerShared::m_ConditionData");
186+
}
187+
188+
offs_CTFPlayerShared_flBurnDuration = GameConfGetAddressOffset(hGameConf,
189+
"CTFPlayerShared::m_flBurnDuration");
190+
191+
sizeof_TFCondInfo = GameConfGetOffset(hGameConf, "sizeof(TFCondInfo_t)");
192+
193+
offs_TFCondInfo_flDuration = GameConfGetAddressOffset(hGameConf,
194+
"TFCondInfo_t::m_flDuration");
195+
196+
offs_TFCondInfo_hProvider = GameConfGetAddressOffset(hGameConf,
197+
"TFCondInfo_t::m_hProvider");
198+
199+
Address pNumConds = GameConfGetAddress(hGameConf, "&TF_COND_LAST");
200+
if (!pNumConds) {
201+
LogError("Could not determine location to read TF_COND_LAST from. "
202+
... "Condition bounds checking will produce false positives and condition "
203+
... "count native will report incorrect values.");
204+
g_nConditions = 0xFF;
205+
} else if (!(g_nConditions = LoadFromAddress(pNumConds, NumberType_Int32))
206+
|| g_nConditions != g_nConditions & 0xFF) {
207+
// we expect the value to be within [1, 255]; if it isn't, then our address is invalid
208+
LogError("TF_COND_LAST is not within expected bounds (found %08x). "
209+
... "Condition bounds checking will produce false positives and condition "
210+
... "count native will report incorrect values.", g_nConditions);
211+
g_nConditions = 0xFF;
212+
}
213+
164214
delete hGameConf;
165215
}
166216

@@ -374,6 +424,99 @@ int Native_GetPlayerLoadoutEntity(Handle plugin, int numParams) {
374424
return SDKCall(g_SDKCallPlayerGetEntityForLoadoutSlot, client, loadoutSlot, check_wearable);
375425
}
376426

427+
// int();
428+
int Native_GetPlayerConditionCount(Handle plugin, int numParams) {
429+
return g_nConditions;
430+
}
431+
432+
// float(int client, TFCond cond);
433+
any Native_GetPlayerConditionDuration(Handle plugin, int numParams) {
434+
int client = GetNativeCell(1);
435+
TFCond cond = GetNativeCell(2);
436+
437+
if (!IsConditionValid(cond)) {
438+
return ThrowNativeError(SP_ERROR_NATIVE, "Condition index %d is invalid", cond);
439+
} else if (!TF2_IsPlayerInCondition(client, cond)) {
440+
return 0.0;
441+
}
442+
443+
Address pData = GetConditionData(client, cond);
444+
return LoadFromAddress(pData + offs_TFCondInfo_flDuration, NumberType_Int32);
445+
}
446+
447+
// void(int client, TFCond cond, float duration);
448+
any Native_SetPlayerConditionDuration(Handle plugin, int numParams) {
449+
int client = GetNativeCell(1);
450+
TFCond cond = GetNativeCell(2);
451+
float duration = GetNativeCell(3);
452+
453+
if (!IsConditionValid(cond)) {
454+
ThrowNativeError(SP_ERROR_NATIVE, "Condition index %d is invalid", cond);
455+
} else if (!TF2_IsPlayerInCondition(client, cond)) {
456+
ThrowNativeError(SP_ERROR_NATIVE, "Player is not in condition %d", cond);
457+
}
458+
459+
Address pData = GetConditionData(client, cond);
460+
StoreToAddress(pData + offs_TFCondInfo_flDuration, view_as<any>(duration),
461+
NumberType_Int32);
462+
}
463+
464+
// int(int client, TFCond cond);
465+
any Native_GetPlayerConditionProvider(Handle plugin, int numParams) {
466+
int client = GetNativeCell(1);
467+
TFCond cond = GetNativeCell(2);
468+
469+
if (!IsConditionValid(cond)) {
470+
return ThrowNativeError(SP_ERROR_NATIVE, "Condition index %d is invalid", cond);
471+
} else if (!TF2_IsPlayerInCondition(client, cond)) {
472+
return INVALID_ENT_REFERENCE;
473+
}
474+
475+
Address pData = GetConditionData(client, cond);
476+
return LoadEntityHandleFromAddress(pData + offs_TFCondInfo_hProvider);
477+
}
478+
479+
// void(int client, TFCond cond, int provider);
480+
any Native_SetPlayerConditionProvider(Handle plugin, int numParams) {
481+
int client = GetNativeCell(1);
482+
TFCond cond = GetNativeCell(2);
483+
int provider = GetNativeCell(3);
484+
485+
if (!IsConditionValid(cond)) {
486+
ThrowNativeError(SP_ERROR_NATIVE, "Condition index %d is invalid", cond);
487+
} else if (!TF2_IsPlayerInCondition(client, cond)) {
488+
ThrowNativeError(SP_ERROR_NATIVE, "Player is not in condition %d", cond);
489+
} else if (!IsValidEntity(provider)) {
490+
ThrowNativeError(SP_ERROR_NATIVE, "Entity %d is invalid", provider);
491+
}
492+
493+
Address pData = GetConditionData(client, cond);
494+
StoreEntityHandleToAddress(pData + offs_TFCondInfo_hProvider, provider);
495+
}
496+
497+
// float(int client);
498+
any Native_GetPlayerBurnDuration(Handle plugin, int numParams) {
499+
int client = GetNativeCell(1);
500+
if (!TF2_IsPlayerInCondition(client, TFCond_OnFire)) {
501+
return 0.0;
502+
}
503+
int pOffsSharedBurnDuration = FindSendPropInfo("CTFPlayer", "m_Shared")
504+
+ view_as<int>(offs_CTFPlayerShared_flBurnDuration);
505+
return GetEntDataFloat(client, pOffsSharedBurnDuration);
506+
}
507+
508+
// void(int client, float duration);
509+
any Native_SetPlayerBurnDuration(Handle plugin, int numParams) {
510+
int client = GetNativeCell(1);
511+
float duration = GetNativeCell(2);
512+
if (!TF2_IsPlayerInCondition(client, TFCond_OnFire)) {
513+
return;
514+
}
515+
int pOffsSharedBurnDuration = FindSendPropInfo("CTFPlayer", "m_Shared")
516+
+ view_as<int>(offs_CTFPlayerShared_flBurnDuration);
517+
SetEntDataFloat(client, pOffsSharedBurnDuration, duration);
518+
}
519+
377520
bool IsEntityWeapon(int entity) {
378521
if (!IsValidEntity(entity)) {
379522
ThrowNativeError(SP_ERROR_NATIVE, "Entity %d (%d) is invalid", entity,
@@ -390,6 +533,17 @@ bool IsEntityWearable(int entity) {
390533
return SDKCall(g_SDKCallIsEntityWearable, entity);
391534
}
392535

536+
static Address GetConditionData(int client, TFCond cond) {
537+
Address pCondMemory = DereferencePointer(GetEntityAddress(client)
538+
+ view_as<Address>(FindSendPropInfo("CTFPlayer", "m_Shared"))
539+
+ offs_CTFPlayerShared_ConditionData);
540+
return pCondMemory + view_as<Address>(view_as<int>(cond) * sizeof_TFCondInfo);
541+
}
542+
543+
static bool IsConditionValid(TFCond cond) {
544+
return 0 <= view_as<any>(cond) < g_nConditions;
545+
}
546+
393547
static Address GameConfGetAddressOffset(Handle gamedata, const char[] key) {
394548
Address offs = view_as<Address>(GameConfGetOffset(gamedata, key));
395549
if (offs == view_as<Address>(-1)) {

0 commit comments

Comments
 (0)