heretic2-sdk/Toolkit/Programming/GameCode/game/spl_wall.c
1998-11-24 00:00:00 +00:00

534 lines
17 KiB
C

//
// Heretic II
// Copyright 1998 Raven Software
//
#include "g_local.h"
#include "fx.h"
#include "vector.h"
#include "angles.h"
#include "matrix.h"
#include "g_volume_effect.h"
#include "Utilities.h"
#include "g_ClassStatics.h"
#include "g_Physics.h"
#include "g_volume_effect.h"
#include "g_playstats.h"
#define FIREWALL_DOT_MIN 0.25
// ****************************************************************************
// FireBlast
// Unpowered
// ****************************************************************************
void FireBlastBlocked(edict_t *self, trace_t *trace);
void FireBlastStartThink(edict_t *self);
// ****************************************************************************
// SpellCastBlast
// ****************************************************************************
edict_t *CreateFireBlast(vec3_t startpos, vec3_t angles, edict_t *owner, int health, float timestamp)
{
edict_t *wall;
wall = G_Spawn();
VectorSet(wall->mins, -FIREBLAST_PROJ_RADIUS, -FIREBLAST_PROJ_RADIUS, -FIREBLAST_PROJ_RADIUS);
VectorSet(wall->maxs, FIREBLAST_PROJ_RADIUS, FIREBLAST_PROJ_RADIUS, FIREBLAST_PROJ_RADIUS);
VectorCopy(startpos, wall->s.origin);
VectorCopy(angles, wall->s.angles);
AngleVectors(angles, wall->movedir, NULL, NULL);
VectorScale(wall->movedir, FIREBLAST_SPEED, wall->velocity);
wall->mass = 250;
wall->elasticity = ELASTICITY_NONE;
wall->friction = 0;
wall->gravity = 0;
wall->s.effects |= EF_ALWAYS_ADD_EFFECTS;
wall->svflags |= SVF_ALWAYS_SEND|SVF_DO_NO_IMPACT_DMG;
wall->movetype = PHYSICSTYPE_FLY;
wall->isBlocked = FireBlastBlocked;
wall->classname = "Spell_FireBlast";
wall->solid = SOLID_BBOX;
wall->clipmask = MASK_DRIP;
wall->owner = owner;
wall->think = FireBlastStartThink;
wall->nextthink = level.time + 0.1;
wall->dmg_radius = FIREBLAST_RADIUS;
wall->dmg = FIREBLAST_DAMAGE;
wall->health = health; // Can bounce 3 times
wall->fire_timestamp = timestamp; // This marks the wall with a more-or-less unique value so the wall
// doesn't damage twice.
gi.linkentity(wall);
gi.CreateEffect(&wall->s, FX_WEAPON_FIREBURST, CEF_OWNERS_ORIGIN, NULL, "ff", angles[YAW], angles[PITCH]);
return wall;
}
// This called when missile touches anything (world or edict)
void FireBlastBlocked(edict_t *self, trace_t *trace)
{
edict_t *newwall;
float dot, speed, factor;
vec3_t surfvect, surfvel, testpos, newang;
trace_t newtrace;
assert(trace);
// If we haven't damaged what we are hitting yet, damage it now. Mainly for the Trial Beast.
if (trace->ent && trace->ent->takedamage && self->fire_timestamp > trace->ent->fire_timestamp)
{
// if we have reflection on, then no damage
if(!EntReflecting(trace->ent, true, true))
{
if(trace->ent != self->owner) // No damage to casting player
{
T_Damage(trace->ent, self, self->owner, self->movedir, self->s.origin, vec3_origin,
self->dmg, self->dmg, DAMAGE_FIRE | DAMAGE_FIRE_LINGER, MOD_FIREWALL);
gi.CreateEffect(&(trace->ent->s), FX_FLAREUP, CEF_OWNERS_ORIGIN, NULL, "");
gi.sound(self,CHAN_WEAPON,gi.soundindex("weapons/FirewallDamage.wav"),1,ATTN_NORM,0);
trace->ent->fire_timestamp = self->fire_timestamp; // Mark so the fire doesn't damage an ent twice.
}
}
}
if (self->health > 0 && !(trace->contents & CONTENTS_WATER) &&
(trace->plane.normal[2] > FIREWALL_DOT_MIN || trace->plane.normal[2] < -FIREWALL_DOT_MIN))
dot = DotProduct(self->movedir, trace->plane.normal);
speed = VectorLength(self->velocity);
if (dot < 0 && dot > -0.67) // slide on all but the most extreme angles.
{
VectorMA(self->movedir, -dot, trace->plane.normal, surfvel); // Vel then holds the velocity negated by the impact.
factor = VectorNormalize2(surfvel, surfvect); // Yes, there is the tiniest chance this could be a zero vect,
if (factor > 0)
{
VectorMA(self->s.origin, 16.0, surfvect, testpos); // test distance
gi.trace(self->s.origin, self->mins, self->maxs, testpos, self, MASK_SHOT, &newtrace);
if (newtrace.fraction > 0.99)
{ // If this is successful, then we can make another fireblast moving in the new direction.
vectoangles(surfvect, newang);
newwall = CreateFireBlast(self->s.origin, newang, self->owner, self->health-1, level.time);
}
}
}
// Well, whatever happened, free the current blast.
VectorSet(self->velocity, 0.0, 0.0, 0.0);
self->s.effects |= EF_ALTCLIENTFX; // Indicate to the wall that it's time to die
G_SetToFree(self);
}
// ****************************************************************************
// FireBlast think
// ****************************************************************************
void FireBlastThink(edict_t *self)
{ // Check in the area and try to damage anything in the immediate area
edict_t *ent=NULL;
vec3_t min, max;
// Set up the checking volume
VectorSet(min, -self->dmg_radius, -self->dmg_radius, -FIREBLAST_VRADIUS);
VectorAdd(self->s.origin, min, min);
VectorSet(max, self->dmg_radius, self->dmg_radius, FIREBLAST_VRADIUS);
VectorAdd(self->s.origin, max, max);
// find all the entities in the volume
while(ent = findinbounds(ent, min, max))
{
// if ((!(ent->svflags & SVF_MONSTER) && !(ent->client && deathmatch->value)) || (ent->svflags & SVF_DEADMONSTER))
if(!ent->takedamage)
{ // Anything that takes damage now.
continue;
}
if (self->fire_timestamp <= ent->fire_timestamp)
continue;
// if we have reflection on, then no damage
if(EntReflecting(ent, true, true))
continue;
if(ent != self->owner) // No damage to casting player
{
T_Damage(ent, self, self->owner, self->movedir, self->s.origin, vec3_origin,
self->dmg, self->dmg, DAMAGE_FIRE | DAMAGE_FIRE_LINGER, MOD_FIREWALL);
gi.CreateEffect(&ent->s, FX_FLAREUP, CEF_OWNERS_ORIGIN, NULL, "");
gi.sound(self,CHAN_WEAPON,gi.soundindex("weapons/FirewallDamage.wav"),1,ATTN_NORM,0);
ent->fire_timestamp = self->fire_timestamp; // Mark so the fire doesn't damage an ent twice.
}
}
self->nextthink = level.time + 0.1;
self->dmg_radius += FIREBLAST_DRADIUS*0.1;
self->dmg -= 3;
if (self->dmg < FIREBLAST_DAMAGE_MIN)
self->dmg = FIREBLAST_DAMAGE_MIN;
}
void FireBlastStartThink(edict_t *self)
{
self->svflags |= SVF_NOCLIENT; // Allow transmission to client
FireBlastThink(self);
self->think = FireBlastThink;
self->nextthink = level.time + 0.1;
}
void CastFireBlast(edict_t *caster, vec3_t startpos, vec3_t aimangles)
{
edict_t *wall;
vec3_t newpos, fwd;
trace_t trace;
wall = G_Spawn();
AngleVectors(aimangles, fwd, NULL, NULL);
VectorMA(startpos, FIREBLAST_RADIUS*0.5, fwd, newpos);
wall = CreateFireBlast(newpos, aimangles, caster, 3, level.time); // Bounce 3 times
// Check to see if this is a legit spawn.
gi.trace(caster->s.origin, wall->mins, wall->maxs, wall->s.origin, caster, MASK_SOLID, &trace);
if (trace.startsolid || trace.fraction < .99)
{
if (trace.startsolid)
VectorCopy(caster->s.origin, wall->s.origin);
else
VectorCopy(trace.endpos, wall->s.origin);
FireBlastBlocked(wall, &trace);
return;
}
FireBlastThink(wall);
}
// ****************************************************************************
// FireWall
// Powered up
// ****************************************************************************
void WallMissileBlocked(edict_t *self, trace_t *trace);
void WallMissileStartThink(edict_t *self);
edict_t *CreateFireWall(vec3_t startpos, vec3_t angles, edict_t *owner, int health, float timestamp, float sidespeed)
{
edict_t *wall;
vec3_t right;
int flags=0;
wall = G_Spawn();
VectorSet(wall->mins, -FIREWAVE_PROJ_RADIUS, -FIREWAVE_PROJ_RADIUS, -FIREWAVE_PROJ_RADIUS);
VectorSet(wall->maxs, FIREWAVE_PROJ_RADIUS, FIREWAVE_PROJ_RADIUS, FIREWAVE_PROJ_RADIUS);
VectorCopy(startpos, wall->s.origin);
VectorCopy(angles, wall->s.angles);
AngleVectors(angles, wall->movedir, right, NULL);
if (deathmatch->value)
{
flags |= CEF_FLAG8;
VectorScale(wall->movedir, FIREWAVE_DM_SPEED, wall->velocity); // Goes faster in deathmatch
}
else
{
VectorScale(wall->movedir, FIREWAVE_SPEED, wall->velocity);
}
VectorMA(wall->velocity, sidespeed, right, wall->velocity);
if (sidespeed < 0)
flags |= CEF_FLAG6;
else if (sidespeed > 0)
flags |= CEF_FLAG7;
wall->mass = 250;
wall->elasticity = ELASTICITY_NONE;
wall->friction = 0;
wall->gravity = 0;
wall->s.effects |= EF_ALWAYS_ADD_EFFECTS;
wall->svflags |= SVF_ALWAYS_SEND;
wall->movetype = PHYSICSTYPE_FLY;
wall->isBlocked = WallMissileBlocked;
wall->classname = "Spell_FireWall";
wall->solid = SOLID_BBOX;
wall->clipmask = MASK_DRIP;
wall->owner = owner;
wall->think = WallMissileStartThink;
wall->nextthink = level.time + 0.1;
wall->dmg = FIREWAVE_DAMAGE;
wall->dmg_radius = FIREWAVE_RADIUS;
wall->health = health; // Can bounce 3 times
wall->fire_timestamp = timestamp; // Mark the wall so it can't damage something twice.
gi.linkentity(wall);
gi.CreateEffect(&wall->s, FX_WEAPON_FIREWAVE, CEF_OWNERS_ORIGIN | flags, startpos, "ff", angles[YAW], angles[PITCH]);
return wall;
}
// ****************************************************************************
// WallMissile touch
// ****************************************************************************
void WallMissileWormThink(edict_t *self)
{
T_DamageRadius(self, self->owner, self->owner, 64.0,
FIREWAVE_WORM_DAMAGE, FIREWAVE_WORM_DAMAGE, DAMAGE_FIRE, MOD_FIREWALL);
G_SetToFree(self);
}
#define FIREWORM_LIFETIME 1.0
// This called when missile touches anything (world or edict)
void WallMissileBlocked(edict_t *self, trace_t *trace)
{
edict_t *newwall;
float dot, speed, factor;
vec3_t surfvect, surfvel, testpos, newang;
trace_t newtrace;
edict_t *worm;
assert(trace);
// If we haven't damaged what we are hitting yet, damage it now. Mainly for the Trial Beast.
if (trace->ent && trace->ent->takedamage && self->fire_timestamp > trace->ent->fire_timestamp)
{
// if we have reflection on, then no damage
if(!EntReflecting(trace->ent, true, true))
{
if(trace->ent != self->owner) // No damage to casting player
{
T_Damage(trace->ent, self, self->owner, self->movedir, self->s.origin, vec3_origin,
self->dmg, self->dmg, DAMAGE_FIRE | DAMAGE_FIRE_LINGER, MOD_FIREWALL);
gi.CreateEffect(&(trace->ent->s), FX_FLAREUP, CEF_OWNERS_ORIGIN, NULL, "");
trace->ent->fire_timestamp = self->fire_timestamp;
gi.CreateEffect(NULL, FX_WEAPON_FIREWAVEWORM, 0, trace->ent->s.origin, "t", self->movedir);
worm = G_Spawn();
VectorCopy(trace->ent->s.origin, worm->s.origin);
worm->think = WallMissileWormThink;
worm->nextthink = level.time + FIREWORM_LIFETIME;
worm->solid = SOLID_NOT;
worm->clipmask = MASK_DRIP;
worm->owner = self->owner;
gi.linkentity(worm);
gi.sound(self,CHAN_WEAPON,gi.soundindex("weapons/FirewallDamage.wav"),1,ATTN_NORM,0);
}
}
}
if (self->health > 0 && !(trace->contents & CONTENTS_WATER) &&
(trace->plane.normal[2] > FIREWALL_DOT_MIN || trace->plane.normal[2] < -FIREWALL_DOT_MIN))
dot = DotProduct(self->movedir, trace->plane.normal);
speed = VectorLength(self->velocity);
if (dot < 0 && dot > -0.67) // slide on all but the most extreme angles.
{
VectorMA(self->movedir, -dot, trace->plane.normal, surfvel); // Vel then holds the velocity negated by the impact.
factor = VectorNormalize2(surfvel, surfvect); // Yes, there is the tiniest chance this could be a zero vect,
if (factor > 0)
{
VectorMA(self->s.origin, 16.0, surfvect, testpos); // test distance
gi.trace(self->s.origin, self->mins, self->maxs, testpos, self, MASK_SOLID, &newtrace);
if (newtrace.fraction > 0.99)
{ // If this is successful, then we can make another fireblast moving in the new direction.
vectoangles(surfvect, newang);
newwall = CreateFireWall(self->s.origin, newang, self->owner, self->health-1, level.time, 0);
}
}
}
// Well, whatever happened, free the current blast.
VectorSet(self->velocity, 0.0, 0.0, 0.0);
self->s.effects |= EF_ALTCLIENTFX; // Indicate to the wall that it's time to die
G_SetToFree(self);
}
// ****************************************************************************
// WallMissile think
// ****************************************************************************
void WallMissileThink(edict_t *self)
{ // Check in the area and try to damage anything in the immediate area
edict_t *ent=NULL, *worm;
vec3_t min, max;
// Set up the checking volume
VectorSet(min, -self->dmg_radius, -self->dmg_radius, -FIREWAVE_DOWN);
VectorAdd(self->s.origin, min, min);
VectorSet(max, self->dmg_radius, self->dmg_radius, FIREWAVE_UP);
VectorAdd(self->s.origin, max, max);
// find all the entities in the volume
while(ent = findinbounds(ent, min, max))
{
// if ((!(ent->svflags & SVF_MONSTER) && !(ent->client && deathmatch->value)) || (ent->svflags & SVF_DEADMONSTER))
if(!ent->takedamage)
{ // Anything that takes damage now.
continue;
}
if (ent->fire_timestamp >= self->fire_timestamp)
continue;
// if we have reflection on, then no damage
if(EntReflecting(ent, true, true))
continue;
if(ent != self->owner) // No damage to casting player
{
T_Damage(ent, self, self->owner, self->movedir, self->s.origin, vec3_origin,
self->dmg, self->dmg, DAMAGE_FIRE | DAMAGE_FIRE_LINGER, MOD_FIREWALL);
gi.CreateEffect(&ent->s, FX_FLAREUP, CEF_OWNERS_ORIGIN, NULL, "");
ent->fire_timestamp = self->fire_timestamp;
gi.CreateEffect(NULL, FX_WEAPON_FIREWAVEWORM, 0, ent->s.origin, "t", self->movedir);
worm = G_Spawn();
VectorCopy(ent->s.origin, worm->s.origin);
worm->think = WallMissileWormThink;
worm->nextthink = level.time + FIREWORM_LIFETIME;
worm->solid = SOLID_NOT;
worm->clipmask = MASK_DRIP;
worm->owner = self->owner;
gi.linkentity(worm);
gi.sound(self,CHAN_WEAPON,gi.soundindex("weapons/FirewallDamage.wav"),1,ATTN_NORM,0);
}
}
self->nextthink = level.time + 0.1;
self->dmg_radius += .1*FIREWAVE_DRADIUS;
self->dmg -= 3;
if (self->dmg < FIREWAVE_DAMAGE_MIN)
self->dmg = FIREWAVE_DAMAGE_MIN;
}
void WallMissileStartThink(edict_t *self)
{
self->svflags |= SVF_NOCLIENT; // Allow transmission to client
WallMissileThink(self);
self->think = WallMissileThink;
self->nextthink = level.time + 0.1;
}
void CastFireWall(edict_t *caster, vec3_t startpos, vec3_t aimangles)
{ // Big wall is powered up
edict_t *wall;
vec3_t fwd, right, spawnpos;
trace_t trace;
AngleVectors(aimangles, fwd, right, NULL);
// Spawn wall to left
VectorMA(startpos, -FIREWAVE_RADIUS, right, spawnpos);
wall = CreateFireWall(spawnpos, aimangles, caster, 3, level.time, -FIREWAVE_DRADIUS);
// Check to see if this is a legit spawn.
gi.trace(caster->s.origin, wall->mins, wall->maxs, wall->s.origin, caster, MASK_SOLID, &trace);
if (trace.startsolid || trace.fraction < .99)
{
if (trace.startsolid)
VectorCopy(caster->s.origin, wall->s.origin);
else
VectorCopy(trace.endpos, wall->s.origin);
WallMissileBlocked(wall, &trace);
goto rightwall;
}
WallMissileThink(wall);
rightwall:
// Spawn wall to right
VectorMA(startpos, FIREWAVE_RADIUS, right, spawnpos);
wall = CreateFireWall(spawnpos, aimangles, caster, 3, level.time, FIREWAVE_DRADIUS);
// Check to see if this is a legit spawn.
gi.trace(caster->s.origin, wall->mins, wall->maxs, wall->s.origin, caster, MASK_SOLID, &trace);
if (trace.startsolid || trace.fraction < .99)
{
if (trace.startsolid)
VectorCopy(caster->s.origin, wall->s.origin);
else
VectorCopy(trace.endpos, wall->s.origin);
WallMissileBlocked(wall, &trace);
return;
}
WallMissileThink(wall);
}
// ****************************************************************************
// The Firewall spell is cast.
// ****************************************************************************
void SpellCastWall(edict_t *caster, vec3_t startpos, vec3_t aimangles, vec3_t unused, float value)
{
vec3_t castpos;
VectorCopy(startpos, castpos);
if (caster->client->playerinfo.powerup_timer <= level.time)
{ // Not powered up.
castpos[2] += 16; // Aim higher than powered up version.
CastFireBlast(caster, castpos, aimangles);
gi.sound(caster, CHAN_WEAPON, gi.soundindex("weapons/FirewallCast.wav"), 1, ATTN_NORM, 0);
}
else
{ // Powered up, cast big wall o' doom.
CastFireWall(caster, castpos, aimangles);
gi.sound(caster, CHAN_WEAPON, gi.soundindex("weapons/FirewallPowerCast.wav"), 1, ATTN_NORM, 0);
}
}
// end