jedioutcast/code/game/g_missile.cpp

1348 lines
40 KiB
C++
Raw Normal View History

2013-04-04 14:52:42 +00:00
// leave this line at the top for all g_xxxx.cpp files...
#include "g_headers.h"
#include "g_local.h"
#include "g_functions.h"
#include "wp_saber.h"
#include "bg_local.h"
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 G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir );
2013-04-04 18:02:27 +00:00
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 );
2013-04-04 14:52:42 +00:00
//-------------------------------------------------------------------------
void G_MissileBounceEffect( 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 )
{
2013-04-04 18:02:27 +00:00
case WP_BOWCASTER:
G_PlayEffect( "bowcaster/deflect", ent->currentOrigin, dir );
break;
2013-04-04 14:52:42 +00:00
case WP_BLASTER:
case WP_BRYAR_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;
}
}
//-------------------------------------------------------------------------
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
2013-04-04 18:02:27 +00:00
vec3_t dir;
2013-04-04 14:52:42 +00:00
G_SetOrigin( missile, tr->endpos );
2013-04-04 18:02:27 +00:00
// get our bounce direction, not accurate, but it simplifies things.
dir[0] = missile->s.pos.trDelta[0] + crandom() * 40.0f; // randomize bounce a bit
dir[1] = missile->s.pos.trDelta[1] + crandom() * 40.0f;
dir[2] = 0;//missile->s.pos.trDelta[2] * 0.1f;// kill upward thrust
VectorNormalize( dir );
VectorScale( dir, -72, missile->s.pos.trDelta );
missile->s.pos.trTime = level.time - 100; // move a bit on the first frame
2013-04-04 14:52:42 +00:00
// 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 )) ) )
2013-04-04 18:02:27 +00:00
{//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
2013-04-04 14:52:42 +00:00
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 );
2013-04-04 18:02:27 +00:00
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 );
2013-04-04 14:52:42 +00:00
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 );
}
2013-04-04 18:02:27 +00:00
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 );
}
2013-04-04 14:52:42 +00:00
}
}
VectorNormalize( bounce_dir );
VectorScale( bounce_dir, speed, missile->s.pos.trDelta );
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....
2013-04-04 18:02:27 +00:00
VectorAdd( trace->endpos, trace->plane.normal, ent->currentOrigin );
2013-04-04 14:52:42 +00:00
if ( hitTime >= level.time )
{//trace fraction must have been 1
ent->s.pos.trTime = level.time - 10;
}
else
{
2013-04-04 18:02:27 +00:00
ent->s.pos.trTime = hitTime - 10; // this is kinda dumb hacking, but it pushes the missile away from the impact plane a bit
2013-04-04 14:52:42 +00:00
}
#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 )
{//not a saber
//now you can damage the guy you came from
ent->owner = NULL;
}
}
/*
================
G_MissileImpact
================
*/
extern void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missleBlock );
extern void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal );
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
}
if ( ent->owner )
{
if( LogAccuracyHit( other, ent->owner ) )
{
ent->owner->client->ps.persistant[PERS_ACCURACY_HITS]++;
}
}
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_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 ||
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;
}
}
}
G_Damage( other, ent, ent->owner, velocity,
impactPos, damage,
ent->dflags, ent->methodOfDeath, hitLoc);
if ( ent->owner && !ent->owner->s.number && ent->owner->client )
{
ent->owner->client->sess.missionStats.hits++;
}
}
}
// 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 )
{
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;
}
if ( ent->owner )//&& ent->owner->s.number == 0 )
{
//Add the event
AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_SUSPICIOUS );
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 );
}
gi.linkentity( ent );
}
//------------------------------------------------
static void G_MissileAddAlerts( gentity_t *ent )
{
//Add the event
if ( ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_STATIONARY )
{
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER );
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;
}
// check for bounce
//OR: if the surfaceParm is has a reflect property (magnetic shielding) and the missile isn't an exploding missile
qboolean bounce = !!( (!other->takedamage && (ent->s.eFlags&(EF_BOUNCE|EF_BOUNCE_HALF))) || (((trace->surfaceFlags&SURF_FORCEFIELD)||(other->flags&FL_SHIELDED))&&!ent->splashDamage&&!ent->splashRadius) );
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 );
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) )
{
G_BounceMissile( ent, trace );
if ( --ent->bounceCount < 0 )
{
ent->s.eFlags &= ~EF_BOUNCE_SHRAPNEL;
}
G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal );
return;
}
if ( !other->takedamage && 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*/ );
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DISCOVERED/*AEL_DANGER*/, 60 );
/*
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER );
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 60 );
*/
}
else
{
AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED );
AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 10 );
}
}
G_MissileStick( ent, other, trace );
return;
}
// check for hitting a lightsaber
2013-04-04 18:02:27 +00:00
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
)
&& (!ent->splashDamage || !ent->splashRadius) )//this would be cool, though, to "bat" the thermal det away...
2013-04-04 14:52:42 +00:00
{
//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 ) )//other->owner->s.number != 0 ||
{//Jedi cannot block shots from behind!
2013-04-04 18:02:27 +00:00
if ( (other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 3 ))
||(other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && Q_irand( 0, 1 )))
{//level 1 reflects 50% of the time, level 2 reflects 75% of the time
VectorSubtract(ent->currentOrigin, other->currentOrigin, diff);
VectorNormalize(diff);
//FIXME: take other's owner's FP_SABER_DEFENSE into account here somehow?
G_ReflectMissile( other, ent, diff);
//WP_SaberBlock( other, ent->currentOrigin, qtrue );
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_MissileBounceEffect( ent, trace->endpos, trace->plane.normal );
return;
2013-04-04 14:52:42 +00:00
}
}
}
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 );
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 );
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;
}
/*
==================
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
PM_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
PM_ClipVelocity( ent->s.pos.trDelta, planes[i], clipVelocity, bounceAmt );
// slide along the plane
PM_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
PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, bounceAmt );
PM_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 origin, oldOrg;
trace_t tr;
int trHitLoc=HL_NONE;
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
{
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 : ENTITYNUM_NONE, ent->clipmask, G2_RETURNONHIT, 10 );
*/
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 ( !VectorCompare( ent->mins, vec3_origin ) || !VectorCompare( ent->maxs, vec3_origin ) )
{//don't do ghoul trace if ent has size because g2 just ignores that anyway
gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin,
ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_NOCOLLIDE, 10 );
}
else
//Now we always do ghoul trace, regardless of bbox size of missile, this is presuming that non-point ghoul traces will be possible...?
{
gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, origin,
ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_RETURNONHIT, 10 );
}
*/
/*
if ( tr.fraction == 0.0f && tr.plane.normal[2] == 1.0f && origin[2] < ent->currentOrigin[2] )
{
if ( ent->s.pos.trType == TR_GRAVITY && !(ent->s.eFlags&EF_BOUNCE) && !(ent->s.eFlags&EF_BOUNCE_HALF) && ent->s.weapon == WP_THERMAL )//FIXME: EF_ROLLING
{
//FIXME: Needs to stop sometime!
ent->s.pos.trType = TR_LINEAR;
ent->s.pos.trDelta[2] = 0;
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 : ENTITYNUM_NONE, ent->clipmask | CONTENTS_GHOUL2 );
if ( tr.fraction == 1.0f )
{
VectorCopy( tr.endpos, ent->s.pos.trBase );
VectorScale( ent->s.pos.trDelta, 0.975f, ent->s.pos.trDelta );
ent->s.pos.trTime = level.time;
}
ent->s.pos.trType = TR_GRAVITY;
}
}
*/
if ( tr.entityNum != ENTITYNUM_NONE && &g_entities[tr.entityNum] != NULL )
{
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 && !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];
/* Sorry...this was just getting in the way....
#if _DEBUG
vec3_t delta;
VectorSubtract(origin, coll.mCollisionPosition, delta);
VectorNormalize(delta);
VectorScale(delta, 30, delta);
if (coll.mFlags & G2_BACKFACE)
{
VectorAdd(delta, coll.mCollisionPosition, delta);
G_DebugLine(coll.mCollisionPosition, delta, 10000, 0x00ff0000, qtrue);
}
else
{
VectorSubtract(coll.mCollisionPosition, delta, delta);
G_DebugLine(coll.mCollisionPosition, delta, 10000, 0x0000ff00, qtrue);
}
//loadsavecrash
// VectorCopy(hitEnt->mins, hitEnt->s.mins);
// VectorCopy(hitEnt->maxs, hitEnt->s.maxs);
#endif
*/
// 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)
{
// create a new surface using the details of what poly/surface/model we hit
// int newSurface = gi.G2API_AddSurface(&hitEnt->ghoul2[coll.mModelIndex], coll.mSurfaceIndex, coll.mPolyIndex, coll.mBarycentricI, coll.mBarycentricJ, 10);
// surfaceInfo_t *newSuf = &hitEnt->ghoul2[coll.mModelIndex].mSlist[newSurface];
// attach a bolt to this surface
// int newBolt = gi.G2API_AddBoltSurfNum(&hitEnt->ghoul2[coll.mModelIndex], newSurface);
// now attach an effect to this new bolt
// Bolting on this effect just looks dumb and adds lots of unnecessary effects to the scene
//
// G_PlayEffect( G_EffectIndex( "blaster/smoke_bolton") , coll.mModelIndex, newBolt, hitEnt->s.number);
//
//
// G_SetBoltSurfaceRemoval(coll.mEntityNum, coll.mModelIndex, newBolt, newSurface, 10000);
// hitModel = qtrue;
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 );
}
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 )
{
return;
}
// never explode or bounce on sky
if ( tr.surfaceFlags & SURF_NOIMPACT )
{
G_FreeEntity( ent );
return;
}
G_MissileImpact( ent, &tr, trHitLoc );
}