1241 lines
34 KiB
C++
1241 lines
34 KiB
C++
|
//g_ambient.cpp
|
||
|
//Ambient creatures, effects, hazards, etc.
|
||
|
#include "g_local.h"
|
||
|
#include "g_functions.h"
|
||
|
#include "b_local.h"
|
||
|
|
||
|
extern void G_StartObjectMoving( gentity_t *object, vec3_t dir, float speed, trType_t trType );
|
||
|
extern void G_RunObject( gentity_t *ent );
|
||
|
extern void G_StopObjectMoving( gentity_t *object );
|
||
|
extern void CG_ElectricalExplosion( vec3_t start, vec3_t end, float radius );
|
||
|
extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, float speed, int numChunks, material_t chunkType, int customChunk, float baseScale );
|
||
|
extern void CG_StasisFixitsThink ( centity_t *cent );
|
||
|
extern void CG_StasisFlierAttackThink ( centity_t *cent );
|
||
|
extern void CG_StasisFlierIdleThink ( centity_t *cent );
|
||
|
extern void CG_StasisFlierChildDeath ( vec3_t pos );
|
||
|
extern void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point );
|
||
|
extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
|
||
|
|
||
|
void SP_ambient_etherian_fliers_child (gentity_t *self);
|
||
|
|
||
|
#define FLIER_HOMELESS 1
|
||
|
#define FLIER_CHILD_MIN_SPEED 5
|
||
|
#define FLIER_CHILD_SPEED_RANGE 800
|
||
|
#define FLIER_CHILD_MIN_DIST 48
|
||
|
#define FLIER_CHILD_DIST_RANGE 128
|
||
|
#define FLIER_ATTACK_TIME 6000
|
||
|
#define FLIER_NOATTACK_TIME_MIN 1000
|
||
|
#define FLIER_NOATTACK_TIME_MAX 3000
|
||
|
#define FLIER_CHILD_DAMAGE 1
|
||
|
#define FLIER_REACHED_TARGET 48
|
||
|
#define FLIER_INSPECT_DAMAGE 4
|
||
|
|
||
|
/*QUAKED ambient_etherian_mine (1 0 0) (-8 -8 -30) (8 8 8)
|
||
|
Floats in position
|
||
|
Homes in on anything within it's radius (default is 160)
|
||
|
Impact with solid objects makes it explode with Etherian Stasis Energy effect
|
||
|
Damage affects other gords, does knockback
|
||
|
Can be shot, 1 hit one kill.
|
||
|
|
||
|
splashDamage and splashRadius - explosion size and damage
|
||
|
radius - how close someone has to be to go after them
|
||
|
speed - movement speed (default is 40)
|
||
|
target -fires them when explode
|
||
|
|
||
|
TODO:
|
||
|
Allow targetting to explode when used
|
||
|
Allow to start dormant and wake up?
|
||
|
Allow setting of health?
|
||
|
*/
|
||
|
//---------------------------------------
|
||
|
void SP_ambient_etherian_mine (gentity_t *self)
|
||
|
{
|
||
|
G_SetOrigin(self, self->s.origin);
|
||
|
//pos1 is where to return to
|
||
|
VectorCopy(self->s.origin, self->pos1);
|
||
|
|
||
|
self->s.eType = ET_GENERAL;
|
||
|
self->s.modelindex = G_ModelIndex("models/mapobjects/stasis/mine.md3");
|
||
|
G_SoundIndex("sound/weapons/explosions/mine1.wav"); //precache
|
||
|
G_SoundIndex("sound/weapons/explosions/mine2.wav"); //precache
|
||
|
G_SoundIndex("sound/weapons/explosions/mine3.wav"); //precache
|
||
|
|
||
|
if ( self->radius <= 0 )
|
||
|
self->radius = 160;
|
||
|
|
||
|
if ( self->speed <= 0 )
|
||
|
self->speed = 40;
|
||
|
|
||
|
VectorSet( self->mins, -8, -8, -30 );
|
||
|
VectorSet( self->maxs, 8, 8, 8 );
|
||
|
|
||
|
self->health = 1;
|
||
|
self->takedamage = qtrue;
|
||
|
self->contents = CONTENTS_SOLID;
|
||
|
self->e_DieFunc = dieF_mine_die;
|
||
|
self->e_TouchFunc = touchF_mine_touch;
|
||
|
self->e_ThinkFunc = thinkF_mine_think;
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
self->clipmask = MASK_NPCSOLID;
|
||
|
self->noDamageTeam = TEAM_STASIS;
|
||
|
|
||
|
if ( self->splashDamage <= 0 )
|
||
|
self->splashDamage = 30;
|
||
|
|
||
|
if ( self->splashRadius <= 0 )
|
||
|
self->splashRadius = 64;
|
||
|
|
||
|
self->s.eFlags |= EF_ANIM_ALLFAST;
|
||
|
|
||
|
gi.linkentity(self);
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void mine_think (gentity_t *self)
|
||
|
{
|
||
|
//FIXME: if not even in PVS of player, don't think at all, maybe should stop too?
|
||
|
gentity_t *target;
|
||
|
gentity_t *entity_list[MAX_GENTITIES];
|
||
|
int count;
|
||
|
float dist, speed = self->speed;
|
||
|
float bestDist = Q3_INFINITE;
|
||
|
qboolean doMove = qfalse;
|
||
|
qboolean changeDir = qfalse;
|
||
|
vec3_t vec, targOrg;
|
||
|
|
||
|
if ( !Q_irand(0, 3) )
|
||
|
{
|
||
|
count = G_RadiusList( self->currentOrigin, self->radius, self, qtrue, entity_list );
|
||
|
for ( int i = 0; i < count; i++ )
|
||
|
{
|
||
|
target = entity_list[i];
|
||
|
|
||
|
if ( ( target == self) || ( target == self->owner ) )
|
||
|
continue;
|
||
|
|
||
|
if ( target->client && target->client->playerTeam == TEAM_STARFLEET && !(target->flags & FL_NOTARGET) )
|
||
|
{
|
||
|
VectorCopy( target->currentOrigin, targOrg );
|
||
|
targOrg[2] += target->client->ps.viewheight;
|
||
|
|
||
|
VectorSubtract( targOrg, self->pos1, vec );
|
||
|
dist = VectorLengthSquared( vec );
|
||
|
if ( dist < self->radius*self->radius )
|
||
|
{//Close to my start spot
|
||
|
VectorSubtract( targOrg, self->currentOrigin, vec );
|
||
|
dist = VectorLengthSquared( vec );
|
||
|
if ( dist < bestDist )//all things equal, keep current
|
||
|
{
|
||
|
if ( !self->enemy )
|
||
|
{//Aquired new enemy
|
||
|
self->pushDebounceTime = 0;
|
||
|
}
|
||
|
G_SetEnemy(self, target);
|
||
|
bestDist = dist;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( self->pushDebounceTime > level.time )
|
||
|
{//Just drifting
|
||
|
doMove = qtrue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( self->enemy )
|
||
|
{//31337 44!
|
||
|
if ( self->aimDebounceTime <= level.time )
|
||
|
{
|
||
|
VectorCopy( self->enemy->currentOrigin, targOrg );
|
||
|
targOrg[2] += self->enemy->client->ps.viewheight;
|
||
|
|
||
|
VectorSubtract( targOrg, self->currentOrigin, self->movedir );
|
||
|
dist = VectorNormalize( self->movedir );
|
||
|
if ( dist < self->radius )
|
||
|
{
|
||
|
changeDir = qtrue;
|
||
|
doMove = qtrue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->enemy = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !self->enemy )
|
||
|
{
|
||
|
vec3_t homeDir;
|
||
|
|
||
|
changeDir = qtrue;
|
||
|
doMove = qtrue;
|
||
|
|
||
|
VectorSubtract( self->pos1, self->currentOrigin, homeDir );
|
||
|
dist = VectorNormalize( homeDir );
|
||
|
|
||
|
if ( dist > self->speed / 4 )
|
||
|
{//Trying to get back to center
|
||
|
VectorCopy( homeDir, self->movedir );
|
||
|
}
|
||
|
else
|
||
|
{//hover about
|
||
|
speed *= 0.25f;
|
||
|
VectorSet( self->movedir, homeDir[0] + crandom(), homeDir[1] + crandom(), homeDir[2] + crandom() );
|
||
|
self->pushDebounceTime = level.time + 500 + random() * 200;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//FIXME: accelerate to top speed?
|
||
|
if ( doMove )
|
||
|
{
|
||
|
G_RunObject(self);
|
||
|
if ( changeDir )
|
||
|
{
|
||
|
G_StartObjectMoving( self, self->movedir, speed, TR_LINEAR );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
G_StopObjectMoving( self );
|
||
|
}
|
||
|
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void mine_explode( gentity_t *self )
|
||
|
{
|
||
|
if ( self->attackDebounceTime <= level.time )
|
||
|
{
|
||
|
if ( self->target )
|
||
|
{
|
||
|
G_UseTargets( self, self->enemy );
|
||
|
}
|
||
|
|
||
|
if ( self->splashDamage > 0 && self->splashRadius > 0 )
|
||
|
{
|
||
|
G_TempEntity( self->currentOrigin, EV_STASIS_MINE_EXPLODE );
|
||
|
G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );
|
||
|
}
|
||
|
G_Sound ( self, G_SoundIndex ( va( "sound/weapons/explosions/mine%d.wav", Q_irand(1, 3) ) ) );
|
||
|
|
||
|
G_FreeEntity( self );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->s.eFlags |= EF_SCALE_UP;
|
||
|
VectorSet( self->currentAngles, self->currentAngles[0]+crandom(), self->currentAngles[1]+crandom()*3, self->currentAngles[2]+crandom() );
|
||
|
G_SetAngles( self, self->currentAngles );
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void mine_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
|
||
|
{
|
||
|
//Do explosion
|
||
|
self->health = 0;
|
||
|
self->takedamage = qfalse;
|
||
|
|
||
|
//Throw some chunks?
|
||
|
self->e_PainFunc = painF_NULL;
|
||
|
self->e_DieFunc = dieF_NULL;
|
||
|
self->e_TouchFunc = touchF_NULL;
|
||
|
self->e_ThinkFunc = thinkF_mine_explode;
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
self->attackDebounceTime = level.time + Q_irand(300, 500);
|
||
|
|
||
|
G_SetEnemy(self, attacker);
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void mine_touch (gentity_t *self, gentity_t *other, trace_t *trace)
|
||
|
{
|
||
|
//If valid enemy, explode, otherwise, if another mine, push it, otherwise bounce off
|
||
|
if ( other->client && other->client->playerTeam && other->client->playerTeam != self->noDamageTeam )
|
||
|
{//Hit something, go boom
|
||
|
self->attackDebounceTime = 0;
|
||
|
G_SetEnemy(self, other);
|
||
|
mine_explode( self );
|
||
|
}
|
||
|
else if ( self->s.pos.trDelta && Q_stricmp( other->classname, self->classname ) == 0 )
|
||
|
{//I'm moving and hit another dude
|
||
|
//Drift for a second
|
||
|
G_StartObjectMoving( self, self->movedir, self->speed, TR_LINEAR );
|
||
|
self->pushDebounceTime = level.time + 1000;
|
||
|
}
|
||
|
else if ( other->s.number >= ENTITYNUM_WORLD )//Hit wall, bounce off
|
||
|
{//FIXME: use normal of wall to determine bounce angle?
|
||
|
G_StartObjectMoving( self, self->movedir, -self->speed, TR_LINEAR );
|
||
|
self->pushDebounceTime = level.time + 1000;
|
||
|
}
|
||
|
else if ( !self->s.pos.trDelta )
|
||
|
{//I'm not moving, push me
|
||
|
vec3_t center;
|
||
|
|
||
|
VectorSubtract(other->absmax, other->absmin, center);//size
|
||
|
VectorMA(other->absmin, 0.5, center, center);//center
|
||
|
VectorSubtract( self->currentOrigin, center, self->movedir );
|
||
|
|
||
|
//FIXME: get speed from THEM?
|
||
|
G_StartObjectMoving( self, self->movedir, self->speed, TR_LINEAR );
|
||
|
self->pushDebounceTime = level.time + 1000;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
G_StopObjectMoving( self );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*QUAKED ambient_etherian_fliers (1 0 0) (-8 -8 -8) (8 8 8)
|
||
|
Fly around on paths, occaisionally swoop down and do a melee-range discharge and return to path
|
||
|
|
||
|
"target" them at a circular series of path_corners
|
||
|
|
||
|
"target2" - What they fire when they die
|
||
|
"splashDamage" - Damage their melee attack does per frame (default 3)
|
||
|
"splashRadius" - Size of their melee attack (default 64)
|
||
|
"radius" - How close they can be before I pick them up and attack (default 512)
|
||
|
"health" - (default 40)
|
||
|
|
||
|
TODO:
|
||
|
Allow custom speed...?
|
||
|
*/
|
||
|
|
||
|
void flier_pain (gentity_t *self, gentity_t *attacker, int damage)
|
||
|
{//move? Anim? scream? Change dir? Pitch/roll?
|
||
|
//spin/drift?
|
||
|
if ( !self->enemy &&
|
||
|
(attacker->noDamageTeam != self->noDamageTeam || (attacker->client && attacker->client->playerTeam != self->noDamageTeam)) )
|
||
|
{
|
||
|
G_SetEnemy(self, attacker);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void flier_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
|
||
|
{//drift, scream and disintegrate
|
||
|
|
||
|
if ( self->target2 && self->target2[0] )
|
||
|
{
|
||
|
G_UseTargets2(self, attacker, self->target2);
|
||
|
}
|
||
|
self->health = 0;
|
||
|
self->takedamage = qfalse;
|
||
|
//Throw some chunks?
|
||
|
self->e_PainFunc = painF_NULL;
|
||
|
self->e_DieFunc = dieF_NULL;
|
||
|
self->e_ThinkFunc = thinkF_mine_explode;
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
//VectorCopy( self->currentOrigin, self->s.origin );
|
||
|
|
||
|
// self->attackDebounceTime = level.time + Q_irand(300, 500);
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
extern void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha );
|
||
|
float flier_move( gentity_t *self, vec3_t destOrg, float turnRate, qboolean modTurnSpeeds )
|
||
|
{
|
||
|
vec3_t oldDir, newMoveDir, newAngles, dirDiff;
|
||
|
float moveDist=0;
|
||
|
float moveSpeed;
|
||
|
|
||
|
EvaluateTrajectory( &self->s.apos, level.time, self->s.apos.trBase );
|
||
|
|
||
|
if( self->s.pos.trType == TR_GRAVITY )
|
||
|
{ // so this will inherit whatever the flier was doing when it died
|
||
|
// dammit - do this manually because the gravity stuff isn't right.
|
||
|
vec3_t velVect;
|
||
|
|
||
|
VectorScale( self->movedir, self->speed, velVect );
|
||
|
velVect[2] -= DEFAULT_GRAVITY * ( FRAMETIME / 1000.0 ) * .5; // one quarter of gravity to look floaty
|
||
|
VectorCopy( velVect, self->movedir );
|
||
|
self->speed = VectorNormalize( self->movedir );
|
||
|
|
||
|
G_StartObjectMoving( self, self->movedir, self->speed, TR_GRAVITY );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
VectorSubtract( destOrg, self->currentOrigin, newMoveDir );
|
||
|
vectoangles( newMoveDir, newAngles );
|
||
|
moveDist = VectorNormalize( newMoveDir );
|
||
|
|
||
|
VectorScale( self->movedir, 1.0 - turnRate, oldDir );
|
||
|
VectorScale( newMoveDir, turnRate, newMoveDir );
|
||
|
|
||
|
VectorAdd( oldDir, newMoveDir, self->movedir );
|
||
|
VectorNormalize( self->movedir );
|
||
|
//set movement
|
||
|
//FIXME: mod speed so they slow down on turns
|
||
|
//Speed slows as the change in moveDir increases
|
||
|
VectorSubtract( self->movedir, oldDir, dirDiff );
|
||
|
if( modTurnSpeeds )
|
||
|
{
|
||
|
moveSpeed = (2 - VectorLength( dirDiff )) * 0.5 * self->speed;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
moveSpeed = self->speed;
|
||
|
}
|
||
|
|
||
|
G_StartObjectMoving( self, self->movedir, moveSpeed, TR_LINEAR );
|
||
|
}
|
||
|
|
||
|
|
||
|
// make the angles correct
|
||
|
vec3_t destinationAngle;
|
||
|
vectoangles( self->movedir, destinationAngle );
|
||
|
|
||
|
AnglesSubtract( destinationAngle, self->s.apos.trBase, self->s.apos.trDelta );
|
||
|
|
||
|
//since this is in terms of units per second but we need the amount for a frame
|
||
|
VectorScale( self->s.apos.trDelta, 1000/FRAMETIME, self->s.apos.trDelta );
|
||
|
self->s.apos.trTime = level.time;
|
||
|
|
||
|
|
||
|
return moveDist;
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void flier_find_first_path_corner( gentity_t *self )
|
||
|
{
|
||
|
if ( !self->target || !self->target[0] )
|
||
|
{//Nowhere to start!
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
//gi.Error( "NO start path_corner for etherian flier\n" );
|
||
|
}
|
||
|
}
|
||
|
self->activator = G_Find( NULL, FOFS(targetname), self->target );
|
||
|
if ( !self->activator )
|
||
|
{//Nowhere to start!
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
//gi.Error( "Can't find start path_corner %s for etherian flier\n", self->target );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void flier_check_attack( gentity_t *self )
|
||
|
{
|
||
|
if ( self->etherian_fixit )
|
||
|
{//FIXME: heal
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( self->attackDebounceTime > level.time )
|
||
|
{
|
||
|
//Do effect
|
||
|
self->e_clThinkFunc = clThinkF_CG_StasisFlierAttackThink;
|
||
|
//Do radius damage
|
||
|
G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_STASIS );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->e_clThinkFunc = clThinkF_NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
qboolean flier_reached_path_corner( gentity_t *self )
|
||
|
{
|
||
|
vec3_t vec;
|
||
|
float dist;
|
||
|
|
||
|
if ( !self->activator )
|
||
|
{
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
VectorSubtract(self->currentOrigin, self->activator->currentOrigin, vec);
|
||
|
dist = VectorLengthSquared(vec);
|
||
|
|
||
|
if(dist < 256)//16 squared
|
||
|
{
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
if( !Q_irand(0, 3) )
|
||
|
{
|
||
|
//G_Sound( self, G_SoundIndex( "sound/enemies/eflyer/?.wav" ) );
|
||
|
}
|
||
|
}
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
qboolean flier_check_valid_enemy( gentity_t *self )
|
||
|
{
|
||
|
if( !self->enemy )
|
||
|
{//none
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
if ( self->etherian_fixit )
|
||
|
{//FIXME: see if still exists and still damaged
|
||
|
if( !(self->enemy->svFlags & SVF_BROKEN) )
|
||
|
{//already fixed
|
||
|
self->s.eFlags &= ~EF_EYEBEAM;
|
||
|
self->enemy = NULL;
|
||
|
return qfalse;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( self->enemy->flags & FL_NOTARGET )
|
||
|
{//notarget
|
||
|
self->enemy = NULL;
|
||
|
return qfalse;
|
||
|
}
|
||
|
if( !self->enemy->health )
|
||
|
{//Dead
|
||
|
self->enemy = NULL;
|
||
|
return qfalse;
|
||
|
}
|
||
|
else if ( !gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) )
|
||
|
{//Not potentially visible
|
||
|
self->enemy = NULL;
|
||
|
return qfalse;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void flier_find_enemy (gentity_t *self, qboolean alwaysAcquire )
|
||
|
{
|
||
|
gentity_t *target;
|
||
|
gentity_t *entity_list[MAX_GENTITIES];
|
||
|
vec3_t vec;
|
||
|
float dist, bestDist = Q3_INFINITE;
|
||
|
|
||
|
if ( Q_irand(0, 1) && !alwaysAcquire )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( !self->etherian_fixit )
|
||
|
{ // we only bother with the player - we make not so much sense for the pals
|
||
|
//g_entities[0]
|
||
|
trace_t tr;
|
||
|
|
||
|
gi.trace( &tr, self->currentOrigin, vec3_origin, vec3_origin, g_entities[0].currentOrigin, self->s.number, MASK_SHOT );
|
||
|
|
||
|
if ( tr.fraction == 1.0f || tr.entityNum == g_entities[0].s.number )
|
||
|
{
|
||
|
G_SetEnemy(self, &g_entities[0]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int count = G_RadiusList( self->currentOrigin, 1024, self, !self->etherian_fixit, entity_list );
|
||
|
for ( int i = 0; i < count; i++ )
|
||
|
{
|
||
|
target = entity_list[i];
|
||
|
|
||
|
if ( target == self )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( target->svFlags & SVF_BROKEN )
|
||
|
{
|
||
|
VectorSubtract( target->currentOrigin, self->currentOrigin, vec );
|
||
|
dist = VectorLengthSquared( vec );
|
||
|
if ( dist < bestDist )//all things equal, keep current
|
||
|
{
|
||
|
if ( gi.inPVS( self->currentOrigin, target->currentOrigin ) )
|
||
|
{//NAV_ClearPathToPoint changed to always add monsterclip, so have to do our own trace now
|
||
|
trace_t trace;
|
||
|
|
||
|
gi.trace( &trace, self->currentOrigin, self->mins, self->maxs, target->currentOrigin, self->s.number, self->clipmask );
|
||
|
|
||
|
if( ( ( trace.startsolid == qfalse ) && ( trace.allsolid == qfalse ) ) && ( trace.fraction == 1.0f ) )
|
||
|
{
|
||
|
G_SetEnemy(self, target);
|
||
|
bestDist = dist;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void fixit_touch ( gentity_t *self, gentity_t *other, trace_t *trace )
|
||
|
{
|
||
|
if ( other->s.number == ENTITYNUM_WORLD )
|
||
|
{
|
||
|
if ( VectorLengthSquared( trace->plane.normal ) )
|
||
|
{
|
||
|
vec3_t oldDir, newMoveDir;
|
||
|
|
||
|
VectorScale( self->movedir, 0.3, oldDir );
|
||
|
VectorScale( trace->plane.normal, 0.7, newMoveDir );
|
||
|
|
||
|
VectorAdd( oldDir, newMoveDir, self->movedir );
|
||
|
VectorNormalize( self->movedir );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void flier_child_touch ( gentity_t *self, gentity_t *other, trace_t *trace )
|
||
|
{
|
||
|
G_RadiusDamage( self->currentOrigin, self, FLIER_CHILD_DAMAGE, 36, self, MOD_STASIS );
|
||
|
|
||
|
CG_StasisFlierChildDeath( self->currentOrigin );
|
||
|
//CG_ElectricalExplosion( self->currentOrigin, self->currentOrigin, self->splashRadius );
|
||
|
G_Sound ( self, G_SoundIndex ( va( "sound/weapons/explosions/mine%d.wav", Q_irand(1, 3) ) ) );
|
||
|
|
||
|
G_FreeEntity( self );
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void flier_fire_children (gentity_t *attacker)
|
||
|
{
|
||
|
int curChild = 0;
|
||
|
|
||
|
gentity_t *curCheck = NULL;
|
||
|
|
||
|
while( (curCheck = G_Find( curCheck, FOFS(classname), "flier_child" )) != NULL )
|
||
|
{
|
||
|
if( curCheck->owner == attacker )
|
||
|
{
|
||
|
VectorCopy( g_entities[0].currentOrigin, curCheck->pos1 );
|
||
|
curCheck->attackDebounceTime = level.time + ( curChild * 200 );
|
||
|
curCheck->e_clThinkFunc = clThinkF_CG_StasisFlierAttackThink;
|
||
|
//curCheck->enemy = &g_entities[0]; // always target the player - this might be bad, though. Who knows.
|
||
|
|
||
|
if( Q_irand(0, 3) == 0 )
|
||
|
{
|
||
|
curChild++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---------------------------------------
|
||
|
void flier_follow_path (gentity_t *self)
|
||
|
{//FIXME: don't think when enemy out of PVS?
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
if ( self->speed > 140 )
|
||
|
{
|
||
|
self->speed -= 10;
|
||
|
}
|
||
|
}
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
G_RunObject( self );
|
||
|
|
||
|
flier_check_valid_enemy( self );
|
||
|
flier_check_attack( self );
|
||
|
|
||
|
if ( !self->enemy )
|
||
|
{//Look for enemies
|
||
|
flier_find_enemy( self, qfalse );
|
||
|
}
|
||
|
|
||
|
if ( self->enemy && self->aimDebounceTime < level.time)
|
||
|
{
|
||
|
if( self->etherian_fixit )
|
||
|
{
|
||
|
//FIXME: Decide whether or not to swoop
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
//G_Sound( self, G_SoundIndex( "sound/enemies/eflyer/dssklatk.wav" ) );
|
||
|
}
|
||
|
self->e_ThinkFunc = thinkF_flier_swoop_to_enemy;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->enemy = NULL;
|
||
|
// G_SetEnemy(self, NULL);//?
|
||
|
flier_find_enemy ( self, qtrue );
|
||
|
if( self->enemy )
|
||
|
{
|
||
|
// assault the player with my guys
|
||
|
flier_fire_children( self );
|
||
|
self->aimDebounceTime = level.time + FLIER_ATTACK_TIME;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->aimDebounceTime = level.time + Q_irand(FLIER_NOATTACK_TIME_MIN, FLIER_NOATTACK_TIME_MAX);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{//Follow path, with some variance
|
||
|
qboolean goToNext = qfalse;
|
||
|
|
||
|
if ( self->activator )
|
||
|
{//We're already heading to a path_corner
|
||
|
if ( flier_reached_path_corner( self ) )
|
||
|
{
|
||
|
goToNext = qtrue;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{//First time, let's grab one
|
||
|
flier_find_first_path_corner( self );
|
||
|
goToNext = qtrue;
|
||
|
}
|
||
|
|
||
|
if ( goToNext && self->activator )
|
||
|
{//Need to find a new path_corner
|
||
|
//Should always find one, else: ERROR! Must loop!
|
||
|
if ( self->activator->target && self->activator->target[0] )
|
||
|
{//Find our next path_corner
|
||
|
self->activator = G_Find( NULL, FOFS(targetname), self->activator->target );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( self->activator )
|
||
|
{
|
||
|
flier_move( self, self->activator->currentOrigin, 0.3f, qtrue );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void misc_model_breakable_init( gentity_t *ent );
|
||
|
void flier_swoop_to_enemy (gentity_t *self)
|
||
|
{
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
if ( self->speed < 300 )
|
||
|
{
|
||
|
self->speed += 10;
|
||
|
}
|
||
|
}
|
||
|
self->wait = 0;// will need to reacquire path
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
G_RunObject( self );
|
||
|
//Home on on enemy
|
||
|
flier_check_valid_enemy( self );
|
||
|
flier_check_attack( self );
|
||
|
if ( self->enemy )
|
||
|
{
|
||
|
vec3_t targOrg, vec;
|
||
|
float dist;
|
||
|
|
||
|
VectorCopy( self->enemy->currentOrigin, targOrg );
|
||
|
if ( !self->etherian_fixit )
|
||
|
{//flier
|
||
|
targOrg[2] += self->enemy->client->ps.viewheight;
|
||
|
}
|
||
|
VectorSubtract( targOrg, self->currentOrigin, vec );
|
||
|
dist = VectorLengthSquared( vec );
|
||
|
|
||
|
if ( dist < (self->splashRadius*self->splashRadius) )
|
||
|
{//attack!
|
||
|
if ( !self->etherian_fixit )
|
||
|
{//Zap it
|
||
|
//G_Sound( self, G_SoundIndex( "sound/enemies/etherians/attack.wav" ) );
|
||
|
G_Sound( self, G_SoundIndex( "sound/weapons/stasis_alien/fire.wav" ) );
|
||
|
//G_Sound( self, G_SoundIndex( "sound/enemies/borg/borgtaser.wav" ) );
|
||
|
self->aimDebounceTime = level.time + 99999999;
|
||
|
self->attackDebounceTime = level.time + 2000;
|
||
|
self->e_ThinkFunc = thinkF_flier_return_to_path;
|
||
|
}
|
||
|
else
|
||
|
{//Heal it
|
||
|
if ( self->enemy->health >= self->enemy->max_health )
|
||
|
{
|
||
|
if ( self->enemy->health == self->enemy->max_health )
|
||
|
{
|
||
|
//Do this only the first time
|
||
|
misc_model_breakable_init( self->enemy );
|
||
|
|
||
|
// Make sure that the animation restarts when it gets fixed
|
||
|
self->enemy->s.frame = 0;
|
||
|
self->enemy->s.eFlags &= ~ EF_ANIM_ALLFAST;
|
||
|
self->enemy->s.eFlags |= EF_ANIM_ONCE;
|
||
|
|
||
|
if ( self->enemy->target2 && self->enemy->target2[0] )
|
||
|
{//Fixing a breakable uses its target2
|
||
|
G_UseTargets2( self->enemy, self, self->enemy->target2 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( self->spawnflags & FLIER_HOMELESS )
|
||
|
{//Stay here until we find something else to fix
|
||
|
self->activator = NULL;
|
||
|
self->target = NULL;
|
||
|
VectorCopy( self->currentOrigin, self->fixit_start_position );
|
||
|
}
|
||
|
|
||
|
self->enemy->svFlags &= ~SVF_BROKEN;
|
||
|
|
||
|
self->s.eFlags &= ~EF_EYEBEAM;
|
||
|
self->aimDebounceTime = level.time + 300;
|
||
|
self->attackDebounceTime = level.time + 100;
|
||
|
self->e_ThinkFunc = thinkF_flier_return_to_path;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int saveSpeed = self->speed;
|
||
|
|
||
|
if ( ! (self->enemy->s.eFlags & EF_FIXING ) )
|
||
|
{
|
||
|
// Fun fixing has now begun
|
||
|
self->enemy->s.eFlags |= EF_FIXING;
|
||
|
self->enemy->fx_time = level.time;
|
||
|
|
||
|
G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/ambience/stasis/fireflyfixed.wav" ));
|
||
|
}
|
||
|
|
||
|
// Fixing is based on time because of the shader effect used, it animates at a set rate, so
|
||
|
// we should sync up to that if we have any hopes of the effect looking like we want.
|
||
|
if ( self->enemy->fx_time + 1500 < level.time )
|
||
|
{
|
||
|
//Yeah! Fixing is done!
|
||
|
self->enemy->health = self->enemy->max_health;
|
||
|
}
|
||
|
self->s.eFlags |= EF_EYEBEAM;
|
||
|
self->speed = 20;
|
||
|
flier_move( self, targOrg, 0.3f, qtrue );
|
||
|
self->speed = saveSpeed;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
flier_move( self, targOrg, 0.3f, qtrue );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->e_ThinkFunc = thinkF_flier_return_to_path;
|
||
|
}
|
||
|
//When in range, start discharge timer
|
||
|
//Once started discharge, begin return_to_path thinking
|
||
|
}
|
||
|
|
||
|
void flier_return_to_path (gentity_t *self)
|
||
|
{
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
if ( self->speed > 140 )
|
||
|
{
|
||
|
self->speed -= 10;
|
||
|
}
|
||
|
}
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
G_RunObject( self );
|
||
|
|
||
|
flier_check_valid_enemy( self );
|
||
|
//If discharge timer on, keep effect going
|
||
|
flier_check_attack( self );
|
||
|
|
||
|
if ( self->etherian_fixit && !self->enemy && self->aimDebounceTime > level.time )
|
||
|
{//FIXITs can look for enemies on the way back
|
||
|
flier_find_enemy( self, qfalse );
|
||
|
}
|
||
|
|
||
|
if ( self->enemy && self->aimDebounceTime < level.time )
|
||
|
{
|
||
|
//FIXME: Decide whether or not to swoop
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
//need sounds!
|
||
|
//G_Sound( self, G_SoundIndex( "sound/enemies/eflyer/dssklatk.wav" ) );
|
||
|
}
|
||
|
self->e_ThinkFunc = thinkF_flier_swoop_to_enemy;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gentity_t *pCorner = NULL;
|
||
|
vec3_t vec;
|
||
|
float dist, bestDist = Q3_INFINITE;
|
||
|
|
||
|
//FIXME: if no path, return to spawn spot?
|
||
|
if ( !self->activator )
|
||
|
{
|
||
|
if( !self->target || !self->target[0] )
|
||
|
{
|
||
|
VectorSubtract( self->fixit_start_position, self->currentOrigin, vec );
|
||
|
if ( VectorLengthSquared( vec ) < 256 )
|
||
|
{
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
self->aimDebounceTime = level.time + 3000;
|
||
|
}
|
||
|
G_StopObjectMoving( self );
|
||
|
self->e_ThinkFunc = thinkF_flier_follow_path;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
flier_move( self, self->fixit_start_position, 0.3f, qtrue );
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
//If don't have a path corner, pick one - what if we followed the player into an area without one?
|
||
|
flier_find_first_path_corner( self );
|
||
|
if ( !self->activator )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( flier_reached_path_corner( self ) )
|
||
|
{
|
||
|
//gi.Printf( "%s got on path\n", self->classname );
|
||
|
//When reach path corner, start follow_path behavior
|
||
|
if ( !self->etherian_fixit )
|
||
|
{
|
||
|
self->aimDebounceTime = level.time + 3000;
|
||
|
}
|
||
|
self->e_ThinkFunc = thinkF_flier_follow_path;
|
||
|
return;
|
||
|
}
|
||
|
//Find our closest path_corner in PVS
|
||
|
if ( !self->wait )
|
||
|
{//Off path
|
||
|
while ( (pCorner = G_Find( pCorner, FOFS(targetname), self->activator->target )) != NULL )
|
||
|
{
|
||
|
VectorSubtract( pCorner->currentOrigin, self->pos1, vec );
|
||
|
dist = VectorLengthSquared( vec );
|
||
|
if ( dist < bestDist )
|
||
|
{//closest so far
|
||
|
//NAV_ClearPathToPoint changed to always add monsterclip, so have to do our own trace now
|
||
|
if ( gi.inPVS( self->currentOrigin, pCorner->currentOrigin ) )
|
||
|
{
|
||
|
trace_t trace;
|
||
|
|
||
|
gi.trace( &trace, self->currentOrigin, self->mins, self->maxs, pCorner->currentOrigin, self->s.number, self->clipmask );
|
||
|
|
||
|
if( ( ( trace.startsolid == qfalse ) && ( trace.allsolid == qfalse ) ) && ( trace.fraction == 1.0f ) )
|
||
|
{
|
||
|
self->activator = pCorner;
|
||
|
self->wait = 1;//on path now
|
||
|
bestDist = dist;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( self->activator )
|
||
|
{//Head back to the path corner
|
||
|
flier_move( self, self->activator->currentOrigin, 0.3f, qtrue );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void adjust_flier_child_dest (vec3_t inVect, int randomKey)
|
||
|
{
|
||
|
// this is pretty gruesome
|
||
|
|
||
|
// the first number scales the period of the cosine wave,
|
||
|
// the second number intensifies the randomKey,
|
||
|
// and the third number is the height of the cosine wave (basically distance of variation)
|
||
|
inVect[0] += cos( level.time * .0013 + (randomKey*26) ) * 15;
|
||
|
inVect[1] += cos( level.time * .0017 + (randomKey*17)) * 25;
|
||
|
inVect[2] += cos( level.time * .002 + (randomKey*31)) * 25;
|
||
|
}
|
||
|
|
||
|
void flier_child (gentity_t *self)
|
||
|
{
|
||
|
vec3_t destDist;
|
||
|
float destLen;
|
||
|
vec3_t destPos;
|
||
|
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
G_RunObject( self );
|
||
|
|
||
|
if( self->inuse && self->owner )
|
||
|
{ // only adjust this stuff if we're still following the parent
|
||
|
if( self->owner->inuse == qfalse )
|
||
|
{ // if our owner is dead, fall to the ground lifeless
|
||
|
self->s.pos.trType = TR_GRAVITY;
|
||
|
self->e_TouchFunc = touchF_flier_child_touch;
|
||
|
self->owner = NULL;
|
||
|
VectorCopy( self->currentOrigin, destPos ); //?
|
||
|
self->clipmask = MASK_PLAYERSOLID;
|
||
|
//self->speed *= .2; // slow down as we fall out of the air
|
||
|
}
|
||
|
else if( self->owner )
|
||
|
{
|
||
|
float targetSpeed;
|
||
|
|
||
|
// locate my destination
|
||
|
if( self->attackDebounceTime > 0 && self->attackDebounceTime <= level.time )
|
||
|
{ // I am on the attack... So go there until I hit it.
|
||
|
VectorCopy( self->pos1, destPos );
|
||
|
|
||
|
// See if we got there yet
|
||
|
vec3_t diff;
|
||
|
|
||
|
VectorSubtract( self->pos1, self->currentOrigin, diff );
|
||
|
if( VectorLength( diff ) < FLIER_REACHED_TARGET )
|
||
|
{
|
||
|
self->attackDebounceTime = 0;
|
||
|
self->e_clThinkFunc = clThinkF_CG_StasisFlierIdleThink;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
VectorCopy( self->owner->currentOrigin, destPos );
|
||
|
}
|
||
|
|
||
|
if( self->attackDebounceTime > 0 && self->attackDebounceTime <= level.time )
|
||
|
{
|
||
|
G_RadiusDamage( self->currentOrigin, self, FLIER_INSPECT_DAMAGE, 36, self, MOD_STASIS );
|
||
|
}
|
||
|
|
||
|
adjust_flier_child_dest( destPos, (int)self );
|
||
|
|
||
|
VectorSubtract( destPos, self->currentOrigin, destDist);
|
||
|
destLen = VectorLength( destDist );
|
||
|
|
||
|
if( destLen > FLIER_CHILD_DIST_RANGE + FLIER_CHILD_MIN_DIST )
|
||
|
{
|
||
|
// out of range - go maximum speed
|
||
|
targetSpeed = FLIER_CHILD_SPEED_RANGE + FLIER_CHILD_MIN_SPEED;
|
||
|
}
|
||
|
else if( destLen < FLIER_CHILD_MIN_DIST )
|
||
|
{
|
||
|
// too close - go minimum speed
|
||
|
targetSpeed = FLIER_CHILD_MIN_SPEED;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float ratio;
|
||
|
|
||
|
ratio = ( destLen - FLIER_CHILD_MIN_DIST ) / FLIER_CHILD_DIST_RANGE;
|
||
|
//ratio *= ratio; // since this will always be less than one, it will be a lower curve (this mult makes it smaller)
|
||
|
targetSpeed = ratio * FLIER_CHILD_SPEED_RANGE + FLIER_CHILD_MIN_SPEED;
|
||
|
}
|
||
|
|
||
|
if( targetSpeed > self->speed + 90 )
|
||
|
{
|
||
|
self->speed += 90;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->speed = targetSpeed;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( self->inuse)
|
||
|
{
|
||
|
flier_move( self, destPos, 0.65f, qfalse );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SP_ambient_etherian_fliers_child (gentity_t *self)
|
||
|
{
|
||
|
//how do I change this fellas size?
|
||
|
|
||
|
self->s.scale = .05;
|
||
|
|
||
|
G_SetOrigin(self, self->s.origin);
|
||
|
VectorCopy( self->s.origin, self->fixit_start_position );
|
||
|
|
||
|
self->s.eType = ET_THINKER;
|
||
|
//self->s.modelindex = G_ModelIndex("models/mapobjects/stasis/mine.md3");
|
||
|
//self->s.modelindex = G_ModelIndex("models/mapobjects/cargo/hypo.md3");
|
||
|
self->s.modelindex = 0; // Is this correct?
|
||
|
|
||
|
self->speed = 100;
|
||
|
self->health = 40;
|
||
|
|
||
|
//Set contents type and clipmask to contents_shotclip
|
||
|
VectorSet( self->mins, -8, -8, -8 );
|
||
|
VectorSet( self->maxs, 8, 8, 8 );
|
||
|
self->takedamage = qfalse;
|
||
|
self->contents = CONTENTS_NONE;
|
||
|
self->clipmask = CONTENTS_NONE;//?
|
||
|
self->noDamageTeam = TEAM_STASIS;
|
||
|
|
||
|
//Put on first path corner
|
||
|
self->e_ThinkFunc = thinkF_flier_child;
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
self->e_PainFunc = painF_flier_pain;
|
||
|
self->e_DieFunc = dieF_flier_die;
|
||
|
//Need a use func? touch func?
|
||
|
|
||
|
self->s.apos.trType = TR_LINEAR;
|
||
|
|
||
|
self->etherian_fixit = qfalse;
|
||
|
|
||
|
self->noDamageTeam = TEAM_STASIS;
|
||
|
|
||
|
self->svFlags |= SVF_NO_TELEPORT;
|
||
|
|
||
|
self->classname = "flier_child";
|
||
|
|
||
|
// when these guys have an enemy, they will charge it
|
||
|
// otherwise, they will just swarm after their parent
|
||
|
self->enemy = NULL;
|
||
|
|
||
|
self->e_clThinkFunc = clThinkF_CG_StasisFlierIdleThink;
|
||
|
|
||
|
// give myself a creation timestamp
|
||
|
//self->attackDebounceTime = level.time;
|
||
|
|
||
|
//rather, this indicates that I should go to pos1 rather than my owner, 'till I hit it
|
||
|
self->attackDebounceTime = 0;
|
||
|
|
||
|
gi.linkentity(self);
|
||
|
}
|
||
|
|
||
|
void SP_ambient_etherian_fliers (gentity_t *self)
|
||
|
{
|
||
|
G_SetOrigin(self, self->s.origin);
|
||
|
VectorCopy( self->s.origin, self->fixit_start_position );
|
||
|
|
||
|
self->s.eType = ET_THINKER;
|
||
|
self->s.modelindex = G_ModelIndex("models/players/skull/flyingskull.md3");
|
||
|
//G_SoundIndex( "sound/enemies/etherians/attack.wav" );
|
||
|
G_SoundIndex( "sound/weapons/stasis_alien/fire.wav" );
|
||
|
|
||
|
self->speed = 140;
|
||
|
|
||
|
if ( self->splashDamage <= 0 )
|
||
|
{
|
||
|
self->splashDamage = 3;
|
||
|
}
|
||
|
|
||
|
if ( self->splashRadius <= 0 )
|
||
|
{
|
||
|
self->splashRadius = 64;
|
||
|
}
|
||
|
|
||
|
if ( self->radius <= 0 )
|
||
|
{//How close they can be before I pick them up and attack
|
||
|
self->radius = 512;
|
||
|
}
|
||
|
|
||
|
if ( self->health <= 0 )
|
||
|
{
|
||
|
self->health = 40;
|
||
|
}
|
||
|
|
||
|
//Set contents type and clipmask to contents_shotclip
|
||
|
VectorSet( self->mins, -20, -20, -20 );
|
||
|
VectorSet( self->maxs, 20, 20, 20 );
|
||
|
self->takedamage = qtrue;
|
||
|
self->contents = CONTENTS_BODY;
|
||
|
self->clipmask = CONTENTS_SHOTCLIP;
|
||
|
self->noDamageTeam = TEAM_STASIS;
|
||
|
|
||
|
//Put on first path corner
|
||
|
self->e_ThinkFunc = thinkF_flier_return_to_path;
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
self->e_PainFunc = painF_flier_pain;
|
||
|
self->e_DieFunc = dieF_flier_die;
|
||
|
//Need a use func? touch func?
|
||
|
|
||
|
self->s.apos.trType = TR_LINEAR;
|
||
|
|
||
|
self->etherian_fixit = qfalse;
|
||
|
|
||
|
self->noDamageTeam = TEAM_STASIS;
|
||
|
|
||
|
self->svFlags |= SVF_NO_TELEPORT;
|
||
|
|
||
|
// let it animate automatically
|
||
|
self->s.eFlags |= EF_ANIM_ALLFAST;
|
||
|
|
||
|
gi.linkentity(self);
|
||
|
|
||
|
// set up the children for this thing
|
||
|
for( int i = 0; i < 6; i++ ){
|
||
|
gentity_t *child;
|
||
|
child = G_Spawn();
|
||
|
VectorCopy(self->s.origin, child->s.origin);
|
||
|
SP_ambient_etherian_fliers_child(child);//probably missing a bunch of important fields this way
|
||
|
child->owner = self;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/*QUAKED ambient_etherian_fixits (1 0 0) (-8 -8 -8) (8 8 8) HOMELESS
|
||
|
Creates a group of fixit sprites - they will fly to
|
||
|
broken units in their view and fix them.
|
||
|
|
||
|
HOMELESS - Tells them to not try to return to their start position/path once they've fixed something... they'll just stay where they were.
|
||
|
|
||
|
"radius" - max radius for fixits (default 20)
|
||
|
"speed" - how fast they swarm (default 100)
|
||
|
"count" - count of fixits (max of 10, default 6)
|
||
|
"random" - how much variance in the radius 0-100 (default 40)
|
||
|
0 = no variance; 100 = max variance
|
||
|
|
||
|
TODO:
|
||
|
Give a path?
|
||
|
*/
|
||
|
|
||
|
void SP_ambient_etherian_fixits (gentity_t *self)
|
||
|
{//FIXME: implement
|
||
|
G_SetOrigin( self, self->s.origin );
|
||
|
VectorCopy( self->s.origin, self->fixit_start_position );
|
||
|
#ifdef _DEBUG
|
||
|
self->s.modelindex = 0;
|
||
|
#endif
|
||
|
// Apply the defaults
|
||
|
G_SpawnFloat( "radius", "20", &self->radius );
|
||
|
G_SpawnFloat( "speed", "100", &self->speed );
|
||
|
G_SpawnInt( "count", "6", &self->count );
|
||
|
G_SpawnFloat( "random", "40", &self->random );
|
||
|
|
||
|
if ( self->count > 10 )
|
||
|
{
|
||
|
Com_Printf( "ambient_etherian_fixits: count was exceeded!" );
|
||
|
self->count = 10;
|
||
|
}
|
||
|
|
||
|
self->splashRadius = 32;
|
||
|
|
||
|
// pre-cache sounds
|
||
|
self->s.loopSound = G_SoundIndex( "sound/ambience/stasis/fireflywhisper.wav" );
|
||
|
G_SoundIndex( "sound/ambience/stasis/fireflyfixed.wav" );
|
||
|
|
||
|
// Set a random start (pos1) and movement angle/speed(pos2) for the leader..the rest of the swarm will
|
||
|
// be generated by use of an angular offset table.
|
||
|
VectorSet( self->pos1, crandom() * 360, crandom() * 360, 0 );
|
||
|
VectorSet( self->pos2, crandom() * 0.4 * self->speed, crandom() * 0.4 * self->speed, 0 );
|
||
|
|
||
|
VectorCopy( self->currentOrigin, self->s.pos.trBase );
|
||
|
VectorClear( self->s.pos.trDelta );
|
||
|
self->clipmask = CONTENTS_SHOTCLIP|CONTENTS_SOLID;
|
||
|
self->s.pos.trType = TR_LINEAR;
|
||
|
self->s.pos.trTime = level.time;
|
||
|
|
||
|
VectorCopy( self->s.angles, self->s.apos.trBase );
|
||
|
VectorClear( self->s.apos.trDelta );
|
||
|
self->s.apos.trType = TR_LINEAR;
|
||
|
self->s.apos.trTime = level.time;
|
||
|
|
||
|
//Start thinking
|
||
|
self->s.eType = ET_THINKER;
|
||
|
self->e_clThinkFunc = clThinkF_CG_StasisFixitsThink;
|
||
|
|
||
|
self->e_TouchFunc = touchF_fixit_touch;
|
||
|
self->e_ThinkFunc = thinkF_flier_return_to_path;
|
||
|
self->nextthink = level.time + FRAMETIME;
|
||
|
|
||
|
self->etherian_fixit = qtrue;
|
||
|
|
||
|
self->noDamageTeam = TEAM_STASIS;
|
||
|
|
||
|
self->svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY;
|
||
|
|
||
|
gi.linkentity(self);
|
||
|
}
|