sin-2015/flamethrower.cpp
1999-04-22 00:00:00 +00:00

765 lines
16 KiB
C++

/*
================================================================
FLAMETHROWER
================================================================
Copyright (C) 1998 by 2015, Inc.
All rights reserved.
This source is may not be distributed and/or modified without
expressly written permission by 2015, Inc.
*/
#include "g_local.h"
#include "bullet.h"
#include "flamethrower.h"
#include "specialfx.h"
#include "player.h"
#include "surface.h"
#include "hoverbike.h"
#define FLAMETHROWER_MINBURST 0.8
#define FLAMETHROWER_MOVETOLERANCE 24
#define FLAMETHROWER_LENGTH 256
#define FLAMETHROWER_ARCFALLOFF 1
#define FLAMETHROWER_DAMAGE 2.2
#define FLAMETHROWER_DM_LENGTH 256
#define FLAMETHROWER_DM_ARCFALLOFF 0.5
#define FLAMETHROWER_DM_DAMAGE 1.75
//=================================================================
// ThrowerFlame is what does the actual damage for the Flamethrower
CLASS_DECLARATION(Entity, ThrowerFlame, NULL);
Event EV_ThrowerFlame_Burn("flame_burn");
ResponseDef ThrowerFlame::Responses[] =
{
{&EV_ThrowerFlame_Burn, (Response)ThrowerFlame::Burn},
{NULL, NULL}
};
qboolean ThrowerFlame::CanToast(Entity *target, float arc)
{
trace_t trace;
Vector org, pos, v;
float dot, dist;
qboolean in_arc;
int i;
// these are used to set where
// to spawn hit flames from
hitpos = vec_zero;
in_arc = false;
// bmodels need special checking because their origin is 0,0,0
if ( target->getMoveType() == MOVETYPE_PUSH )
{
org = ( target->absmin + target->absmax ) * 0.5;
v = org - origin;
dist = v.normalize2();
dot = DotProduct(dir.vec3(), v.vec3());
if(dot > arc)
{
in_arc = true;
hitpos = org;
}
trace = G_Trace( origin, vec_origin, vec_origin, org, this, MASK_SHOT, "ThrowerFlame::CanToast 1a" );
if((trace.fraction == 1 || trace.ent == target->edict) && in_arc)
{
return true;
}
}
else
{
org = target->centroid;
v = org - origin;
dist = v.normalize2();
dot = DotProduct(dir.vec3(), v.vec3());
if(dot > arc)
{
in_arc = true;
hitpos = org;
}
trace = G_Trace( origin, vec_origin, vec_origin, org, this, MASK_SHOT, "ThrowerFlame::CanToast 1b" );
if((trace.fraction == 1 || trace.ent == target->edict) && in_arc)
{
return true;
}
}
// we didn't hit the center, so try the centers of the six sides
for(i = 0; i < 6; i++)
{
pos = org;
switch(i)
{
case 0:
pos.x = target->absmin.x;
break;
case 1:
pos.x = target->absmax.x;
break;
case 2:
pos.y = target->absmin.y;
break;
case 3:
pos.y = target->absmax.y;
break;
case 4:
pos.z = target->absmin.z;
break;
case 5:
pos.z = target->absmax.z;
break;
}
v = pos - origin;
dist = v.normalize2();
dot = DotProduct(dir.vec3(), v.vec3());
if(dot > arc)
{
in_arc = true;
hitpos = pos;
}
trace = G_Trace( origin, vec_origin, vec_origin, pos, this, MASK_SHOT, "ThrowerFlame::CanToast 2" );
if((trace.fraction == 1 || trace.ent == target->edict) && in_arc)
{
return true;
}
}
return false;
}
void ThrowerFlame::Burn(Event *ev)
{
float points;
Entity *ent;
Vector org;
Vector v;
float entdist;
float entarc, entdot;
float burnlength, arcfalloff, burndamage;
ent = G_GetEntity(owner);
if(!ent) // remove self if owner is gone
{
PostEvent(EV_Remove, 0.1);
return;
}
if(deathmatch->value)
{
burnlength = FLAMETHROWER_DM_LENGTH;
arcfalloff = FLAMETHROWER_DM_ARCFALLOFF;
burndamage = FLAMETHROWER_DM_DAMAGE;
}
else
{
burnlength = FLAMETHROWER_LENGTH;
arcfalloff = FLAMETHROWER_ARCFALLOFF;
burndamage = FLAMETHROWER_DAMAGE;
}
// monsters do less damage with the flamethrower
if(!ent->isClient())
{
burndamage *= 0.6;
}
ent = NULL;
do
{
ent = findradius(ent, origin, burnlength);
if(!ent)
continue;
if (
(ent == G_GetEntity(owner)) ||
(!ent->takedamage) ||
(ent->deadflag) ||
(ent->isSubclassOf(HoverbikeBox))
)
{
continue;
}
org = ent->origin + (ent->mins + ent->maxs)*0.5;
v = org - origin;
entdist = v.normalize2();
// adjust for length of different timming counts
if(counter == 3)
{
if(entdist < 64)
continue;
}
else if(counter < 3)
{
if(entdist < 128)
continue;
}
entarc = DotProduct(dir.vec3(), v.vec3());
// make sure entity is in front
if(entarc < 0)
continue;
// determine amount of damage to do
points = 1 - ((1 - entarc)*arcfalloff);
points *= burndamage;
if(points <= 0)
{
continue;
}
// see if entity is in damage arc
if(entdist < 16)
{
entdot = 0.4;
}
else if(entdist < 32)
{
entdot = 0.7;
}
else if(entdist < 64)
{
entdot = 0.9;
}
else if(entdist < 128)
{
entdot = 0.95;
}
else if(entdist < 192)
{
entdot = 0.97;
}
else
{
entdot = 0.98;
}
if(!CanToast(ent, entdot))
continue;
ent->Damage(this, G_GetEntity(owner), (int)(points + 0.5), org, v, vec_zero, 0, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_FLAMETHROWER, -1, -1, 1.0f );
// make a bit of a visual cue that you hit something
if(ent->isSubclassOf(Sentient) && !ent->deadflag)
{
((Sentient *)ent)->CancelEventsOfType(EV_Sentient_HurtFlame);
ent->edict->s.effects |= EF_FLAMES;
((Sentient *)ent)->PostEvent(EV_Sentient_HurtFlame, 1);
}
else
{
v = hitpos + Vector(-6, -6, 0);
org = hitpos + Vector(6, 6, 0);
SpawnFlame(org, ent->velocity*0.5 + Vector(24, 24, 40), v, ent->velocity*0.5 + Vector(-24, -24, 40), 1, 154, 20);
v.x = hitpos.x + 6;
org.x = hitpos.x - 6;
SpawnFlame(org, ent->velocity*0.5 + Vector(-24, 24, 40), v, ent->velocity*0.5 + Vector(24, 24, 40), 1, 154, 20);
}
} while(ent);
counter--;
if(counter)
{
PostEvent(EV_ThrowerFlame_Burn, 0.1);
}
else
{
PostEvent(EV_Remove, 0.1);
}
}
void ThrowerFlame::Setup(Entity *owner, Vector pos, Vector streamend)
{
this->owner = owner->entnum;
edict->owner = owner->edict;
setMoveType(MOVETYPE_NONE);
setSolidType(SOLID_NOT);
hideModel();
// make sure not to send this to clients
edict->svflags |= SVF_NOCLIENT;
setOrigin(pos);
end = streamend;
dir = end - origin;
length = dir.normalize2();
counter = 4;
CancelEventsOfType(EV_ThrowerFlame_Burn);
ProcessEvent(EV_ThrowerFlame_Burn);
}
//=======================================================
CLASS_DECLARATION( Weapon, FlameThrower, "weapon_flamethrower" );
Event EV_FlameThrower_BlastTimer("flameblast_timer");
ResponseDef FlameThrower::Responses[] =
{
{ &EV_Weapon_Shoot, ( Response )FlameThrower::Shoot },
{ &EV_FlameThrower_BlastTimer, ( Response )FlameThrower::BlastTimer },
{ NULL, NULL }
};
FlameThrower::FlameThrower()
{
SetModels( "flamethrower.def", "view_flame.def" );
SetAmmo( "FlameFuel", 1, 100 );
modelIndex("flamethrowerfuel.def");
SetRank( 85, 45 );
SetType(WEAPON_2HANDED_LO);
SetMinRange(0);
SetMaxRange(250);
blastcounter = 0;
flamelength = 0;
lastfiretime = 0;
lastanimtime = 0;
}
void FlameThrower::SecondaryUse (Event *ev)
{
// switch to the assaultrifle
owner->useWeapon("AssaultRifle");
}
void FlameThrower::BlastTimer(Event *ev)
{
// check for owner dying or getting on a hoverbike
if((!owner) || (owner->IsOnBike()))
{
CancelEventsOfType(EV_Weapon_Shoot);
CancelEventsOfType(EV_FlameThrower_BlastTimer);
weaponstate = WEAPON_READY;
StopAnimating();
return;
}
// only do this stuff for clients
if(!owner->isClient())
return;
// check if we still need to keep the weapon firing by itself
if((!(((Player *)owner.Ptr())->Buttons() & BUTTON_ATTACK)) && (blastcounter > level.time))
{
Fire();
}
// check if we need to keep doing this
if(blastcounter > level.time && HasAmmo() && !ChangingWeapons())
{
PostEvent(EV_FlameThrower_BlastTimer, 0.1);
}
}
void FlameThrower::Fire (void)
{
if(!ReadyToFire())
{
return;
}
if(!HasAmmoInClip())
{
CheckReload();
return;
}
UseAmmo(ammorequired);
weaponstate = WEAPON_FIRING;
CancelEventsOfType(EV_Weapon_DoneFiring);
// this is just a precaution that we can re-trigger
NextAttack( 5 );
if(lastanimtime < level.time)
{
RandomAnimate("fire_sound", EV_Weapon_DoneFiring);
lastanimtime = level.time + 0.4;
}
else
{
RandomAnimate("fire", EV_Weapon_DoneFiring);
}
last_attack_time = level.time;
}
void FlameThrower::Shoot (Event *ev)
{
ThrowerFlame *flame;
Vector pos, dir;
Vector up, right;
Vector tmpvec, dest;
float length;
trace_t trace;
int mask;
assert(owner);
if (!owner)
return;
mask = MASK_SHOT | MASK_WATER;
GetMuzzlePosition(&pos, &dir, &right, &up);
tmpvec = pos + dir*4 - up;
pos -= dir*4;
trace = G_Trace(pos, Vector(-2, -2, -2), Vector(2, 2, 2), tmpvec, owner, mask, "FlameThrower::Shoot 1");
pos = trace.endpos - dir*2;
// can't fire if underwater
if(gi.pointcontents(pos.vec3()) & MASK_WATER)
{
Bubble *bubble;
if((level.time - lastfiretime) > 0.3) // starting a new stream, so init the needed stuff
{
// only do blast timmer for clients
if(owner->isClient())
{
// init firing timer
blastcounter = level.time + FLAMETHROWER_MINBURST;
PostEvent(EV_FlameThrower_BlastTimer, 0.1);
}
}
for(mask = 0; mask < 2; mask++)
{
bubble = new Bubble;
bubble->Setup(pos);
bubble->velocity += dir*128;
}
flamelength = 0;
lastpos = vec_zero;
lastfiretime = level.time;
NextAttack(0);
return;
}
if((level.time - lastfiretime) > 0.3) // starting a new stream, so init the needed stuff
{
flamelength = 128;
lastpos = vec_zero;
// only do blast timmer for clients
if(owner->isClient())
{
// init firing timer
blastcounter = level.time + FLAMETHROWER_MINBURST;
PostEvent(EV_FlameThrower_BlastTimer, 0.1);
}
}
else // continuing a stream
{
flamelength += 128;
if(deathmatch->value)
{
if(flamelength > FLAMETHROWER_DM_LENGTH)
flamelength = FLAMETHROWER_DM_LENGTH;
}
else
{
if(flamelength > FLAMETHROWER_LENGTH)
flamelength = FLAMETHROWER_LENGTH;
}
}
dest = pos + dir*flamelength;
trace = G_Trace(pos, Vector(-2, -2, -2), Vector(2, 2, 2), dest, owner, mask, "FlameThrower::Shoot 2");
// make the flamethrower damage surfaces
surfaceManager.DamageSurface(&trace, FLAMETHROWER_DAMAGE, owner);
mask = MASK_SOLIDNONFENCE | MASK_WATER;
trace = G_Trace(pos, Vector(-2, -2, -2), Vector(2, 2, 2), dest, owner, mask, "FlameThrower::Shoot 3");
dest = trace.endpos;
// set the damage entity
if(!mainflame)
{
flame = new ThrowerFlame;
mainflame = flame;
}
mainflame->Setup(owner, pos, dest);
// make a flame splash for stream hiting something solid
if(trace.fraction != 1)
{
tmpvec = dest - dir*16;
SpawnThrowerFlameHit(tmpvec, dir);
}
// see if we need to make movement streams
if(lastpos != vec_zero)
{
tmpvec = lastdest - dest;
length = tmpvec.length();
if(length > FLAMETHROWER_MOVETOLERANCE)
{
Vector tmppos, tmpdest;
float poslength;
Vector posmove, destmove;
tmpvec = lastpos - pos;
poslength = tmpvec.length();
length /= FLAMETHROWER_MOVETOLERANCE;
poslength /= length;
posmove = lastpos - pos;
posmove.normalize();
destmove = lastdest - dest;
destmove.normalize();
destmove *= FLAMETHROWER_MOVETOLERANCE;
tmppos = pos;
tmpdest = dest;
while(length >= 1)
{
tmppos += posmove*poslength;
tmpdest += destmove;
// make a damage entity for this stream
flame = new ThrowerFlame;
flame->Setup(owner, tmppos, tmpdest);
length--;
}
SpawnThrowerFlameRow(pos, dest, lastpos, lastdest);
}
else
SpawnThrowerFlame(pos, dest);
}
else
SpawnThrowerFlame(pos, dest);
lastfiretime = level.time;
lastpos = pos;
lastdest = dest;
NextAttack(0);
}
//==================================================================
// And yae, from yonder, hither weapon doth seth fourth evil things...
#include "actor.h"
class EXPORT_FROM_DLL EvilFlameThrower : public FlameThrower
{
public:
CLASS_PROTOTYPE( EvilFlameThrower );
EvilFlameThrower();
virtual void Shoot(Event *ev);
};
CLASS_DECLARATION(FlameThrower, EvilFlameThrower, "weapon_evilflamethrower");
ResponseDef EvilFlameThrower::Responses[] =
{
{&EV_Weapon_Shoot, (Response)EvilFlameThrower::Shoot},
{NULL, NULL}
};
EvilFlameThrower::EvilFlameThrower()
{
}
void EvilFlameThrower::Shoot (Event *ev)
{
ThrowerFlame *flame;
Vector pos, dir;
Vector up, right;
Vector tmpvec, dest;
float length;
trace_t trace;
int mask;
int i, n;
float entdelta, nearest;
Sentient *ent, *eviltarget;
assert(owner);
if (!owner)
return;
mask = MASK_SHOT | MASK_WATER;
GetMuzzlePosition(&pos, &dir, &right, &up);
tmpvec = pos + dir*4 - up;
pos -= dir*4;
// search for a sentient target within reach
if(deathmatch->value)
nearest = FLAMETHROWER_DM_LENGTH;
else
nearest = FLAMETHROWER_LENGTH;
eviltarget = NULL;
n = SentientList.NumObjects();
for(i = 1; i <= n; i++)
{
ent = SentientList.ObjectAt(i);
if(ent == owner)
continue;
if(ent->deadflag || ent->health <= 0 || !ent->takedamage)
continue;
if(!ent->isClient() && !ent->isSubclassOf(Actor))
continue;
dest = ent->centroid - pos;
entdelta = dest.length();
if(entdelta < nearest)
{
nearest = entdelta;
eviltarget = ent;
}
}
if(eviltarget)
{
dir = eviltarget->centroid - pos;
dir.normalize();
}
trace = G_Trace(pos, Vector(-2, -2, -2), Vector(2, 2, 2), tmpvec, owner, mask, "FlameThrower::Shoot 1");
pos = trace.endpos - dir*2;
if((level.time - lastfiretime) > 0.3) // starting a new stream, so init the needed stuff
{
flamelength = 128;
lastpos = vec_zero;
// only do blast timmer for clients
if(owner->isClient())
{
// init firing timer
blastcounter = level.time + FLAMETHROWER_MINBURST;
PostEvent(EV_FlameThrower_BlastTimer, 0.1);
}
}
else // continuing a stream
{
flamelength += 128;
if(deathmatch->value)
{
if(flamelength > FLAMETHROWER_DM_LENGTH)
flamelength = FLAMETHROWER_DM_LENGTH;
}
else
{
if(flamelength > FLAMETHROWER_LENGTH)
flamelength = FLAMETHROWER_LENGTH;
}
}
dest = pos + dir*flamelength;
trace = G_Trace(pos, Vector(-2, -2, -2), Vector(2, 2, 2), dest, owner, mask, "FlameThrower::Shoot 2");
// make the flamethrower damage surfaces
surfaceManager.DamageSurface(&trace, FLAMETHROWER_DAMAGE, owner);
mask = MASK_SOLIDNONFENCE | MASK_WATER;
trace = G_Trace(pos, Vector(-2, -2, -2), Vector(2, 2, 2), dest, owner, mask, "FlameThrower::Shoot 3");
dest = trace.endpos;
// set the damage entity
if(!mainflame)
{
flame = new ThrowerFlame;
mainflame = flame;
}
mainflame->Setup(owner, pos, dest);
// make a flame splash for stream hiting something solid
if(trace.fraction != 1)
{
tmpvec = dest - dir*16;
SpawnThrowerFlameHit(tmpvec, dir);
}
// see if we need to make movement streams
if(lastpos != vec_zero)
{
tmpvec = lastdest - dest;
length = tmpvec.length();
if(length > FLAMETHROWER_MOVETOLERANCE)
{
Vector tmppos, tmpdest;
float poslength;
Vector posmove, destmove;
tmpvec = lastpos - pos;
poslength = tmpvec.length();
length /= FLAMETHROWER_MOVETOLERANCE;
poslength /= length;
posmove = lastpos - pos;
posmove.normalize();
destmove = lastdest - dest;
destmove.normalize();
destmove *= FLAMETHROWER_MOVETOLERANCE;
tmppos = pos;
tmpdest = dest;
while(length >= 1)
{
tmppos += posmove*poslength;
tmpdest += destmove;
// make a damage entity for this stream
flame = new ThrowerFlame;
flame->Setup(owner, tmppos, tmpdest);
length--;
}
SpawnThrowerFlameRow(pos, dest, lastpos, lastdest);
}
else
SpawnThrowerFlame(pos, dest);
}
else
SpawnThrowerFlame(pos, dest);
lastfiretime = level.time;
lastpos = pos;
lastdest = dest;
NextAttack(0);
}