305 lines
9.3 KiB
C
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);
|
|
}
|
|
|