* Prevented purchase of bsuit when crouched, prevents bsuit crouch glitch * Added cvar `g_devmapVotes`, switches map votes to devmap votes * Added proxemity mines (grenades), `g_proximityMines` * Added !denyweapons/!alloweapons * Added search to !listmaps * Added maplog fixes, adds devmap info * Improved black name filter * Added timed messages, `g_msg` controls the message, `g_msgtime` controls the repeat rate * Added feature to echo cp to player consoles * Added newline support to !cp * Added vote logging to !adminlog * Added location data to say_area, `g_sayAreaLocations` * Added `g_specASpec`, allows spectators to follow other spectators hovelports didn't make it to this release, hopefully they will make it to 1.5.1 Makefile changes make compiling quicker, as it only builds qvm now
899 lines
25 KiB
C
899 lines
25 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2000-2006 Tim Angus
|
|
|
|
This file is part of Tremulous.
|
|
|
|
Tremulous is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Tremulous is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Tremulous; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
|
|
#define MISSILE_PRESTEP_TIME 50
|
|
|
|
/*
|
|
================
|
|
G_BounceMissile
|
|
|
|
================
|
|
*/
|
|
void G_BounceMissile( gentity_t *ent, trace_t *trace )
|
|
{
|
|
vec3_t velocity;
|
|
float dot;
|
|
int hitTime;
|
|
|
|
// reflect the velocity on the trace plane
|
|
hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
|
|
BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
|
|
dot = DotProduct( velocity, trace->plane.normal );
|
|
VectorMA( velocity, -2 * dot, trace->plane.normal, ent->s.pos.trDelta );
|
|
|
|
if( ent->s.eFlags & EF_BOUNCE_HALF )
|
|
{
|
|
if(!strcmp(ent->classname,"grenade") && g_proximityMines.integer) {
|
|
VectorScale( ent->s.pos.trDelta, 0.00, ent->s.pos.trDelta );
|
|
} else {
|
|
VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta );
|
|
}
|
|
// check for stop
|
|
if( trace->plane.normal[ 2 ] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 )
|
|
{
|
|
G_SetOrigin( ent, trace->endpos );
|
|
return;
|
|
}
|
|
}
|
|
|
|
VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin );
|
|
VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
|
|
ent->s.pos.trTime = level.time;
|
|
}
|
|
|
|
/*
|
|
================
|
|
G_ExplodeMissile
|
|
|
|
Explode a missile without an impact
|
|
================
|
|
*/
|
|
void G_ExplodeMissile( gentity_t *ent )
|
|
{
|
|
vec3_t dir;
|
|
vec3_t origin;
|
|
|
|
BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
|
|
SnapVector( origin );
|
|
G_SetOrigin( ent, origin );
|
|
|
|
// we don't have a valid direction, so just point straight up
|
|
dir[ 0 ] = dir[ 1 ] = 0;
|
|
dir[ 2 ] = 1;
|
|
|
|
ent->s.eType = ET_GENERAL;
|
|
|
|
//TA: tired... can't be fucked... hack
|
|
if( ent->s.weapon != WP_LOCKBLOB_LAUNCHER &&
|
|
ent->s.weapon != WP_FLAMER )
|
|
G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
|
|
|
|
ent->freeAfterEvent = qtrue;
|
|
|
|
// splash damage
|
|
if( ent->splashDamage )
|
|
G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage,
|
|
ent->splashRadius, ent, ent->splashMethodOfDeath );
|
|
|
|
trap_LinkEntity( ent );
|
|
}
|
|
|
|
/*
|
|
================
|
|
G_ProcessProximityMine
|
|
|
|
If an enemy is close to the entity, go boom!
|
|
================
|
|
*/
|
|
void G_ProcessProximityMine(gentity_t *ent) {
|
|
int i, total_entities, entityList[MAX_GENTITIES];
|
|
vec3_t range, mins, maxs;
|
|
gentity_t *target;
|
|
|
|
// Set the next time to run this check (can be overwritten below)
|
|
ent->nextthink = level.time + PROXIMITY_CHECK_FREQUENCY;
|
|
|
|
// Grab all entities around us
|
|
VectorSet(range, PROXIMITY_RANGE, PROXIMITY_RANGE, PROXIMITY_RANGE);
|
|
VectorAdd(ent->s.origin, range, maxs);
|
|
VectorSubtract(ent->s.origin, range, mins);
|
|
|
|
total_entities = trap_EntitiesInBox(mins, maxs, entityList, MAX_GENTITIES);
|
|
|
|
// Loop entities looking for an enemy body
|
|
for(i=0; i<total_entities; i++) {
|
|
target = &g_entities[entityList[i]];
|
|
if(target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS) {
|
|
// Found an enemy, boom time!
|
|
ent->nextthink = level.time + PROXIMITY_BOOM_TIME;
|
|
ent->think = G_ExplodeMissile;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AHive_ReturnToHive( gentity_t *self );
|
|
|
|
/*
|
|
================
|
|
G_MissileImpact
|
|
|
|
================
|
|
*/
|
|
void G_MissileImpact( gentity_t *ent, trace_t *trace )
|
|
{
|
|
gentity_t *other, *attacker;
|
|
qboolean returnAfterDamage = qfalse;
|
|
vec3_t dir;
|
|
|
|
other = &g_entities[ trace->entityNum ];
|
|
attacker = &g_entities[ ent->r.ownerNum ];
|
|
|
|
// check for bounce
|
|
if( !other->takedamage &&
|
|
( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
|
|
{
|
|
G_BounceMissile( ent, trace );
|
|
|
|
//only play a sound if requested
|
|
if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) )
|
|
G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
|
|
|
|
return;
|
|
}
|
|
|
|
if( !strcmp( ent->classname, "grenade" ) )
|
|
{
|
|
//grenade doesn't explode on impact
|
|
G_BounceMissile( ent, trace );
|
|
|
|
//only play a sound if requested
|
|
if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) )
|
|
G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
|
|
|
|
return;
|
|
}
|
|
else if( !strcmp( ent->classname, "lockblob" ) )
|
|
{
|
|
if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
|
|
{
|
|
other->client->ps.stats[ STAT_STATE ] |= SS_BLOBLOCKED;
|
|
other->client->lastLockTime = level.time;
|
|
AngleVectors( other->client->ps.viewangles, dir, NULL, NULL );
|
|
other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir );
|
|
}
|
|
}
|
|
else if( !strcmp( ent->classname, "slowblob" ) )
|
|
{
|
|
if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
|
|
{
|
|
other->client->ps.stats[ STAT_STATE ] |= SS_SLOWLOCKED;
|
|
other->client->lastSlowTime = level.time;
|
|
AngleVectors( other->client->ps.viewangles, dir, NULL, NULL );
|
|
other->client->ps.stats[ STAT_VIEWLOCK ] = DirToByte( dir );
|
|
}
|
|
}
|
|
else if( !strcmp( ent->classname, "hive" ) )
|
|
{
|
|
if( other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_HIVE )
|
|
{
|
|
if( !ent->parent )
|
|
G_Printf( S_COLOR_YELLOW "WARNING: hive entity has no parent in G_MissileImpact\n" );
|
|
else
|
|
ent->parent->active = qfalse;
|
|
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//prevent collision with the client when returning
|
|
ent->r.ownerNum = other->s.number;
|
|
|
|
ent->think = AHive_ReturnToHive;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
|
|
//only damage humans
|
|
if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
|
|
returnAfterDamage = qtrue;
|
|
else
|
|
return;
|
|
}
|
|
}
|
|
|
|
// impact damage
|
|
if( other->takedamage )
|
|
{
|
|
// FIXME: wrong damage direction?
|
|
if( ent->damage )
|
|
{
|
|
vec3_t velocity;
|
|
|
|
BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
|
|
if( VectorLength( velocity ) == 0 )
|
|
velocity[ 2 ] = 1; // stepped on a grenade
|
|
|
|
G_Damage( other, ent, attacker, velocity, ent->s.origin, ent->damage,
|
|
0, ent->methodOfDeath );
|
|
}
|
|
}
|
|
|
|
if( returnAfterDamage )
|
|
return;
|
|
|
|
// is it cheaper in bandwidth to just remove this ent and create a new
|
|
// one, rather than changing the missile into the explosion?
|
|
|
|
if( other->takedamage && other->client )
|
|
{
|
|
G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
|
|
ent->s.otherEntityNum = other->s.number;
|
|
}
|
|
else if( trace->surfaceFlags & SURF_METALSTEPS )
|
|
G_AddEvent( ent, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) );
|
|
else
|
|
G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) );
|
|
|
|
ent->freeAfterEvent = qtrue;
|
|
|
|
// change over to a normal entity right at the point of impact
|
|
ent->s.eType = ET_GENERAL;
|
|
|
|
SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth
|
|
|
|
G_SetOrigin( ent, trace->endpos );
|
|
|
|
// splash damage (doesn't apply to person directly hit)
|
|
if( ent->splashDamage )
|
|
G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius,
|
|
other, ent->splashMethodOfDeath );
|
|
|
|
trap_LinkEntity( ent );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
G_RunMissile
|
|
|
|
================
|
|
*/
|
|
void G_RunMissile( gentity_t *ent )
|
|
{
|
|
vec3_t origin;
|
|
trace_t tr;
|
|
int passent;
|
|
|
|
// get current position
|
|
BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
|
|
|
|
// ignore interactions with the missile owner
|
|
passent = ent->r.ownerNum;
|
|
|
|
// trace a line from the previous position to the current position
|
|
trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask );
|
|
|
|
if( tr.startsolid || tr.allsolid )
|
|
{
|
|
// make sure the tr.entityNum is set to the entity we're stuck in
|
|
trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, ent->clipmask );
|
|
tr.fraction = 0;
|
|
}
|
|
else
|
|
VectorCopy( tr.endpos, ent->r.currentOrigin );
|
|
|
|
ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into...
|
|
trap_LinkEntity( ent );
|
|
ent->r.contents = 0; //...encoding bbox information
|
|
|
|
if( tr.fraction != 1 )
|
|
{
|
|
// never explode or bounce on sky
|
|
if( tr.surfaceFlags & SURF_NOIMPACT )
|
|
{
|
|
// If grapple, reset owner
|
|
if( ent->parent && ent->parent->client && ent->parent->client->hook == ent )
|
|
ent->parent->client->hook = NULL;
|
|
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
G_MissileImpact( ent, &tr );
|
|
if( ent->s.eType != ET_MISSILE )
|
|
return; // exploded
|
|
}
|
|
|
|
// check think function after bouncing
|
|
G_RunThink( ent );
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
fire_flamer
|
|
|
|
=================
|
|
*/
|
|
gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
vec3_t pvel;
|
|
|
|
VectorNormalize (dir);
|
|
|
|
bolt = G_Spawn();
|
|
bolt->classname = "flame";
|
|
bolt->nextthink = level.time + FLAMER_LIFETIME;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_FLAMER;
|
|
bolt->s.generic1 = self->s.generic1; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = FLAMER_DMG;
|
|
bolt->splashDamage = FLAMER_DMG;
|
|
bolt->splashRadius = FLAMER_RADIUS;
|
|
bolt->methodOfDeath = MOD_FLAMER;
|
|
bolt->splashMethodOfDeath = MOD_FLAMER_SPLASH;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -15.0f;
|
|
bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 15.0f;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( self->client->ps.velocity, FLAMER_LAG, pvel );
|
|
VectorMA( pvel, FLAMER_SPEED, dir, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
fire_blaster
|
|
|
|
=================
|
|
*/
|
|
gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize (dir);
|
|
|
|
bolt = G_Spawn();
|
|
bolt->classname = "blaster";
|
|
bolt->nextthink = level.time + 10000;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_BLASTER;
|
|
bolt->s.generic1 = self->s.generic1; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = BLASTER_DMG;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_BLASTER;
|
|
bolt->splashMethodOfDeath = MOD_BLASTER;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, BLASTER_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
fire_pulseRifle
|
|
|
|
=================
|
|
*/
|
|
gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize (dir);
|
|
|
|
bolt = G_Spawn();
|
|
bolt->classname = "pulse";
|
|
bolt->nextthink = level.time + 10000;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_PULSE_RIFLE;
|
|
bolt->s.generic1 = self->s.generic1; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = PRIFLE_DMG;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_PRIFLE;
|
|
bolt->splashMethodOfDeath = MOD_PRIFLE;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, PRIFLE_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
fire_luciferCannon
|
|
|
|
=================
|
|
*/
|
|
gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius )
|
|
{
|
|
gentity_t *bolt;
|
|
int localDamage = (int)( ceil( ( (float)damage /
|
|
(float)LCANNON_TOTAL_CHARGE ) * (float)LCANNON_DAMAGE ) );
|
|
|
|
VectorNormalize( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "lcannon";
|
|
|
|
if( damage == LCANNON_TOTAL_CHARGE )
|
|
bolt->nextthink = level.time;
|
|
else
|
|
bolt->nextthink = level.time + 10000;
|
|
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_LUCIFER_CANNON;
|
|
bolt->s.generic1 = self->s.generic1; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = localDamage;
|
|
bolt->splashDamage = localDamage / 2;
|
|
bolt->splashRadius = radius;
|
|
bolt->methodOfDeath = MOD_LCANNON;
|
|
bolt->splashMethodOfDeath = MOD_LCANNON_SPLASH;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, LCANNON_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
launch_grenade
|
|
|
|
=================
|
|
*/
|
|
gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "grenade";
|
|
if(g_proximityMines.integer) {
|
|
bolt->nextthink = level.time + PROXIMITY_INIT_TIME;
|
|
bolt->think = G_ProcessProximityMine;
|
|
} else {
|
|
bolt->nextthink = level.time + 5000;
|
|
bolt->think = G_ExplodeMissile;
|
|
}
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_GRENADE;
|
|
bolt->s.eFlags = EF_BOUNCE_HALF;
|
|
bolt->s.generic1 = WPM_PRIMARY; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = GRENADE_DAMAGE;
|
|
bolt->splashDamage = GRENADE_DAMAGE;
|
|
bolt->splashRadius = GRENADE_RANGE;
|
|
bolt->methodOfDeath = MOD_GRENADE;
|
|
bolt->splashMethodOfDeath = MOD_GRENADE;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -3.0f;
|
|
bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 3.0f;
|
|
bolt->s.time = level.time;
|
|
|
|
bolt->s.pos.trType = TR_GRAVITY;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, GRENADE_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
//=============================================================================
|
|
|
|
/*
|
|
================
|
|
AHive_ReturnToHive
|
|
|
|
Adjust the trajectory to point towards the hive
|
|
================
|
|
*/
|
|
void AHive_ReturnToHive( gentity_t *self )
|
|
{
|
|
vec3_t dir;
|
|
trace_t tr;
|
|
|
|
if( !self->parent )
|
|
{
|
|
G_Printf( S_COLOR_YELLOW "WARNING: AHive_ReturnToHive called with no self->parent\n" );
|
|
return;
|
|
}
|
|
|
|
trap_UnlinkEntity( self->parent );
|
|
trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
|
|
self->parent->r.currentOrigin, self->r.ownerNum, self->clipmask );
|
|
trap_LinkEntity( self->parent );
|
|
|
|
if( tr.fraction < 1.0f )
|
|
{
|
|
//if can't see hive then disperse
|
|
VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
self->s.pos.trType = TR_STATIONARY;
|
|
self->s.pos.trTime = level.time;
|
|
|
|
self->think = G_ExplodeMissile;
|
|
self->nextthink = level.time + 2000;
|
|
self->parent->active = qfalse; //allow the parent to start again
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract( self->parent->r.currentOrigin, self->r.currentOrigin, dir );
|
|
VectorNormalize( dir );
|
|
|
|
//change direction towards the hive
|
|
VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta );
|
|
SnapVector( self->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
self->s.pos.trTime = level.time;
|
|
|
|
self->think = G_ExplodeMissile;
|
|
self->nextthink = level.time + 15000;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
AHive_SearchAndDestroy
|
|
|
|
Adjust the trajectory to point towards the target
|
|
================
|
|
*/
|
|
void AHive_SearchAndDestroy( gentity_t *self )
|
|
{
|
|
vec3_t dir;
|
|
trace_t tr;
|
|
|
|
trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
|
|
self->target_ent->r.currentOrigin, self->r.ownerNum, self->clipmask );
|
|
|
|
//if there is no LOS or the parent hive is too far away or the target is dead or notargeting, return
|
|
if( tr.entityNum == ENTITYNUM_WORLD ||
|
|
Distance( self->r.currentOrigin, self->parent->r.currentOrigin ) > ( HIVE_RANGE * 5 ) ||
|
|
self->target_ent->health <= 0 || self->target_ent->flags & FL_NOTARGET )
|
|
{
|
|
self->r.ownerNum = ENTITYNUM_WORLD;
|
|
|
|
self->think = AHive_ReturnToHive;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract( self->target_ent->r.currentOrigin, self->r.currentOrigin, dir );
|
|
VectorNormalize( dir );
|
|
|
|
//change direction towards the player
|
|
VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta );
|
|
SnapVector( self->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
self->s.pos.trTime = level.time;
|
|
|
|
self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
fire_hive
|
|
=================
|
|
*/
|
|
gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize ( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "hive";
|
|
bolt->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;
|
|
bolt->think = AHive_SearchAndDestroy;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->s.eFlags |= EF_BOUNCE|EF_NO_BOUNCE_SOUND;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_HIVE;
|
|
bolt->s.generic1 = WPM_PRIMARY; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = HIVE_DMG;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_SWARM;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = self->target_ent;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, HIVE_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
fire_lockblob
|
|
=================
|
|
*/
|
|
gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize ( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "lockblob";
|
|
bolt->nextthink = level.time + 15000;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_LOCKBLOB_LAUNCHER;
|
|
bolt->s.generic1 = WPM_PRIMARY; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = 0;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_UNKNOWN; //doesn't do damage so will never kill
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
|
|
bolt->s.pos.trType = TR_LINEAR;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, 500, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
fire_slowBlob
|
|
=================
|
|
*/
|
|
gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize ( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "slowblob";
|
|
bolt->nextthink = level.time + 15000;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_ABUILD2;
|
|
bolt->s.generic1 = self->s.generic1; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = ABUILDER_BLOB_DMG;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_SLOWBLOB;
|
|
bolt->splashMethodOfDeath = MOD_SLOWBLOB;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
|
|
bolt->s.pos.trType = TR_GRAVITY;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, ABUILDER_BLOB_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
fire_paraLockBlob
|
|
=================
|
|
*/
|
|
gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize ( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "lockblob";
|
|
bolt->nextthink = level.time + 15000;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_LOCKBLOB_LAUNCHER;
|
|
bolt->s.generic1 = self->s.generic1; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = 0;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
|
|
bolt->s.pos.trType = TR_GRAVITY;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, LOCKBLOB_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
fire_bounceBall
|
|
=================
|
|
*/
|
|
gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize ( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "bounceball";
|
|
bolt->nextthink = level.time + 3000;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_ALEVEL3_UPG;
|
|
bolt->s.generic1 = self->s.generic1; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = LEVEL3_BOUNCEBALL_DMG;
|
|
bolt->splashDamage = 0;
|
|
bolt->splashRadius = 0;
|
|
bolt->methodOfDeath = MOD_LEVEL3_BOUNCEBALL;
|
|
bolt->splashMethodOfDeath = MOD_LEVEL3_BOUNCEBALL;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
|
|
bolt->s.pos.trType = TR_GRAVITY;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, LEVEL3_BOUNCEBALL_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
/*bolt->s.eFlags |= EF_BOUNCE;*/
|
|
|
|
return bolt;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
launch_grenade2
|
|
|
|
This is called in the explode command in g_admin.c
|
|
=================
|
|
*/
|
|
gentity_t *launch_grenade2( gentity_t *self, vec3_t start, vec3_t dir )
|
|
{
|
|
gentity_t *bolt;
|
|
|
|
VectorNormalize( dir );
|
|
|
|
bolt = G_Spawn( );
|
|
bolt->classname = "grenade";
|
|
bolt->nextthink = level.time;
|
|
bolt->think = G_ExplodeMissile;
|
|
bolt->s.eType = ET_MISSILE;
|
|
bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
bolt->s.weapon = WP_GRENADE;
|
|
bolt->s.eFlags = EF_BOUNCE_HALF;
|
|
bolt->s.generic1 = WPM_PRIMARY; //weaponMode
|
|
bolt->r.ownerNum = self->s.number;
|
|
bolt->parent = self;
|
|
bolt->damage = GRENADE_DAMAGE;
|
|
bolt->splashDamage = GRENADE_DAMAGE;
|
|
bolt->splashRadius = GRENADE_RANGE;
|
|
bolt->methodOfDeath = MOD_SUICIDE;
|
|
bolt->splashMethodOfDeath = MOD_EXPLODE;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->target_ent = NULL;
|
|
bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -3.0f;
|
|
bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 3.0f;
|
|
bolt->s.time = level.time;
|
|
|
|
bolt->s.pos.trType = TR_GRAVITY;
|
|
bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; // move a bit on the very first frame
|
|
VectorCopy( start, bolt->s.pos.trBase );
|
|
VectorScale( dir, GRENADE_SPEED, bolt->s.pos.trDelta );
|
|
SnapVector( bolt->s.pos.trDelta ); // save net bandwidth
|
|
|
|
VectorCopy( start, bolt->r.currentOrigin );
|
|
|
|
return bolt;
|
|
}
|
|
//=============================================================================
|