#include "defs.qh" /* PYRO.QC TeamFortress v2.5 31/10/96 */ /*============================================== Weapons and functions for the PYRO class and associated weaponry ==============================================*/ void() NapalmGrenadeTouch; //WK Extern float (entity foobar) IsBuilding; void (vector org, entity shooter) NapalmGrenadeLaunch ; void() Napalm_touch; float (string id_flame) RemoveFlameFromQueue; #define DAMAGE_TIME 3 //** different types of flames (decreasing priority) // 1 : burning flames making light and damage (1 per players or monsters) // 2 : exploding flames (grenade) // 3 : burning flames (players, monsters) // 4 : world flames (on ground) // create a flame of a given type, maintaining the count for each type entity (string type, entity p_owner) FlameSpawn = { if (type != "1") return world; num_world_flames = num_world_flames + 1; /* db1 = ftos(num_world_flames); RPrint("num_world_flames : "); RPrint(db1); RPrint("\n"); */ while (num_world_flames > FLAME_MAXWORLDNUM) { if (!(RemoveFlameFromQueue(type))) { // RPrint("Create flame failed: too many\n"); return world; } } newmis = spawn(); // to keep track of the number of each type of flames if (type == "1") { newmis.movetype = MOVETYPE_FLYMISSILE; newmis.solid = SOLID_BBOX; newmis.effects = EF_DIMLIGHT; newmis.flame_id = "1"; setmodel (newmis, "progs/flame2.mdl"); setsize (newmis, '0 0 0', '0 0 0'); } else if (type == "2") { newmis.movetype = MOVETYPE_BOUNCE; newmis.solid = SOLID_BBOX; newmis.flame_id = "2"; setmodel (newmis, "progs/flame2.mdl"); newmis.frame=1; setsize (newmis, '0 0 0', '0 0 0'); } else if (type == "3") { newmis.movetype = MOVETYPE_FLYMISSILE; newmis.solid = SOLID_BBOX; newmis.flame_id = "3"; setmodel (newmis, "progs/flame2.mdl"); setsize (newmis, '0 0 0', '0 0 0'); } else if (type == "4") { newmis.movetype = MOVETYPE_FLYMISSILE; newmis.solid = SOLID_BBOX; newmis.flame_id = "4"; setmodel (newmis, "progs/flame2.mdl"); newmis.frame=1; setsize (newmis, '0 0 0', '0 0 0'); } newmis.owner = p_owner; return newmis; }; // destroy a given flame, maintaining counters and links in the queue void (entity this) FlameDestroy = { // local string db1; num_world_flames = num_world_flames - 1; /* db1 = ftos(num_world_flames); RPrint("num_world_flames : "); RPrint(db1); RPrint("\n"); */ remove(this); }; float (string id_flame) RemoveFlameFromQueue = { local entity tmp; // local string db1; if (num_world_flames < FLAME_MAXWORLDNUM) { RPrint("ERROR in RemoveFlameFromQueue\n"); return FALSE; } /* db1 = ftos(num_world_flames); RPrint("num_world_flames : "); RPrint(db1); RPrint("\n"); */ num_world_flames = num_world_flames - 1; tmp = find(world, flame_id, "4"); if (!tmp) { if (id_flame == "4") // if priority not high enough, don't continue return FALSE; tmp = find(world, flame_id, "3"); if (!tmp) { if (id_flame == "3") return FALSE; tmp = find(world, flame_id, "2"); if (!tmp) { if (id_flame == "2") return FALSE; tmp = find(world, flame_id, "1"); if (!tmp) { // oh shit, no flames found! // the queue must be wrong RPrint("\n\nRemoveFlameFromQueue():BOOM!\n"); RPrint("!! please report this bug !!\n"); return FALSE; } } } } /* RPrint("flame removed: flame_id: "); RPrint(id_flame); RPrint("\n"); */ /* if (tmp.effects == EF_DIMLIGHT) { RPrint("** error: this flame should not be removed **\n"); } */ remove(tmp); return TRUE; }; void() Remove= { FlameDestroy(self); }; // function used by the flames spawned when the grenade explode : killed in water or when stopped void() NapalmGrenadeFollow = { traceline(self.origin,self.origin,TRUE,self); //WK Bugfix if (self.enemy.waterlevel > 1) { sound (self, CHAN_VOICE, "misc/vapeur2.wav", 1, ATTN_NORM); FlameDestroy(self); } if (self.velocity == '0 0 0') FlameDestroy(self); self.nextthink = time + 0.1; // self.think = NapalmGrenadeFollow; }; void() NapalmGrenadeTouch = { sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound if (self.velocity == '0 0 0') self.avelocity = '0 0 0'; }; void() NapalmGrenadeExplode = { local float i; sound (self, CHAN_AUTO, "weapons/flmgrexp.wav", 1, ATTN_NORM); // bounce sound // Launch flames traceline(self.origin,self.origin,TRUE,self); if (trace_inwater == TRUE) { dremove(self); return; } #ifdef DEMO_STUFF // Remove any camera's locks on this missile if (self.enemy) CamProjectileLockOff(); #endif local entity head; // do an incendiary-cannon explosion instead self.effects = self.effects | EF_BRIGHTLIGHT; // don't do radius damage to the other, because all the damage // was done in the impact head = findradius(self.origin, 140); while (head) { if (head.takedamage) { deathmsg = DMSG_FLAME; TF_T_Damage (head, self, self.owner, 120, TF_TD_NOTTEAM, TF_TD_FIRE); // set 'em on fire other = head; // i can't believe this works! Napalm_touch(); if (other.classname == "player") stuffcmd(other, "bf\nbf\n"); } head = head.chain; } WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); WriteByte (MSG_BROADCAST, TE_EXPLOSION); WriteCoord (MSG_BROADCAST, self.origin_x); WriteCoord (MSG_BROADCAST, self.origin_y); WriteCoord (MSG_BROADCAST, self.origin_z); #ifdef QUAKE_WORLD multicast (self.origin, MULTICAST_PHS); dremove(self); #else BecomeExplosion (); #endif }; //========================================================================= // Launch a flame foe the grenade explosion void (vector org, entity shooter) NapalmGrenadeLaunch = { local float xdir,ydir,zdir, spin; xdir = 150 * random() - 75; ydir = 150 * random() - 75; zdir = 40 * random(); newmis = FlameSpawn ("2", shooter); if (newmis == world) return; self.touch = SUB_Null; newmis.classname = "fire"; newmis.touch = Napalm_touch; newmis.think = NapalmGrenadeFollow; newmis.nextthink = time + 0.1; newmis.enemy = shooter.owner; newmis.velocity_x = xdir * 2; newmis.velocity_y = ydir * 2; newmis.velocity_z = zdir * 15; spin = (random() * 10) / 2; if (spin <= 0) newmis.avelocity='250 300 400'; if (spin == 1) newmis.avelocity='400 250 300'; if (spin == 2) newmis.avelocity='300 400 250'; if (spin == 3) newmis.avelocity='300 300 300'; if (spin >= 4) newmis.avelocity='400 250 400'; setorigin (newmis, org); setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); }; void() OnPlayerFlame_touch; void() FlameFollow = { local vector dir,vtemp,boundsize; local float damage; vtemp = self.enemy.absmin; boundsize = self.enemy.size; self.solid = SOLID_NOT; self.movetype = MOVETYPE_NONE; // if no flames, remove itself if (self.enemy.numflames == 0) { FlameDestroy(self); return; } if (self.enemy.health < 1) { deathmsg = DMSG_FLAME; T_RadiusDamage(self,self,10,self); self.enemy.numflames = 0; FlameDestroy(self); return; } if (self.enemy.armorclass & AT_SAVEFIRE) { // if ((random()*100) < self.enemy.armorvalue) if (self.enemy.armorvalue > 0) { self.health = 0; } } if (self.health < 1) { // only remove the flame if it is not the master flame, or if it is the last flame if (self.effects != EF_DIMLIGHT || self.enemy.numflames <= 1) { self.enemy.numflames = self.enemy.numflames - 1; self.enemy.numflames = 0; FlameDestroy(self); return; } } self.health = self.health - 1; #ifndef QUAKE_WORLD dir_x = (random() * boundsize_x/2)+boundsize_x/4; dir_y = (random() * boundsize_y/2)+boundsize_y/4; dir_z = (random() * boundsize_z/3)+boundsize_z/2; vtemp = vtemp + dir; setorigin(self, vtemp); #else // if player is moving too fast, hide flames if (vlen(self.enemy.velocity) < 50) { dir_x = (random() * boundsize_x/2)+boundsize_x/4; dir_y = (random() * boundsize_y/2)+boundsize_y/4; dir_z = (random() * boundsize_z/3)+boundsize_z/2; vtemp = vtemp + dir; setorigin(self, vtemp); if (self.model != "progs/flame2.mdl") { self.model = "progs/flame2.mdl"; setmodel(self, self.model); } } else if (self.model == "progs/flame2.mdl") { self.model = string_null; setmodel(self, self.model); } #endif //traceline(self.origin,self.origin,TRUE,self); //WK Bugfix if (self.enemy.waterlevel > 1) { sound (self, CHAN_VOICE, "misc/vapeur2.wav", 1, ATTN_NORM); self.enemy.numflames = self.enemy.numflames - 1; FlameDestroy(self); return; } self.nextthink = time + 0.1; if (self.effects == EF_DIMLIGHT && self.heat >= DAMAGE_TIME) { damage = self.enemy.numflames * 0.3 * DAMAGE_TIME; if (damage < 1) damage = 1; self.heat = 1; deathmsg = DMSG_FLAME; TF_T_Damage(self.enemy, self, self.owner, damage, TF_TD_NOTTEAM, TF_TD_FIRE); } else if (self.effects == EF_DIMLIGHT) { self.heat = self.heat + 1; } }; // OnPlayerflame : no damage if enemy not dead, spawn flames if touched void() OnPlayerFlame_touch = { local entity flame; local vector vtemp; if (other != world && other.health > 0 && other != self.enemy) { if (other.numflames >= FLAME_MAXPLYRFLAMES) return; if (other.classname == "player") { if ((teamplay & TEAMPLAY_NOEXPLOSIVE) && Teammate(other.team_no, self.owner.team_no)) return; CenterPrint(other,"You are on fire!\n"); stuffcmd (other,"bf\n"); } if (other.numflames < FLAME_NUMLIGHTS) { flame = FlameSpawn ("1", other); sound (flame, CHAN_VOICE, "ambience/fire1.wav", 1, ATTN_NORM); } else { flame = FlameSpawn ("3", other); if (flame == world) return; } flame.classname = "fire"; flame.health = FLAME_PLYRMAXTIME; other.numflames = other.numflames + 1; flame.velocity = other.velocity; flame.enemy = other; flame.touch = OnPlayerFlame_touch; flame.owner = self.owner; vtemp = self.origin; setorigin(flame, vtemp); flame.nextthink = time + 0.1; flame.think = FlameFollow; } }; // worldflame : lot of damage, spawn flames if touched void() WorldFlame_touch = { local entity flame; local vector dir,vtemp; deathmsg = DMSG_FLAME; TF_T_Damage(other, self, self.enemy, 2, TF_TD_NOTTEAM, TF_TD_FIRE); if (other != world && other.solid != SOLID_TRIGGER && other.health > 0) { if (other.numflames >= FLAME_MAXPLYRFLAMES) return; if (other.classname == "player") { if ((teamplay & TEAMPLAY_NOEXPLOSIVE) && Teammate(other.team_no, self.owner.team_no)) return; CenterPrint(other,"You are on fire!\n"); stuffcmd (other,"bf\n"); } if (other.numflames < FLAME_NUMLIGHTS) { flame = FlameSpawn ("1", other); sound (flame,CHAN_VOICE, "ambience/fire1.wav", 1, ATTN_NORM); } else { flame = FlameSpawn ("3", other); if (flame == world) return; } flame.classname = "fire"; flame.health = 0; other.numflames=other.numflames + 1; flame.velocity = other.velocity; flame.enemy = other; flame.touch = OnPlayerFlame_touch; flame.owner = self.owner; vtemp = self.origin + '0 0 10'; setorigin(flame, vtemp); flame.nextthink = time + 0.15; flame.think = FlameFollow; } }; //Like the flamethrower touch, but it doesn't light them on fire void() Boot_Flamer_stream_touch = { deathmsg = DMSG_HOVER; //WK Sweep for mines at point of contact GuerillaMineSweep(self.origin); //WK if (other.takedamage && other.classname == "player") if (other.takedamage) TF_T_Damage(other,self,self.owner, 40, TF_TD_NOTTEAM, TF_TD_FIRE); remove(self); }; // first touch : direct touch with the flamer stream or flame from grenade void() Flamer_stream_touch = { local entity flame; local vector dir,vtemp; if (other.classname == "fire") return; //WK Sweep for mines at point of contact GuerillaMineSweep(self.origin); if (other != world) { if (other.takedamage == DAMAGE_AIM && other.health > 0) { deathmsg = DMSG_FLAME; TF_T_Damage(other,self,self.owner, 15, TF_TD_NOTTEAM, TF_TD_FIRE); if (other.numflames >= FLAME_MAXPLYRFLAMES) return; if (other.armorclass & AT_SAVEFIRE) { // if ((random()*100) < other.armorvalue) if (other.armorvalue > 0) { return; } } if (other.classname == "player") { if ((teamplay & TEAMPLAY_NOEXPLOSIVE) && Teammate(other.team_no, self.owner.team_no)) return; CenterPrint(other,"You are on fire!\n"); stuffcmd (other,"bf\n"); } if (other.numflames < FLAME_NUMLIGHTS) { flame = FlameSpawn("1", other); sound (flame,CHAN_VOICE, "ambience/fire1.wav", 1, ATTN_NORM); } else { flame = FlameSpawn("3", other); if (flame == world) return; } flame.classname = "fire"; flame.health = FLAME_PLYRMAXTIME; other.numflames=other.numflames + 1; flame.velocity = other.velocity; flame.enemy = other; flame.touch = OnPlayerFlame_touch; flame.owner = self.owner; vtemp=self.origin; setorigin(flame , vtemp); flame.nextthink = time + 0.1; flame.think = FlameFollow; } } else { if (random() < FLAME_BURNRATIO) { remove(self); return; } flame = FlameSpawn("4", other); if (flame != world) { flame.touch = WorldFlame_touch; flame.classname = "fire"; vtemp=self.origin + '0 0 10'; setorigin(flame , vtemp); flame.nextthink = time + FLAME_MAXBURNTIME; flame.think = Remove; flame.enemy = self.owner; } remove(self); } }; void() Napalm_touch = { local entity flame; local vector dir,vtemp; if (other.classname == "fire") return; if (other != world) { if (other.takedamage == DAMAGE_AIM && other.health > 0) { deathmsg = DMSG_FLAME; TF_T_Damage(other, self, self.owner, 6, TF_TD_NOTTEAM, TF_TD_FIRE); if (other.numflames >= FLAME_MAXPLYRFLAMES) return; if (other.armorclass & AT_SAVEFIRE && other.armorvalue > 0) return; if (other.classname == "player") { if ((teamplay & TEAMPLAY_NOEXPLOSIVE) && Teammate(other.team_no, self.owner.team_no)) return; CenterPrint(other,"You are on fire!\n"); stuffcmd (other,"bf\n"); } if (other.numflames < FLAME_NUMLIGHTS) // = 0 { flame = FlameSpawn("1", other); sound (flame,CHAN_VOICE, "ambience/fire1.wav", 1, ATTN_NORM); } else { flame = FlameSpawn("3", other); if (flame == world) return; } flame.classname = "fire"; flame.health = FLAME_PLYRMAXTIME; other.numflames = other.numflames + 1; flame.velocity = other.velocity; flame.enemy = other; flame.touch = OnPlayerFlame_touch; flame.owner = self.owner; vtemp=self.origin; setorigin(flame , vtemp); flame.nextthink = time + 0.1; flame.think = FlameFollow; } } else { flame = FlameSpawn("4", other); if (flame != world) { flame.touch = WorldFlame_touch; flame.classname = "fire"; vtemp=self.origin + '0 0 10'; setorigin(flame , vtemp); flame.nextthink = time + NAPALM_MAXBURNTIME; flame.think = Remove; flame.enemy = self.owner; } FlameDestroy(self); } }; // Player.qc declaration void () DeathBubblesSpawn; // Slightly varied version of DEATHBUBBLES void(float num_bubbles, vector bub_origin) NewBubbles = { local entity bubble_spawner; bubble_spawner = spawn(); setorigin (bubble_spawner, bub_origin); bubble_spawner.movetype = MOVETYPE_NONE; bubble_spawner.solid = SOLID_NOT; bubble_spawner.nextthink = time + 0.1; if (self.classname == "player") bubble_spawner.owner = self; else bubble_spawner.owner = self.enemy; bubble_spawner.think = DeathBubblesSpawn; bubble_spawner.bubble_count = num_bubbles; return; }; void() W_FireFlame = { local entity flame; local float rn; if (self.waterlevel > 2) { makevectors (self.v_angle); NewBubbles(2, self.origin+v_forward*64); rn = random(); if (rn < 0.5) sound (self, CHAN_WEAPON, "misc/water1.wav", 1, ATTN_NORM); else sound (self, CHAN_WEAPON, "misc/water2.wav", 1, ATTN_NORM); return; } // Take away a cell self.currentammo = self.ammo_cells = self.ammo_cells - 1; sound (self, CHAN_AUTO, "weapons/flmfire2.wav", 1, ATTN_NORM); flame = spawn (); flame.owner = self; flame.movetype = MOVETYPE_FLYMISSILE; flame.solid = SOLID_BBOX; flame.classname = "flamerflame"; // set flame speed makevectors (self.v_angle); flame.velocity = aim(self, 10000); flame.velocity = flame.velocity * 600; flame.touch = Flamer_stream_touch; flame.think = s_explode1; flame.nextthink = time + 0.15; setmodel (flame, "progs/s_explod.spr"); setsize (flame, '0 0 0', '0 0 0'); setorigin (flame, self.origin + v_forward * 16 + '0 0 16'); }; /*====================== Touch function for incendiary cannon rockets ======================*/ void() T_IncendiaryTouch = { local float damg; local float points; local entity head; local vector org; if (other == self.owner) return; // don't explode on owner if (pointcontents(self.origin) == CONTENT_SKY) { remove(self); return; } self.effects = self.effects | EF_BRIGHTLIGHT; damg = 20 + random()*20; if (other.health) { deathmsg = DMSG_FLAME; TF_T_Damage (other, self, self.owner, damg, TF_TD_NOTTEAM, TF_TD_FIRE); } // don't do radius damage to the other, because all the damage // was done in the impact head = findradius(self.origin, 180); while (head) { if (head.takedamage) { deathmsg = DMSG_FLAME; TF_T_Damage (head, self, self.owner, 15, TF_TD_NOTTEAM, TF_TD_FIRE); // set 'em on fire other = head; // i can't believe this works! Napalm_touch(); if (other.classname == "player") stuffcmd(other, "bf\nbf\n"); if (IsBuilding(other)) TF_T_Damage (head, self, self.owner, 35, TF_TD_NOTTEAM, TF_TD_FIRE); } head = head.chain; } // sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); self.origin = self.origin - 8*normalize(self.velocity); #ifdef DEMO_STUFF // Remove any camera's locks on this missile if (self.enemy) CamProjectileLockOff(); #endif WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); WriteByte (MSG_BROADCAST, TE_EXPLOSION); WriteCoord (MSG_BROADCAST, self.origin_x); WriteCoord (MSG_BROADCAST, self.origin_y); WriteCoord (MSG_BROADCAST, self.origin_z); #ifdef QUAKE_WORLD multicast (self.origin, MULTICAST_PHS); if (other.classname == "force_field") //- OfN - Makes field explosion b4 removing it FieldExplosion(other,self.origin,self); dremove(self); #else BecomeExplosion (); #endif }; /* ================ W_FireIncendiaryCannon ================ */ void() W_FireIncendiaryCannon = { if (self.ammo_rockets < 3) return; self.currentammo = self.ammo_rockets = self.ammo_rockets - 3; sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); KickPlayer(-3, self); newmis = spawn (); newmis.owner = self; newmis.movetype = MOVETYPE_FLYMISSILE; newmis.solid = SOLID_BBOX; // set newmis speed makevectors (self.v_angle); newmis.velocity = aim(self, 1000); newmis.velocity = newmis.velocity * 600; newmis.angles = vectoangles(newmis.velocity); newmis.touch = T_IncendiaryTouch; //- OfN - For airfist newmis.classname = "rocket"; // set newmis duration newmis.nextthink = time + 5; newmis.think = SUB_Remove; newmis.weapon = DMSG_INCENDIARY; setmodel (newmis, "progs/missile.mdl"); setsize (newmis, '0 0 0', '0 0 0'); setorigin (newmis, self.origin + v_forward*8 + '0 0 16'); #ifdef DEMO_STUFF // Have we got a live camera in projectile mode? if (live_camera) CamProjectileLockOn(); #endif }; //========================================================================= // Incendiary cannon selection function void() TeamFortress_IncendiaryCannon = { if (!(self.weapons_carried & WEAP_INCENDIARY)) return; if (self.ammo_rockets < 3) { sprint (self, PRINT_HIGH, "not enough ammo.\n"); return; } self.current_weapon = WEAP_INCENDIARY; W_SetCurrentAmmo(); }; // Flamethrower selection function void() TeamFortress_FlameThrower = { if (!(self.weapons_carried & WEAP_FLAMETHROWER)) return; if (self.ammo_cells < 1) { sprint (self, PRINT_HIGH, "not enough ammo.\n"); return; } self.current_weapon = WEAP_FLAMETHROWER; W_SetCurrentAmmo(); };