Get the Flash Player to see this player.

Home EVOL's Dev Blog
HG SourceMod Development
Distinguising The First "Lunge/Pounce" Jump From Subsequent Wall Jumps PDF Print E-mail
Written by evol   
Wednesday, 18 March 2009 03:53

Ok, so I am currently working on the next mini-game mode for HG.  It's called "Walljump / Hot Lava".  The short description is all 8 hunters have to get to and pounce the survivors (who are all clustered together in some far corner of the map), all while touching the ground the fewest number of times.  Come on, you all played HOT LAVA as a kid right?

So according to my requirements, I have to be able to distinguish a hunter's FIRST lunge off the ground from subsequent wall jumps.  You would think that these events are different.  Well.....they aren't.

To get an idea of what's firing and when, I used these hooks in OnPluginStart():

    HookEvent("pounce_stopped", Event_PounceStopped);
    HookEvent("player_shoved", Event_PlayerShoved);
    HookEvent("lunge_shove", Event_LungeShove);
    HookEvent("lunge_pounce", Event_LungePounce);
    HookEvent("ability_use", Event_AbilityUse);
    HookEvent("weapon_fire", Event_WeaponFire);
    HookEvent("player_blocked", Event_PlayerBlocked);
    HookEvent("player_jump_apex", Event_PlayerJumpApex);
    HookEvent("player_falldamage", Event_PlayerFallDamage);
    HookEvent("player_jump", Event_PlayerJump);
    HookEvent("player_footstep", Event_PlayerFootstep);
    HookEvent("player_shoved", Event_PlayerShoved);
    HookEvent("entity_shoved", Event_EntityShoved);
    HookEvent("player_shoot", Event_PlayerShoot);

Not all of those fire when you do a jump, but I added anything that I thought MIGHT, so I could see exactly what was going on, and how I might be able to distinguish the first lunge from subsequent walljumps.

