/*** * * 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. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ //========================================================= // Hornets //========================================================= #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "weapons.h" #include "soundent.h" #include "hornet.h" #include "gamerules.h" int iHornetTrail; int iHornetPuff; LINK_ENTITY_TO_CLASS( hornet, CHornet ) //========================================================= // Save/Restore //========================================================= TYPEDESCRIPTION CHornet::m_SaveData[] = { DEFINE_FIELD( CHornet, m_flStopAttack, FIELD_TIME ), DEFINE_FIELD( CHornet, m_iHornetType, FIELD_INTEGER ), DEFINE_FIELD( CHornet, m_flFlySpeed, FIELD_FLOAT ), }; IMPLEMENT_SAVERESTORE( CHornet, CBaseMonster ) //========================================================= // don't let hornets gib, ever. //========================================================= int CHornet::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { // filter these bits a little. bitsDamageType &= ~( DMG_ALWAYSGIB ); bitsDamageType |= DMG_NEVERGIB; return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } //========================================================= //========================================================= void CHornet::Spawn( void ) { Precache(); pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; pev->takedamage = DAMAGE_YES; pev->flags |= FL_MONSTER; pev->health = 1;// weak! if( g_pGameRules->IsMultiplayer() ) { // hornets don't live as long in multiplayer m_flStopAttack = gpGlobals->time + 3.5; } else { m_flStopAttack = gpGlobals->time + 5.0; } m_flFieldOfView = 0.9; // +- 25 degrees if( RANDOM_LONG( 1, 5 ) <= 2 ) { m_iHornetType = HORNET_TYPE_RED; m_flFlySpeed = HORNET_RED_SPEED; } else { m_iHornetType = HORNET_TYPE_ORANGE; m_flFlySpeed = HORNET_ORANGE_SPEED; } SET_MODEL( ENT( pev ), "models/hornet.mdl" ); UTIL_SetSize( pev, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) ); SetTouch( &CHornet::DieTouch ); SetThink( &CHornet::StartTrack ); edict_t *pSoundEnt = pev->owner; if( !pSoundEnt ) pSoundEnt = edict(); // no real owner, or owner isn't a client. pev->dmg = gSkillData.monDmgHornet; SetNextThink( 0.1 ); ResetSequenceInfo(); } void CHornet::Precache() { PRECACHE_MODEL( "models/hornet.mdl" ); PRECACHE_SOUND( "agrunt/ag_fire1.wav" ); PRECACHE_SOUND( "agrunt/ag_fire2.wav" ); PRECACHE_SOUND( "agrunt/ag_fire3.wav" ); PRECACHE_SOUND( "hornet/ag_buzz1.wav" ); PRECACHE_SOUND( "hornet/ag_buzz2.wav" ); PRECACHE_SOUND( "hornet/ag_buzz3.wav" ); PRECACHE_SOUND( "hornet/ag_hornethit1.wav" ); PRECACHE_SOUND( "hornet/ag_hornethit2.wav" ); PRECACHE_SOUND( "hornet/ag_hornethit3.wav" ); iHornetPuff = PRECACHE_MODEL( "sprites/muz1.spr" ); iHornetTrail = PRECACHE_MODEL( "sprites/laserbeam.spr" ); } //========================================================= // hornets will never get mad at each other, no matter who the owner is. //========================================================= int CHornet::IRelationship( CBaseEntity *pTarget ) { if( pTarget->pev->modelindex == pev->modelindex ) { return R_NO; } return CBaseMonster::IRelationship( pTarget ); } //========================================================= // ID's Hornet as their owner //========================================================= int CHornet::Classify( void ) { if (m_iClass) return m_iClass; if( pev->owner && pev->owner->v.flags & FL_CLIENT ) { return CLASS_PLAYER_BIOWEAPON; } return CLASS_ALIEN_BIOWEAPON; } //========================================================= // StartTrack - starts a hornet out tracking its target //========================================================= void CHornet::StartTrack( void ) { IgniteTrail(); SetTouch( &CHornet::TrackTouch ); SetThink( &CHornet::TrackTarget ); SetNextThink( 0.1 ); } //========================================================= // StartDart - starts a hornet out just flying straight. //========================================================= void CHornet::StartDart( void ) { IgniteTrail(); SetTouch( &CHornet::DartTouch ); SetThink(&CHornet :: SUB_Remove ); SetNextThink( 4 ); } void CHornet::IgniteTrail( void ) { /* ted's suggested trail colors: r161 g25 b97 r173 g39 b14 old colors case HORNET_TYPE_RED: WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 128 ); // r, g, b WRITE_BYTE( 0 ); // r, g, b break; case HORNET_TYPE_ORANGE: WRITE_BYTE( 0 ); // r, g, b WRITE_BYTE( 100 ); // r, g, b WRITE_BYTE( 255 ); // r, g, b break; */ // trail MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMFOLLOW ); WRITE_SHORT( entindex() ); // entity WRITE_SHORT( iHornetTrail ); // model WRITE_BYTE( 10 ); // life WRITE_BYTE( 2 ); // width switch( m_iHornetType ) { case HORNET_TYPE_RED: WRITE_BYTE( 179 ); // r, g, b WRITE_BYTE( 39 ); // r, g, b WRITE_BYTE( 14 ); // r, g, b break; case HORNET_TYPE_ORANGE: WRITE_BYTE( 255 ); // r, g, b WRITE_BYTE( 128 ); // r, g, b WRITE_BYTE( 0 ); // r, g, b break; } WRITE_BYTE( 128 ); // brightness MESSAGE_END(); } //========================================================= // Hornet is flying, gently tracking target //========================================================= void CHornet::TrackTarget( void ) { Vector vecFlightDir; Vector vecDirToEnemy; float flDelta; StudioFrameAdvance(); if( gpGlobals->time > m_flStopAttack ) { SetTouch( NULL ); SetThink(&CHornet :: SUB_Remove ); SetNextThink( 0.1 ); return; } // UNDONE: The player pointer should come back after returning from another level if( m_hEnemy == 0 ) { // enemy is dead. Look( 512 ); m_hEnemy = BestVisibleEnemy(); } if( m_hEnemy != 0 && FVisible( m_hEnemy ) ) { m_vecEnemyLKP = m_hEnemy->BodyTarget( pev->origin ); } else { m_vecEnemyLKP = m_vecEnemyLKP + pev->velocity * m_flFlySpeed * 0.1; } vecDirToEnemy = ( m_vecEnemyLKP - pev->origin ).Normalize(); if( pev->velocity.Length() < 0.1 ) vecFlightDir = vecDirToEnemy; else vecFlightDir = pev->velocity.Normalize(); // measure how far the turn is, the wider the turn, the slow we'll go this time. flDelta = DotProduct( vecFlightDir, vecDirToEnemy ); if( flDelta < 0.5 ) { // hafta turn wide again. play sound switch( RANDOM_LONG( 0, 2 ) ) { case 0: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM ); break; } } if( flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED ) { // no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far. flDelta = 0.25; } pev->velocity = ( vecFlightDir + vecDirToEnemy ).Normalize(); if( pev->owner && ( pev->owner->v.flags & FL_MONSTER ) ) { // random pattern only applies to hornets fired by monsters, not players. pev->velocity.x += RANDOM_FLOAT( -0.10, 0.10 );// scramble the flight dir a bit. pev->velocity.y += RANDOM_FLOAT( -0.10, 0.10 ); pev->velocity.z += RANDOM_FLOAT( -0.10, 0.10 ); } switch( m_iHornetType ) { case HORNET_TYPE_RED: pev->velocity = pev->velocity * ( m_flFlySpeed * flDelta );// scale the dir by the ( speed * width of turn ) SetNextThink( RANDOM_FLOAT( 0.1, 0.3 ) ); break; case HORNET_TYPE_ORANGE: pev->velocity = pev->velocity * m_flFlySpeed;// do not have to slow down to turn. SetNextThink( 0.1 );// fixed think time break; } pev->angles = UTIL_VecToAngles( pev->velocity ); pev->solid = SOLID_BBOX; // if hornet is close to the enemy, jet in a straight line for a half second. // (only in the single player game) if( m_hEnemy != 0 && !g_pGameRules->IsMultiplayer() ) { if( flDelta >= 0.4 && ( pev->origin - m_vecEnemyLKP ).Length() <= 300 ) { MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); WRITE_BYTE( TE_SPRITE ); WRITE_COORD( pev->origin.x ); // pos WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z ); WRITE_SHORT( iHornetPuff ); // model // WRITE_BYTE( 0 ); // life * 10 WRITE_BYTE( 2 ); // size * 10 WRITE_BYTE( 128 ); // brightness MESSAGE_END(); switch( RANDOM_LONG( 0, 2 ) ) { case 0: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM ); break; } pev->velocity = pev->velocity * 2; SetNextThink( 1.0 ); // don't attack again m_flStopAttack = gpGlobals->time; } } } //========================================================= // Tracking Hornet hit something //========================================================= void CHornet::TrackTouch( CBaseEntity *pOther ) { if( pOther->edict() == pev->owner || pOther->pev->modelindex == pev->modelindex ) { // bumped into the guy that shot it. pev->solid = SOLID_NOT; return; } if( IRelationship( pOther ) <= R_NO ) { // hit something we don't want to hurt, so turn around. pev->velocity = pev->velocity.Normalize(); pev->velocity.x *= -1; pev->velocity.y *= -1; pev->origin = pev->origin + pev->velocity * 4; // bounce the hornet off a bit. pev->velocity = pev->velocity * m_flFlySpeed; return; } DieTouch( pOther ); } void CHornet::DartTouch( CBaseEntity *pOther ) { DieTouch( pOther ); } void CHornet::DieTouch( CBaseEntity *pOther ) { if( pOther && pOther->pev->takedamage && pev->owner ) { // do the damage switch( RANDOM_LONG( 0, 2 ) ) { // buzz when you plug someone case 0: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_hornethit1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_hornethit2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT( pev ), CHAN_VOICE, "hornet/ag_hornethit3.wav", 1, ATTN_NORM ); break; } pOther->TakeDamage( pev, VARS( pev->owner ), pev->dmg, DMG_BULLET ); } pev->modelindex = 0;// so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid pev->solid = SOLID_NOT; SetThink(&CHornet:: SUB_Remove ); SetNextThink( 1 );// stick around long enough for the sound to finish! }