mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
3672ea6be8
Improved Tactician Gunner prox mine detection in missionpack DLL. Implemented Zaero modifications (can be pushed off ledge) to exploding barrels in missionpack DLL. Changed incomplete vector parsing check in ED_ParseField() to a warning instead of an error in all game DLLs.
3649 lines
No EOL
99 KiB
C
3649 lines
No EOL
99 KiB
C
#include "g_local.h"
|
|
|
|
#define NUKE_QUAKE_TIME 3
|
|
#define NUKE_QUAKE_STRENGTH 100
|
|
|
|
/*
|
|
=================
|
|
check_dodge
|
|
|
|
This is a support routine used when a client is firing
|
|
a non-instant attack weapon. It checks to see if a
|
|
monster's dodge function should be called.
|
|
=================
|
|
*/
|
|
//static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed)
|
|
void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) //PGM
|
|
{
|
|
vec3_t end;
|
|
vec3_t v;
|
|
trace_t tr;
|
|
float eta;
|
|
|
|
// easy mode only ducks one quarter the time
|
|
if (skill->value == 0)
|
|
{
|
|
if (random() > 0.25)
|
|
return;
|
|
}
|
|
VectorMA (start, WORLD_SIZE, dir, end); // was 8192
|
|
tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
|
|
if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
|
|
{
|
|
VectorSubtract (tr.endpos, start, v);
|
|
eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
|
|
// tr.ent->monsterinfo.dodge (tr.ent, self, eta);
|
|
tr.ent->monsterinfo.dodge (tr.ent, self, eta, &tr);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
fire_hit
|
|
|
|
Used for all impact (hit/punch/slash) attacks
|
|
=================
|
|
*/
|
|
qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick)
|
|
{
|
|
trace_t tr;
|
|
vec3_t forward, right, up;
|
|
vec3_t v;
|
|
vec3_t point;
|
|
float range;
|
|
vec3_t dir;
|
|
|
|
//see if enemy is in range
|
|
VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
|
|
range = VectorLength(dir);
|
|
if (range > aim[0])
|
|
return false;
|
|
|
|
if (aim[1] > self->mins[0] && aim[1] < self->maxs[0])
|
|
{
|
|
// the hit is straight on so back the range up to the edge of their bbox
|
|
range -= self->enemy->maxs[0];
|
|
}
|
|
else
|
|
{
|
|
// this is a side hit so adjust the "right" value out to the edge of their bbox
|
|
if (aim[1] < 0)
|
|
aim[1] = self->enemy->mins[0];
|
|
else
|
|
aim[1] = self->enemy->maxs[0];
|
|
}
|
|
|
|
VectorMA (self->s.origin, range, dir, point);
|
|
|
|
tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT);
|
|
if (tr.fraction < 1)
|
|
{
|
|
if (!tr.ent->takedamage)
|
|
return false;
|
|
// if it will hit any client/monster then hit the one we wanted to hit
|
|
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
|
|
tr.ent = self->enemy;
|
|
}
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
VectorMA (self->s.origin, range, forward, point);
|
|
VectorMA (point, aim[1], right, point);
|
|
VectorMA (point, aim[2], up, point);
|
|
VectorSubtract (point, self->enemy->s.origin, dir);
|
|
|
|
// do the damage
|
|
T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
|
|
|
|
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
|
return false;
|
|
|
|
// do our special form of knockback here
|
|
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v);
|
|
VectorSubtract (v, point, v);
|
|
VectorNormalize (v);
|
|
VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity);
|
|
if (self->enemy->velocity[2] > 0)
|
|
self->enemy->groundentity = NULL;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
fire_lead
|
|
|
|
This is an internal support routine used for bullet/pellet based weapons.
|
|
=================
|
|
*/
|
|
/*static*/ void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod)
|
|
{
|
|
trace_t tr;
|
|
vec3_t dir;
|
|
vec3_t forward, right, up;
|
|
vec3_t end;
|
|
float r;
|
|
float u;
|
|
vec3_t water_start;
|
|
qboolean water = false;
|
|
int content_mask = MASK_SHOT | MASK_WATER;
|
|
|
|
tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
|
|
if (!(tr.fraction < 1.0))
|
|
{
|
|
vectoangles (aimdir, dir);
|
|
AngleVectors (dir, forward, right, up);
|
|
|
|
r = crandom()*hspread;
|
|
u = crandom()*vspread;
|
|
// Knightmare- adjust spread for expanded world size
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
r *= (WORLD_SIZE / 8192);
|
|
u *= (WORLD_SIZE / 8192);
|
|
#endif
|
|
VectorMA (start, WORLD_SIZE, forward, end); // was 8192
|
|
VectorMA (end, r, right, end);
|
|
VectorMA (end, u, up, end);
|
|
|
|
if (gi.pointcontents (start) & MASK_WATER)
|
|
{
|
|
water = true;
|
|
VectorCopy (start, water_start);
|
|
content_mask &= ~MASK_WATER;
|
|
}
|
|
|
|
tr = gi.trace (start, NULL, NULL, end, self, content_mask);
|
|
|
|
// see if we hit water
|
|
if (tr.contents & MASK_WATER)
|
|
{
|
|
int color;
|
|
|
|
water = true;
|
|
VectorCopy (tr.endpos, water_start);
|
|
|
|
if (!VectorCompare (start, tr.endpos))
|
|
{
|
|
if (tr.ent->svflags & SVF_MUD)
|
|
color = SPLASH_BROWN_WATER;
|
|
else if (tr.contents & CONTENTS_WATER)
|
|
{
|
|
if (strcmp(tr.surface->name, "*brwater") == 0)
|
|
color = SPLASH_BROWN_WATER;
|
|
else
|
|
color = SPLASH_BLUE_WATER;
|
|
}
|
|
else if (tr.contents & CONTENTS_SLIME)
|
|
color = SPLASH_SLIME;
|
|
else if (tr.contents & CONTENTS_LAVA)
|
|
color = SPLASH_LAVA;
|
|
else
|
|
color = SPLASH_UNKNOWN;
|
|
|
|
if (color != SPLASH_UNKNOWN)
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_SPLASH);
|
|
gi.WriteByte (8);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.WriteDir (tr.plane.normal);
|
|
gi.WriteByte (color);
|
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
|
}
|
|
|
|
// change bullet's course when it enters water
|
|
VectorSubtract (end, start, dir);
|
|
vectoangles (dir, dir);
|
|
AngleVectors (dir, forward, right, up);
|
|
r = crandom()*hspread*2;
|
|
u = crandom()*vspread*2;
|
|
// Knightmare- adjust spread for expanded world size
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
r *= (WORLD_SIZE / 8192);
|
|
u *= (WORLD_SIZE / 8192);
|
|
#endif
|
|
VectorMA (water_start, WORLD_SIZE, forward, end); // was 8192
|
|
VectorMA (end, r, right, end);
|
|
VectorMA (end, u, up, end);
|
|
}
|
|
|
|
// re-trace ignoring water this time
|
|
tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
|
|
}
|
|
}
|
|
|
|
// send gun puff / flash
|
|
if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
|
|
{
|
|
if (tr.fraction < 1.0)
|
|
{
|
|
if (tr.ent->takedamage)
|
|
{
|
|
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod);
|
|
}
|
|
else
|
|
{
|
|
if (strncmp (tr.surface->name, "sky", 3) != 0)
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (te_impact);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.WriteDir (tr.plane.normal);
|
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectSparks (te_impact, tr.endpos, tr.plane.normal);
|
|
|
|
if (self->client)
|
|
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if went through water, determine where the end and make a bubble trail
|
|
if (water)
|
|
{
|
|
vec3_t pos;
|
|
|
|
VectorSubtract (tr.endpos, water_start, dir);
|
|
VectorNormalize (dir);
|
|
VectorMA (tr.endpos, -2, dir, pos);
|
|
if (gi.pointcontents (pos) & MASK_WATER)
|
|
VectorCopy (pos, tr.endpos);
|
|
else
|
|
tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
|
|
|
|
VectorAdd (water_start, tr.endpos, pos);
|
|
VectorScale (pos, 0.5, pos);
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_BUBBLETRAIL);
|
|
gi.WritePosition (water_start);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.multicast (pos, MULTICAST_PVS);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
fire_bullet
|
|
|
|
Fires a single round. Used for machinegun and chaingun. Would be fine for
|
|
pistols, rifles, etc....
|
|
=================
|
|
*/
|
|
void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
|
|
{
|
|
fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
fire_shotgun
|
|
|
|
Shoots shotgun pellets. Used by shotgun and super shotgun.
|
|
=================
|
|
*/
|
|
void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
fire_blaster
|
|
|
|
Fires a single blaster bolt. Used by the blaster and hyper blaster.
|
|
=================
|
|
*/
|
|
void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
int mod;
|
|
int tempevent;
|
|
|
|
if (other == self->owner)
|
|
return;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
// PMM - crash prevention
|
|
if (self->owner && self->owner->client)
|
|
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
|
|
|
|
if (other->takedamage)
|
|
{
|
|
if (self->spawnflags & 1)
|
|
mod = MOD_HYPERBLASTER;
|
|
else
|
|
mod = MOD_BLASTER;
|
|
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
|
|
}
|
|
else
|
|
{
|
|
if (self->style == BLASTER_GREEN) //green
|
|
tempevent = TE_BLASTER2;
|
|
else if (self->style == BLASTER_BLUE) //blue
|
|
// #ifdef KMQUAKE2_ENGINE_MOD
|
|
#if defined (KMQUAKE2_ENGINE_MOD) || defined (Q2E_ENGINE_MOD)
|
|
tempevent = TE_BLUEHYPERBLASTER; // Knightmare- looks better than flechette
|
|
#else
|
|
tempevent = TE_FLECHETTE;
|
|
#endif
|
|
// #ifdef KMQUAKE2_ENGINE_MOD
|
|
#if defined (KMQUAKE2_ENGINE_MOD) || defined (Q2E_ENGINE_MOD)
|
|
else if (self->style == BLASTER_RED) //red
|
|
tempevent = TE_REDBLASTER;
|
|
#endif
|
|
else //standard orange
|
|
tempevent = TE_BLASTER;
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (tempevent);
|
|
gi.WritePosition (self->s.origin);
|
|
if (!plane)
|
|
gi.WriteDir (vec3_origin);
|
|
else
|
|
gi.WriteDir (plane->normal);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
{
|
|
if (!plane)
|
|
ReflectSparks (tempevent, self->s.origin, vec3_origin);
|
|
else
|
|
ReflectSparks (tempevent, self->s.origin, plane->normal);
|
|
}
|
|
}
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper, int color)
|
|
{
|
|
edict_t *bolt;
|
|
trace_t tr;
|
|
|
|
VectorNormalize (dir);
|
|
|
|
bolt = G_Spawn();
|
|
bolt->svflags = SVF_DEADMONSTER;
|
|
// yes, I know it looks weird that projectiles are deadmonsters
|
|
// what this means is that when prediction is used against the object
|
|
// (blaster/hyperblaster shots), the player won't be solid clipped against
|
|
// the object. Right now trying to run into a firing hyperblaster
|
|
// is very jerky since you are predicted 'against' the shots.
|
|
VectorCopy (start, bolt->s.origin);
|
|
VectorCopy (start, bolt->s.old_origin);
|
|
vectoangles (dir, bolt->s.angles);
|
|
VectorScale (dir, speed, bolt->velocity);
|
|
bolt->movetype = MOVETYPE_FLYMISSILE;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->solid = SOLID_BBOX;
|
|
bolt->s.effects |= effect;
|
|
bolt->s.renderfx |= RF_NOSHADOW; // Knightmare- no shadow
|
|
VectorClear (bolt->mins);
|
|
VectorClear (bolt->maxs);
|
|
|
|
if (color == BLASTER_GREEN) // green
|
|
bolt->s.skinnum = 1;
|
|
else if (color == BLASTER_BLUE) // blue
|
|
bolt->s.skinnum = 2;
|
|
else if (color == BLASTER_RED) // red
|
|
bolt->s.skinnum = 3;
|
|
else // standard orange
|
|
bolt->s.skinnum = 0;
|
|
bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2");
|
|
bolt->style = color;
|
|
|
|
bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
|
|
|
|
bolt->owner = self;
|
|
bolt->touch = blaster_touch;
|
|
bolt->nextthink = level.time + 4;
|
|
bolt->think = G_FreeEdict;
|
|
bolt->dmg = damage;
|
|
if (hyper)
|
|
bolt->spawnflags = 1;
|
|
bolt->classname = "bolt";
|
|
bolt->class_id = ENTITY_BOLT;
|
|
gi.linkentity (bolt);
|
|
|
|
if (self->client)
|
|
check_dodge (self, bolt->s.origin, dir, speed);
|
|
|
|
tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
|
|
if (tr.fraction < 1.0 && !(self->flags & FL_TURRET_OWNER))
|
|
{
|
|
VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
|
|
bolt->touch (bolt, tr.ent, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
// RAFAEL
|
|
void fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
|
|
{
|
|
edict_t *bolt;
|
|
trace_t tr;
|
|
|
|
VectorNormalize (dir);
|
|
|
|
bolt = G_Spawn ();
|
|
VectorCopy (start, bolt->s.origin);
|
|
VectorCopy (start, bolt->s.old_origin);
|
|
vectoangles (dir, bolt->s.angles);
|
|
VectorScale (dir, speed, bolt->velocity);
|
|
bolt->movetype = MOVETYPE_FLYMISSILE;
|
|
bolt->clipmask = MASK_SHOT;
|
|
bolt->solid = SOLID_BBOX;
|
|
bolt->s.effects |= effect;
|
|
bolt->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow
|
|
VectorClear (bolt->mins);
|
|
VectorClear (bolt->maxs);
|
|
|
|
bolt->s.modelindex = gi.modelindex ("models/objects/blaser/tris.md2");
|
|
bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
|
|
bolt->style = BLASTER_BLUE;
|
|
bolt->spawnflags |= 1;
|
|
bolt->owner = self;
|
|
bolt->touch = blaster_touch;
|
|
bolt->nextthink = level.time + 4;
|
|
bolt->think = G_FreeEdict;
|
|
bolt->dmg = damage;
|
|
bolt->classname = "bolt";
|
|
bolt->class_id = ENTITY_BOLT;
|
|
gi.linkentity (bolt);
|
|
|
|
if (self->client)
|
|
check_dodge (self, bolt->s.origin, dir, speed);
|
|
|
|
tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
|
|
|
|
if (tr.fraction < 1.0 && !(self->flags & FL_TURRET_OWNER))
|
|
{
|
|
VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
|
|
bolt->touch (bolt, tr.ent, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
// NOTE: SP_bolt should ONLY be used for blaster/hyperblaster bolts that have
|
|
// changed maps via trigger_transition. It should NOT be used for map
|
|
// entities.
|
|
void bolt_delayed_start (edict_t *bolt)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(bolt->movedir,bolt->moveinfo.speed,bolt->velocity);
|
|
bolt->nextthink = level.time + 2;
|
|
bolt->think = G_FreeEdict;
|
|
gi.linkentity(bolt);
|
|
}
|
|
else
|
|
bolt->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_bolt (edict_t *bolt)
|
|
{
|
|
if (bolt->style == BLASTER_GREEN) // green bolt
|
|
bolt->s.skinnum = 1;
|
|
else if (bolt->style == BLASTER_BLUE) // blue bolt
|
|
bolt->s.skinnum = 2;
|
|
else if (bolt->style == BLASTER_RED) // red bolt
|
|
bolt->s.skinnum = 3;
|
|
else // orange bolt
|
|
bolt->s.skinnum = 0;
|
|
bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2");
|
|
bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
|
|
bolt->touch = blaster_touch;
|
|
VectorCopy(bolt->velocity,bolt->movedir);
|
|
VectorNormalize(bolt->movedir);
|
|
bolt->moveinfo.speed = VectorLength(bolt->velocity);
|
|
VectorClear(bolt->velocity);
|
|
bolt->think = bolt_delayed_start;
|
|
bolt->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(bolt);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
fire_grenade
|
|
=================
|
|
*/
|
|
// Lazarus additions: next_grenade and prev_grenade linked lists facilitate checking
|
|
// for grenades near monsters, so that monsters can evade w/o bogging down the game
|
|
|
|
void Grenade_Evade (edict_t *monster)
|
|
{
|
|
edict_t *grenade;
|
|
vec3_t grenade_vec;
|
|
float grenade_dist, best_r, best_yaw, r;
|
|
float yaw;
|
|
int i;
|
|
vec3_t forward;
|
|
vec3_t pos, best_pos;
|
|
trace_t tr;
|
|
|
|
// We assume on entry here that monster is alive and that he's not already
|
|
// AI_CHASE_THING
|
|
grenade = world->next_grenade;
|
|
while (grenade)
|
|
{
|
|
// we only care about grenades on the ground
|
|
if (grenade->inuse && grenade->groundentity)
|
|
{
|
|
// if it ain't in the PVS, it can't hurt us (I think?)
|
|
if (gi.inPVS(grenade->s.origin,monster->s.origin))
|
|
{
|
|
VectorSubtract (grenade->s.origin, monster->s.origin, grenade_vec);
|
|
grenade_dist = VectorNormalize(grenade_vec);
|
|
if (grenade_dist <= grenade->dmg_radius)
|
|
break;
|
|
}
|
|
}
|
|
grenade = grenade->next_grenade;
|
|
}
|
|
if (!grenade)
|
|
return;
|
|
// Find best escape route.
|
|
best_r = 9999;
|
|
for (i=0; i<8; i++)
|
|
{
|
|
yaw = anglemod( i*45 );
|
|
forward[0] = cos( DEG2RAD(yaw) );
|
|
forward[1] = sin( DEG2RAD(yaw) );
|
|
forward[2] = 0;
|
|
// Estimate of required distance to run. This is conservative.
|
|
r = grenade->dmg_radius + grenade_dist*DotProduct(forward, grenade_vec) + monster->size[0] + 16;
|
|
if ( r < best_r )
|
|
{
|
|
VectorMA (monster->s.origin, r, forward,pos);
|
|
tr = gi.trace(monster->s.origin, monster->mins, monster->maxs, pos, monster, MASK_MONSTERSOLID);
|
|
if (tr.fraction < 1.0)
|
|
continue;
|
|
best_r = r;
|
|
best_yaw = yaw;
|
|
VectorCopy (tr.endpos, best_pos);
|
|
}
|
|
}
|
|
if (best_r < 9000)
|
|
{
|
|
edict_t *thing = SpawnThing();
|
|
VectorCopy(best_pos,thing->s.origin);
|
|
thing->touch_debounce_time = grenade->nextthink;
|
|
thing->target_ent = monster;
|
|
ED_CallSpawn(thing);
|
|
monster->ideal_yaw = best_yaw;
|
|
monster->movetarget = monster->goalentity = thing;
|
|
monster->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
|
|
monster->monsterinfo.aiflags |= (AI_CHASE_THING | AI_EVADE_GRENADE);
|
|
monster->monsterinfo.run(monster);
|
|
monster->next_grenade = grenade;
|
|
}
|
|
}
|
|
|
|
void Grenade_Add_To_Chain (edict_t *grenade)
|
|
{
|
|
edict_t *ancestor;
|
|
|
|
ancestor = world;
|
|
while (ancestor->next_grenade && ancestor->next_grenade->inuse)
|
|
ancestor = ancestor->next_grenade;
|
|
ancestor->next_grenade = grenade;
|
|
grenade->prev_grenade = ancestor;
|
|
}
|
|
|
|
void Grenade_Remove_From_Chain (edict_t *grenade)
|
|
{
|
|
if (grenade->prev_grenade)
|
|
{
|
|
// "prev_grenade" should always be valid for other than player-thrown
|
|
// grenades that explode in player's hand
|
|
grenade->prev_grenade->next_grenade = grenade->next_grenade;
|
|
if (grenade->next_grenade)
|
|
grenade->next_grenade->prev_grenade = grenade->prev_grenade;
|
|
}
|
|
}
|
|
|
|
void Grenade_Explode (edict_t *ent)
|
|
{
|
|
vec3_t origin;
|
|
int mod;
|
|
int type; // Knightmare added
|
|
|
|
// Grenade_Remove_From_Chain (ent);
|
|
|
|
if (ent->owner->client)
|
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// FIXME: if we are onground then raise our Z just a bit since we are a point?
|
|
if (ent->enemy)
|
|
{
|
|
float points;
|
|
vec3_t v;
|
|
vec3_t dir;
|
|
|
|
VectorAdd (ent->enemy->mins, ent->enemy->maxs, v);
|
|
VectorMA (ent->enemy->s.origin, 0.5, v, v);
|
|
VectorSubtract (ent->s.origin, v, v);
|
|
points = ent->dmg - 0.5 * VectorLength (v);
|
|
VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir);
|
|
if (ent->spawnflags & 1)
|
|
mod = MOD_HANDGRENADE;
|
|
else
|
|
mod = MOD_GRENADE;
|
|
T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
|
}
|
|
|
|
if (ent->spawnflags & 2)
|
|
mod = MOD_HELD_GRENADE;
|
|
else if (ent->spawnflags & 1)
|
|
mod = MOD_HG_SPLASH;
|
|
else
|
|
mod = MOD_G_SPLASH;
|
|
T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
|
|
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
gi.WriteByte (svc_temp_entity);
|
|
if (ent->waterlevel)
|
|
{
|
|
if (ent->groundentity)
|
|
type = TE_GRENADE_EXPLOSION_WATER;
|
|
else
|
|
type = TE_ROCKET_EXPLOSION_WATER;
|
|
}
|
|
else
|
|
{
|
|
if (ent->groundentity)
|
|
type = TE_GRENADE_EXPLOSION;
|
|
else
|
|
type = TE_ROCKET_EXPLOSION;
|
|
}
|
|
gi.WriteByte (type);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (type, origin);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if (other == ent->owner)
|
|
return;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
// Grenade_Remove_From_Chain (ent);
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
if (!other->takedamage)
|
|
{
|
|
if (ent->spawnflags & 1)
|
|
{
|
|
if (random() > 0.5)
|
|
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
else
|
|
{
|
|
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ent->enemy = other;
|
|
Grenade_Explode (ent);
|
|
}
|
|
|
|
void ContactGrenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if (other == ent->owner)
|
|
return;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
// Grenade_Remove_From_Chain (ent);
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
if (other->takedamage)
|
|
ent->enemy = other;
|
|
|
|
Grenade_Explode (ent);
|
|
}
|
|
|
|
void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean contact)
|
|
{
|
|
edict_t *grenade;
|
|
vec3_t dir;
|
|
vec3_t forward, right, up;
|
|
|
|
vectoangles (aimdir, dir);
|
|
AngleVectors (dir, forward, right, up);
|
|
|
|
grenade = G_Spawn();
|
|
VectorCopy (start, grenade->s.origin);
|
|
VectorScale (aimdir, speed, grenade->velocity);
|
|
// Lazarus - keep same vertical boost for players, but monsters do a better job
|
|
// of calculating aim direction, so throw that out
|
|
if (self->client)
|
|
VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
|
|
else
|
|
VectorMA (grenade->velocity, crandom() * 10.0, up, grenade->velocity);
|
|
|
|
VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
|
|
// Lazarus: Add owner velocity
|
|
// VectorAdd (grenade->velocity, self->velocity, grenade->velocity);
|
|
// NO. This is too unrealistic. Instead, if owner is riding a moving entity,
|
|
// add velocity of the thing he's riding
|
|
|
|
// Knightmare- add player's base velocity to grenade
|
|
if (add_velocity_throw->value && self->client)
|
|
VectorAdd (grenade->velocity, self->velocity, grenade->velocity);
|
|
else if (self->groundentity)
|
|
VectorAdd (grenade->velocity, self->groundentity->velocity, grenade->velocity);
|
|
|
|
VectorSet (grenade->avelocity, 300, 300, 300);
|
|
grenade->movetype = MOVETYPE_BOUNCE;
|
|
grenade->clipmask = MASK_SHOT;
|
|
grenade->solid = SOLID_BBOX;
|
|
grenade->s.effects |= EF_GRENADE;
|
|
grenade->s.renderfx |= RF_IR_VISIBLE;
|
|
VectorClear (grenade->mins);
|
|
VectorClear (grenade->maxs);
|
|
grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
|
|
grenade->owner = self;
|
|
if (contact)
|
|
grenade->touch = ContactGrenade_Touch;
|
|
else
|
|
grenade->touch = Grenade_Touch;
|
|
grenade->nextthink = level.time + timer;
|
|
grenade->think = Grenade_Explode;
|
|
grenade->dmg = damage;
|
|
grenade->dmg_radius = damage_radius;
|
|
grenade->classname = "grenade";
|
|
grenade->class_id = ENTITY_GRENADE;
|
|
|
|
// Grenade_Add_To_Chain (grenade);
|
|
|
|
gi.linkentity (grenade);
|
|
}
|
|
|
|
void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
|
|
{
|
|
edict_t *grenade;
|
|
vec3_t dir;
|
|
vec3_t forward, right, up;
|
|
|
|
vectoangles (aimdir, dir);
|
|
AngleVectors (dir, forward, right, up);
|
|
|
|
grenade = G_Spawn();
|
|
VectorCopy (start, grenade->s.origin);
|
|
VectorScale (aimdir, speed, grenade->velocity);
|
|
VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
|
|
VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
|
|
// Knightmare- add player's base velocity to thrown grenade
|
|
if (add_velocity_throw->value && self->client)
|
|
VectorAdd (grenade->velocity, self->velocity, grenade->velocity);
|
|
else if (self->groundentity)
|
|
VectorAdd (grenade->velocity, self->groundentity->velocity, grenade->velocity);
|
|
|
|
VectorSet (grenade->avelocity, 300, 300, 300);
|
|
grenade->movetype = MOVETYPE_BOUNCE;
|
|
grenade->clipmask = MASK_SHOT;
|
|
grenade->solid = SOLID_BBOX;
|
|
grenade->s.effects |= EF_GRENADE;
|
|
grenade->s.renderfx |= RF_IR_VISIBLE;
|
|
VectorClear (grenade->mins);
|
|
VectorClear (grenade->maxs);
|
|
grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2");
|
|
grenade->owner = self;
|
|
grenade->touch = Grenade_Touch;
|
|
grenade->nextthink = level.time + timer;
|
|
grenade->think = Grenade_Explode;
|
|
grenade->dmg = damage;
|
|
grenade->dmg_radius = damage_radius;
|
|
grenade->classname = "hgrenade";
|
|
grenade->class_id = ENTITY_HANDGRENADE;
|
|
if (held)
|
|
grenade->spawnflags = 3;
|
|
else
|
|
grenade->spawnflags = 1;
|
|
grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
|
|
|
|
if (timer <= 0.0)
|
|
Grenade_Explode (grenade);
|
|
else
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
|
|
// Grenade_Add_To_Chain (grenade);
|
|
gi.linkentity (grenade);
|
|
}
|
|
}
|
|
|
|
// NOTE: SP_grenade and SP_handgrenade should ONLY be used to spawn grenades that change
|
|
// maps via a trigger_transition. They should NOT be used for map entities.
|
|
|
|
void grenade_delayed_start (edict_t *grenade)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(grenade->movedir,grenade->moveinfo.speed,grenade->velocity);
|
|
grenade->movetype = MOVETYPE_BOUNCE;
|
|
grenade->nextthink = level.time + 2.5;
|
|
grenade->think = Grenade_Explode;
|
|
gi.linkentity(grenade);
|
|
}
|
|
else
|
|
grenade->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_grenade (edict_t *grenade)
|
|
{
|
|
grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
|
|
grenade->touch = Grenade_Touch;
|
|
|
|
// For SP, freeze grenade until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
grenade->movetype = MOVETYPE_NONE;
|
|
VectorCopy(grenade->velocity,grenade->movedir);
|
|
VectorNormalize(grenade->movedir);
|
|
grenade->moveinfo.speed = VectorLength(grenade->velocity);
|
|
VectorClear(grenade->velocity);
|
|
grenade->think = grenade_delayed_start;
|
|
grenade->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
grenade->movetype = MOVETYPE_BOUNCE;
|
|
grenade->nextthink = level.time + 2.5;
|
|
grenade->think = Grenade_Explode;
|
|
}
|
|
gi.linkentity (grenade);
|
|
}
|
|
|
|
void handgrenade_delayed_start (edict_t *grenade)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(grenade->movedir,grenade->moveinfo.speed,grenade->velocity);
|
|
grenade->movetype = MOVETYPE_BOUNCE;
|
|
grenade->nextthink = level.time + 2.5;
|
|
grenade->think = Grenade_Explode;
|
|
if (grenade->owner)
|
|
gi.sound (grenade->owner, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
|
|
gi.linkentity(grenade);
|
|
}
|
|
else
|
|
grenade->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_handgrenade (edict_t *grenade)
|
|
{
|
|
grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2");
|
|
grenade->touch = Grenade_Touch;
|
|
|
|
// For SP, freeze grenade until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
grenade->movetype = MOVETYPE_NONE;
|
|
VectorCopy(grenade->velocity,grenade->movedir);
|
|
VectorNormalize(grenade->movedir);
|
|
grenade->moveinfo.speed = VectorLength(grenade->velocity);
|
|
VectorClear(grenade->velocity);
|
|
grenade->think = handgrenade_delayed_start;
|
|
grenade->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
grenade->movetype = MOVETYPE_BOUNCE;
|
|
grenade->nextthink = level.time + 2.5;
|
|
grenade->think = Grenade_Explode;
|
|
}
|
|
gi.linkentity (grenade);
|
|
}
|
|
|
|
|
|
//==========================================================================================
|
|
//
|
|
// AimGrenade finds the correct aim vector to get a grenade from start to target at initial
|
|
// velocity = speed. Returns false if grenade can't make it to target.
|
|
//
|
|
//==========================================================================================
|
|
qboolean AimGrenade (edict_t *self, vec3_t start, vec3_t target, vec_t speed, vec3_t aim, qboolean isProx)
|
|
{
|
|
vec3_t angles, forward, right, up;
|
|
vec3_t from_origin, from_muzzle;
|
|
vec3_t aim_point;
|
|
vec_t xo, yo;
|
|
vec_t x;
|
|
float cosa, t, vx, y;
|
|
float drop;
|
|
float last_error, v_error;
|
|
int i;
|
|
vec3_t last_aim;
|
|
// Knightmare- wider bounds to avoid prox collision
|
|
vec3_t proxMins = {-PROX_TEST_SIZE, -PROX_TEST_SIZE, -PROX_TEST_SIZE};
|
|
vec3_t proxMaxs = {PROX_TEST_SIZE, PROX_TEST_SIZE, PROX_TEST_SIZE};
|
|
|
|
VectorCopy (target, aim_point);
|
|
VectorSubtract (aim_point, self->s.origin, from_origin);
|
|
VectorSubtract (aim_point, start, from_muzzle);
|
|
|
|
if (self->svflags & SVF_MONSTER)
|
|
{
|
|
VectorCopy (from_muzzle, aim);
|
|
VectorNormalize (aim);
|
|
yo = from_muzzle[2];
|
|
xo = sqrt (from_muzzle[0] * from_muzzle[0] + from_muzzle[1] * from_muzzle[1]);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy (from_origin, aim);
|
|
VectorNormalize (aim);
|
|
yo = from_origin[2];
|
|
xo = sqrt (from_origin[0] * from_origin[0] + from_origin[1] * from_origin[1]);
|
|
}
|
|
|
|
// If resulting aim vector is looking straight up or straight down, we're
|
|
// done. Actually now that I write this down and think about it... should
|
|
// probably check straight up to make sure grenade will actually reach the
|
|
// target.
|
|
if ( (aim[2] == 1.0) || (aim[2] == -1.0))
|
|
return true;
|
|
|
|
// horizontal distance to target from muzzle
|
|
x = sqrt (from_muzzle[0] * from_muzzle[0] + from_muzzle[1] * from_muzzle[1]);
|
|
cosa = sqrt (aim[0] * aim[0] + aim[1] * aim[1]);
|
|
// constant horizontal velocity (since grenades don't have drag)
|
|
vx = speed * cosa;
|
|
// time to reach target x
|
|
t = x / vx;
|
|
// if flight time is less than one frame, no way grenade will drop much,
|
|
// shoot the sucker now.
|
|
if (t < FRAMETIME)
|
|
return true;
|
|
// in that time, grenade will drop this much:
|
|
drop = 0.5 * sv_gravity->value * t * t;
|
|
y = speed * aim[2] * t - drop;
|
|
v_error = target[2] - start[2] - y;
|
|
|
|
// if we're fairly close and we'll hit target at current angle,
|
|
// no need for all this, just shoot it
|
|
if ( (x < 128) && (fabs(v_error) < 16) )
|
|
return true;
|
|
|
|
last_error = 100000.;
|
|
VectorCopy (aim, last_aim);
|
|
|
|
// Unfortunately there is no closed-form solution for this problem,
|
|
// so we creep up on an answer and balk if it takes more than
|
|
// 10 iterations to converge to the tolerance we'll accept.
|
|
for (i=0; i<10 && fabs(v_error) > 4 && fabs(v_error) < fabs(last_error); i++)
|
|
{
|
|
last_error = v_error;
|
|
aim[2] = cosa * (yo + drop) / xo;
|
|
VectorNormalize (aim);
|
|
if (!(self->svflags & SVF_MONSTER))
|
|
{
|
|
vectoangles (aim, angles);
|
|
AngleVectors (angles, forward, right, up);
|
|
G_ProjectSource2 (self->s.origin, self->move_origin, forward, right, up, start);
|
|
VectorSubtract (aim_point, start, from_muzzle);
|
|
x = sqrt(from_muzzle[0] * from_muzzle[0] + from_muzzle[1] * from_muzzle[1]);
|
|
}
|
|
cosa = sqrt(aim[0] * aim[0] + aim[1] * aim[1]);
|
|
vx = speed * cosa;
|
|
t = x / vx;
|
|
drop = 0.5 * sv_gravity->value * t * t;
|
|
y = speed * aim[2] * t - drop;
|
|
v_error = target[2] - start[2] - y;
|
|
if (fabs(v_error) < fabs(last_error))
|
|
VectorCopy (aim, last_aim);
|
|
}
|
|
|
|
if (i >= 10 || v_error > 64)
|
|
return false;
|
|
|
|
if (fabs(v_error) > fabs(last_error))
|
|
{
|
|
VectorCopy (last_aim, aim);
|
|
if (!(self->svflags & SVF_MONSTER))
|
|
{
|
|
vectoangles (aim, angles);
|
|
AngleVectors (angles, forward, right, up);
|
|
G_ProjectSource2 (self->s.origin, self->move_origin, forward, right, up, start);
|
|
VectorSubtract (aim_point, start, from_muzzle);
|
|
}
|
|
}
|
|
|
|
// Sanity check... if launcher is at the same elevation or a bit above the
|
|
// target entity, check to make sure he won't bounce grenades off the
|
|
// top of a doorway or other obstruction. If he WOULD do that, then figure out
|
|
// the max elevation angle that will get the grenade through the door, and
|
|
// hope we get a good bounce.
|
|
if ( (start[2] - target[2] < 160) &&
|
|
(start[2] - target[2] > -16) )
|
|
{
|
|
trace_t tr;
|
|
vec3_t dist;
|
|
|
|
if (isProx)
|
|
tr = gi.trace(start, proxMins, proxMaxs, aim_point, self, MASK_SOLID);
|
|
else
|
|
tr = gi.trace(start, vec3_origin, vec3_origin, aim_point, self, MASK_SOLID);
|
|
if ( (tr.fraction < 1.0) && (!self->enemy || (tr.ent != self->enemy) ))
|
|
{
|
|
// OK... the aim vector hit a solid, but would the grenade actually hit?
|
|
int contents;
|
|
cosa = sqrt(aim[0] * aim[0] + aim[1] * aim[1]);
|
|
vx = speed * cosa;
|
|
VectorSubtract (tr.endpos, start, dist);
|
|
dist[2] = 0;
|
|
x = VectorLength (dist);
|
|
t = x / vx;
|
|
drop = 0.5 * sv_gravity->value * t * (t + FRAMETIME);
|
|
tr.endpos[2] -= drop;
|
|
// move just a bit in the aim direction
|
|
tr.endpos[0] += aim[0];
|
|
tr.endpos[1] += aim[1];
|
|
contents = gi.pointcontents(tr.endpos);
|
|
while ((contents & MASK_SOLID) && (aim_point[2] > target[2]))
|
|
{
|
|
aim_point[2] -= 8.0;
|
|
VectorSubtract (aim_point, self->s.origin, from_origin);
|
|
VectorCopy (from_origin, aim);
|
|
VectorNormalize (aim);
|
|
if (!(self->svflags & SVF_MONSTER))
|
|
{
|
|
vectoangles (aim, angles);
|
|
AngleVectors (angles, forward, right, up);
|
|
G_ProjectSource2 (self->s.origin, self->move_origin, forward, right, up, start);
|
|
VectorSubtract (aim_point, start, from_muzzle);
|
|
}
|
|
if (isProx)
|
|
tr = gi.trace(start, proxMins, proxMaxs, aim_point, self, MASK_SOLID);
|
|
else
|
|
tr = gi.trace(start, vec3_origin, vec3_origin, aim_point, self, MASK_SOLID);
|
|
if (tr.fraction < 1.0)
|
|
{
|
|
cosa = sqrt(aim[0] * aim[0] + aim[1] * aim[1]);
|
|
vx = speed * cosa;
|
|
VectorSubtract (tr.endpos, start, dist);
|
|
dist[2] = 0;
|
|
x = VectorLength(dist);
|
|
t = x / vx;
|
|
drop = 0.5 * sv_gravity->value * t * (t + FRAMETIME);
|
|
tr.endpos[2] -= drop;
|
|
tr.endpos[0] += aim[0];
|
|
tr.endpos[1] += aim[1];
|
|
contents = gi.pointcontents(tr.endpos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
fire_missile
|
|
=================
|
|
*/
|
|
void missile_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
vec3_t origin;
|
|
int n;
|
|
int type;
|
|
|
|
if (other == ent->owner)
|
|
return;
|
|
|
|
if (ent->owner->client && (ent->owner->client->homing_rocket == ent))
|
|
ent->owner->client->homing_rocket = NULL;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
if (ent->owner && ent->owner->client)
|
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// calculate position for the explosion entity
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
|
|
if (other->takedamage)
|
|
{
|
|
T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_MISSILE);
|
|
}
|
|
else
|
|
{
|
|
// don't throw any debris in net games
|
|
if (!deathmatch->value && !coop->value)
|
|
{
|
|
if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
|
|
{
|
|
n = rand() % 5;
|
|
while (n--)
|
|
ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lazarus: bad monsters have a large damage radius
|
|
if (ent->owner && (ent->owner->svflags & SVF_MONSTER) && !(ent->owner->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
// T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius + 17.5*skill->value, MOD_MISSILE_SPLASH, -2.0/(4.0+skill->value) );
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius + 17.5*skill->value, MOD_MISSILE_SPLASH);
|
|
else
|
|
// T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_MISSILE_SPLASH, -0.5);
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_MISSILE_SPLASH);
|
|
gi.WriteByte (svc_temp_entity);
|
|
if (ent->waterlevel)
|
|
type = TE_ROCKET_EXPLOSION_WATER;
|
|
else
|
|
type = TE_ROCKET_EXPLOSION;
|
|
gi.WriteByte (type);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (type, origin);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
/*static*/ void missile_explode (edict_t *ent)
|
|
{
|
|
vec3_t origin;
|
|
int type;
|
|
|
|
if (ent->owner->client)
|
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// calculate position for the explosion entity
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
|
|
// T_RadiusDamage(ent, ent->owner, ent->radius_dmg, NULL, ent->dmg_radius, MOD_MISSILE_SPLASH, -0.5);
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, NULL, ent->dmg_radius, MOD_MISSILE_SPLASH);
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
if (ent->waterlevel)
|
|
type = TE_ROCKET_EXPLOSION_WATER;
|
|
else
|
|
type = TE_EXPLOSION1_BIG;
|
|
gi.WriteByte (type);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (type, origin);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
/*static*/ void missile_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
self->takedamage = DAMAGE_NO;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->think = missile_explode;
|
|
}
|
|
|
|
void homing_think (edict_t *self);
|
|
void Rocket_Evade (edict_t *rocket, vec3_t dir, float speed);
|
|
void fire_missile (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage, edict_t *home_target)
|
|
{
|
|
edict_t *missile;
|
|
|
|
missile = G_Spawn();
|
|
VectorCopy (start, missile->s.origin);
|
|
VectorCopy (dir, missile->movedir);
|
|
vectoangles (dir, missile->s.angles);
|
|
VectorScale (dir, speed, missile->velocity);
|
|
// Lazarus: add shooter's lateral velocity
|
|
if (rocket_strafe->value)
|
|
{
|
|
vec3_t right, up;
|
|
vec3_t lateral_speed;
|
|
|
|
AngleVectors(self->s.angles,NULL,right,up);
|
|
VectorCopy(self->velocity,lateral_speed);
|
|
lateral_speed[0] *= fabs(right[0]);
|
|
lateral_speed[1] *= fabs(right[1]);
|
|
lateral_speed[2] *= fabs(up[2]);
|
|
VectorAdd(missile->velocity,lateral_speed,missile->velocity);
|
|
}
|
|
missile->movetype = MOVETYPE_FLYMISSILE;
|
|
missile->clipmask = MASK_SHOT;
|
|
missile->solid = SOLID_BBOX;
|
|
VectorSet (self->mins, -8, -8, -8);
|
|
VectorSet (self->maxs, 8, 8, 8);
|
|
missile->s.effects |= EF_ROCKET;
|
|
missile->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow
|
|
missile->s.renderfx |= RF_IR_VISIBLE;
|
|
VectorClear (missile->mins);
|
|
VectorClear (missile->maxs);
|
|
missile->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2");
|
|
missile->owner = self;
|
|
missile->touch = missile_touch;
|
|
missile->dmg = damage;
|
|
missile->radius_dmg = radius_damage;
|
|
missile->dmg_radius = damage_radius;
|
|
missile->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
|
|
if (home_target)
|
|
{
|
|
// homers are shootable
|
|
VectorSet(missile->mins, -10, -3, 0);
|
|
VectorSet(missile->maxs, 10, 3, 6);
|
|
missile->mass = 10;
|
|
missile->health = 5;
|
|
missile->die = missile_die;
|
|
missile->takedamage = DAMAGE_YES;
|
|
missile->monsterinfo.aiflags = AI_NOSTEP;
|
|
|
|
missile->enemy = home_target;
|
|
missile->classname = "homing rocket";
|
|
missile->class_id = ENTITY_ROCKET;
|
|
missile->nextthink = level.time + FRAMETIME;
|
|
missile->think = homing_think;
|
|
missile->starttime = level.time + 0.3; // play homing sound on 3rd frame
|
|
missile->endtime = level.time + 8000.0f/speed;
|
|
if (self->client)
|
|
{
|
|
self->client->homing_rocket = missile;
|
|
check_dodge (self, missile->s.origin, dir, speed);
|
|
}
|
|
Rocket_Evade (missile, dir, speed);
|
|
}
|
|
else
|
|
{
|
|
missile->classname = "missile";
|
|
missile->class_id = ENTITY_ROCKET;
|
|
missile->nextthink = level.time + 8000.0f/speed;
|
|
missile->think = G_FreeEdict;
|
|
Rocket_Evade (missile, dir, speed);
|
|
}
|
|
gi.linkentity (missile);
|
|
}
|
|
|
|
// NOTE: SP_missile should ONLY be used to spawn missiles that change maps
|
|
// via a trigger_transition. It should NOT be used for map entities.
|
|
|
|
void missile_delayed_start (edict_t *missile)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(missile->movedir,missile->moveinfo.speed,missile->velocity);
|
|
missile->nextthink = level.time + 8000/missile->moveinfo.speed;
|
|
missile->think = G_FreeEdict;
|
|
gi.linkentity(missile);
|
|
}
|
|
else
|
|
missile->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_missile (edict_t *missile)
|
|
{
|
|
vec3_t dir;
|
|
|
|
missile->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2");
|
|
missile->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
missile->touch = missile_touch;
|
|
AngleVectors(missile->s.angles,dir,NULL,NULL);
|
|
VectorCopy (dir, missile->movedir);
|
|
missile->moveinfo.speed = VectorLength(missile->velocity);
|
|
if (missile->moveinfo.speed <= 0)
|
|
missile->moveinfo.speed = 650;
|
|
|
|
// For SP, freeze missile until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
VectorClear(missile->velocity);
|
|
missile->think = missile_delayed_start;
|
|
missile->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
missile->think = G_FreeEdict;
|
|
missile->nextthink = level.time + 8000/missile->moveinfo.speed;
|
|
}
|
|
gi.linkentity (missile);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
fire_rocket
|
|
=================
|
|
*/
|
|
// Lazarus: homing rocket
|
|
void homing_think (edict_t *self)
|
|
{
|
|
trace_t tr;
|
|
vec3_t dir, target;
|
|
vec_t speed;
|
|
|
|
if (level.time > self->endtime)
|
|
{
|
|
if (self->owner->client && (self->owner->client->homing_rocket == self))
|
|
self->owner->client->homing_rocket = NULL;
|
|
BecomeExplosion1 (self);
|
|
return;
|
|
}
|
|
if (self->enemy && self->enemy->inuse)
|
|
{
|
|
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, target);
|
|
tr = gi.trace(self->s.origin, vec3_origin, vec3_origin, target, self, MASK_OPAQUE);
|
|
if (tr.fraction == 1)
|
|
{
|
|
// target in view; apply correction
|
|
VectorSubtract (target, self->s.origin, dir);
|
|
VectorNormalize (dir);
|
|
if (self->enemy->client)
|
|
VectorScale (dir, 0.8+0.1*skill->value, dir);
|
|
else
|
|
VectorScale (dir, 1.0, dir); // 0=no correction, 1=turn on a dime
|
|
VectorAdd (dir, self->movedir, dir);
|
|
VectorNormalize (dir);
|
|
VectorCopy (dir, self->movedir);
|
|
vectoangles (dir, self->s.angles);
|
|
speed = VectorLength(self->velocity);
|
|
VectorScale (dir, speed, self->velocity);
|
|
|
|
if (level.time >= self->starttime && self->starttime > 0)
|
|
{
|
|
if (level.time > self->owner->fly_sound_debounce_time)
|
|
{
|
|
// this prevents multiple lockon sounds resulting from
|
|
// monsters firing multiple rockets in quick succession
|
|
#ifdef KMQUAKE2_ENGINE_MOD // extra sound only with engine mod
|
|
if (self->enemy->client)
|
|
gi.sound (self->enemy, CHAN_AUTO, gi.soundindex ("weapons/homing/lockon.wav"), 1, ATTN_NORM, 0);
|
|
#endif
|
|
self->owner->fly_sound_debounce_time = level.time + 2.0;
|
|
}
|
|
self->starttime = 0;
|
|
}
|
|
}
|
|
}
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
// Lazarus: Rocket_Evade tells monsters to get da hell outta da way
|
|
|
|
void Rocket_Evade (edict_t *rocket, vec3_t dir, float speed)
|
|
{
|
|
float rocket_dist, best_r, best_yaw, dist, r;
|
|
float time;
|
|
float dot;
|
|
float yaw;
|
|
int i;
|
|
edict_t *ent=NULL;
|
|
trace_t tr;
|
|
vec3_t hitpoint;
|
|
vec3_t forward, pos, best_pos;
|
|
vec3_t rocket_vec, vec;
|
|
|
|
// Find out what rocket will hit, assuming everything remains static
|
|
VectorMA (rocket->s.origin, WORLD_SIZE, dir, rocket_vec); // was 8192
|
|
tr = gi.trace(rocket->s.origin, rocket->mins, rocket->maxs, rocket_vec, rocket,MASK_SHOT);
|
|
VectorCopy (tr.endpos, hitpoint);
|
|
VectorSubtract (hitpoint, rocket->s.origin, vec);
|
|
dist = VectorLength (vec);
|
|
time = dist / speed;
|
|
|
|
while ((ent = findradius(ent, hitpoint, rocket->dmg_radius)) != NULL)
|
|
{
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (!(ent->svflags & SVF_MONSTER))
|
|
continue;
|
|
if (!ent->takedamage)
|
|
continue;
|
|
if (ent->health <= 0)
|
|
continue;
|
|
if (!ent->monsterinfo.run) // takes care of turret_driver
|
|
continue;
|
|
if (rocket->owner == ent)
|
|
continue;
|
|
|
|
VectorSubtract (hitpoint, ent->s.origin, rocket_vec);
|
|
rocket_dist = VectorNormalize(rocket_vec);
|
|
|
|
// Not much hope in evading if distance is < 1K or so.
|
|
if (rocket_dist < 1024)
|
|
continue;
|
|
|
|
// Find best escape route.
|
|
best_r = 9999;
|
|
for (i=0; i<8; i++)
|
|
{
|
|
yaw = anglemod( i*45 );
|
|
forward[0] = cos( DEG2RAD(yaw) );
|
|
forward[1] = sin( DEG2RAD(yaw) );
|
|
forward[2] = 0;
|
|
dot = DotProduct(forward,dir);
|
|
if ((dot > 0.96) || (dot < -0.96))
|
|
continue;
|
|
// Estimate of required distance to run. This is conservative.
|
|
r = rocket->dmg_radius + rocket_dist*DotProduct(forward, rocket_vec) + ent->size[0] + 16;
|
|
if ( r < best_r )
|
|
{
|
|
VectorMA (ent->s.origin, r, forward ,pos);
|
|
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, pos, ent, MASK_MONSTERSOLID);
|
|
if (tr.fraction < 1.0)
|
|
continue;
|
|
best_r = r;
|
|
best_yaw = yaw;
|
|
VectorCopy (tr.endpos, best_pos);
|
|
}
|
|
}
|
|
if (best_r < 9000)
|
|
{
|
|
edict_t *thing = SpawnThing();
|
|
VectorCopy (best_pos, thing->s.origin);
|
|
thing->touch_debounce_time = level.time + time;
|
|
thing->target_ent = ent;
|
|
ED_CallSpawn (thing);
|
|
ent->ideal_yaw = best_yaw;
|
|
ent->movetarget = ent->goalentity = thing;
|
|
ent->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
|
|
ent->monsterinfo.aiflags |= (AI_CHASE_THING | AI_EVADE_GRENADE);
|
|
ent->monsterinfo.run(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
vec3_t origin;
|
|
int n;
|
|
int type;
|
|
|
|
if (other == ent->owner)
|
|
return;
|
|
|
|
if (ent->owner->client && (ent->owner->client->homing_rocket == ent))
|
|
ent->owner->client->homing_rocket = NULL;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
if (ent->owner && ent->owner->client)
|
|
PlayerNoise (ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// calculate position for the explosion entity
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
|
|
if (other->takedamage)
|
|
{
|
|
T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
|
|
}
|
|
else
|
|
{
|
|
// don't throw any debris in net games
|
|
if (!deathmatch->value && !coop->value)
|
|
{
|
|
if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
|
|
{
|
|
n = rand() % 5;
|
|
while (n--)
|
|
ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lazarus: bad monsters have a large damage radius
|
|
if (ent->owner && (ent->owner->svflags & SVF_MONSTER) && !(ent->owner->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
// T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius + 17.5*skill->value, MOD_R_SPLASH, -2.0/(4.0+skill->value) );
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius + 17.5*skill->value, MOD_R_SPLASH);
|
|
else
|
|
// T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH, -0.5);
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
|
|
gi.WriteByte (svc_temp_entity);
|
|
if (ent->waterlevel)
|
|
type = TE_ROCKET_EXPLOSION_WATER;
|
|
else
|
|
type = TE_ROCKET_EXPLOSION;
|
|
gi.WriteByte (type);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (type, origin);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
/*static*/ void rocket_explode (edict_t *ent)
|
|
{
|
|
vec3_t origin;
|
|
int type;
|
|
|
|
if (ent->owner->client)
|
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// calculate position for the explosion entity
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
|
|
// T_RadiusDamage(ent, ent->owner, ent->radius_dmg, NULL, ent->dmg_radius, MOD_R_SPLASH, -0.5);
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, NULL, ent->dmg_radius, MOD_R_SPLASH);
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
if (ent->waterlevel)
|
|
type = TE_ROCKET_EXPLOSION_WATER;
|
|
else
|
|
type = TE_ROCKET_EXPLOSION;
|
|
gi.WriteByte (type);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (type, origin);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
/*static*/ void rocket_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
self->takedamage = DAMAGE_NO;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->think = rocket_explode;
|
|
}
|
|
|
|
void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage, edict_t *home_target)
|
|
{
|
|
edict_t *rocket;
|
|
qboolean homing = false;
|
|
|
|
rocket = G_Spawn();
|
|
VectorCopy (start, rocket->s.origin);
|
|
VectorCopy (dir, rocket->movedir);
|
|
vectoangles (dir, rocket->s.angles);
|
|
VectorScale (dir, speed, rocket->velocity);
|
|
// Lazarus: add shooter's lateral velocity
|
|
if (rocket_strafe->value)
|
|
{
|
|
vec3_t right, up;
|
|
vec3_t lateral_speed;
|
|
|
|
AngleVectors (self->s.angles, NULL, right, up);
|
|
VectorCopy (self->velocity, lateral_speed);
|
|
lateral_speed[0] *= fabs(right[0]);
|
|
lateral_speed[1] *= fabs(right[1]);
|
|
lateral_speed[2] *= fabs(up[2]);
|
|
VectorAdd(rocket->velocity,lateral_speed,rocket->velocity);
|
|
}
|
|
|
|
rocket->movetype = MOVETYPE_FLYMISSILE;
|
|
rocket->clipmask = MASK_SHOT;
|
|
rocket->solid = SOLID_BBOX;
|
|
rocket->s.effects |= EF_ROCKET;
|
|
rocket->s.renderfx |= RF_IR_VISIBLE|RF_NOSHADOW; // Knightmare- no shadow
|
|
VectorClear (rocket->mins);
|
|
VectorClear (rocket->maxs);
|
|
// if (home_target)
|
|
// rocket->s.modelindex = gi.modelindex ("models/objects/hrocket/tris.md2");
|
|
// else
|
|
rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
|
|
|
|
if (strstr(self->classname,"monster_") && (self->spawnflags & SF_MONSTER_SPECIAL)
|
|
&& strcmp(self->classname, "monster_turret"))
|
|
homing = true;
|
|
// Knightmare- use different skin, not different model, for homing rocket
|
|
if (home_target || (self->client && self->client->pers.fire_mode) || homing)
|
|
{
|
|
rocket->s.skinnum = 1;
|
|
rocket->classname = "homing rocket";
|
|
}
|
|
else
|
|
rocket->classname = "rocket";
|
|
rocket->class_id = ENTITY_ROCKET;
|
|
|
|
rocket->owner = self;
|
|
rocket->touch = rocket_touch;
|
|
rocket->dmg = damage;
|
|
rocket->radius_dmg = radius_damage;
|
|
rocket->dmg_radius = damage_radius;
|
|
rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
|
|
if (home_target)
|
|
{
|
|
// homers are shootable
|
|
VectorSet(rocket->mins, -10, -3, 0);
|
|
VectorSet(rocket->maxs, 10, 3, 6);
|
|
rocket->mass = 10;
|
|
rocket->health = 5;
|
|
rocket->die = rocket_die;
|
|
rocket->takedamage = DAMAGE_YES;
|
|
rocket->monsterinfo.aiflags = AI_NOSTEP;
|
|
|
|
rocket->enemy = home_target;
|
|
rocket->nextthink = level.time + FRAMETIME;
|
|
rocket->think = homing_think;
|
|
rocket->starttime = level.time + 0.3; // play homing sound on 3rd frame
|
|
rocket->endtime = level.time + 8000.0f/speed;
|
|
|
|
if (self->client)
|
|
{
|
|
self->client->homing_rocket = rocket;
|
|
// check_dodge (self, rocket->s.origin, dir, speed);
|
|
}
|
|
Rocket_Evade (rocket, dir, speed);
|
|
}
|
|
else
|
|
{
|
|
rocket->nextthink = level.time + 8000.0f/speed;
|
|
rocket->think = G_FreeEdict;
|
|
Rocket_Evade (rocket, dir, speed);
|
|
}
|
|
|
|
gi.linkentity (rocket);
|
|
}
|
|
|
|
// NOTE: SP_rocket should ONLY be used to spawn rockets that change maps
|
|
// via a trigger_transition. It should NOT be used for map entities.
|
|
|
|
void rocket_delayed_start (edict_t *rocket)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(rocket->movedir,rocket->moveinfo.speed,rocket->velocity);
|
|
rocket->nextthink = level.time + 8000/rocket->moveinfo.speed;
|
|
rocket->think = G_FreeEdict;
|
|
gi.linkentity(rocket);
|
|
}
|
|
else
|
|
rocket->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_rocket (edict_t *rocket)
|
|
{
|
|
vec3_t dir;
|
|
|
|
rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
|
|
if (!strcmp(rocket->classname, "homing rocket"))
|
|
rocket->s.skinnum = 1;
|
|
rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
rocket->touch = rocket_touch;
|
|
AngleVectors(rocket->s.angles,dir,NULL,NULL);
|
|
VectorCopy (dir, rocket->movedir);
|
|
rocket->moveinfo.speed = VectorLength(rocket->velocity);
|
|
if (rocket->moveinfo.speed <= 0)
|
|
rocket->moveinfo.speed = 650;
|
|
|
|
// For SP, freeze rocket until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
VectorClear(rocket->velocity);
|
|
rocket->think = rocket_delayed_start;
|
|
rocket->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
rocket->think = G_FreeEdict;
|
|
rocket->nextthink = level.time + 8000/rocket->moveinfo.speed;
|
|
}
|
|
gi.linkentity (rocket);
|
|
}
|
|
|
|
/*void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
vec3_t origin;
|
|
int n;
|
|
|
|
if (other == ent->owner)
|
|
return;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
if (ent->owner->client)
|
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// calculate position for the explosion entity
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
|
|
if (other->takedamage)
|
|
{
|
|
T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
|
|
}
|
|
else
|
|
{
|
|
// don't throw any debris in net games
|
|
if (!deathmatch->value && !coop->value)
|
|
{
|
|
if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
|
|
{
|
|
n = rand() % 5;
|
|
while (n--)
|
|
ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
if (ent->waterlevel)
|
|
gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
|
|
else
|
|
gi.WriteByte (TE_ROCKET_EXPLOSION);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
|
|
{
|
|
edict_t *rocket;
|
|
|
|
rocket = G_Spawn();
|
|
VectorCopy (start, rocket->s.origin);
|
|
VectorCopy (dir, rocket->movedir);
|
|
vectoangles (dir, rocket->s.angles);
|
|
VectorScale (dir, speed, rocket->velocity);
|
|
rocket->movetype = MOVETYPE_FLYMISSILE;
|
|
rocket->clipmask = MASK_SHOT;
|
|
rocket->solid = SOLID_BBOX;
|
|
rocket->s.effects |= EF_ROCKET;
|
|
VectorClear (rocket->mins);
|
|
VectorClear (rocket->maxs);
|
|
rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
|
|
rocket->owner = self;
|
|
rocket->touch = rocket_touch;
|
|
rocket->nextthink = level.time + 8000.0f/speed;
|
|
rocket->think = G_FreeEdict;
|
|
rocket->dmg = damage;
|
|
rocket->radius_dmg = radius_damage;
|
|
rocket->dmg_radius = damage_radius;
|
|
rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
rocket->classname = "rocket";
|
|
|
|
if (self->client)
|
|
check_dodge (self, rocket->s.origin, dir, speed);
|
|
|
|
gi.linkentity (rocket);
|
|
}*/
|
|
|
|
/*
|
|
=================
|
|
fire_rail
|
|
=================
|
|
*/
|
|
void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
|
|
{
|
|
vec3_t from, end;
|
|
trace_t tr;
|
|
edict_t *ignore;
|
|
int mask, tempevent, i=0;
|
|
qboolean water;
|
|
|
|
// Knightmare- changeable trail color
|
|
//#ifdef KMQUAKE2_ENGINE_MOD
|
|
#if defined (KMQUAKE2_ENGINE_MOD) || defined (Q2E_ENGINE_MOD)
|
|
if ( (self->client) && (sk_rail_color->value == 2) )
|
|
tempevent = TE_RAILTRAIL2;
|
|
else
|
|
#endif
|
|
tempevent = TE_RAILTRAIL;
|
|
|
|
VectorMA (start, WORLD_SIZE, aimdir, end); // was 8192
|
|
VectorCopy (start, from);
|
|
ignore = self;
|
|
water = false;
|
|
|
|
// Zaero- hack for certain maps to shoot through windows
|
|
if ( (self->client) && level.isZaeroRailgunHackMap )
|
|
mask = MASK_SHOT_NO_WINDOW|CONTENTS_SLIME|CONTENTS_LAVA;
|
|
else
|
|
mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
|
|
// end Zaero
|
|
|
|
while (ignore && i<256)
|
|
{
|
|
tr = gi.trace (from, NULL, NULL, end, ignore, mask);
|
|
|
|
if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA))
|
|
{
|
|
mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA);
|
|
water = true;
|
|
}
|
|
else
|
|
{
|
|
// ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc)
|
|
if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) ||
|
|
(tr.ent->svflags & SVF_DAMAGEABLE) ||
|
|
(tr.ent->solid == SOLID_BBOX))
|
|
ignore = tr.ent;
|
|
else
|
|
ignore = NULL;
|
|
|
|
if ((tr.ent != self) && (tr.ent->takedamage))
|
|
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
|
|
}
|
|
|
|
VectorCopy (tr.endpos, from);
|
|
i++;
|
|
}
|
|
|
|
// send gun puff / flash
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (tempevent);
|
|
gi.WritePosition (start);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.multicast (self->s.origin, MULTICAST_PHS);
|
|
// gi.multicast (start, MULTICAST_PHS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectTrail (tempevent, start, tr.endpos);
|
|
|
|
if (water)
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (tempevent);
|
|
gi.WritePosition (start);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.multicast (tr.endpos, MULTICAST_PHS);
|
|
}
|
|
|
|
if (self->client)
|
|
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
fire_bfg
|
|
=================
|
|
*/
|
|
void bfg_explode (edict_t *self)
|
|
{
|
|
edict_t *ent;
|
|
float points;
|
|
vec3_t v;
|
|
float dist;
|
|
|
|
if (self->s.frame == 0)
|
|
{
|
|
// the BFG effect
|
|
ent = NULL;
|
|
while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
|
|
{
|
|
if (!ent->takedamage)
|
|
continue;
|
|
if (ent == self->owner)
|
|
continue;
|
|
if (!CanDamage (ent, self))
|
|
continue;
|
|
if (!CanDamage (ent, self->owner))
|
|
continue;
|
|
|
|
VectorAdd (ent->mins, ent->maxs, v);
|
|
VectorMA (ent->s.origin, 0.5, v, v);
|
|
VectorSubtract (self->s.origin, v, v);
|
|
dist = VectorLength(v);
|
|
points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius));
|
|
// PMM - happened to notice this copy/paste bug
|
|
// if (ent == self->owner)
|
|
// points = points * 0.5;
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_BFG_EXPLOSION);
|
|
gi.WritePosition (ent->s.origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PHS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (TE_BFG_EXPLOSION, ent->s.origin);
|
|
|
|
T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
|
|
}
|
|
}
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->s.frame++;
|
|
if (self->s.frame == 5)
|
|
self->think = G_FreeEdict;
|
|
}
|
|
|
|
void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if (other == self->owner)
|
|
return;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
if (self->owner->client)
|
|
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
|
|
|
|
// core explosion - prevents firing it into the wall/floor
|
|
if (other->takedamage)
|
|
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, sk_bfg_rdamage->value, 0, 0, MOD_BFG_BLAST); //was 200
|
|
T_RadiusDamage(self, self->owner, sk_bfg_rdamage->value, other, 100, MOD_BFG_BLAST); //was 200
|
|
|
|
gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
|
|
self->solid = SOLID_NOT;
|
|
self->touch = NULL;
|
|
VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin);
|
|
VectorClear (self->velocity);
|
|
self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2");
|
|
self->s.frame = 0;
|
|
self->s.sound = 0;
|
|
self->s.effects &= ~EF_ANIM_ALLFAST;
|
|
self->think = bfg_explode;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->enemy = other;
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_BFG_BIGEXPLOSION);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (TE_BFG_BIGEXPLOSION, self->s.origin);
|
|
}
|
|
|
|
|
|
void bfg_think (edict_t *self)
|
|
{
|
|
edict_t *ent;
|
|
edict_t *ignore;
|
|
vec3_t point;
|
|
vec3_t dir;
|
|
vec3_t start;
|
|
vec3_t end;
|
|
int dmg;
|
|
trace_t tr;
|
|
|
|
if (deathmatch->value)
|
|
dmg = sk_bfg_damage2_dm->value; //was 5
|
|
else
|
|
dmg = sk_bfg_damage2->value; //was 10
|
|
|
|
ent = NULL;
|
|
while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
|
|
{
|
|
if (ent == self)
|
|
continue;
|
|
|
|
if (ent == self->owner)
|
|
continue;
|
|
|
|
if (!ent->takedamage)
|
|
continue;
|
|
|
|
//ROGUE - make tesla hurt by bfg
|
|
if (!(ent->svflags & SVF_MONSTER) && !(ent->svflags & SVF_DAMAGEABLE) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
|
|
// if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)
|
|
// && (strcmp(ent->classname, "tesla") != 0))
|
|
continue;
|
|
|
|
VectorMA (ent->absmin, 0.5, ent->size, point);
|
|
|
|
VectorSubtract (point, self->s.origin, dir);
|
|
VectorNormalize (dir);
|
|
|
|
ignore = self;
|
|
VectorCopy (self->s.origin, start);
|
|
VectorMA (start, 2048, dir, end);
|
|
while (1)
|
|
{
|
|
tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
|
|
|
|
if (!tr.ent)
|
|
break;
|
|
|
|
// hurt it if we can
|
|
if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
|
|
T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
|
|
|
|
// if we hit something that's not a monster or player we're done
|
|
// if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
|
if (!(tr.ent->svflags & SVF_MONSTER) && !(tr.ent->svflags & SVF_DAMAGEABLE) && (!tr.ent->client))
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_LASER_SPARKS);
|
|
gi.WriteByte (4);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.WriteDir (tr.plane.normal);
|
|
gi.WriteByte (self->s.skinnum);
|
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
|
break;
|
|
}
|
|
|
|
ignore = tr.ent;
|
|
VectorCopy (tr.endpos, start);
|
|
}
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_BFG_LASER);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.multicast (self->s.origin, MULTICAST_PHS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectTrail (TE_BFG_LASER, self->s.origin, tr.endpos);
|
|
}
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
|
|
void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius)
|
|
{
|
|
edict_t *bfg;
|
|
|
|
bfg = G_Spawn();
|
|
VectorCopy (start, bfg->s.origin);
|
|
VectorCopy (dir, bfg->movedir);
|
|
vectoangles (dir, bfg->s.angles);
|
|
VectorScale (dir, speed, bfg->velocity);
|
|
bfg->movetype = MOVETYPE_FLYMISSILE;
|
|
bfg->clipmask = MASK_SHOT;
|
|
bfg->solid = SOLID_BBOX;
|
|
bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
|
|
VectorClear (bfg->mins);
|
|
VectorClear (bfg->maxs);
|
|
bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2");
|
|
bfg->owner = self;
|
|
bfg->touch = bfg_touch;
|
|
bfg->nextthink = level.time + 8000.0f/speed;
|
|
bfg->think = G_FreeEdict;
|
|
bfg->radius_dmg = damage;
|
|
bfg->dmg_radius = damage_radius;
|
|
bfg->classname = "bfg blast";
|
|
bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav");
|
|
|
|
bfg->think = bfg_think;
|
|
bfg->nextthink = level.time + FRAMETIME;
|
|
bfg->teammaster = bfg;
|
|
bfg->teamchain = NULL;
|
|
|
|
if (self->client)
|
|
check_dodge (self, bfg->s.origin, dir, speed);
|
|
|
|
gi.linkentity (bfg);
|
|
}
|
|
|
|
// RAFAEL
|
|
|
|
/*
|
|
fire_ionripper
|
|
*/
|
|
|
|
void ionripper_sparks (edict_t *self)
|
|
{
|
|
byte count, color;
|
|
|
|
count = 0;
|
|
color = 0xe4 + (rand()&3);
|
|
|
|
// Knightmare- explode sound
|
|
if (sk_ionripper_extra_sounds->value)
|
|
gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/ionexp.wav"), 1, ATTN_NONE, 0);
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_WELDING_SPARKS);
|
|
gi.WriteByte (count); // 0
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WriteDir (vec3_origin);
|
|
gi.WriteByte (color); // 0xe4 + (rand()&3)
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectWeldingSparks (count, color, self->s.origin, vec3_origin);
|
|
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
// RAFAEL
|
|
void ionripper_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
if (other == self->owner)
|
|
return;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
if (self->owner->client)
|
|
PlayerNoise (self->owner, self->s.origin, PNOISE_IMPACT);
|
|
|
|
if (other->takedamage)
|
|
{
|
|
// Knightmare- hit sound
|
|
if (sk_ionripper_extra_sounds->value)
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
{
|
|
float r = random();
|
|
if (r < 0.3333)
|
|
gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/ionhit1.wav"), 1, ATTN_NONE, 0);
|
|
else if (r < 0.6666)
|
|
gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/ionhit2.wav"), 1, ATTN_NONE, 0);
|
|
else
|
|
gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/ionhit3.wav"), 1, ATTN_NONE, 0);
|
|
}
|
|
#else
|
|
gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/ionhit1.wav"), 1, ATTN_NONE, 0);
|
|
#endif
|
|
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
|
|
// RAFAEL
|
|
void fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
|
|
{
|
|
edict_t *ion;
|
|
trace_t tr;
|
|
|
|
VectorNormalize (dir);
|
|
|
|
ion = G_Spawn ();
|
|
ion->classname = "ion";
|
|
ion->class_id = ENTITY_ION;
|
|
VectorCopy (start, ion->s.origin);
|
|
VectorCopy (start, ion->s.old_origin);
|
|
vectoangles (dir, ion->s.angles);
|
|
VectorScale (dir, speed, ion->velocity);
|
|
|
|
ion->movetype = MOVETYPE_WALLBOUNCE;
|
|
ion->clipmask = MASK_SHOT;
|
|
ion->solid = SOLID_BBOX;
|
|
ion->s.effects |= effect;
|
|
|
|
ion->s.renderfx |= RF_FULLBRIGHT | RF_NOSHADOW; // Knightmare- no shadow
|
|
|
|
VectorClear (ion->mins);
|
|
VectorClear (ion->maxs);
|
|
ion->s.modelindex = gi.modelindex ("models/objects/boomrang/tris.md2");
|
|
ion->s.sound = gi.soundindex ("misc/lasfly.wav");
|
|
ion->owner = self;
|
|
ion->touch = ionripper_touch;
|
|
ion->nextthink = level.time + 3;
|
|
ion->think = ionripper_sparks;
|
|
ion->dmg = damage;
|
|
ion->dmg_radius = 100;
|
|
gi.linkentity (ion);
|
|
|
|
if (self->client)
|
|
check_dodge (self, ion->s.origin, dir, speed);
|
|
|
|
tr = gi.trace (self->s.origin, NULL, NULL, ion->s.origin, ion, MASK_SHOT);
|
|
if (tr.fraction < 1.0)
|
|
{
|
|
VectorMA (ion->s.origin, -10, dir, ion->s.origin);
|
|
ion->touch (ion, tr.ent, NULL, NULL);
|
|
}
|
|
|
|
}
|
|
|
|
// NOTE: SP_ion should ONLY be used for ION Ripper projectiles that have
|
|
// changed maps via trigger_transition. It should NOT be used for map
|
|
// entities.
|
|
void ion_delayed_start (edict_t *ion)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(ion->movedir,ion->moveinfo.speed,ion->velocity);
|
|
ion->nextthink = level.time + 3;
|
|
ion->think = ionripper_sparks;
|
|
gi.linkentity(ion);
|
|
}
|
|
else
|
|
ion->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_ion (edict_t *ion)
|
|
{
|
|
ion->s.modelindex = gi.modelindex ("models/objects/boomrang/tris.md2");
|
|
ion->s.sound = gi.soundindex ("misc/lasfly.wav");
|
|
ion->touch = ionripper_touch;
|
|
VectorCopy(ion->velocity,ion->movedir);
|
|
VectorNormalize(ion->movedir);
|
|
ion->moveinfo.speed = VectorLength(ion->velocity);
|
|
VectorClear(ion->velocity);
|
|
ion->think = ion_delayed_start;
|
|
ion->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(ion);
|
|
}
|
|
|
|
// NOTE: the new Rogue fire_heat is in g_newweap.c
|
|
/*
|
|
fire_rocket_heat
|
|
*/
|
|
void rocket_heat_think (edict_t *self)
|
|
{
|
|
edict_t *target = NULL;
|
|
edict_t *aquire = NULL;
|
|
vec3_t vec;
|
|
vec3_t oldang;
|
|
int len;
|
|
int oldlen = 0;
|
|
|
|
VectorClear (vec);
|
|
|
|
// aquire new target
|
|
while (( target = findradius (target, self->s.origin, 1024)) != NULL)
|
|
{
|
|
|
|
if (self->owner == target)
|
|
continue;
|
|
if (!target->svflags & SVF_MONSTER)
|
|
continue;
|
|
//If player fires this, do track monsters
|
|
if (!self->owner->client && !target->client)
|
|
continue;
|
|
if (target->health <= 0)
|
|
continue;
|
|
if (!visible (self, target))
|
|
continue;
|
|
|
|
// if we need to reduce the tracking cone
|
|
/*
|
|
{
|
|
vec3_t vec;
|
|
float dot;
|
|
vec3_t forward;
|
|
|
|
AngleVectors (self->s.angles, forward, NULL, NULL);
|
|
VectorSubtract (target->s.origin, self->s.origin, vec);
|
|
VectorNormalize (vec);
|
|
dot = DotProduct (vec, forward);
|
|
|
|
if (dot > 0.6)
|
|
continue;
|
|
}
|
|
*/
|
|
|
|
if (!infront (self, target))
|
|
continue;
|
|
|
|
VectorSubtract (self->s.origin, target->s.origin, vec);
|
|
len = VectorLength (vec);
|
|
|
|
if (aquire == NULL || len < oldlen)
|
|
{
|
|
aquire = target;
|
|
self->target_ent = aquire;
|
|
oldlen = len;
|
|
}
|
|
}
|
|
|
|
if (aquire != NULL)
|
|
{
|
|
VectorCopy (self->s.angles, oldang);
|
|
VectorSubtract (aquire->s.origin, self->s.origin, vec);
|
|
|
|
vectoangles (vec, self->s.angles);
|
|
|
|
VectorNormalize (vec);
|
|
VectorCopy (vec, self->movedir);
|
|
VectorScale (vec, 500, self->velocity);
|
|
}
|
|
|
|
self->nextthink = level.time + 0.1;
|
|
}
|
|
|
|
// RAFAEL
|
|
void fire_rocket_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
|
|
{
|
|
edict_t *heat;
|
|
|
|
heat = G_Spawn();
|
|
heat->classname = "rocket";
|
|
heat->class_id = ENTITY_ROCKET;
|
|
VectorCopy (start, heat->s.origin);
|
|
VectorCopy (dir, heat->movedir);
|
|
vectoangles (dir, heat->s.angles);
|
|
VectorScale (dir, speed, heat->velocity);
|
|
heat->movetype = MOVETYPE_FLYMISSILE;
|
|
heat->clipmask = MASK_SHOT;
|
|
heat->solid = SOLID_BBOX;
|
|
heat->s.effects |= EF_ROCKET;
|
|
heat->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow
|
|
VectorClear (heat->mins);
|
|
VectorClear (heat->maxs);
|
|
// heat->s.modelindex = gi.modelindex ("models/objects/hrocket/tris.md2");
|
|
heat->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
|
|
heat->s.skinnum = 1;
|
|
heat->owner = self;
|
|
heat->touch = rocket_touch;
|
|
|
|
heat->nextthink = level.time + 0.1;
|
|
heat->think = rocket_heat_think;
|
|
|
|
heat->dmg = damage;
|
|
heat->radius_dmg = radius_damage;
|
|
heat->dmg_radius = damage_radius;
|
|
heat->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
|
|
if (self->client)
|
|
check_dodge (self, heat->s.origin, dir, speed);
|
|
|
|
gi.linkentity (heat);
|
|
}
|
|
|
|
// RAFAEL
|
|
/*
|
|
fire_phalanx_plasma
|
|
*/
|
|
void phalanx_plasma_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
vec3_t origin;
|
|
|
|
if (other == ent->owner)
|
|
return;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
if (ent->owner->client)
|
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// calculate position for the explosion entity
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
|
|
if (other->takedamage)
|
|
{
|
|
T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_PHALANX);
|
|
}
|
|
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_PHALANX_SPLASH);
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_PLASMA_EXPLOSION);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (TE_PLASMA_EXPLOSION, ent->s.origin);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
// Knightmare- remove this after the next savegame version increment!
|
|
void plasma_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
phalanx_plasma_touch (ent, other, plane, surf);
|
|
}
|
|
|
|
// RAFAEL
|
|
void fire_phalanx_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
|
|
{
|
|
edict_t *ph_plasma;
|
|
|
|
ph_plasma = G_Spawn();
|
|
ph_plasma->classname = "phalanx_plasma";
|
|
ph_plasma->class_id = ENTITY_PLASMA;
|
|
VectorCopy (start, ph_plasma->s.origin);
|
|
VectorCopy (dir, ph_plasma->movedir);
|
|
vectoangles (dir, ph_plasma->s.angles);
|
|
VectorScale (dir, speed, ph_plasma->velocity);
|
|
ph_plasma->movetype = MOVETYPE_FLYMISSILE;
|
|
ph_plasma->clipmask = MASK_SHOT;
|
|
ph_plasma->solid = SOLID_BBOX;
|
|
|
|
VectorClear (ph_plasma->mins);
|
|
VectorClear (ph_plasma->maxs);
|
|
|
|
ph_plasma->owner = self;
|
|
ph_plasma->touch = phalanx_plasma_touch;
|
|
ph_plasma->nextthink = level.time + 8000.0f/speed;
|
|
ph_plasma->think = G_FreeEdict;
|
|
ph_plasma->dmg = damage;
|
|
ph_plasma->radius_dmg = radius_damage;
|
|
ph_plasma->dmg_radius = damage_radius;
|
|
ph_plasma->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
|
|
ph_plasma->s.modelindex = gi.modelindex ("sprites/s_photon.sp2");
|
|
ph_plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST;
|
|
ph_plasma->s.renderfx |= RF_FULLBRIGHT;
|
|
|
|
if (self->client)
|
|
check_dodge (self, ph_plasma->s.origin, dir, speed);
|
|
|
|
gi.linkentity (ph_plasma);
|
|
}
|
|
|
|
// NOTE: SP_phalanx_plasma should ONLY be used to spawn phalanx magslugs that change maps
|
|
// via a trigger_transition. It should NOT be used for map entities.
|
|
|
|
void phalanx_plasma_delayed_start (edict_t *ph_plasma)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(ph_plasma->movedir, ph_plasma->moveinfo.speed, ph_plasma->velocity);
|
|
ph_plasma->nextthink = level.time + 8000.0f / ph_plasma->moveinfo.speed;
|
|
ph_plasma->think = G_FreeEdict;
|
|
gi.linkentity(ph_plasma);
|
|
}
|
|
else
|
|
ph_plasma->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_phalanx_plasma (edict_t *ph_plasma)
|
|
{
|
|
vec3_t dir;
|
|
|
|
ph_plasma->s.modelindex = gi.modelindex ("sprites/s_photon.sp2");
|
|
ph_plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST;
|
|
ph_plasma->s.sound = gi.soundindex ("weapons/rockfly.wav");
|
|
ph_plasma->touch = phalanx_plasma_touch;
|
|
AngleVectors(ph_plasma->s.angles, dir, NULL, NULL);
|
|
VectorCopy (dir, ph_plasma->movedir);
|
|
ph_plasma->moveinfo.speed = VectorLength(ph_plasma->velocity);
|
|
if (ph_plasma->moveinfo.speed <= 0)
|
|
ph_plasma->moveinfo.speed = 650;
|
|
|
|
// For SP, freeze plasma until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
VectorClear(ph_plasma->velocity);
|
|
ph_plasma->think = phalanx_plasma_delayed_start;
|
|
ph_plasma->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
ph_plasma->think = G_FreeEdict;
|
|
ph_plasma->nextthink = level.time + 8000.0f / ph_plasma->moveinfo.speed;
|
|
}
|
|
gi.linkentity (ph_plasma);
|
|
}
|
|
|
|
// RAFAEL
|
|
extern void SP_item_foodcube (edict_t *best);
|
|
// RAFAEL
|
|
|
|
extern int gibsthisframe;
|
|
extern int lastgibframe;
|
|
|
|
/*static*/ void Trap_Think (edict_t *ent)
|
|
{
|
|
edict_t *target = NULL;
|
|
edict_t *best = NULL;
|
|
vec3_t vec;
|
|
int len, i;
|
|
int oldlen = 8000;
|
|
//vec3_t forward, right, up;
|
|
|
|
if (ent->timestamp < level.time)
|
|
{
|
|
ent->s.frame = 6;
|
|
//BecomeExplosion1(ent);
|
|
// note to self
|
|
// cause explosion damage???
|
|
return;
|
|
}
|
|
|
|
ent->nextthink = level.time + 0.1;
|
|
|
|
if (!ent->groundentity)
|
|
return;
|
|
|
|
// ok lets do the blood effect
|
|
if (ent->s.frame > 4)
|
|
{
|
|
if (ent->s.frame == 5)
|
|
{
|
|
if (ent->wait == 64)
|
|
gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_IDLE, 0);
|
|
|
|
ent->wait -= 2;
|
|
ent->delay += level.time;
|
|
|
|
for (i=0; i<3; i++)
|
|
{
|
|
|
|
// Knightmare- forget this, enough gibs are spawned already
|
|
// Lazarus: Prevent gib showers from causing SZ_GetSpace: overflow
|
|
/* if (level.framenum > lastgibframe)
|
|
{
|
|
gibsthisframe = 0;
|
|
lastgibframe = level.framenum;
|
|
}
|
|
gibsthisframe++;
|
|
if (gibsthisframe <= sv_maxgibs->value)
|
|
{
|
|
best = G_Spawn();
|
|
|
|
//if (strcmp (ent->enemy->classname, "monster_gekk") == 0)
|
|
if (ent->enemy->blood_type == 1)
|
|
{
|
|
best->s.modelindex = gi.modelindex ("models/objects/gekkgib/torso/tris.md2");
|
|
// best->s.effects |= TE_GREENBLOOD;
|
|
best->s.effects |= EF_GREENGIB|EF_BLASTER;
|
|
}
|
|
// Kngihtmare- added
|
|
else if (strcmp (ent->enemy->classname, "monster_fixbot") == 0)
|
|
{
|
|
best->s.modelindex = gi.modelindex ("models/objects/debris1/tris.md2");
|
|
best->s.effects |= EF_GRENADE;
|
|
}
|
|
else if (ent->enemy->mass > 200)
|
|
{
|
|
best->s.modelindex = gi.modelindex ("models/objects/gibs/chest/tris.md2");
|
|
// best->s.effects |= TE_BLOOD;
|
|
best->s.effects |= EF_GIB;
|
|
}
|
|
else
|
|
{
|
|
best->s.modelindex = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2");
|
|
// best->s.effects |= TE_BLOOD;
|
|
best->s.effects |= EF_GIB;
|
|
}
|
|
|
|
AngleVectors (ent->s.angles, forward, right, up);
|
|
|
|
RotatePointAroundVector( vec, up, right, ((360.0/3)* i)+ent->delay);
|
|
VectorMA (vec, ent->wait/2, vec, vec);
|
|
VectorAdd(vec, ent->s.origin, vec);
|
|
VectorAdd(vec, forward, best->s.origin);
|
|
|
|
best->s.origin[2] = ent->s.origin[2] + ent->wait;
|
|
|
|
VectorCopy (ent->s.angles, best->s.angles);
|
|
|
|
best->solid = SOLID_NOT;
|
|
best->s.effects |= EF_GIB;
|
|
best->takedamage = DAMAGE_YES;
|
|
|
|
best->movetype = MOVETYPE_TOSS;
|
|
best->svflags |= SVF_MONSTER;
|
|
best->deadflag = DEAD_DEAD;
|
|
|
|
VectorClear (best->mins);
|
|
VectorClear (best->maxs);
|
|
|
|
best->watertype = gi.pointcontents(best->s.origin);
|
|
if (best->watertype & MASK_WATER)
|
|
best->waterlevel = 1;
|
|
|
|
//best->nextthink = level.time + 0.1;
|
|
best->nextthink = level.time + 10 + random()*10;
|
|
//best->think = G_FreeEdict;
|
|
best->think = gib_fade;
|
|
gi.linkentity (best);
|
|
} */
|
|
|
|
best = G_Spawn ();
|
|
VectorCopy (ent->s.origin, best->s.origin);
|
|
best->s.origin[2]+= 32;
|
|
SP_item_foodcube (best);
|
|
best->velocity[2] = 400;
|
|
best->count = 20; //was best->mass
|
|
gi.linkentity (best);
|
|
if (ent->timestamp < (level.time - 1)) //close if time is just about up
|
|
ent->s.frame++;
|
|
else //go back to previous state
|
|
ent->s.frame = 4;
|
|
}
|
|
|
|
//if (ent->wait < 19)
|
|
// ent->s.frame++;
|
|
|
|
return;
|
|
}
|
|
ent->s.frame ++;
|
|
if (ent->s.frame == 8)
|
|
{
|
|
ent->nextthink = level.time + 1.0;
|
|
ent->think = G_FreeEdict;
|
|
|
|
/* best = G_Spawn ();
|
|
SP_item_foodcube (best);
|
|
VectorCopy (ent->s.origin, best->s.origin);
|
|
best->s.origin[2]+= 16;
|
|
best->velocity[2] = 400;
|
|
best->count = ent->mass;
|
|
gi.linkentity (best);
|
|
*/
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
ent->s.effects &= ~EF_TRAP;
|
|
if (ent->s.frame >= 4)
|
|
{
|
|
ent->s.effects |= EF_TRAP;
|
|
VectorClear (ent->mins);
|
|
VectorClear (ent->maxs);
|
|
|
|
}
|
|
|
|
if (ent->s.frame < 4)
|
|
ent->s.frame++;
|
|
|
|
while ((target = findradius(target, ent->s.origin, 256)) != NULL)
|
|
{
|
|
if (target == ent)
|
|
continue;
|
|
if (!(target->svflags & SVF_MONSTER) && !target->client)
|
|
continue;
|
|
// if (target == ent->owner)
|
|
// continue;
|
|
if (target->health <= 0)
|
|
continue;
|
|
if (!visible (ent, target))
|
|
continue;
|
|
if (!best)
|
|
{
|
|
best = target;
|
|
continue;
|
|
}
|
|
VectorSubtract (ent->s.origin, target->s.origin, vec);
|
|
len = VectorLength (vec);
|
|
if (len < oldlen)
|
|
{
|
|
oldlen = len;
|
|
best = target;
|
|
}
|
|
}
|
|
|
|
// pull the enemy in
|
|
if (best)
|
|
{
|
|
vec3_t forward;
|
|
|
|
if (best->groundentity)
|
|
{
|
|
best->s.origin[2] += 1;
|
|
best->groundentity = NULL;
|
|
}
|
|
VectorSubtract (ent->s.origin, best->s.origin, vec);
|
|
len = VectorLength (vec);
|
|
if (best->client)
|
|
{
|
|
VectorNormalize (vec);
|
|
VectorMA (best->velocity, 250, vec, best->velocity);
|
|
}
|
|
else
|
|
{
|
|
best->ideal_yaw = vectoyaw(vec);
|
|
M_ChangeYaw (best);
|
|
AngleVectors (best->s.angles, forward, NULL, NULL);
|
|
VectorScale (forward, 256, best->velocity);
|
|
}
|
|
|
|
gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapsuck.wav"), 1, ATTN_IDLE, 0);
|
|
|
|
if (len < 32)
|
|
{
|
|
if (best->mass <= 400)
|
|
{
|
|
T_Damage (best, ent, ent->owner, vec3_origin, best->s.origin, vec3_origin, 100000, 1, 0, MOD_TRAP);
|
|
ent->enemy = best;
|
|
ent->wait = 64;
|
|
VectorCopy (ent->s.origin, ent->s.old_origin);
|
|
ent->timestamp = level.time + 600;
|
|
if (deathmatch->value)
|
|
ent->mass = best->mass/4;
|
|
else
|
|
ent->mass = best->mass/10;
|
|
// ok spawn the food cube
|
|
ent->s.frame = 5;
|
|
}
|
|
else
|
|
{
|
|
BecomeExplosion1(ent);
|
|
// note to self
|
|
// cause explosion damage???
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// RAFAEL
|
|
void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
|
|
{
|
|
edict_t *trap;
|
|
vec3_t dir;
|
|
vec3_t forward, right, up;
|
|
|
|
vectoangles (aimdir, dir);
|
|
AngleVectors (dir, forward, right, up);
|
|
|
|
trap = G_Spawn();
|
|
trap->classname = "trap";
|
|
VectorCopy (start, trap->s.origin);
|
|
VectorScale (aimdir, speed, trap->velocity);
|
|
VectorMA (trap->velocity, 200 + crandom() * 10.0, up, trap->velocity);
|
|
VectorMA (trap->velocity, crandom() * 10.0, right, trap->velocity);
|
|
// Knightmare- add player's base velocity to thrown trap
|
|
if (add_velocity_throw->value && self->client)
|
|
VectorAdd (trap->velocity, self->velocity, trap->velocity);
|
|
else if (self->groundentity)
|
|
VectorAdd (trap->velocity, self->groundentity->velocity, trap->velocity);
|
|
|
|
VectorSet (trap->avelocity, 0, 300, 0);
|
|
trap->movetype = MOVETYPE_BOUNCE;
|
|
trap->clipmask = MASK_SHOT;
|
|
trap->solid = SOLID_BBOX;
|
|
// VectorClear (trap->mins);
|
|
// VectorClear (trap->maxs);
|
|
VectorSet (trap->mins, -4, -4, 0);
|
|
VectorSet (trap->maxs, 4, 4, 8);
|
|
trap->s.modelindex = gi.modelindex ("models/weapons/z_trap/tris.md2");
|
|
trap->s.renderfx |= RF_IR_VISIBLE;
|
|
trap->owner = self;
|
|
trap->nextthink = level.time + 1.0;
|
|
trap->think = Trap_Think;
|
|
trap->dmg = damage;
|
|
trap->dmg_radius = damage_radius;
|
|
trap->classname = "htrap";
|
|
trap->class_id = ENTITY_TRAP;
|
|
// RAFAEL 16-APR-98
|
|
trap->s.sound = gi.soundindex ("weapons/traploop.wav");
|
|
// END 16-APR-98
|
|
if (held)
|
|
trap->spawnflags = 3;
|
|
else
|
|
trap->spawnflags = 1;
|
|
|
|
trap->health = sk_trap_health->value;
|
|
trap->takedamage = DAMAGE_YES;
|
|
trap->die = Trap_Die;
|
|
|
|
if (timer <= 0.0)
|
|
Grenade_Explode (trap);
|
|
else
|
|
{
|
|
// gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_NORM, 0);
|
|
gi.linkentity (trap);
|
|
}
|
|
trap->timestamp = level.time + sk_trap_life->value;
|
|
}
|
|
|
|
// NOTE: SP_trap should ONLY be used to spawn traps that change maps
|
|
// via a trigger_transition. They should NOT be used for map entities.
|
|
|
|
void trap_delayed_start (edict_t *trap)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(trap->movedir,trap->moveinfo.speed,trap->velocity);
|
|
trap->movetype = MOVETYPE_BOUNCE;
|
|
trap->nextthink = level.time + 1.0;
|
|
trap->think = Trap_Think;
|
|
gi.linkentity(trap);
|
|
}
|
|
else
|
|
trap->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
|
|
void SP_trap (edict_t *trap)
|
|
{
|
|
trap->s.modelindex = gi.modelindex ("models/weapons/z_trap/tris.md2");
|
|
trap->s.sound = gi.soundindex ("weapons/traploop.wav");
|
|
// For SP, freeze trap until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
trap->movetype = MOVETYPE_NONE;
|
|
VectorCopy(trap->velocity,trap->movedir);
|
|
VectorNormalize(trap->movedir);
|
|
trap->moveinfo.speed = VectorLength(trap->velocity);
|
|
VectorClear(trap->velocity);
|
|
trap->think = trap_delayed_start;
|
|
trap->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
trap->movetype = MOVETYPE_BOUNCE;
|
|
trap->nextthink = level.time + 1.0;
|
|
trap->think = Trap_Think;
|
|
}
|
|
gi.linkentity (trap);
|
|
}
|
|
|
|
|
|
void Trap_Die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
Trap_Explode (self);
|
|
}
|
|
|
|
|
|
void Cmd_KillTrap_f (edict_t *ent)
|
|
{
|
|
edict_t *blip = NULL;
|
|
|
|
while ((blip = findradius(blip, ent->s.origin, 1000)) != NULL)
|
|
{
|
|
if (!strcmp(blip->classname, "htrap") && blip->owner == ent)
|
|
{
|
|
blip->think = Trap_Explode;
|
|
blip->nextthink = level.time + 0.1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Trap_Explode (edict_t *ent)
|
|
{
|
|
vec3_t origin;
|
|
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_GRENADE_EXPLOSION);
|
|
gi.WritePosition (origin);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
// Grenade_Explode(ent);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (TE_GRENADE_EXPLOSION, origin);
|
|
|
|
G_FreeEdict (ent);
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
Shockwave
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
void Nuke_Quake (edict_t *self);
|
|
|
|
void shock_effect_think (edict_t *self)
|
|
{
|
|
if (++self->s.frame < 19)
|
|
self->nextthink = level.time + FRAMETIME;
|
|
else
|
|
{
|
|
self->s.frame = 0;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
self->count--;
|
|
|
|
// fade out
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
if (self->count <= 6)
|
|
self->s.alpha -= 0.10;
|
|
if (self->s.alpha < 0.10)
|
|
self->s.alpha = 0.10;
|
|
#else
|
|
if (self->count == 5)
|
|
{
|
|
self->s.effects |= EF_SPHERETRANS;
|
|
self->s.renderfx &= ~RF_TRANSLUCENT;
|
|
}
|
|
#endif
|
|
// remove after 6 secs
|
|
if (self->count == 0)
|
|
G_FreeEdict (self);
|
|
// inflict field damage on surroundings
|
|
T_RadiusDamage(self, self->owner, self->radius_dmg, NULL, self->dmg_radius, MOD_SHOCK_SPLASH);
|
|
}
|
|
|
|
void shock_effect_center_think (edict_t *self)
|
|
{
|
|
self->nextthink = level.time + FRAMETIME;
|
|
if ((self->count % 5) == 0)
|
|
{
|
|
if (self->count > 10) // double effect for first 40 seconds
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_NUKEBLAST);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_ALL);
|
|
}
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_NUKEBLAST);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_ALL);
|
|
}
|
|
// fade out
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
if (self->count <= 10)
|
|
self->s.alpha -= 0.10;
|
|
self->s.alpha = max(self->s.alpha, 1/255);
|
|
// remove after 5 secs
|
|
self->count--;
|
|
if (self->count == 0 || self->s.alpha <= 1/255)
|
|
G_FreeEdict (self);
|
|
#else
|
|
if (self->count == 10)
|
|
self->s.renderfx |= RF_TRANSLUCENT;
|
|
if (self->count == 5)
|
|
{
|
|
self->s.effects |= EF_SPHERETRANS;
|
|
self->s.renderfx &= ~RF_TRANSLUCENT;
|
|
}
|
|
//remove after 5 secs
|
|
self->count--;
|
|
if (self->count == 0)
|
|
G_FreeEdict (self);
|
|
#endif
|
|
}
|
|
|
|
void ShockEffect (edict_t *source, edict_t *attacker, float damage, float radius, cplane_t *plane)
|
|
{
|
|
edict_t *ent;
|
|
edict_t *center;
|
|
vec3_t hit_point;
|
|
|
|
if (plane->normal)
|
|
{ // put origin of effect 32 units away from last hit surface
|
|
VectorMA (source->s.origin, 32.0, plane->normal, hit_point);
|
|
}
|
|
|
|
ent = G_Spawn();
|
|
// same origin as exploding shock sphere
|
|
if (plane->normal)
|
|
VectorCopy (hit_point, ent->s.origin);
|
|
else
|
|
VectorCopy (source->s.origin, ent->s.origin);
|
|
ent->radius_dmg = sk_shockwave_effect_damage->value;
|
|
ent->dmg_radius = sk_shockwave_effect_radius->value;
|
|
ent->owner = attacker;
|
|
ent->movetype = MOVETYPE_NONE;
|
|
ent->solid = SOLID_NOT;
|
|
VectorSet (ent->mins, -8, -8, 8);
|
|
VectorSet (ent->maxs, 8, 8, 8);
|
|
ent->s.modelindex = gi.modelindex ("models/objects/shockfield/tris.md2");
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
ent->s.alpha = 0.70;
|
|
#else
|
|
ent->s.renderfx |= RF_TRANSLUCENT;
|
|
#endif
|
|
ent->s.renderfx |= RF_NOSHADOW|RF_FULLBRIGHT;
|
|
ent->s.effects = EF_FLAG2;
|
|
ent->count = 60; // lasts 6 seconds
|
|
ent->think = shock_effect_think;
|
|
ent->nextthink = level.time + 2 * FRAMETIME;
|
|
gi.linkentity (ent);
|
|
|
|
// center light burst effect
|
|
center = G_Spawn();
|
|
// same origin as exploding shock sphere
|
|
if (plane->normal)
|
|
VectorCopy (hit_point, center->s.origin);
|
|
else
|
|
VectorCopy (source->s.origin, center->s.origin);
|
|
center->movetype = MOVETYPE_NONE;
|
|
center->solid = SOLID_NOT;
|
|
VectorSet (center->mins, -8, -8, 8);
|
|
VectorSet (center->maxs, 8, 8, 8);
|
|
center->s.modelindex = gi.modelindex ("sprites/s_trap.sp2");
|
|
center->s.effects |= EF_FLAG2 | EF_ANIM_ALLFAST;
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
center->s.alpha = 0.90;
|
|
#endif
|
|
center->count = 50; // lasts 5 seconds
|
|
center->think = shock_effect_center_think;
|
|
center->nextthink = level.time + 2 * FRAMETIME;
|
|
ent->teamchain = center;
|
|
gi.linkentity (center);
|
|
}
|
|
|
|
#ifndef KMQUAKE2_ENGINE_MOD
|
|
void ShockSplashThink (edict_t *self)
|
|
{
|
|
self->s.frame++;
|
|
if (self->s.frame > 8)
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
#else
|
|
// Gotta have this for extractfuncs...
|
|
void ShockSplashThink (edict_t *self)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
void ShockSplash(edict_t *self, cplane_t *plane)
|
|
{
|
|
#ifdef KMQUAKE2_ENGINE_MOD // use new client-side effect
|
|
vec3_t shockdir;
|
|
|
|
if (!plane->normal)
|
|
AngleVectors(self->s.angles, shockdir, NULL, NULL);
|
|
else
|
|
VectorCopy (plane->normal, shockdir);
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_SHOCKSPLASH);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WriteDir (shockdir);
|
|
gi.multicast (self->s.origin, MULTICAST_ALL);
|
|
|
|
// Lazarus reflections
|
|
if (level.num_reflectors)
|
|
ReflectSparks (TE_SHOCKSPLASH, self->s.origin, shockdir);
|
|
|
|
#else
|
|
edict_t *shockring;
|
|
int i;
|
|
|
|
gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/shockhit.wav"), 1, ATTN_NONE, 0);
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
shockring = G_Spawn();
|
|
shockring->classname = "shock_ring";
|
|
if (plane->normal)
|
|
{ //put origin of impact effect 16*i+1 units away from last hit surface
|
|
VectorMA (self->s.origin, 16.0*(i+1), plane->normal, shockring->s.origin);
|
|
vectoangles(plane->normal, shockring->s.angles);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy (self->s.origin, shockring->s.origin);
|
|
VectorCopy (self->s.angles, shockring->s.angles);
|
|
}
|
|
shockring->solid = SOLID_NOT;
|
|
shockring->movetype = MOVETYPE_NONE;
|
|
shockring->owner = self;
|
|
shockring->s.modelindex = gi.modelindex("models/objects/shocksplash/tris.md2");
|
|
shockring->s.frame = (4-i);
|
|
shockring->s.effects |= EF_SPHERETRANS;
|
|
shockring->s.renderfx |= (RF_FULLBRIGHT | RF_NOSHADOW);
|
|
shockring->nextthink = level.time + FRAMETIME;
|
|
shockring->think = ShockSplashThink;
|
|
gi.linkentity (shockring);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void shock_sphere_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
vec3_t origin;
|
|
edict_t *impact;
|
|
|
|
// ignore other projectiles
|
|
if ((other->movetype == MOVETYPE_FLYMISSILE) || (other->movetype == MOVETYPE_BOUNCE)
|
|
|| (other->movetype == MOVETYPE_WALLBOUNCE))
|
|
return;
|
|
|
|
ent->count++; // add to count
|
|
ent->movetype = MOVETYPE_BOUNCE;
|
|
|
|
if (other == ent->owner)
|
|
return;
|
|
|
|
// detonate if hit a monster
|
|
if (other->takedamage)
|
|
ent->count = sk_shockwave_bounces->value + 1;
|
|
|
|
if ( (ent->velocity[0] < 20) && (ent->velocity[0] > -20)
|
|
&& (ent->velocity[1] < 20) && (ent->velocity[1] > -20)
|
|
&& (ent->velocity[2] < 20) && (ent->velocity[2] > -20) )
|
|
ent->count = sk_shockwave_bounces->value + 1;
|
|
|
|
if (surf && (surf->flags & SURF_SKY))
|
|
{
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
if (ent->owner->client)
|
|
PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
|
|
|
|
// calculate position for the explosion entity
|
|
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
|
|
|
|
if (other->takedamage)
|
|
T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_SHOCK_SPHERE);
|
|
T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_SHOCK_SPLASH);
|
|
|
|
// spawn impact
|
|
impact = G_Spawn();
|
|
impact->classname = "shock_impact";
|
|
VectorCopy (ent->s.origin, impact->s.origin);
|
|
gi.linkentity (impact);
|
|
impact->think = Nuke_Quake;
|
|
impact->speed = 250;
|
|
impact->nextthink = level.time + FRAMETIME;
|
|
|
|
if ((ent->count <= sk_shockwave_bounces->value) && !other->takedamage) //no shock rings if hit a monster
|
|
{
|
|
ShockSplash (ent, plane); // Spawn shock rings
|
|
|
|
if (impact)
|
|
impact->timestamp = level.time + 3;
|
|
}
|
|
else // don't exploode until final hit or hit a monster
|
|
{
|
|
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/shockexp.wav"), 1, ATTN_NONE, 0);
|
|
|
|
ShockEffect (ent, ent->owner, ent->radius_dmg, ent->dmg_radius, plane);
|
|
|
|
if (impact)
|
|
impact->timestamp = level.time + 6;
|
|
|
|
// remove after x bounces
|
|
G_FreeEdict (ent);
|
|
}
|
|
}
|
|
|
|
void shock_sphere_think (edict_t *sphere)
|
|
{
|
|
sphere->avelocity[PITCH] = 80;
|
|
sphere->avelocity[ROLL] = 80;
|
|
sphere->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void fire_shock_sphere (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
|
|
{
|
|
edict_t *sphere;
|
|
|
|
sphere = G_Spawn();
|
|
sphere->classname = "shocksphere";
|
|
sphere->class_id = ENTITY_SHOCK_SPHERE;
|
|
VectorCopy (start, sphere->s.origin);
|
|
VectorCopy (dir, sphere->movedir);
|
|
vectoangles (dir, sphere->s.angles);
|
|
VectorScale (dir, speed, sphere->velocity);
|
|
sphere->avelocity[PITCH] = 80;
|
|
sphere->avelocity[ROLL] = 80;
|
|
sphere->movetype = MOVETYPE_WALLBOUNCE;
|
|
sphere->count = 0;
|
|
sphere->clipmask = MASK_SHOT;
|
|
sphere->solid = SOLID_BBOX;
|
|
VectorClear (sphere->mins);
|
|
VectorClear (sphere->maxs);
|
|
VectorSet (sphere->mins, -14, -14, -14);
|
|
VectorSet (sphere->maxs, 14, 14, 14);
|
|
sphere->s.modelindex = gi.modelindex ("models/objects/shocksphere/tris.md2");
|
|
sphere->s.renderfx |= RF_IR_VISIBLE;
|
|
sphere->owner = self;
|
|
sphere->touch = shock_sphere_touch;
|
|
sphere->timestamp = level.time;
|
|
sphere->nextthink = level.time + 0.1;
|
|
sphere->think = shock_sphere_think;
|
|
sphere->dmg = damage;
|
|
sphere->radius_dmg = radius_damage;
|
|
sphere->dmg_radius = damage_radius;
|
|
|
|
if (self->client)
|
|
check_dodge (self, sphere->s.origin, dir, speed);
|
|
gi.linkentity (sphere);
|
|
}
|
|
|
|
// NOTE: SP_shocksphere should ONLY be used to spawn shockspheres that change maps
|
|
// via a trigger_transition. It should NOT be used for map entities.
|
|
|
|
void shocksphere_delayed_start (edict_t *shocksphere)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(shocksphere->movedir,shocksphere->moveinfo.speed,shocksphere->velocity);
|
|
if (shocksphere->count > 0)
|
|
shocksphere->movetype = MOVETYPE_BOUNCE;
|
|
else
|
|
shocksphere->movetype = MOVETYPE_WALLBOUNCE;
|
|
shocksphere->nextthink = level.time + FRAMETIME;
|
|
shocksphere->think = shock_sphere_think;
|
|
gi.linkentity(shocksphere);
|
|
}
|
|
else
|
|
shocksphere->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_shocksphere (edict_t *shocksphere)
|
|
{
|
|
shocksphere->s.modelindex = gi.modelindex ("models/objects/shocksphere/tris.md2");
|
|
shocksphere->touch = shock_sphere_touch;
|
|
|
|
// For SP, freeze shocksphere until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
shocksphere->movetype = MOVETYPE_NONE;
|
|
VectorCopy(shocksphere->velocity,shocksphere->movedir);
|
|
VectorNormalize(shocksphere->movedir);
|
|
shocksphere->moveinfo.speed = VectorLength(shocksphere->velocity);
|
|
VectorClear(shocksphere->velocity);
|
|
shocksphere->think = shocksphere_delayed_start;
|
|
shocksphere->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
if (shocksphere->count > 0)
|
|
shocksphere->movetype = MOVETYPE_BOUNCE;
|
|
else
|
|
shocksphere->movetype = MOVETYPE_WALLBOUNCE;
|
|
shocksphere->nextthink = level.time + FRAMETIME;
|
|
shocksphere->think = shock_sphere_think;
|
|
}
|
|
gi.linkentity (shocksphere);
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
/*
|
|
* M82 Plasma Rifle Source
|
|
* Copyright (C) 1999 Team HOSTILE
|
|
* Copyright (C) 1999 LMCTF 5.0
|
|
* created by James "SWKiD" Tomaschke
|
|
*/
|
|
|
|
/*
|
|
==============================================================================
|
|
plasma_rifle_bounce_touch
|
|
|
|
If touched a hurtable object, hurt it. Otherwise bounce with splash damage.
|
|
==============================================================================
|
|
*/
|
|
void plasma_rifle_bounce_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
// sanity check
|
|
if (!self || !other) {
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
// If hit the sky, remove from world
|
|
if (surf && (surf->flags & SURF_SKY)) {
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if ( self->owner->client )
|
|
PlayerNoise (self->owner, self->s.origin, PNOISE_IMPACT);
|
|
|
|
if ( other->takedamage )
|
|
{
|
|
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_PLASMA); // Knightmare- use parm damage
|
|
|
|
// play hit sound
|
|
gi.sound( self, CHAN_BODY, gi.soundindex(PLASMA_SOUND_HIT), 1, ATTN_IDLE, 0 );
|
|
|
|
self->solid = SOLID_NOT;
|
|
self->touch = NULL;
|
|
VectorMA (self->s.origin, -1*FRAMETIME, self->velocity, self->s.origin);
|
|
VectorClear (self->velocity);
|
|
|
|
// Run Plasma Hit Animation
|
|
self->s.modelindex = gi.modelindex (PLASMA_SPRITE_HIT);
|
|
self->s.frame = 0;
|
|
self->s.sound = 0;
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 0.1;
|
|
}
|
|
else
|
|
{ // fx to do when it bounces off something
|
|
// play reflection sound
|
|
gi.sound ( self, CHAN_BODY, gi.soundindex(PLASMA_SOUND_BOUNCE), 1, ATTN_STATIC, 0 );
|
|
|
|
// Draw blue sparks
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_LASER_SPARKS);
|
|
gi.WriteByte (32); // ammount
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WriteDir (plane->normal);
|
|
gi.WriteByte (176); // stecki's choice of id's blue
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
// -bat
|
|
T_RadiusDamage (self, self->owner, self->dmg, NULL, self->dmg+sk_plasma_rifle_radius->value, MOD_PLASMA); // Knightmare- use parm damage and radius cvar
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
plasma_rifle_spread_touch
|
|
|
|
If hits an entity, do some damage, otherwise do some splash damage.
|
|
==============================================================================
|
|
*/
|
|
void plasma_rifle_spread_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
// sanity check
|
|
if (!self || !other) {
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
// If hit the sky, remove from world
|
|
if (surf && (surf->flags & SURF_SKY)) {
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
// Don't collide with other plasma goops
|
|
if ( Q_stricmp(other->classname, "goop") == 0 )
|
|
return;
|
|
|
|
if ( self->owner->client )
|
|
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
|
|
|
|
// If can damage, hurt it
|
|
if ( other->takedamage )
|
|
{
|
|
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_PLASMA); // Knightmare- use parm damage
|
|
}
|
|
else // otherwise, splash damage
|
|
{
|
|
//-bat added sparks to this too.
|
|
// Draw blue sparks
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_LASER_SPARKS);
|
|
gi.WriteByte (32); // ammount
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WriteDir (plane->normal);
|
|
gi.WriteByte (176); // stecki's choice of id's blue
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
T_RadiusDamage (self, self->owner, self->dmg, NULL, self->dmg+sk_plasma_rifle_radius->value, MOD_PLASMA); // Knightmare- use parm damage and radius cvar
|
|
}
|
|
// play hit sound
|
|
gi.sound ( self, CHAN_BODY, gi.soundindex(PLASMA_SOUND_HIT), 1, ATTN_IDLE,0 ); // idle static none
|
|
|
|
self->solid = SOLID_NOT;
|
|
self->touch = NULL;
|
|
VectorMA (self->s.origin, -1*FRAMETIME, self->velocity, self->s.origin);
|
|
VectorClear (self->velocity);
|
|
|
|
// Run Plasma Hit Animation
|
|
self->s.modelindex = gi.modelindex (PLASMA_SPRITE_HIT);
|
|
self->s.frame = 0;
|
|
self->s.sound = 0;
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 0.1;
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
Spawn_Goop
|
|
|
|
Spawns the plasma entities, and defines values global to both weapon modes.
|
|
==============================================================================
|
|
*/
|
|
edict_t *Spawn_Goop (edict_t *ent, vec3_t start)
|
|
{
|
|
edict_t *goop; // = G_Spawn();
|
|
|
|
// sanity check
|
|
if (!ent) {
|
|
return NULL;
|
|
}
|
|
|
|
goop = G_Spawn();
|
|
|
|
goop->owner = ent;
|
|
goop->clipmask = MASK_SHOT;
|
|
goop->solid = SOLID_BBOX;
|
|
goop->svflags = SVF_DEADMONSTER;
|
|
|
|
VectorCopy (start, goop->s.origin);
|
|
goop->classname = "goop";
|
|
goop->class_id = ENTITY_GOOP;
|
|
|
|
goop->s.effects |= EF_BLUEHYPERBLASTER | EF_ANIM_ALLFAST;
|
|
// bat to get rid of the blue flag effect
|
|
// goop->s.effects |= EF_IONRIPPER | EF_ANIM_ALLFAST;
|
|
goop->s.renderfx = RF_TRANSLUCENT;
|
|
goop->s.modelindex = gi.modelindex(PLASMA_SPRITE_FLY);
|
|
goop->s.sound = gi.soundindex(PLASMA_SOUND_FLYBY);
|
|
|
|
// give it some thickness for the bounce
|
|
// VectorSet (goop->mins, -12, -12, -12);
|
|
// VectorSet (goop->maxs, 12, 12, 12);
|
|
VectorSet (goop->mins, -6, -6, -6);
|
|
VectorSet (goop->maxs, 6, 6, 6);
|
|
|
|
return goop;
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
fire_plasma_rifle_bounce
|
|
|
|
Unique code to fire a bouncy plasma goob.
|
|
Uses MOVETYPE_WALLBOUNCE.
|
|
==============================================================================
|
|
*/
|
|
void fire_plasma_rifle_bounce (edict_t *ent, vec3_t start, vec3_t dir, int damage, int speed)
|
|
{
|
|
edict_t *goop = NULL;
|
|
|
|
// sanity check
|
|
if (!ent) {
|
|
return;
|
|
}
|
|
|
|
goop = Spawn_Goop (ent, start);
|
|
if (!goop) {
|
|
return;
|
|
}
|
|
|
|
goop->movetype = MOVETYPE_WALLBOUNCE; // Knightmare- use same movetype as ION Ripper projectiles
|
|
|
|
VectorScale (dir, speed, goop->velocity); // Knightmare- use parm speed
|
|
VectorCopy (goop->velocity, goop->s.angles); // needed for post touch
|
|
|
|
//-bat
|
|
goop->dmg = damage; // Knightmare- use parm damage
|
|
goop->touch = plasma_rifle_bounce_touch;
|
|
|
|
goop->think = G_FreeEdict; // change this to handle
|
|
// goop->nextthink = level.time + 3.0; // sprite animation?
|
|
// goop->nextthink = level.time + 1.5;
|
|
goop->nextthink = level.time + sk_plasma_rifle_life_bounce->value;
|
|
|
|
gi.linkentity (goop);
|
|
|
|
// Knightmare- added missing check_dodge() call
|
|
if (ent->client)
|
|
check_dodge (ent, goop->s.origin, dir, speed);
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
fire_plasma_rifle_spread
|
|
|
|
Unique code to fire a spread of three bullets, each with 1/3 the damage of
|
|
one initial bouncy bullet.
|
|
==============================================================================
|
|
*/
|
|
void fire_plasma_rifle_spread (edict_t *ent, vec3_t start, vec3_t dir, int damage, int speed)
|
|
{
|
|
edict_t *goop_l = NULL;
|
|
edict_t *goop_c = NULL;
|
|
edict_t *goop_r = NULL;
|
|
vec3_t dir_r, dir_l, angles;
|
|
|
|
// sanity check
|
|
if (!ent) {
|
|
return;
|
|
}
|
|
|
|
goop_l = Spawn_Goop (ent, start);
|
|
goop_c = Spawn_Goop (ent, start);
|
|
goop_r = Spawn_Goop (ent, start);
|
|
if (!goop_l || !goop_c || !goop_r) {
|
|
return;
|
|
}
|
|
|
|
goop_l->movetype = MOVETYPE_FLYMISSILE;
|
|
goop_c->movetype = MOVETYPE_FLYMISSILE;
|
|
goop_r->movetype = MOVETYPE_FLYMISSILE;
|
|
|
|
VectorClear (goop_l->mins);
|
|
VectorClear (goop_l->maxs);
|
|
VectorClear (goop_c->mins);
|
|
VectorClear (goop_c->maxs);
|
|
VectorClear (goop_r->mins);
|
|
VectorClear (goop_r->maxs);
|
|
|
|
// Knightmare- use parm damage
|
|
goop_l->dmg = damage;
|
|
goop_c->dmg = damage;
|
|
goop_r->dmg = damage;
|
|
|
|
// center spread, line of sight
|
|
VectorScale (dir, speed, goop_c->velocity); // Knightmare- use parm speed
|
|
vectoangles (dir, angles);
|
|
|
|
// right spread, has 10+ in yaw
|
|
angles[YAW] -= 10;
|
|
AngleVectors (angles, dir_r, NULL, NULL);
|
|
VectorScale (dir_r, speed, goop_r->velocity); // Knightmare- use parm speed
|
|
|
|
// left spread, has 10- in yaw
|
|
angles[YAW] += 20;
|
|
AngleVectors (angles, dir_l, NULL, NULL);
|
|
VectorScale (dir_l, speed, goop_l->velocity); // Knightmare- use parm speed
|
|
|
|
goop_l->touch = plasma_rifle_spread_touch;
|
|
goop_c->touch = plasma_rifle_spread_touch;
|
|
goop_r->touch = plasma_rifle_spread_touch;
|
|
|
|
goop_l->think = G_FreeEdict;
|
|
goop_c->think = G_FreeEdict;
|
|
goop_r->think = G_FreeEdict;
|
|
// goop_l->nextthink = level.time + 3.0;
|
|
// goop_c->nextthink = level.time + 3.0;
|
|
// goop_r->nextthink = level.time + 3.0;
|
|
goop_l->nextthink = level.time + sk_plasma_rifle_life_spread->value;
|
|
goop_c->nextthink = level.time + sk_plasma_rifle_life_spread->value;
|
|
goop_r->nextthink = level.time + sk_plasma_rifle_life_spread->value;
|
|
|
|
gi.linkentity (goop_l);
|
|
gi.linkentity (goop_c);
|
|
gi.linkentity (goop_r);
|
|
|
|
// Knightmare- added missing check_dodge() calls
|
|
if (ent->client) {
|
|
check_dodge (ent, goop_c->s.origin, dir, speed);
|
|
check_dodge (ent, goop_r->s.origin, dir_r, speed);
|
|
check_dodge (ent, goop_l->s.origin, dir_l, speed);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
fire_plasma_rifle
|
|
|
|
If "reflect" is > 0, it will fire a bouncy shot, else, it will fire a spread.
|
|
"start" and "dir" are not set here, but in 'p_weapon.c', where it is setup to
|
|
handle weapon firing.
|
|
==============================================================================
|
|
*/
|
|
void fire_plasma_rifle (edict_t *ent, vec3_t start, vec3_t dir, int damage, int speed, qboolean spread)
|
|
{
|
|
if (spread) {
|
|
fire_plasma_rifle_spread (ent, start, dir, damage, speed);
|
|
}
|
|
else {
|
|
fire_plasma_rifle_bounce (ent, start, dir, damage, speed);
|
|
}
|
|
}
|
|
|
|
// NOTE: SP_goop should ONLY be used to spawn plasma rifle shots that change maps
|
|
// via a trigger_transition. It should NOT be used for map entities.
|
|
|
|
void goop_delayed_start (edict_t *goop)
|
|
{
|
|
if (g_edicts[1].linkcount)
|
|
{
|
|
VectorScale(goop->movedir, goop->moveinfo.speed, goop->velocity);
|
|
goop->nextthink = level.time + 8000/goop->moveinfo.speed;
|
|
goop->think = G_FreeEdict;
|
|
gi.linkentity(goop);
|
|
}
|
|
else
|
|
goop->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void SP_goop (edict_t *goop)
|
|
{
|
|
vec3_t dir;
|
|
|
|
goop->s.modelindex = gi.modelindex(PLASMA_SPRITE_FLY);
|
|
goop->s.effects |= EF_BLUEHYPERBLASTER | EF_ANIM_ALLFAST;
|
|
goop->s.sound = gi.soundindex(PLASMA_SOUND_FLYBY);
|
|
|
|
// set to the spread variant as we don't need a high speed bouncing projectile just after a map transition
|
|
goop->touch = plasma_rifle_spread_touch;
|
|
AngleVectors(goop->s.angles, dir, NULL, NULL);
|
|
VectorCopy (dir, goop->movedir);
|
|
goop->moveinfo.speed = VectorLength(goop->velocity);
|
|
if (goop->moveinfo.speed <= 0)
|
|
goop->moveinfo.speed = sk_plasma_rifle_speed_spread->value; // 1200
|
|
|
|
// For SP, freeze goop until player spawns in
|
|
if (game.maxclients == 1)
|
|
{
|
|
VectorClear(goop->velocity);
|
|
goop->think = goop_delayed_start;
|
|
goop->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
goop->think = G_FreeEdict;
|
|
goop->nextthink = level.time + 8000/goop->moveinfo.speed;
|
|
}
|
|
gi.linkentity (goop);
|
|
}
|
|
// end M82 Plasma Rifle
|