|
Senior Member
Join Date: Jul 2010
Location: Sydney, Australia
|

11-16-2010
, 02:50
[TF2] Pyro Extinguish Emulation
|
#1
|
As a part of a project, I recently had the misfortune to discover the deflection and extinguish are within the same DefectPlayer call. So here's the code that emulates emulation ... (all of it)
PHP Code:
/* Declare these globally ... remember to enable and init them */ void *pCTFGameStats = NULL; /* CTFGameStats pointer */ CDetour *CTFGameStats_EventLevelInit_Detour; /* GameStats Init Detour */
/* Flag, Condition Int, and Extinguish usermessage Int */ #define TF_CONDFLAG_ONFIRE (1 << 22) #define TF_COND_ONFIRE 22 #define TFUMSG_EXTINGUISHED 37
/* CHackyRecipientFilter * Code here is modified from Valve's Code: see irecipientfilter.h, recipintfilter.h, and recipientfilter.cpp. */
class CHackyRecipientFilter : public IRecipientFilter { public: CHackyRecipientFilter(); virtual ~CHackyRecipientFilter(); virtual bool IsReliable( void ) const; virtual bool IsInitMessage( void ) const; virtual int GetRecipientCount( void ) const; virtual int GetRecipientIndex( int slot ) const;
public: void MakeInitMessage( void ); void MakeReliable( void ); void AddRecipientByPlayerIndex( int playerindex ); void RemoveRecipientByPlayerIndex( int playerindex ); private: bool m_bReliable; bool m_bInitMessage; CUtlVector<int> m_Recipients; };
CHackyRecipientFilter::CHackyRecipientFilter() { m_bReliable = false; m_bInitMessage = false; m_Recipients.RemoveAll(); }
CHackyRecipientFilter::~CHackyRecipientFilter() { }
void CHackyRecipientFilter::MakeReliable( void ) { m_bReliable = true; }
bool CHackyRecipientFilter::IsReliable( void ) const { return m_bReliable; }
int CHackyRecipientFilter::GetRecipientCount( void ) const { return m_Recipients.Size(); }
int CHackyRecipientFilter::GetRecipientIndex( int slot ) const { if ( slot < 0 || slot >= GetRecipientCount() ) return -1;
return m_Recipients[ slot ]; }
void CHackyRecipientFilter::AddRecipientByPlayerIndex( int playerindex ) { if ( m_Recipients.Find( playerindex ) != m_Recipients.InvalidIndex() ) return; m_Recipients.AddToTail( playerindex ); }
void CHackyRecipientFilter::RemoveRecipientByPlayerIndex( int playerindex ) { m_Recipients.FindAndRemove( playerindex ); }
bool CHackyRecipientFilter::IsInitMessage( void ) const { return m_bInitMessage; }
void CHackyRecipientFilter::MakeInitMessage( void ) { m_bInitMessage = true; }
/* End of CHackyRecipientFilter */
/* Helper function */ int GetPlayerTeam(CBaseEntity *player) { datamap_t *data = gamehelpers->GetDataMap(player); if (!data) { META_CONPRINTF("[WTFF] Invalid DataMap"); return 0; }
typedescription_t *type = gamehelpers->FindInDataMap(data, "m_iTeamNum"); if (!type) { META_CONPRINTF("[WTFF] Invalid Type"); return 0; } int iTeam = *(int *)((char *)player + type->fieldOffset[TD_OFFSET_NORMAL]); if(!iTeam) { META_CONPRINTF("[WTFF] Invalid Team"); return 0; } return iTeam; }
/* Detour CTFGameStats::Init() This detour gets 'this' (i.e. CTFGameStats) and stores it locally for later ab/use. */ DETOUR_DECL_MEMBER0(CTFGameStats_EventLevelInit, int) { pCTFGameStats = reinterpret_cast<void *>(this); META_CONPRINTF("[WTFF] CTFGameStat Pointer: 0x%.8X\n\n", pCTFGameStats); return DETOUR_MEMBER_CALL(CTFGameStats_EventLevelInit)(); }
/* Detour CTFFlameThrower::DeflectPlayer(CTFPlayer *, CTFPlayer *, Vector &, Vector &, Vector &) The aim here is work out what (if at all) is called. This influences extinguish and blowback (both airblast functions), when called against a player. */ DETOUR_DECL_MEMBER5(CTFFlamethrower_Deflectplayer, int, CBaseEntity*, participant, CBaseEntity*, actor, Vector &, vec1, Vector &, vec2, Vector &, vec3) { if(!uracvar.GetBool()) { /* Disabled, so return the default */ DETOUR_MEMBER_CALL(CTFFlamethrower_Deflectplayer)(participant, actor, vec1, vec2, vec3); } else { Pyro_ExtinguishPlayer(participant, actor, vec1, vec2, vec3, "tf_weapon_flamethrower"); /* Deflect Codens? */ } return 0; // Valve returns 0 on everything bar 1 case (deflect, iirc.), but meh. }
/* Emulate Pyro Extinguish Code */ void Pyro_ExtinguishPlayer(CBaseEntity *beneficiary, CBaseEntity *actor, Vector unk1, Vector unk2, Vector unk3, const char *wpn_logname) { /* Valve has it so that this _always_ returns false... so unless you're breaking the CTFFlamethrower::DeflectPlayer function with a detour, don't bother catching the result. (By catching I mean faking that this did something significant.) */
if(!actor) { META_CONPRINTF("[WTFF] Invalid Actor.\n"); return; } if(!beneficiary) { META_CONPRINTF("[WTFF] Invalid Beneficiary.\n"); return; }
/* Get Conds... */ sm_sendprop_info_t info; if(gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCond", &info)); int playercond = *(int *)((unsigned char *)beneficiary + info.actual_offset);
/* Is the beneficiary burning? If not, bailopan. */ if(!(playercond & (TF_CONDFLAG_ONFIRE))) { return; }
/* Beneficiary: edict, index, IGamePlayer, and vector/pos */ edict_t *beneficiary_edict = gameents->BaseEntityToEdict(beneficiary); int beneficiary_index = engine->IndexOfEdict(beneficiary_edict); IGamePlayer *beneficiary_player = playerhelpers->GetGamePlayer(beneficiary_index); const Vector &beneficiary_pos = beneficiary_edict->GetCollideable()->GetCollisionOrigin();
/* Actor: edict, index, IGamePlayer, and vector/pos */ edict_t *actor_edict = gameents->BaseEntityToEdict(actor); int actor_index = engine->IndexOfEdict(actor_edict); IGamePlayer *actor_player = playerhelpers->GetGamePlayer(actor_index); const Vector &actor_pos = actor_edict->GetCollideable()->GetCollisionOrigin();
/* SourceMod RemoveCond signature was used here, but it's now duplicated into my gamedata because of their warning ... */ char *addr; if(!g_pGameConf->GetMemSig("RemoveCondition", (void **)&addr) || !addr) { META_CONPRINTF("[WTFF] Could not find signature for 'RemoveCond'.\n"); return; } if(!addr) { META_CONPRINTF("[WTFF] Address is void.\n"); return; }
/* Call RemoveCond on the player */ union { int (VEmptyClass::*RemoveCond)(int cond, bool unk); #ifndef PLATFORM_POSIX void *addr; } u; u.addr = addr; #else struct { void *addr; intptr_t adjustor; } s; } u; u.s.addr = addr; u.s.adjustor = 0; #endif
/* Valve passes (22, 0) to this ... it's just "extinguish" */ void *obj = (void *)((uint8_t *)beneficiary + playerSharedOffset->actual_offset); (reinterpret_cast<VEmptyClass *>(obj)->*u.RemoveCond)(TF_COND_ONFIRE, false);
/*-- Extinguish Sound Code */ cell_t player_list[256], total_players = 0; // Player list info int maxClients = playerhelpers->GetMaxClients(); // Get everyone IGamePlayer *other; // Temp IGamePlayer for (int i=1; i<=maxClients; i++) // Get everyone who is in game { other = playerhelpers->GetGamePlayer(i); if (other->IsInGame()) { player_list[total_players++] = i; } }
/* Recipient Filter */ CellRecipientFilter rf; rf.SetToReliable(true); rf.Initialize(player_list, total_players);
/* Emit Sound */ engsound->EmitSound(rf, beneficiary_index, CHAN_STATIC, SND_TFPLAYER_FLAMEOUT, VOL_NORM, ATTN_NORM, 0, PITCH_NORM, &beneficiary_pos); /*-- End Extinguish Sound Code */
/* User Message "PlayerExtinguished" */ CHackyRecipientFilter filter; if(actor_player->IsInGame()) { filter.AddRecipientByPlayerIndex(actor_index); } if(beneficiary_player->IsInGame()) { filter.AddRecipientByPlayerIndex(beneficiary_index); } bf_write *extinguish = engine->UserMessageBegin(&filter, TFUMSG_EXTINGUISHED); extinguish->WriteByte(beneficiary_index); extinguish->WriteByte(actor_index); engine->MessageEnd();
/* Increment Stat Code - the memory patch is Wazz's, the pointer is from the detour above */ if (pCTFGameStats) { int iPlayerStatsOffset = 220 * playerindex; *(uint16 *)((uint8_t *)pCTFGameStats + 4 * iPlayerStatsOffset + 148) += 10; *(uint16 *)((uint8_t *)pCTFGameStats + 4 * iPlayerStatsOffset + 272) += 10; *(uint16 *)((uint8_t *)pCTFGameStats + 4 * iPlayerStatsOffset + 396) += 10; } /* End Increment Code */
/* Finally, log some giant freaking thing */ META_LOG(g_PLAPI,"\"%s<%i><%s><%i>\" triggered \"player_extinguished\" against \"%s<%i><%s><%i>\" with \"%s\" (attacker_position \"%d %d %d\") (beneficiary_position \"%d %d %d\")\n", actor_player->GetName(), actor_player->GetUserId(), actor_player->GetAuthString(), GetPlayerTeam(actor), beneficiary_player->GetName(), beneficiary_player->GetUserId(), beneficiary_player->GetAuthString(), GetPlayerTeam(beneficiary), wpn_logname, actor_pos[0], actor_pos[1], actor_pos[2], beneficiary_pos[0], beneficiary_pos[1], beneficiary_pos[2]);
}
Thanks to: - asherkin (as always) for help with C++/debugging -- because I'm an idiot and I have so many damn references to players that I got lost in my own code (don't ask);
- Wazz for the original increment stat code/memory patching method (I finally understand it, and have a bit more on it);
- Drunken_F00l for pointing out gameents (not as game ents) and pointing out the issues with the recipient filter weren't (entirely) me.
If I've forgotten anything, my apologies ... there's a lot of code around this, but this is the bulk of it.
(Sidenote: This will function as a 100% faithful replication of a pyro's extinguish, if you call it. So it can/will break stats if you abuse it... I'm releasing it in the name of science/community/GPL/cheese, so be good.)
Gamedata!
PHP Code:
"Games" { "tf" { "Signatures" { "CTFFlameThrower_DeflectPlayer" { "library" "server" "windows" "\x83\xEC\x44\x53\x55\x56\x8B\x74\x24\x54\x8B\xE9\x57\x8B\xCE\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x8B\xCF\x8B\xD8\x2A\x2A\x2A\x2A\x2A\x3B\xD8\x2A\x2A\x2A\x2A\x2A\x2A\x8D\x9E\x38\x15\x00\x00\x6A\x16\x8B\xCB\x2A\x2A\x2A\x2A\x2A\x84\xC0\x2A\x2A\x2A\x2A\x2A\x2A\xD9\xEE\x6A\x00\x51" "linux" "@_ZN15CTFFlameThrower13DeflectPlayerEP9CTFPlayerS1_R6VectorS3_S3_" } "CTFGameStats_EventLevelInit" { "library" "server" "windows" "\x53\x56\x8B\xD9\x8B\x73\x74\x85\xF6\x57\x2A\x2A\x8B\xCE\x2A\x2A\x2A\x2A\x2A\x56\x2A\x2A\x2A\x2A\x2A\x83\xC4\x2A" "linux" "@_ZN12CTFGameStats15Event_LevelInitEv" } "RemoveCondition" { "library" "server" "windows" "\x8B\x44\x2A\x2A\x56\x57\x8B\x7C\x2A\x2A\x8B\xF1\x50\x57\x8D\x8E\x2A\x2A\x2A\x2A\xE8" "linux" "@_ZN15CTFPlayerShared10RemoveCondEib" "mac" "@_ZN15CTFPlayerShared10RemoveCondEib" } } } }
Last edited by Swixel; 11-16-2010 at 03:07.
Reason: Removing two comments from the increment code that have no meaning in this snippet.
|
|