Raised This Month: $ Target: $400
 0% 

[TF2] Pyro Extinguish Emulation


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
Swixel
Senior Member
Join Date: Jul 2010
Location: Sydney, Australia
Old 11-16-2010 , 02:50   [TF2] Pyro Extinguish Emulation
Reply With Quote #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    IsReliablevoid ) const;
    
virtual bool    IsInitMessagevoid ) const;
    
virtual int        GetRecipientCountvoid ) const;
    
virtual int        GetRecipientIndexint slot ) const;

public:
    
void            MakeInitMessagevoid );
    
void            MakeReliablevoid );
    
void            AddRecipientByPlayerIndexint playerindex );
    
void            RemoveRecipientByPlayerIndexint 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::MakeReliablevoid )
{
    
m_bReliable true;
}

bool CHackyRecipientFilter::IsReliablevoid ) const
{
    return 
m_bReliable;
}

int CHackyRecipientFilter::GetRecipientCountvoid ) const
{
    return 
m_Recipients.Size();
}

int    CHackyRecipientFilter::GetRecipientIndexint slot ) const
{
    if ( 
slot || slot >= GetRecipientCount() )
        return -
1;

    return 
m_Recipientsslot ];
}

void CHackyRecipientFilter::AddRecipientByPlayerIndexint playerindex )
{
    if ( 
m_Recipients.Findplayerindex ) != m_Recipients.InvalidIndex() ) return;
    
m_Recipients.AddToTailplayerindex );
}

void CHackyRecipientFilter::RemoveRecipientByPlayerIndexint playerindex )
{
    
m_Recipients.FindAndRemoveplayerindex );
}

bool CHackyRecipientFilter::IsInitMessagevoid ) const
{
    return 
m_bInitMessage;
}

void CHackyRecipientFilter::MakeInitMessagevoid )
{
    
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_EventLevelInitint)
{
    
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_DeflectplayerintCBaseEntity*, participantCBaseEntity*, actorVector &, vec1Vector &, vec2Vector &, vec3)
{
    if(!
uracvar.GetBool())
    {
        
/* Disabled, so return the default */
        
DETOUR_MEMBER_CALL(CTFFlamethrower_Deflectplayer)(participantactorvec1vec2vec3);
    }
    else
    {
        
Pyro_ExtinguishPlayer(participantactorvec1vec2vec3"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 *beneficiaryCBaseEntity *actorVector unk1Vector unk2Vector 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 condbool unk);
#ifndef PLATFORM_POSIX
        
void *addr; } uu.addr addr;
#else
    
struct void *addrintptr_t adjustor; } s; } uu.s.addr addru.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_ONFIREfalse);

/*-- 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=1i<=maxClientsi++)                                                        // 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_listtotal_players);

    
/* Emit Sound */
    
engsound->EmitSound(rfbeneficiary_indexCHAN_STATICSND_TFPLAYER_FLAMEOUTVOL_NORMATTN_NORM0PITCH_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(&filterTFUMSG_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 iPlayerStatsOffset 148) += 10;
        *(
uint16 *)((uint8_t *)pCTFGameStats iPlayerStatsOffset 272) += 10;
        *(
uint16 *)((uint8_t *)pCTFGameStats 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.
Swixel is offline
Seta00
The Seta00 user has crashed.
Join Date: Jan 2010
Location: Berlin
Old 11-16-2010 , 10:12   Re: [TF2] Pyro Extinguish Emulation
Reply With Quote #2

Quote:
Originally Posted by Swixel View Post
PHP Code:
    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; } 
I'm posting whitespace to compensate the lack of it:

































Nice snippet by the way.
Seta00 is offline
Swixel
Senior Member
Join Date: Jul 2010
Location: Sydney, Australia
Old 11-16-2010 , 17:49   Re: [TF2] Pyro Extinguish Emulation
Reply With Quote #3

People actually use whitespace? Weird. I thought it was just something Python and Ruby did to annoy people. >_>
Swixel is offline
Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 18:18.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode