You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
11 KiB
435 lines
11 KiB
/*** |
|
* |
|
* 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(); |
|
|
|
if( !FNullEnt( pev->owner ) && ( pev->owner->v.flags & FL_CLIENT ) ) |
|
{ |
|
pev->dmg = gSkillData.plrDmgHornet; |
|
} |
|
else |
|
{ |
|
// no real owner, or owner isn't a client. |
|
pev->dmg = gSkillData.monDmgHornet; |
|
} |
|
|
|
pev->nextthink = gpGlobals->time + 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( 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 ); |
|
|
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
|
|
//========================================================= |
|
// StartDart - starts a hornet out just flying straight. |
|
//========================================================= |
|
void CHornet::StartDart( void ) |
|
{ |
|
IgniteTrail(); |
|
|
|
SetTouch( &CHornet::DartTouch ); |
|
|
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + 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( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + 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 ) |
|
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; |
|
|
|
// 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; |
|
pev->nextthink = gpGlobals->time + 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( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + 1;// stick around long enough for the sound to finish! |
|
}
|
|
|