765 lines
16 KiB
C++
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);
|
|
}
|