Fix apache not giving score upon kill. Fix monster trigger target crashing the server. Add "squadmaker" alias entity. Added plenty of keyvalues to monstermaker, similar to SC's squadmaker.
998 lines
26 KiB
C++
998 lines
26 KiB
C++
/***
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*
|
|
****/
|
|
#ifndef OEM_BUILD
|
|
|
|
#include "extdll.h"
|
|
#include "util.h"
|
|
#include "cmbase.h"
|
|
#include "cmbasemonster.h"
|
|
#include "monsters.h"
|
|
#include "weapons.h"
|
|
#include "nodes.h"
|
|
#include "effects.h"
|
|
#include "explode.h"
|
|
|
|
|
|
#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix!
|
|
#define SF_NOWRECKAGE 0x08
|
|
|
|
void CMApache :: Spawn( void )
|
|
{
|
|
Precache( );
|
|
// motor
|
|
pev->movetype = MOVETYPE_FLY;
|
|
pev->solid = SOLID_BBOX;
|
|
|
|
SET_MODEL(ENT(pev), (!FStringNull( pev->model ) ? STRING( pev->model ) : "models/apache.mdl"));
|
|
UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) );
|
|
UTIL_SetOrigin( pev, pev->origin );
|
|
|
|
pev->flags |= FL_MONSTER;
|
|
pev->takedamage = DAMAGE_AIM;
|
|
pev->health = gSkillData.apacheHealth;
|
|
|
|
m_flFieldOfView = -0.707; // 270 degrees
|
|
|
|
pev->sequence = 0;
|
|
ResetSequenceInfo( );
|
|
pev->frame = RANDOM_LONG(0, 0xFF);
|
|
|
|
InitBoneControllers();
|
|
|
|
if (pev->spawnflags & SF_WAITFORTRIGGER)
|
|
{
|
|
SetUse( &CMApache::StartupUse );
|
|
}
|
|
else
|
|
{
|
|
SetThink( &CMApache::HuntThink );
|
|
SetTouch( &CMApache::FlyTouch );
|
|
pev->nextthink = gpGlobals->time + 1.0;
|
|
}
|
|
|
|
m_iRockets = 10;
|
|
m_pBeam = NULL;
|
|
|
|
m_pGoalEnt = NULL;
|
|
m_flGoalSpeed = 0.0f;
|
|
|
|
m_flForce = 0.0f;
|
|
m_flNextRocket = 0.0f;
|
|
|
|
Vector m_vecTarget = g_vecZero;
|
|
Vector m_posTarget = g_vecZero;
|
|
|
|
Vector m_vecDesired = g_vecZero;
|
|
Vector m_posDesired = g_vecZero;
|
|
|
|
Vector m_vecGoal = g_vecZero;
|
|
Vector m_angGun = g_vecZero;
|
|
|
|
m_flLastSeen = 0.0f;
|
|
m_flPrevSeen = 0.0f;
|
|
|
|
m_iSoundState = 0;
|
|
|
|
pev->classname = MAKE_STRING( "monster_apache" );
|
|
if ( strlen( STRING( m_szMonsterName ) ) == 0 )
|
|
{
|
|
// default name
|
|
m_szMonsterName = MAKE_STRING( "Apache" );
|
|
}
|
|
}
|
|
|
|
|
|
void CMApache::Precache( void )
|
|
{
|
|
PRECACHE_MODEL("models/apache.mdl");
|
|
|
|
PRECACHE_SOUND("apache/ap_rotor1.wav");
|
|
PRECACHE_SOUND("apache/ap_rotor2.wav");
|
|
PRECACHE_SOUND("apache/ap_rotor3.wav");
|
|
PRECACHE_SOUND("apache/ap_whine1.wav");
|
|
|
|
PRECACHE_SOUND("weapons/mortarhit.wav");
|
|
|
|
m_iSpriteTexture = PRECACHE_MODELINDEX( "sprites/white.spr" );
|
|
|
|
PRECACHE_SOUND("turret/tu_fire1.wav");
|
|
|
|
PRECACHE_MODEL("sprites/lgtning.spr");
|
|
|
|
m_iExplode = PRECACHE_MODELINDEX( "sprites/fexplo.spr" );
|
|
m_iBodyGibs = PRECACHE_MODELINDEX( "models/metalplategibs_green.mdl" );
|
|
|
|
CMApacheHVR apache_rocket;
|
|
apache_rocket.Precache();
|
|
}
|
|
|
|
int CMApache :: Classify ( void )
|
|
{
|
|
if ( m_iClassifyOverride == -1 ) // helper
|
|
return CLASS_NONE;
|
|
else if ( m_iClassifyOverride > 0 )
|
|
return m_iClassifyOverride; // override
|
|
|
|
return CLASS_HUMAN_MILITARY;
|
|
}
|
|
|
|
void CMApache::NullThink( void )
|
|
{
|
|
StudioFrameAdvance( );
|
|
pev->nextthink = gpGlobals->time + 0.5;
|
|
}
|
|
|
|
|
|
void CMApache::StartupUse( edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
SetThink( &CMApache::HuntThink );
|
|
SetTouch( &CMApache::FlyTouch );
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
SetUse( NULL );
|
|
}
|
|
|
|
void CMApache :: Killed( entvars_t *pevAttacker, int iGib )
|
|
{
|
|
pev->movetype = MOVETYPE_TOSS;
|
|
pev->gravity = 0.3;
|
|
|
|
STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav" );
|
|
|
|
UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) );
|
|
SetThink( &CMApache::DyingThink );
|
|
SetTouch( &CMApache::CrashTouch );
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
pev->health = 0;
|
|
pev->takedamage = DAMAGE_NO;
|
|
|
|
pev->deadflag = DEAD_DYING;
|
|
FCheckAITrigger(); // trigger death condition
|
|
if ( UTIL_IsPlayer( ENT( pevAttacker ) ) ) // If a player killed this monster, add score
|
|
pevAttacker->frags += 1.0;
|
|
|
|
if (pev->spawnflags & SF_NOWRECKAGE)
|
|
{
|
|
m_flNextRocket = gpGlobals->time + 4.0;
|
|
}
|
|
else
|
|
{
|
|
m_flNextRocket = gpGlobals->time + 15.0;
|
|
}
|
|
}
|
|
|
|
void CMApache :: DyingThink( void )
|
|
{
|
|
StudioFrameAdvance( );
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
|
|
pev->avelocity = pev->avelocity * 1.02;
|
|
|
|
// still falling?
|
|
if (m_flNextRocket > gpGlobals->time )
|
|
{
|
|
if (g_sModelIndexFireball == 0)
|
|
g_sModelIndexFireball = PRECACHE_MODELINDEX("sprites/zerogxplode.spr"); // fireball
|
|
|
|
// random explosions
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin );
|
|
WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now
|
|
WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 ));
|
|
WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 ));
|
|
WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 ));
|
|
WRITE_SHORT( g_sModelIndexFireball );
|
|
WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10
|
|
WRITE_BYTE( 12 ); // framerate
|
|
WRITE_BYTE( TE_EXPLFLAG_NONE );
|
|
MESSAGE_END();
|
|
|
|
if (g_sModelIndexSmoke == 0)
|
|
g_sModelIndexSmoke = PRECACHE_MODELINDEX("sprites/steam1.spr"); // smoke
|
|
|
|
// lots of smoke
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin );
|
|
WRITE_BYTE( TE_SMOKE );
|
|
WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 ));
|
|
WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 ));
|
|
WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 ));
|
|
WRITE_SHORT( g_sModelIndexSmoke );
|
|
WRITE_BYTE( 100 ); // scale * 10
|
|
WRITE_BYTE( 10 ); // framerate
|
|
MESSAGE_END();
|
|
|
|
Vector 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( 400 );
|
|
WRITE_COORD( 400 );
|
|
WRITE_COORD( 132 );
|
|
|
|
// velocity
|
|
WRITE_COORD( pev->velocity.x );
|
|
WRITE_COORD( pev->velocity.y );
|
|
WRITE_COORD( pev->velocity.z );
|
|
|
|
// randomization
|
|
WRITE_BYTE( 50 );
|
|
|
|
// Model
|
|
WRITE_SHORT( m_iBodyGibs ); //model id#
|
|
|
|
// # of shards
|
|
WRITE_BYTE( 4 ); // let client decide
|
|
|
|
// duration
|
|
WRITE_BYTE( 30 );// 3.0 seconds
|
|
|
|
// flags
|
|
|
|
WRITE_BYTE( BREAK_METAL );
|
|
MESSAGE_END();
|
|
|
|
// don't stop it we touch a entity
|
|
pev->flags &= ~FL_ONGROUND;
|
|
pev->nextthink = gpGlobals->time + 0.2;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5;
|
|
|
|
/*
|
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
|
|
WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now
|
|
WRITE_COORD( vecSpot.x );
|
|
WRITE_COORD( vecSpot.y );
|
|
WRITE_COORD( vecSpot.z + 300 );
|
|
WRITE_SHORT( g_sModelIndexFireball );
|
|
WRITE_BYTE( 250 ); // scale * 10
|
|
WRITE_BYTE( 8 ); // framerate
|
|
MESSAGE_END();
|
|
*/
|
|
|
|
// fireball
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot );
|
|
WRITE_BYTE( TE_SPRITE );
|
|
WRITE_COORD( vecSpot.x );
|
|
WRITE_COORD( vecSpot.y );
|
|
WRITE_COORD( vecSpot.z + 256 );
|
|
WRITE_SHORT( m_iExplode );
|
|
WRITE_BYTE( 120 ); // scale * 10
|
|
WRITE_BYTE( 255 ); // brightness
|
|
MESSAGE_END();
|
|
|
|
// big smoke
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot );
|
|
WRITE_BYTE( TE_SMOKE );
|
|
WRITE_COORD( vecSpot.x );
|
|
WRITE_COORD( vecSpot.y );
|
|
WRITE_COORD( vecSpot.z + 512 );
|
|
WRITE_SHORT( g_sModelIndexSmoke );
|
|
WRITE_BYTE( 250 ); // scale * 10
|
|
WRITE_BYTE( 5 ); // framerate
|
|
MESSAGE_END();
|
|
|
|
// blast circle
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin );
|
|
WRITE_BYTE( TE_BEAMCYLINDER );
|
|
WRITE_COORD( pev->origin.x);
|
|
WRITE_COORD( pev->origin.y);
|
|
WRITE_COORD( pev->origin.z);
|
|
WRITE_COORD( pev->origin.x);
|
|
WRITE_COORD( pev->origin.y);
|
|
WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds
|
|
WRITE_SHORT( m_iSpriteTexture );
|
|
WRITE_BYTE( 0 ); // startframe
|
|
WRITE_BYTE( 0 ); // framerate
|
|
WRITE_BYTE( 4 ); // life
|
|
WRITE_BYTE( 32 ); // width
|
|
WRITE_BYTE( 0 ); // noise
|
|
WRITE_BYTE( 255 ); // r, g, b
|
|
WRITE_BYTE( 255 ); // r, g, b
|
|
WRITE_BYTE( 192 ); // r, g, b
|
|
WRITE_BYTE( 128 ); // brightness
|
|
WRITE_BYTE( 0 ); // speed
|
|
MESSAGE_END();
|
|
|
|
EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3);
|
|
|
|
RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST );
|
|
|
|
if (/*!(pev->spawnflags & SF_NOWRECKAGE) && */(pev->flags & FL_ONGROUND))
|
|
{
|
|
/*jlb
|
|
CMBaseEntity *pWreckage = Create( "cycler_wreckage", pev->origin, pev->angles );
|
|
// SET_MODEL( ENT(pWreckage->pev), STRING(pev->model) );
|
|
UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) );
|
|
pWreckage->pev->frame = pev->frame;
|
|
pWreckage->pev->sequence = pev->sequence;
|
|
pWreckage->pev->framerate = 0;
|
|
pWreckage->pev->dmgtime = gpGlobals->time + 5;
|
|
jlb*/
|
|
}
|
|
|
|
// gibs
|
|
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 + 64);
|
|
|
|
// size
|
|
WRITE_COORD( 400 );
|
|
WRITE_COORD( 400 );
|
|
WRITE_COORD( 128 );
|
|
|
|
// velocity
|
|
WRITE_COORD( 0 );
|
|
WRITE_COORD( 0 );
|
|
WRITE_COORD( 200 );
|
|
|
|
// randomization
|
|
WRITE_BYTE( 30 );
|
|
|
|
// Model
|
|
WRITE_SHORT( m_iBodyGibs ); //model id#
|
|
|
|
// # of shards
|
|
WRITE_BYTE( 200 );
|
|
|
|
// duration
|
|
WRITE_BYTE( 200 );// 10.0 seconds
|
|
|
|
// flags
|
|
|
|
WRITE_BYTE( BREAK_METAL );
|
|
MESSAGE_END();
|
|
|
|
SetThink( &CMApache::SUB_Remove );
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
}
|
|
}
|
|
|
|
|
|
void CMApache::FlyTouch( edict_t *pOther )
|
|
{
|
|
// bounce if we hit something solid
|
|
if ( pOther->v.solid == SOLID_BSP)
|
|
{
|
|
TraceResult tr = UTIL_GetGlobalTrace( );
|
|
|
|
// UNDONE, do a real bounce
|
|
pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200);
|
|
}
|
|
}
|
|
|
|
|
|
void CMApache::CrashTouch( edict_t *pOther )
|
|
{
|
|
// only crash if we hit something solid
|
|
if ( pOther->v.solid == SOLID_BSP)
|
|
{
|
|
SetTouch( NULL );
|
|
m_flNextRocket = gpGlobals->time;
|
|
pev->nextthink = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CMApache :: GibMonster( void )
|
|
{
|
|
// EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200);
|
|
}
|
|
|
|
|
|
void CMApache :: HuntThink( void )
|
|
{
|
|
StudioFrameAdvance( );
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
|
|
ShowDamage( );
|
|
|
|
if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target
|
|
{
|
|
m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) );
|
|
if (m_pGoalEnt)
|
|
{
|
|
m_posDesired = m_pGoalEnt->v.origin;
|
|
UTIL_MakeAimVectors( m_pGoalEnt->v.angles );
|
|
m_vecGoal = gpGlobals->v_forward;
|
|
}
|
|
}
|
|
|
|
// if (m_hEnemy == NULL)
|
|
{
|
|
Look( 4092 );
|
|
m_hEnemy = BestVisibleEnemy( );
|
|
}
|
|
|
|
// generic speed up
|
|
if (m_flGoalSpeed < 800)
|
|
m_flGoalSpeed += 5;
|
|
|
|
if (m_hEnemy != NULL)
|
|
{
|
|
// ALERT( at_console, "%s\n", STRING( m_hEnemy->pev->classname ) );
|
|
if (UTIL_FVisible( m_hEnemy, this->edict() ))
|
|
{
|
|
if (m_flLastSeen < gpGlobals->time - 5)
|
|
m_flPrevSeen = gpGlobals->time;
|
|
m_flLastSeen = gpGlobals->time;
|
|
m_posTarget = UTIL_Center( m_hEnemy );
|
|
}
|
|
else
|
|
{
|
|
m_hEnemy = NULL;
|
|
}
|
|
}
|
|
|
|
m_vecTarget = (m_posTarget - pev->origin).Normalize();
|
|
|
|
float flLength = (pev->origin - m_posDesired).Length();
|
|
|
|
if (m_pGoalEnt)
|
|
{
|
|
// ALERT( at_console, "%.0f\n", flLength );
|
|
|
|
if (flLength < 128)
|
|
{
|
|
m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->v.target ) );
|
|
if (m_pGoalEnt)
|
|
{
|
|
m_posDesired = m_pGoalEnt->v.origin;
|
|
UTIL_MakeAimVectors( m_pGoalEnt->v.angles );
|
|
m_vecGoal = gpGlobals->v_forward;
|
|
flLength = (pev->origin - m_posDesired).Length();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_posDesired = pev->origin;
|
|
}
|
|
|
|
if (flLength > 250) // 500
|
|
{
|
|
// float flLength2 = (m_posTarget - pev->origin).Length() * (1.5 - DotProduct((m_posTarget - pev->origin).Normalize(), pev->velocity.Normalize() ));
|
|
// if (flLength2 < flLength)
|
|
if (m_flLastSeen + 90 > gpGlobals->time && DotProduct( (m_posTarget - pev->origin).Normalize(), (m_posDesired - pev->origin).Normalize( )) > 0.25)
|
|
{
|
|
m_vecDesired = (m_posTarget - pev->origin).Normalize( );
|
|
}
|
|
else
|
|
{
|
|
m_vecDesired = (m_posDesired - pev->origin).Normalize( );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_vecDesired = m_vecGoal;
|
|
}
|
|
|
|
Flight( );
|
|
|
|
// ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->time, m_flLastSeen, m_flPrevSeen );
|
|
if ((m_flLastSeen + 1 > gpGlobals->time) && (m_flPrevSeen + 2 < gpGlobals->time))
|
|
{
|
|
if (FireGun( ))
|
|
{
|
|
// slow down if we're fireing
|
|
if (m_flGoalSpeed > 400)
|
|
m_flGoalSpeed = 400;
|
|
}
|
|
}
|
|
|
|
UTIL_MakeAimVectors( pev->angles );
|
|
Vector vecEst = (gpGlobals->v_forward * 800 + pev->velocity).Normalize( );
|
|
// ALERT( at_console, "%d %d %d %4.2f\n", pev->angles.x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->time, DotProduct( m_vecTarget, vecEst ) );
|
|
|
|
if ((m_iRockets % 2) == 1)
|
|
{
|
|
FireRocket( );
|
|
m_flNextRocket = gpGlobals->time + 0.5;
|
|
if (m_iRockets <= 0)
|
|
{
|
|
m_flNextRocket = gpGlobals->time + 10;
|
|
m_iRockets = 10;
|
|
}
|
|
}
|
|
else if (pev->angles.x < 0 && DotProduct( pev->velocity, gpGlobals->v_forward ) > -100 && m_flNextRocket < gpGlobals->time)
|
|
{
|
|
if (m_flLastSeen + 60 > gpGlobals->time)
|
|
{
|
|
if (m_hEnemy != NULL)
|
|
{
|
|
// make sure it's a good shot
|
|
if (DotProduct( m_vecTarget, vecEst) > .965)
|
|
{
|
|
TraceResult tr;
|
|
|
|
UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, ignore_monsters, edict(), &tr );
|
|
if ((tr.vecEndPos - m_posTarget).Length() < 512)
|
|
FireRocket( );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceResult tr;
|
|
|
|
UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, dont_ignore_monsters, edict(), &tr );
|
|
// just fire when close
|
|
if ((tr.vecEndPos - m_posTarget).Length() < 512)
|
|
FireRocket( );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CMApache :: Flight( void )
|
|
{
|
|
// tilt model 5 degrees
|
|
Vector vecAdj = Vector( 5.0, 0, 0 );
|
|
|
|
// estimate where I'll be facing in one seconds
|
|
UTIL_MakeAimVectors( pev->angles + pev->avelocity * 2 + vecAdj);
|
|
// Vector vecEst1 = pev->origin + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 );
|
|
// float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right );
|
|
|
|
float flSide = DotProduct( m_vecDesired, gpGlobals->v_right );
|
|
|
|
if (flSide < 0)
|
|
{
|
|
if (pev->avelocity.y < 60)
|
|
{
|
|
pev->avelocity.y += 8; // 9 * (3.0/2.0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pev->avelocity.y > -60)
|
|
{
|
|
pev->avelocity.y -= 8; // 9 * (3.0/2.0);
|
|
}
|
|
}
|
|
pev->avelocity.y *= 0.98;
|
|
|
|
// estimate where I'll be in two seconds
|
|
UTIL_MakeAimVectors( pev->angles + pev->avelocity * 1 + vecAdj);
|
|
Vector vecEst = pev->origin + pev->velocity * 2.0 + gpGlobals->v_up * m_flForce * 20 - Vector( 0, 0, 384 * 2 );
|
|
|
|
// add immediate force
|
|
UTIL_MakeAimVectors( pev->angles + vecAdj);
|
|
pev->velocity.x += gpGlobals->v_up.x * m_flForce;
|
|
pev->velocity.y += gpGlobals->v_up.y * m_flForce;
|
|
pev->velocity.z += gpGlobals->v_up.z * m_flForce;
|
|
// add gravity
|
|
pev->velocity.z -= 38.4; // 32ft/sec
|
|
|
|
|
|
float flSpeed = pev->velocity.Length();
|
|
float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( pev->velocity.x, pev->velocity.y, 0 ) );
|
|
if (flDir < 0)
|
|
flSpeed = -flSpeed;
|
|
|
|
float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward );
|
|
|
|
// float flSlip = DotProduct( pev->velocity, gpGlobals->v_right );
|
|
float flSlip = -DotProduct( m_posDesired - vecEst, gpGlobals->v_right );
|
|
|
|
// fly sideways
|
|
if (flSlip > 0)
|
|
{
|
|
if (pev->angles.z > -30 && pev->avelocity.z > -15)
|
|
pev->avelocity.z -= 4;
|
|
else
|
|
pev->avelocity.z += 2;
|
|
}
|
|
else
|
|
{
|
|
|
|
if (pev->angles.z < 30 && pev->avelocity.z < 15)
|
|
pev->avelocity.z += 4;
|
|
else
|
|
pev->avelocity.z -= 2;
|
|
}
|
|
|
|
// sideways drag
|
|
pev->velocity.x = pev->velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05);
|
|
pev->velocity.y = pev->velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05);
|
|
pev->velocity.z = pev->velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05);
|
|
|
|
// general drag
|
|
pev->velocity = pev->velocity * 0.995;
|
|
|
|
// apply power to stay correct height
|
|
if (m_flForce < 80 && vecEst.z < m_posDesired.z)
|
|
{
|
|
m_flForce += 12;
|
|
}
|
|
else if (m_flForce > 30)
|
|
{
|
|
if (vecEst.z > m_posDesired.z)
|
|
m_flForce -= 8;
|
|
}
|
|
|
|
// pitch forward or back to get to target
|
|
if (flDist > 0 && flSpeed < m_flGoalSpeed /* && flSpeed < flDist */ && pev->angles.x + pev->avelocity.x > -40)
|
|
{
|
|
// ALERT( at_console, "F " );
|
|
// lean forward
|
|
pev->avelocity.x -= 12.0;
|
|
}
|
|
else if (flDist < 0 && flSpeed > -50 && pev->angles.x + pev->avelocity.x < 20)
|
|
{
|
|
// ALERT( at_console, "B " );
|
|
// lean backward
|
|
pev->avelocity.x += 12.0;
|
|
}
|
|
else if (pev->angles.x + pev->avelocity.x > 0)
|
|
{
|
|
// ALERT( at_console, "f " );
|
|
pev->avelocity.x -= 4.0;
|
|
}
|
|
else if (pev->angles.x + pev->avelocity.x < 0)
|
|
{
|
|
// ALERT( at_console, "b " );
|
|
pev->avelocity.x += 4.0;
|
|
}
|
|
|
|
// ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", pev->origin.x, pev->velocity.x, flDist, flSpeed, pev->angles.x, pev->avelocity.x, m_flForce );
|
|
// ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", pev->origin.z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce );
|
|
|
|
// make rotor, engine sounds
|
|
if (m_iSoundState == 0)
|
|
{
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, 0, 110 );
|
|
// EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 );
|
|
|
|
m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions
|
|
}
|
|
else
|
|
{
|
|
edict_t *pPlayer = NULL;
|
|
|
|
pPlayer = UTIL_FindEntityByClassname( NULL, "player" );
|
|
// UNDONE: this needs to send different sounds to every player for multiplayer.
|
|
if (pPlayer)
|
|
{
|
|
|
|
float pitch = DotProduct( pev->velocity - pPlayer->v.velocity, (pPlayer->v.origin - pev->origin).Normalize() );
|
|
|
|
pitch = (int)(100 + pitch / 50.0);
|
|
|
|
if (pitch > 250)
|
|
pitch = 250;
|
|
if (pitch < 50)
|
|
pitch = 50;
|
|
if (pitch == 100)
|
|
pitch = 101;
|
|
|
|
float flVol = (m_flForce / 100.0) + .1;
|
|
if (flVol > 1.0)
|
|
flVol = 1.0;
|
|
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch);
|
|
}
|
|
// EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch);
|
|
|
|
// ALERT( at_console, "%.0f %.2f\n", pitch, flVol );
|
|
}
|
|
}
|
|
|
|
|
|
void CMApache :: FireRocket( void )
|
|
{
|
|
static float side = 1.0;
|
|
static int count;
|
|
|
|
if (m_iRockets <= 0)
|
|
return;
|
|
|
|
UTIL_MakeAimVectors( pev->angles );
|
|
Vector vecSrc = pev->origin + 1.5 * (gpGlobals->v_forward * 21 + gpGlobals->v_right * 70 * side + gpGlobals->v_up * -79);
|
|
|
|
switch( m_iRockets % 5)
|
|
{
|
|
case 0: vecSrc = vecSrc + gpGlobals->v_right * 10; break;
|
|
case 1: vecSrc = vecSrc - gpGlobals->v_right * 10; break;
|
|
case 2: vecSrc = vecSrc + gpGlobals->v_up * 10; break;
|
|
case 3: vecSrc = vecSrc - gpGlobals->v_up * 10; break;
|
|
case 4: break;
|
|
}
|
|
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc );
|
|
WRITE_BYTE( TE_SMOKE );
|
|
WRITE_COORD( vecSrc.x );
|
|
WRITE_COORD( vecSrc.y );
|
|
WRITE_COORD( vecSrc.z );
|
|
WRITE_SHORT( g_sModelIndexSmoke );
|
|
WRITE_BYTE( 20 ); // scale * 10
|
|
WRITE_BYTE( 12 ); // framerate
|
|
MESSAGE_END();
|
|
|
|
//jlb CMBaseEntity *pRocket = CMBaseEntity::Create( "hvr_rocket", vecSrc, pev->angles, edict() );
|
|
CMApacheHVR *pRocket = CreateClassPtr((CMApacheHVR *)NULL);
|
|
|
|
if (pRocket)
|
|
{
|
|
pRocket->pev->origin = vecSrc;
|
|
pRocket->pev->angles = pev->angles;
|
|
pRocket->pev->owner = edict();
|
|
|
|
// Initialize these for entities who don't link to the world
|
|
pRocket->pev->absmin = pRocket->pev->origin - Vector(1,1,1);
|
|
pRocket->pev->absmax = pRocket->pev->origin + Vector(1,1,1);
|
|
|
|
pRocket->Spawn();
|
|
|
|
pRocket->pev->velocity = pev->velocity + gpGlobals->v_forward * 100;
|
|
}
|
|
|
|
m_iRockets--;
|
|
|
|
side = - side;
|
|
}
|
|
|
|
|
|
|
|
BOOL CMApache :: FireGun( )
|
|
{
|
|
UTIL_MakeAimVectors( pev->angles );
|
|
|
|
Vector posGun, angGun;
|
|
GetAttachment( 1, posGun, angGun );
|
|
|
|
Vector vecTarget = (m_posTarget - posGun).Normalize( );
|
|
|
|
Vector vecOut;
|
|
|
|
vecOut.x = DotProduct( gpGlobals->v_forward, vecTarget );
|
|
vecOut.y = -DotProduct( gpGlobals->v_right, vecTarget );
|
|
vecOut.z = DotProduct( gpGlobals->v_up, vecTarget );
|
|
|
|
Vector angles = UTIL_VecToAngles (vecOut);
|
|
|
|
angles.x = -angles.x;
|
|
if (angles.y > 180)
|
|
angles.y = angles.y - 360;
|
|
if (angles.y < -180)
|
|
angles.y = angles.y + 360;
|
|
if (angles.x > 180)
|
|
angles.x = angles.x - 360;
|
|
if (angles.x < -180)
|
|
angles.x = angles.x + 360;
|
|
|
|
if (angles.x > m_angGun.x)
|
|
m_angGun.x = min( angles.x, m_angGun.x + 12 );
|
|
if (angles.x < m_angGun.x)
|
|
m_angGun.x = max( angles.x, m_angGun.x - 12 );
|
|
if (angles.y > m_angGun.y)
|
|
m_angGun.y = min( angles.y, m_angGun.y + 12 );
|
|
if (angles.y < m_angGun.y)
|
|
m_angGun.y = max( angles.y, m_angGun.y - 12 );
|
|
|
|
m_angGun.y = SetBoneController( 0, m_angGun.y );
|
|
m_angGun.x = SetBoneController( 1, m_angGun.x );
|
|
|
|
Vector posBarrel, angBarrel;
|
|
GetAttachment( 0, posBarrel, angBarrel );
|
|
Vector vecGun = (posBarrel - posGun).Normalize( );
|
|
|
|
if (DotProduct( vecGun, vecTarget ) > 0.98)
|
|
{
|
|
#if 1
|
|
FireBullets( 1, posGun, vecGun, VECTOR_CONE_4DEGREES, 8192, BULLET_MONSTER_12MM, 1 );
|
|
EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.3);
|
|
#else
|
|
static float flNext;
|
|
TraceResult tr;
|
|
UTIL_TraceLine( posGun, posGun + vecGun * 8192, dont_ignore_monsters, ENT( pev ), &tr );
|
|
|
|
if (!m_pBeam)
|
|
{
|
|
m_pBeam = CBeam::BeamCreate( "sprites/lgtning.spr", 80 );
|
|
m_pBeam->PointEntInit( pev->origin, entindex( ) );
|
|
m_pBeam->SetEndAttachment( 1 );
|
|
m_pBeam->SetColor( 255, 180, 96 );
|
|
m_pBeam->SetBrightness( 192 );
|
|
}
|
|
|
|
if (flNext < gpGlobals->time)
|
|
{
|
|
flNext = gpGlobals->time + 0.5;
|
|
m_pBeam->SetStartPos( tr.vecEndPos );
|
|
}
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (m_pBeam)
|
|
{
|
|
UTIL_Remove( m_pBeam->edict() );
|
|
m_pBeam = NULL;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
void CMApache :: ShowDamage( void )
|
|
{
|
|
if (m_iDoSmokePuff > 0 || RANDOM_LONG(0,99) > pev->health)
|
|
{
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin );
|
|
WRITE_BYTE( TE_SMOKE );
|
|
WRITE_COORD( pev->origin.x );
|
|
WRITE_COORD( pev->origin.y );
|
|
WRITE_COORD( pev->origin.z - 32 );
|
|
WRITE_SHORT( g_sModelIndexSmoke );
|
|
WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10
|
|
WRITE_BYTE( 12 ); // framerate
|
|
MESSAGE_END();
|
|
}
|
|
if (m_iDoSmokePuff > 0)
|
|
m_iDoSmokePuff--;
|
|
}
|
|
|
|
|
|
int CMApache :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType )
|
|
{
|
|
if (pevInflictor->owner == edict())
|
|
return 0;
|
|
|
|
if (bitsDamageType & DMG_BLAST)
|
|
{
|
|
flDamage *= 2;
|
|
}
|
|
|
|
/*
|
|
if ( (bitsDamageType & DMG_BULLET) && flDamage > 50)
|
|
{
|
|
// clip bullet damage at 50
|
|
flDamage = 50;
|
|
}
|
|
*/
|
|
|
|
// ALERT( at_console, "%.0f\n", flDamage );
|
|
return CMBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
|
|
}
|
|
|
|
|
|
|
|
void CMApache::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType)
|
|
{
|
|
// ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage );
|
|
|
|
// ignore blades
|
|
if (ptr->iHitgroup == 6 && (bitsDamageType & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB)))
|
|
return;
|
|
|
|
// hit hard, hits cockpit, hits engines
|
|
if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2)
|
|
{
|
|
// ALERT( at_console, "%.0f\n", flDamage );
|
|
AddMultiDamage( pevAttacker, this->edict(), flDamage, bitsDamageType );
|
|
m_iDoSmokePuff = 3 + (flDamage / 5.0);
|
|
}
|
|
else
|
|
{
|
|
// do half damage in the body
|
|
// AddMultiDamage( pevAttacker, this, flDamage / 2.0, bitsDamageType );
|
|
UTIL_Ricochet( ptr->vecEndPos, 2.0 );
|
|
}
|
|
}
|
|
|
|
|
|
void CMApacheHVR :: Spawn( void )
|
|
{
|
|
Precache( );
|
|
// motor
|
|
pev->movetype = MOVETYPE_FLY;
|
|
pev->solid = SOLID_BBOX;
|
|
|
|
SET_MODEL(ENT(pev), "models/HVR.mdl");
|
|
UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0));
|
|
UTIL_SetOrigin( pev, pev->origin );
|
|
|
|
SetThink( &CMApacheHVR::IgniteThink );
|
|
SetTouch( &CMApacheHVR::ExplodeTouch );
|
|
|
|
UTIL_MakeAimVectors( pev->angles );
|
|
m_vecForward = gpGlobals->v_forward;
|
|
pev->gravity = 0.5;
|
|
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
|
|
pev->dmg = 150;
|
|
}
|
|
|
|
|
|
void CMApacheHVR :: Precache( void )
|
|
{
|
|
PRECACHE_MODEL("models/HVR.mdl");
|
|
m_iTrail = PRECACHE_MODELINDEX("sprites/smoke.spr");
|
|
PRECACHE_SOUND ("weapons/rocket1.wav");
|
|
}
|
|
|
|
|
|
void CMApacheHVR :: IgniteThink( void )
|
|
{
|
|
// pev->movetype = MOVETYPE_TOSS;
|
|
|
|
// pev->movetype = MOVETYPE_FLY;
|
|
pev->effects |= EF_LIGHT;
|
|
|
|
// make rocket sound
|
|
EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 );
|
|
|
|
// rocket trail
|
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
|
|
|
|
WRITE_BYTE( TE_BEAMFOLLOW );
|
|
WRITE_SHORT(entindex()); // entity
|
|
WRITE_SHORT(m_iTrail ); // model
|
|
WRITE_BYTE( 15 ); // life
|
|
WRITE_BYTE( 5 ); // width
|
|
WRITE_BYTE( 224 ); // r, g, b
|
|
WRITE_BYTE( 224 ); // r, g, b
|
|
WRITE_BYTE( 255 ); // r, g, b
|
|
WRITE_BYTE( 255 ); // brightness
|
|
|
|
MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS)
|
|
|
|
// set to accelerate
|
|
SetThink( &CMApacheHVR::AccelerateThink );
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
}
|
|
|
|
|
|
void CMApacheHVR :: AccelerateThink( void )
|
|
{
|
|
// check world boundaries
|
|
if (pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096)
|
|
{
|
|
UTIL_Remove( this->edict() );
|
|
return;
|
|
}
|
|
|
|
// accelerate
|
|
float flSpeed = pev->velocity.Length();
|
|
if (flSpeed < 1800)
|
|
{
|
|
pev->velocity = pev->velocity + m_vecForward * 200;
|
|
}
|
|
|
|
// re-aim
|
|
pev->angles = UTIL_VecToAngles( pev->velocity );
|
|
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
}
|
|
|
|
|
|
#endif
|