heretic2-sdk/Toolkit/Programming/GameCode/game/spl_flyingfist.c
1999-03-18 00:00:00 +00:00

305 lines
9.3 KiB
C

//
// spl_flyingfist.c
//
// Heretic II
// Copyright 1998 Raven Software
//
#include "g_local.h"
#include "fx.h"
#include "vector.h"
#include "random.h"
#include "decals.h"
#include "Utilities.h"
#include "g_playstats.h"
#define FIST_RADIUS 2.0
extern void AlertMonsters (edict_t *self, edict_t *enemy, float lifetime, qboolean ignore_shadows);
static void FlyingFistThink(edict_t *self);
static void FlyingFistTouch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surface);
// ************************************************************************************************
// FlyingFistThink
// ************************************************************************************************
static void FlyingFistFizzleThink(edict_t *self)
{
// Don't fizzle in deathmatch, or if powered up
if (!(deathmatch->value) && self->health==0)
{
if ( (self->dmg - 2) > FIREBALL_MIN_FIZZLE_DAMAGE )
{
self->dmg -= 2;
}
self->nextthink = level.time + 0.1;
}
}
// ****************************************
// Creation functions
// ****************************************
void FlyingFistInitThink(edict_t *self)
{
self->svflags |= SVF_NOCLIENT;
self->think = FlyingFistFizzleThink;
FlyingFistFizzleThink(self);
}
void CreateFlyingFist(edict_t *flyingfist)
{
flyingfist->s.effects |= EF_ALWAYS_ADD_EFFECTS;
flyingfist->svflags |= SVF_ALWAYS_SEND;
flyingfist->movetype = MOVETYPE_FLYMISSILE;
flyingfist->s.scale = 1.0;
flyingfist->touch = FlyingFistTouch;
flyingfist->think = FlyingFistInitThink;
flyingfist->classname = "Spell_FlyingFist";
flyingfist->nextthink = level.time + 0.1;
VectorSet(flyingfist->mins, -FIST_RADIUS, -FIST_RADIUS, -FIST_RADIUS);
VectorSet(flyingfist->maxs, FIST_RADIUS, FIST_RADIUS, FIST_RADIUS);
flyingfist->solid = SOLID_BBOX;
flyingfist->clipmask = MASK_SHOT;
}
edict_t *FlyingFistReflect(edict_t *self, edict_t *other, vec3_t vel)
{
edict_t *flyingfist;
// create a new missile to replace the old one - this is necessary cos physics will do nasty things
// with the existing one,since we hit something. Hence, we create a new one totally.
flyingfist = G_Spawn();
// copy everything across
VectorCopy(self->s.origin, flyingfist->s.origin);
CreateFlyingFist(flyingfist);
VectorCopy(vel, flyingfist->velocity);
VectorNormalize2(vel, flyingfist->movedir);
AnglesFromDir(flyingfist->movedir, flyingfist->s.angles);
flyingfist->owner = other;
flyingfist->health = self->health;
flyingfist->enemy = self->owner;
flyingfist->flags |= (self->flags & FL_NO_KNOCKBACK);
flyingfist->reflect_debounce_time = self->reflect_debounce_time -1; //so it doesn't infinitely reflect in one frame somehow
flyingfist->reflected_time=self->reflected_time;
G_LinkMissile(flyingfist);
// create new trails for the new missile
gi.CreateEffect(&flyingfist->s, FX_WEAPON_FLYINGFIST, CEF_OWNERS_ORIGIN | CEF_FLAG6, NULL,
"t", flyingfist->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(&flyingfist->s, FX_LIGHTNING_HIT, CEF_OWNERS_ORIGIN, NULL, "t", flyingfist->velocity);
return(flyingfist);
}
// ************************************************************************************************
// FlyingFistTouch
// ************************************************************************************************
static void FlyingFistTouch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surface)
{
int damage;
vec3_t planedir;
qboolean powerup, wimpy;
int flags;
if(other == self->owner)
{
return;
}
if(surface && (surface->flags & SURF_SKY))
{
SkyFly(self);
return;
}
if (self->health)
{ // Powered up meteor
powerup=true;
flags = CEF_FLAG7;
}
else
{ // Unpowered fireball
powerup=false;
flags = 0;
}
if (self->flags & FL_NO_KNOCKBACK)
{ // Wimpy out-of-ammo weapon.
wimpy=true;
flags |= CEF_FLAG8;
}
else
{
wimpy=false;
}
// 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);
// scale speed down
Vec3ScaleAssign(FLYING_FIST_SPEED/2, self->velocity);
FlyingFistReflect(self, other, self->velocity);
return;
}
}
AlertMonsters (self, self->owner, 1, false);
if(other->takedamage)
{
if(powerup) // Check for powered up meteor
{
if(deathmatch->value)
damage = irand(FIREBALL_DAMAGE_MIN_POWER/2, FIREBALL_DAMAGE_MAX_POWER/2);
else
damage = irand(FIREBALL_DAMAGE_MIN_POWER, FIREBALL_DAMAGE_MAX_POWER);
if (wimpy)
{ // Wimpy shots do half damage.
damage /= 2;
T_Damage(other, self, self->owner, self->movedir, self->s.origin, plane->normal,
damage, damage, DAMAGE_SPELL,MOD_FIREBALL); // No blast damage, just direct.
}
else
{
T_Damage(other, self, self->owner, self->movedir, self->s.origin, plane->normal,
damage>>1, damage, DAMAGE_SPELL|DAMAGE_EXTRA_KNOCKBACK,MOD_FIREBALL); // Half goes directly to target, blast does rest.
T_DamageRadius(self, self->owner, self->owner, FIREBALL_RADIUS,
FIREBALL_DAMAGE_MAX_POWER, FIREBALL_DAMAGE_MIN_POWER, DAMAGE_SPELL,MOD_FIREBALL);
}
}
else
{
if(deathmatch->value)
damage = irand(FIREBALL_DAMAGE_MIN/2, FIREBALL_DAMAGE_MAX/2);
else
damage = irand(FIREBALL_DAMAGE_MIN, FIREBALL_DAMAGE_MAX);
if (wimpy) // Wimpy (no mana) shots do half damage
damage /= 2;
T_Damage(other, self, self->owner, self->movedir, self->s.origin, plane->normal, damage, damage, DAMAGE_SPELL,MOD_FIREBALL);
}
}
else
{
VectorMA(self->s.origin, -8.0, self->movedir, self->s.origin);
}
// Attempt to apply a scorchmark decal to the thing I hit.
if(IsDecalApplicable(self, other, self->s.origin, surface, plane, planedir))
{
flags |= CEF_FLAG6;
}
gi.CreateEffect(NULL, FX_WEAPON_FLYINGFISTEXPLODE, flags, self->s.origin, "d", self->movedir);
G_SetToFree(self);
}
// ************************************************************************************************
// SpellCastFlyingFist
// ************************************************************************************************
void SpellCastFlyingFist(edict_t *caster, vec3_t startpos, vec3_t aimangles, vec3_t aimdir, float value)
{
edict_t *flyingfist;
trace_t trace;
int flags;
qboolean powerup, wimpy;
vec3_t forward, endpos;
playerinfo_t *playerinfo;
// Spawn the flying-fist (fireball)
flyingfist = G_Spawn();
playerinfo = &caster->client->playerinfo;
if (playerinfo->pers.inventory.Items[playerinfo->weap_ammo_index] < playerinfo->pers.weapon->quantity)
{
wimpy=true;
flags = CEF_FLAG8;
}
else
{
wimpy=false;
flags = 0;
}
if (playerinfo->powerup_timer > level.time)
{ // Powered up flying fist. Make it a meteor!
powerup = true;
flags |= CEF_FLAG7;
flyingfist->health = 1;
if (wimpy) // Play it quiet
gi.sound(caster, CHAN_WEAPON, gi.soundindex("weapons/FireballPowerCast.wav"), 0.5, ATTN_NORM, 0);
else // Play it loud
gi.sound(caster, CHAN_WEAPON, gi.soundindex("weapons/FireballPowerCast.wav"), 1.0, ATTN_NORM, 0);
}
else
{ // Not powered up
powerup = false;
if (wimpy) // Play special wimpy sound
gi.sound(caster, CHAN_WEAPON, gi.soundindex("weapons/FireballNoMana.wav"), 1.0, ATTN_NORM, 0);
else // Normal fireball sound
gi.sound(caster, CHAN_WEAPON, gi.soundindex("weapons/FlyingFistCast.wav"), 1.0, ATTN_NORM, 0);
}
CreateFlyingFist(flyingfist);
flyingfist->reflect_debounce_time = MAX_REFLECT;
VectorCopy(startpos, flyingfist->s.origin);
if (wimpy)
flyingfist->flags |= FL_NO_KNOCKBACK; // Just using the no knockback flag to indicate a wussy weapon.
//Check ahead first to see if it's going to hit anything at this angle
AngleVectors(aimangles, forward, NULL, NULL);
VectorMA(flyingfist->s.origin, FLYING_FIST_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, FLYING_FIST_SPEED, flyingfist->velocity);
}
else
{//autotarget current enemy
GetAimVelocity(caster->enemy, flyingfist->s.origin, FLYING_FIST_SPEED, aimangles, flyingfist->velocity);
}
flyingfist->owner = caster;
flyingfist->enemy = caster->enemy;
// Remember velocity in case we have to reverse it
VectorNormalize2(flyingfist->velocity, flyingfist->movedir);
G_LinkMissile(flyingfist);
// Make sure we don`t start in a solid
gi.trace(caster->s.origin, vec3_origin, vec3_origin, flyingfist->s.origin, caster, MASK_PLAYERSOLID,&trace);
if (trace.startsolid || trace.fraction < 1.0)
{
VectorCopy(trace.endpos, flyingfist->s.origin);
FlyingFistTouch(flyingfist, trace.ent, &trace.plane, trace.surface);
return;
}
// Spawn effect after it has been determined it has not started in wall
// This is so it won`t try to remove it before it exists
gi.CreateEffect(&flyingfist->s, FX_WEAPON_FLYINGFIST, CEF_OWNERS_ORIGIN | flags, NULL,
"t", flyingfist->velocity);
}