thirtyflightsofloving/missionpack/g_newweap.c
Knightmare66 0d4e872ce9 Added LMCTF / LM Escape plasma rifle to missionpack DLL.
Added plasma guards (monster_soldier_plasma_re and monster_soldier_plasma_sp) from LM Escape to missionpack DLL.
Added Zaero items/weapons to missionpack DLL.
Added support for Zaero doors  to missionpack DLL.
Fixed crash caused by killtargeting sentien (laser edict not freed) in missionpack DLL.
Fixed bug with broken Rogue turrets in missionpack DLL.
Fixed crash in g_combat.c->M_ReactToDamage() caused by attacker with NULL classname in missionpack DLL.
2020-08-09 02:45:19 -04:00

3003 lines
No EOL
81 KiB
C

#include "g_local.h"
#define INCLUDE_ETF_RIFLE 1
#define INCLUDE_PROX 1
//#define INCLUDE_FLAMETHROWER 1
//#define INCLUDE_INCENDIARY 1
#define INCLUDE_NUKE 1
#define INCLUDE_NBOMB 1
#define INCLUDE_MELEE 1
#define INCLUDE_TESLA 1
#define INCLUDE_BEAMS 1
extern void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed);
extern void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
extern void droptofloor (edict_t *ent);
extern void Grenade_Explode (edict_t *ent);
//extern void Nbomb_Explode (edict_t *ent);
extern void drawbbox (edict_t *ent);
#ifdef INCLUDE_ETF_RIFLE
/*
========================
fire_flechette
========================
*/
void flechette_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t dir;
if (other == self->owner)
return;
if (surf && (surf->flags & SURF_SKY))
{
G_FreeEdict (self);
return;
}
if (self->client)
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
if (other->takedamage)
{
//gi.dprintf("t_damage %s\n", other->classname);
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
self->dmg, self->dmg_radius, DAMAGE_NO_REG_ARMOR, MOD_ETF_RIFLE);
}
else
{
if (!plane)
VectorClear (dir);
else
VectorScale (plane->normal, 256, dir);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_FLECHETTE);
gi.WritePosition (self->s.origin);
gi.WriteDir (dir);
gi.multicast (self->s.origin, MULTICAST_PVS);
// Lazarus reflections
if (level.num_reflectors)
ReflectSparks(TE_FLECHETTE, self->s.origin, dir);
// T_RadiusDamage(self, self->owner, 24, self, 48, MOD_ETF_RIFLE);
}
//Knightmare- added splash damage
T_RadiusDamage(self, self->owner, self->radius_dmg, self, self->dmg_radius, MOD_ETF_SPLASH);
G_FreeEdict (self);
}
void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
{
edict_t *flechette;
VectorNormalize (dir);
flechette = G_Spawn();
flechette->classname = "flechette";
VectorCopy (start, flechette->s.origin);
VectorCopy (start, flechette->s.old_origin);
vectoangles2 (dir, flechette->s.angles);
VectorScale (dir, speed, flechette->velocity);
flechette->movetype = MOVETYPE_FLYMISSILE;
flechette->clipmask = MASK_SHOT;
flechette->solid = SOLID_BBOX;
flechette->s.renderfx = RF_FULLBRIGHT;
flechette->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow
VectorClear (flechette->mins);
VectorClear (flechette->maxs);
flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2");
// flechette->s.sound = gi.soundindex (""); // FIXME - correct sound!
flechette->owner = self;
flechette->touch = flechette_touch;
flechette->nextthink = level.time + 8000/speed;
flechette->think = G_FreeEdict;
flechette->dmg = damage;
flechette->dmg_radius = damage_radius;
flechette->radius_dmg = radius_damage;
gi.linkentity (flechette);
if (self->client)
check_dodge (self, flechette->s.origin, dir, speed);
}
// SP_flechette should ONLY be used for flechettes that have changed
// maps via trigger_transition. It should NOT be used for map entities.
void flechette_delayed_start (edict_t *flechette)
{
if (g_edicts[1].linkcount)
{
VectorScale(flechette->movedir,flechette->moveinfo.speed,flechette->velocity);
flechette->nextthink = level.time + 2;
flechette->think = G_FreeEdict;
gi.linkentity(flechette);
}
else
flechette->nextthink = level.time + FRAMETIME;
}
void SP_flechette (edict_t *flechette)
{
flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2");
flechette->touch = flechette_touch;
VectorCopy(flechette->velocity,flechette->movedir);
VectorNormalize(flechette->movedir);
flechette->moveinfo.speed = VectorLength(flechette->velocity);
VectorClear(flechette->velocity);
flechette->think = flechette_delayed_start;
flechette->nextthink = level.time + FRAMETIME;
gi.linkentity(flechette);
}
#endif
// **************************
// PROX
// **************************
#ifdef INCLUDE_PROX
#define PROX_TIME_TO_LIVE 600 // 45, 30, 15, 10
#define PROX_TIME_DELAY 0.5
#define PROX_BOUND_SIZE 96
#define PROX_DAMAGE_RADIUS 192
#define PROX_HEALTH 20
#define PROX_DAMAGE 90
void Prox_Explode (edict_t *ent);
//===============
//===============
// Knightmare- move prox and trigger field with host
void prox_movewith_host (edict_t *self)
{
edict_t *host;
vec3_t forward, right, up, offset;
vec3_t host_angle_change, amove;
int count = 0;
// explode if parent has been destroyed
if (!self->movewith_ent || !self->movewith_ent->inuse)
{
// gi.dprintf("prox_no_parent_die\n");
Prox_Explode (self);
return;
}
host = self->movewith_ent;
// if parent has disappeared (func_wall, etc.), then drop to ground
if (host->svflags & SVF_NOCLIENT)
{
VectorClear(self->s.angles);
self->movetype = MOVETYPE_TOSS;
self->movewith_ent = NULL;
self->movewith_set = 0;
self->teamchain->movewith_ent = NULL;
self->teamchain->movewith_set = 0;
self->postthink = NULL;
return;
}
movefield:
if (!self) // more paranoia
return;
self->movetype = MOVETYPE_PUSH;
if (!self->movewith_set)
{
VectorCopy(self->mins, self->org_mins);
VectorCopy(self->maxs, self->org_maxs);
VectorSubtract (self->s.origin, host->s.origin, self->movewith_offset);
// Remeber child's and parent's angles when child was attached
VectorCopy(host->s.angles, self->parent_attach_angles);
VectorCopy(self->s.angles, self->child_attach_angles);
self->movewith_set = 1;
}
// Get change in parent's angles from when child was attached, this tells us how far we need to rotate
VectorSubtract(host->s.angles, self->parent_attach_angles, host_angle_change);
AngleVectors(host_angle_change, forward, right, up);
VectorNegate(right, right);
VectorMA(host->s.origin, self->movewith_offset[0], forward, self->s.origin);
VectorMA(self->s.origin, self->movewith_offset[1], right, self->s.origin);
VectorMA(self->s.origin, self->movewith_offset[2], up, self->s.origin);
VectorCopy(host->velocity, self->velocity);
// If parent is spinning, add appropriate velocities
VectorSubtract(self->s.origin, host->s.origin, offset);
if (host->avelocity[PITCH] != 0)
{
self->velocity[2] -= offset[0] * host->avelocity[PITCH] * M_PI / 180;
self->velocity[0] += offset[2] * host->avelocity[PITCH] * M_PI / 180;
}
if (host->avelocity[YAW] != 0)
{
self->velocity[0] -= offset[1] * host->avelocity[YAW] * M_PI / 180.;
self->velocity[1] += offset[0] * host->avelocity[YAW] * M_PI / 180.;
}
if (host->avelocity[ROLL] != 0)
{
self->velocity[1] -= offset[2] * host->avelocity[ROLL] * M_PI / 180;
self->velocity[2] += offset[1] * host->avelocity[ROLL] * M_PI / 180;
}
VectorScale (host->avelocity, FRAMETIME, amove);
VectorAdd(self->child_attach_angles, host_angle_change, self->s.angles); // add rotation to angles
// VectorAdd(self->s.angles, amove, self->s.angles); //add rotation to angles
if (amove[YAW]) // Cross fingers here... move bounding box
{
float ca, sa, yaw;
vec3_t p00, p01, p10, p11;
// Adjust bounding box for yaw
yaw = self->s.angles[YAW] * M_PI / 180.;
ca = cos(yaw);
sa = sin(yaw);
p00[0] = self->org_mins[0]*ca - self->org_mins[1]*sa;
p00[1] = self->org_mins[1]*ca + self->org_mins[0]*sa;
p01[0] = self->org_mins[0]*ca - self->org_maxs[1]*sa;
p01[1] = self->org_maxs[1]*ca + self->org_mins[0]*sa;
p10[0] = self->org_maxs[0]*ca - self->org_mins[1]*sa;
p10[1] = self->org_mins[1]*ca + self->org_maxs[0]*sa;
p11[0] = self->org_maxs[0]*ca - self->org_maxs[1]*sa;
p11[1] = self->org_maxs[1]*ca + self->org_maxs[0]*sa;
self->mins[0] = p00[0];
self->mins[0] = min(self->mins[0],p01[0]);
self->mins[0] = min(self->mins[0],p10[0]);
self->mins[0] = min(self->mins[0],p11[0]);
self->mins[1] = p00[1];
self->mins[1] = min(self->mins[1],p01[1]);
self->mins[1] = min(self->mins[1],p10[1]);
self->mins[1] = min(self->mins[1],p11[1]);
self->maxs[0] = p00[0];
self->maxs[0] = max(self->maxs[0],p01[0]);
self->maxs[0] = max(self->maxs[0],p10[0]);
self->maxs[0] = max(self->maxs[0],p11[0]);
self->maxs[1] = p00[1];
self->maxs[1] = max(self->maxs[1],p01[1]);
self->maxs[1] = max(self->maxs[1],p10[1]);
self->maxs[1] = max(self->maxs[1],p11[1]);
}
self->s.event = host->s.event;
gi.linkentity (self);
if (count < 1 && self->teamchain) // now move the trigger field
{
self = self->teamchain;
count++;
goto movefield;
}
}
void Prox_Explode (edict_t *ent)
{
vec3_t origin;
edict_t *owner;
int type;
// free the trigger field
//PMM - changed teammaster to "mover" .. owner of the field is the prox
if (ent->teamchain && ent->teamchain->owner == ent)
G_FreeEdict(ent->teamchain);
owner = ent;
if (ent->teammaster)
{
owner = ent->teammaster;
PlayerNoise(owner, ent->s.origin, PNOISE_IMPACT);
}
// play quad sound if appopriate
if (ent->dmg > sk_prox_damage->value)
{
if (ent->dmg < (4 * sk_prox_damage->value)) //double sound
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
else //quad sound
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
}
ent->takedamage = DAMAGE_NO;
T_RadiusDamage(ent, owner, ent->dmg, ent, sk_prox_radius->value, MOD_PROX);
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
if (ent->groundentity)
type = TE_GRENADE_EXPLOSION;
else
type = TE_ROCKET_EXPLOSION;
gi.WriteByte (svc_temp_entity);
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);
}
//===============
//===============
void prox_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
//gi.dprintf("prox_die\n");
// if set off by another prox, delay a little (chained explosions)
if (strcmp(inflictor->classname, "prox"))
{
self->takedamage = DAMAGE_NO;
Prox_Explode(self);
}
else
{
self->takedamage = DAMAGE_NO;
self->think = Prox_Explode;
self->nextthink = level.time + FRAMETIME;
}
}
//===============
//===============
void Prox_Field_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *prox;
if (!(other->svflags & SVF_MONSTER) && !other->client)
return;
// trigger the prox mine if it's still there, and still mine.
prox = ent->owner;
if (other == prox) // don't set self off
return;
if (prox->think == Prox_Explode) // we're set to blow!
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("%f - prox already gone off!\n", level.time);
return;
}
if (prox->teamchain == ent)
{
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
prox->think = Prox_Explode;
prox->nextthink = level.time + PROX_TIME_DELAY;
return;
}
ent->solid = SOLID_NOT;
G_FreeEdict(ent);
}
//===============
//===============
void prox_seek (edict_t *ent)
{
if (level.time > ent->wait)
{
Prox_Explode(ent);
}
else
{
ent->s.frame++;
if (ent->s.frame > 13)
ent->s.frame = 9;
ent->think = prox_seek;
ent->nextthink = level.time + 0.1;
}
//If we're not attached to a host and may be bouncing around, move field with us
if (!ent->movewith_ent && ent->teamchain)
{
VectorCopy (ent->s.origin, ent->teamchain->s.origin);
gi.linkentity (ent->teamchain);
}
}
//===============
//===============
void prox_open (edict_t *ent)
{
edict_t *search;
search = NULL;
// gi.dprintf("prox_open %d\n", ent->s.frame);
// gi.dprintf("%f\n", ent->velocity[2]);
if (ent->s.frame == 9) // end of opening animation
{
// set the owner to NULL so the owner can shoot it, etc. needs to be done here so the owner
// doesn't get stuck on it while it's opening if fired at point blank wall
ent->s.sound = 0;
// Knightmare- the above never happens, and the owner pointer is needed for remote detonation
//ent->owner = NULL;
if (ent->teamchain)
ent->teamchain->touch = Prox_Field_Touch;
while ((search = findradius(search, ent->s.origin, sk_prox_radius->value + 10)) != NULL)
{
if (!search->classname) // tag token and other weird shit
continue;
// if (!search->takedamage)
// continue;
// if it's a monster or player with health > 0
// or it's a player start point
// and we can see it
// blow up
if (
(
(((search->svflags & SVF_MONSTER) || (search->client)) && (search->health > 0)) ||
(
(deathmatch->value) &&
(
(!strcmp(search->classname, "info_player_deathmatch")) ||
(!strcmp(search->classname, "info_player_start")) ||
(!strcmp(search->classname, "info_player_coop")) ||
(!strcmp(search->classname, "misc_teleporter_dest"))
)
)
)
&& (visible (search, ent))
)
{
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
Prox_Explode (ent);
return;
}
}
if (strong_mines && (strong_mines->value))
ent->wait = level.time + sk_prox_life->value;
else
{
switch (ent->dmg / (int)sk_prox_damage->value)
{
case 1:
ent->wait = level.time + sk_prox_life->value;
break;
case 2:
ent->wait = level.time + sk_prox_life->value / 2;
break;
case 4:
ent->wait = level.time + sk_prox_life->value / 4;
break;
case 8:
ent->wait = level.time + sk_prox_life->value / 8;
break;
default:
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("prox with unknown multiplier %d!\n", ent->dmg/PROX_DAMAGE);
ent->wait = level.time + sk_prox_life->value;
break;
}
}
ent->think = prox_seek;
ent->nextthink = level.time + 0.2;
}
else
{
if (ent->s.frame == 0)
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxopen.wav"), 1, ATTN_NORM, 0);
//ent->s.sound = gi.soundindex ("weapons/proxopen.wav");
ent->s.frame++;
ent->think = prox_open;
ent->nextthink = level.time + 0.10; //was 0.05
}
}
//===============
//===============
#define STOP_EPSILON 0.1
void prox_land (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *field;
vec3_t dir;
vec3_t forward, right, up;
int makeslave = 0;
int movetype = MOVETYPE_NONE;
int stick_ok = 0;
vec3_t land_point;
qboolean havehost = false;
// must turn off owner so owner can shoot it and set it off
// moved to prox_open so owner can get away from it if fired at pointblank range into
// wall
// ent->owner = NULL;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("land - %2.2f %2.2f %2.2f\n", ent->velocity[0], ent->velocity[1], ent->velocity[2]);
if (surf && (surf->flags & SURF_SKY))
{
G_FreeEdict(ent);
return;
}
if (plane != NULL && plane->normal)
{
VectorMA (ent->s.origin, -10.0, plane->normal, land_point);
if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA))
{
Prox_Explode (ent);
return;
}
}
if ((other->svflags & SVF_MONSTER) || other->client || (other->svflags & SVF_DAMAGEABLE))
{
if (other != ent->teammaster)
Prox_Explode(ent);
return;
}
else if (other != world)
{
// Here we need to check to see if we can stop on this entity.
// Note that plane can be NULL
// PMM - code stolen from g_phys (ClipVelocity)
vec3_t out;
float backoff, change;
int i;
if (!plane || !plane->normal) // this happens if you hit a point object, maybe other cases
{
// Since we can't tell what's going to happen, just blow up
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("bad normal for surface, exploding!\n");
Prox_Explode(ent);
return;
}
//Knightmare- stick to bmodels
if (other->solid == SOLID_BSP && other->movetype != MOVETYPE_CONVEYOR
&& (other->movetype == MOVETYPE_PUSH || other->movetype == MOVETYPE_PUSHABLE))
{
ent->movewith_ent = other;
ent->s.effects &= ~EF_GRENADE; // remove smoke trail
stick_ok = 1;
havehost = true;
ent->postthink = prox_movewith_host;
}
else if (other->movetype == MOVETYPE_CONVEYOR)
havehost = true;
else if ((other->movetype == MOVETYPE_PUSH) && (plane->normal[2] > 0.7))
stick_ok = 1;
else
stick_ok = 0;
backoff = DotProduct (ent->velocity, plane->normal) * 1.5;
for (i=0 ; i<3 ; i++)
{
change = plane->normal[i]*backoff;
out[i] = ent->velocity[i] - change;
if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
out[i] = 0;
}
if ((out[2] > 60) && !havehost) // always open up if attached to host
return;
if (havehost && other->movetype != MOVETYPE_CONVEYOR)
movetype = MOVETYPE_PUSH;
else if (other->movetype == MOVETYPE_CONVEYOR)
{
movetype = MOVETYPE_TOSS;
ent->s.effects &= ~EF_GRENADE; //remove smoke trail
stick_ok = 1;
havehost = false;
}
else
movetype = MOVETYPE_BOUNCE;
// if we're here, we're going to stop on an entity
if (stick_ok)
{ // it's a happy entity
VectorCopy (vec3_origin, ent->velocity);
VectorCopy (vec3_origin, ent->avelocity);
}
else // no-stick. teflon time
{
if (plane->normal[2] > 0.7)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("stuck on entity, blowing up!\n");
Prox_Explode(ent);
return;
}
return;
}
}
else if (other->s.modelindex != 1)
return;
vectoangles2 (plane->normal, dir);
AngleVectors (dir, forward, right, up);
if (gi.pointcontents (ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME))
{
Prox_Explode (ent);
return;
}
field = G_Spawn();
VectorCopy (ent->s.origin, field->s.origin);
VectorClear(field->velocity);
VectorClear(field->avelocity);
VectorSet(field->mins, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE);
VectorSet(field->maxs, PROX_BOUND_SIZE, PROX_BOUND_SIZE, PROX_BOUND_SIZE);
field->movetype = MOVETYPE_NONE;
field->solid = SOLID_TRIGGER;
field->owner = ent;
field->classname = "prox_field";
field->teammaster = ent;
if (havehost) // Knightmare- set up field to move with host
field->movewith_ent = other;
gi.linkentity (field);
VectorClear(ent->velocity);
VectorClear(ent->avelocity);
// rotate to vertical
dir[PITCH] = dir[PITCH] + 90;
if (other->movetype == MOVETYPE_CONVEYOR)
VectorClear (ent->s.angles);
else
VectorCopy (dir, ent->s.angles);
ent->takedamage = DAMAGE_AIM;
ent->movetype = movetype; // either bounce or none, depending on whether we stuck to something
ent->die = prox_die;
ent->teamchain = field;
ent->health = sk_prox_health->value;
ent->nextthink = level.time + 0.05;
ent->think = prox_open;
ent->touch = NULL;
ent->solid = SOLID_BBOX;
// Knightmare- if host is moving, setup movewith immediately so we don't lag
if (VectorLength(other->velocity) || VectorLength(other->avelocity))
prox_movewith_host (ent);
// record who we're attached to
// ent->teammaster = other;
// if (other->movetype == MOVETYPE_PUSHABLE)
// gi.dprintf("prox successfully attached to func_pushable\n");
gi.linkentity(ent);
}
//===============
//===============
void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed)
{
edict_t *prox;
vec3_t dir;
vec3_t forward, right, up;
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("start %s aim %s speed %d\n", vtos(start), vtos(aimdir), speed);
prox = G_Spawn();
VectorCopy (start, prox->s.origin);
VectorScale (aimdir, speed, prox->velocity);
VectorMA (prox->velocity, 200 + crandom() * 10.0, up, prox->velocity);
VectorMA (prox->velocity, crandom() * 10.0, right, prox->velocity);
//Knightmare- add player's base velocity to prox
if (add_velocity_throw->value && self->client)
VectorAdd (prox->velocity, self->velocity, prox->velocity);
else if (self->groundentity)
VectorAdd (prox->velocity, self->groundentity->velocity, prox->velocity);
VectorCopy (dir, prox->s.angles);
prox->s.angles[PITCH]-=90;
prox->movetype = MOVETYPE_BOUNCE;
prox->solid = SOLID_BBOX;
prox->s.effects |= EF_GRENADE;
prox->clipmask = MASK_SHOT|CONTENTS_LAVA|CONTENTS_SLIME;
prox->s.renderfx |= RF_IR_VISIBLE;
//FIXME - this needs to be bigger. Has other effects, though. Maybe have to change origin to compensate
// so it sinks in correctly. Also in lavacheck, might have to up the distance
VectorSet (prox->mins, -6, -6, -6);
VectorSet (prox->maxs, 6, 6, 6);
prox->s.modelindex = gi.modelindex ("models/weapons/g_prox/tris.md2");
prox->owner = self;
prox->teammaster = self;
prox->touch = prox_land;
// prox->nextthink = level.time + sk_prox_life->value;
prox->think = Prox_Explode;
prox->dmg = sk_prox_damage->value*damage_multiplier;
prox->classname = "prox";
prox->svflags |= SVF_DAMAGEABLE;
prox->flags |= FL_MECHANICAL;
switch (damage_multiplier)
{
case 1:
prox->nextthink = level.time + sk_prox_life->value;
break;
case 2:
prox->nextthink = level.time + sk_prox_life->value;
break;
case 4:
prox->nextthink = level.time + sk_prox_life->value;
break;
case 8:
prox->nextthink = level.time + sk_prox_life->value;
break;
default:
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("prox with unknown multiplier %d!\n", damage_multiplier);
prox->nextthink = level.time + sk_prox_life->value;
break;
}
gi.linkentity (prox);
}
void Cmd_DetProx_f (edict_t *ent)
{
edict_t *blip = NULL;
while ((blip = findradius(blip, ent->s.origin, WORLD_SIZE)) != NULL) // was 8192
{
if (!strcmp(blip->classname, "prox") && blip->owner == ent)
{
blip->think = Prox_Explode;
blip->nextthink = level.time + 0.1;
}
}
}
// NOTE: SP_prox should ONLY be used to spawn prox mines that change maps
// via a trigger_transition. They should NOT be used for map entities.
void prox_delayed_start (edict_t *prox)
{
if (g_edicts[1].linkcount)
{
VectorScale(prox->movedir,prox->moveinfo.speed,prox->velocity);
prox->movetype = MOVETYPE_BOUNCE;
prox->nextthink = level.time + sk_prox_life->value;
prox->think = Prox_Explode;
gi.linkentity(prox);
}
else
prox->nextthink = level.time + FRAMETIME;
}
void SP_prox (edict_t *prox)
{
prox->s.modelindex = gi.modelindex ("models/weapons/g_prox/tris.md2");
prox->touch = prox_land;
prox->movewith_ent = NULL;
prox->movewith_set = 0;
prox->postthink = NULL;
// For SP, freeze prox until player spawns in
if (game.maxclients == 1 && VectorLength (prox->velocity))
{
prox->movetype = MOVETYPE_NONE;
VectorCopy(prox->velocity,prox->movedir);
VectorNormalize(prox->movedir);
prox->moveinfo.speed = VectorLength(prox->velocity);
VectorClear(prox->velocity);
prox->think = prox_delayed_start;
prox->nextthink = level.time + FRAMETIME;
}
else
{
prox->movetype = MOVETYPE_BOUNCE;
prox->nextthink = level.time + sk_prox_life->value;
prox->think = Prox_Explode;
}
gi.linkentity (prox);
}
#endif
// *************************
// FLAMETHROWER
// *************************
#ifdef INCLUDE_FLAMETHROWER
#define FLAMETHROWER_RADIUS 8
void fire_remove (edict_t *ent)
{
if (ent == ent->owner->teamchain)
ent->owner->teamchain = NULL;
G_FreeEdict(ent);
}
void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed)
{
edict_t *flame;
vec3_t dir;
vec3_t forward, right, up;
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
flame = G_Spawn();
// the origin is the first control point, put it speed forward.
VectorMA(start, speed, forward, flame->s.origin);
// record that velocity
VectorScale (aimdir, speed, flame->velocity);
VectorCopy (dir, flame->s.angles);
flame->movetype = MOVETYPE_NONE;
flame->solid = SOLID_NOT;
VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS);
VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS);
flame->s.sound = gi.soundindex ("weapons/flame.wav");
flame->owner = self;
flame->dmg = damage;
flame->classname = "flame";
// clear control points and velocities
VectorCopy (flame->s.origin, flame->flameinfo.pos1);
VectorCopy (flame->velocity, flame->flameinfo.vel1);
VectorCopy (flame->s.origin, flame->flameinfo.pos2);
VectorCopy (flame->velocity, flame->flameinfo.vel2);
VectorCopy (flame->s.origin, flame->flameinfo.pos3);
VectorCopy (flame->velocity, flame->flameinfo.vel3);
VectorCopy (flame->s.origin, flame->flameinfo.pos4);
// hook flame stream to owner
self->teamchain = flame;
gi.linkentity (flame);
}
// fixme - change to use start location, not entity origin
void fire_maintain (edict_t *ent, edict_t *flame, vec3_t start, vec3_t aimdir, int damage, int speed)
{
trace_t tr;
// move the control points out the appropriate direction and velocity
VectorAdd(flame->flameinfo.pos3, flame->flameinfo.vel3, flame->flameinfo.pos4);
VectorAdd(flame->flameinfo.pos2, flame->flameinfo.vel2, flame->flameinfo.pos3);
VectorAdd(flame->flameinfo.pos1, flame->flameinfo.vel1, flame->flameinfo.pos2);
VectorAdd(flame->s.origin, flame->velocity, flame->flameinfo.pos1);
// move the velocities for the control points
VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3);
VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2);
VectorCopy(flame->velocity, flame->flameinfo.vel1);
// set velocity and location for new control point 0.
VectorMA(start, speed, aimdir, flame->s.origin);
VectorScale(aimdir, speed, flame->velocity);
//
// does it hit a wall? if so, when?
//
// player fire point to flame origin.
tr = gi.trace(start, flame->mins, flame->maxs,
flame->s.origin, flame, MASK_SHOT);
if (tr.fraction == 1.0)
{
// origin to point 1
tr = gi.trace(flame->s.origin, flame->mins, flame->maxs,
flame->flameinfo.pos1, flame, MASK_SHOT);
if (tr.fraction == 1.0)
{
// point 1 to point 2
tr = gi.trace(flame->flameinfo.pos1, flame->mins, flame->maxs,
flame->flameinfo.pos2, flame, MASK_SHOT);
if (tr.fraction == 1.0)
{
// point 2 to point 3
tr = gi.trace(flame->flameinfo.pos2, flame->mins, flame->maxs,
flame->flameinfo.pos3, flame, MASK_SHOT);
if (tr.fraction == 1.0)
{
// point 3 to point 4, point 3 valid
tr = gi.trace(flame->flameinfo.pos3, flame->mins, flame->maxs,
flame->flameinfo.pos4, flame, MASK_SHOT);
if (tr.fraction < 1.0) // point 4 blocked
{
VectorCopy(tr.endpos, flame->flameinfo.pos4);
}
}
else // point 3 blocked, point 2 valid
{
VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3);
VectorCopy(tr.endpos, flame->flameinfo.pos3);
VectorCopy(tr.endpos, flame->flameinfo.pos4);
}
}
else // point 2 blocked, point 1 valid
{
VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2);
VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel3);
VectorCopy(tr.endpos, flame->flameinfo.pos2);
VectorCopy(tr.endpos, flame->flameinfo.pos3);
VectorCopy(tr.endpos, flame->flameinfo.pos4);
}
}
else // point 1 blocked, origin valid
{
VectorCopy(flame->velocity, flame->flameinfo.vel1);
VectorCopy(flame->velocity, flame->flameinfo.vel2);
VectorCopy(flame->velocity, flame->flameinfo.vel3);
VectorCopy(tr.endpos, flame->flameinfo.pos1);
VectorCopy(tr.endpos, flame->flameinfo.pos2);
VectorCopy(tr.endpos, flame->flameinfo.pos3);
VectorCopy(tr.endpos, flame->flameinfo.pos4);
}
}
else // origin blocked!
{
// gi.dprintf("point 2 blocked\n");
VectorCopy(flame->velocity, flame->flameinfo.vel1);
VectorCopy(flame->velocity, flame->flameinfo.vel2);
VectorCopy(flame->velocity, flame->flameinfo.vel3);
VectorCopy(tr.endpos, flame->s.origin);
VectorCopy(tr.endpos, flame->flameinfo.pos1);
VectorCopy(tr.endpos, flame->flameinfo.pos2);
VectorCopy(tr.endpos, flame->flameinfo.pos3);
VectorCopy(tr.endpos, flame->flameinfo.pos4);
}
if (tr.fraction < 1.0 && tr.ent->takedamage)
{
T_Damage (tr.ent, flame, ent, flame->velocity, tr.endpos, tr.plane.normal,
damage, 0, DAMAGE_NO_KNOCKBACK | DAMAGE_ENERGY | DAMAGE_FIRE);
}
gi.linkentity(flame);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_FLAME);
gi.WriteShort(ent - g_edicts);
gi.WriteShort(6);
gi.WritePosition (start);
gi.WritePosition (flame->s.origin);
gi.WritePosition (flame->flameinfo.pos1);
gi.WritePosition (flame->flameinfo.pos2);
gi.WritePosition (flame->flameinfo.pos3);
gi.WritePosition (flame->flameinfo.pos4);
gi.multicast (flame->s.origin, MULTICAST_PVS);
}
/*QUAKED trap_flameshooter (1 0 0) (-8 -8 -8) (8 8 8)
*/
#define FLAMESHOOTER_VELOCITY 50
#define FLAMESHOOTER_DAMAGE 20
#define FLAMESHOOTER_BURST_VELOCITY 300
#define FLAMESHOOTER_BURST_DAMAGE 30
//#define FLAMESHOOTER_PUFF 1
#define FLAMESHOOTER_STREAM 1
void flameshooter_think (edict_t *self)
{
vec3_t forward, right, up;
edict_t *flame;
if (self->delay)
{
if (self->teamchain)
fire_remove (self->teamchain);
return;
}
self->s.angles[1] += self->speed;
if (self->s.angles[1] > 135 || self->s.angles[1] < 45)
self->speed = -self->speed;
AngleVectors (self->s.angles, forward, right, up);
#ifdef FLAMESHOOTER_STREAM
flame = self->teamchain;
if (!self->teamchain)
fire_flame (self, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY);
else
fire_maintain (self, flame, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY);
self->think = flameshooter_think;
self->nextthink = level.time + 0.05;
#else
fire_burst (self, self->s.origin, forward, FLAMESHOOTER_BURST_DAMAGE, FLAMESHOOTER_BURST_VELOCITY);
self->think = flameshooter_think;
self->nextthink = level.time + 0.1;
#endif
}
void flameshooter_use (edict_t *self, edict_t *other, edict_t *activator)
{
if (self->delay)
{
self->delay = 0;
self->think = flameshooter_think;
self->nextthink = level.time + 0.1;
}
else
self->delay = 1;
}
void SP_trap_flameshooter(edict_t *self)
{
vec3_t tempAngles;
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->delay = 0;
self->use = flameshooter_use;
if (self->delay == 0)
{
self->think = flameshooter_think;
self->nextthink = level.time + 0.1;
}
// self->flags |= FL_NOCLIENT;
self->speed = 10;
// self->speed = 0; // FIXME this stops the spraying
VectorCopy(self->s.angles, tempAngles);
if (!VectorCompare(self->s.angles, vec3_origin))
G_SetMovedir (self->s.angles, self->movedir);
VectorCopy(tempAngles, self->s.angles);
// gi.setmodel (self, self->model);
gi.linkentity (self);
}
// *************************
// fire_burst
// *************************
#define FLAME_BURST_MAX_SIZE 64
#define FLAME_BURST_FRAMES 20
#define FLAME_BURST_MIDPOINT 10
void fire_burst_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
int powerunits;
int damage, radius;
vec3_t origin;
if (surf && (surf->flags & SURF_SKY))
{
// gi.dprintf("Hit sky. Removed\n");
G_FreeEdict (ent);
return;
}
if (other == ent->owner || ent == other)
return;
// don't let flame puffs blow each other up
if (other->classname && !strcmp(other->classname, ent->classname))
return;
if (ent->waterlevel)
{
// gi.dprintf("Hit water. Removed\n");
G_FreeEdict(ent);
}
if (!(other->svflags & SVF_MONSTER) && !other->client)
{
powerunits = FLAME_BURST_FRAMES - ent->s.frame;
damage = powerunits * 6;
radius = powerunits * 4;
// T_RadiusDamage (inflictor, attacker, damage, ignore, radius)
T_RadiusDamage(ent, ent->owner, damage, ent, radius, DAMAGE_FIRE);
// gi.dprintf("Hit world: %d pts, %d rad\n", damage, radius);
// calculate position for the explosion entity
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
gi.WriteByte (svc_temp_entity);
//gi.WriteByte (TE_PLAIN_EXPLOSION);
gi.WriteByte (TE_EXPLOSION1);
gi.WritePosition (origin);
gi.multicast (ent->s.origin, MULTICAST_PVS);
G_FreeEdict (ent);
}
}
void fire_burst_think (edict_t *self)
{
int current_radius;
if (self->waterlevel)
{
G_FreeEdict(self);
return;
}
self->s.frame++;
if (self->s.frame >= FLAME_BURST_FRAMES)
{
G_FreeEdict(self);
return;
}
else if (self->s.frame < FLAME_BURST_MIDPOINT)
{
current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * self->s.frame;
}
else
{
current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * (FLAME_BURST_FRAMES - self->s.frame);
}
if (self->s.frame == 3)
self->s.skinnum = 1;
else if (self->s.frame == 7)
self->s.skinnum = 2;
else if (self->s.frame == 10)
self->s.skinnum = 3;
else if (self->s.frame == 13)
self->s.skinnum = 4;
else if (self->s.frame == 16)
self->s.skinnum = 5;
else if (self->s.frame == 19)
self->s.skinnum = 6;
if (current_radius < 8)
current_radius = 8;
else if (current_radius > FLAME_BURST_MAX_SIZE)
current_radius = FLAME_BURST_MAX_SIZE;
T_RadiusDamage(self, self->owner, self->dmg, self, current_radius, DAMAGE_FIRE);
self->think = fire_burst_think;
self->nextthink = level.time + 0.1;
}
void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed)
{
edict_t *flame;
vec3_t dir;
vec3_t baseVel;
vec3_t forward, right, up;
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
flame = G_Spawn();
VectorCopy(start, flame->s.origin);
// VectorScale (aimdir, speed, flame->velocity);
// scale down so only 30% of player's velocity is taken into account.
VectorScale (self->velocity, 0.3, baseVel);
VectorMA(baseVel, speed, aimdir, flame->velocity);
VectorCopy (dir, flame->s.angles);
flame->movetype = MOVETYPE_FLY;
flame->solid = SOLID_TRIGGER;
VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS);
VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS);
flame->s.sound = gi.soundindex ("weapons/flame.wav");
flame->s.modelindex = gi.modelindex ("models/projectiles/puff/tris.md2");
flame->owner = self;
flame->touch = fire_burst_touch;
flame->think = fire_burst_think;
flame->nextthink = level.time + 0.1;
flame->dmg = damage;
flame->classname = "flameburst";
flame->s.effects = EF_FIRE_PUFF;
gi.linkentity (flame);
}
#endif
// *************************
// INCENDIARY GRENADES
// *************************
#ifdef INCLUDE_INCENDIARY
void FireThink (edict_t *ent)
{
if (level.time > ent->wait)
G_FreeEdict(ent);
else
{
ent->s.frame++;
if (ent->s.frame>10)
ent->s.frame = 0;
ent->nextthink = level.time + 0.05;
ent->think = FireThink;
}
}
#define FIRE_HEIGHT 64
#define FIRE_RADIUS 64
#define FIRE_DAMAGE 3
#define FIRE_DURATION 15
edict_t *StartFire(edict_t *fireOwner, vec3_t fireOrigin, float fireDuration, float fireDamage)
{
edict_t *fire;
fire = G_Spawn();
VectorCopy (fireOrigin, fire->s.origin);
fire->movetype = MOVETYPE_TOSS;
fire->solid = SOLID_TRIGGER;
VectorSet(fire->mins, -FIRE_RADIUS, -FIRE_RADIUS, 0);
VectorSet(fire->maxs, FIRE_RADIUS, FIRE_RADIUS, FIRE_HEIGHT);
fire->s.sound = gi.soundindex ("weapons/incend.wav");
fire->s.modelindex = gi.modelindex ("models/objects/fire/tris.md2");
fire->owner = fireOwner;
fire->touch = hurt_touch;
fire->nextthink = level.time + 0.05;
fire->wait = level.time + fireDuration;
fire->think = FireThink;
// fire->nextthink = level.time + fireDuration;
// fire->think = G_FreeEdict;
fire->dmg = fireDamage;
fire->classname = "incendiary_fire";
gi.linkentity (fire);
// gi.sound (fire, CHAN_VOICE, gi.soundindex ("weapons/incend.wav"), 1, ATTN_NORM, 0);
return fire;
}
/*static*/ void Incendiary_Explode (edict_t *ent)
{
vec3_t origin;
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?
T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, DAMAGE_FIRE);
VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
gi.WriteByte (svc_temp_entity);
if (ent->groundentity)
gi.WriteByte (TE_GRENADE_EXPLOSION);
else
gi.WriteByte (TE_ROCKET_EXPLOSION);
gi.WritePosition (origin);
gi.multicast (ent->s.origin, MULTICAST_PVS);
StartFire(ent->owner, ent->s.origin, FIRE_DURATION, FIRE_DAMAGE);
G_FreeEdict (ent);
}
/*static*/ void Incendiary_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (other == ent->owner)
return;
if (surf && (surf->flags & SURF_SKY))
{
G_FreeEdict (ent);
return;
}
if (!(other->svflags & SVF_MONSTER) && !(ent->client))
// 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
{
if (random() > 0.5)
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
else
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb2b.wav"), 1, ATTN_NORM, 0);
}
return;
}
Incendiary_Explode (ent);
}
void fire_incendiary_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
{
edict_t *grenade;
vec3_t dir;
vec3_t forward, right, up;
vectoangles2 (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);
VectorSet (grenade->avelocity, 300, 300, 300);
grenade->movetype = MOVETYPE_BOUNCE;
grenade->clipmask = MASK_SHOT;
grenade->solid = SOLID_BBOX;
grenade->s.effects |= EF_GRENADE;
// if (self->client)
// grenade->s.effects &= ~EF_TELEPORT;
VectorClear (grenade->mins);
VectorClear (grenade->maxs);
grenade->s.modelindex = gi.modelindex ("models/projectiles/incend/tris.md2");
grenade->owner = self;
grenade->touch = Incendiary_Touch;
grenade->nextthink = level.time + timer;
grenade->think = Incendiary_Explode;
grenade->dmg = damage;
grenade->dmg_radius = damage_radius;
grenade->classname = "incendiary_grenade";
gi.linkentity (grenade);
}
#endif
// *************************
// MELEE WEAPONS
// *************************
#ifdef INCLUDE_MELEE
void fire_player_melee (edict_t *self, vec3_t start, vec3_t aim, int reach, int damage, int kick, int quiet, int mod)
{
vec3_t forward, right, up;
vec3_t v;
vec3_t point;
trace_t tr;
vectoangles2 (aim, v);
AngleVectors (v, forward, right, up);
VectorNormalize (forward);
VectorMA( start, reach, forward, point);
//see if the hit connects
tr = gi.trace(start, NULL, NULL, point, self, MASK_SHOT);
if (tr.fraction == 1.0)
{
if (!quiet)
gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/swish.wav"), 1, ATTN_NORM, 0);
//FIXME some sound here?
return;
}
if (tr.ent->takedamage == DAMAGE_YES || tr.ent->takedamage == DAMAGE_AIM)
{
// pull the player forward if you do damage
VectorMA(self->velocity, 75, forward, self->velocity);
VectorMA(self->velocity, 75, up, self->velocity);
// do the damage
// FIXME - make the damage appear at right spot and direction
if (mod == MOD_CHAINFIST)
T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2,
DAMAGE_DESTROY_ARMOR | DAMAGE_NO_KNOCKBACK, mod);
else
T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, mod);
if (!quiet)
gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/meatht.wav"), 1, ATTN_NORM, 0);
}
else
{
if (!quiet)
gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/tink1.wav"), 1, ATTN_NORM, 0);
VectorScale (tr.plane.normal, 256, point);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_GUNSHOT);
gi.WritePosition (tr.endpos);
gi.WriteDir (point);
gi.multicast (tr.endpos, MULTICAST_PVS);
}
}
#endif
// *************************
// NUKE
// *************************
#ifdef INCLUDE_NUKE
#define NUKE_DELAY 4
#define NUKE_TIME_TO_LIVE 6
//#define NUKE_TIME_TO_LIVE 40
#define NUKE_RADIUS 512
#define NUKE_DAMAGE 400
#define NUKE_QUAKE_TIME 3
#define NUKE_QUAKE_STRENGTH 100
void Nuke_Quake (edict_t *self)
{
int i;
edict_t *e;
if (self->last_move_time < level.time)
{
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 0.75, ATTN_NONE, 0);
self->last_move_time = level.time + 0.5;
}
for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
{
if (!e->inuse)
continue;
if (!e->client)
continue;
if (!e->groundentity)
continue;
e->groundentity = NULL;
if (!strcmp(self->classname, "shock_sphere"))
{
e->velocity[0] += crandom()* 125;
e->velocity[1] += crandom()* 125;
e->velocity[2] = self->speed * (150.0 / e->mass);
}
else
{
e->velocity[0] += crandom()* 150;
e->velocity[1] += crandom()* 150;
e->velocity[2] = self->speed * (100.0 / e->mass);
}
}
/* if (!strcmp(self->classname, "shock_sphere")) //remove shock sphere after x bounces
{
if (self->count > sk_shockwave_bounces->value)
G_FreeEdict (self);
return; //don't loop
}*/
if (level.time < self->timestamp)
self->nextthink = level.time + FRAMETIME;
else
G_FreeEdict (self);
}
/*static*/ void Nuke_Explode (edict_t *ent)
{
// vec3_t origin;
// nuke_framenum = level.framenum + 20;
if (ent->teammaster->client)
PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT);
T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NUKE);
// VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
if (ent->dmg > NUKE_DAMAGE)
{
if (ent->dmg < (4 * NUKE_DAMAGE)) //double sound
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
else //quad sound
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
}
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0);
/*
gi.WriteByte (svc_temp_entity);
if (ent->groundentity)
gi.WriteByte (TE_GRENADE_EXPLOSION);
else
gi.WriteByte (TE_ROCKET_EXPLOSION);
gi.WritePosition (origin);
gi.multicast (ent->s.origin, MULTICAST_PVS);
*/
// BecomeExplosion1(ent);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_EXPLOSION1_BIG);
gi.WritePosition (ent->s.origin);
gi.multicast (ent->s.origin, MULTICAST_PVS);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_NUKEBLAST);
gi.WritePosition (ent->s.origin);
gi.multicast (ent->s.origin, MULTICAST_ALL);
// Lazarus reflections
if (level.num_reflectors)
{
ReflectExplosion (TE_EXPLOSION1_BIG, ent->s.origin);
// ReflectExplosion (TE_NUKEBLAST, ent->s.origin);
}
// become a quake
ent->svflags |= SVF_NOCLIENT;
ent->noise_index = gi.soundindex ("world/rumble.wav");
ent->think = Nuke_Quake;
ent->speed = NUKE_QUAKE_STRENGTH;
ent->timestamp = level.time + NUKE_QUAKE_TIME;
ent->nextthink = level.time + FRAMETIME;
ent->last_move_time = 0;
}
void nuke_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
self->takedamage = DAMAGE_NO;
if ((attacker) && !(strcmp(attacker->classname, "nuke")))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("nuke nuked by a nuke, not nuking\n");
G_FreeEdict (self);
return;
}
Nuke_Explode(self);
}
void Nuke_Think(edict_t *ent)
{
float attenuation, default_atten = 1.8;
int damage_multiplier, muzzleflash;
// gi.dprintf ("player range: %2.2f damage radius: %2.2f\n", realrange (ent, ent->teammaster), ent->dmg_radius*2);
damage_multiplier = ent->dmg/NUKE_DAMAGE;
switch (damage_multiplier)
{
case 1:
attenuation = default_atten/1.4;
muzzleflash = MZ_NUKE1;
break;
case 2:
attenuation = default_atten/2.0;
muzzleflash = MZ_NUKE2;
break;
case 4:
attenuation = default_atten/3.0;
muzzleflash = MZ_NUKE4;
break;
case 8:
attenuation = default_atten/5.0;
muzzleflash = MZ_NUKE8;
break;
default:
attenuation = default_atten;
muzzleflash = MZ_NUKE1;
break;
}
if (ent->wait < level.time)
Nuke_Explode(ent);
else if (level.time >= (ent->wait - sk_nuke_life->value))
{
ent->s.frame++;
if (ent->s.frame > 11)
ent->s.frame = 6;
if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
{
Nuke_Explode (ent);
return;
}
// Knightmare- remove smoke trail if we've stopped moving
if (ent->groundentity && !VectorLength(ent->velocity))
ent->s.effects &= ~EF_GRENADE;
// but restore it if we go flying
else if (!ent->groundentity)
ent->s.effects |= EF_GRENADE;
ent->think = Nuke_Think;
ent->nextthink = level.time + 0.1;
ent->health = 1;
ent->owner = NULL;
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (ent-g_edicts);
gi.WriteByte (muzzleflash);
gi.multicast (ent->s.origin, MULTICAST_PVS);
if (ent->timestamp <= level.time)
{
/* gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn.wav"), 1, ATTN_NORM, 0);
ent->timestamp += 10.0;
}
*/
if ((ent->wait - level.time) <= (sk_nuke_life->value/2.0))
{
// ent->s.sound = gi.soundindex ("weapons/nukewarn.wav");
// gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
// gi.dprintf ("time %2.2f\n", ent->wait-level.time);
ent->timestamp = level.time + 0.3;
}
else
{
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
ent->timestamp = level.time + 0.5;
// gi.dprintf ("time %2.2f\n", ent->wait-level.time);
}
}
}
else
{
if (ent->timestamp <= level.time)
{
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
// gi.dprintf ("time %2.2f\n", ent->wait-level.time);
ent->timestamp = level.time + 1.0;
}
ent->nextthink = level.time + FRAMETIME;
}
}
void nuke_bounce (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
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);
}
extern byte P_DamageModifier(edict_t *ent);
void fire_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed)
{
edict_t *nuke;
vec3_t dir;
vec3_t forward, right, up;
int damage_modifier;
damage_modifier = (int) P_DamageModifier (self);
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
nuke = G_Spawn();
VectorCopy (start, nuke->s.origin);
VectorScale (aimdir, speed, nuke->velocity);
VectorMA (nuke->velocity, 200 + crandom() * 10.0, up, nuke->velocity);
VectorMA (nuke->velocity, crandom() * 10.0, right, nuke->velocity);
VectorClear (nuke->avelocity);
VectorClear (nuke->s.angles);
nuke->movetype = MOVETYPE_BOUNCE;
nuke->clipmask = MASK_SHOT;
nuke->solid = SOLID_BBOX;
nuke->s.effects |= EF_GRENADE;
nuke->s.renderfx |= RF_IR_VISIBLE;
VectorSet (nuke->mins, -8, -8, 0);
VectorSet (nuke->maxs, 8, 8, 16);
nuke->s.modelindex = gi.modelindex ("models/weapons/g_nuke/tris.md2");
nuke->owner = self;
nuke->teammaster = self;
nuke->wait = level.time + sk_nuke_delay->value + sk_nuke_life->value;
nuke->nextthink = level.time + FRAMETIME;
nuke->think = Nuke_Think;
nuke->touch = nuke_bounce;
nuke->health = 10000;
nuke->takedamage = DAMAGE_YES;
nuke->svflags |= SVF_DAMAGEABLE;
nuke->dmg = NUKE_DAMAGE * damage_modifier;
if (damage_modifier == 1)
nuke->dmg_radius = sk_nuke_radius->value;
else
nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS*(0.25*(float)damage_modifier);
// this yields 1.0, 1.5, 2.0, 3.0 times radius
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("nuke modifier = %d, damage = %d, radius = %f\n", damage_modifier, nuke->dmg, nuke->dmg_radius);
nuke->classname = "nuke";
nuke->die = nuke_die;
gi.linkentity (nuke);
}
#endif
// *************************
// BLU-86 (aka Fuel-Air Explosive, aka NEUTRON BOMB)
// *************************
#ifdef INCLUDE_NBOMB
#define NBOMB_DELAY 4
#define NBOMB_TIME_TO_LIVE 6
//#define NBOMB_TIME_TO_LIVE 40
#define NBOMB_RADIUS 256
#define NBOMB_DAMAGE 5000
#define NBOMB_QUAKE_TIME 3
#define NBOMB_QUAKE_STRENGTH 100
void Nbomb_Quake (edict_t *self)
{
int i;
edict_t *e;
if (self->last_move_time < level.time)
{
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 0.75, ATTN_NONE, 0);
self->last_move_time = level.time + 0.5;
}
for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
{
if (!e->inuse)
continue;
if (!e->client)
continue;
if (!e->groundentity)
continue;
e->groundentity = NULL;
e->velocity[0] += crandom()* 150;
e->velocity[1] += crandom()* 150;
e->velocity[2] = self->speed * (100.0 / e->mass);
}
if (level.time < self->timestamp)
self->nextthink = level.time + FRAMETIME;
else
G_FreeEdict (self);
}
/*static*/ void Nbomb_Explode (edict_t *ent)
{
if (ent->teammaster->client)
PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT);
T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NBOMB);
if (ent->dmg > sk_nbomb_damage->value)
{
if (ent->dmg < (4 * sk_nbomb_damage->value)) //double sound
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
else //quad sound
gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
}
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_EXPLOSION1_BIG);
gi.WritePosition (ent->s.origin);
gi.multicast (ent->s.origin, MULTICAST_PVS);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_NUKEBLAST);
gi.WritePosition (ent->s.origin);
gi.multicast (ent->s.origin, MULTICAST_ALL);
// Lazarus reflections
if (level.num_reflectors)
{
ReflectExplosion (TE_EXPLOSION1_BIG, ent->s.origin);
// ReflectExplosion (TE_NUKEBLAST, ent->s.origin);
}
// become a quake
ent->svflags |= SVF_NOCLIENT;
ent->noise_index = gi.soundindex ("world/rumble.wav");
ent->think = Nbomb_Quake;
ent->speed = NBOMB_QUAKE_STRENGTH;
ent->timestamp = level.time + NBOMB_QUAKE_TIME;
ent->nextthink = level.time + FRAMETIME;
ent->last_move_time = 0;
}
void nbomb_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
self->takedamage = DAMAGE_NO;
if ((attacker) && !(strcmp(attacker->classname, "nbomb")))
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("nbomb nuked by a nbomb, not nuking\n");
G_FreeEdict (self);
return;
}
Nbomb_Explode(self);
}
void Nbomb_Think(edict_t *ent)
{
float attenuation, default_atten = 1.8;
int muzzleflash;
attenuation = default_atten/3.0;
muzzleflash = MZ_NUKE4;
if (ent->wait < level.time)
Nbomb_Explode(ent);
else if (level.time >= (ent->wait - sk_nbomb_life->value))
{
if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
{
Nbomb_Explode (ent);
return;
}
// Knightmare- remove smoke trail if we've stopped moving
if (ent->groundentity && !VectorLength(ent->velocity))
ent->s.effects &= ~EF_GRENADE;
// but restore it if we go flying
else if (!ent->groundentity)
ent->s.effects |= EF_GRENADE;
ent->think = Nbomb_Think;
ent->nextthink = level.time + 0.1;
ent->health = 1;
ent->owner = NULL;
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (ent-g_edicts);
gi.WriteByte (muzzleflash);
gi.multicast (ent->s.origin, MULTICAST_PVS);
if (ent->timestamp <= level.time)
{
/* gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn.wav"), 1, ATTN_NORM, 0);
ent->timestamp += 10.0;
}
*/
if ((ent->wait - level.time) <= (sk_nbomb_life->value / 2.0))
{
// gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
ent->timestamp = level.time + 0.3;
}
else
{
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
ent->timestamp = level.time + 0.5;
}
}
}
else
{
if (ent->timestamp <= level.time)
{
gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
// gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
// gi.dprintf ("time %2.2f\n", ent->wait-level.time);
ent->timestamp = level.time + 1.0;
}
ent->nextthink = level.time + FRAMETIME;
}
}
void nbomb_bounce (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
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);
}
extern byte P_DamageModifier(edict_t *ent);
void fire_nbomb (edict_t *self, vec3_t start, vec3_t aimdir, int speed)
{
edict_t *nbomb;
vec3_t dir;
vec3_t forward, right, up;
int damage_modifier;
damage_modifier = (int) P_DamageModifier (self);
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
nbomb = G_Spawn();
VectorCopy (start, nbomb->s.origin);
VectorScale (aimdir, speed, nbomb->velocity);
VectorMA (nbomb->velocity, 200 + crandom() * 10.0, up, nbomb->velocity);
VectorMA (nbomb->velocity, crandom() * 10.0, right, nbomb->velocity);
//Knightmare- add player's base velocity to nbomb
if (self->groundentity)
VectorAdd (nbomb->velocity, self->groundentity->velocity, nbomb->velocity);
VectorClear (nbomb->avelocity);
VectorClear (nbomb->s.angles);
nbomb->movetype = MOVETYPE_BOUNCE;
nbomb->clipmask = MASK_SHOT;
nbomb->solid = SOLID_BBOX;
nbomb->s.effects |= EF_GRENADE;
nbomb->s.renderfx |= RF_IR_VISIBLE;
VectorSet (nbomb->mins, -8, -8, -16);
VectorSet (nbomb->maxs, 8, 8, 14);
nbomb->s.modelindex = gi.modelindex ("models/items/ammo/nbomb/tris.md2");
nbomb->owner = self;
nbomb->teammaster = self;
nbomb->wait = level.time + sk_nbomb_delay->value + sk_nbomb_life->value;
nbomb->nextthink = level.time + FRAMETIME;
nbomb->think = Nbomb_Think;
nbomb->touch = nbomb_bounce;
nbomb->health = 10000;
nbomb->takedamage = DAMAGE_YES;
nbomb->svflags |= SVF_DAMAGEABLE;
nbomb->dmg = sk_nbomb_damage->value * damage_modifier;
if (damage_modifier == 1)
nbomb->dmg_radius = sk_nbomb_radius->value;
else
nbomb->dmg_radius = sk_nbomb_radius->value + sk_nbomb_radius->value * (0.25*(float)damage_modifier);
// this yields 1.0, 1.5, 2.0, 3.0 times radius
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("nbomb modifier = %d, damage = %d, radius = %f\n", damage_modifier, nbomb->dmg, nbomb->dmg_radius);
nbomb->classname = "nbomb";
nbomb->die = nbomb_die;
gi.linkentity (nbomb);
}
#endif
// *************************
// TESLA
// *************************
#ifdef INCLUDE_TESLA
#define TESLA_TIME_TO_LIVE 600
#define TESLA_DAMAGE_RADIUS 128
#define TESLA_DAMAGE 3 // 3
#define TESLA_KNOCKBACK 8
#define TESLA_ACTIVATE_TIME 3
#define TESLA_EXPLOSION_DAMAGE_MULT 50 // this is the amount the damage is multiplied by for underwater explosions
#define TESLA_EXPLOSION_RADIUS 200
void tesla_remove (edict_t *self)
{
edict_t *cur, *next;
self->takedamage = DAMAGE_NO;
if (self->teamchain)
{
cur = self->teamchain;
while (cur)
{
next = cur->teamchain;
G_FreeEdict ( cur );
cur = next;
}
}
else if (self->air_finished)
gi.dprintf ("tesla without a field!\n");
self->owner = self->teammaster; // Going away, set the owner correctly.
// PGM - grenade explode does damage to self->enemy
self->enemy = NULL;
// play quad sound if quadded and an underwater explosion
if ((self->dmg_radius) && (self->dmg > (sk_tesla_damage->value*TESLA_EXPLOSION_DAMAGE_MULT)))
{
if (self->dmg < 4 * (sk_tesla_damage->value*TESLA_EXPLOSION_DAMAGE_MULT)) //double sound
gi.sound(self, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
else //quad sound
gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
}
Grenade_Explode(self);
}
void tesla_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
// gi.dprintf("tesla killed\n");
tesla_remove(self);
}
void tesla_blow (edict_t *self)
{
// T_RadiusDamage(self, self->owner, TESLA_EXPLOSION_DAMAGE, NULL, TESLA_EXPLOSION_RADIUS, MOD_TESLA);
self->dmg = self->dmg * TESLA_EXPLOSION_DAMAGE_MULT;
self->dmg_radius = TESLA_EXPLOSION_RADIUS;
tesla_remove(self);
}
void tesla_zap (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
}
void tesla_think_active (edict_t *self)
{
int i,num;
edict_t *touch[MAX_EDICTS], *hit;
vec3_t dir, start;
trace_t tr;
if (level.time > self->air_finished)
{
tesla_remove(self);
return;
}
VectorCopy(self->s.origin, start);
start[2] += 12; // was 16
num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax, touch, MAX_EDICTS, AREA_SOLID);
for (i=0;i<num;i++)
{
// if the tesla died while zapping things, stop zapping.
if (!(self->inuse))
break;
hit=touch[i];
if (!hit->inuse)
continue;
if (hit == self)
continue;
if (hit->health < 1)
continue;
// don't hit clients in single-player or coop
if (hit->client)
if (coop->value || !deathmatch->value)
continue;
if (!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client)
continue;
tr = gi.trace(start, vec3_origin, vec3_origin, hit->s.origin, self, MASK_SHOT);
if (tr.fraction == 1 || tr.ent == hit)// || tr.ent->client || (tr.ent->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)))
{
VectorSubtract(hit->s.origin, start, dir);
// PMM - play quad sound if it's above the "normal" damage
if (self->dmg > sk_tesla_damage->value)
{
if (self->dmg < (4 * sk_tesla_damage->value)) //double sound
gi.sound(self, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
else //quad sound
gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
}
// PGM - don't do knockback to walking monsters
if ((hit->svflags & SVF_MONSTER) && !(hit->flags & (FL_FLY|FL_SWIM)))
T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal,
self->dmg, 0, 0, MOD_TESLA);
else
T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal,
self->dmg, TESLA_KNOCKBACK, 0, MOD_TESLA);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_LIGHTNING);
gi.WriteShort (hit - g_edicts); // destination entity
gi.WriteShort (self - g_edicts); // source entity
gi.WritePosition (tr.endpos);
gi.WritePosition (start);
gi.multicast (start, MULTICAST_PVS);
// Lazarus reflections
if (level.num_reflectors)
ReflectLightning (hit, self, tr.endpos, start);
}
}
if (self->inuse)
{
self->think = tesla_think_active;
self->nextthink = level.time + FRAMETIME;
}
}
void tesla_activate (edict_t *self)
{
edict_t *trigger;
edict_t *search;
if (gi.pointcontents (self->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WATER))
{
tesla_blow (self);
return;
}
// only check for spawn points in deathmatch
if (deathmatch->value)
{
search = NULL;
while ((search = findradius(search, self->s.origin, 1.5*TESLA_DAMAGE_RADIUS)) != NULL)
{
//if (!search->takedamage)
// continue;
// if it's a monster or player with health > 0
// or it's a deathmatch start point
// and we can see it
// blow up
if (search->classname)
{
if ( ( (!strcmp(search->classname, "info_player_deathmatch"))
|| (!strcmp(search->classname, "info_player_start"))
|| (!strcmp(search->classname, "info_player_coop"))
|| (!strcmp(search->classname, "misc_teleporter_dest"))
)
&& (visible (search, self))
)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("Tesla to close to %s, removing!\n", search->classname);
tesla_remove (self);
return;
}
}
}
}
trigger = G_Spawn();
// if (trigger->nextthink)
// {
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("tesla_activate: fixing nextthink\n");
// trigger->nextthink = 0;
// }
VectorCopy (self->s.origin, trigger->s.origin);
VectorSet (trigger->mins, (sk_tesla_radius->value * -1), (sk_tesla_radius->value * -1), self->mins[2]);
VectorSet (trigger->maxs, sk_tesla_radius->value, sk_tesla_radius->value, sk_tesla_radius->value);
trigger->movetype = MOVETYPE_NONE;
trigger->solid = SOLID_TRIGGER;
trigger->owner = self;
trigger->touch = tesla_zap;
trigger->classname = "tesla trigger";
// doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains
gi.linkentity (trigger);
VectorClear (self->s.angles);
// clear the owner if in deathmatch
if (deathmatch->value)
self->owner = NULL;
self->teamchain = trigger;
self->think = tesla_think_active;
self->nextthink = level.time + FRAMETIME;
self->air_finished = level.time + sk_tesla_life->value;
}
void tesla_think (edict_t *ent)
{
if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
{
tesla_remove (ent);
return;
}
VectorClear (ent->s.angles);
// Knightmare- remove smoke trail if we've stopped moving
if (ent->groundentity && !VectorLength(ent->velocity))
ent->s.effects &= ~EF_GRENADE;
// but restore it if we go flying
else if (!ent->groundentity)
ent->s.effects |= EF_GRENADE;
if (!(ent->s.frame))
gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/teslaopen.wav"), 1, ATTN_NORM, 0);
ent->s.frame++;
if (ent->s.frame > 14)
{
ent->s.frame = 14;
ent->think = tesla_activate;
ent->nextthink = level.time + 0.1;
}
else
{
if (ent->s.frame > 9)
{
if (ent->s.frame == 10)
{
if (ent->owner && ent->owner->client)
{
PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON); // PGM
}
ent->s.skinnum = 1;
}
else if (ent->s.frame == 12)
ent->s.skinnum = 2;
else if (ent->s.frame == 14)
ent->s.skinnum = 3;
}
ent->think = tesla_think;
ent->nextthink = level.time + 0.1;
}
}
void tesla_lava (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t land_point;
// catch bad pointers
// if ( !plane || !plane->normal )
// return;
if (plane != NULL && plane->normal)
{
VectorMA (ent->s.origin, -20.0, plane->normal, land_point);
if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA))
{
tesla_blow (ent);
return;
}
}
if (ent->s.effects & EF_GRENADE) // don't make bounce sound if resting on lift
{
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);
}
}
void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed)
{
edict_t *tesla;
vec3_t dir;
vec3_t forward, right, up;
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
tesla = G_Spawn();
VectorCopy (start, tesla->s.origin);
VectorScale (aimdir, speed, tesla->velocity);
VectorMA (tesla->velocity, 200 + crandom() * 10.0, up, tesla->velocity);
VectorMA (tesla->velocity, crandom() * 10.0, right, tesla->velocity);
//Knightmare- add player's base velocity to thrown tesla
if (add_velocity_throw->value && self->client)
VectorAdd (tesla->velocity, self->velocity, tesla->velocity);
else if (self->groundentity)
VectorAdd (tesla->velocity, self->groundentity->velocity, tesla->velocity);
// VectorCopy (dir, tesla->s.angles);
VectorClear (tesla->s.angles);
tesla->movetype = MOVETYPE_BOUNCE;
tesla->solid = SOLID_BBOX;
tesla->s.effects |= EF_GRENADE;
tesla->s.renderfx |= RF_IR_VISIBLE;
// VectorClear (tesla->mins);
// VectorClear (tesla->maxs);
VectorSet (tesla->mins, -12, -12, 0);
VectorSet (tesla->maxs, 12, 12, 20);
tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2");
tesla->owner = self; // PGM - we don't want it owned by self YET.
tesla->teammaster = self;
tesla->wait = level.time + sk_tesla_life->value;
tesla->think = tesla_think;
tesla->nextthink = level.time + TESLA_ACTIVATE_TIME;
// blow up on contact with lava & slime code
tesla->touch = tesla_lava;
if (deathmatch->value)
// PMM - lowered from 50 - 7/29/1998
tesla->health = sk_tesla_health->value;
else
tesla->health = sk_tesla_health->value; // FIXME - change depending on skill?
tesla->takedamage = DAMAGE_YES;
tesla->die = tesla_die;
tesla->dmg = sk_tesla_damage->value * damage_multiplier;
// tesla->dmg = 0;
tesla->classname = "tesla";
tesla->svflags |= SVF_DAMAGEABLE;
tesla->clipmask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
tesla->flags |= FL_MECHANICAL;
gi.linkentity (tesla);
}
// NOTE: SP_tesla should ONLY be used to tesla mines that change maps
// via a trigger_transition. They should NOT be used for map entities.
void tesla_delayed_start (edict_t *tesla)
{
if (g_edicts[1].linkcount)
{
VectorScale(tesla->movedir,tesla->moveinfo.speed,tesla->velocity);
tesla->movetype = MOVETYPE_BOUNCE;
tesla->think = tesla_think;
tesla->nextthink = level.time + TESLA_ACTIVATE_TIME;
gi.linkentity(tesla);
}
else
tesla->nextthink = level.time + FRAMETIME;
}
void SP_tesla (edict_t *tesla)
{
tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2");
tesla->touch = tesla_lava;
// For SP, freeze tesla until player spawns in
if (game.maxclients == 1)
{
tesla->movetype = MOVETYPE_NONE;
VectorCopy(tesla->velocity,tesla->movedir);
VectorNormalize(tesla->movedir);
tesla->moveinfo.speed = VectorLength(tesla->velocity);
VectorClear(tesla->velocity);
tesla->think = tesla_delayed_start;
tesla->nextthink = level.time + FRAMETIME;
}
else
{
tesla->movetype = MOVETYPE_BOUNCE;
tesla->think = tesla_think;
tesla->nextthink = level.time + TESLA_ACTIVATE_TIME;
}
gi.linkentity (tesla);
}
#endif
// *************************
// HEATBEAM
// *************************
#ifdef INCLUDE_BEAMS
/*static*/ void fire_beams (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, int te_beam, int te_impact, int mod)
{
trace_t tr;
vec3_t dir;
vec3_t forward, right, up;
vec3_t end;
vec3_t water_start, endpoint;
qboolean water = false, underwater = false;
int content_mask = MASK_SHOT | MASK_WATER;
vec3_t beam_endpt;
// tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
// if (!(tr.fraction < 1.0))
// {
vectoangles2 (aimdir, dir);
AngleVectors (dir, forward, right, up);
VectorMA (start, WORLD_SIZE, forward, end); // was 8192
if (gi.pointcontents (start) & MASK_WATER)
{
// gi.dprintf ("Heat beam under water\n");
underwater = 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)
{
water = true;
VectorCopy (tr.endpos, water_start);
if (!VectorCompare (start, tr.endpos))
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_HEATBEAM_SPARKS);
// gi.WriteByte (50);
gi.WritePosition (water_start);
gi.WriteDir (tr.plane.normal);
// gi.WriteByte (8);
// gi.WriteShort (60);
gi.multicast (tr.endpos, MULTICAST_PVS);
}
// re-trace ignoring water this time
tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
}
VectorCopy (tr.endpos, endpoint);
// }
// halve the damage if target underwater
if (water)
{
damage = damage /2;
}
// 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_ENERGY, mod);
}
else
{
if ((!water) && (strncmp (tr.surface->name, "sky", 3)))
{
// This is the truncated steam entry - uses 1+1+2 extra bytes of data
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_HEATBEAM_STEAM);
// gi.WriteByte (20);
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
// gi.WriteByte (0xe0);
// gi.WriteShort (60);
gi.multicast (tr.endpos, MULTICAST_PVS);
// Lazarus reflections
if (level.num_reflectors)
ReflectSparks (TE_HEATBEAM_STEAM, 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) || (underwater))
{
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_BUBBLETRAIL2);
// gi.WriteByte (8);
gi.WritePosition (water_start);
gi.WritePosition (tr.endpos);
gi.multicast (pos, MULTICAST_PVS);
}
if ((!underwater) && (!water))
{
VectorCopy (tr.endpos, beam_endpt);
}
else
{
VectorCopy (endpoint, beam_endpt);
}
// Knightmare- Gen cam code
// if (self->client->chasetoggle)
if (self->client && self->client->chaseactive)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (te_beam);
gi.WriteShort (self->client->oldplayer - g_edicts);
gi.WritePosition (start);
gi.WritePosition (beam_endpt);
gi.multicast (self->client->oldplayer->s.origin, MULTICAST_ALL);
// Lazarus reflections
if (level.num_reflectors)
ReflectHeatBeam (te_beam, self->client->oldplayer, start, beam_endpt);
}
else
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (te_beam);
gi.WriteShort (self - g_edicts);
gi.WritePosition (start);
gi.WritePosition (beam_endpt);
gi.multicast (self->s.origin, MULTICAST_ALL);
// Lazarus reflections
if (level.num_reflectors)
ReflectHeatBeam (te_beam, self, start, beam_endpt);
}
}
/*
=================
fire_heat
Fires a single heat beam. Zap.
=================
*/
void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster)
{
if (monster)
fire_beams (self, start, aimdir, offset, damage, kick, TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
else
fire_beams (self, start, aimdir, offset, damage, kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
}
#endif
// *************************
// BLASTER 2
// *************************
/*
=================
fire_blaster2
Fires a single green blaster bolt. Used by monsters, generally.
=================
*/
void blaster2_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
int mod;
int damagestat;
if (other == self->owner)
return;
if (surf && (surf->flags & SURF_SKY))
{
G_FreeEdict (self);
return;
}
if (self->owner && self->owner->client)
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
if (other->takedamage)
{
// the only time players will be firing blaster2 bolts will be from the
// defender sphere.
if (self->owner->client)
mod = MOD_DEFENDER_SPHERE;
else
mod = MOD_BLASTER2;
if (self->owner)
{
damagestat = self->owner->takedamage;
self->owner->takedamage = DAMAGE_NO;
if (self->dmg >= 5)
T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0);
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
self->owner->takedamage = damagestat;
}
else
{
if (self->dmg >= 5)
T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0);
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
}
}
else
{
//PMM - yeowch this will get expensive
if (self->dmg >= 5)
T_RadiusDamage(self, self->owner, self->dmg*3, self->owner, self->dmg_radius, 0);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_BLASTER2);
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(TE_BLASTER2, self->s.origin, vec3_origin);
else
ReflectSparks(TE_BLASTER2, self->s.origin, plane->normal);
}
}
G_FreeEdict (self);
}
void fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper)
{
edict_t *bolt;
trace_t tr;
VectorNormalize (dir);
bolt = G_Spawn();
VectorCopy (start, bolt->s.origin);
VectorCopy (start, bolt->s.old_origin);
vectoangles2 (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 (effect)
bolt->s.effects |= EF_TRACKER;
bolt->dmg_radius = 128;
bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2");
bolt->touch = blaster2_touch;
bolt->owner = self;
bolt->nextthink = level.time + 2;
bolt->think = G_FreeEdict;
bolt->dmg = damage;
bolt->classname = "bolt2";
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_bolt2 should ONLY be used for green monster blaster bolts that have
// changed maps via trigger_transition. It should NOT be used for map
// entities.
void bolt2_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_bolt2 (edict_t *bolt)
{
bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2");
bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
bolt->touch = blaster2_touch;
VectorCopy(bolt->velocity,bolt->movedir);
VectorNormalize(bolt->movedir);
bolt->moveinfo.speed = VectorLength(bolt->velocity);
VectorClear(bolt->velocity);
bolt->think = bolt2_delayed_start;
bolt->nextthink = level.time + FRAMETIME;
gi.linkentity(bolt);
}
// *************************
// tracker
// *************************
/*
void tracker_boom_think (edict_t *self)
{
self->s.frame--;
if (self->s.frame < 0)
G_FreeEdict(self);
else
self->nextthink = level.time + 0.1;
}
void tracker_boom_spawn (vec3_t origin)
{
edict_t *boom;
boom = G_Spawn();
VectorCopy (origin, boom->s.origin);
boom->s.modelindex = gi.modelindex ("models/items/spawngro/tris.md2");
boom->s.skinnum = 1;
boom->s.frame = 2;
boom->classname = "tracker boom";
gi.linkentity (boom);
boom->think = tracker_boom_think;
boom->nextthink = level.time + 0.1;
//PMM
// boom->s.renderfx |= RF_TRANSLUCENT;
boom->s.effects |= EF_SPHERETRANS;
//pmm
}
*/
#define TRACKER_DAMAGE_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK)
#define TRACKER_IMPACT_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY)
#define TRACKER_DAMAGE_TIME 0.5 // seconds
void tracker_pain_daemon_think (edict_t *self)
{
static vec3_t pain_normal = { 0, 0, 1 };
int hurt;
if (!self->inuse)
return;
if ((level.time - self->timestamp) > TRACKER_DAMAGE_TIME)
{
if (!self->enemy->client)
self->enemy->s.effects &= ~EF_TRACKERTRAIL;
G_FreeEdict (self);
}
else
{
if (self->enemy->health > 0)
{
// gi.dprintf("ouch %x\n", self);
T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, pain_normal,
self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
// if we kill the player, we'll be removed.
if (self->inuse)
{
// if we killed a monster, gib them.
if (self->enemy->health < 1)
{
if (self->enemy->gib_health)
hurt = - self->enemy->gib_health;
else
hurt = 500;
// gi.dprintf("non-player killed. ensuring gib! %d\n", hurt);
T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin,
pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
}
if (self->enemy->client)
self->enemy->client->tracker_pain_framenum = level.framenum + 1;
else
self->enemy->s.effects |= EF_TRACKERTRAIL;
self->nextthink = level.time + FRAMETIME;
}
}
else
{
if (!self->enemy->client)
self->enemy->s.effects &= ~EF_TRACKERTRAIL;
G_FreeEdict (self);
}
}
}
void tracker_pain_daemon_spawn (edict_t *owner, edict_t *enemy, int damage)
{
edict_t *daemon;
if (enemy == NULL)
return;
daemon = G_Spawn();
daemon->classname = "pain daemon";
daemon->think = tracker_pain_daemon_think;
daemon->nextthink = level.time + FRAMETIME;
daemon->timestamp = level.time;
daemon->owner = owner;
daemon->enemy = enemy;
daemon->dmg = damage;
}
void tracker_explode (edict_t *self, cplane_t *plane)
{
vec3_t dir;
if (!plane)
VectorClear (dir);
else
VectorScale (plane->normal, 256, dir);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_TRACKER_EXPLOSION);
gi.WritePosition (self->s.origin);
gi.multicast (self->s.origin, MULTICAST_PVS);
// Lazarus reflections
if (level.num_reflectors)
ReflectExplosion (TE_TRACKER_EXPLOSION, self->s.origin);
// gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/disrupthit.wav"), 1, ATTN_NORM, 0);
// tracker_boom_spawn(self->s.origin);
G_FreeEdict (self);
}
void tracker_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
float damagetime;
if (other == self->owner)
return;
if (surf && (surf->flags & SURF_SKY))
{
G_FreeEdict (self);
return;
}
if (self->client)
PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
if (other->takedamage)
{
if ((other->svflags & SVF_MONSTER) || other->client)
{
if (other->health > 0) // knockback only for living creatures
{
// PMM - kickback was times 4 .. reduced to 3
// now this does no damage, just knockback
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
/* self->dmg */ 0, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
if (!(other->flags & (FL_FLY|FL_SWIM)))
other->velocity[2] += 140;
damagetime = ((float)self->dmg)*FRAMETIME;
damagetime = damagetime / TRACKER_DAMAGE_TIME;
// gi.dprintf ("damage is %f\n", damagetime);
tracker_pain_daemon_spawn (self->owner, other, (int)damagetime);
}
else // lots of damage (almost autogib) for dead bodies
{
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
self->dmg*4, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
}
}
else // full damage in one shot for inanimate objects
{
T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
self->dmg, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
}
}
tracker_explode (self, plane);
return;
}
void tracker_fly (edict_t *self)
{
vec3_t dest;
vec3_t dir;
vec3_t center;
if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1))
{
tracker_explode (self, NULL);
return;
}
/*
VectorCopy (self->enemy->s.origin, dest);
if (self->enemy->client)
dest[2] += self->enemy->viewheight;
*/
// PMM - try to hunt for center of enemy, if possible and not client
if (self->enemy->client)
{
VectorCopy (self->enemy->s.origin, dest);
dest[2] += self->enemy->viewheight;
}
// paranoia
else if (VectorCompare(self->enemy->absmin, vec3_origin) || VectorCompare(self->enemy->absmax, vec3_origin))
{
VectorCopy (self->enemy->s.origin, dest);
}
else
{
VectorMA (vec3_origin, 0.5, self->enemy->absmin, center);
VectorMA (center, 0.5, self->enemy->absmax, center);
VectorCopy (center, dest);
}
VectorSubtract (dest, self->s.origin, dir);
VectorNormalize (dir);
vectoangles2 (dir, self->s.angles);
VectorScale (dir, self->speed, self->velocity);
VectorCopy(dest, self->monsterinfo.saved_goal);
self->nextthink = level.time + 0.1;
}
void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy)
{
edict_t *tracker;
trace_t tr;
VectorNormalize (dir);
tracker = G_Spawn();
VectorCopy (start, tracker->s.origin);
VectorCopy (start, tracker->s.old_origin);
vectoangles2 (dir, tracker->s.angles);
VectorScale (dir, speed, tracker->velocity);
tracker->movetype = MOVETYPE_FLYMISSILE;
tracker->clipmask = MASK_SHOT;
tracker->solid = SOLID_BBOX;
tracker->speed = speed;
tracker->s.effects = EF_TRACKER;
tracker->s.renderfx |= RF_NOSHADOW; //Knightmare- no shadow
tracker->s.sound = gi.soundindex ("weapons/disrupt.wav");
VectorClear (tracker->mins);
VectorClear (tracker->maxs);
tracker->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2");
tracker->touch = tracker_touch;
tracker->enemy = enemy;
tracker->owner = self;
tracker->dmg = damage;
tracker->classname = "tracker";
gi.linkentity (tracker);
if (enemy)
{
tracker->nextthink = level.time + 0.1;
tracker->think = tracker_fly;
}
else
{
tracker->nextthink = level.time + 10;
tracker->think = G_FreeEdict;
}
if (self->client)
check_dodge (self, tracker->s.origin, dir, speed);
tr = gi.trace (self->s.origin, NULL, NULL, tracker->s.origin, tracker, MASK_SHOT);
if (tr.fraction < 1.0)
{
VectorMA (tracker->s.origin, -10, dir, tracker->s.origin);
tracker->touch (tracker, tr.ent, NULL, NULL);
}
}
// SP_tracker should ONLY be used for disruptors that have changed
// maps via trigger_transition. It should NOT be used for map entities.
void tracker_delayed_start (edict_t *tracker)
{
if (g_edicts[1].linkcount)
{
VectorScale(tracker->movedir,tracker->moveinfo.speed,tracker->velocity);
tracker->nextthink = level.time + 10;
tracker->think = G_FreeEdict;
gi.linkentity(tracker);
}
else
tracker->nextthink = level.time + FRAMETIME;
}
void SP_tracker (edict_t *tracker)
{
tracker->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2");
tracker->s.sound = gi.soundindex ("weapons/disrupt.wav");
tracker->touch = tracker_touch;
VectorCopy(tracker->velocity,tracker->movedir);
VectorNormalize(tracker->movedir);
tracker->moveinfo.speed = VectorLength(tracker->velocity);
VectorClear(tracker->velocity);
tracker->think = tracker_delayed_start;
tracker->nextthink = level.time + FRAMETIME;
gi.linkentity(tracker);
}