HIT Forums

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: [C++] Break and/or explode doors in Half-Life  (Read 2159 times)

0 Members and 1 Guest are viewing this topic.

Shepard62700FR

  • Snark
  • **
  • Offline Offline
  • Posts: 4
    • View Profile
[C++] Break and/or explode doors in Half-Life
« on: January 01, 2009, 10:17:35 AM »

This tutorial will show you how to modify "func_door" to be explodable and breakable.

Are you ready ? Let's start !

First, there will be no client side coding, everything is server (mp or hl) side.

In doors.cpp, just include explode.h and add the following :
Code: [Select]
typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matRocks, matNone, matLastMaterial } Materials;Be careful "matUnbreakableGlass" is not included because it's not useful.

Find the function CBaseDoor : public CBaseToggle, and add the following after virtual void Blocked( CBaseEntity *pOther ); :
Code: [Select]
inline int ExplosionMagnitude( void ) { return pev->impulse; }

    static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount );
    int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType );
    void DamageSound( void );
    void MaterialSoundPrecache( Materials precacheMaterial );
    void BreakTouch( CBaseEntity *pOther );
    void Die( void );

    static const char *pSoundsWood[];
    static const char *pSoundsFlesh[];
    static const char *pSoundsGlass[];
    static const char *pSoundsMetal[];
    static const char *pSoundsConcrete[];
    static const char *pSpawnObjects[];

    int m_idShard;
    Materials m_Material;
    bool m_bBreakable;
    bool m_bExplodable;

After the function CBaseDoor::Blocked, add the following :

Code: [Select]
// Table of sounds for which material
const char *CBaseDoor::pSoundsWood[] =
{
    "debris/wood1.wav",
    "debris/wood2.wav",
    "debris/wood3.wav",
};

const char *CBaseDoor::pSoundsFlesh[] =
{
    "debris/flesh1.wav",
    "debris/flesh2.wav",
    "debris/flesh3.wav",
    "debris/flesh5.wav",
    "debris/flesh6.wav",
    "debris/flesh7.wav",
};

const char *CBaseDoor::pSoundsMetal[] =
{
    "debris/metal1.wav",
    "debris/metal2.wav",
    "debris/metal3.wav",
};

const char *CBaseDoor::pSoundsConcrete[] =
{
    "debris/concrete1.wav",
    "debris/concrete2.wav",
    "debris/concrete3.wav",
};

const char *CBaseDoor::pSoundsGlass[] =
{
    "debris/glass1.wav",
    "debris/glass2.wav",
    "debris/glass3.wav",
};

// Which list of sounds we're going to need ?
const char **CBaseDoor::MaterialSoundList( Materials precacheMaterial, int &soundCount )
{
    const char **pSoundList = NULL;

    switch ( precacheMaterial )
    {
    case matWood:
        pSoundList = pSoundsWood;
        soundCount = ARRAYSIZE(pSoundsWood);
        break;

    case matFlesh:
        pSoundList = pSoundsFlesh;
        soundCount = ARRAYSIZE(pSoundsFlesh);
        break;

    case matGlass:
        pSoundList = pSoundsGlass;
        soundCount = ARRAYSIZE(pSoundsGlass);
        break;

    case matComputer:
    case matMetal:
        pSoundList = pSoundsMetal;
        soundCount = ARRAYSIZE(pSoundsMetal);
        break;

    case matCinderBlock:
    case matRocks:
        pSoundList = pSoundsConcrete;
        soundCount = ARRAYSIZE(pSoundsConcrete);
        break;
   
    case matCeilingTile:
    case matNone:
    default:
        soundCount = 0;
        break;
    }

    return pSoundList;
}

// Precache sounds
void CBaseDoor::MaterialSoundPrecache( Materials precacheMaterial )
{
    const char **pSoundList;
    int i, soundCount = 0;

    pSoundList = MaterialSoundList( precacheMaterial, soundCount );

    for ( i = 0; i < soundCount; i++ )
    {
        PRECACHE_SOUND( (char *)pSoundList[i] );
    }
}

// Function which take cover if the door take damage
void CBaseDoor::BreakTouch( CBaseEntity *pOther )
{
    float flDamage;
    entvars_t* pevToucher = pOther->pev;

    // only players can break these right now
    if ( !pOther->IsPlayer() || !m_bBreakable ) // If door isn't breakable get out of here !
    {
        return;
    }

    if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) )
    {// can be broken when run into
        flDamage = pevToucher->velocity.Length() * 0.01;

        if (flDamage >= pev->health)
        {
            SetTouch( NULL );
            TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH);

            // do a little damage to player if we broke glass or computer
            pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH );
        }
    }

    if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 )
    {// can be broken when stood upon
       
        // play creaking sound here.
        DamageSound();

        SetThink ( Die );
        SetTouch( NULL );
       
        if ( m_flDelay == 0 )
        {// !!!BUGBUG - why doesn't zero delay work?
            m_flDelay = 0.1;
        }

        pev->nextthink = pev->ltime + m_flDelay;
    }
}

