Tsuey / L4D2-Community-Update

Help us shape the potential future of L4D2 vanilla.
70 stars 23 forks source link

Some bugs are related to the tank. #21

Open A1mDev opened 3 years ago

A1mDev commented 3 years ago
  1. Frozen tank in T or A pose. How to reproduce it: You need to play as a tank and wait until your life drops to 0 and the tank death animation starts, then you just need to change the team or exit the game, the dead tank will be transferred to another player and so it will be transferred between the players, it cannot be killed (because his hp is 0 and the damage does not pass) and you cannot move (because the entity flag is set FL_FROZEN). A plugin that was written by the community to fix this: frozen_tank_fix.smx. There is also a picture on the forum showing the problem. This problem is still relevant and works on any server, be it official or not.

  2. Flying rock through the player. How to reproduce it: You need to be with a black and white screen, and with hp less than a stone can do damage (cvar responsible for stone damage 'vs_tank_damage', the default is 24. You need to have hp less than 24). Then, when the stone touches you, it does not collapse, but continues to fly, it flies through you and kill. Plugin l4d2_bw_rock_hit.smx written by the community to fix these issue.

  3. Surviving players can pick up incapacitated teammates after hitting a tank. It doesn't matter at all whether you hit the player or not, he can also pick up teammates if he does not release the 'use' key, although this should not happen, and it looks strange.

  4. Problem when passing the tank to the player. if we play the charger with the character and start using our ability and at that moment the tank will be passed to us, then this bug occurs. When replacing a player with a bot, the FL_FROZEN flag must be set for the bot, for this case.

  5. Problem with tank rock, for some reason it get stuck in common, the rock deals damage to them, but cannot move further and falls next to the common to the ground, the same thing happens with some classes of special infected and the witch, the rock deals damage to them and immediately falls to the ground. Second problem with tank rock with an incapacitated player, we cannot damage it with a rock, but a rock cannot pass through an incapacitated player, the rock simply breaks against an invisible obstacle above the player. I recorded a demo to show the problems.

lunatixxx commented 3 years ago

Also rare but on some occasions if a player about to become the tank leave it could glitch and transpose inside another special infected and this player will not be able to move (cf hunter become the tank but is still a hunter), or in the second case simply die and disappear without having the opportunity to play it. I'm not entirely sure this one is not related to: https://github.com/SirPlease/L4D2-Competitive-Rework/blob/master/addons/sourcemod/scripting/l4d_tank_control_eq.sp

A1mDev commented 1 year ago

It would be nice if the developers fixed problem №1, because it breaks games on official servers so far.

A1mDev commented 1 year ago

Some description of the problem:

Frozen tank in T or A pose. How to reproduce it: You need to play as a tank and wait until your life drops to 0 and the tank death animation starts, then you just need to change the team or exit the game, the dead tank will be transferred to another player and so it will be transferred between the players, it cannot be killed (because his hp is 0 and the damage does not pass) and you cannot move (because the entity flag is set FL_FROZEN). A plugin that was written by the community to fix this: frozen_tank_fix.smx. There is also a picture on the forum showing the problem. This problem is still relevant and works on any server, be it official or not.

image

The code that needs to be added to the game to fix the problem:

Similar code is used in the game to pass animation states when replacing a player with a bot.

// enum constant ZombieClass_Tank == 8
// constant PLAYERANIMEVENT_DIE == 10
// constant LIFE_ALIVE == 0

void ZombieReplacement::Restore( CTerorPlayer *pPlayer, CTerorPlayer *pNewPlayer )
{
    ...

    + if ( pNewPlayer->IsIncapacitated() && pNewPlayer->GetClass() == ZombieClass_Tank )
    + {
    +   pNewPlayer->DoAnimationEvent( PLAYERANIMEVENT_DIE );
    + }
}

// The code existing in the game is shown for example
bool CTerrorPlayer::IsIncapacitated()
{
    if ( IsAlive() )
    {
        return m_isIncapacitated;
    }

    return false;
}

inline bool CBaseEntity::IsAlive( void ) const
{
    return m_lifeState == LIFE_ALIVE;
}

ZombieClassType GetClass()
{
    return m_zombieClass;
}

Detailed description of the problem:

