/* ================================================================ 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); }