// s*** !!
int CBaseDoor :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType )
{
    Vector vecTemp;

    // if Attacker == Inflictor, the attack was a melee or other instant-hit attack.
    // (that is, no actual entity projectile was involved in the attack so use the shooter's origin).
    if ( pevAttacker == pevInflictor )
    {
        vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) );
       
        // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now.
        if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) &&
                 FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB))
            flDamage = pev->health;
    }
    else
    // an actual missile was involved.
    {
        vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) );
    }
   
    // Unbreakable door ? Get the **** out of here
    if ( !m_bBreakable )
        return 0;

    // Breakables take double damage from the crowbar
    if ( bitsDamageType & DMG_CLUB )
        flDamage *= 2;

    // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10%
    if ( bitsDamageType & DMG_POISON )
        flDamage *= 0.1;

// this global is still used for glass and other non-monster killables, along with decals.
    /*g_vecAttackDir = vecTemp.Normalize();*/ // freezebit (direction de l'explosion... on ne gère pas)
       
// do the damage
    pev->health -= flDamage;
    if (pev->health <= 0)
    {
        Killed( pevAttacker, GIB_NORMAL );
        Die();
        return 0;
    }

    // Make a shard noise each time func breakable is hit.
    // Don't play shard noise if cbreakable actually died.

    DamageSound();

    return 1;
}

// Function which will play sound when the door take damage
void CBaseDoor::DamageSound( void )
{
    int pitch;
    float fvol;
    char *rgpsz[6];
    int i;
    int material = m_Material;

//    if (RANDOM_LONG(0,1))
//        return;

    if (RANDOM_LONG(0,2))
        pitch = PITCH_NORM;
    else
        pitch = 95 + RANDOM_LONG(0,34);

    fvol = RANDOM_FLOAT(0.75, 1.0);

    if (material == matComputer && RANDOM_LONG(0,1))
        material = matMetal;

    switch (material)
    {
    case matComputer:
    case matGlass:
        rgpsz[0] = "debris/glass1.wav";
        rgpsz[1] = "debris/glass2.wav";
        rgpsz[2] = "debris/glass3.wav";
        i = 3;
        break;

    case matWood:
        rgpsz[0] = "debris/wood1.wav";
        rgpsz[1] = "debris/wood2.wav";
        rgpsz[2] = "debris/wood3.wav";
        i = 3;
        break;

    case matMetal:
        rgpsz[0] = "debris/metal1.wav";
        rgpsz[1] = "debris/metal3.wav";
        rgpsz[2] = "debris/metal2.wav";
        i = 2;
        break;

    case matFlesh:
        rgpsz[0] = "debris/flesh1.wav";
        rgpsz[1] = "debris/flesh2.wav";
        rgpsz[2] = "debris/flesh3.wav";
        rgpsz[3] = "debris/flesh5.wav";
        rgpsz[4] = "debris/flesh6.wav";
        rgpsz[5] = "debris/flesh7.wav";
        i = 6;
        break;

    case matRocks:
    case matCinderBlock:
        rgpsz[0] = "debris/concrete1.wav";
        rgpsz[1] = "debris/concrete2.wav";
        rgpsz[2] = "debris/concrete3.wav";
        i = 3;
        break;

    case matCeilingTile:
        // UNDONE: no ceiling tile shard sound yet
        i = 0;
        break;
    }

    if (i)
        EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch);
}

