stvoy-sp-sdk/game/g_ambients.cpp

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);
}