jkxr/Projects/Android/jni/OpenJK/code/game/g_missile.cpp
Simon 4597b03873 Initial Commit
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
2022-09-18 16:37:21 +01:00

1501 lines
43 KiB
C++

/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "g_local.h"
#include "g_functions.h"
#include "wp_saber.h"
#include "bg_local.h"
#include "../cgame/cg_local.h"
#include "b_local.h"
#ifdef _DEBUG
#include <float.h>
#endif //_DEBUG
extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker );
extern qboolean PM_SaberInParry( int move );
extern qboolean PM_SaberInReflect( int move );
extern qboolean PM_SaberInIdle( int move );
extern qboolean PM_SaberInAttack( int move );
extern qboolean PM_SaberInTransitionAny( int move );
extern qboolean PM_SaberInSpecialAttack( int anim );
//-------------------------------------------------------------------------
void G_MissileBounceEffect( gentity_t *ent, vec3_t org, vec3_t dir, qboolean hitWorld )
{
//FIXME: have an EV_BOUNCE_MISSILE event that checks the s.weapon and does the appropriate effect
switch( ent->s.weapon )
{
case WP_BOWCASTER:
if ( hitWorld )
{
G_PlayEffect( "bowcaster/bounce_wall", org, dir );
}
else
{
G_PlayEffect( "bowcaster/deflect", ent->currentOrigin, dir );
}
break;
case WP_BLASTER:
case WP_BRYAR_PISTOL:
case WP_BLASTER_PISTOL:
G_PlayEffect( "blaster/deflect", ent->currentOrigin, dir );
break;
default:
{
gentity_t *tent = G_TempEntity( org, EV_GRENADE_BOUNCE );
VectorCopy( dir, tent->pos1 );
tent->s.weapon = ent->s.weapon;
}
break;
}
}
void G_MissileReflectEffect( gentity_t *ent, vec3_t org, vec3_t dir )
{
//FIXME: have an EV_BOUNCE_MISSILE event that checks the s.weapon and does the appropriate effect
switch( ent->s.weapon )
{
case WP_BOWCASTER:
G_PlayEffect( "bowcaster/deflect", ent->currentOrigin, dir );
break;
case WP_BLASTER:
case WP_BRYAR_PISTOL:
case WP_BLASTER_PISTOL:
default:
G_PlayEffect( "blaster/deflect", ent->currentOrigin, dir );
break;
}
}
//-------------------------------------------------------------------------
static void G_MissileStick( gentity_t *missile, gentity_t *other, trace_t *tr )
{
if ( other->NPC || !Q_stricmp( other->classname, "misc_model_breakable" ))
{
// we bounce off of NPC's and misc model breakables because sticking to them requires too much effort
vec3_t velocity;
int hitTime = level.previousTime + ( level.time - level.previousTime ) * tr->fraction;
EvaluateTrajectoryDelta( &missile->s.pos, hitTime, velocity );
float dot = DotProduct( velocity, tr->plane.normal );
G_SetOrigin( missile, tr->endpos );
VectorMA( velocity, -1.6f * dot, tr->plane.normal, missile->s.pos.trDelta );
VectorMA( missile->s.pos.trDelta, 10, tr->plane.normal, missile->s.pos.trDelta );
missile->s.pos.trTime = level.time - 10; // move a bit on the first frame
// check for stop
if ( tr->entityNum >= 0 && tr->entityNum < ENTITYNUM_WORLD &&
tr->plane.normal[2] > 0.7 && missile->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7
{
missile->nextthink = level.time + 100;
}
else
{
// fall till we hit the ground
missile->s.pos.trType = TR_GRAVITY;
}
return; // don't stick yet
}
if ( missile->e_TouchFunc != touchF_NULL )
{
GEntity_TouchFunc( missile, other, tr );
}
G_AddEvent( missile, EV_MISSILE_STICK, 0 );
if ( other->s.eType == ET_MOVER || other->e_DieFunc == dieF_funcBBrushDie || other->e_DieFunc == dieF_funcGlassDie)
{
// movers and breakable brushes need extra info...so sticky missiles can ride lifts and blow up when the thing they are attached to goes away.
missile->s.groundEntityNum = tr->entityNum;
}
}
/*
================
G_ReflectMissile
Reflect the missile roughly back at it's owner
================
*/
extern gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot );
void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward )
{
vec3_t bounce_dir;
int i;
float speed;
qboolean reflected = qfalse;
gentity_t *owner = ent;
if ( ent->owner )
{
owner = ent->owner;
}
//save the original speed
speed = VectorNormalize( missile->s.pos.trDelta );
if ( ent && owner && owner->client && !owner->client->ps.saberInFlight &&
(owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 || (owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&!Q_irand( 0, 3 )) ) )
{//if high enough defense skill and saber in-hand (100% at level 3, 25% at level 2, 0% at level 1), reflections are perfectly deflected toward an enemy
gentity_t *enemy;
if ( owner->enemy && Q_irand( 0, 3 ) )
{//toward current enemy 75% of the time
enemy = owner->enemy;
}
else
{//find another enemy
enemy = Jedi_FindEnemyInCone( owner, owner->enemy, 0.3f );
}
if ( enemy )
{
vec3_t bullseye;
CalcEntitySpot( enemy, SPOT_HEAD, bullseye );
bullseye[0] += Q_irand( -4, 4 );
bullseye[1] += Q_irand( -4, 4 );
bullseye[2] += Q_irand( -16, 4 );
VectorSubtract( bullseye, missile->currentOrigin, bounce_dir );
VectorNormalize( bounce_dir );
if ( !PM_SaberInParry( owner->client->ps.saberMove )
&& !PM_SaberInReflect( owner->client->ps.saberMove )
&& !PM_SaberInIdle( owner->client->ps.saberMove ) )
{//a bit more wild
if ( PM_SaberInAttack( owner->client->ps.saberMove )
|| PM_SaberInTransitionAny( owner->client->ps.saberMove )
|| PM_SaberInSpecialAttack( owner->client->ps.torsoAnim ) )
{//moderately more wild
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.2f, 0.2f );
}
}
else
{//mildly more wild
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.1f, 0.1f );
}
}
}
VectorNormalize( bounce_dir );
reflected = qtrue;
}
}
if ( !reflected )
{
if ( missile->owner && missile->s.weapon != WP_SABER )
{//bounce back at them if you can
VectorSubtract( missile->owner->currentOrigin, missile->currentOrigin, bounce_dir );
VectorNormalize( bounce_dir );
}
else
{
vec3_t missile_dir;
VectorSubtract( ent->currentOrigin, missile->currentOrigin, missile_dir );
VectorCopy( missile->s.pos.trDelta, bounce_dir );
VectorScale( bounce_dir, DotProduct( forward, missile_dir ), bounce_dir );
VectorNormalize( bounce_dir );
}
if ( owner->s.weapon == WP_SABER && owner->client )
{//saber
if ( owner->client->ps.saberInFlight )
{//reflecting off a thrown saber is totally wild
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.8f, 0.8f );
}
}
else if ( owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] <= FORCE_LEVEL_1 )
{// at level 1
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.4f, 0.4f );
}
}
else
{// at level 2
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.2f, 0.2f );
}
}
if ( !PM_SaberInParry( owner->client->ps.saberMove )
&& !PM_SaberInReflect( owner->client->ps.saberMove )
&& !PM_SaberInIdle( owner->client->ps.saberMove ) )
{//a bit more wild
if ( PM_SaberInAttack( owner->client->ps.saberMove )
|| PM_SaberInTransitionAny( owner->client->ps.saberMove )
|| PM_SaberInSpecialAttack( owner->client->ps.torsoAnim ) )
{//really wild
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.3f, 0.3f );
}
}
else
{//mildly more wild
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.1f, 0.1f );
}
}
}
}
else
{//some other kind of reflection
for ( i = 0; i < 3; i++ )
{
bounce_dir[i] += Q_flrand( -0.2f, 0.2f );
}
}
}
VectorNormalize( bounce_dir );
VectorScale( bounce_dir, speed, missile->s.pos.trDelta );
#ifdef _DEBUG
assert( !Q_isnan(missile->s.pos.trDelta[0])&&!Q_isnan(missile->s.pos.trDelta[1])&&!Q_isnan(missile->s.pos.trDelta[2]));
#endif// _DEBUG
missile->s.pos.trTime = level.time - 10; // move a bit on the very first frame
VectorCopy( missile->currentOrigin, missile->s.pos.trBase );
if ( missile->s.weapon != WP_SABER )
{//you are mine, now!
if ( !missile->lastEnemy )
{//remember who originally shot this missile
missile->lastEnemy = missile->owner;
}
missile->owner = owner;
}
if ( missile->s.weapon == WP_ROCKET_LAUNCHER )
{//stop homing
missile->e_ThinkFunc = thinkF_NULL;
}
}
/*
================
G_BounceRollMissile
================
*/
void G_BounceRollMissile( gentity_t *ent, trace_t *trace )
{
vec3_t velocity, normal;
float dot, speedXY, velocityZ, normalZ;
int hitTime;
// reflect the velocity on the trace plane
hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
//Do horizontal
//FIXME: Need to roll up, down slopes
velocityZ = velocity[2];
velocity[2] = 0;
speedXY = VectorLength( velocity );//friction
VectorCopy( trace->plane.normal, normal );
normalZ = normal[2];
normal[2] = 0;
dot = DotProduct( velocity, normal );
VectorMA( velocity, -2*dot, normal, ent->s.pos.trDelta );
//now do the z reflection
//FIXME: Bobbles when it stops
VectorSet( velocity, 0, 0, velocityZ );
VectorSet( normal, 0, 0, normalZ );
dot = DotProduct( velocity, normal )*-1;
if ( dot > 10 )
{
ent->s.pos.trDelta[2] = dot*0.3f;//not very bouncy
}
else
{
ent->s.pos.trDelta[2] = 0;
}
// check for stop
if ( speedXY <= 0 )
{
G_SetOrigin( ent, trace->endpos );
VectorCopy( ent->currentAngles, ent->s.apos.trBase );
VectorClear( ent->s.apos.trDelta );
ent->s.apos.trType = TR_STATIONARY;
return;
}
//FIXME: rolling needs to match direction
VectorCopy( ent->currentAngles, ent->s.apos.trBase );
VectorCopy( ent->s.pos.trDelta, ent->s.apos.trDelta );
//remember this spot
VectorCopy( trace->endpos, ent->currentOrigin );
ent->s.pos.trTime = hitTime - 10;
VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
//VectorCopy( trace->plane.normal, ent->pos1 );
}
/*
================
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;
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_SHRAPNEL )
{
VectorScale( ent->s.pos.trDelta, 0.25f, ent->s.pos.trDelta );
ent->s.pos.trType = TR_GRAVITY;
// check for stop
if ( trace->plane.normal[2] > 0.7 && ent->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7
{
G_SetOrigin( ent, trace->endpos );
ent->nextthink = level.time + 100;
return;
}
}
else if ( ent->s.eFlags & EF_BOUNCE_HALF )
{
VectorScale( ent->s.pos.trDelta, 0.5, ent->s.pos.trDelta );
// check for stop
if ( trace->plane.normal[2] > 0.7 && ent->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7
{
if ( ent->s.weapon == WP_THERMAL )
{//roll when you "stop"
ent->s.pos.trType = TR_INTERPOLATE;
}
else
{
G_SetOrigin( ent, trace->endpos );
ent->nextthink = level.time + 500;
return;
}
}
if ( ent->s.weapon == WP_THERMAL )
{
ent->has_bounced = qtrue;
}
}
#if 0
// OLD--this looks so wrong. It looked wrong in EF. It just must be wrong.
VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin);
ent->s.pos.trTime = level.time - 10;
#else
// NEW--It would seem that we want to set our trBase to the trace endpos
// and set the trTime to the actual time of impact....
VectorAdd( trace->endpos, trace->plane.normal, ent->currentOrigin );
if ( hitTime >= level.time )
{//trace fraction must have been 1
ent->s.pos.trTime = level.time - 10;
}
else
{
ent->s.pos.trTime = hitTime - 10; // this is kinda dumb hacking, but it pushes the missile away from the impact plane a bit
}
#endif
VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
VectorCopy( trace->plane.normal, ent->pos1 );
if ( ent->s.weapon != WP_SABER
&& ent->s.weapon != WP_THERMAL
&& ent->e_clThinkFunc != clThinkF_CG_Limb
&& ent->e_ThinkFunc != thinkF_LimbThink )
{//not a saber, bouncing thermal or limb
//now you can damage the guy you came from
ent->owner = NULL;
}
}
/*
================
G_MissileImpact
================
*/
void NoghriGasCloudThink( gentity_t *self )
{
self->nextthink = level.time + FRAMETIME;
AddSightEvent( self->owner, self->currentOrigin, 200, AEL_DANGER, 50 );
if ( self->fx_time < level.time )
{
vec3_t up = {0,0,1};
G_PlayEffect( "noghri_stick/gas_cloud", self->currentOrigin, up );
self->fx_time = level.time + 250;
}
if ( level.time - self->s.time <= 2500 )
{
if ( !Q_irand( 0, 3-g_spskill->integer ) )
{
G_RadiusDamage( self->currentOrigin, self->owner, Q_irand( 1, 4 ), self->splashRadius,
self->owner, self->splashMethodOfDeath );
}
}
if ( level.time - self->s.time > 3000 )
{
G_FreeEntity( self );
}
}
void G_SpawnNoghriGasCloud( gentity_t *ent )
{//FIXME: force-pushable/dispersable?
ent->freeAfterEvent = qfalse;
ent->e_TouchFunc = touchF_NULL;
//ent->s.loopSound = G_SoundIndex( "sound/weapons/noghri/smoke.wav" );
//G_SoundOnEnt( ent, CHAN_AUTO, "sound/weapons/noghri/smoke.wav" );
G_SetOrigin( ent, ent->currentOrigin );
ent->e_ThinkFunc = thinkF_NoghriGasCloudThink;
ent->nextthink = level.time + FRAMETIME;
vec3_t up = {0,0,1};
G_PlayEffect( "noghri_stick/gas_cloud", ent->currentOrigin, up );
ent->fx_time = level.time + 250;
ent->s.time = level.time;
}
extern void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal );
extern qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod );
void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE )
{
// impact damage
if ( other->takedamage )
{
// FIXME: wrong damage direction?
if ( ent->damage )
{
vec3_t velocity;
EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
if ( VectorLength( velocity ) == 0 )
{
velocity[2] = 1; // stepped on a grenade
}
int damage = ent->damage;
if( other->client )
{
class_t npc_class = other->client->NPC_class;
// If we are a robot and we aren't currently doing the full body electricity...
if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE ||
npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE ||
npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 || //npc_class == CLASS_PROTOCOL ||//no protocol, looks odd
npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY )
{
// special droid only behaviors
if ( other->client->ps.powerups[PW_SHOCKED] < level.time + 100 )
{
// ... do the effect for a split second for some more feedback
other->s.powerups |= ( 1 << PW_SHOCKED );
other->client->ps.powerups[PW_SHOCKED] = level.time + 450;
}
//FIXME: throw some sparks off droids,too
}
}
G_Damage( other, ent, ent->owner, velocity,
impactPos, damage,
ent->dflags, ent->methodOfDeath, hitLoc);
if ( ent->s.weapon == WP_DEMP2 )
{//a hit with demp2 decloaks saboteurs
if ( other && other->client && other->client->NPC_class == CLASS_SABOTEUR )
{//FIXME: make this disabled cloak hold for some amount of time?
Saboteur_Decloak( other, Q_irand( 3000, 10000 ) );
if ( ent->methodOfDeath == MOD_DEMP2_ALT )
{//direct hit with alt disabled cloak forever
if ( other->NPC )
{//permanently disable the saboteur's cloak
other->NPC->aiFlags &= ~NPCAI_SHIELDS;
}
}
}
}
}
}
// is it cheaper in bandwidth to just remove this ent and create a new
// one, rather than changing the missile into the explosion?
//G_FreeEntity(ent);
if ( (other->takedamage && other->client ) || (ent->s.weapon == WP_FLECHETTE && other->contents&CONTENTS_LIGHTSABER) )
{
G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( normal ) );
ent->s.otherEntityNum = other->s.number;
}
else
{
G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( normal ) );
ent->s.otherEntityNum = other->s.number;
}
VectorCopy( normal, ent->pos1 );
if ( ent->owner )//&& ent->owner->s.number == 0 )
{
//Add the event
AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_SUSPICIOUS, qfalse, qtrue );
AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 );
}
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
VectorCopy( impactPos, ent->s.pos.trBase );
G_SetOrigin( ent, impactPos );
// splash damage (doesn't apply to person directly hit)
if ( ent->splashDamage )
{
G_RadiusDamage( impactPos, ent->owner, ent->splashDamage, ent->splashRadius,
other, ent->splashMethodOfDeath );
}
if ( ent->s.weapon == WP_NOGHRI_STICK )
{
G_SpawnNoghriGasCloud( ent );
}
gi.linkentity( ent );
}
//------------------------------------------------
static void G_MissileAddAlerts( gentity_t *ent )
{
//Add the event
if ( ent->s.weapon == WP_THERMAL && ((ent->delay-level.time) < 2000 || ent->s.pos.trType == TR_INTERPOLATE) )
{//a thermal about to explode or rolling
if ( (ent->delay-level.time) < 500 )
{//half a second before it explodes!
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER_GREAT, qfalse, qtrue );
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER_GREAT, 20 );
}
else
{//2 seconds until it explodes or it's rolling
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue );
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 20 );
}
}
else
{
AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED );
AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 40 );
}
}
//------------------------------------------------------
void G_MissileImpact( gentity_t *ent, trace_t *trace, int hitLoc=HL_NONE )
{
gentity_t *other;
vec3_t diff;
other = &g_entities[trace->entityNum];
if ( other == ent )
{
assert(0&&"missile hit itself!!!");
return;
}
if ( trace->plane.normal[0] == 0.0f &&
trace->plane.normal[1] == 0.0f &&
trace->plane.normal[2] == 0.0f
)
{//model moved into missile in flight probably...
trace->plane.normal[0] = -ent->s.pos.trDelta[0];
trace->plane.normal[1] = -ent->s.pos.trDelta[1];
trace->plane.normal[2] = -ent->s.pos.trDelta[2];
VectorNormalize(trace->plane.normal);
}
if ( ent->owner && (other->takedamage||other->client) )
{
if ( !ent->lastEnemy || ent->lastEnemy == ent->owner )
{//a missile that was not reflected or, if so, still is owned by original owner
if( LogAccuracyHit( other, ent->owner ) )
{
ent->owner->client->ps.persistant[PERS_ACCURACY_HITS]++;
}
if ( ent->owner->client && !ent->owner->s.number )
{
if ( W_AccuracyLoggableWeapon( ent->s.weapon, qfalse, ent->methodOfDeath ) )
{
ent->owner->client->sess.missionStats.hits++;
}
}
}
}
// check for bounce
//OR: if the surfaceParm is has a reflect property (magnetic shielding) and the missile isn't an exploding missile
qboolean bounce = (qboolean)( (!other->takedamage && (ent->s.eFlags&(EF_BOUNCE|EF_BOUNCE_HALF))) || (((trace->surfaceFlags&SURF_FORCEFIELD)||(other->flags&FL_SHIELDED))&&!ent->splashDamage&&!ent->splashRadius&&ent->s.weapon != WP_NOGHRI_STICK) );
if ( ent->dflags & DAMAGE_HEAVY_WEAP_CLASS )
{
// heavy class missiles generally never bounce.
bounce = qfalse;
}
if ( other->flags & (FL_DMG_BY_HEAVY_WEAP_ONLY | FL_SHIELDED ))
{
// Dumb assumption, but I guess we must be a shielded ion_cannon?? We should probably verify
// if it's an ion_cannon that's Heavy Weapon only, we don't want to make it shielded do we...?
if ( (!strcmp( "misc_ion_cannon", other->classname )) && (other->flags & FL_SHIELDED) )
{
// Anything will bounce off of us.
bounce = qtrue;
// Not exactly the debounce time, but rather the impact time for the shield effect...play effect for 1 second
other->painDebounceTime = level.time + 1000;
}
}
if ( ent->s.weapon == WP_DEMP2 )
{
// demp2 shots can never bounce
bounce = qfalse;
// in fact, alt-charge shots will not call the regular impact functions
if ( ent->alt_fire )
{
// detonate at the trace end
VectorCopy( trace->endpos, ent->currentOrigin );
VectorCopy( trace->plane.normal, ent->pos1 );
DEMP2_AltDetonate( ent );
return;
}
}
if ( bounce )
{
// Check to see if there is a bounce count
if ( ent->bounceCount )
{
// decrement number of bounces and then see if it should be done bouncing
if ( !(--ent->bounceCount) ) {
// He (or she) will bounce no more (after this current bounce, that is).
ent->s.eFlags &= ~( EF_BOUNCE | EF_BOUNCE_HALF );
}
}
if ( other->NPC )
{
G_Damage( other, ent, ent->owner, ent->currentOrigin, ent->s.pos.trDelta, 0, DAMAGE_NO_DAMAGE, MOD_UNKNOWN );
}
G_BounceMissile( ent, trace );
if ( ent->owner )//&& ent->owner->s.number == 0 )
{
G_MissileAddAlerts( ent );
}
G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, (qboolean)(trace->entityNum==ENTITYNUM_WORLD) );
return;
}
// I would glom onto the EF_BOUNCE code section above, but don't feel like risking breaking something else
if ( (!other->takedamage && ( ent->s.eFlags&(EF_BOUNCE_SHRAPNEL) ) )
|| ((trace->surfaceFlags&SURF_FORCEFIELD)&&!ent->splashDamage&&!ent->splashRadius) )
{
if ( !(other->contents&CONTENTS_LIGHTSABER)
|| g_spskill->integer <= 0//on easy, it reflects all shots
|| (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots
|| (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots
)
{
G_BounceMissile( ent, trace );
if ( --ent->bounceCount < 0 )
{
ent->s.eFlags &= ~EF_BOUNCE_SHRAPNEL;
}
G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, (qboolean)(trace->entityNum==ENTITYNUM_WORLD) );
return;
}
}
if ( (!other->takedamage || (other->client && other->health <= 0))
&& ent->s.weapon == WP_THERMAL
&& !ent->alt_fire )
{//rolling thermal det - FIXME: make this an eFlag like bounce & stick!!!
//G_BounceRollMissile( ent, trace );
if ( ent->owner )//&& ent->owner->s.number == 0 )
{
G_MissileAddAlerts( ent );
}
//gi.linkentity( ent );
return;
}
// check for sticking
if ( ent->s.eFlags & EF_MISSILE_STICK )
{
if ( ent->owner )//&& ent->owner->s.number == 0 )
{
//Add the event
if ( ent->s.weapon == WP_TRIP_MINE )
{
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius/2, AEL_DISCOVERED/*AEL_DANGER*/, qfalse, qtrue );
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DISCOVERED/*AEL_DANGER*/, 60 );
/*
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue );
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 60 );
*/
}
else
{
AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );
AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 10 );
}
}
G_MissileStick( ent, other, trace );
return;
}
extern bool WP_DoingMoronicForcedAnimationForForcePowers(gentity_t *ent);
// check for hitting a lightsaber
if ( other->contents & CONTENTS_LIGHTSABER )
{
if ( other->owner && !other->owner->s.number && other->owner->client )
{
other->owner->client->sess.missionStats.saberBlocksCnt++;
}
if ( ( g_spskill->integer <= 0//on easy, it reflects all shots
|| (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots
|| (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots
)
&& (!ent->splashDamage || !ent->splashRadius) //this would be cool, though, to "bat" the thermal det away...
&& ent->s.weapon != WP_NOGHRI_STICK )//gas bomb, don't reflect
{
//FIXME: take other's owner's FP_SABER_DEFENSE into account here somehow?
if ( !other->owner || !other->owner->client || other->owner->client->ps.saberInFlight
|| (InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) &&
!WP_DoingMoronicForcedAnimationForForcePowers(other)) )//other->owner->s.number != 0 ||
{//Jedi cannot block shots from behind!
int blockChance = 0;
switch ( other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] )
{//level 1 reflects 50% of the time, level 2 reflects 75% of the time
case FORCE_LEVEL_3:
blockChance = 10;
break;
case FORCE_LEVEL_2:
blockChance = 3;
break;
case FORCE_LEVEL_1:
blockChance = 1;
break;
}
if ( blockChance && (other->owner->client->ps.forcePowersActive&(1<<FP_SPEED)) )
{//in in force speed, better chance of deflecting the shot
blockChance += other->owner->client->ps.forcePowerLevel[FP_SPEED]*2;
}
if ( Q_irand( 0, blockChance ) )
{
VectorSubtract(ent->currentOrigin, other->currentOrigin, diff);
VectorNormalize(diff);
G_ReflectMissile( other, ent, diff);
if ( other->owner && other->owner->client )
{
other->owner->client->ps.saberEventFlags |= SEF_DEFLECTED;
}
//do the effect
VectorCopy( ent->s.pos.trDelta, diff );
VectorNormalize( diff );
G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal );
return;
}
}
}
else
{//still do the bounce effect
G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal );
}
}
G_MissileImpacted( ent, other, trace->endpos, trace->plane.normal, hitLoc );
}
/*
================
G_ExplodeMissile
Explode a missile without an impact
================
*/
void G_ExplodeMissile( gentity_t *ent )
{
vec3_t dir;
vec3_t origin;
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;
if ( ent->owner )//&& ent->owner->s.number == 0 )
{
//Add the event
AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, qfalse, qtrue );//FIXME: are we on ground or not?
AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 100 );
}
/* ent->s.eType = ET_GENERAL;
G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );
ent->freeAfterEvent = qtrue;*/
// splash damage
if ( ent->splashDamage )
{
G_RadiusDamage( ent->currentOrigin, ent->owner, ent->splashDamage, ent->splashRadius, NULL
, ent->splashMethodOfDeath );
}
G_FreeEntity(ent);
//gi.linkentity( ent );
}
void G_RunStuckMissile( gentity_t *ent )
{
if ( ent->takedamage )
{
if ( ent->s.groundEntityNum >= 0 && ent->s.groundEntityNum < ENTITYNUM_WORLD )
{
gentity_t *other = &g_entities[ent->s.groundEntityNum];
if ( (!VectorCompare( vec3_origin, other->s.pos.trDelta ) && other->s.pos.trType != TR_STATIONARY) ||
(!VectorCompare( vec3_origin, other->s.apos.trDelta ) && other->s.apos.trType != TR_STATIONARY) )
{//thing I stuck to is moving or rotating now, kill me
G_Damage( ent, other, other, NULL, NULL, 99999, 0, MOD_CRUSH );
return;
}
}
}
// check think function
G_RunThink( ent );
}
/*
==================
G_GroundTrace
==================
*/
int G_GroundTrace( gentity_t *ent, pml_t *pPml )
{
vec3_t point;
trace_t trace;
point[0] = ent->currentOrigin[0];
point[1] = ent->currentOrigin[1];
point[2] = ent->currentOrigin[2] - 0.25;
gi.trace ( &trace, ent->currentOrigin, ent->mins, ent->maxs, point, ent->s.number, ent->clipmask, (EG2_Collision)0, 0 );
pPml->groundTrace = trace;
// do something corrective if the trace starts in a solid...
if ( trace.allsolid )
{
pPml->groundPlane = qfalse;
pPml->walking = qfalse;
return ENTITYNUM_NONE;
}
// if the trace didn't hit anything, we are in free fall
if ( trace.fraction == 1.0 )
{
pPml->groundPlane = qfalse;
pPml->walking = qfalse;
return ENTITYNUM_NONE;
}
// check if getting thrown off the ground
if ( ent->s.pos.trDelta[2] > 0 && DotProduct( ent->s.pos.trDelta, trace.plane.normal ) > 10 )
{
pPml->groundPlane = qfalse;
pPml->walking = qfalse;
return ENTITYNUM_NONE;
}
// slopes that are too steep will not be considered onground
if ( trace.plane.normal[2] < MIN_WALK_NORMAL )
{
pPml->groundPlane = qtrue;
pPml->walking = qfalse;
return ENTITYNUM_NONE;
}
pPml->groundPlane = qtrue;
pPml->walking = qtrue;
/*
if ( ent->s.groundEntityNum == ENTITYNUM_NONE )
{
// just hit the ground
}
*/
return trace.entityNum;
}
void G_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce )
{
float backoff;
float change;
int i;
backoff = DotProduct (in, normal);
if ( backoff < 0 ) {
backoff *= overbounce;
} else {
backoff /= overbounce;
}
for ( i=0 ; i<3 ; i++ )
{
change = normal[i]*backoff;
out[i] = in[i] - change;
}
}
/*
==================
G_RollMissile
reworking the rolling object code,
still needs to stop bobbling up & down,
need to get roll angles right,
and need to maybe make the transfer of velocity happen on impacts?
Also need bounce sound for bounces off a floor.
Also need to not bounce as much off of enemies
Also gets stuck inside thrower if looking down when thrown
==================
*/
#define MAX_CLIP_PLANES 5
#define BUMPCLIP 1.5f
void G_RollMissile( gentity_t *ent )
{
int bumpcount, numbumps;
vec3_t dir;
float d;
int numplanes;
vec3_t planes[MAX_CLIP_PLANES];
vec3_t primal_velocity;
vec3_t clipVelocity;
int i, j, k;
trace_t trace;
vec3_t end;
float time_left;
float into;
vec3_t endVelocity;
vec3_t endClipVelocity;
pml_t objPML;
float bounceAmt = BUMPCLIP;
gentity_t *hitEnt = NULL;
memset( &objPML, 0, sizeof( objPML ) );
G_GroundTrace( ent, &objPML );
objPML.frametime = (level.time - level.previousTime)*0.001;
numbumps = 4;
VectorCopy ( ent->s.pos.trDelta, primal_velocity );
VectorCopy( ent->s.pos.trDelta, endVelocity );
endVelocity[2] -= g_gravity->value * objPML.frametime;
ent->s.pos.trDelta[2] = ( ent->s.pos.trDelta[2] + endVelocity[2] ) * 0.5;
primal_velocity[2] = endVelocity[2];
if ( objPML.groundPlane )
{//FIXME: never happens!
// slide along the ground plane
G_ClipVelocity( ent->s.pos.trDelta, objPML.groundTrace.plane.normal, ent->s.pos.trDelta, BUMPCLIP );
VectorScale( ent->s.pos.trDelta, 0.9f, ent->s.pos.trDelta );
}
time_left = objPML.frametime;
// never turn against the ground plane
if ( objPML.groundPlane )
{
numplanes = 1;
VectorCopy( objPML.groundTrace.plane.normal, planes[0] );
}
else
{
numplanes = 0;
}
// never turn against original velocity
/*
VectorNormalize2( ent->s.pos.trDelta, planes[numplanes] );
numplanes++;
*/
for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ )
{
// calculate position we are trying to move to
VectorMA( ent->currentOrigin, time_left, ent->s.pos.trDelta, end );
// see if we can make it there
gi.trace ( &trace, ent->currentOrigin, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 );
//TEMP HACK:
//had to move this up above the trace.allsolid check now that for some reason ghoul2 impacts tell me I'm allsolid?!
//this needs to be fixed, really
if ( trace.entityNum < ENTITYNUM_WORLD )
{//hit another ent
hitEnt = &g_entities[trace.entityNum];
if ( hitEnt && (hitEnt->takedamage || (hitEnt->contents&CONTENTS_LIGHTSABER) ) )
{
G_MissileImpact( ent, &trace );
if ( ent->s.eType == ET_GENERAL )
{//exploded
return;
}
}
}
if ( trace.allsolid )
{
// entity is completely trapped in another solid
//FIXME: this happens a lot now when we hit a G2 ent... WTF?
ent->s.pos.trDelta[2] = 0; // don't build up falling damage, but allow sideways acceleration
return;// qtrue;
}
if ( trace.fraction > 0 )
{
// actually covered some distance
VectorCopy( trace.endpos, ent->currentOrigin );
}
if ( trace.fraction == 1 )
{
break; // moved the entire distance
}
//pm->ps->pm_flags |= PMF_BUMPED;
// save entity for contact
//PM_AddTouchEnt( trace.entityNum );
//Hit it
/*
if ( PM_ClientImpact( trace.entityNum, qtrue ) )
{
continue;
}
*/
time_left -= time_left * trace.fraction;
if ( numplanes >= MAX_CLIP_PLANES )
{
// this shouldn't really happen
VectorClear( ent->s.pos.trDelta );
return;// qtrue;
}
//
// if this is the same plane we hit before, nudge velocity
// out along it, which fixes some epsilon issues with
// non-axial planes
//
for ( i = 0 ; i < numplanes ; i++ )
{
if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 )
{
VectorAdd( trace.plane.normal, ent->s.pos.trDelta, ent->s.pos.trDelta );
break;
}
}
if ( i < numplanes )
{
continue;
}
VectorCopy( trace.plane.normal, planes[numplanes] );
numplanes++;
//
// modify velocity so it parallels all of the clip planes
//
if ( &g_entities[trace.entityNum] != NULL && g_entities[trace.entityNum].client )
{//hit a person, bounce off much less
bounceAmt = OVERCLIP;
}
else
{
bounceAmt = BUMPCLIP;
}
// find a plane that it enters
for ( i = 0 ; i < numplanes ; i++ )
{
into = DotProduct( ent->s.pos.trDelta, planes[i] );
if ( into >= 0.1 )
{
continue; // move doesn't interact with the plane
}
// see how hard we are hitting things
if ( -into > pml.impactSpeed )
{
pml.impactSpeed = -into;
}
// slide along the plane
G_ClipVelocity( ent->s.pos.trDelta, planes[i], clipVelocity, bounceAmt );
// slide along the plane
G_ClipVelocity( endVelocity, planes[i], endClipVelocity, bounceAmt );
// see if there is a second plane that the new move enters
for ( j = 0 ; j < numplanes ; j++ )
{
if ( j == i )
{
continue;
}
if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 )
{
continue; // move doesn't interact with the plane
}
// try clipping the move to the plane
G_ClipVelocity( clipVelocity, planes[j], clipVelocity, bounceAmt );
G_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, bounceAmt );
// see if it goes back into the first clip plane
if ( DotProduct( clipVelocity, planes[i] ) >= 0 )
{
continue;
}
// slide the original velocity along the crease
CrossProduct (planes[i], planes[j], dir);
VectorNormalize( dir );
d = DotProduct( dir, ent->s.pos.trDelta );
VectorScale( dir, d, clipVelocity );
CrossProduct (planes[i], planes[j], dir);
VectorNormalize( dir );
d = DotProduct( dir, endVelocity );
VectorScale( dir, d, endClipVelocity );
// see if there is a third plane the the new move enters
for ( k = 0 ; k < numplanes ; k++ )
{
if ( k == i || k == j )
{
continue;
}
if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 )
{
continue; // move doesn't interact with the plane
}
// stop dead at a triple plane interaction
VectorClear( ent->s.pos.trDelta );
return;// qtrue;
}
}
// if we have fixed all interactions, try another move
VectorCopy( clipVelocity, ent->s.pos.trDelta );
VectorCopy( endClipVelocity, endVelocity );
break;
}
VectorScale( endVelocity, 0.975f, endVelocity );
}
VectorCopy( endVelocity, ent->s.pos.trDelta );
// don't change velocity if in a timer (FIXME: is this correct?)
/*
if ( pm->ps->pm_time )
{
VectorCopy( primal_velocity, ent->s.pos.trDelta );
}
*/
return;// ( bumpcount != 0 );
}
/*
================
G_RunMissile
================
*/
void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg );
void G_RunMissile( gentity_t *ent )
{
vec3_t oldOrg;
trace_t tr;
int trHitLoc=HL_NONE;
if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) )
{//in a sand creature's mouth
if ( ent->activator )
{
mdxaBone_t boltMatrix;
// Getting the bolt here
//in hand
vec3_t scAngles = {0};
scAngles[YAW] = ent->activator->currentAngles[YAW];
gi.G2API_GetBoltMatrix( ent->activator->ghoul2, ent->activator->playerModel, ent->activator->gutBolt,
&boltMatrix, scAngles, ent->activator->currentOrigin, (cg.time?cg.time:level.time),
NULL, ent->activator->s.modelScale );
// Storing ent position, bolt position, and bolt axis
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->currentOrigin );
G_SetOrigin( ent, ent->currentOrigin );
}
// check think function
G_RunThink( ent );
return;
}
VectorCopy( ent->currentOrigin, oldOrg );
// get current position
if ( ent->s.pos.trType == TR_INTERPOLATE )
{//rolling missile?
//FIXME: WTF?!! Sticks to stick missiles?
//FIXME: they stick inside the player
G_RollMissile( ent );
if ( ent->s.eType != ET_GENERAL )
{//didn't explode
VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
gi.trace( &tr, oldOrg, ent->mins, ent->maxs, ent->currentOrigin, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 );
if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) )
{
//VectorCopy( ent->currentAngles, ent->s.apos.trBase );
VectorClear( ent->s.apos.trDelta );
}
else
{
vec3_t ang, fwdDir, rtDir;
float speed;
ent->s.apos.trType = TR_INTERPOLATE;
VectorSet( ang, 0, ent->s.apos.trBase[1], 0 );
AngleVectors( ang, fwdDir, rtDir, NULL );
speed = VectorLength( ent->s.pos.trDelta )*4;
//HMM, this works along an axis-aligned dir, but not along diagonals
//This is because when roll gets to 90, pitch becomes yaw, and vice-versa
//Maybe need to just set the angles directly?
ent->s.apos.trDelta[0] = DotProduct( fwdDir, ent->s.pos.trDelta );
ent->s.apos.trDelta[1] = 0;//never spin!
ent->s.apos.trDelta[2] = DotProduct( rtDir, ent->s.pos.trDelta );
VectorNormalize( ent->s.apos.trDelta );
VectorScale( ent->s.apos.trDelta, speed, ent->s.apos.trDelta );
ent->s.apos.trTime = level.previousTime;
}
}
}
else
{
vec3_t origin;
EvaluateTrajectory( &ent->s.pos, level.time, origin );
// trace a line from the previous position to the current position,
// ignoring interactions with the missile owner
gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin,
ent->owner ? ent->owner->s.number : ent->s.number, ent->clipmask, G2_COLLIDE, 10 );
if ( tr.entityNum != ENTITYNUM_NONE )
{
gentity_t *other = &g_entities[tr.entityNum];
// check for hitting a lightsaber
if ( other->contents & CONTENTS_LIGHTSABER )
{//hit a lightsaber bbox
if ( other->owner
&& other->owner->client
&& !other->owner->client->ps.saberInFlight
&& ( Q_irand( 0, (other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) ) == 0
|| !InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) ) )//other->owner->s.number == 0 &&
{//Jedi cannot block shots from behind!
//re-trace from here, ignoring the lightsaber
gi.trace( &tr, tr.endpos, ent->mins, ent->maxs, origin, tr.entityNum, ent->clipmask, G2_RETURNONHIT, 10 );
}
}
}
VectorCopy( tr.endpos, ent->currentOrigin );
}
// get current angles
VectorMA( ent->s.apos.trBase, (level.time - ent->s.apos.trTime) * 0.001, ent->s.apos.trDelta, ent->s.apos.trBase );
//FIXME: Rolling things hitting G2 polys is weird
///////////////////////////////////////////////////////
//? if ( tr.fraction != 1 )
{
// did we hit or go near a Ghoul2 model?
// qboolean hitModel = qfalse;
for (int i=0; i < MAX_G2_COLLISIONS; i++)
{
if (tr.G2CollisionMap[i].mEntityNum == -1)
{
break;
}
CCollisionRecord &coll = tr.G2CollisionMap[i];
gentity_t *hitEnt = &g_entities[coll.mEntityNum];
// process collision records here...
// make sure we only do this once, not for all the entrance wounds we might generate
if ((coll.mFlags & G2_FRONTFACE)/* && !(hitModel)*/ && hitEnt->health)
{
if (trHitLoc==HL_NONE)
{
G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, NULL, NULL, ent->methodOfDeath );
}
break; // NOTE: the way this whole section was working, it would only get inside of this IF once anyway, might as well break out now
}
}
}
/////////////////////////////////////////////////////////
if ( tr.startsolid )
{
tr.fraction = 0;
}
gi.linkentity( ent );
if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) )
{//stuck missiles should check some special stuff
G_RunStuckMissile( ent );
return;
}
// check think function
G_RunThink( ent );
if ( ent->s.eType != ET_MISSILE )
{
return; // exploded
}
if ( ent->mass )
{
G_MoverTouchPushTriggers( ent, oldOrg );
}
/*
if ( !(ent->s.eFlags & EF_TELEPORT_BIT) )
{
G_MoverTouchTeleportTriggers( ent, oldOrg );
if ( ent->s.eFlags & EF_TELEPORT_BIT )
{//was teleported
return;
}
}
else
{
ent->s.eFlags &= ~EF_TELEPORT_BIT;
}
*/
AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 );//wakes them up when see a shot passes in front of them
if ( !Q_irand( 0, 10 ) )
{//not so often...
if ( ent->splashDamage && ent->splashRadius )
{//I'm an exploder, let people around me know danger is coming
if ( ent->s.weapon == WP_TRIP_MINE )
{//???
}
else
{
if ( ent->s.weapon == WP_ROCKET_LAUNCHER && ent->e_ThinkFunc == thinkF_rocketThink )
{//homing rocket- run like hell!
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER_GREAT, 50 );
}
else
{
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER, 50 );
}
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER );
}
}
else
{//makes them run from near misses
AddSightEvent( ent->owner, ent->currentOrigin, 48, AEL_DANGER, 50 );
}
}
if ( tr.fraction == 1 )
{
if ( ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_INTERPOLATE )
{//a rolling thermal that didn't hit anything
G_MissileAddAlerts( ent );
}
return;
}
// never explode or bounce on sky
if ( tr.surfaceFlags & SURF_NOIMPACT )
{
G_FreeEntity( ent );
return;
}
G_MissileImpact( ent, &tr, trHitLoc );
}