// If the door has no more health, kill him
void CBaseDoor::Die( void )
{
    Vector vecSpot;// shard origin
    Vector vecVelocity;// shard velocity
    CBaseEntity *pEntity = NULL;
    char cFlag = 0;
    int pitch;
    float fvol;
   
    pitch = 95 + RANDOM_LONG(0,29);

    if (pitch > 97 && pitch < 103)
        pitch = 100;

    // The more negative pev->health, the louder
    // the sound should be.

    fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(pev->health) / 100.0);

    if (fvol > 1.0)
        fvol = 1.0;

    switch (m_Material)
    {
    case matGlass:
        switch ( RANDOM_LONG(0,1) )
        {
        case 0:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass1.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        case 1:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        }
        cFlag = break_GLASS;
        break;

    case matWood:
        switch ( RANDOM_LONG(0,1) )
        {
        case 0:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        case 1:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        }
        cFlag = break_WOOD;
        break;

    case matComputer:
    case matMetal:
        switch ( RANDOM_LONG(0,1) )
        {
        case 0:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        case 1:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        }
        cFlag = break_METAL;
        break;

    case matFlesh:
        switch ( RANDOM_LONG(0,1) )
        {
        case 0:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        case 1:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        }
        cFlag = break_FLESH;
        break;

    case matRocks:
    case matCinderBlock:
        switch ( RANDOM_LONG(0,1) )
        {
        case 0:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        case 1:    EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch);
            break;
        }
        cFlag = break_CONCRETE;
        break;

    case matCeilingTile:
        EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch);
        break;
    }
    // Remove this if you want to set the direction of the explosion
    /*if (m_Explosion == expDirected)
        vecVelocity = g_vecAttackDir * 200;
    else
    {*/
        vecVelocity.x = 0;
        vecVelocity.y = 0;
        vecVelocity.z = 0;
    /*}*/

    vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5;
    MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot );
        WRITE_BYTE( TE_BREAKMODEL);

        // position
        WRITE_COORD( vecSpot.x );
        WRITE_COORD( vecSpot.y );
        WRITE_COORD( vecSpot.z );

        // size
        WRITE_COORD( pev->size.x);
        WRITE_COORD( pev->size.y);
        WRITE_COORD( pev->size.z);

        // velocity
        WRITE_COORD( vecVelocity.x );
        WRITE_COORD( vecVelocity.y );
        WRITE_COORD( vecVelocity.z );

        // randomization
        WRITE_BYTE( 10 );

        // Model
        WRITE_SHORT( m_idShard ); //model id#

        // # of shards
        WRITE_BYTE( 0 ); // let client decide

        // duration
        WRITE_BYTE( 25 ); // 2.5 seconds

        // flags
        WRITE_BYTE( cFlag );
    MESSAGE_END();

    float size = pev->size.x;
    if ( size < pev->size.y )
        size = pev->size.y;
    if ( size < pev->size.z )
        size = pev->size.z;

    // !!! HACK  this should work!
    // Build a box above the entity that looks like an 8 pixel high sheet
    Vector mins = pev->absmin;
    Vector maxs = pev->absmax;
    mins.z = pev->absmax.z;
    maxs.z += 8;

    // BUGBUG -- can only find 256 entities on a breakable -- should be enough
    CBaseEntity *pList[256];
    int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND );
    if ( count )
    {
        for ( int i = 0; i < count; i++ )
        {
            ClearBits( pList[i]->pev->flags, FL_ONGROUND );
            pList[i]->pev->groundentity = NULL;
        }
    }

    // Don't fire something that could fire myself
    pev->targetname = 0;

    pev->solid = SOLID_NOT;
    // Fire targets on break
    SUB_UseTargets( NULL, USE_TOGGLE, 0 );

    SetThink( SUB_Remove );
    pev->nextthink = pev->ltime + 0.1;

    // No, a door will not drop a object
    /*if ( m_iszSpawnObject )
        CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() );*/

    if ( m_bExplodable ) // If the door explode
        ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), true );
}

