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

468 lines
16 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_playstats.h"
#include "random.h"
#define ARROW_RADIUS 2.0F
#define ARROW_BACKUP (45.0F - ARROW_RADIUS)
extern void AlertMonsters (edict_t *self, edict_t *enemy, float lifetime, qboolean ignore_shadows);
void create_redarrow(edict_t *redarrow);
void RedRainRemove(edict_t *self)
{
gi.RemoveEffects(&self->s, 0);
G_SetToFree(self);
}
void RedRainThink(edict_t *self)
{
edict_t *victim=NULL;
vec3_t startpos, endpos, diffpos, min, max, hitpos, vec;
trace_t trace;
qboolean poweredup;
float lradius, rradius, rad_dmg;
int damage;
if(deathmatch->value)
rad_dmg = self->dmg * 0.25;
else
rad_dmg = self->dmg;
// find all the entities in the volume
while(victim = findinblocking(victim, self))
{
if(victim != self->owner && victim->takedamage &&
(victim->client || victim->svflags & SVF_MONSTER) && !(victim->svflags & SVF_DEADMONSTER))
{ // No damage to casting player
VectorSubtract(self->pos1, victim->s.origin, vec);
VectorNormalize(vec);
VectorMA(victim->s.origin, victim->maxs[0], vec, hitpos);
if (victim->svflags & SVF_BOSS)
{
T_Damage(victim, self, self->owner, vec3_origin, hitpos, vec3_origin,
rad_dmg/2, 0, DAMAGE_SPELL,MOD_STORM);
}
else
{
T_Damage(victim, self, self->owner, vec3_origin, hitpos, vec3_origin,
rad_dmg, 0, DAMAGE_SPELL,MOD_STORM);
}
}
}
if (self->delay <= level.time || (self->owner->red_rain_count - self->red_rain_count) > NUM_STORMS_PER_PLAYER)
{
self->owner->red_rain_count--;
self->s.effects |= EF_DISABLE_EXTRA_FX;
self->nextthink = level.time + 1.0;//lasts another 1.0 secs
self->think = RedRainRemove;
}
else
{
// Powerup value comes from the health in the edict.
poweredup = self->health;
// Check for lightning
if (self->delay - level.time < RED_RAIN_LIGHTNING_DURATION && irand(0, RED_RAIN_LIGHTNING_CHANCE) == 0)
{ // First check the area for a potential victim.
if (!poweredup)
{
rradius = RED_RAIN_RADIUS;
lradius = RED_RAIN_LIGHTNING_RADIUS;
}
else
{
rradius = POWER_RAIN_RADIUS;
lradius = POWER_RAIN_LIGHTNING_RADIUS;
}
// Find the bounds to search under.
VectorSet(min, -lradius, -lradius, self->mins[2]);
// Only search the lower half of the area for lightning hits.
VectorSet(max, lradius, lradius, self->mins[2] + ((self->maxs[2] - self->mins[2])*0.5));
VectorAdd(self->s.origin, min, min);
VectorAdd(self->s.origin, max, max);
victim=NULL;
while (victim = findinbounds(victim, min, max))
{
if (victim != self->owner && victim->takedamage &&
(victim->client || victim->svflags & SVF_MONSTER) && !(victim->svflags & SVF_DEADMONSTER))
break;
}
if (victim)
{ // Try to zap somebody with lightning
VectorSet(startpos,
flrand(-rradius*0.6, rradius*0.6),
flrand(-rradius*0.6, rradius*0.6),
self->maxs[2]);
VectorAdd(startpos, self->s.origin, startpos);
VectorSet(endpos,
flrand(victim->mins[0]*0.5, victim->maxs[0]*0.5),
flrand(victim->mins[1]*0.5, victim->maxs[1]*0.5),
flrand(victim->mins[2]*0.5, victim->maxs[2]*0.5));
VectorAdd(endpos, victim->s.origin, endpos);
gi.trace(startpos, vec3_origin, vec3_origin, endpos, self->owner, MASK_SOLID,&trace);
if (!trace.startsolid && trace.fraction == 1.0)
{ // FINALLY! A clear lightning strike!
VectorSubtract(endpos, startpos, diffpos);
VectorNormalize(diffpos);
if (!poweredup)
{
gi.CreateEffect(NULL, FX_LIGHTNING, CEF_FLAG6,
startpos, "vbb", endpos, (byte)RED_RAIN_LIGHTNING_WIDTH, (byte)0);
gi.sound(victim,CHAN_WEAPON,gi.soundindex("weapons/Lightning.wav"),1,ATTN_NORM,0);
// Do a nasty looking blast at the impact point
gi.CreateEffect(&victim->s, FX_LIGHTNING_HIT, CEF_OWNERS_ORIGIN | CEF_FLAG7, NULL, "t", diffpos);
if(!(EntReflecting(victim, true, true)))
{
T_Damage(victim, self, self->owner, diffpos, endpos, vec3_origin,
irand(RED_RAIN_DMG_LIGHTNING_MIN, RED_RAIN_DMG_LIGHTNING_MAX), 0, DAMAGE_SPELL,MOD_STORM);
}
}
else
{
gi.CreateEffect(NULL, FX_POWER_LIGHTNING, 0,
startpos, "vb", endpos, (byte)POWER_RAIN_LIGHTNING_WIDTH);
gi.sound(victim,CHAN_WEAPON,gi.soundindex("weapons/LightningPower.wav"),1,ATTN_NORM,0);
if(!(EntReflecting(victim, true, true)))
{
damage = irand(POWER_RAIN_DMG_LIGHTNING_MIN, POWER_RAIN_DMG_LIGHTNING_MAX);
T_DamageRadiusFromLoc(endpos, self, self->owner, self->owner, POWER_RAIN_DMG_LIGHTNING_RADIUS,
damage, damage*0.25, DAMAGE_SPELL,MOD_P_STORM);
}
}
}
}
else
{
VectorSet(startpos,
flrand(-rradius*0.75, rradius*0.75),
flrand(-rradius*0.75, rradius*0.75),
self->maxs[2]);
VectorAdd(startpos, self->s.origin, startpos);
VectorSet(endpos,
flrand(-rradius, rradius),
flrand(-rradius, rradius),
self->mins[2]);
VectorAdd(endpos, self->s.origin, endpos);
if (!poweredup)
{
gi.CreateEffect(NULL, FX_LIGHTNING, CEF_FLAG6,
startpos, "vbb", endpos, (byte)RED_RAIN_LIGHTNING_WIDTH, (byte)0);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/Lightning.wav"), 2, ATTN_NORM,0);
}
else
{
gi.CreateEffect(NULL, FX_POWER_LIGHTNING, 0,
startpos, "vb", endpos, (byte)POWER_RAIN_LIGHTNING_WIDTH);
gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/LightningPower.wav"), 2, ATTN_NORM,0);
// The lightning does radius damage even if no target.
damage = irand(POWER_RAIN_DMG_LIGHTNING_MIN, POWER_RAIN_DMG_LIGHTNING_MAX);
T_DamageRadiusFromLoc(endpos, self, self->owner, self->owner, POWER_RAIN_DMG_LIGHTNING_RADIUS,
damage, damage*0.25, DAMAGE_SPELL,MOD_P_STORM);
}
}
}
self->nextthink = level.time + self->wait;
}
}
// ****************************************************************************
// RedRainMissile reflect
// ****************************************************************************
edict_t *RedRainMissileReflect(edict_t *self, edict_t *other, vec3_t vel)
{
edict_t *redarrow;
// create a new missile to replace the old one - this is necessary cos physics will do nasty shit
// with the existing one,since we hit something. Hence, we create a new one totally.
redarrow = G_Spawn();
VectorCopy(self->s.origin, redarrow->s.origin);
redarrow->health = self->health;
redarrow->owner = other;
redarrow->enemy = self->owner;
redarrow->owner->red_rain_count++;
self->owner->red_rain_count--;
create_redarrow(redarrow);
VectorCopy(vel, redarrow->velocity);
redarrow->reflect_debounce_time = self->reflect_debounce_time -1;
G_LinkMissile(redarrow);
gi.CreateEffect(&redarrow->s, FX_WEAPON_REDRAINMISSILE,
CEF_OWNERS_ORIGIN|(redarrow->health<<5)|CEF_FLAG8, NULL, "t", redarrow->velocity);
// kill the existing missile, since its a pain in the ass to modify it so the physics won't screw it.
G_SetToFree(self);
// Do a nasty looking blast at the impact point
gi.CreateEffect(&redarrow->s, FX_LIGHTNING_HIT, CEF_OWNERS_ORIGIN, NULL, "t", redarrow->velocity);
return(redarrow);
}
// ****************************************************************************
// RedRainMissile touch
// ****************************************************************************
void RedRainMissileTouch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surface)
{
vec3_t org, end;
edict_t *damagearea;
trace_t trace;
float radius;
// has the target got reflection turned on ?
if (self->reflect_debounce_time)
{
if(EntReflecting(other, true, true))
{
Create_rand_relect_vect(self->velocity, self->velocity);
Vec3ScaleAssign(RED_ARROW_SPEED/2, self->velocity);
RedRainMissileReflect(self, other, self->velocity);
return;
}
}
if(other->takedamage)
{ // Damage from direct impact of arrow, normal or powered up.
T_Damage(other, self, self->owner, self->movedir, self->s.origin, plane->normal, self->dmg, self->dmg, DAMAGE_SPELL,MOD_STORM);
}
AlertMonsters (self, self->owner, 5, false);
// Backup effect a little so it doesn`t appear in the wall (but only if we hit the wall)
if(other->svflags & SVF_MONSTER)
{
VectorCopy(self->s.origin, org);
}
else
{
VectorNormalize2(self->velocity, org);
Vec3ScaleAssign(-ARROW_BACKUP, org);
Vec3AddAssign(self->s.origin, org);
}
VectorClear(self->velocity);
// Create a damage handling effect
damagearea = G_Spawn();
VectorCopy(org, damagearea->s.origin);
damagearea->think = RedRainThink;
damagearea->nextthink = level.time + RED_RAIN_DAMAGE_INTERVAL;
damagearea->solid = SOLID_NOT;
damagearea->clipmask = CONTENTS_EMPTY;
damagearea->movetype = MOVETYPE_FLYMISSILE; // Necessary for proper processing of thinkers
damagearea->wait = RED_RAIN_DAMAGE_INTERVAL;
if(deathmatch->value)
damagearea->delay = level.time + RED_RAIN_DURATION - 2;//5 secs in DM
else
damagearea->delay = level.time + RED_RAIN_DURATION;
damagearea->owner = self->owner;
damagearea->red_rain_count = self->owner->red_rain_count;
damagearea->classname = "Spell_RedRain";
damagearea->health = self->health; // Copy over the powerup status.
damagearea->s.effects |= EF_ALWAYS_ADD_EFFECTS;
if (self->health == 0)
{ // Not powered up
damagearea->dmg = RED_RAIN_DAMAGE;
radius = RED_RAIN_RADIUS;
}
else
{ // Powered up rain
damagearea->dmg = POWER_RAIN_DAMAGE;
radius = POWER_RAIN_RADIUS;
}
// Find the top of the damage area. Check down in an area less than the max size.
VectorSet(damagearea->mins, -radius*0.5, -radius*0.5, -1.0F);
VectorSet(damagearea->maxs, radius*0.5, radius*0.5, 1.0F);
VectorCopy(org, end);
end[2] += MAX_REDRAINHEIGHT;
gi.trace(org, damagearea->mins, damagearea->maxs, end, damagearea, MASK_SOLID,&trace);
// if(trace.startsolid) // Ignore startsolids.
// damagearea->maxs[2] = 1.0;
// else
if (trace.fraction == 1.0F)
damagearea->maxs[2] = MAX_REDRAINHEIGHT; // Put the bounds up all the way
else
damagearea->maxs[2] = trace.endpos[2] - org[2]; // Set the bounds up only part way
// Find the bottom of the damage area.
end[2] = org[2] - MAX_REDRAINFALLDIST;
gi.trace(org, damagearea->mins, damagearea->maxs, end, damagearea, MASK_SOLID,&trace);
// if(trace.startsolid) // Startsolids mean that the area is too close to a wall
// damagearea->mins[2] = -1.0;
// else
if (trace.fraction == 1.0F)
damagearea->mins[2] = -MAX_REDRAINFALLDIST; // Put the bounds down all the way
else
damagearea->mins[2] = trace.endpos[2] - org[2]; // Set the bounds down where the trace stopped
// Put the bounds of the damage area out to the max position now.
damagearea->mins[0] = damagearea->mins[1] = -radius;
damagearea->maxs[0] = damagearea->maxs[1] = radius;
VectorSet(damagearea->pos1, damagearea->s.origin[0], damagearea->s.origin[1], damagearea->maxs[2]);
gi.linkentity(damagearea);
// Start the red rain
// Send along the health as a flag, to indicate if powered up.
gi.CreateEffect(&damagearea->s, FX_WEAPON_REDRAIN, CEF_BROADCAST|(self->health<<5), org, "");
// gi.sound(damagearea, CHAN_VOICE, gi.soundindex("weapons/RedRainFall.wav"), 2, ATTN_NORM,0);
damagearea->s.sound = gi.soundindex("weapons/RedRainFall.wav");
damagearea->s.sound_data = (255 & ENT_VOL_MASK) | ATTN_NORM;
// Turn off the client effect
gi.RemoveEffects(&self->s, FX_WEAPON_REDRAINMISSILE);
G_SetToFree(self);
}
// ****************************************************************************
// RedRainMissile think
// ****************************************************************************
void RedRainMissileThink(edict_t *self)
{
self->svflags |= SVF_NOCLIENT;
self->think = NULL;
}
// create the guts of the red rain missile
void create_redarrow(edict_t *redarrow)
{
redarrow->s.effects |= EF_ALWAYS_ADD_EFFECTS;
redarrow->svflags |= SVF_ALWAYS_SEND;
redarrow->movetype = MOVETYPE_FLYMISSILE;
VectorSet(redarrow->mins, -ARROW_RADIUS, -ARROW_RADIUS, -ARROW_RADIUS);
VectorSet(redarrow->maxs, ARROW_RADIUS, ARROW_RADIUS, ARROW_RADIUS);
redarrow->solid = SOLID_BBOX;
redarrow->clipmask = MASK_SHOT;
redarrow->touch = RedRainMissileTouch;
redarrow->think = RedRainMissileThink;
redarrow->classname = "Spell_RedRainArrow";
redarrow->nextthink = level.time + 0.1;
if (redarrow->health==1)
{ // powerup arrow
redarrow->dmg = irand(POWER_RAIN_DMG_ARROW_MIN, POWER_RAIN_DMG_ARROW_MAX);
}
else
{
redarrow->dmg = irand(RED_RAIN_DMG_ARROW_MIN, RED_RAIN_DMG_ARROW_MAX);
}
}
// ****************************************************************************
// SpellCastRedRain
// ****************************************************************************
void SpellCastRedRain(edict_t *Caster, vec3_t StartPos, vec3_t AimAngles, vec3_t unused, float value)
{
edict_t *redarrow;
trace_t trace;
vec3_t dir, forward, endpos;
qboolean powerup;
redarrow = G_Spawn();
Caster->red_rain_count++;
// health indicates a level of powerup
if (Caster->client->playerinfo.powerup_timer > level.time)
{ // Shoot powered up red rain.
redarrow->health = 1;
powerup=true;
}
else
{ // Normal red rain arrow
redarrow->health = 0;
powerup=false;
}
VectorCopy(StartPos, redarrow->s.origin);
//Check ahead first to see if it's going to hit anything at this angle
AngleVectors(AimAngles, forward, NULL, NULL);
VectorMA(StartPos, RED_ARROW_SPEED, forward, endpos);
gi.trace(StartPos, vec3_origin, vec3_origin, endpos, Caster, MASK_MONSTERSOLID,&trace);
if(trace.ent && ok_to_autotarget(Caster, trace.ent))
{//already going to hit a valid target at this angle- so don't autotarget
VectorScale(forward, RED_ARROW_SPEED, redarrow->velocity);
}
else
{//autotarget current enemy
GetAimVelocity(Caster->enemy, redarrow->s.origin, RED_ARROW_SPEED, AimAngles, redarrow->velocity);
}
VectorNormalize2(redarrow->velocity, dir);
// naughty naughty - this requires a normalised vector
AnglesFromDir(dir, redarrow->s.angles);
create_redarrow(redarrow);
redarrow->reflect_debounce_time = MAX_REFLECT;
redarrow->owner = Caster;
G_LinkMissile(redarrow);
gi.RemoveEffects(&Caster->s, FX_WEAPON_REDRAINGLOW);
if (powerup)
{ // Play powerup firing sound
gi.sound(Caster, CHAN_WEAPON, gi.soundindex("weapons/RedRainPowerFire.wav"), 1, ATTN_NORM, 0);
}
else
{ // Player normal red rain firing sound
gi.sound(Caster, CHAN_WEAPON, gi.soundindex("weapons/RedRainFire.wav"), 1, ATTN_NORM, 0);
}
// Trace from the player's origin because then if we hit a wall, the effect won't be inside it...
gi.trace(Caster->s.origin, redarrow->mins, redarrow->maxs, redarrow->s.origin, Caster, MASK_PLAYERSOLID,&trace);
if (trace.startsolid || trace.fraction < .99)
{
if (trace.startsolid)
VectorCopy(Caster->s.origin, redarrow->s.origin);
else
VectorCopy(trace.endpos, redarrow->s.origin);
RedRainMissileTouch(redarrow, trace.ent, &trace.plane, trace.surface);
return;
}
// Create the missile and trail effect only if we successfully launch the missile
if (powerup)
{ // Magenta trail
gi.CreateEffect(&redarrow->s, FX_WEAPON_REDRAINMISSILE, CEF_OWNERS_ORIGIN|CEF_FLAG6,
NULL, "t", redarrow->velocity);
}
else
{ // Red trail
gi.CreateEffect(&redarrow->s, FX_WEAPON_REDRAINMISSILE, CEF_OWNERS_ORIGIN,
NULL, "t", redarrow->velocity);
}
}
// end