/*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * This source code contains proprietary and confidential information of * Valve LLC and its suppliers. Access to this code is restricted to * persons who have executed a written SDK license with Valve. Any access, * use or distribution of this code by or to any unlicensed person is illegal. * ****/ //========================================================= // barnacle - stationary ceiling mounted 'fishing' monster //========================================================= #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "schedule.h" #define BARNACLE_BODY_HEIGHT 44 // how 'tall' the barnacle's model is. #define BARNACLE_PULL_SPEED 8 #define BARNACLE_KILL_VICTIM_DELAY 5 // how many seconds after pulling prey in to gib them. //========================================================= // Monster's Anim Events Go Here //========================================================= #define BARNACLE_AE_PUKEGIB 2 class CBarnacle : public CBaseMonster { public: void Spawn( void ); void Precache( void ); CBaseEntity *TongueTouchEnt ( float *pflLength ); int Classify ( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); void EXPORT BarnacleThink ( void ); void EXPORT WaitTillDead ( void ); void Killed( entvars_t *pevAttacker, int iGib ); int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; float m_flAltitude; float m_flCachedLength; // tongue cached length float m_flKillVictimTime; int m_cGibs; // barnacle loads up on gibs each time it kills something. BOOL m_fTongueExtended; BOOL m_fLiftingPrey; float m_flTongueAdj; // FIXME: need a custom barnacle model with non-generic hitgroup // otherwise we can apply to damage to tongue instead of body #ifdef BARNACLE_FIX_VISIBILITY void SetObjectCollisionBox( void ) { pev->absmin = pev->origin + Vector( -16, -16, -m_flCachedLength ); pev->absmax = pev->origin + Vector( 16, 16, 0 ); } #endif }; LINK_ENTITY_TO_CLASS( monster_barnacle, CBarnacle ); TYPEDESCRIPTION CBarnacle::m_SaveData[] = { DEFINE_FIELD( CBarnacle, m_flAltitude, FIELD_FLOAT ), DEFINE_FIELD( CBarnacle, m_flKillVictimTime, FIELD_TIME ), DEFINE_FIELD( CBarnacle, m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something. DEFINE_FIELD( CBarnacle, m_fTongueExtended, FIELD_BOOLEAN ), DEFINE_FIELD( CBarnacle, m_fLiftingPrey, FIELD_BOOLEAN ), DEFINE_FIELD( CBarnacle, m_flTongueAdj, FIELD_FLOAT ), DEFINE_FIELD( CBarnacle, m_flCachedLength, FIELD_FLOAT ), }; IMPLEMENT_SAVERESTORE( CBarnacle, CBaseMonster ); //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CBarnacle :: Classify ( void ) { return CLASS_ALIEN_MONSTER; } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. // // Returns number of events handled, 0 if none. //========================================================= void CBarnacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case BARNACLE_AE_PUKEGIB: CGib::SpawnRandomGibs( pev, 1, 1 ); break; default: CBaseMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CBarnacle :: Spawn() { Precache( ); SET_MODEL(ENT(pev), "models/barnacle.mdl"); UTIL_SetSize( pev, Vector(-16, -16, -32), Vector(16, 16, 0) ); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_NONE; pev->takedamage = DAMAGE_AIM; m_bloodColor = BLOOD_COLOR_RED; pev->effects = EF_INVLIGHT; // take light from the ceiling pev->health = 25; m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) m_MonsterState = MONSTERSTATE_NONE; m_flKillVictimTime = 0; m_flCachedLength = 32; // mins.z m_cGibs = 0; m_fLiftingPrey = FALSE; m_flTongueAdj = -100; InitBoneControllers(); SetActivity ( ACT_IDLE ); SetThink( &BarnacleThink ); pev->nextthink = gpGlobals->time + 0.5; UTIL_SetOrigin ( pev, pev->origin ); } int CBarnacle::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { if ( bitsDamageType & DMG_CLUB ) { flDamage = pev->health; } return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } //========================================================= //========================================================= void CBarnacle :: BarnacleThink ( void ) { CBaseEntity *pTouchEnt; CBaseMonster *pVictim; float flLength; #ifdef BARNACLE_FIX_VISIBILITY if( m_flCachedLength != ( m_flAltitude + m_flTongueAdj ) || ( pev->absmin.z != pev->origin.z + -m_flCachedLength )) { // recalc collision box here to avoid barnacle disappears bug m_flCachedLength = (m_flAltitude + m_flTongueAdj); UTIL_SetOrigin( pev, pev->origin ); } #endif pev->nextthink = gpGlobals->time + 0.1; if ( m_hEnemy != NULL ) { // barnacle has prey. if ( !m_hEnemy->IsAlive() ) { // someone (maybe even the barnacle) killed the prey. Reset barnacle. m_fLiftingPrey = FALSE;// indicate that we're not lifting prey. m_hEnemy = NULL; return; } if ( m_fLiftingPrey ) { if ( m_hEnemy != NULL && m_hEnemy->pev->deadflag != DEAD_NO ) { // crap, someone killed the prey on the way up. m_hEnemy = NULL; m_fLiftingPrey = FALSE; return; } // still pulling prey. Vector vecNewEnemyOrigin = m_hEnemy->pev->origin; vecNewEnemyOrigin.x = pev->origin.x; vecNewEnemyOrigin.y = pev->origin.y; // guess as to where their neck is vecNewEnemyOrigin.x -= 6 * cos(m_hEnemy->pev->angles.y * M_PI/180.0); vecNewEnemyOrigin.y -= 6 * sin(m_hEnemy->pev->angles.y * M_PI/180.0); m_flAltitude -= BARNACLE_PULL_SPEED; vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED; if ( fabs( pev->origin.z - ( vecNewEnemyOrigin.z + m_hEnemy->pev->view_ofs.z - 8 ) ) < BARNACLE_BODY_HEIGHT ) { // prey has just been lifted into position ( if the victim origin + eye height + 8 is higher than the bottom of the barnacle, it is assumed that the head is within barnacle's body ) m_fLiftingPrey = FALSE; EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_bite3.wav", 1, ATTN_NORM ); pVictim = m_hEnemy->MyMonsterPointer(); m_flKillVictimTime = gpGlobals->time + 10;// now that the victim is in place, the killing bite will be administered in 10 seconds. if ( pVictim ) { pVictim->BarnacleVictimBitten( pev ); SetActivity ( ACT_EAT ); } } UTIL_SetOrigin ( m_hEnemy->pev, vecNewEnemyOrigin ); } else { // prey is lifted fully into feeding position and is dangling there. pVictim = m_hEnemy->MyMonsterPointer(); if ( m_flKillVictimTime != -1 && gpGlobals->time > m_flKillVictimTime ) { // kill! if ( pVictim ) { pVictim->TakeDamage ( pev, pev, pVictim->pev->health, DMG_SLASH | DMG_ALWAYSGIB ); m_cGibs = 3; } return; } // bite prey every once in a while if ( pVictim && ( RANDOM_LONG(0,49) == 0 ) ) { switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; } pVictim->BarnacleVictimBitten( pev ); } } } else { // barnacle has no prey right now, so just idle and check to see if anything is touching the tongue. // If idle and no nearby client, don't think so often if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); // Stagger a bit to keep barnacles from thinking on the same frame if ( m_fSequenceFinished ) {// this is done so barnacle will fidget. SetActivity ( ACT_IDLE ); m_flTongueAdj = -100; } if ( m_cGibs && RANDOM_LONG(0,99) == 1 ) { // cough up a gib. CGib::SpawnRandomGibs( pev, 1, 1 ); m_cGibs--; switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; } } pTouchEnt = TongueTouchEnt( &flLength ); if ( pTouchEnt != NULL && m_fTongueExtended ) { // tongue is fully extended, and is touching someone. if ( pTouchEnt->FBecomeProne() ) { EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_alert2.wav", 1, ATTN_NORM ); SetSequenceByName ( "attack1" ); m_flTongueAdj = -20; m_hEnemy = pTouchEnt; pTouchEnt->pev->movetype = MOVETYPE_FLY; pTouchEnt->pev->velocity = g_vecZero; pTouchEnt->pev->basevelocity = g_vecZero; pTouchEnt->pev->origin.x = pev->origin.x; pTouchEnt->pev->origin.y = pev->origin.y; m_fLiftingPrey = TRUE;// indicate that we should be lifting prey. m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted. m_flAltitude = (pev->origin.z - pTouchEnt->EyePosition().z); } } else { // calculate a new length for the tongue to be clear of anything else that moves under it. if ( m_flAltitude < flLength ) { // if tongue is higher than is should be, lower it kind of slowly. m_flAltitude += BARNACLE_PULL_SPEED; m_fTongueExtended = FALSE; } else { m_flAltitude = flLength; m_fTongueExtended = TRUE; } } } // ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj ); SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) ); StudioFrameAdvance( 0.1 ); } //========================================================= // Killed. //========================================================= void CBarnacle :: Killed( entvars_t *pevAttacker, int iGib ) { CBaseMonster *pVictim; pev->solid = SOLID_NOT; pev->takedamage = DAMAGE_NO; if ( m_hEnemy != NULL ) { pVictim = m_hEnemy->MyMonsterPointer(); if ( pVictim ) { pVictim->BarnacleVictimReleased(); } } // CGib::SpawnRandomGibs( pev, 4, 1 ); switch ( RANDOM_LONG ( 0, 1 ) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die3.wav", 1, ATTN_NORM ); break; } SetActivity ( ACT_DIESIMPLE ); SetBoneController( 0, 0 ); StudioFrameAdvance( 0.1 ); pev->nextthink = gpGlobals->time + 0.1; SetThink( &WaitTillDead ); } //========================================================= //========================================================= void CBarnacle :: WaitTillDead ( void ) { pev->nextthink = gpGlobals->time + 0.1; float flInterval = StudioFrameAdvance( 0.1 ); DispatchAnimEvents ( flInterval ); if ( m_fSequenceFinished ) { // death anim finished. StopAnimation(); SetThink( NULL ); } } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CBarnacle :: Precache() { PRECACHE_MODEL("models/barnacle.mdl"); PRECACHE_SOUND("barnacle/bcl_alert2.wav");//happy, lifting food up PRECACHE_SOUND("barnacle/bcl_bite3.wav");//just got food to mouth PRECACHE_SOUND("barnacle/bcl_chew1.wav"); PRECACHE_SOUND("barnacle/bcl_chew2.wav"); PRECACHE_SOUND("barnacle/bcl_chew3.wav"); PRECACHE_SOUND("barnacle/bcl_die1.wav" ); PRECACHE_SOUND("barnacle/bcl_die3.wav" ); } //========================================================= // TongueTouchEnt - does a trace along the barnacle's tongue // to see if any entity is touching it. Also stores the length // of the trace in the int pointer provided. //========================================================= #define BARNACLE_CHECK_SPACING 8 CBaseEntity *CBarnacle :: TongueTouchEnt ( float *pflLength ) { TraceResult tr; float length; // trace once to hit architecture and see if the tongue needs to change position. UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0 , 0 , 2048 ), ignore_monsters, ENT(pev), &tr ); length = fabs( pev->origin.z - tr.vecEndPos.z ); if ( pflLength ) { *pflLength = length; } Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 ); Vector mins = pev->origin - delta; Vector maxs = pev->origin + delta; maxs.z = pev->origin.z; mins.z -= length; CBaseEntity *pList[10]; int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_MONSTER) ); if ( count ) { for ( int i = 0; i < count; i++ ) { // only clients and monsters if ( pList[i] != this && IRelationship( pList[i] ) > R_NO && pList[ i ]->pev->deadflag == DEAD_NO ) // this ent is one of our enemies. Barnacle tries to eat it. { return pList[i]; } } } return NULL; }