sof2-sdk/code/game/g_missile.c
2002-07-15 00:00:00 +00:00

624 lines
16 KiB
C

// Copyright (C) 2001-2002 Raven Software
//
#include "g_local.h"
int G_MultipleDamageLocations(int hitLocation);
#define MISSILE_PRESTEP_TIME 50
/*
================
G_BounceMissile
================
*/
void G_BounceMissile( gentity_t *ent, trace_t *trace )
{
vec3_t velocity;
float dot;
int hitTime;
// nothing to do if already stationary
if ( ent->s.pos.trType == TR_STATIONARY )
{
return;
}
// 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 )
{
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;
}
}
else if ( ent->s.eFlags & EF_BOUNCE_SCALE )
{
// IF it hit a client then barely bounce off of them since they are "soft"
if ( trace->entityNum < MAX_CLIENTS )
{
VectorScale( ent->s.pos.trDelta, 0.04f, ent->s.pos.trDelta );
// Make sure the grenade doesnt continuously collide with teh player it hit
ent->target_ent = &g_entities[trace->entityNum];
}
else
{
VectorScale( ent->s.pos.trDelta, ent->bounceScale, 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 );
if ( ent->parent && ent->parent->client )
{
gentity_t* nearby;
// Find someone on the opposite team near wher ethe grenade landed
nearby = G_FindNearbyClient ( trace->endpos, ent->parent->client->sess.team==TEAM_RED?TEAM_BLUE:TEAM_RED, 800, NULL );
if ( nearby )
{
// Make sure there is someone around to hear them scream
nearby = G_FindNearbyClient ( trace->endpos, nearby->client->sess.team, 800, nearby );
G_VoiceGlobal ( nearby, "grenade", qtrue );
}
}
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_AddEvent( ent, EV_GRENADE_BOUNCE, trace->surfaceFlags& MATERIAL_MASK );
}
/*
================
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;
G_AddEvent( ent, EV_MISSILE_MISS, (DirToByte( dir ) << MATERIAL_BITS) | MATERIAL_NONE);
ent->freeAfterEvent = qtrue;
// All grenade explosions are now broadcast to ensure that fire and smoke is always seen
ent->r.svFlags |= SVF_BROADCAST;
// splash damage
if ( ent->splashDamage )
{
if (ent->dflags & DAMAGE_AREA_DAMAGE)
{
// do damage over time rather than instantly
G_CreateDamageArea ( ent->r.currentOrigin, ent->parent, ent->splashDamage*0.05f,ent->splashRadius, 8000,ent->methodOfDeath );
// do some instant damage
G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->damage, ent->splashRadius, ent,
1, ent->dflags, ent->splashMethodOfDeath );
}
else
{ // normal radius of effect damage
G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent,
1, ent->dflags, ent->splashMethodOfDeath );
}
}
trap_LinkEntity( ent );
}
/*
================
G_GrenadeThink
Marks the grenade ready to explode
================
*/
void G_GrenadeThink ( gentity_t* ent )
{
ent->s.eFlags |= EF_EXPLODE;
}
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, HL_NONE );
return;
}
}
}
// check think function
G_RunThink( ent );
}
/*
================
G_BounceProjectile
================
*/
void G_BounceProjectile( vec3_t start, vec3_t impact, vec3_t dir, vec3_t endout ) {
vec3_t v, newv;
float dot;
VectorSubtract( impact, start, v );
dot = DotProduct( v, dir );
VectorMA( v, -2*dot, dir, newv );
VectorNormalize(newv);
VectorMA(impact, 8192, newv, endout);
}
/*
================
G_CreateMissile
================
*/
gentity_t* G_CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, attackType_t attack )
{
gentity_t *missile;
missile = G_Spawn();
missile->nextthink = level.time + life;
missile->think = G_FreeEntity;
missile->s.eType = ET_MISSILE;
missile->r.svFlags = SVF_USE_CURRENT_ORIGIN;
missile->parent = owner;
missile->r.ownerNum = owner->s.number;
if ( attack == ATTACK_ALTERNATE )
{
missile->s.eFlags |= EF_ALT_FIRING;
}
missile->s.pos.trType = TR_LINEAR;
missile->s.pos.trTime = level.time;// - MISSILE_PRESTEP_TIME; // NOTENOTE This is a Quake 3 addition over JK2
missile->target_ent = NULL;
SnapVector(org);
VectorCopy( org, missile->s.pos.trBase );
VectorScale( dir, vel, missile->s.pos.trDelta );
VectorCopy( org, missile->r.currentOrigin);
SnapVector(missile->s.pos.trDelta);
return missile;
}
/*
================
G_CauseAreaDamage
================
*/
void G_CauseAreaDamage( gentity_t *ent )
{
G_RadiusDamage ( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent, 3, DAMAGE_NO_TEAMKILL, ent->methodOfDeath );
ent->s.time2--;
if ( ent->s.time2 <= 0 )
{
G_FreeEntity ( ent );
return;
}
ent->nextthink = level.time + 350;
trap_LinkEntity( ent );
}
/*
================
G_CreateDamageArea
================
*/
gentity_t* G_CreateDamageArea ( vec3_t origin, gentity_t* attacker, float damage, float radius, int duration, int mod )
{
gentity_t *damageArea;
damageArea = G_Spawn();
damageArea->nextthink = level.time + 350;
damageArea->think = G_CauseAreaDamage;
damageArea->s.eType = ET_DAMAGEAREA;
damageArea->r.svFlags = SVF_USE_CURRENT_ORIGIN;
damageArea->parent = attacker;
damageArea->r.ownerNum = attacker->s.number;
damageArea->s.pos.trType = TR_STATIONARY;
damageArea->s.pos.trTime = level.time;
damageArea->s.time2 = duration / 350;
damageArea->target_ent = NULL;
damageArea->classname = "DamageArea";
VectorSet( damageArea->r.maxs, 1, 1, 1 );
VectorScale( damageArea->r.maxs, -1, damageArea->r.mins );
damageArea->splashDamage = damage;
damageArea->splashRadius = radius;
damageArea->methodOfDeath = mod;
damageArea->dflags = DAMAGE_RADIUS;
damageArea->clipmask = MASK_SHOT;
VectorCopy( origin, damageArea->s.pos.trBase );
VectorCopy( origin, damageArea->r.currentOrigin);
SnapVector( damageArea->r.currentOrigin );
return damageArea;
}
/*
================
G_MissileImpact
================
*/
extern gentity_t *CreateWeaponPickup(vec3_t pos,weapon_t weapon);
extern int G_GetHitLocation(gentity_t *target, vec3_t ppoint, vec3_t dir );
void G_MissileImpact( gentity_t *ent, trace_t *trace )
{
gentity_t *other;
vec3_t velocity;
int d;
other = &g_entities[trace->entityNum];
d = 0;
// check for bounce
if ( ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF | EF_BOUNCE_SCALE ) ) )
{
G_BounceMissile( ent, trace );
return;
}
// impact damage
if (other->takedamage)
{
// FIXME: wrong damage direction?
BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
if ( VectorLength( velocity ) == 0 )
{
velocity[2] = 1; // stepped on a grenade
}
if ( ent->damage )
{
int location;
location = HL_NONE;
if ( other->client )
{
VectorNormalize ( velocity );
location = G_GetHitLocation ( other, ent->r.currentOrigin, velocity );
if ( ent->splashDamage )
{
location = G_MultipleDamageLocations(location);
}
}
d = G_Damage(other, ent, &g_entities[ent->r.ownerNum], velocity,
ent->s.origin, ent->damage, ent->dflags,
ent->methodOfDeath, location );
if ( d && other->client )
{
gentity_t *tent;
vec3_t hitdir;
// Put some procedural gore on the target.
tent = G_TempEntity( ent->r.currentOrigin, EV_EXPLOSION_HIT_FLESH );
// send entity and direction
VectorSubtract(other->r.currentOrigin, ent->r.currentOrigin, hitdir);
VectorNormalize(hitdir);
tent->s.eventParm = DirToByte( hitdir );
tent->s.otherEntityNum2 = other->s.number; // Victim entity number
// Pack the shot info into the temp end for gore
tent->s.time = ent->s.weapon + ((((int)other->s.apos.trBase[YAW]&0x7FFF) % 360) << 16);
if ( ent->s.eFlags & EF_ALT_FIRING )
{
tent->s.time += (ATTACK_ALTERNATE<<8);
}
VectorCopy ( other->r.currentOrigin, tent->s.angles );
SnapVector ( tent->s.angles );
}
}
}
// is it cheaper in bandwidth to just remove this ent and create a new
// one, rather than changing the missile into the explosion?
if ( d && other->client )
{
G_AddEvent( ent, EV_MISSILE_HIT,
(DirToByte( trace->plane.normal ) << MATERIAL_BITS) | (trace->surfaceFlags & MATERIAL_MASK));
ent->s.otherEntityNum = other->s.number;
if( ent->damage )
{
// FIXME: might be able to use the value from inside G_Damage to avoid recalc???
ent->s.otherEntityNum2 = G_GetHitLocation ( other, g_entities[ent->r.ownerNum].r.currentOrigin, velocity );
}
}
else
{
G_AddEvent( ent, EV_MISSILE_MISS,
(DirToByte( trace->plane.normal ) << MATERIAL_BITS) | (trace->surfaceFlags & MATERIAL_MASK));
// If missile should stick into impact point (e.g. a thrown knife).
if(!Q_stricmp(ent->classname,"Knife"))
{
// Create a pickup where we impacted.
vec3_t pickupPos;
gentity_t *pickupEnt;
VectorMA(trace->endpos,1,trace->plane.normal,pickupPos);
pickupEnt=CreateWeaponPickup(pickupPos,WP_KNIFE);
if(pickupEnt)
{
vec3_t knifeDir,knifeAngles;
BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, knifeDir );
//FIXME: needs work to set model angles!
VectorNormalize ( knifeDir );
vectoangles(knifeDir,knifeAngles);
knifeAngles[YAW] += 90;
knifeAngles[ROLL] = knifeAngles[PITCH];
knifeAngles[PITCH] = 0;
pickupEnt->s.angles[0]=knifeAngles[0];
pickupEnt->s.angles[1]=knifeAngles[1];
pickupEnt->s.angles[2]=knifeAngles[2];
pickupEnt->think = G_FreeEntity;
pickupEnt->nextthink = level.time + 30000; // Stick around for 30 seconds
pickupEnt->count = 1;
pickupEnt->s.eFlags |= EF_ANGLE_OVERRIDE;
VectorCopy(pickupEnt->s.angles,pickupEnt->r.currentAngles);
VectorCopy(pickupEnt->s.angles,pickupEnt->s.apos.trBase);
pickupEnt->s.pos.trType=TR_STATIONARY;
pickupEnt->s.apos.trTime=level.time;
pickupEnt->clipmask = ent->clipmask;
pickupEnt->s.groundEntityNum = trace->entityNum;
trap_LinkEntity(pickupEnt);
}
}
}
ent->freeAfterEvent = qtrue;
// All grenade explosions are now broadcast to ensure that fire and smoke is always seen
ent->r.svFlags |= SVF_BROADCAST;
// 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 )
{
if (ent->dflags & DAMAGE_AREA_DAMAGE)
{
// do damage over time rather than instantly
G_CreateDamageArea ( trace->endpos, ent->parent, ent->splashDamage*0.10f,ent->splashRadius*2, 8000,ent->methodOfDeath );
// do some instant damage
G_RadiusDamage( trace->endpos, ent->parent, ent->damage, ent->splashRadius, other,
1, ent->dflags, ent->splashMethodOfDeath );
}
else
{ // normal radius of effect damage
G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius,
other, 1, ent->dflags, 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 );
// if this missile bounced off an invulnerability sphere
if ( ent->target_ent )
{
passent = ent->target_ent->s.number;
}
else
{
// ignore interactions with the missile owner
passent = ent->r.ownerNum;
}
// Special case where the grenade has gone up into the sky
if ( ent->s.eFlags & EF_INSKY )
{
// Check to see if its out of the world on the X,Y plane
// or below it (above is a special case)
if ( origin[0] < level.worldMins[0] ||
origin[1] < level.worldMins[1] ||
origin[0] > level.worldMaxs[0] ||
origin[1] > level.worldMaxs[1] ||
origin[2] < level.worldMins[2] )
{
G_FreeEntity( ent );
return;
}
// Above it only kills it if the item has no gravity
if ( origin[2] > level.worldMaxs[2] && ent->s.pos.trType != TR_GRAVITY && ent->s.pos.trType != TR_LIGHTGRAVITY)
{
G_FreeEntity( ent );
return;
}
trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask );
// Hit another sky, must be reentering
if ( tr.fraction == 1.0f )
{
ent->s.eFlags &= ~EF_INSKY;
VectorCopy ( origin, ent->r.currentOrigin );
}
VectorCopy ( origin, ent->r.currentOrigin );
trap_LinkEntity ( ent );
}
else
{
// Run the same test again because the condition may have changed as a result
// of the greande falling below the sky again
// Loop this trace so we can break windows
while ( 1 )
{
// 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 its glass then redo the trace after breaking the glass
if ( tr.fraction != 1 && !Q_stricmp ( g_entities[tr.entityNum].classname, "func_glass" ) )
{
g_entities[tr.entityNum].use ( &g_entities[tr.entityNum], ent, ent );
continue;
}
break;
}
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 );
}
trap_LinkEntity( ent );
if ( tr.fraction != 1 )
{
// Hit the sky or moving through something
if ( tr.surfaceFlags & SURF_NOIMPACT )
{
// Dont kill a missle that hits the sky and has gravity
if ( tr.surfaceFlags & SURF_SKY )
{
ent->s.eFlags |= EF_INSKY;
ent->r.svFlags |= SVF_BROADCAST;
VectorCopy ( origin, ent->r.currentOrigin );
trap_LinkEntity( ent );
}
else
{
G_FreeEntity( ent );
return;
}
}
else
{
G_MissileImpact( ent, &tr );
// Is it time to explode
if ( ent->s.eFlags & EF_EXPLODE )
{
ent->s.eFlags &= (~EF_EXPLODE);
G_ExplodeMissile ( ent );
return;
}
// Exploded
if ( ent->s.eType != ET_MISSILE )
{
return;
}
}
}
// Is it time to explode
else if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags & EF_EXPLODE) )
{
ent->s.eFlags &= (~EF_EXPLODE);
G_ExplodeMissile ( ent );
return;
}
}
// If this is a knife then reorient its angles
if ( ent->s.weapon == WP_KNIFE )
{
vec3_t vel;
BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, vel );
vectoangles( vel, ent->s.angles );
ent->s.angles[YAW] += 90;
// ent->s.angles[ROLL] = ent->s.angles[PITCH];
ent->s.angles[ROLL] = 0;
ent->s.angles[PITCH] = 0;
}
// check think function after bouncing
G_RunThink( ent );
}
//=============================================================================