Each of the event handler methods simply prints to the server console the name of the event, and any data that comes with the event.  When I do a normal "lunge" off the ground (hold alt till I'm charged up, look up, and hit primary fire), I see this in the server console, as a result from my PrintToServer() calls in my individual event handlers for each of the events:

pounce_end. client=5, victim=0, client=^x | EVOL, victim=Local L4D Server: Hunter Games
ability_use event fired for ^x | EVOL, ability=ability_lunge, context=1
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
player_jump. client=5, client=^x | EVOL
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
player_jump_apex. client=5, client=^x | EVOL

weapon_fire usually doesn't fire more than 3 times.  Sometimes it's only twice.  It fires additional times if you hit primary fire or secondary fire as well, but the event parameters are the same as above in all those cases.

Also, player_jump_apex won't fire in the order above if you do a wall jump before you hit your apex.

Ok, so that's from a NORMAL, FIRST lunge into the air (starting from the ground).  Let's see what I get when I do a lunge from the ground, and then do a WALL JUMP right after:

 

pounce_end. client=5, victim=0, client=^x | EVOL, victim=Local L4D Server: Hunter Games
ability_use event fired for ^x | EVOL, ability=ability_lunge, context=1
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
player_jump. client=5, client=^x | EVOL
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0

pounce_end. client=5, victim=0, client=^x | EVOL, victim=Local L4D Server: Hunter Games
ability_use event fired for ^x | EVOL, ability=ability_lunge, context=1
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
player_jump. client=5, client=^x | EVOL
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
weapon_fire event fired for ^x | EVOL, weapon=hunter_claw, weaponid=18, count=0
player_jump_apex. client=5, client=^x | EVOL

 

Do you see any difference in the values or order of events from the first set (the FIRST lunge off the GROUND) and the SECOND (the subsequent wall jump).

NO!  Because there isn't any.  Aside from the additional calls to weapon_fire, which happened because you have to hit primary attack to do the walljump.

REALLY? ability_use context is the same?  count is the same??? Crap...

 

So I thought I wouldn't be able to determine when I jump off the ground versus when I jump off a wall.  I pulled out my handy "bintext" utility, that strips readable strings out of binary files, and dropped  l4d\left4dead\bin\server.dll in there, and started searching for:

player_

footstep

crouch

duck

pounce

lunge

And in the results, I noticed a reference somewhere to m_fFlags.  Hey wait!  I've used that before!  How can you tell if an entity is on fire??  You can use my handy helper fuction _isOnFire():

bool:_isOnFire(client)
{
    new bool:isOnFire = (GetEntProp(client, Prop_Data, "m_fFlags") & FL_ONFIRE) == FL_ONFIRE;
    return isOnFire;
}

So what is FL_ONFIRE?  And where is it defined?  Here's a clever excerpt from entity_prop_stocks:

 

// CBaseEntity::m_fFlags
// PLAYER SPECIFIC FLAGS FIRST BECAUSE WE USE ONLY A FEW BITS OF NETWORK PRECISION
#define FL_ONGROUND (1 << 0) // At rest / on the ground
#define FL_DUCKING (1 << 1) // Player flag -- Player is fully crouched
#define FL_WATERJUMP (1 << 2) // player jumping out of water
#define FL_ONTRAIN (1 << 3) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction.
#define FL_INRAIN (1 << 4) // Indicates the entity is standing in rain
#define FL_FROZEN (1 << 5) // Player is frozen for 3rd person camera
#define FL_ATCONTROLS (1 << 6) // Player can't move, but keeps key inputs for controlling another entity
#define FL_CLIENT (1 << 7) // Is a player
#define FL_FAKECLIENT (1 << 8) // Fake client, simulated server side; don't send network messages to them
// NOTE if you move things up, make sure to change this value
#define PLAYER_FLAG_BITS 9
// NON-PLAYER SPECIFIC (i.e., not used by GameMovement or the client .dll ) -- Can still be applied to players, though
#define FL_INWATER (1 << 9) // In water
#define FL_FLY (1 << 10) // Changes the SV_Movestep() behavior to not need to be on ground
#define FL_SWIM (1 << 11) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water)
#define FL_CONVEYOR (1 << 12)
#define FL_NPC (1 << 13)
#define FL_GODMODE (1 << 14)
#define FL_NOTARGET (1 << 15)
#define FL_AIMTARGET (1 << 16) // set if the crosshair needs to aim onto the entity
#define FL_PARTIALGROUND (1 << 17) // not all corners are valid
#define FL_STATICPROP (1 << 18) // Eetsa static prop!
#define FL_GRAPHED (1 << 19) // worldgraph has this ent listed as something that blocks a connection
#define FL_GRENADE (1 << 20)
#define FL_STEPMOVEMENT (1 << 21) // Changes the SV_Movestep() behavior to not do any processing
#define FL_DONTTOUCH (1 << 22) // Doesn't generate touch functions, generates Untouch() for anything it was touching when this flag was set
#define FL_BASEVELOCITY (1 << 23) // Base velocity has been applied this frame (used to convert base velocity into momentum)
#define FL_WORLDBRUSH (1 << 24) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something)
#define FL_OBJECT (1 << 25) // Terrible name. This is an object that NPCs should see. Missiles, for example.
#define FL_KILLME (1 << 26) // This entity is marked for death -- will be freed by game DLL
#define FL_ONFIRE (1 << 27) // You know...
#define FL_DISSOLVING (1 << 28) // We're dissolving!
#define FL_TRANSRAGDOLL (1 << 29) // In the process of turning into a client side ragdoll.
#define FL_UNBLOCKABLE_BY_PLAYER (1 << 30) // pusher that can't be blocked by the player
// END m_fFlags #defines

 

 So FL_ONFIRE is defined as(1 << 27), or the 28th bit of a bitfield.

OH!  HEY!  What is (1 << 0) in that same document? FL_ONGROUND!  Hmmm....

So I made this:

bool:_isOnGround(client)
{
    new bool:isOnGround = (GetEntProp(client, Prop_Data, "m_fFlags") & FL_ONGROUND) == FL_ONGROUND;
    return isOnGround;
}

 

Now that isn't the EXACT way I discovered FL_ONGROUND.  In my event handler for pounce_end (which is strangely the FIRST event that fires when a hunter crouches, charges, and then presses primary attack to "lunge"), I started by retrieving the value of m_fFlags for the player, and printing it to the screen.  On my initial lunge, I was getting 134299779, and on walljumps I was getting 134299778.

 

134299779 (first)

134299778 (walljumps)

Off by 1.  Let's see them in binary.

1000000000010100000010000011

1000000000010100000010000010

So the m_fFlags for the player are all the same EXCEPT FOR THE FIRST BIT (we start from the right in binary, of course, so the right-most digit is the "first" bit).  This is when I went to look up the FL_ values, to see what the first bit is, and it was FL_ONGROUND.  Yay!

 

Alright, so that settled it for me!  On to coding the WALL JUMPER / HOT LAVA mini-game for HUNTER GAMES!

 

-EVOL

Last Updated on Wednesday, 18 March 2009 05:00