In the end of Precache function :
Code: [Select]
switch (m_bUnlockedSentence)
    {
        case 1: m_ls.sUnlockedSentence = ALLOC_STRING("EA"); break; // access granted
        case 2: m_ls.sUnlockedSentence = ALLOC_STRING("ED"); break; // security door
        case 3: m_ls.sUnlockedSentence = ALLOC_STRING("EF"); break; // blast door
        case 4: m_ls.sUnlockedSentence = ALLOC_STRING("EFIRE"); break; // fire door
        case 5: m_ls.sUnlockedSentence = ALLOC_STRING("ECHEM"); break; // chemical door
        case 6: m_ls.sUnlockedSentence = ALLOC_STRING("ERAD"); break; // radiation door
        case 7: m_ls.sUnlockedSentence = ALLOC_STRING("ECON"); break; // gen containment
        case 8: m_ls.sUnlockedSentence = ALLOC_STRING("EH"); break; // maintenance door
       
        default: m_ls.sUnlockedSentence = 0; break;
    }

    // Precache sounds and gibs
    const char *pGibName;

    switch (m_Material)
    {
    case matWood:
        pGibName = "models/woodgibs.mdl";
       
        PRECACHE_SOUND("debris/bustcrate1.wav");
        PRECACHE_SOUND("debris/bustcrate2.wav");
        break;

    case matFlesh:
        pGibName = "models/fleshgibs.mdl";
       
        PRECACHE_SOUND("debris/bustflesh1.wav");
        PRECACHE_SOUND("debris/bustflesh2.wav");
        break;

    case matComputer:
        PRECACHE_SOUND("buttons/spark5.wav");
        PRECACHE_SOUND("buttons/spark6.wav");
        pGibName = "models/computergibs.mdl";
       
        PRECACHE_SOUND("debris/bustmetal1.wav");
        PRECACHE_SOUND("debris/bustmetal2.wav");
        break;

    case matGlass:
        pGibName = "models/glassgibs.mdl";
       
        PRECACHE_SOUND("debris/bustglass1.wav");
        PRECACHE_SOUND("debris/bustglass2.wav");
        break;

    case matMetal:
        pGibName = "models/metalplategibs.mdl";
       
        PRECACHE_SOUND("debris/bustmetal1.wav");
        PRECACHE_SOUND("debris/bustmetal2.wav");
        break;

    case matCinderBlock:
        pGibName = "models/cindergibs.mdl";
       
        PRECACHE_SOUND("debris/bustconcrete1.wav");
        PRECACHE_SOUND("debris/bustconcrete2.wav");
        break;

    case matRocks:
        pGibName = "models/rockgibs.mdl";
       
        PRECACHE_SOUND("debris/bustconcrete1.wav");
        PRECACHE_SOUND("debris/bustconcrete2.wav");
        break;

    case matCeilingTile:
        pGibName = "models/ceilinggibs.mdl";
       
        PRECACHE_SOUND ("debris/bustceiling.wav");
        break;
    }
    MaterialSoundPrecache( m_Material );
   
    m_idShard = PRECACHE_MODEL( (char *)pGibName );
}
In table m_SaveData[], add the following in the end of the function :
Code: [Select]
DEFINE_FIELD( CBaseDoor, m_Material, FIELD_INTEGER ),
    DEFINE_FIELD( CBaseDoor, m_bBreakable, FIELD_BOOLEAN ),
    DEFINE_FIELD( CBaseDoor, m_bExplodable, FIELD_BOOLEAN ),
Go to Spawn function and add :
Code: [Select]
pev->takedamage = DAMAGE_YES;Before pev->movetype. Find the condition :
Code: [Select]
if (DoorActivate( ))
        SetTouch( NULL );
And put :
Code: [Select]
BreakTouch( pOther );After it.

Now we're going to add a little bit of a code to let the mapper if the door is breakable and/or explodable or not.
In function CBaseDoor::KeyValue( KeyValueData *pkvd ) add :
Code: [Select]
int i;after { and :
Code: [Select]
else if (FStrEq(pkvd->szKeyName, "breakable"))
    {
        i = atoi( pkvd->szValue );
        m_bBreakable = (i == 1);
    }
    // Getting explosion power (if 0 => no explode)
    else if (FStrEq(pkvd->szKeyName, "explodemagnitude"))
    {
        i = atoi( pkvd->szValue );
        if( i < 0)
            i *= -1;
        pev->impulse = i;
        m_bExplodable = (i > 0);
    }
    // What is the material ?
    else if (FStrEq(pkvd->szKeyName, "material"))
    {
        i = atoi( pkvd->szValue );

        // 0:glass, 1:metal, 2:flesh, 3:wood

        if ((i < 0) || (i >= matLastMaterial))
            m_Material = matWood;
        else
            m_Material = (Materials)i;

        pkvd->fHandled = true;
    }
Before else CBaseToggle::KeyValue( pkvd );

You can now compile the DLL.
Open your FGD and find @BaseClass base(Appearflags, Targetname, RenderFields, Global, Angles) = Door, at the bottom put :
Code: [Select]
breakable(choices) :"Breakable" : 0 =
[
0: "No"
1: "Yes"
]

explodemagnitude(integer) : "Explode Magnitude (0=none)" : 0

material(choices) :"Material Type" : 0 =
[
0: "Glass"
1: "Wood"
2: "Metal"
3: "Flesh"
4: "Cinder Block"
5: "Ceiling Tile"
6: "Computer"
7: "Rocks"
]
If you want "func_door_rotating' to be breakable or/and explodable, just add the following in the CRotDoor Spawn function :
Code: [Select]
pev->takedamage = DAMAGE_YES;Before pev->movetype.
You can now compile the DLL and test.

Enjoy!

Tutorial by Bicou
Original Version (in French) at : http://www.game-lab.com/index.php?section=tutorials&section_id=2&p=tutorial&action=showtut&id=180
Translated in English by Shepard62700FR
Logged