2247 lines
58 KiB
C
2247 lines
58 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_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 drawbbox (edict_t *ent);
|
|
|
|
#define DAMAGE_FIRE 5
|
|
|
|
void vectoangles2 (vec3_t value1, vec3_t angles)
|
|
{
|
|
float forward;
|
|
float yaw, pitch;
|
|
|
|
if (value1[1] == 0 && value1[0] == 0)
|
|
{
|
|
yaw = 0;
|
|
if (value1[2] > 0)
|
|
pitch = 90;
|
|
else
|
|
pitch = 270;
|
|
}
|
|
else
|
|
{
|
|
// PMM - fixed to correct for pitch of 0
|
|
if (value1[0])
|
|
yaw = (atan2(value1[1], value1[0]) * 180 / M_PI);
|
|
else if (value1[1] > 0)
|
|
yaw = 90;
|
|
else
|
|
yaw = 270;
|
|
|
|
if (yaw < 0)
|
|
yaw += 360;
|
|
|
|
forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
|
|
pitch = (atan2(value1[2], forward) * 180 / M_PI);
|
|
if (pitch < 0)
|
|
pitch += 360;
|
|
}
|
|
|
|
angles[PITCH] = -pitch;
|
|
angles[YAW] = yaw;
|
|
angles[ROLL] = 0;
|
|
}
|
|
|
|
void G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result)
|
|
{
|
|
result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] + up[0] * distance[2];
|
|
result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] + up[1] * distance[2];
|
|
result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + up[2] * distance[2];
|
|
}
|
|
static void P_ProjectSource2 (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward,
|
|
vec3_t right, vec3_t up, vec3_t result)
|
|
{
|
|
vec3_t _distance;
|
|
|
|
VectorCopy (distance, _distance);
|
|
if (client->pers.hand == LEFT_HANDED)
|
|
_distance[1] *= -1;
|
|
else if (client->pers.hand == CENTER_HANDED)
|
|
_distance[1] = 0;
|
|
G_ProjectSource2 (point, _distance, forward, right, up, result);
|
|
}
|
|
|
|
#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, 0, 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);
|
|
|
|
// T_RadiusDamage(self, self->owner, 24, self, 48, MOD_ETF_RIFLE);
|
|
}
|
|
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick)
|
|
{
|
|
edict_t *flechette;
|
|
|
|
VectorNormalize (dir);
|
|
|
|
flechette = G_Spawn();
|
|
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;
|
|
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 = kick;
|
|
|
|
gi.linkentity (flechette);
|
|
|
|
if (self->client)
|
|
check_dodge (self, flechette->s.origin, dir, speed);
|
|
}
|
|
#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_new (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, MOD_FLAMETHROWER);
|
|
}
|
|
|
|
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_new (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.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; //gar
|
|
|
|
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
|
|
|
|
// *************************
|
|
// 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;
|
|
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 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);
|
|
T_RadiusDamage(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)
|
|
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);
|
|
|
|
// 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:
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("default attenuation used for nuke!\n");
|
|
attenuation = default_atten;
|
|
muzzleflash = MZ_NUKE1;
|
|
break;
|
|
}
|
|
|
|
if(ent->wait < level.time)
|
|
Nuke_Explode(ent);
|
|
else if (level.time >= (ent->wait - NUKE_TIME_TO_LIVE))
|
|
{
|
|
ent->s.frame++;
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("nuke frame %d\n", 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;
|
|
}
|
|
|
|
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) <= (NUKE_TIME_TO_LIVE/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);
|
|
}
|
|
|
|
|
|
//========
|
|
//ROGUE
|
|
byte P_DamageModifier(edict_t *ent)
|
|
{
|
|
int damage_multiplier;
|
|
|
|
is_quad = 0;
|
|
damage_multiplier = 1;
|
|
|
|
if(ent->client->quad_framenum > level.framenum)
|
|
{
|
|
damage_multiplier *= 4;
|
|
is_quad = 1;
|
|
|
|
// if we're quad and DF_NO_STACK_DOUBLE is on, return now.
|
|
if(((int)(dmflags->value) & DF_NO_STACK_DOUBLE))
|
|
return damage_multiplier;
|
|
}
|
|
if(ent->client->double_framenum > level.framenum)
|
|
{
|
|
if ((deathmatch->value) || (damage_multiplier == 1))
|
|
{
|
|
damage_multiplier *= 2;
|
|
is_quad = 1;
|
|
}
|
|
}
|
|
|
|
return damage_multiplier;
|
|
}
|
|
//ROGUE
|
|
//========
|
|
|
|
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->nextthink = level.time + FRAMETIME;
|
|
nuke->wait = level.time + NUKE_DELAY + NUKE_TIME_TO_LIVE;
|
|
nuke->think = Nuke_Think;
|
|
nuke->touch = nuke_bounce;
|
|
|
|
nuke->health = 10000;
|
|
nuke->takedamage = DAMAGE_YES;
|
|
// nuke->svflags |= SVF_DAMAGEABLE; //gar
|
|
nuke->dmg = NUKE_DAMAGE * damage_modifier;
|
|
if (damage_modifier == 1)
|
|
nuke->dmg_radius = NUKE_RADIUS;
|
|
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
|
|
|
|
// *************************
|
|
// TESLA
|
|
// *************************
|
|
|
|
#ifdef INCLUDE_TESLA
|
|
#define TESLA_TIME_TO_LIVE 30
|
|
#define TESLA_DAMAGE_RADIUS 128
|
|
#define TESLA_DAMAGE DAMAGE_TESLA
|
|
#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 > (TESLA_DAMAGE*TESLA_EXPLOSION_DAMAGE_MULT)))
|
|
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] += 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;
|
|
//GAR if(!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client)
|
|
if(!(hit->svflags & (SVF_MONSTER )) && !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 > TESLA_DAMAGE)
|
|
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);
|
|
}
|
|
}
|
|
|
|
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, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, self->mins[2]);
|
|
VectorSet (trigger->maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS);
|
|
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 + TESLA_TIME_TO_LIVE;
|
|
}
|
|
|
|
void tesla_think (edict_t *ent)
|
|
{
|
|
if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
|
|
{
|
|
tesla_remove (ent);
|
|
return;
|
|
}
|
|
VectorClear (ent->s.angles);
|
|
|
|
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;
|
|
|
|
if (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 (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);
|
|
// 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->s.modelindex = gi.modelindex (GRTESLA_MODEL);
|
|
tesla->s.skinnum = GRTESLA_SKIN;
|
|
|
|
tesla->owner = self; // PGM - we don't want it owned by self YET.
|
|
tesla->teammaster = self;
|
|
|
|
tesla->wait = level.time + TESLA_TIME_TO_LIVE;
|
|
//GAR tesla->think = tesla_think;
|
|
tesla->think = tesla_activate;
|
|
//GAR tesla->nextthink = level.time + TESLA_ACTIVATE_TIME;
|
|
tesla->nextthink = level.time + 3.0;
|
|
|
|
// blow up on contact with lava & slime code
|
|
tesla->touch = tesla_lava;
|
|
|
|
if(deathmatch->value)
|
|
// PMM - lowered from 50 - 7/29/1998
|
|
tesla->health = 20;
|
|
else
|
|
tesla->health = 30; // FIXME - change depending on skill?
|
|
|
|
tesla->takedamage = DAMAGE_YES;
|
|
tesla->die = tesla_die;
|
|
tesla->dmg = TESLA_DAMAGE*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);
|
|
}
|
|
#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, 8192, forward, end);
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
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_blaster3
|
|
|
|
Fires a single green blaster bolt. Used by monsters, generally.
|
|
=================
|
|
*/
|
|
void blaster3_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 blaster3 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);
|
|
}
|
|
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
void fire_blaster3 (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;
|
|
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 = blaster3_touch;
|
|
|
|
bolt->owner = self;
|
|
bolt->nextthink = level.time + 2;
|
|
bolt->think = G_FreeEdict;
|
|
bolt->dmg = damage;
|
|
bolt->classname = "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)
|
|
{
|
|
VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
|
|
bolt->touch (bolt, tr.ent, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
// *************************
|
|
// 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_ENERGY | DAMAGE_NO_KNOCKBACK)
|
|
#define TRACKER_IMPACT_FLAGS (DAMAGE_ENERGY)
|
|
//#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,
|
|
//gar self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
|
|
self->dmg, 0, 0, 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);
|
|
|
|
// 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 *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->speed = speed;
|
|
bolt->s.effects = EF_TRACKER;
|
|
bolt->s.sound = gi.soundindex ("weapons/disrupt.wav");
|
|
VectorClear (bolt->mins);
|
|
VectorClear (bolt->maxs);
|
|
|
|
bolt->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2");
|
|
bolt->touch = tracker_touch;
|
|
bolt->enemy = enemy;
|
|
bolt->owner = self;
|
|
bolt->dmg = damage;
|
|
bolt->classname = "tracker";
|
|
gi.linkentity (bolt);
|
|
|
|
if(enemy)
|
|
{
|
|
bolt->nextthink = level.time + 0.1;
|
|
bolt->think = tracker_fly;
|
|
}
|
|
else
|
|
{
|
|
bolt->nextthink = level.time + 10;
|
|
bolt->think = G_FreeEdict;
|
|
}
|
|
|
|
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)
|
|
{
|
|
VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
|
|
bolt->touch (bolt, tr.ent, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Disintegrator
|
|
//
|
|
|
|
void weapon_tracker_fire (edict_t *self)
|
|
{
|
|
vec3_t forward, right;
|
|
vec3_t start;
|
|
vec3_t end;
|
|
vec3_t offset;
|
|
edict_t *enemy;
|
|
trace_t tr;
|
|
int damage;
|
|
vec3_t mins, maxs;
|
|
|
|
// PMM - felt a little high at 25
|
|
damage = wf_game.weapon_damage[WEAPON_DISRUPTOR];
|
|
|
|
// if (is_quad)
|
|
// damage *= damage_multiplier; //pgm
|
|
|
|
VectorSet(mins, -16, -16, -16);
|
|
VectorSet(maxs, 16, 16, 16);
|
|
AngleVectors (self->client->v_angle, forward, right, NULL);
|
|
VectorSet(offset, 24, 8, self->viewheight-8);
|
|
P_ProjectSource (self->client, self->s.origin, offset, forward, right, start);
|
|
|
|
// FIXME - can we shorten this? do we need to?
|
|
VectorMA (start, 8192, forward, end);
|
|
enemy = NULL;
|
|
//PMM - doing two traces .. one point and one box.
|
|
tr = gi.trace (start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
|
|
if(tr.ent != world)
|
|
{
|
|
if(tr.ent->svflags & SVF_MONSTER || tr.ent->client )
|
|
{
|
|
if(tr.ent->health > 0)
|
|
enemy = tr.ent;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tr = gi.trace (start, mins, maxs, end, self, MASK_SHOT);
|
|
if(tr.ent != world)
|
|
{
|
|
if(tr.ent->svflags & SVF_MONSTER || tr.ent->client)
|
|
{
|
|
if(tr.ent->health > 0)
|
|
enemy = tr.ent;
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorScale (forward, -2, self->client->kick_origin);
|
|
self->client->kick_angles[0] = -1;
|
|
|
|
fire_tracker (self, start, forward, damage, wf_game.weapon_speed[WEAPON_DISRUPTOR], enemy);
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (self-g_edicts);
|
|
gi.WriteByte (MZ_TRACKER);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
PlayerNoise(self, start, PNOISE_WEAPON);
|
|
|
|
self->client->ps.gunframe++;
|
|
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
|
|
}
|
|
|
|
void Weapon_Disintegrator (edict_t *ent)
|
|
{
|
|
static int pause_frames[] = {14, 19, 23, 0};
|
|
// static int fire_frames[] = {7, 0};
|
|
static int fire_frames[] = {5, 0};
|
|
|
|
// Weapon_Generic (ent, 4, 9, 29, 34, pause_frames, fire_frames, weapon_tracker_fire);
|
|
Weapon_Generic (ent, 4, 12, 32, 37, pause_frames, fire_frames, weapon_tracker_fire);
|
|
}
|
|
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
ETF RIFLE
|
|
|
|
======================================================================
|
|
*/
|
|
void weapon_etf_rifle_fire (edict_t *ent)
|
|
{
|
|
vec3_t forward, right, up;
|
|
vec3_t start, tempPt;
|
|
int damage;
|
|
int kick = 3;
|
|
int i;
|
|
vec3_t angles;
|
|
vec3_t offset;
|
|
|
|
damage = wf_game.weapon_damage[WEAPON_ETF_RIFLE];
|
|
|
|
// PGM - adjusted to use the quantity entry in the weapon structure.
|
|
if(ent->client->pers.inventory[ent->client->ammo_index] < ent->client->pers.weapon->quantity)
|
|
{
|
|
VectorClear (ent->client->kick_origin);
|
|
VectorClear (ent->client->kick_angles);
|
|
ent->client->ps.gunframe = 8;
|
|
|
|
if (level.time >= ent->pain_debounce_time)
|
|
{
|
|
gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
|
|
ent->pain_debounce_time = level.time + 1;
|
|
}
|
|
NoAmmoWeaponChange (ent);
|
|
return;
|
|
}
|
|
|
|
if (is_quad)
|
|
{
|
|
// damage *= damage_multiplier;
|
|
// kick *= damage_multiplier;
|
|
}
|
|
|
|
for(i=0;i<3;i++)
|
|
{
|
|
ent->client->kick_origin[i] = crandom() * 0.85;
|
|
ent->client->kick_angles[i] = crandom() * 0.85;
|
|
}
|
|
|
|
// get start / end positions
|
|
VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles);
|
|
// AngleVectors (angles, forward, right, NULL);
|
|
// gi.dprintf("v_angle: %s\n", vtos(ent->client->v_angle));
|
|
AngleVectors (ent->client->v_angle, forward, right, up);
|
|
|
|
// FIXME - set correct frames for different offsets.
|
|
|
|
if(ent->client->ps.gunframe == 6) // right barrel
|
|
{
|
|
// gi.dprintf("right\n");
|
|
VectorSet(offset, 15, 8, -8);
|
|
}
|
|
else // left barrel
|
|
{
|
|
// gi.dprintf("left\n");
|
|
VectorSet(offset, 15, 6, -8);
|
|
}
|
|
|
|
VectorCopy (ent->s.origin, tempPt);
|
|
tempPt[2] += ent->viewheight;
|
|
P_ProjectSource2 (ent->client, tempPt, offset, forward, right, up, start);
|
|
// gi.dprintf("start: %s\n", vtos(start));
|
|
fire_flechette (ent, start, forward, damage, wf_game.weapon_speed[WEAPON_ETF_RIFLE], kick);
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (ent-g_edicts);
|
|
gi.WriteByte (MZ_ETF_RIFLE);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
ent->client->ps.gunframe++;
|
|
ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
|
|
|
|
ent->client->anim_priority = ANIM_ATTACK;
|
|
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
|
|
{
|
|
ent->s.frame = FRAME_crattak1 - 1;
|
|
ent->client->anim_end = FRAME_crattak9;
|
|
}
|
|
else
|
|
{
|
|
ent->s.frame = FRAME_attack1 - 1;
|
|
ent->client->anim_end = FRAME_attack8;
|
|
}
|
|
|
|
}
|
|
|
|
void Weapon_ETF_Rifle (edict_t *ent)
|
|
{
|
|
static int pause_frames[] = {18, 28, 0};
|
|
static int fire_frames[] = {6, 7, 0};
|
|
// static int idle_seq;
|
|
|
|
// note - if you change the fire frame number, fix the offset in weapon_etf_rifle_fire.
|
|
|
|
// if (!(ent->client->buttons & BUTTON_ATTACK))
|
|
// ent->client->machinegun_shots = 0;
|
|
|
|
if (ent->client->weaponstate == WEAPON_FIRING)
|
|
{
|
|
if (ent->client->pers.inventory[ent->client->ammo_index] <= 0)
|
|
ent->client->ps.gunframe = 8;
|
|
}
|
|
|
|
Weapon_Generic (ent, 4, 7, 37, 41, pause_frames, fire_frames, weapon_etf_rifle_fire);
|
|
|
|
if(ent->client->ps.gunframe == 8 && (ent->client->buttons & BUTTON_ATTACK))
|
|
ent->client->ps.gunframe = 6;
|
|
|
|
// gi.dprintf("etf rifle %d\n", ent->client->ps.gunframe);
|
|
}
|
|
|
|
// pgm - this now uses ent->client->pers.weapon->quantity like all the other weapons
|
|
//#define HEATBEAM_AMMO_USE 2
|
|
#define HEATBEAM_DM_DMG 15
|
|
#define HEATBEAM_SP_DMG 15
|
|
|
|
void Heatbeam_Fire (edict_t *ent)
|
|
{
|
|
vec3_t start;
|
|
vec3_t forward, right, up;
|
|
vec3_t offset;
|
|
int damage;
|
|
int kick;
|
|
|
|
// for comparison, the hyperblaster is 15/20
|
|
// jim requested more damage, so try 15/15 --- PGM 07/23/98
|
|
if (deathmatch->value)
|
|
damage = HEATBEAM_DM_DMG;
|
|
else
|
|
damage = HEATBEAM_SP_DMG;
|
|
|
|
if (deathmatch->value) // really knock 'em around in deathmatch
|
|
kick = 75;
|
|
else
|
|
kick = 30;
|
|
|
|
// if(ent->client->pers.inventory[ent->client->ammo_index] < HEATBEAM_AMMO_USE)
|
|
// {
|
|
// NoAmmoWeaponChange (ent);
|
|
// return;
|
|
// }
|
|
|
|
ent->client->ps.gunframe++;
|
|
ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
|
|
|
|
/*
|
|
if (is_quad)
|
|
{
|
|
damage *= damage_multiplier;
|
|
kick *= damage_multiplier;
|
|
}
|
|
*/
|
|
|
|
VectorClear (ent->client->kick_origin);
|
|
VectorClear (ent->client->kick_angles);
|
|
|
|
// get start / end positions
|
|
AngleVectors (ent->client->v_angle, forward, right, up);
|
|
|
|
// This offset is the "view" offset for the beam start (used by trace)
|
|
|
|
VectorSet(offset, 7, 2, ent->viewheight-3);
|
|
P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
|
|
|
|
// This offset is the entity offset
|
|
VectorSet(offset, 2, 7, -3);
|
|
|
|
fire_heat (ent, start, forward, offset, damage, kick, false);
|
|
|
|
// send muzzle flash
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (ent-g_edicts);
|
|
gi.WriteByte (MZ_HEATBEAM | is_silenced);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
PlayerNoise(ent, start, PNOISE_WEAPON);
|
|
|
|
if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
|
|
ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
|
|
|
|
ent->client->anim_priority = ANIM_ATTACK;
|
|
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
|
|
{
|
|
ent->s.frame = FRAME_crattak1 - 1;
|
|
ent->client->anim_end = FRAME_crattak9;
|
|
}
|
|
else
|
|
{
|
|
ent->s.frame = FRAME_attack1 - 1;
|
|
ent->client->anim_end = FRAME_attack8;
|
|
}
|
|
|
|
}
|
|
|
|
void Weapon_Heatbeam (edict_t *ent)
|
|
{
|
|
// static int pause_frames[] = {38, 43, 51, 61, 0};
|
|
// static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};
|
|
static int pause_frames[] = {35, 0};
|
|
// static int fire_frames[] = {9, 0};
|
|
static int fire_frames[] = {9, 10, 11, 12, 0};
|
|
// static int attack_index;
|
|
// static int off_model, on_model;
|
|
|
|
// if ((g_showlogic) && (g_showlogic->value)) {
|
|
// gi.dprintf ("Frame %d, skin %d\n", ent->client->ps.gunframe, ent->client->ps.gunskin);
|
|
// }
|
|
|
|
// if (!attack_index)
|
|
// {
|
|
// attack_index = gi.soundindex ("weapons/bfg__l1a.wav");
|
|
// off_model = gi.modelindex ("models/weapons/v_beamer/tris.md2");
|
|
// on_model = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
|
|
//ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
|
|
// }
|
|
|
|
if (ent->client->weaponstate == WEAPON_FIRING)
|
|
{
|
|
// ent->client->weapon_sound = attack_index;
|
|
ent->client->weapon_sound = gi.soundindex ("weapons/bfg__l1a.wav");
|
|
if ((ent->client->pers.inventory[ent->client->ammo_index] >= 2) && ((ent->client->buttons) & BUTTON_ATTACK))
|
|
{
|
|
// if(ent->client->ps.gunframe >= 9 && ((ent->client->buttons) & BUTTON_ATTACK))
|
|
// if(ent->client->ps.gunframe >= 12 && ((ent->client->buttons) & BUTTON_ATTACK))
|
|
if(ent->client->ps.gunframe >= 13)
|
|
{
|
|
ent->client->ps.gunframe = 9;
|
|
// ent->client->ps.gunframe = 8;
|
|
// ent->client->ps.gunskin = 0;
|
|
// ent->client->ps.gunindex = on_model;
|
|
ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
|
|
}
|
|
else
|
|
{
|
|
// ent->client->ps.gunskin = 1;
|
|
// ent->client->ps.gunindex = on_model;
|
|
ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ent->client->ps.gunframe = 10;
|
|
ent->client->ps.gunframe = 13;
|
|
// ent->client->ps.gunskin = 1;
|
|
// ent->client->ps.gunindex = off_model;
|
|
ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ent->client->ps.gunskin = 1;
|
|
// ent->client->ps.gunindex = off_model;
|
|
ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2");
|
|
ent->client->weapon_sound = 0;
|
|
}
|
|
|
|
// Weapon_Generic (ent, 8, 9, 39, 44, pause_frames, fire_frames, Heatbeam_Fire);
|
|
Weapon_Generic (ent, 8, 12, 39, 44, pause_frames, fire_frames, Heatbeam_Fire);
|
|
}
|