/*** * * Copyright (c) 1996-2001, 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 "cmbase.h" #include "cmbasemonster.h" #include "monsters.h" #include "weapons.h" #include "hornet.h" int iHornetTrail; int iHornetPuff; //========================================================= // don't let hornets gib, ever. //========================================================= int CMHornet :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { // filter these bits a little. bitsDamageType &= ~ ( DMG_ALWAYSGIB ); bitsDamageType |= DMG_NEVERGIB; return CMBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } //========================================================= //========================================================= void CMHornet :: Spawn( void ) { Precache(); pev->movetype = MOVETYPE_FLY; pev->solid = SOLID_BBOX; pev->takedamage = DAMAGE_YES; pev->flags |= FL_MONSTER; pev->health = 1;// weak! // hornets don't live as long in multiplayer m_flStopAttack = gpGlobals->time + 3.5; 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( &CMHornet::DieTouch ); SetThink( &CMHornet::StartTrack ); edict_t *pSoundEnt = pev->owner; if ( !pSoundEnt ) pSoundEnt = edict(); // no real owner, or owner isn't a client. pev->dmg = gSkillData.monDmgHornet; pev->nextthink = gpGlobals->time + 0.1; ResetSequenceInfo( ); } void CMHornet :: 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 CMHornet::IRelationship ( CMBaseEntity *pTarget ) { if ( pTarget->pev->modelindex == pev->modelindex ) { return R_NO; } return CMBaseMonster :: IRelationship( pTarget ); } //========================================================= // ID's Hornet as their owner //========================================================= int CMHornet::Classify ( void ) { 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 CMHornet :: StartTrack ( void ) { IgniteTrail(); SetTouch( &CMHornet::TrackTouch ); SetThink( &CMHornet::TrackTarget ); pev->nextthink = gpGlobals->time + 0.1; } //========================================================= // StartDart - starts a hornet out just flying straight. //========================================================= void CMHornet :: StartDart ( void ) { IgniteTrail(); SetTouch( &CMHornet::DartTouch ); SetThink( &CMHornet::SUB_Remove ); pev->nextthink = gpGlobals->time + 4; } void CMHornet::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 CMHornet :: TrackTarget ( void ) { Vector vecFlightDir; Vector vecDirToEnemy; float flDelta; StudioFrameAdvance( ); if (gpGlobals->time > m_flStopAttack) { SetTouch( NULL ); SetThink( &CMHornet::SUB_Remove ); pev->nextthink = gpGlobals->time + 0.1; return; } // UNDONE: The player pointer should come back after returning from another level if ( m_hEnemy == NULL ) {// enemy is dead. Look( 512 ); m_hEnemy = BestVisibleEnemy( ); } if ( m_hEnemy != NULL && UTIL_FVisible( m_hEnemy, ENT(pev) )) { m_vecEnemyLKP = UTIL_BodyTarget( m_hEnemy, 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 ) pev->nextthink = gpGlobals->time + 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. pev->nextthink = gpGlobals->time + 0.1;// fixed think time break; } pev->angles = UTIL_VecToAngles (pev->velocity); pev->solid = SOLID_BBOX; } //========================================================= // Tracking Hornet hit something //========================================================= void CMHornet :: TrackTouch ( edict_t *pOther ) { if ( (pOther == pev->owner) || pOther->v.modelindex == pev->modelindex ) {// bumped into the guy that shot it. pev->solid = SOLID_NOT; return; } // is this NOT a player and IS a monster? if (!UTIL_IsPlayer(pOther) && (pOther->v.euser4 != NULL)) { CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); if ( IRelationship( pMonster ) <= 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 CMHornet::DartTouch( edict_t *pOther ) { DieTouch( pOther ); } void CMHornet::DieTouch ( edict_t *pOther ) { if ( pOther && pOther->v.takedamage ) {// 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; } if (UTIL_IsPlayer(pOther)) UTIL_TakeDamage( pOther, pev, VARS( pev->owner ), pev->dmg, DMG_BULLET ); else if (pOther->v.euser4 != NULL) { CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); pMonster->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 ( &CMHornet::SUB_Remove ); pev->nextthink = gpGlobals->time + 1;// stick around long enough for the sound to finish! }