After the tank runs out of HP, the game sets the CTerrorPlayerAnimState::m_bIsDead bool to true. Every frame the CTerrorPlayerAnimState::CalcMainActivity function is called, it handles the animation state. And if state CTerrorPlayerAnimState::m_bIsDead is set, then function CTerrorPlayerAnimState::HandleActivity_Death is always called, and the CTerrorPlayerAnimState::HandleActivity_Incapacitated function is never called for the tank, because condition is not met. But when a tank is replaced by a bot, this state will not be passed to the bot, so function CTerrorPlayerAnimState::HandleActivity_Incapacitated is called (this functionis exclusively for survivors). We also can't kill the tank quickly in this state, its HP is set to z_tank_incapacitated_health (5000). After some time, the tank dies by itself due to function CTerrorPlayer::UpdateZombieIncapacitation(), the code is shown below.

Activity CTerrorPlayerAnimState::CalcMainActivity()
{
    Activity iRet = 706;

    if ( m_pTerrorPlayer->m_usingMountedWeapon )
    {
        return iRet;
    }

    UpdateChainsaw();

    iRet = HandleActivity_Death();
    if ( iRet != -1 )
    {
        return iRet;
    }
    ...

    iRet = HandleActivity_Incapacitated();
    if ( iRet != -1 )
    {
        return iRet;
    }
    ...
}

Some sequence of function calls: CTerrorPlayer::PostThink()->CTerrorPlayer::UpdateZombieIncapacitation()

void CTerrorPlayer::UpdateZombieIncapacitation()
{
    // ACT_TERROR_DIE_WHILE_RUNNING 669
    // ACT_TERROR_DIE_FROM_STAND 667

    float fIncapDamage = z_tank_incapacitated_decay_rate.GetFloat();
    bool bDieActivity = (m_PlayerAnimState->GetMainActivity() == ACT_TERROR_DIE_WHILE_RUNNING
                            || m_PlayerAnimState->GetMainActivity() == ACT_TERROR_DIE_FROM_STAND);

    if ( m_bSequenceFinished && bDieActivity )
    {
        fIncapDamage = z_tank_incapacitated_health.GetFloat();
    }

    if ( l4d_show_incapupdate.GetBool() )
    {
        Msg( "%s: delta = %f\n", GetCharacterDisplayName(), fIncapDamage );
    }

    CTerrorPlayer *pIncapCause = m_incapacitatedCause.Get();
    if ( pIncapCause )
    {
        TakeDamage( CTakeDamageInfo( pIncapCause, pIncapCause, fIncapDamage, DMG_POISON ) );

        return;
    }

    CBaseEntity *pWorld = GetContainingEntity( INDEXENT( 0 ) );

    TakeDamage( CTakeDamageInfo( pWorld, pWorld, fIncapDamage, DMG_POISON ) );
}

This problem is still relevant, and some people intentionally use it on official servers.

A1mDev commented 1 year ago

Some description of the problem:

Problem when passing the tank to the player. if we play the charger with the character and start using our ability and at that moment the tank will be passed to us, then this bug occurs. When replacing a player with > a bot, the FL_FROZEN flag must be set for the bot, for this case.

The code that needs to be added to the game to fix the problem:

// Constants FL_FROZEN == (1<<5) // 32
// Constants ZombieClass_Charger == 6

void ZombieReplacement::Restore( CTerorPlayer *pPlayer, CTerorPlayer *pNewPlayer )
{
    ...

    + CBaseAbility *pAbility = pNewPlayer->GetCustomAbility();

    + if ( pAbility && pNewPlayer->GetClass() == ZombieClass_Charger && pNewPlayer->IsAlive() )
    + {
    +   CCharge *pChargeAbility = dynamic_cast< CCharge >( pAbility );

    +   if ( pChargeAbility && pChargeAbility->IsActive() )
    +   {
    +       pNewPlayer->AddFlag( FL_FROZEN );
    +   }
    + }
}

// The code existing in the game is shown for example
CBaseAbility *CTerrorPlayer::GetCustomAbility()
{
    return m_customAbility;
}

bool CCharge::IsActive()
{
    return m_isCharging;
}

ZombieClassType GetClass()
{
    return m_zombieClass;
}

#if !defined( CLIENT_DLL )
#define CHANGE_FLAGS(flags,newFlags) { unsigned int old = flags; flags = (newFlags); gEntList.ReportEntityFlagsChanged( this, old, flags ); }
#else
#define CHANGE_FLAGS(flags,newFlags) (flags = (newFlags))
#endif

void CBaseEntity::AddFlag( int flags )
{
    CHANGE_FLAGS( m_fFlags, m_fFlags | flags );
}

Detailed description of the problem:

The FL_FROZEN flag is not passed when replacing a player with a bot, when the player is playing as a charger and their ability is used, this flag is only set when the ability is activated, and is not set anywhere else.

Tsuey commented 1 year ago

These are extremely good write-ups, thank you and very nice work.