]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
On second thought, add those prefixes back, it helps a bit
authorSamual Lenks <samual@xonotic.org>
Tue, 11 Jun 2013 00:04:06 +0000 (20:04 -0400)
committerSamual Lenks <samual@xonotic.org>
Tue, 11 Jun 2013 00:04:06 +0000 (20:04 -0400)
43 files changed:
qcsrc/common/weapons/all.qh
qcsrc/common/weapons/arc.qc [deleted file]
qcsrc/common/weapons/arc.qh [deleted file]
qcsrc/common/weapons/blaster.qc [deleted file]
qcsrc/common/weapons/crylink.qc [deleted file]
qcsrc/common/weapons/devastator.qc [deleted file]
qcsrc/common/weapons/electro.qc [deleted file]
qcsrc/common/weapons/electro.qh [deleted file]
qcsrc/common/weapons/fireball.qc [deleted file]
qcsrc/common/weapons/hagar.qc [deleted file]
qcsrc/common/weapons/hlac.qc [deleted file]
qcsrc/common/weapons/hook.qc [deleted file]
qcsrc/common/weapons/machinegun.qc [deleted file]
qcsrc/common/weapons/minelayer.qc [deleted file]
qcsrc/common/weapons/minstanex.qc [deleted file]
qcsrc/common/weapons/mortar.qc [deleted file]
qcsrc/common/weapons/nex.qc [deleted file]
qcsrc/common/weapons/porto.qc [deleted file]
qcsrc/common/weapons/rifle.qc [deleted file]
qcsrc/common/weapons/seeker.qc [deleted file]
qcsrc/common/weapons/shockwave.qc [deleted file]
qcsrc/common/weapons/tuba.qc [deleted file]
qcsrc/common/weapons/w_arc.qc [new file with mode: 0644]
qcsrc/common/weapons/w_arc.qh [new file with mode: 0644]
qcsrc/common/weapons/w_blaster.qc [new file with mode: 0644]
qcsrc/common/weapons/w_crylink.qc [new file with mode: 0644]
qcsrc/common/weapons/w_devastator.qc [new file with mode: 0644]
qcsrc/common/weapons/w_electro.qc [new file with mode: 0644]
qcsrc/common/weapons/w_electro.qh [new file with mode: 0644]
qcsrc/common/weapons/w_fireball.qc [new file with mode: 0644]
qcsrc/common/weapons/w_hagar.qc [new file with mode: 0644]
qcsrc/common/weapons/w_hlac.qc [new file with mode: 0644]
qcsrc/common/weapons/w_hook.qc [new file with mode: 0644]
qcsrc/common/weapons/w_machinegun.qc [new file with mode: 0644]
qcsrc/common/weapons/w_minelayer.qc [new file with mode: 0644]
qcsrc/common/weapons/w_minstanex.qc [new file with mode: 0644]
qcsrc/common/weapons/w_mortar.qc [new file with mode: 0644]
qcsrc/common/weapons/w_nex.qc [new file with mode: 0644]
qcsrc/common/weapons/w_porto.qc [new file with mode: 0644]
qcsrc/common/weapons/w_rifle.qc [new file with mode: 0644]
qcsrc/common/weapons/w_seeker.qc [new file with mode: 0644]
qcsrc/common/weapons/w_shockwave.qc [new file with mode: 0644]
qcsrc/common/weapons/w_tuba.qc [new file with mode: 0644]

index 37458a6ab6386d2b340d8cde005d902104ba15e0..de6266f3dfab84f6ad9b3ac9a291d186fca37b36 100644 (file)
@@ -3,24 +3,24 @@
 // IF YOU DISREGARD THIS NOTICE, I'LL KILL YOU WITH THE @!#%'N TUBA
 
 // core weapons
-#include "blaster.qc"
-#include "shockwave.qc"
-#include "machinegun.qc"
-#include "mortar.qc"
-#include "minelayer.qc"
-#include "electro.qc"
-#include "arc.qc"
-#include "crylink.qc"
-#include "nex.qc"
-#include "hagar.qc"
-#include "devastator.qc"
+#include "w_blaster.qc"
+#include "w_shockwave.qc"
+#include "w_machinegun.qc"
+#include "w_mortar.qc"
+#include "w_minelayer.qc"
+#include "w_electro.qc"
+#include "w_arc.qc"
+#include "w_crylink.qc"
+#include "w_nex.qc"
+#include "w_hagar.qc"
+#include "w_devastator.qc"
 
 // other weapons
-#include "porto.qc"
-#include "minstanex.qc"
-#include "hook.qc"
-#include "hlac.qc"
-#include "tuba.qc"
-#include "rifle.qc"
-#include "fireball.qc"
-#include "seeker.qc"
+#include "w_porto.qc"
+#include "w_minstanex.qc"
+#include "w_hook.qc"
+#include "w_hlac.qc"
+#include "w_tuba.qc"
+#include "w_rifle.qc"
+#include "w_fireball.qc"
+#include "w_seeker.qc"
diff --git a/qcsrc/common/weapons/arc.qc b/qcsrc/common/weapons/arc.qc
deleted file mode 100644 (file)
index e4b7230..0000000
+++ /dev/null
@@ -1,296 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ LIGHTNING,
-/* function  */ w_lightning,
-/* ammotype  */ IT_CELLS,
-/* impulse   */ 5,
-/* flags     */ WEP_FLAG_NORMAL | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "lightning",
-/* shortname */ "lightning",
-/* fullname  */ _("Lightning")
-);
-#else
-#ifdef SVQC
-
-// Declarations ========================= 
-.vector hook_start, hook_end; // used for beam
-.entity lightning_beam; // used for beam
-.float BUTTON_ATCK_prev; // for better animation control
-.float lg_fire_prev; // for better animation control
-
-// Lightning functions ========================= 
-float W_Lightning_Beam_Send(entity to, float sf)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_LIGHTNING_BEAM);
-       sf = sf & 0x7F;
-       if(sound_allowed(MSG_BROADCAST, self.owner))
-               sf |= 0x80;
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & 1)
-       {
-               WriteByte(MSG_ENTITY, num_for_edict(self.owner));
-               WriteCoord(MSG_ENTITY, autocvar_g_balance_lightning_primary_range);
-       }
-       if(sf & 2)
-       {
-               WriteCoord(MSG_ENTITY, self.hook_start_x);
-               WriteCoord(MSG_ENTITY, self.hook_start_y);
-               WriteCoord(MSG_ENTITY, self.hook_start_z);
-       }
-       if(sf & 4)
-       {
-               WriteCoord(MSG_ENTITY, self.hook_end_x);
-               WriteCoord(MSG_ENTITY, self.hook_end_y);
-               WriteCoord(MSG_ENTITY, self.hook_end_z);
-       }
-       return TRUE;
-}
-
-void W_Lightning_Beam_Think()
-{
-       self.owner.lg_fire_prev = time;
-       if (self != self.owner.lightning_beam)
-       {
-               remove(self);
-               return;
-       }
-       if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen)
-       {
-               if(self == self.owner.lightning_beam)
-                       self.owner.lightning_beam = world;
-               remove(self);
-               return;
-       }
-
-       self.nextthink = time;
-
-       makevectors(self.owner.v_angle);
-
-       float dt, f;
-       dt = frametime;
-       if not(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)
-       {
-               if(autocvar_g_balance_lightning_primary_ammo)
-               {
-                       dt = min(dt, self.owner.ammo_cells / autocvar_g_balance_lightning_primary_ammo);
-                       self.owner.ammo_cells = max(0, self.owner.ammo_cells - autocvar_g_balance_lightning_primary_ammo * frametime);
-               }
-       }
-
-       W_SetupShot_Range(self.owner, TRUE, 0, "", 0, autocvar_g_balance_lightning_primary_damage * dt, autocvar_g_balance_lightning_primary_range);
-       WarpZone_traceline_antilag(self.owner, w_shotorg, w_shotend, MOVE_NORMAL, self.owner, ANTILAG_LATENCY(self.owner));
-
-       // apply the damage
-       if(trace_ent)
-       {
-               vector force;
-               force = w_shotdir * autocvar_g_balance_lightning_primary_force;
-
-               f = ExponentialFalloff(autocvar_g_balance_lightning_primary_falloff_mindist, autocvar_g_balance_lightning_primary_falloff_maxdist, autocvar_g_balance_lightning_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg));
-
-               if(accuracy_isgooddamage(self.owner, trace_ent))
-                       accuracy_add(self.owner, WEP_LIGHTNING, 0, autocvar_g_balance_lightning_primary_damage * dt * f);
-               Damage (trace_ent, self.owner, self.owner, autocvar_g_balance_lightning_primary_damage * dt * f, WEP_LIGHTNING, trace_endpos, force * dt);
-       }
-
-       // draw effect
-       if(w_shotorg != self.hook_start)
-       {
-               self.SendFlags |= 2;
-               self.hook_start = w_shotorg;
-       }
-       if(w_shotend != self.hook_end)
-       {
-               self.SendFlags |= 4;
-               self.hook_end = w_shotend;
-       }
-}
-
-// Attack functions ========================= 
-void W_Lightning_Attack1 (void)
-{
-       // only play fire sound if 0.5 sec has passed since player let go the fire button
-       if(time - self.lg_fire_prev > 0.5)
-               sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
-
-       entity beam, oldself;
-
-       self.lightning_beam = beam = spawn();
-       beam.classname = "W_Lightning_Beam";
-       beam.solid = SOLID_NOT;
-       beam.think = W_Lightning_Beam_Think;
-       beam.owner = self;
-       beam.movetype = MOVETYPE_NONE;
-       beam.shot_spread = 1;
-       beam.bot_dodge = TRUE;
-       beam.bot_dodgerating = autocvar_g_balance_lightning_primary_damage;
-       Net_LinkEntity(beam, FALSE, 0, W_Lightning_Beam_Send);
-
-       oldself = self;
-       self = beam;
-       self.think();
-       self = oldself;
-}
-
-float w_lightning(float req)
-{
-       if (req == WR_AIM)
-       {
-               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
-               /*
-               self.BUTTON_ATCK=FALSE;
-               self.BUTTON_ATCK2=FALSE;
-               if(vlen(self.origin-self.enemy.origin) > 1000)
-                       self.bot_aim_whichfiretype = 0;
-               if(self.bot_aim_whichfiretype == 0)
-               {
-                       float shoot;
-
-                       if(autocvar_g_balance_lightning_primary_speed)
-                               shoot = bot_aim(autocvar_g_balance_lightning_primary_speed, 0, autocvar_g_balance_lightning_primary_lifetime, FALSE);
-                       else
-                               shoot = bot_aim(1000000, 0, 0.001, FALSE);
-
-                       if(shoot)
-                       {
-                               self.BUTTON_ATCK = TRUE;
-                               if(random() < 0.01) self.bot_aim_whichfiretype = 1;
-                       }
-               }
-               else // todo
-               {
-                       //if(bot_aim(autocvar_g_balance_lightning_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_lightning_secondary_lifetime, TRUE))
-                       //{
-                       //      self.BUTTON_ATCK2 = TRUE;
-                       //      if(random() < 0.03) self.bot_aim_whichfiretype = 0;
-                       //}
-               }
-               */
-       }
-       else if (req == WR_THINK)
-       {
-               if (self.BUTTON_ATCK)
-               {
-                       if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this!
-                               /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
-                                       weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_lightning_primary_animtime, w_ready);
-                               else*/
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready);
-                       
-                       if (weapon_prepareattack(0, 0))
-                       {
-                               if ((!self.lightning_beam) || wasfreed(self.lightning_beam))
-                                       W_Lightning_Attack1();
-                               
-                               if(!self.BUTTON_ATCK_prev)
-                               {
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready);
-                                       self.BUTTON_ATCK_prev = 1;
-                               }
-                       }
-               } 
-               else // todo
-               {
-                       if (self.BUTTON_ATCK_prev != 0)
-                       {
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready);
-                               ATTACK_FINISHED(self) = time + autocvar_g_balance_lightning_primary_refire * W_WeaponRateFactor();
-                       }
-                       self.BUTTON_ATCK_prev = 0;
-               }
-
-               //if (self.BUTTON_ATCK2)
-                       //if (weapon_prepareattack(1, autocvar_g_balance_lightning_secondary_refire))
-                       //{
-                       //      W_Lightning_Attack2();
-                       //      self.lightning_count = autocvar_g_balance_lightning_secondary_count;
-                       //      weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_lightning_secondary_animtime, w_lightning_checkattack);
-                       //      self.lightning_secondarytime = time + autocvar_g_balance_lightning_secondary_refire2 * W_WeaponRateFactor();
-                       //}
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_lightning.md3");
-               precache_model ("models/weapons/v_lightning.md3");
-               precache_model ("models/weapons/h_lightning.iqm");
-               //precache_sound ("weapons/lightning_bounce.wav");
-               precache_sound ("weapons/lightning_fire.wav");
-               precache_sound ("weapons/lightning_fire2.wav");
-               precache_sound ("weapons/lightning_impact.wav");
-               //precache_sound ("weapons/lightning_impact_combo.wav");
-               //precache_sound ("weapons/W_Lightning_Beam_fire.wav");
-       }
-       else if (req == WR_SETUP)
-               weapon_setup(WEP_LIGHTNING);
-       else if (req == WR_CHECKAMMO1)
-       {
-               return !autocvar_g_balance_lightning_primary_ammo || (self.ammo_cells > 0);
-       }
-       else if (req == WR_CHECKAMMO2)
-               return self.ammo_cells >= autocvar_g_balance_lightning_secondary_ammo;
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       return WEAPON_ELECTRO_MURDER_ORBS;
-               }
-               else
-               {
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                               return WEAPON_ELECTRO_MURDER_COMBO;
-                       else
-                               return WEAPON_ELECTRO_MURDER_BOLT;
-               }
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               //self.lightning_secondarytime = time;
-       }
-       return TRUE;
-};
-
-void LightningInit()
-{
-       weapon_action(WEP_LIGHTNING, WR_PRECACHE);
-       lightning_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 1);
-       lightning_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 2);
-       lightning_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 3);
-       lightning_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 4);
-}
-
-void spawnfunc_weapon_lightning (void) // should this really be here?
-{
-       weapon_defaultspawnfunc(WEP_LIGHTNING);
-}
-#endif
-#ifdef CSQC
-float w_lightning(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 6;
-               
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       pointparticles(particleeffectnum("lightning_ballexplode"), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM);
-               }
-               else
-               {
-                       pointparticles(particleeffectnum("lightning_impact"), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM);
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/lightning_impact.wav");
-               precache_sound("weapons/lightning_impact_combo.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/arc.qh b/qcsrc/common/weapons/arc.qh
deleted file mode 100644 (file)
index 57d6ceb..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-void LightningInit();
-vector lightning_shotorigin[4];
diff --git a/qcsrc/common/weapons/blaster.qc b/qcsrc/common/weapons/blaster.qc
deleted file mode 100644 (file)
index c331c5b..0000000
+++ /dev/null
@@ -1,576 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ LASER,
-/* function  */ W_Laser,
-/* ammotype  */ 0,
-/* impulse   */ 1,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ 0,
-/* model     */ "laser",
-/* shortname */ "laser",
-/* fullname  */ _("Blaster")
-);
-#else
-#ifdef SVQC
-void(float imp) W_SwitchWeapon;
-void() W_LastWeapon;
-.float swing_prev;
-.entity swing_alreadyhit;
-
-void SendCSQCShockwaveParticle(vector endpos) 
-{
-       //endpos = WarpZone_UnTransformOrigin(transform, endpos);
-       
-       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
-       WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE);
-       WriteCoord(MSG_BROADCAST, w_shotorg_x);
-       WriteCoord(MSG_BROADCAST, w_shotorg_y);
-       WriteCoord(MSG_BROADCAST, w_shotorg_z);
-       WriteCoord(MSG_BROADCAST, endpos_x);
-       WriteCoord(MSG_BROADCAST, endpos_y);
-       WriteCoord(MSG_BROADCAST, endpos_z);
-       WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_max, 255));
-       WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_min, 255));
-       WriteByte(MSG_BROADCAST, num_for_edict(self));
-}
-
-void W_Laser_Touch()
-{
-       PROJECTILE_TOUCH;
-
-       self.event_damage = func_null;
-       
-       if(self.dmg)
-               RadiusDamage(self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other);
-       else
-               RadiusDamage(self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other);
-
-       remove(self);
-}
-
-void W_Laser_Think()
-{
-       self.movetype = MOVETYPE_FLY;
-       self.think = SUB_Remove;
-       
-       if(self.dmg)
-               self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime;
-       else
-               self.nextthink = time + autocvar_g_balance_laser_primary_lifetime;
-               
-       CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE);
-}
-
-
-float W_Laser_Shockwave_CheckSpread(vector targetorg, vector nearest_on_line, vector sw_shotorg, vector attack_endpos)
-{
-       float spreadlimit;
-       float distance_of_attack = vlen(sw_shotorg - attack_endpos);
-       float distance_from_line = vlen(targetorg - nearest_on_line);
-       
-       spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1);
-       spreadlimit = (autocvar_g_balance_laser_shockwave_spread_min * (1 - spreadlimit) + autocvar_g_balance_laser_shockwave_spread_max * spreadlimit);
-       
-       if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90))
-               return bound(0, (distance_from_line / spreadlimit), 1);
-       else
-               return FALSE;
-}
-
-float W_Laser_Shockwave_IsVisible(entity head, vector nearest_on_line, vector sw_shotorg, vector attack_endpos)
-{
-       vector nearest_to_attacker = head.WarpZone_findradius_nearest;
-       vector center = (head.origin + (head.mins + head.maxs) * 0.5);
-       vector corner;
-       float i;
-
-       // STEP ONE: Check if the nearest point is clear
-       if(W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos))
-       {
-               WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self);
-               if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage
-       }
-
-       // STEP TWO: Check if shotorg to center point is clear
-       if(W_Laser_Shockwave_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos))
-       {
-               WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self);
-               if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage
-       }
-
-       // STEP THREE: Check each corner to see if they are clear
-       for(i=1; i<=8; ++i)
-       {
-               corner = get_corner_position(head, i);
-               if(W_Laser_Shockwave_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos))
-               {
-                       WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self);
-                       if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage
-               }
-       }
-
-       return FALSE;
-}
-
-#define PLAYER_CENTER(ent) (ent.origin + ((ent.classname == "player") ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
-
-entity shockwave_hit[32];
-float shockwave_hit_damage[32];
-vector shockwave_hit_force[32];
-
-float W_Laser_Shockwave_CheckHit(float queue, entity head, vector final_force, float final_damage)
-{
-       if not(head) { return FALSE; }
-       float i;
-
-       ++queue;
-       
-       for(i = 1; i <= queue; ++i)
-       {
-               if(shockwave_hit[i] == head)
-               {
-                       if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; }
-                       if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; }
-                       return FALSE;
-               }
-       }
-
-       shockwave_hit[queue] = head;
-       shockwave_hit_force[queue] = final_force;
-       shockwave_hit_damage[queue] = final_damage;
-       return TRUE;
-}
-
-void W_Laser_Shockwave()
-{
-       // declarations
-       float multiplier, multiplier_from_accuracy, multiplier_from_distance;
-       float final_damage; //, final_spread;
-       vector final_force, center, vel;
-       entity head, next;
-
-       float i, queue = 0;
-       
-       // set up the shot direction
-       W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_shockwave_damage);
-       vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_shockwave_distance));
-       WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self);
-       vector attack_hitpos = trace_endpos;
-       float distance_to_end = vlen(w_shotorg - attack_endpos);
-       float distance_to_hit = vlen(w_shotorg - attack_hitpos);
-       //entity transform = WarpZone_trace_transform;
-
-       // do the firing effect now
-       SendCSQCShockwaveParticle(attack_endpos);
-       Damage_DamageInfo(attack_hitpos, autocvar_g_balance_laser_shockwave_splash_damage, autocvar_g_balance_laser_shockwave_splash_edgedamage, autocvar_g_balance_laser_shockwave_splash_radius, w_shotdir * autocvar_g_balance_laser_shockwave_splash_force, WEP_LASER, 0, self);
-
-       // splash damage/jumping trace
-       head = WarpZone_FindRadius(attack_hitpos, max(autocvar_g_balance_laser_shockwave_splash_radius, autocvar_g_balance_laser_shockwave_jump_radius), FALSE);
-       while(head)
-       {
-               next = head.chain;
-
-               if(head.takedamage)
-               {
-                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
-                       center = PLAYER_CENTER(head);
-
-                       float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest);
-                       
-                       if((head == self) && (distance_to_head <= autocvar_g_balance_laser_shockwave_jump_radius))
-                       {
-                               multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_jump_radius)) : 0));
-                               multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0));
-                               multiplier = max(autocvar_g_balance_laser_shockwave_jump_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_jump_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_jump_multiplier_distance)));
-
-                               final_force = ((normalize(center - attack_hitpos) * autocvar_g_balance_laser_shockwave_jump_force) * multiplier);
-                               vel = head.velocity; vel_z = 0;
-                               vel = normalize(vel) * bound(0, vlen(vel) / autocvar_sv_maxspeed, 1) * autocvar_g_balance_laser_shockwave_jump_force_velocitybias;
-                               final_force = (vlen(final_force) * normalize(normalize(final_force) + vel));
-                               final_force_z *= autocvar_g_balance_laser_shockwave_jump_force_zscale;
-                               final_damage = (autocvar_g_balance_laser_shockwave_jump_damage * multiplier + autocvar_g_balance_laser_shockwave_jump_edgedamage * (1 - multiplier));
-
-                               Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force);
-                               //print("SELF HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n");
-                       }
-                       else if (distance_to_head <= autocvar_g_balance_laser_shockwave_splash_radius)
-                       {       
-                               multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_splash_radius)) : 0));
-                               multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0));
-                               multiplier = max(autocvar_g_balance_laser_shockwave_splash_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_splash_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_splash_multiplier_distance)));
-
-                               final_force = normalize(center - (attack_hitpos - (w_shotdir * autocvar_g_balance_laser_shockwave_splash_force_forwardbias)));
-                               //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200)));
-                               final_force = ((final_force * autocvar_g_balance_laser_shockwave_splash_force) * multiplier);
-                               final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale;
-                               final_damage = (autocvar_g_balance_laser_shockwave_splash_damage * multiplier + autocvar_g_balance_laser_shockwave_splash_edgedamage * (1 - multiplier));
-
-                               if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; }
-                               //print("SPLASH HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n");
-                       }
-               }
-               head = next;
-       }
-
-       // cone damage trace
-       head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_shockwave_distance, FALSE);
-       while(head)
-       {
-               next = head.chain;
-               
-               if((head != self) && head.takedamage)
-               {
-                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
-                       center = PLAYER_CENTER(head);
-
-                       // find the closest point on the enemy to the center of the attack
-                       float ang; // angle between shotdir and h
-                       float h; // hypotenuse, which is the distance between attacker to head
-                       float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
-                       
-                       h = vlen(center - self.origin);
-                       ang = acos(dotproduct(normalize(center - self.origin), w_shotdir));
-                       a = h * cos(ang);
-
-                       vector nearest_on_line = (w_shotorg + a * w_shotdir);
-                       vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
-                       float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this
-
-                       if((distance_to_target <= autocvar_g_balance_laser_shockwave_distance) 
-                               && (W_Laser_Shockwave_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos)))
-                       {
-                               multiplier_from_accuracy = (1 - W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_endpos));
-                               multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_target / distance_to_end)) : 0));
-                               multiplier = max(autocvar_g_balance_laser_shockwave_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_multiplier_distance)));
-
-                               final_force = normalize(center - (nearest_on_line - (w_shotdir * autocvar_g_balance_laser_shockwave_force_forwardbias)));
-                               //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200)));
-                               final_force = ((final_force * autocvar_g_balance_laser_shockwave_force) * multiplier);
-                               final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale;
-                               final_damage = (autocvar_g_balance_laser_shockwave_damage * multiplier + autocvar_g_balance_laser_shockwave_edgedamage * (1 - multiplier));
-
-                               if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; }
-                               //print("CONE HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n");
-                       }
-               }
-               head = next;
-       }
-
-       for(i = 1; i <= queue; ++i)
-       {
-               head = shockwave_hit[i];
-               final_force = shockwave_hit_force[i];
-               final_damage = shockwave_hit_damage[i];
-               
-               Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force);
-               print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n");
-               
-               shockwave_hit[i] = world;
-               shockwave_hit_force[i] = '0 0 0';
-               shockwave_hit_damage[i] = 0;
-       }
-       //print("queue was ", ftos(queue), ".\n\n");
-}
-
-void W_Laser_Melee_Think()
-{
-       // declarations
-       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
-       entity target_victim;
-       vector targpos;
-
-       if(!self.cnt) // set start time of melee
-       {
-               self.cnt = time; 
-               W_PlayStrengthSound(self.realowner);
-       }
-
-       makevectors(self.realowner.v_angle); // update values for v_* vectors
-       
-       // calculate swing percentage based on time
-       meleetime = autocvar_g_balance_laser_melee_time * W_WeaponRateFactor();
-       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
-       f = ((1 - swing) * autocvar_g_balance_laser_melee_traces);
-       
-       // check to see if we can still continue, otherwise give up now
-       if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_laser_melee_no_doubleslap)
-       {
-               remove(self);
-               return;
-       }
-       
-       // if okay, perform the traces needed for this frame 
-       for(i=self.swing_prev; i < f; ++i)
-       {
-               swing_factor = ((1 - (i / autocvar_g_balance_laser_melee_traces)) * 2 - 1);
-               
-               targpos = (self.realowner.origin + self.realowner.view_ofs 
-                       + (v_forward * autocvar_g_balance_laser_melee_range)
-                       + (v_up * swing_factor * autocvar_g_balance_laser_melee_swing_up)
-                       + (v_right * swing_factor * autocvar_g_balance_laser_melee_swing_side));
-
-               WarpZone_traceline_antilag(self.realowner, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, ANTILAG_LATENCY(self.realowner));
-               
-               // draw lightning beams for debugging
-               te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); 
-               te_customflash(targpos, 40,  2, '1 1 1');
-               
-               is_player = (trace_ent.classname == "player" || trace_ent.classname == "body");
-
-               if((trace_fraction < 1) // if trace is good, apply the damage and remove self
-                       && (trace_ent.takedamage == DAMAGE_AIM)  
-                       && (trace_ent != self.swing_alreadyhit)
-                       && (is_player || autocvar_g_balance_laser_melee_nonplayerdamage))
-               {
-                       target_victim = trace_ent; // so it persists through other calls
-                       
-                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
-                               swing_damage = (autocvar_g_balance_laser_melee_damage * min(1, swing_factor + 1));
-                       else
-                               swing_damage = (autocvar_g_balance_laser_melee_nonplayerdamage * min(1, swing_factor + 1));
-                       
-                       //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
-                       
-                       Damage(target_victim, self.realowner, self.realowner, 
-                               swing_damage, WEP_LASER | HITTYPE_SECONDARY, 
-                               self.realowner.origin + self.realowner.view_ofs, 
-                               v_forward * autocvar_g_balance_laser_melee_force);
-                               
-                       if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LASER, 0, swing_damage); }
-                       
-                       if(autocvar_g_balance_laser_melee_multihit) // allow multiple hits with one swing, but not against the same player twice.
-                       {
-                               self.swing_alreadyhit = target_victim;
-                               continue; // move along to next trace
-                       }
-                       else
-                       {
-                               remove(self);
-                               return;
-                       }
-               }
-       }
-       
-       if(time >= self.cnt + meleetime)
-       {
-               // melee is finished
-               remove(self);
-               return;
-       }
-       else
-       {
-               // set up next frame 
-               self.swing_prev = i;
-               self.nextthink = time;
-       }
-}
-
-void W_Laser_Melee()
-{
-       sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM);
-       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_melee_animtime, w_ready);
-
-       entity meleetemp;
-       meleetemp = spawn();
-       meleetemp.owner = meleetemp.realowner = self;
-       meleetemp.think = W_Laser_Melee_Think;
-       meleetemp.nextthink = time + autocvar_g_balance_laser_melee_delay * W_WeaponRateFactor();
-       W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_laser_melee_damage, autocvar_g_balance_laser_melee_range);
-}
-
-void W_Laser_Attack(float issecondary)
-{
-       entity missile;
-       vector s_forward;
-       float a;
-
-       a = autocvar_g_balance_laser_primary_shotangle;
-       s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD);
-
-       //if(nodamage)
-       //      W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0);
-       /*else*/if(issecondary == 1)
-               W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage);
-       else
-               W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage);
-       pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       missile = spawn();
-       missile.owner = missile.realowner = self;
-       missile.classname = "laserbolt";
-       missile.dmg = 0;
-       missile.bot_dodge = TRUE;
-       missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage;
-
-       PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_LASER;
-
-       setorigin(missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary);
-       missile.angles = vectoangles(missile.velocity);
-       //missile.glow_color = 250; // 244, 250
-       //missile.glow_size = 120;
-       missile.touch = W_Laser_Touch;
-
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH; 
-
-       missile.think = W_Laser_Think;
-       missile.nextthink = time + autocvar_g_balance_laser_primary_delay;
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-
-       if(time >= missile.nextthink)
-       {
-               entity oldself;
-               oldself = self;
-               self = missile;
-               self.think();
-               self = oldself;
-       }
-}
-
-void spawnfunc_weapon_laser(void)
-{
-       weapon_defaultspawnfunc(WEP_LASER);
-}
-
-float W_Laser(float request)
-{
-       switch(request)
-       {
-               case WR_AIM:
-               {
-                       if((autocvar_g_balance_laser_secondary == 2) && (vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_laser_melee_range))
-                               self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
-                       else
-                               self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE);
-                       return TRUE;
-               }
-               
-               case WR_THINK:
-               {
-                       if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload
-                               weapon_action(self.weapon, WR_RELOAD);
-                       else if(self.BUTTON_ATCK)
-                       {
-                               if(weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire))
-                               {
-                                       W_DecreaseAmmo(ammo_none, 1, TRUE);
-
-                                       if not(autocvar_g_balance_laser_primary)
-                                               W_Laser_Shockwave();
-                                       else
-                                               W_Laser_Attack(FALSE);
-
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready);
-                               }
-                       }
-                       else if(self.BUTTON_ATCK2)
-                       {
-                               switch(autocvar_g_balance_laser_secondary)
-                               {
-                                       case 0: // switch to last used weapon
-                                       {
-                                               if(self.switchweapon == WEP_LASER) // don't do this if already switching
-                                                       W_LastWeapon();
-
-                                               break;
-                                       }
-
-                                       case 1: // normal projectile secondary
-                                       {
-                                               if(weapon_prepareattack(1, autocvar_g_balance_laser_secondary_refire))
-                                               {
-                                                       W_DecreaseAmmo(ammo_none, 1, TRUE);
-                                                       W_Laser_Attack(TRUE);
-                                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
-                                               }
-
-                                               break;
-                                       }
-
-                                       case 2: // melee attack secondary
-                                       {
-                                               if(!self.crouch) // we are not currently crouching; this fixes an exploit where your melee anim is not visible, and besides wouldn't make much sense
-                                               if(weapon_prepareattack(1, autocvar_g_balance_laser_melee_refire))
-                                               {
-                                                       // attempt forcing playback of the anim by switching to another anim (that we never play) here...
-                                                       W_Laser_Melee();
-                                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_melee_animtime, w_ready);
-                                               }
-                                       }
-                               }
-                       }
-                       return TRUE;
-               }
-               
-               case WR_PRECACHE: 
-               {
-                       precache_model("models/weapons/g_laser.md3");
-                       precache_model("models/weapons/v_laser.md3");
-                       precache_model("models/weapons/h_laser.iqm");
-                       precache_sound("weapons/lasergun_fire.wav");
-                       return TRUE;
-               }
-               
-               case WR_SETUP:
-               {
-                       weapon_setup(WEP_LASER);
-                       self.current_ammo = ammo_none;
-                       return TRUE;
-               }
-               
-               case WR_CHECKAMMO1:
-               case WR_CHECKAMMO2:
-               {
-                       return TRUE; // laser has infinite ammo
-               }
-               
-               case WR_RELOAD:
-               {
-                       W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav");
-                       return TRUE;
-               }
-               
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_LASER_SUICIDE;
-               }
-               
-               case WR_KILLMESSAGE:
-               {
-                       return WEAPON_LASER_MURDER;
-               }
-       }
-       
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float W_Laser(float request)
-{
-       switch(request)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 6;
-                       pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1);
-                       if(!w_issilent) { sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); }
-                       return TRUE;
-               }
-               
-               case WR_PRECACHE:
-               {
-                       precache_sound("weapons/laserimpact.wav");
-                       return TRUE;
-               }
-       }
-       
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/crylink.qc b/qcsrc/common/weapons/crylink.qc
deleted file mode 100644 (file)
index 7cfc6c3..0000000
+++ /dev/null
@@ -1,736 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ CRYLINK,
-/* function  */ w_crylink,
-/* ammotype  */ IT_CELLS,
-/* impulse   */ 6,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "crylink",
-/* shortname */ "crylink",
-/* fullname  */ _("Crylink")
-);
-#else
-#ifdef SVQC
-.float gravity;
-.float crylink_waitrelease;
-.entity crylink_lastgroup;
-
-.entity queuenext;
-.entity queueprev;
-
-void W_Crylink_CheckLinks(entity e)
-{
-       float i;
-       entity p;
-
-       if(e == world)
-               error("W_Crylink_CheckLinks: entity is world");
-       if(e.classname != "spike" || wasfreed(e))
-               error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
-
-       p = e;
-       for(i = 0; i < 1000; ++i)
-       {
-               if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
-                       error("W_Crylink_CheckLinks: queue is inconsistent");
-               p = p.queuenext;
-               if(p == e)
-                       break;
-       }
-       if(i >= 1000)
-               error("W_Crylink_CheckLinks: infinite chain");
-}
-
-void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
-{
-       W_Crylink_CheckLinks(next);
-       if(me == own.crylink_lastgroup)
-               own.crylink_lastgroup = ((me == next) ? world : next);
-       prev.queuenext = next;
-       next.queueprev = prev;
-       me.classname = "spike_oktoremove";
-       if(me != next)
-               W_Crylink_CheckLinks(next);
-}
-
-void W_Crylink_Dequeue(entity e)
-{
-       W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext);
-}
-
-void W_Crylink_Reset(void)
-{
-       W_Crylink_Dequeue(self);
-       remove(self);
-}
-
-// force projectile to explode
-void W_Crylink_LinkExplode (entity e, entity e2)
-{
-       float a;
-
-       if(e == e2)
-               return;
-
-       a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
-
-       if(e == e.realowner.crylink_lastgroup)
-               e.realowner.crylink_lastgroup = world;
-
-       if(e.projectiledeathtype & HITTYPE_SECONDARY)
-               RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_damage * a, autocvar_g_balance_crylink_secondary_edgedamage * a, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * a, e.projectiledeathtype, other);
-       else
-               RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_damage * a, autocvar_g_balance_crylink_primary_edgedamage * a, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * a, e.projectiledeathtype, other);
-
-       W_Crylink_LinkExplode(e.queuenext, e2);
-
-       e.classname = "spike_oktoremove";
-       remove (e);
-}
-
-// adjust towards center
-// returns the origin where they will meet... and the time till the meeting is
-// stored in w_crylink_linkjoin_time.
-// could possibly network this origin and time, and display a special particle
-// effect when projectiles meet there :P
-// jspeed: MINIMUM jing speed
-// jtime: MAXIMUM jing time (0: none)
-float w_crylink_linkjoin_time;
-vector W_Crylink_LinkJoin(entity e, float jspeed, float jtime)
-{
-       vector avg_origin, avg_velocity;
-       vector targ_origin;
-       float avg_dist, n;
-       entity p;
-
-       // FIXME remove this debug code
-       W_Crylink_CheckLinks(e);
-
-       w_crylink_linkjoin_time = 0;
-
-       avg_origin = e.origin;
-       avg_velocity = e.velocity;
-       n = 1;
-       for(p = e; (p = p.queuenext) != e; )
-       {
-               avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
-               avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
-               ++n;
-       }
-       avg_origin *= (1.0 / n);
-       avg_velocity *= (1.0 / n);
-
-       if(n < 2)
-               return avg_origin; // nothing to do
-
-       // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
-       avg_dist = pow(vlen(e.origin - avg_origin), 2);
-       for(p = e; (p = p.queuenext) != e; )
-               avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2);
-       avg_dist *= (1.0 / n);
-       avg_dist = sqrt(avg_dist);
-
-       if(avg_dist == 0)
-               return avg_origin; // no change needed
-
-       if(jspeed == 0 && jtime == 0)
-       {
-               e.velocity = avg_velocity;
-               UpdateCSQCProjectile(e);
-               for(p = e; (p = p.queuenext) != e; )
-               {
-                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
-                       UpdateCSQCProjectile(p);
-               }
-               targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
-       }
-       else
-       {
-               if(jtime)
-               {
-                       if(jspeed)
-                               w_crylink_linkjoin_time = min(jtime, avg_dist / jspeed);
-                       else
-                               w_crylink_linkjoin_time = jtime;
-               }
-               else
-                       w_crylink_linkjoin_time = avg_dist / jspeed;
-               targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
-
-               e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
-               UpdateCSQCProjectile(e);
-               for(p = e; (p = p.queuenext) != e; )
-               {
-                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
-                       UpdateCSQCProjectile(p);
-               }
-
-               // analysis:
-               //   jspeed -> +infinity:
-               //      w_crylink_linkjoin_time -> +0
-               //      targ_origin -> avg_origin
-               //      p->velocity -> HUEG towards center
-               //   jspeed -> 0:
-               //      w_crylink_linkjoin_time -> +/- infinity
-               //      targ_origin -> avg_velocity * +/- infinity
-               //      p->velocity -> avg_velocity
-               //   jspeed -> -infinity:
-               //      w_crylink_linkjoin_time -> -0
-               //      targ_origin -> avg_origin
-               //      p->velocity -> HUEG away from center
-       }
-
-       W_Crylink_CheckLinks(e);
-
-       return targ_origin;
-}
-
-void W_Crylink_LinkJoinEffect_Think()
-{
-       // is there at least 2 projectiles very close?
-       entity e, p;
-       float n;
-       e = self.owner.crylink_lastgroup;
-       n = 0;
-       if(e)
-       {
-               if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime)
-                       ++n;
-               for(p = e; (p = p.queuenext) != e; )
-               {
-                       if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime)
-                               ++n;
-               }
-               if(n >= 2)
-               {
-                       if(e.projectiledeathtype & HITTYPE_SECONDARY)
-                       {
-                               if(autocvar_g_balance_crylink_secondary_joinexplode)
-                               {
-                                       n = n / autocvar_g_balance_crylink_secondary_shots;
-                                       RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_joinexplode_damage * n,
-                                                                       autocvar_g_balance_crylink_secondary_joinexplode_edgedamage * n,
-                                                                       autocvar_g_balance_crylink_secondary_joinexplode_radius * n, e.realowner, world,
-                                                                       autocvar_g_balance_crylink_secondary_joinexplode_force * n, e.projectiledeathtype, other);
-
-                                       pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n);
-                               }
-                       }
-                       else
-                       {
-                               if(autocvar_g_balance_crylink_primary_joinexplode)
-                               {
-                                       n = n / autocvar_g_balance_crylink_primary_shots;
-                                       RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_joinexplode_damage * n,
-                                                                       autocvar_g_balance_crylink_primary_joinexplode_edgedamage * n,
-                                                                       autocvar_g_balance_crylink_primary_joinexplode_radius * n, e.realowner, world,
-                                                                       autocvar_g_balance_crylink_primary_joinexplode_force * n, e.projectiledeathtype, other);
-
-                                       pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n);
-                               }
-                       }
-               }
-       }
-       remove(self);
-}
-
-float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
-{
-       entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, FALSE);
-       float hit_friendly = 0;
-       float hit_enemy = 0;
-
-       while(head)
-       {
-               if((head.takedamage != DAMAGE_NO) && (head.deadflag == DEAD_NO))
-               {
-                       if(IsDifferentTeam(head, projectile.realowner))
-                               ++hit_enemy;
-                       else
-                               ++hit_friendly;
-               }
-                       
-               head = head.chain;
-       }
-
-       return (hit_enemy ? FALSE : hit_friendly);
-}
-
-// NO bounce protection, as bounces are limited!
-void W_Crylink_Touch (void)
-{
-       float finalhit;
-       float f;
-       PROJECTILE_TOUCH;
-
-       float a;
-       a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
-
-       finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
-       if(finalhit)
-               f = 1;
-       else
-               f = autocvar_g_balance_crylink_primary_bouncedamagefactor;
-       if(a)
-               f *= a;
-
-       float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_primary_damage * f, autocvar_g_balance_crylink_primary_edgedamage * f, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * f, self.projectiledeathtype, other);
-       
-       if(totaldamage && ((autocvar_g_balance_crylink_primary_linkexplode == 2) || ((autocvar_g_balance_crylink_primary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_primary_radius))))
-       {
-               if(self == self.realowner.crylink_lastgroup)
-                       self.realowner.crylink_lastgroup = world;
-               W_Crylink_LinkExplode(self.queuenext, self);
-               self.classname = "spike_oktoremove";
-               remove (self);
-               return;
-       }
-       else if(finalhit)
-       {
-               // just unlink
-               W_Crylink_Dequeue(self);
-               remove(self);
-               return;
-       }
-       self.cnt = self.cnt - 1;
-       self.angles = vectoangles(self.velocity);
-       self.owner = world;
-       self.projectiledeathtype |= HITTYPE_BOUNCE;
-       // commented out as it causes a little hitch...
-       //if(proj.cnt == 0)
-       //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
-}
-
-void W_Crylink_Touch2 (void)
-{
-       float finalhit;
-       float f;
-       PROJECTILE_TOUCH;
-
-       float a;
-       a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
-
-       finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
-       if(finalhit)
-               f = 1;
-       else
-               f = autocvar_g_balance_crylink_secondary_bouncedamagefactor;
-       if(a)
-               f *= a;
-
-       float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_secondary_damage * f, autocvar_g_balance_crylink_secondary_edgedamage * f, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * f, self.projectiledeathtype, other);
-               
-       if(totaldamage && ((autocvar_g_balance_crylink_secondary_linkexplode == 2) || ((autocvar_g_balance_crylink_secondary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_secondary_radius))))
-       {
-               if(self == self.realowner.crylink_lastgroup)
-                       self.realowner.crylink_lastgroup = world;
-               W_Crylink_LinkExplode(self.queuenext, self);
-               self.classname = "spike_oktoremove";
-               remove (self);
-               return;
-       }
-       else if(finalhit)
-       {
-               // just unlink
-               W_Crylink_Dequeue(self);
-               remove(self);
-               return;
-       }
-       self.cnt = self.cnt - 1;
-       self.angles = vectoangles(self.velocity);
-       self.owner = world;
-       self.projectiledeathtype |= HITTYPE_BOUNCE;
-       // commented out as it causes a little hitch...
-       //if(proj.cnt == 0)
-       //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
-}
-
-void W_Crylink_Fadethink (void)
-{
-       W_Crylink_Dequeue(self);
-       remove(self);
-}
-
-void W_Crylink_Attack (void)
-{
-       float counter, shots;
-       entity proj, prevproj, firstproj;
-       vector s;
-       vector forward, right, up;
-       float maxdmg;
-
-       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_reload_ammo);
-
-       maxdmg = autocvar_g_balance_crylink_primary_damage*autocvar_g_balance_crylink_primary_shots;
-       maxdmg *= 1 + autocvar_g_balance_crylink_primary_bouncedamagefactor * autocvar_g_balance_crylink_primary_bounces;
-       if(autocvar_g_balance_crylink_primary_joinexplode)
-               maxdmg += autocvar_g_balance_crylink_primary_joinexplode_damage;
-
-       W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", CH_WEAPON_A, maxdmg);
-       forward = v_forward;
-       right = v_right;
-       up = v_up;
-
-       shots = autocvar_g_balance_crylink_primary_shots;
-       pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
-       proj = prevproj = firstproj = world;
-       for(counter = 0; counter < shots; ++counter)
-       {
-               proj = spawn ();
-               proj.reset = W_Crylink_Reset;
-               proj.realowner = proj.owner = self;
-               proj.classname = "spike";
-               proj.bot_dodge = TRUE;
-               proj.bot_dodgerating = autocvar_g_balance_crylink_primary_damage;
-               if(shots == 1) {
-                       proj.queuenext = proj;
-                       proj.queueprev = proj;
-               }
-               else if(counter == 0) { // first projectile, store in firstproj for now
-                       firstproj = proj;
-               }
-               else if(counter == shots - 1) { // last projectile, link up with first projectile
-                       prevproj.queuenext = proj;
-                       firstproj.queueprev = proj;
-                       proj.queuenext = firstproj;
-                       proj.queueprev = prevproj;
-               }
-               else { // else link up with previous projectile
-                       prevproj.queuenext = proj;
-                       proj.queueprev = prevproj;
-               }
-
-               prevproj = proj;
-
-               proj.movetype = MOVETYPE_BOUNCEMISSILE;
-               PROJECTILE_MAKETRIGGER(proj);
-               proj.projectiledeathtype = WEP_CRYLINK;
-               //proj.gravity = 0.001;
-
-               setorigin (proj, w_shotorg);
-               setsize(proj, '0 0 0', '0 0 0');
-
-
-               s = '0 0 0';
-               if (counter == 0)
-                       s = '0 0 0';
-               else
-               {
-                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
-                       s_y = v_forward_x;
-                       s_z = v_forward_y;
-               }
-               s = s * autocvar_g_balance_crylink_primary_spread * g_weaponspreadfactor;
-               W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_crylink_primary_speed, 0, 0, 0, FALSE);
-               proj.touch = W_Crylink_Touch;
-
-               proj.think = W_Crylink_Fadethink;
-               if(counter == 0)
-               {
-                       proj.fade_time = time + autocvar_g_balance_crylink_primary_middle_lifetime;
-                       proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_middle_fadetime;
-                       proj.nextthink = time + autocvar_g_balance_crylink_primary_middle_lifetime + autocvar_g_balance_crylink_primary_middle_fadetime;
-               }
-               else
-               {
-                       proj.fade_time = time + autocvar_g_balance_crylink_primary_other_lifetime;
-                       proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_other_fadetime;
-                       proj.nextthink = time + autocvar_g_balance_crylink_primary_other_lifetime + autocvar_g_balance_crylink_primary_other_fadetime;
-               }
-               proj.teleport_time = time + autocvar_g_balance_crylink_primary_joindelay;
-               proj.cnt = autocvar_g_balance_crylink_primary_bounces;
-               //proj.scale = 1 + 1 * proj.cnt;
-
-               proj.angles = vectoangles (proj.velocity);
-
-               //proj.glow_size = 20;
-
-               proj.flags = FL_PROJECTILE;
-    proj.missile_flags = MIF_SPLASH;
-    
-               CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
-
-               other = proj; MUTATOR_CALLHOOK(EditProjectile);
-       }
-       if(autocvar_g_balance_crylink_primary_joinspread != 0 || autocvar_g_balance_crylink_primary_jointime != 0)
-       {
-               self.crylink_lastgroup = proj;
-               W_Crylink_CheckLinks(proj);
-               self.crylink_waitrelease = 1;
-       }
-}
-
-void W_Crylink_Attack2 (void)
-{
-       float counter, shots;
-       entity proj, prevproj, firstproj;
-       vector s;
-       vector forward, right, up;
-       float maxdmg;
-
-       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_secondary_ammo, autocvar_g_balance_crylink_reload_ammo);
-
-       maxdmg = autocvar_g_balance_crylink_secondary_damage*autocvar_g_balance_crylink_secondary_shots;
-       maxdmg *= 1 + autocvar_g_balance_crylink_secondary_bouncedamagefactor * autocvar_g_balance_crylink_secondary_bounces;
-       if(autocvar_g_balance_crylink_secondary_joinexplode)
-               maxdmg += autocvar_g_balance_crylink_secondary_joinexplode_damage;
-
-       W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", CH_WEAPON_A, maxdmg);
-       forward = v_forward;
-       right = v_right;
-       up = v_up;
-
-       shots = autocvar_g_balance_crylink_secondary_shots;
-       pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
-       proj = prevproj = firstproj = world;
-       for(counter = 0; counter < shots; ++counter)
-       {
-               proj = spawn ();
-               proj.reset = W_Crylink_Reset;
-               proj.realowner = proj.owner = self;
-               proj.classname = "spike";
-               proj.bot_dodge = TRUE;
-               proj.bot_dodgerating = autocvar_g_balance_crylink_secondary_damage;
-               if(shots == 1) {
-                       proj.queuenext = proj;
-                       proj.queueprev = proj;
-               }
-               else if(counter == 0) { // first projectile, store in firstproj for now
-                       firstproj = proj;
-               }
-               else if(counter == shots - 1) { // last projectile, link up with first projectile
-                       prevproj.queuenext = proj;
-                       firstproj.queueprev = proj;
-                       proj.queuenext = firstproj;
-                       proj.queueprev = prevproj;
-               }
-               else { // else link up with previous projectile
-                       prevproj.queuenext = proj;
-                       proj.queueprev = prevproj;
-               }
-
-               prevproj = proj;
-
-               proj.movetype = MOVETYPE_BOUNCEMISSILE;
-               PROJECTILE_MAKETRIGGER(proj);
-               proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY;
-               //proj.gravity = 0.001;
-
-               setorigin (proj, w_shotorg);
-               setsize(proj, '0 0 0', '0 0 0');
-
-               if(autocvar_g_balance_crylink_secondary_spreadtype == 1)
-               {
-                       s = '0 0 0';
-                       if (counter == 0)
-                               s = '0 0 0';
-                       else
-                       {
-                               makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
-                               s_y = v_forward_x;
-                               s_z = v_forward_y;
-                       }
-                       s = s * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor;
-                       s = w_shotdir + right * s_y + up * s_z;
-               }
-               else
-               {
-                       s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor);
-               }
-
-               W_SetupProjectileVelocityEx(proj, s, v_up, autocvar_g_balance_crylink_secondary_speed, 0, 0, 0, FALSE);
-               proj.touch = W_Crylink_Touch2;
-               proj.think = W_Crylink_Fadethink;
-               if(counter == (shots - 1) / 2)
-               {
-                       proj.fade_time = time + autocvar_g_balance_crylink_secondary_middle_lifetime;
-                       proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_middle_fadetime;
-                       proj.nextthink = time + autocvar_g_balance_crylink_secondary_middle_lifetime + autocvar_g_balance_crylink_secondary_middle_fadetime;
-               }
-               else
-               {
-                       proj.fade_time = time + autocvar_g_balance_crylink_secondary_line_lifetime;
-                       proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_line_fadetime;
-                       proj.nextthink = time + autocvar_g_balance_crylink_secondary_line_lifetime + autocvar_g_balance_crylink_secondary_line_fadetime;
-               }
-               proj.teleport_time = time + autocvar_g_balance_crylink_secondary_joindelay;
-               proj.cnt = autocvar_g_balance_crylink_secondary_bounces;
-               //proj.scale = 1 + 1 * proj.cnt;
-
-               proj.angles = vectoangles (proj.velocity);
-
-               //proj.glow_size = 20;
-
-               proj.flags = FL_PROJECTILE;
-        proj.missile_flags = MIF_SPLASH;
-        
-               CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
-
-               other = proj; MUTATOR_CALLHOOK(EditProjectile);
-       }
-       if(autocvar_g_balance_crylink_secondary_joinspread != 0 || autocvar_g_balance_crylink_secondary_jointime != 0)
-       {
-               self.crylink_lastgroup = proj;
-               W_Crylink_CheckLinks(proj);
-               self.crylink_waitrelease = 2;
-       }
-}
-
-void spawnfunc_weapon_crylink (void)
-{
-       weapon_defaultspawnfunc(WEP_CRYLINK);
-}
-
-float w_crylink(float req)
-{
-       float ammo_amount;
-       if (req == WR_AIM)
-       {
-               if (random() < 0.10)
-                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_crylink_primary_speed, 0, autocvar_g_balance_crylink_primary_middle_lifetime, FALSE);
-               else
-                       self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_crylink_secondary_speed, 0, autocvar_g_balance_crylink_secondary_middle_lifetime, FALSE);
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-
-               if (self.BUTTON_ATCK)
-               {
-                       if (self.crylink_waitrelease != 1)
-                       if (weapon_prepareattack(0, autocvar_g_balance_crylink_primary_refire))
-                       {
-                               W_Crylink_Attack();
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_crylink_primary_animtime, w_ready);
-                       }
-               }
-
-               if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary)
-               {
-                       if (self.crylink_waitrelease != 2)
-                       if (weapon_prepareattack(1, autocvar_g_balance_crylink_secondary_refire))
-                       {
-                               W_Crylink_Attack2();
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_crylink_secondary_animtime, w_ready);
-                       }
-               }
-
-               if ((self.crylink_waitrelease == 1 && !self.BUTTON_ATCK) || (self.crylink_waitrelease == 2 && !self.BUTTON_ATCK2))
-               {
-                       if (!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time)
-                       {
-                               // fired and released now!
-                               if(self.crylink_lastgroup)
-                               {
-                                       vector pos;
-                                       entity linkjoineffect;
-
-                                       if(self.crylink_waitrelease == 1)
-                                       {
-                                               pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_primary_joinspread * autocvar_g_balance_crylink_primary_speed, autocvar_g_balance_crylink_primary_jointime);
-
-                                       }
-                                       else
-                                       {
-                                               pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_secondary_joinspread * autocvar_g_balance_crylink_secondary_speed, autocvar_g_balance_crylink_secondary_jointime);
-                                       }
-
-                                       linkjoineffect = spawn();
-                                       linkjoineffect.think = W_Crylink_LinkJoinEffect_Think;
-                                       linkjoineffect.classname = "linkjoineffect";
-                                       linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
-                                       linkjoineffect.owner = self;
-                                       setorigin(linkjoineffect, pos);
-                               }
-                               self.crylink_waitrelease = 0;
-                               if(!w_crylink(WR_CHECKAMMO1) && !w_crylink(WR_CHECKAMMO2))
-                               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-                               {
-                                       // ran out of ammo!
-                                       self.cnt = WEP_CRYLINK;
-                                       self.switchweapon = w_getbestweapon(self);
-                               }
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_crylink.md3");
-               precache_model ("models/weapons/v_crylink.md3");
-               precache_model ("models/weapons/h_crylink.iqm");
-               precache_sound ("weapons/crylink_fire.wav");
-               precache_sound ("weapons/crylink_fire2.wav");
-               precache_sound ("weapons/crylink_linkjoin.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_CRYLINK);
-               self.current_ammo = ammo_cells;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               // don't "run out of ammo" and switch weapons while waiting for release
-               if(self.crylink_lastgroup && self.crylink_waitrelease)
-                       return TRUE;
-
-               ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_primary_ammo;
-               ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_primary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               // don't "run out of ammo" and switch weapons while waiting for release
-               if(self.crylink_lastgroup && self.crylink_waitrelease)
-                       return TRUE;
-
-               ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_secondary_ammo;
-               ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_secondary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo), autocvar_g_balance_crylink_reload_ammo, autocvar_g_balance_crylink_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_CRYLINK_SUICIDE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               return WEAPON_CRYLINK_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_crylink(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 2;
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM);
-               }
-               else
-               {
-                       pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM);
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/crylink_impact2.wav");
-               precache_sound("weapons/crylink_impact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/devastator.qc b/qcsrc/common/weapons/devastator.qc
deleted file mode 100644 (file)
index 504167c..0000000
+++ /dev/null
@@ -1,491 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ ROCKET_LAUNCHER,
-/* function  */ w_rlauncher,
-/* ammotype  */ IT_ROCKETS,
-/* impulse   */ 9,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* model     */ "rl",
-/* shortname */ "rocketlauncher",
-/* fullname  */ _("Rocket Launcher")
-);
-#else
-#ifdef SVQC
-.float rl_release;
-.float rl_detonate_later;
-
-void W_Rocket_Unregister()
-{
-       if(self.realowner && self.realowner.lastrocket == self)
-       {
-               self.realowner.lastrocket = world;
-               // self.realowner.rl_release = 1;
-       }
-}
-
-void W_Rocket_Explode ()
-{
-       W_Rocket_Unregister();
-
-       if(other.takedamage == DAMAGE_AIM)
-               if(IS_PLAYER(other))
-                       if(IsDifferentTeam(self.realowner, other))
-                               if(other.deadflag == DEAD_NO)
-                                       if(IsFlying(other))
-                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other);
-
-       if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
-       {
-               if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
-               {
-                       self.realowner.cnt = WEP_ROCKET_LAUNCHER;
-                       ATTACK_FINISHED(self.realowner) = time;
-                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
-               }
-       }
-       remove (self);
-}
-
-void W_Rocket_DoRemoteExplode ()
-{
-       W_Rocket_Unregister();
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world);
-
-       if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
-       {
-               if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
-               {
-                       self.realowner.cnt = WEP_ROCKET_LAUNCHER;
-                       ATTACK_FINISHED(self.realowner) = time;
-                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
-               }
-       }
-       remove (self);
-}
-
-void W_Rocket_RemoteExplode()
-{
-       if(self.realowner.deadflag == DEAD_NO)
-       if(self.realowner.lastrocket)
-       {
-               if((self.spawnshieldtime >= 0)
-                       ? (time >= self.spawnshieldtime) // timer
-                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device
-               )
-               {
-                       W_Rocket_DoRemoteExplode();
-               }
-       }
-}
-
-vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos)
-{
-       if(thisdir * goaldir > maxturn_cos)
-               return goaldir;
-       if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
-               return thisdir; // refuse to guide (better than letting a numerical error happen)
-       float f, m2;
-       vector v;
-       // solve:
-       //   g = normalize(thisdir + goaldir * X)
-       //   thisdir * g = maxturn
-       //
-       //   gg = thisdir + goaldir * X
-       //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
-       //
-       //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
-       f = thisdir * goaldir;
-       //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
-       //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
-       m2 = maxturn_cos * maxturn_cos;
-       v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
-       return normalize(thisdir + goaldir * v_y); // the larger solution!
-}
-// assume thisdir == -goaldir:
-//   f == -1
-//   v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
-//   (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
-//   x^2 - 2 * x + 1 = 0
-//   (x - 1)^2 = 0
-//   x = 1
-//   normalize(thisdir + goaldir)
-//   normalize(0)
-
-void W_Rocket_Think (void)
-{
-       vector desireddir, olddir, newdir, desiredorigin, goal;
-#if 0
-       float cosminang, cosmaxang, cosang;
-#endif
-       float velspeed, f;
-       self.nextthink = time;
-       if (time > self.cnt)
-       {
-               other = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               W_Rocket_Explode ();
-               return;
-       }
-
-       // accelerate
-       makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
-       velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward);
-       if (velspeed > 0)
-               self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed);
-
-       // laser guided, or remote detonation
-       if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
-       {
-               if(self == self.realowner.lastrocket)
-               if not(self.realowner.rl_release)
-               if not(self.BUTTON_ATCK2)
-               if(autocvar_g_balance_rocketlauncher_guiderate)
-               if(time > self.pushltime)
-               if(self.realowner.deadflag == DEAD_NO)
-               {
-                       f = autocvar_g_balance_rocketlauncher_guideratedelay;
-                       if(f)
-                               f = bound(0, (time - self.pushltime) / f, 1);
-                       else
-                               f = 1;
-
-                       velspeed = vlen(self.velocity);
-
-                       makevectors(self.realowner.v_angle);
-                       desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward);
-                       desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs);
-                       olddir = normalize(self.velocity);
-
-                       // now it gets tricky... we want to move like some curve to approximate the target direction
-                       // but we are limiting the rate at which we can turn!
-                       goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir;
-                       newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD));
-
-                       self.velocity = newdir * velspeed;
-                       self.angles = vectoangles(self.velocity);
-
-                       if(!self.count)
-                       {
-                               pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1);
-                               // TODO add a better sound here
-                               sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM);
-                               self.count = 1;
-                       }
-               }
-
-               if(self.rl_detonate_later)
-                       W_Rocket_RemoteExplode();
-       }
-
-       if(self.csqcprojectile_clientanimate == 0)
-               UpdateCSQCProjectile(self);
-}
-
-void W_Rocket_Touch (void)
-{
-       if(WarpZone_Projectile_Touch())
-       {
-               if(wasfreed(self))
-                       W_Rocket_Unregister();
-               return;
-       }
-       W_Rocket_Unregister();
-       W_Rocket_Explode ();
-}
-
-void W_Rocket_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-       
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-               
-       self.health = self.health - damage;
-       self.angles = vectoangles(self.velocity);
-       
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, W_Rocket_Explode);
-}
-
-void W_Rocket_Attack (void)
-{
-       entity missile;
-       entity flash;
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo);
-
-       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_balance_rocketlauncher_damage);
-       pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       missile = WarpZone_RefSys_SpawnSameRefSys(self);
-       missile.owner = missile.realowner = self;
-       self.lastrocket = missile;
-       if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0)
-               missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay;
-       else
-               missile.spawnshieldtime = -1;
-       missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay;
-       missile.classname = "rocket";
-       missile.bot_dodge = TRUE;
-       missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
-
-       missile.takedamage = DAMAGE_YES;
-       missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale;
-       missile.health = autocvar_g_balance_rocketlauncher_health;
-       missile.event_damage = W_Rocket_Damage;
-       missile.damagedbycontents = TRUE;
-
-       missile.movetype = MOVETYPE_FLY;
-       PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_ROCKET_LAUNCHER;
-       setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
-
-       setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
-       W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0);
-       missile.angles = vectoangles (missile.velocity);
-
-       missile.touch = W_Rocket_Touch;
-       missile.think = W_Rocket_Think;
-       missile.nextthink = time;
-       missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime;
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH; 
-
-       CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound
-
-       // muzzle flash for 1st person view
-       flash = spawn ();
-       setmodel (flash, "models/flash.md3"); // precision set below
-       SUB_SetFade (flash, time, 0.1);
-       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
-
-       // common properties
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc
-
-float w_rlauncher(float req)
-{
-       entity rock;
-       float rockfound;
-       float ammo_amount;
-
-       if (req == WR_AIM)
-       {
-               // aim and decide to fire if appropriate
-               self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE);
-               if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
-               {
-                       // decide whether to detonate rockets
-                       entity missile, targetlist, targ;
-                       float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
-                       float selfdamage, teamdamage, enemydamage;
-                       edgedamage = autocvar_g_balance_rocketlauncher_edgedamage;
-                       coredamage = autocvar_g_balance_rocketlauncher_damage;
-                       edgeradius = autocvar_g_balance_rocketlauncher_radius;
-                       recipricoledgeradius = 1 / edgeradius;
-                       selfdamage = 0;
-                       teamdamage = 0;
-                       enemydamage = 0;
-                       targetlist = findchainfloat(bot_attack, TRUE);
-                       missile = find(world, classname, "rocket");
-                       while (missile)
-                       {
-                               if (missile.realowner != self)
-                               {
-                                       missile = find(missile, classname, "rocket");
-                                       continue;
-                               }
-                               targ = targetlist;
-                               while (targ)
-                               {
-                                       d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin);
-                                       d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
-                                       // count potential damage according to type of target
-                                       if (targ == self)
-                                               selfdamage = selfdamage + d;
-                                       else if (targ.team == self.team && teamplay)
-                                               teamdamage = teamdamage + d;
-                                       else if (bot_shouldattack(targ))
-                                               enemydamage = enemydamage + d;
-                                       targ = targ.chain;
-                               }
-                               missile = find(missile, classname, "rocket");
-                       }
-                       float desirabledamage;
-                       desirabledamage = enemydamage;
-                       if (time > self.invincible_finished && time > self.spawnshieldtime)
-                               desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
-                       if (teamplay && self.team)
-                               desirabledamage = desirabledamage - teamdamage;
-
-                       missile = find(world, classname, "rocket");
-                       while (missile)
-                       {
-                               if (missile.realowner != self)
-                               {
-                                       missile = find(missile, classname, "rocket");
-                                       continue;
-                               }
-                               makevectors(missile.v_angle);
-                               targ = targetlist;
-                               if (skill > 9) // normal players only do this for the target they are tracking
-                               {
-                                       targ = targetlist;
-                                       while (targ)
-                                       {
-                                               if (
-                                                       (v_forward * normalize(missile.origin - targ.origin)< 0.1)
-                                                       && desirabledamage > 0.1*coredamage
-                                               )self.BUTTON_ATCK2 = TRUE;
-                                               targ = targ.chain;
-                                       }
-                               }else{
-                                       float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
-                                       //As the distance gets larger, a correct detonation gets near imposible
-                                       //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player
-                                       if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1)
-                                               if(IS_PLAYER(self.enemy))
-                                                       if(desirabledamage >= 0.1*coredamage)
-                                                               if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
-                                                                       self.BUTTON_ATCK2 = TRUE;
-                               //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
-                               }
-
-                               missile = find(missile, classname, "rocket");
-                       }
-                       // if we would be doing at X percent of the core damage, detonate it
-                       // but don't fire a new shot at the same time!
-                       if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
-                               self.BUTTON_ATCK2 = TRUE;
-                       if ((skill > 6.5) && (selfdamage > self.health))
-                               self.BUTTON_ATCK2 = FALSE;
-                       //if(self.BUTTON_ATCK2 == TRUE)
-                       //      dprint(ftos(desirabledamage),"\n");
-                       if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else
-               {
-                       if (self.BUTTON_ATCK)
-                       {
-                               if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop)
-                               if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire))
-                               {
-                                       W_Rocket_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready);
-                                       self.rl_release = 0;
-                               }
-                       }
-                       else
-                               self.rl_release = 1;
-
-                       if (self.BUTTON_ATCK2)
-                       {
-                               rockfound = 0;
-                               for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self)
-                               {
-                                       if(!rock.rl_detonate_later)
-                                       {
-                                               rock.rl_detonate_later = TRUE;
-                                               rockfound = 1;
-                                       }
-                               }
-                               if(rockfound)
-                                       sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/flash.md3");
-               precache_model ("models/weapons/g_rl.md3");
-               precache_model ("models/weapons/v_rl.md3");
-               precache_model ("models/weapons/h_rl.iqm");
-               precache_sound ("weapons/rocket_det.wav");
-               precache_sound ("weapons/rocket_fire.wav");
-               precache_sound ("weapons/rocket_mode.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_ROCKET_LAUNCHER);
-               self.current_ammo = ammo_rockets;
-               self.rl_release = 1;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               // don't switch while guiding a missile
-               if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER)
-               {
-                       ammo_amount = FALSE;
-                       if(autocvar_g_balance_rocketlauncher_reload_ammo)
-                       {
-                               if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo && self.(weapon_load[WEP_ROCKET_LAUNCHER]) < autocvar_g_balance_rocketlauncher_ammo)
-                                       ammo_amount = TRUE;
-                       }
-                       else if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
-                               ammo_amount = TRUE;
-                       return !ammo_amount;
-               }
-       }
-       else if (req == WR_CHECKAMMO2)
-               return FALSE;
-       else if (req == WR_RESETPLAYER)
-       {
-               self.rl_release = 0;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo, autocvar_g_balance_rocketlauncher_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_ROCKETLAUNCHER_SUICIDE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
-                       return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH;
-               else
-                       return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_rlauncher(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 12;
-               pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
-               if(!w_issilent)
-                       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/rocket_impact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/electro.qc b/qcsrc/common/weapons/electro.qc
deleted file mode 100644 (file)
index bfd9ebe..0000000
+++ /dev/null
@@ -1,619 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ ELECTRO,
-/* function  */ w_electro,
-/* ammotype  */ IT_CELLS,
-/* impulse   */ 5,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "electro",
-/* shortname */ "electro",
-/* fullname  */ _("Electro")
-);
-#else
-#ifdef SVQC
-.float electro_count;
-.float electro_secondarytime;
-
-void W_Plasma_Explode_Combo (void);
-
-void W_Plasma_TriggerCombo(vector org, float rad, entity own)
-{
-       entity e;
-       e = WarpZone_FindRadius(org, rad, TRUE);
-       while (e)
-       {
-               if (e.classname == "plasma")
-               {
-                       // change owner to whoever caused the combo explosion
-                       e.realowner = own;
-                       e.takedamage = DAMAGE_NO;
-                       e.classname = "plasma_chain";
-                       e.think = W_Plasma_Explode_Combo;
-                       e.nextthink = time + vlen(e.WarpZone_findradius_dist) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
-               }
-               e = e.chain;
-       }
-}
-
-void W_Plasma_Explode (void)
-{
-       if(other.takedamage == DAMAGE_AIM)
-               if(IS_PLAYER(other))
-                       if(IsDifferentTeam(self.realowner, other))
-                               if(other.deadflag == DEAD_NO)
-                                       if(IsFlying(other))
-                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-       if (self.movetype == MOVETYPE_BOUNCE)
-       {
-               RadiusDamage (self, self.realowner, autocvar_g_balance_electro_secondary_damage, autocvar_g_balance_electro_secondary_edgedamage, autocvar_g_balance_electro_secondary_radius, world, world, autocvar_g_balance_electro_secondary_force, self.projectiledeathtype, other);
-       }
-       else
-       {
-               W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_primary_comboradius, self.realowner);
-               RadiusDamage (self, self.realowner, autocvar_g_balance_electro_primary_damage, autocvar_g_balance_electro_primary_edgedamage, autocvar_g_balance_electro_primary_radius, world, world, autocvar_g_balance_electro_primary_force, self.projectiledeathtype, other);
-       }
-
-       remove (self);
-}
-
-void W_Plasma_Explode_Combo (void)
-{
-       W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_combo_comboradius, self.realowner);
-
-       self.event_damage = func_null;
-       RadiusDamage (self, self.realowner, autocvar_g_balance_electro_combo_damage, autocvar_g_balance_electro_combo_edgedamage, autocvar_g_balance_electro_combo_radius, world, world, autocvar_g_balance_electro_combo_force, WEP_ELECTRO | HITTYPE_BOUNCE, world); // use THIS type for a combo because primary can't bounce
-
-       remove (self);
-}
-
-void W_Plasma_Touch (void)
-{
-       //self.velocity = self.velocity  * 0.1;
-
-       PROJECTILE_TOUCH;
-       if (other.takedamage == DAMAGE_AIM) {
-               W_Plasma_Explode ();
-       } else {
-               //UpdateCSQCProjectile(self);
-               spamsound (self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTN_NORM);
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-       }
-}
-
-void W_Plasma_TouchExplode (void)
-{
-       PROJECTILE_TOUCH;
-       W_Plasma_Explode ();
-}
-
-void W_Plasma_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-
-       // note: combos are usually triggered by W_Plasma_TriggerCombo, not damage
-       float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim");
-       
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
-               return; // g_projectiles_damage says to halt    
-       
-       self.health = self.health - damage;
-       if (self.health <= 0)
-       {
-               self.takedamage = DAMAGE_NO;
-               self.nextthink = time;
-               if (is_combo)
-               {
-                       // change owner to whoever caused the combo explosion
-                       self.realowner = inflictor.realowner;
-                       self.classname = "plasma_chain";
-                       self.think = W_Plasma_Explode_Combo;
-                       self.nextthink = time + min(autocvar_g_balance_electro_combo_radius, vlen(self.origin - inflictor.origin)) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
-                               //                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bounding the length, because inflictor may be in a galaxy far far away (warpzones)
-               }
-               else
-               {
-                       self.use = W_Plasma_Explode;
-                       self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately"
-               }
-       }
-}
-
-void W_Electro_Attack()
-{
-       entity proj;
-
-       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_reload_ammo);
-
-       W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, "weapons/electro_fire.wav", CH_WEAPON_A, autocvar_g_balance_electro_primary_damage);
-
-       pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       proj = spawn ();
-       proj.classname = "plasma_prim";
-       proj.owner = proj.realowner = self;
-       proj.bot_dodge = TRUE;
-       proj.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
-       proj.use = W_Plasma_Explode;
-       proj.think = adaptor_think2use_hittype_splash;
-       proj.nextthink = time + autocvar_g_balance_electro_primary_lifetime;
-       PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_ELECTRO;
-       setorigin(proj, w_shotorg);
-
-       proj.movetype = MOVETYPE_FLY;
-       W_SETUPPROJECTILEVELOCITY(proj, g_balance_electro_primary);
-       proj.angles = vectoangles(proj.velocity);
-       proj.touch = W_Plasma_TouchExplode;
-       setsize(proj, '0 0 -3', '0 0 -3');
-       proj.flags = FL_PROJECTILE;
-       proj.missile_flags = MIF_SPLASH;
-
-       CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO_BEAM, TRUE);
-
-       other = proj; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void W_Electro_Attack2()
-{
-       entity proj;
-
-       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_secondary_ammo, autocvar_g_balance_electro_reload_ammo);
-
-       W_SetupShot_ProjectileSize (self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, autocvar_g_balance_electro_secondary_damage);
-
-       w_shotdir = v_forward; // no TrueAim for grenades please
-
-       pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       proj = spawn ();
-       proj.classname = "plasma";
-       proj.owner = proj.realowner = self;
-       proj.use = W_Plasma_Explode;
-       proj.think = adaptor_think2use_hittype_splash;
-       proj.bot_dodge = TRUE;
-       proj.bot_dodgerating = autocvar_g_balance_electro_secondary_damage;
-       proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime;
-       PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY;
-       setorigin(proj, w_shotorg);
-
-       //proj.glow_size = 50;
-       //proj.glow_color = 45;
-       proj.movetype = MOVETYPE_BOUNCE;
-       W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary);
-       proj.touch = W_Plasma_Touch;
-       setsize(proj, '0 0 -4', '0 0 -4');
-       proj.takedamage = DAMAGE_YES;
-       proj.damageforcescale = autocvar_g_balance_electro_secondary_damageforcescale;
-       proj.health = autocvar_g_balance_electro_secondary_health;
-       proj.event_damage = W_Plasma_Damage;
-       proj.flags = FL_PROJECTILE;
-       proj.damagedbycontents = (autocvar_g_balance_electro_secondary_damagedbycontents);
-
-       proj.bouncefactor = autocvar_g_balance_electro_secondary_bouncefactor;
-       proj.bouncestop = autocvar_g_balance_electro_secondary_bouncestop;
-       proj.missile_flags = MIF_SPLASH | MIF_ARC;
-
-#if 0
-       entity p2;
-       p2 = spawn();
-       copyentity(proj, p2);
-       setmodel(p2, "models/ebomb.mdl");
-       setsize(p2, proj.mins, proj.maxs);
-#endif
-
-       CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound
-
-       other = proj; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-.vector hook_start, hook_end;
-float lgbeam_send(entity to, float sf)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_ELECTRO_BEAM);
-       sf = sf & 0x7F;
-       if(sound_allowed(MSG_BROADCAST, self.realowner))
-               sf |= 0x80;
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & 1)
-       {
-               WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
-               WriteCoord(MSG_ENTITY, autocvar_g_balance_electro_primary_range);
-       }
-       if(sf & 2)
-       {
-               WriteCoord(MSG_ENTITY, self.hook_start_x);
-               WriteCoord(MSG_ENTITY, self.hook_start_y);
-               WriteCoord(MSG_ENTITY, self.hook_start_z);
-       }
-       if(sf & 4)
-       {
-               WriteCoord(MSG_ENTITY, self.hook_end_x);
-               WriteCoord(MSG_ENTITY, self.hook_end_y);
-               WriteCoord(MSG_ENTITY, self.hook_end_z);
-       }
-       return TRUE;
-}
-.entity lgbeam;
-.float prevlgfire;
-float lgbeam_checkammo()
-{
-       if(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)
-               return TRUE;
-       else if(autocvar_g_balance_electro_reload_ammo)
-               return self.realowner.clip_load > 0;
-       else
-               return self.realowner.ammo_cells > 0;
-}
-
-entity lgbeam_owner_ent;
-void lgbeam_think()
-{
-       entity owner_player;
-       owner_player = self.realowner;
-
-       owner_player.prevlgfire = time;
-       if (self != owner_player.lgbeam)
-       {
-               remove(self);
-               return;
-       }
-
-       if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen)
-       {
-               if(self == owner_player.lgbeam)
-                       owner_player.lgbeam = world;
-               remove(self);
-               return;
-       }
-
-       self.nextthink = time;
-
-       makevectors(owner_player.v_angle);
-
-       float dt, f;
-       dt = frametime;
-
-       // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
-       if not(owner_player.items & IT_UNLIMITED_WEAPON_AMMO)
-       {
-               if(autocvar_g_balance_electro_primary_ammo)
-               {
-                       if(autocvar_g_balance_electro_reload_ammo)
-                       {
-                               dt = min(dt, owner_player.clip_load / autocvar_g_balance_electro_primary_ammo);
-                               owner_player.clip_load = max(0, owner_player.clip_load - autocvar_g_balance_electro_primary_ammo * frametime);
-                               owner_player.(weapon_load[WEP_ELECTRO]) = owner_player.clip_load;
-                       }
-                       else
-                       {
-                               dt = min(dt, owner_player.ammo_cells / autocvar_g_balance_electro_primary_ammo);
-                               owner_player.ammo_cells = max(0, owner_player.ammo_cells - autocvar_g_balance_electro_primary_ammo * frametime);
-                       }
-               }
-       }
-
-       W_SetupShot_Range(owner_player, TRUE, 0, "", 0, autocvar_g_balance_electro_primary_damage * dt, autocvar_g_balance_electro_primary_range);
-       if(!lgbeam_owner_ent)
-       {
-               lgbeam_owner_ent = spawn();
-               lgbeam_owner_ent.classname = "lgbeam_owner_ent";
-       }
-       WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(owner_player));
-
-       // apply the damage
-       if(trace_ent)
-       {
-               vector force;
-               force = w_shotdir * autocvar_g_balance_electro_primary_force + '0 0 1' * autocvar_g_balance_electro_primary_force_up;
-
-               f = ExponentialFalloff(autocvar_g_balance_electro_primary_falloff_mindist, autocvar_g_balance_electro_primary_falloff_maxdist, autocvar_g_balance_electro_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg));
-
-               if(accuracy_isgooddamage(owner_player, trace_ent))
-                       accuracy_add(owner_player, WEP_ELECTRO, 0, autocvar_g_balance_electro_primary_damage * dt * f);
-               Damage (trace_ent, owner_player, owner_player, autocvar_g_balance_electro_primary_damage * dt * f, WEP_ELECTRO, trace_endpos, force * dt);
-       }
-       W_Plasma_TriggerCombo(trace_endpos, autocvar_g_balance_electro_primary_comboradius, owner_player);
-
-       // draw effect
-       if(w_shotorg != self.hook_start)
-       {
-               self.SendFlags |= 2;
-               self.hook_start = w_shotorg;
-       }
-       if(w_shotend != self.hook_end)
-       {
-               self.SendFlags |= 4;
-               self.hook_end = w_shotend;
-       }
-}
-
-// experimental lightning gun
-void W_Electro_Attack3 (void)
-{
-       // only play fire sound if 0.5 sec has passed since player let go the fire button
-       if(time - self.prevlgfire > 0.5)
-               sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
-
-       entity beam, oldself;
-
-       self.lgbeam = beam = spawn();
-       beam.classname = "lgbeam";
-       beam.solid = SOLID_NOT;
-       beam.think = lgbeam_think;
-       beam.owner = beam.realowner = self;
-       beam.movetype = MOVETYPE_NONE;
-       beam.shot_spread = 0;
-       beam.bot_dodge = TRUE;
-       beam.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
-       Net_LinkEntity(beam, FALSE, 0, lgbeam_send);
-
-       oldself = self;
-       self = beam;
-       self.think();
-       self = oldself;
-}
-
-void ElectroInit()
-{
-       weapon_action(WEP_ELECTRO, WR_PRECACHE);
-       electro_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 1);
-       electro_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 2);
-       electro_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 3);
-       electro_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 4);
-}
-
-void spawnfunc_weapon_electro (void)
-{
-       weapon_defaultspawnfunc(WEP_ELECTRO);
-}
-
-void w_electro_checkattack()
-{
-       if(self.electro_count > 1)
-       if(self.BUTTON_ATCK2)
-       if(weapon_prepareattack(1, -1))
-       {
-               W_Electro_Attack2();
-               self.electro_count -= 1;
-               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
-               return;
-       }
-
-       w_ready();
-}
-
-.float bot_secondary_electromooth;
-.float BUTTON_ATCK_prev;
-float w_electro(float req)
-{
-       float ammo_amount;
-       if (req == WR_AIM)
-       {
-               self.BUTTON_ATCK=FALSE;
-               self.BUTTON_ATCK2=FALSE;
-               if(vlen(self.origin-self.enemy.origin) > 1000)
-                       self.bot_secondary_electromooth = 0;
-               if(self.bot_secondary_electromooth == 0)
-               {
-                       float shoot;
-
-                       if(autocvar_g_balance_electro_primary_speed)
-                               shoot = bot_aim(autocvar_g_balance_electro_primary_speed, 0, autocvar_g_balance_electro_primary_lifetime, FALSE);
-                       else
-                               shoot = bot_aim(1000000, 0, 0.001, FALSE);
-
-                       if(shoot)
-                       {
-                               self.BUTTON_ATCK = TRUE;
-                               if(random() < 0.01) self.bot_secondary_electromooth = 1;
-                       }
-               }
-               else
-               {
-                       if(bot_aim(autocvar_g_balance_electro_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_electro_secondary_lifetime, TRUE))
-                       {
-                               self.BUTTON_ATCK2 = TRUE;
-                               if(random() < 0.03) self.bot_secondary_electromooth = 0;
-                       }
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_electro_reload_ammo) // forced reload
-               {
-                       ammo_amount = 0;
-                       if(autocvar_g_balance_electro_lightning)
-                       {
-                               if(self.clip_load > 0)
-                                       ammo_amount = 1;
-                       }
-                       else if(self.clip_load >= autocvar_g_balance_electro_primary_ammo)
-                               ammo_amount = 1;
-                       if(self.clip_load >= autocvar_g_balance_electro_secondary_ammo)
-                               ammo_amount += 1;
-
-                       if(!ammo_amount)
-                       {
-                               weapon_action(self.weapon, WR_RELOAD);
-                               return FALSE;
-                       }
-               }
-               if (self.BUTTON_ATCK)
-               {
-                       if(autocvar_g_balance_electro_lightning)
-                               if(self.BUTTON_ATCK_prev)
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
-
-                       if (weapon_prepareattack(0, (autocvar_g_balance_electro_lightning ? 0 : autocvar_g_balance_electro_primary_refire)))
-                       {
-                               if(autocvar_g_balance_electro_lightning)
-                               {
-                                       if ((!self.lgbeam) || wasfreed(self.lgbeam))
-                                       {
-                                               W_Electro_Attack3();
-                                       }
-                                       if(!self.BUTTON_ATCK_prev)
-                                       {
-                                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
-                                               self.BUTTON_ATCK_prev = 1;
-                                       }
-                               }
-                               else
-                               {
-                                       W_Electro_Attack();
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
-                               }
-                       }
-               } else {
-                       if(autocvar_g_balance_electro_lightning)
-                       {
-                               if (self.BUTTON_ATCK_prev != 0)
-                               {
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
-                                       ATTACK_FINISHED(self) = time + autocvar_g_balance_electro_primary_refire * W_WeaponRateFactor();
-                               }
-                               self.BUTTON_ATCK_prev = 0;
-                       }
-
-                       if (self.BUTTON_ATCK2)
-                       {
-                               if (time >= self.electro_secondarytime)
-                               if (weapon_prepareattack(1, autocvar_g_balance_electro_secondary_refire))
-                               {
-                                       W_Electro_Attack2();
-                                       self.electro_count = autocvar_g_balance_electro_secondary_count;
-                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
-                                       self.electro_secondarytime = time + autocvar_g_balance_electro_secondary_refire2 * W_WeaponRateFactor();
-                               }
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_electro.md3");
-               precache_model ("models/weapons/v_electro.md3");
-               precache_model ("models/weapons/h_electro.iqm");
-               precache_sound ("weapons/electro_bounce.wav");
-               precache_sound ("weapons/electro_fire.wav");
-               precache_sound ("weapons/electro_fire2.wav");
-               precache_sound ("weapons/electro_impact.wav");
-               precache_sound ("weapons/electro_impact_combo.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-               if(autocvar_g_balance_electro_lightning)
-               {
-                       precache_sound ("weapons/lgbeam_fire.wav");
-               }
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_ELECTRO);
-               self.current_ammo = ammo_cells;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               if(autocvar_g_balance_electro_lightning)
-               {
-                       if(!autocvar_g_balance_electro_primary_ammo)
-                               ammo_amount = 1;
-                       else
-                               ammo_amount = self.ammo_cells > 0;
-                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) > 0;
-               }
-               else
-               {
-                       ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_primary_ammo;
-                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_primary_ammo;
-               }
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               if(autocvar_g_balance_electro_combo_safeammocheck) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
-               {
-                       ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
-                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
-               }
-               else
-               {
-                       ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo;
-                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo;
-               }
-               return ammo_amount;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.electro_secondarytime = time;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_secondary_ammo), autocvar_g_balance_electro_reload_ammo, autocvar_g_balance_electro_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_ELECTRO_SUICIDE_ORBS;
-               else
-                       return WEAPON_ELECTRO_SUICIDE_BOLT;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       return WEAPON_ELECTRO_MURDER_ORBS;
-               }
-               else
-               {
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                               return WEAPON_ELECTRO_MURDER_COMBO;
-                       else
-                               return WEAPON_ELECTRO_MURDER_BOLT;
-               }
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_electro(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 6;
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       pointparticles(particleeffectnum("electro_ballexplode"), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM);
-               }
-               else
-               {
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                       {
-                               // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
-                               pointparticles(particleeffectnum("electro_combo"), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                                       sound(self, CH_SHOTS, "weapons/electro_impact_combo.wav", VOL_BASE, ATTN_NORM);
-                       }
-                       else
-                       {
-                               pointparticles(particleeffectnum("electro_impact"), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                                       sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM);
-                       }
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/electro_impact.wav");
-               precache_sound("weapons/electro_impact_combo.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/electro.qh b/qcsrc/common/weapons/electro.qh
deleted file mode 100644 (file)
index 98c0be1..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-void ElectroInit();
-vector electro_shotorigin[4];
diff --git a/qcsrc/common/weapons/fireball.qc b/qcsrc/common/weapons/fireball.qc
deleted file mode 100644 (file)
index 3d84e8e..0000000
+++ /dev/null
@@ -1,434 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ FIREBALL,
-/* function  */ w_fireball,
-/* ammotype  */ 0,
-/* impulse   */ 9,
-/* flags     */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "fireball",
-/* shortname */ "fireball",
-/* fullname  */ _("Fireball")
-);
-#else
-#ifdef SVQC
-.float bot_primary_fireballmooth; // whatever a mooth is
-.vector fireball_impactvec;
-.float fireball_primarytime;
-
-void W_Fireball_Explode (void)
-{
-       entity e;
-       float dist;
-       float points;
-       vector dir;
-       float d;
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       // 1. dist damage
-       d = (self.realowner.health + self.realowner.armorvalue);
-       RadiusDamage (self, self.realowner, autocvar_g_balance_fireball_primary_damage, autocvar_g_balance_fireball_primary_edgedamage, autocvar_g_balance_fireball_primary_radius, world, world, autocvar_g_balance_fireball_primary_force, self.projectiledeathtype, other);
-       if(self.realowner.health + self.realowner.armorvalue >= d)
-       if(!self.cnt)
-       {
-               modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, autocvar_g_balance_fireball_primary_bfgradius, 0.2, 0.05, 0.25);
-
-               // 2. bfg effect
-               // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
-               for(e = findradius(self.origin, autocvar_g_balance_fireball_primary_bfgradius); e; e = e.chain)
-               if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self))
-               {
-                       // can we see fireball?
-                       traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
-                       if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
-                               continue;
-                       // can we see player who shot fireball?
-                       traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
-                       if(trace_ent != self.realowner)
-                       if(/* trace_startsolid || */ trace_fraction != 1)
-                               continue;
-                       dist = vlen(self.origin - e.origin - e.view_ofs);
-                       points = (1 - sqrt(dist / autocvar_g_balance_fireball_primary_bfgradius));
-                       if(points <= 0)
-                               continue;
-                       dir = normalize(e.origin + e.view_ofs - self.origin);
-
-                       if(accuracy_isgooddamage(self.realowner, e))
-                               accuracy_add(self.realowner, WEP_FIREBALL, 0, autocvar_g_balance_fireball_primary_bfgdamage * points);
-
-                       Damage(e, self, self.realowner, autocvar_g_balance_fireball_primary_bfgdamage * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, autocvar_g_balance_fireball_primary_bfgforce * dir);
-                       pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1);
-               }
-       }
-
-       remove (self);
-}
-
-void W_Fireball_TouchExplode (void)
-{
-       PROJECTILE_TOUCH;
-       W_Fireball_Explode ();
-}
-
-void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime)
-{
-       entity e;
-       float d;
-       vector p;
-
-       if(damage <= 0)
-               return;
-
-       RandomSelection_Init();
-       for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain)
-       if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self))
-       {
-               p = e.origin;
-               p_x += e.mins_x + random() * (e.maxs_x - e.mins_x);
-               p_y += e.mins_y + random() * (e.maxs_y - e.mins_y);
-               p_z += e.mins_z + random() * (e.maxs_z - e.mins_z);
-               d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
-               if(d < dist)
-               {
-                       e.fireball_impactvec = p;
-                       RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
-               }
-       }
-       if(RandomSelection_chosen_ent)
-       {
-               d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
-               d = damage + (edgedamage - damage) * (d / dist);
-               Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
-               //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
-               pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
-       }
-}
-
-void W_Fireball_Think()
-{
-       if(time > self.pushltime)
-       {
-               self.cnt = 1;
-               self.projectiledeathtype |= HITTYPE_SPLASH;
-               W_Fireball_Explode();
-               return;
-       }
-
-       W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_primary_laserradius, autocvar_g_balance_fireball_primary_laserdamage, autocvar_g_balance_fireball_primary_laseredgedamage, autocvar_g_balance_fireball_primary_laserburntime);
-
-       self.nextthink = time + 0.1;
-}
-
-void W_Fireball_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if(self.health <= 0)
-               return;
-               
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-               
-       self.health = self.health - damage;
-       if (self.health <= 0)
-       {
-               self.cnt = 1;
-               W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
-       }
-}
-
-void W_Fireball_Attack1()
-{
-       entity proj;
-
-       W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 2, "weapons/fireball_fire2.wav", CH_WEAPON_A, autocvar_g_balance_fireball_primary_damage + autocvar_g_balance_fireball_primary_bfgdamage);
-
-       pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       proj = spawn ();
-       proj.classname = "plasma_prim";
-       proj.owner = proj.realowner = self;
-       proj.bot_dodge = TRUE;
-       proj.bot_dodgerating = autocvar_g_balance_fireball_primary_damage;
-       proj.pushltime = time + autocvar_g_balance_fireball_primary_lifetime;
-       proj.use = W_Fireball_Explode;
-       proj.think = W_Fireball_Think;
-       proj.nextthink = time;
-       proj.health = autocvar_g_balance_fireball_primary_health;
-       proj.team = self.team;
-       proj.event_damage = W_Fireball_Damage;
-       proj.takedamage = DAMAGE_YES;
-       proj.damageforcescale = autocvar_g_balance_fireball_primary_damageforcescale;
-       PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = WEP_FIREBALL;
-       setorigin(proj, w_shotorg);
-
-       proj.movetype = MOVETYPE_FLY;
-       W_SETUPPROJECTILEVELOCITY(proj, g_balance_fireball_primary);
-       proj.angles = vectoangles(proj.velocity);
-       proj.touch = W_Fireball_TouchExplode;
-       setsize(proj, '-16 -16 -16', '16 16 16');
-       proj.flags = FL_PROJECTILE;
-    proj.missile_flags = MIF_SPLASH | MIF_PROXY;
-    
-       CSQCProjectile(proj, TRUE, PROJECTILE_FIREBALL, TRUE);
-
-       other = proj; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void W_Fireball_AttackEffect(float i, vector f_diff)
-{
-       W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 0, "", 0, 0);
-       w_shotorg += f_diff_x * v_up + f_diff_y * v_right;
-       pointparticles(particleeffectnum("fireball_preattack_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-}
-
-void W_Fireball_Attack1_Frame4()
-{
-       W_Fireball_Attack1();
-       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, w_ready);
-}
-
-void W_Fireball_Attack1_Frame3()
-{
-       W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
-       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame4);
-}
-
-void W_Fireball_Attack1_Frame2()
-{
-       W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
-       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame3);
-}
-
-void W_Fireball_Attack1_Frame1()
-{
-       W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
-       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame2);
-}
-
-void W_Fireball_Attack1_Frame0()
-{
-       W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
-       sound (self, CH_WEAPON_SINGLE, "weapons/fireball_prefire2.wav", VOL_BASE, ATTN_NORM);
-       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame1);
-}
-
-void W_Firemine_Think()
-{
-       if(time > self.pushltime)
-       {
-               remove(self);
-               return;
-       }
-
-       // make it "hot" once it leaves its owner
-       if(self.owner)
-       {
-               if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > autocvar_g_balance_fireball_secondary_laserradius)
-               {
-                       self.cnt += 1;
-                       if(self.cnt == 3)
-                               self.owner = world;
-               }
-               else
-                       self.cnt = 0;
-       }
-
-       W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_secondary_laserradius, autocvar_g_balance_fireball_secondary_laserdamage, autocvar_g_balance_fireball_secondary_laseredgedamage, autocvar_g_balance_fireball_secondary_laserburntime);
-
-       self.nextthink = time + 0.1;
-}
-
-void W_Firemine_Touch (void)
-{
-       PROJECTILE_TOUCH;
-       if (other.takedamage == DAMAGE_AIM)
-       if(Fire_AddDamage(other, self.realowner, autocvar_g_balance_fireball_secondary_damage, autocvar_g_balance_fireball_secondary_damagetime, self.projectiledeathtype) >= 0)
-       {
-               remove(self);
-               return;
-       }
-       self.projectiledeathtype |= HITTYPE_BOUNCE;
-}
-
-void W_Fireball_Attack2()
-{
-       entity proj;
-       vector f_diff;
-       float c;
-
-       c = mod(self.bulletcounter, 4);
-       switch(c)
-       {
-               case 0:
-                       f_diff = '-1.25 -3.75 0';
-                       break;
-               case 1:
-                       f_diff = '+1.25 -3.75 0';
-                       break;
-               case 2:
-                       f_diff = '-1.25 +3.75 0';
-                       break;
-               case 3:
-               default:
-                       f_diff = '+1.25 +3.75 0';
-                       break;
-       }
-       W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, "weapons/fireball_fire.wav", CH_WEAPON_A, autocvar_g_balance_fireball_secondary_damage);
-       traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self);
-       w_shotorg = trace_endpos;
-
-       pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       proj = spawn ();
-       proj.owner = proj.realowner = self;
-       proj.classname = "grenade";
-       proj.bot_dodge = TRUE;
-       proj.bot_dodgerating = autocvar_g_balance_fireball_secondary_damage;
-       proj.movetype = MOVETYPE_BOUNCE;
-       proj.projectiledeathtype = WEP_FIREBALL | HITTYPE_SECONDARY;
-       proj.touch = W_Firemine_Touch;
-       PROJECTILE_MAKETRIGGER(proj);
-       setsize(proj, '-4 -4 -4', '4 4 4');
-       setorigin(proj, w_shotorg);
-       proj.think = W_Firemine_Think;
-       proj.nextthink = time;
-       proj.damageforcescale = autocvar_g_balance_fireball_secondary_damageforcescale;
-       proj.pushltime = time + autocvar_g_balance_fireball_secondary_lifetime;
-       W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_fireball_secondary);
-
-       proj.angles = vectoangles(proj.velocity);
-       proj.flags = FL_PROJECTILE;
-    proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
-    
-       CSQCProjectile(proj, TRUE, PROJECTILE_FIREMINE, TRUE);
-
-       other = proj; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void spawnfunc_weapon_fireball (void)
-{
-       weapon_defaultspawnfunc(WEP_FIREBALL);
-}
-
-float w_fireball(float req)
-{
-       //float ammo_amount;
-       if (req == WR_AIM)
-       {
-               self.BUTTON_ATCK = FALSE;
-               self.BUTTON_ATCK2 = FALSE;
-               if (self.bot_primary_fireballmooth == 0)
-               {
-                       if(bot_aim(autocvar_g_balance_fireball_primary_speed, 0, autocvar_g_balance_fireball_primary_lifetime, FALSE))
-                       {
-                               self.BUTTON_ATCK = TRUE;
-                               if(random() < 0.02) self.bot_primary_fireballmooth = 0;
-                       }
-               }
-               else
-               {
-                       if(bot_aim(autocvar_g_balance_fireball_secondary_speed, autocvar_g_balance_fireball_secondary_speed_up, autocvar_g_balance_fireball_secondary_lifetime, TRUE))
-                       {
-                               self.BUTTON_ATCK2 = TRUE;
-                               if(random() < 0.01) self.bot_primary_fireballmooth = 1;
-                       }
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if (self.BUTTON_ATCK)
-               {
-                       if (time >= self.fireball_primarytime)
-                       if (weapon_prepareattack(0, autocvar_g_balance_fireball_primary_refire))
-                       {
-                               W_Fireball_Attack1_Frame0();
-                               self.fireball_primarytime = time + autocvar_g_balance_fireball_primary_refire2 * W_WeaponRateFactor();
-                       }
-               }
-               else if (self.BUTTON_ATCK2)
-               {
-                       if (weapon_prepareattack(1, autocvar_g_balance_fireball_secondary_refire))
-                       {
-                               W_Fireball_Attack2();
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_fireball_secondary_animtime, w_ready);
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_fireball.md3");
-               precache_model ("models/weapons/v_fireball.md3");
-               precache_model ("models/weapons/h_fireball.iqm");
-               precache_model ("models/sphere/sphere.md3");
-               precache_sound ("weapons/fireball_fire.wav");
-               precache_sound ("weapons/fireball_fire2.wav");
-               precache_sound ("weapons/fireball_prefire2.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_FIREBALL);
-               self.current_ammo = ammo_none;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               return 1;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               return 1;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.fireball_primarytime = time;
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_FIREBALL_SUICIDE_FIREMINE;
-               else
-                       return WEAPON_FIREBALL_SUICIDE_BLAST;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       return WEAPON_FIREBALL_MURDER_FIREMINE;
-               }
-               else
-               {
-                       return WEAPON_FIREBALL_MURDER_BLAST;
-               }
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_fireball(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       // firemine goes out silently
-               }
-               else
-               {
-                       org2 = w_org + w_backoff * 16;
-                       pointparticles(particleeffectnum("fireball_explode"), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, "weapons/fireball_impact2.wav", VOL_BASE, ATTN_NORM * 0.25); // long range boom
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/fireball_impact2.wav");
-       }
-
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/hagar.qc b/qcsrc/common/weapons/hagar.qc
deleted file mode 100644 (file)
index 01a7169..0000000
+++ /dev/null
@@ -1,489 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ HAGAR,
-/* function  */ w_hagar,
-/* ammotype  */ IT_ROCKETS,
-/* impulse   */ 8,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "hagar",
-/* shortname */ "hagar",
-/* fullname  */ _("Hagar")
-);
-#else
-#ifdef SVQC
-// NO bounce protection, as bounces are limited!
-
-void W_Hagar_Explode (void)
-{
-       self.event_damage = func_null;
-       RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_primary_damage, autocvar_g_balance_hagar_primary_edgedamage, autocvar_g_balance_hagar_primary_radius, world, world, autocvar_g_balance_hagar_primary_force, self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void W_Hagar_Explode2 (void)
-{
-       self.event_damage = func_null;
-       RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_secondary_damage, autocvar_g_balance_hagar_secondary_edgedamage, autocvar_g_balance_hagar_secondary_radius, world, world, autocvar_g_balance_hagar_secondary_force, self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void W_Hagar_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-               
-       float is_linkexplode = ( ((inflictor.owner != world) ? (inflictor.owner == self.owner) : TRUE)
-               && (inflictor.projectiledeathtype & HITTYPE_SECONDARY) 
-               && (self.projectiledeathtype & HITTYPE_SECONDARY));
-       
-       if(is_linkexplode)
-               is_linkexplode = (is_linkexplode && autocvar_g_balance_hagar_secondary_load_linkexplode);
-       else
-               is_linkexplode = -1; // not secondary load, so continue as normal without exception.
-
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, is_linkexplode))
-               return; // g_projectiles_damage says to halt
-
-       self.health = self.health - damage;
-       self.angles = vectoangles(self.velocity);
-       
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, self.think);
-}
-
-void W_Hagar_Touch (void)
-{
-       PROJECTILE_TOUCH;
-       self.use ();
-}
-
-void W_Hagar_Touch2 (void)
-{
-       PROJECTILE_TOUCH;
-
-       if(self.cnt > 0 || other.takedamage == DAMAGE_AIM) {
-               self.use();
-       } else {
-               self.cnt++;
-               pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1);
-               self.angles = vectoangles (self.velocity);
-               self.owner = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-       }
-}
-
-void W_Hagar_Attack (void)
-{
-       entity missile;
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_reload_ammo);
-
-       W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_primary_damage);
-
-       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       missile = spawn ();
-       missile.owner = missile.realowner = self;
-       missile.classname = "missile";
-       missile.bot_dodge = TRUE;
-       missile.bot_dodgerating = autocvar_g_balance_hagar_primary_damage;
-       
-       missile.takedamage = DAMAGE_YES;
-       missile.health = autocvar_g_balance_hagar_primary_health;
-       missile.damageforcescale = autocvar_g_balance_hagar_primary_damageforcescale;
-       missile.event_damage = W_Hagar_Damage;
-       missile.damagedbycontents = TRUE;
-       
-       missile.touch = W_Hagar_Touch;
-       missile.use = W_Hagar_Explode;
-       missile.think = adaptor_think2use_hittype_splash;
-       missile.nextthink = time + autocvar_g_balance_hagar_primary_lifetime;
-       PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_HAGAR;
-       setorigin (missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       missile.movetype = MOVETYPE_FLY;
-       W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_primary);
-
-       missile.angles = vectoangles (missile.velocity);
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH; 
-
-       CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE);
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void W_Hagar_Attack2 (void)
-{
-       entity missile;
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo);
-
-       W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage);
-
-       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       missile = spawn ();
-       missile.owner = missile.realowner = self;
-       missile.classname = "missile";
-       missile.bot_dodge = TRUE;
-       missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage;
-       
-       missile.takedamage = DAMAGE_YES;
-       missile.health = autocvar_g_balance_hagar_secondary_health;
-       missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale;
-       missile.event_damage = W_Hagar_Damage;
-       missile.damagedbycontents = TRUE;
-
-       missile.touch = W_Hagar_Touch2;
-       missile.cnt = 0;
-       missile.use = W_Hagar_Explode2;
-       missile.think = adaptor_think2use_hittype_splash;
-       missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand;
-       PROJECTILE_MAKETRIGGER(missile);
-       missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY;
-       setorigin (missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       missile.movetype = MOVETYPE_BOUNCEMISSILE;
-       W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_secondary);
-
-       missile.angles = vectoangles (missile.velocity);
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH; 
-
-       CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR_BOUNCING, TRUE);
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-.float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning;
-void W_Hagar_Attack2_Load_Release (void)
-{
-       // time to release the rockets we've loaded
-
-       entity missile;
-       float counter, shots, spread_pershot;
-       vector s;
-       vector forward, right, up;
-
-       if(!self.hagar_load)
-               return;
-
-       weapon_prepareattack_do(1, autocvar_g_balance_hagar_secondary_refire);
-
-       W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage);
-       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       forward = v_forward;
-       right = v_right;
-       up = v_up;
-
-       shots = self.hagar_load;
-       missile = world;
-       for(counter = 0; counter < shots; ++counter)
-       {
-               missile = spawn ();
-               missile.owner = missile.realowner = self;
-               missile.classname = "missile";
-               missile.bot_dodge = TRUE;
-               missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage;
-               
-               missile.takedamage = DAMAGE_YES;
-               missile.health = autocvar_g_balance_hagar_secondary_health;
-               missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale;
-               missile.event_damage = W_Hagar_Damage;
-               missile.damagedbycontents = TRUE;
-
-               missile.touch = W_Hagar_Touch; // not bouncy
-               missile.use = W_Hagar_Explode2;
-               missile.think = adaptor_think2use_hittype_splash;
-               missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand;
-               PROJECTILE_MAKETRIGGER(missile);
-               missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY;
-               setorigin (missile, w_shotorg);
-               setsize(missile, '0 0 0', '0 0 0');
-               missile.movetype = MOVETYPE_FLY;
-               missile.missile_flags = MIF_SPLASH; 
-               
-               // per-shot spread calculation: the more shots there are, the less spread is applied (based on the bias cvar)
-               spread_pershot = ((shots - 1) / (autocvar_g_balance_hagar_secondary_load_max - 1)); 
-               spread_pershot = (1 - (spread_pershot * autocvar_g_balance_hagar_secondary_load_spread_bias));
-               spread_pershot = (autocvar_g_balance_hagar_secondary_spread * spread_pershot * g_weaponspreadfactor);
-               
-               // pattern spread calculation
-               s = '0 0 0';
-               if (counter == 0)
-                       s = '0 0 0';
-               else
-               {
-                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
-                       s_y = v_forward_x;
-                       s_z = v_forward_y;
-               }
-               s = s * autocvar_g_balance_hagar_secondary_load_spread * g_weaponspreadfactor;
-               
-               W_SetupProjectileVelocityEx(missile, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_hagar_secondary_speed, 0, 0, spread_pershot, FALSE);
-
-               missile.angles = vectoangles (missile.velocity);
-               missile.flags = FL_PROJECTILE;
-
-               CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE);
-
-               other = missile; MUTATOR_CALLHOOK(EditProjectile);
-       }
-
-       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_load_animtime, w_ready);
-       self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_refire * W_WeaponRateFactor();
-       self.hagar_load = 0;
-}
-
-void W_Hagar_Attack2_Load (void)
-{
-       // loadable hagar secondary attack, must always run each frame
-       
-       if(time < game_starttime)
-               return;
-
-       float loaded, enough_ammo;
-       loaded = self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max;
-
-       // this is different than WR_CHECKAMMO when it comes to reloading
-       if(autocvar_g_balance_hagar_reload_ammo)
-               enough_ammo = self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo;
-       else
-               enough_ammo = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo;
-
-       if(self.BUTTON_ATCK2)
-       {
-               if(self.BUTTON_ATCK && autocvar_g_balance_hagar_secondary_load_abort)
-               {
-                       if(self.hagar_load)
-                       {
-                               // if we pressed primary fire while loading, unload all rockets and abort
-                               self.weaponentity.state = WS_READY;
-                               W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo
-                               self.hagar_load = 0;
-                               sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM);
-
-                               // pause until we can load rockets again, once we re-press the alt fire button
-                               self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor();
-
-                               // require letting go of the alt fire button before we can load again
-                               self.hagar_loadblock = TRUE;
-                       }
-               }
-               else
-               {
-                       // check if we can attempt to load another rocket
-                       if(!loaded && enough_ammo)
-                       {
-                               if(!self.hagar_loadblock && self.hagar_loadstep < time)
-                               {
-                                       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo);
-                                       self.weaponentity.state = WS_INUSE;
-                                       self.hagar_load += 1;
-                                       sound(self, CH_WEAPON_B, "weapons/hagar_load.wav", VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most
-
-                                       if (self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max)
-                                               self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_hold * W_WeaponRateFactor();
-                                       else
-                                               self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor();
-                               }
-                       }
-                       else if(!self.hagar_loadbeep && self.hagar_load) // prevents the beep from playing each frame
-                       {
-                               // if this is the last rocket we can load, play a beep sound to notify the player
-                               sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM);
-                               self.hagar_loadbeep = TRUE;
-                       }
-               }
-       }
-       else if(self.hagar_loadblock)
-       {
-               // the alt fire button has been released, so re-enable loading if blocked
-               self.hagar_loadblock = FALSE;
-       }
-
-       if(self.hagar_load)
-       {
-               // play warning sound if we're about to release
-               if((loaded || !enough_ammo) && self.hagar_loadstep - 0.5 < time && autocvar_g_balance_hagar_secondary_load_hold >= 0)
-               {
-                       if(!self.hagar_warning && self.hagar_load) // prevents the beep from playing each frame
-                       {
-                               // we're about to automatically release after holding time, play a beep sound to notify the player
-                               sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM);
-                               self.hagar_warning = TRUE;
-                       }
-               }
-               
-               // release if player let go of button or if they've held it in too long
-               if(!self.BUTTON_ATCK2 || ((loaded || !enough_ammo) && self.hagar_loadstep < time && autocvar_g_balance_hagar_secondary_load_hold >= 0))
-               {
-                       self.weaponentity.state = WS_READY;
-                       W_Hagar_Attack2_Load_Release();
-               }
-       }
-       else
-       {
-               self.hagar_loadbeep = FALSE;
-               self.hagar_warning = FALSE;
-       }
-
-       // we aren't checking ammo during an attack, so we must do it here
-       if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2))
-       {
-               // note: this doesn't force the switch
-               W_SwitchToOtherWeapon(self);
-               return;
-       }
-}
-
-void spawnfunc_weapon_hagar (void)
-{
-       weapon_defaultspawnfunc(WEP_HAGAR);
-}
-
-float w_hagar(float req)
-{
-       float ammo_amount;
-       if (req == WR_AIM)
-               if (random()>0.15)
-                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE);
-               else
-               {
-                       // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming
-                       self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE);
-               }
-       else if (req == WR_THINK)
-       {
-               float loadable_secondary;
-               loadable_secondary = (autocvar_g_balance_hagar_secondary_load && autocvar_g_balance_hagar_secondary);
-
-               if (loadable_secondary)
-                       W_Hagar_Attack2_Load(); // must always run each frame
-               if(autocvar_g_balance_hagar_reload_ammo && self.clip_load < min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else if (self.BUTTON_ATCK && !self.hagar_load && !self.hagar_loadblock) // not while secondary is loaded or awaiting reset
-               {
-                       if (weapon_prepareattack(0, autocvar_g_balance_hagar_primary_refire))
-                       {
-                               W_Hagar_Attack();
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hagar_primary_refire, w_ready);
-                       }
-               }
-               else if (self.BUTTON_ATCK2 && !loadable_secondary && autocvar_g_balance_hagar_secondary)
-               {
-                       if (weapon_prepareattack(1, autocvar_g_balance_hagar_secondary_refire))
-                       {
-                               W_Hagar_Attack2();
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_refire, w_ready);
-                       }
-               }
-       }
-       else if (req == WR_GONETHINK)
-       {
-               // we lost the weapon and want to prepare switching away
-               if(self.hagar_load)
-               {
-                       self.weaponentity.state = WS_READY;
-                       W_Hagar_Attack2_Load_Release();
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_hagar.md3");
-               precache_model ("models/weapons/v_hagar.md3");
-               precache_model ("models/weapons/h_hagar.iqm");
-               precache_sound ("weapons/hagar_fire.wav");
-               precache_sound ("weapons/hagar_load.wav");
-               precache_sound ("weapons/hagar_beep.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_HAGAR);
-               self.current_ammo = ammo_rockets;
-               self.hagar_loadblock = FALSE;
-
-               if(self.hagar_load)
-               {
-                       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo if necessary
-                       self.hagar_load = 0;
-               }
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_primary_ammo;
-               ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_primary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo;
-               ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.hagar_load = 0;
-       }
-       else if (req == WR_PLAYERDEATH)
-       {
-               // if we have any rockets loaded when we die, release them
-               if(self.hagar_load && autocvar_g_balance_hagar_secondary_load_releasedeath)
-                       W_Hagar_Attack2_Load_Release();
-       }
-       else if (req == WR_RELOAD)
-       {
-               if not(self.hagar_load) // require releasing loaded rockets first
-                       W_Reload(min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo), autocvar_g_balance_hagar_reload_ammo, autocvar_g_balance_hagar_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_HAGAR_SUICIDE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_HAGAR_MURDER_BURST;
-               else
-                       return WEAPON_HAGAR_MURDER_SPRAY;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_hagar(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 6;
-               pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
-               if(!w_issilent)
-               {
-                       if (w_random<0.15)
-                               sound(self, CH_SHOTS, "weapons/hagexp1.wav", VOL_BASE, ATTN_NORM);
-                       else if (w_random<0.7)
-                               sound(self, CH_SHOTS, "weapons/hagexp2.wav", VOL_BASE, ATTN_NORM);
-                       else
-                               sound(self, CH_SHOTS, "weapons/hagexp3.wav", VOL_BASE, ATTN_NORM);
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/hagexp1.wav");
-               precache_sound("weapons/hagexp2.wav");
-               precache_sound("weapons/hagexp3.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/hlac.qc b/qcsrc/common/weapons/hlac.qc
deleted file mode 100644 (file)
index cc7f053..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ HLAC,
-/* function  */ w_hlac,
-/* ammotype  */ IT_CELLS,
-/* impulse   */ 6,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "hlac",
-/* shortname */ "hlac",
-/* fullname  */ _("Heavy Laser Assault Cannon")
-);
-#else
-#ifdef SVQC
-
-void W_HLAC_Touch (void)
-{
-       PROJECTILE_TOUCH;
-
-       self.event_damage = func_null;
-       
-       if(self.projectiledeathtype & HITTYPE_SECONDARY)
-               RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_secondary_damage, autocvar_g_balance_hlac_secondary_edgedamage, autocvar_g_balance_hlac_secondary_radius, world, world, autocvar_g_balance_hlac_secondary_force, self.projectiledeathtype, other);
-       else
-               RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_primary_damage, autocvar_g_balance_hlac_primary_edgedamage, autocvar_g_balance_hlac_primary_radius, world, world, autocvar_g_balance_hlac_primary_force, self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void W_HLAC_Attack (void)
-{
-       entity missile;
-    float spread;
-
-       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_reload_ammo);
-
-    spread = autocvar_g_balance_hlac_primary_spread_min + (autocvar_g_balance_hlac_primary_spread_add * self.misc_bulletcounter);
-    spread = min(spread,autocvar_g_balance_hlac_primary_spread_max);
-    if(self.crouch)
-        spread = spread * autocvar_g_balance_hlac_primary_spread_crouchmod;
-
-       W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_primary_damage);
-       pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-       if (!g_norecoil)
-       {
-               self.punchangle_x = random () - 0.5;
-               self.punchangle_y = random () - 0.5;
-       }
-
-       missile = spawn ();
-       missile.owner = missile.realowner = self;
-       missile.classname = "hlacbolt";
-       missile.bot_dodge = TRUE;
-
-    missile.bot_dodgerating = autocvar_g_balance_hlac_primary_damage;
-
-       missile.movetype = MOVETYPE_FLY;
-       PROJECTILE_MAKETRIGGER(missile);
-
-       setorigin (missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_primary_speed, spread);
-       //missile.angles = vectoangles (missile.velocity); // csqc
-
-       missile.touch = W_HLAC_Touch;
-       missile.think = SUB_Remove;
-
-    missile.nextthink = time + autocvar_g_balance_hlac_primary_lifetime;
-
-       missile.flags = FL_PROJECTILE;
-       missile.projectiledeathtype = WEP_HLAC;
-
-       CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE);
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void W_HLAC_Attack2f (void)
-{
-       entity missile;
-    float spread;
-
-    spread = autocvar_g_balance_hlac_secondary_spread;
-
-
-    if(self.crouch)
-        spread = spread * autocvar_g_balance_hlac_secondary_spread_crouchmod;
-
-       W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_secondary_damage);
-       pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       missile = spawn ();
-       missile.owner = missile.realowner = self;
-       missile.classname = "hlacbolt";
-       missile.bot_dodge = TRUE;
-
-    missile.bot_dodgerating = autocvar_g_balance_hlac_secondary_damage;
-
-       missile.movetype = MOVETYPE_FLY;
-       PROJECTILE_MAKETRIGGER(missile);
-
-       setorigin (missile, w_shotorg);
-       setsize(missile, '0 0 0', '0 0 0');
-
-       W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_secondary_speed, spread);
-       //missile.angles = vectoangles (missile.velocity); // csqc
-
-       missile.touch = W_HLAC_Touch;
-       missile.think = SUB_Remove;
-
-    missile.nextthink = time + autocvar_g_balance_hlac_secondary_lifetime;
-
-       missile.flags = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH; 
-       missile.projectiledeathtype = WEP_HLAC | HITTYPE_SECONDARY;
-
-       CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE);
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void W_HLAC_Attack2 (void)
-{
-    float i;
-
-       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_secondary_ammo, autocvar_g_balance_hlac_reload_ammo);
-
-    for(i=autocvar_g_balance_hlac_secondary_shots;i>0;--i)
-        W_HLAC_Attack2f();
-
-       if (!g_norecoil)
-       {
-               self.punchangle_x = random () - 0.5;
-               self.punchangle_y = random () - 0.5;
-       }
-}
-
-// weapon frames
-void HLAC_fire1_02()
-{
-       if(self.weapon != self.switchweapon) // abort immediately if switching
-       {
-               w_ready();
-               return;
-       }
-
-       if (self.BUTTON_ATCK)
-       {
-               if (!weapon_action(self.weapon, WR_CHECKAMMO1))
-               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-               {
-                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                       w_ready();
-                       return;
-               }
-
-               ATTACK_FINISHED(self) = time + autocvar_g_balance_hlac_primary_refire * W_WeaponRateFactor();
-               W_HLAC_Attack();
-               self.misc_bulletcounter = self.misc_bulletcounter + 1;
-        weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02);
-       }
-       else
-       {
-               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_animtime, w_ready);
-       }
-}
-
-void spawnfunc_weapon_hlac (void)
-{
-       weapon_defaultspawnfunc(WEP_HLAC);
-}
-
-float w_hlac(float req)
-{
-       float ammo_amount;
-       if (req == WR_AIM)
-        self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hlac_primary_speed, 0, autocvar_g_balance_hlac_primary_lifetime, FALSE);
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else if (self.BUTTON_ATCK)
-               {
-                       if (weapon_prepareattack(0, autocvar_g_balance_hlac_primary_refire))
-                       {
-                               self.misc_bulletcounter = 0;
-                               W_HLAC_Attack();
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02);
-                       }
-               }
-
-               else if (self.BUTTON_ATCK2 && autocvar_g_balance_hlac_secondary)
-               {
-                       if (weapon_prepareattack(1, autocvar_g_balance_hlac_secondary_refire))
-                       {
-                               W_HLAC_Attack2();
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hlac_secondary_animtime, w_ready);
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-        precache_model ("models/weapons/g_hlac.md3");
-               precache_model ("models/weapons/v_hlac.md3");
-               precache_model ("models/weapons/h_hlac.iqm");
-               precache_sound ("weapons/lasergun_fire.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_HLAC);
-               self.current_ammo = ammo_cells;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_primary_ammo;
-               ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_primary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_secondary_ammo;
-               ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_secondary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo), autocvar_g_balance_hlac_reload_ammo, autocvar_g_balance_hlac_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_HLAC_SUICIDE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               return WEAPON_HLAC_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_hlac(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 6;
-               pointparticles(particleeffectnum("laser_impact"), org2, w_backoff * 1000, 1);
-               if(!w_issilent)
-                       sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/laserimpact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/hook.qc b/qcsrc/common/weapons/hook.qc
deleted file mode 100644 (file)
index 7f03744..0000000
+++ /dev/null
@@ -1,307 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ HOOK,
-/* function  */ w_hook,
-/* ammotype  */ IT_CELLS|IT_FUEL,
-/* impulse   */ 0,
-/* flags     */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ 0,
-/* model     */ "hookgun",
-/* shortname */ "hook",
-/* fullname  */ _("Grappling Hook")
-);
-#else
-#ifdef SVQC
-.float dmg;
-.float dmg_edge;
-.float dmg_radius;
-.float dmg_force;
-.float dmg_power;
-.float dmg_duration;
-.float dmg_last;
-.float hook_refire;
-.float hook_time_hooked;
-.float hook_time_fueldecrease;
-
-void W_Hook_ExplodeThink (void)
-{
-       float dt, dmg_remaining_next, f;
-
-       dt = time - self.teleport_time;
-       dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power);
-
-       f = self.dmg_last - dmg_remaining_next;
-       self.dmg_last = dmg_remaining_next;
-
-       RadiusDamage (self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world);
-       self.projectiledeathtype |= HITTYPE_BOUNCE;
-       //RadiusDamage (self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world);
-
-       if(dt < self.dmg_duration)
-               self.nextthink = time + 0.05; // soon
-       else
-               remove(self);
-}
-
-void W_Hook_Explode2 (void)
-{
-       self.event_damage = func_null;
-       self.touch = func_null;
-       self.effects |= EF_NODRAW;
-
-       self.think = W_Hook_ExplodeThink;
-       self.nextthink = time;
-       self.dmg = autocvar_g_balance_hook_secondary_damage;
-       self.dmg_edge = autocvar_g_balance_hook_secondary_edgedamage;
-       self.dmg_radius = autocvar_g_balance_hook_secondary_radius;
-       self.dmg_force = autocvar_g_balance_hook_secondary_force;
-       self.dmg_power = autocvar_g_balance_hook_secondary_power;
-       self.dmg_duration = autocvar_g_balance_hook_secondary_duration;
-       self.teleport_time = time;
-       self.dmg_last = 1;
-       self.movetype = MOVETYPE_NONE;
-}
-
-void W_Hook_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-               
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt    
-       
-       self.health = self.health - damage;
-       
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2);
-}
-
-void W_Hook_Touch2 (void)
-{
-       PROJECTILE_TOUCH;
-       self.use();
-}
-
-void W_Hook_Attack2()
-{
-       entity gren;
-
-       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hook_secondary_ammo, FALSE);
-       W_SetupShot (self, FALSE, 4, "weapons/hookbomb_fire.wav", CH_WEAPON_A, autocvar_g_balance_hook_secondary_damage);
-
-       gren = spawn ();
-       gren.owner = gren.realowner = self;
-       gren.classname = "hookbomb";
-       gren.bot_dodge = TRUE;
-       gren.bot_dodgerating = autocvar_g_balance_hook_secondary_damage;
-       gren.movetype = MOVETYPE_TOSS;
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_HOOK | HITTYPE_SECONDARY;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '0 0 0', '0 0 0');
-
-       gren.nextthink = time + autocvar_g_balance_hook_secondary_lifetime;
-       gren.think = adaptor_think2use_hittype_splash;
-       gren.use = W_Hook_Explode2;
-       gren.touch = W_Hook_Touch2;
-       
-       gren.takedamage = DAMAGE_YES;
-       gren.health = autocvar_g_balance_hook_secondary_health;
-       gren.damageforcescale = autocvar_g_balance_hook_secondary_damageforcescale;
-       gren.event_damage = W_Hook_Damage;
-       gren.damagedbycontents = TRUE;
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-
-       gren.velocity = '0 0 1' * autocvar_g_balance_hook_secondary_speed;
-       if(autocvar_g_projectiles_newton_style)
-               gren.velocity = gren.velocity + self.velocity;
-
-       gren.gravity = autocvar_g_balance_hook_secondary_gravity;
-       //W_SetupProjectileVelocity(gren); // just falling down!
-
-       gren.angles = '0 0 0';
-       gren.flags = FL_PROJECTILE;
-
-       CSQCProjectile(gren, TRUE, PROJECTILE_HOOKBOMB, TRUE);
-
-       other = gren; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void spawnfunc_weapon_hook (void)
-{
-       if(g_grappling_hook) // offhand hook
-       {
-               startitem_failed = TRUE;
-               remove(self);
-               return;
-       }
-       weapon_defaultspawnfunc(WEP_HOOK);
-}
-
-float w_hook(float req)
-{
-       float hooked_time_max, hooked_fuel;
-               
-       if (req == WR_AIM)
-       {
-               // ... sorry ...
-       }
-       else if (req == WR_THINK)
-       {
-               if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK))
-               {
-                       if(!self.hook)
-                       if not(self.hook_state & HOOK_WAITING_FOR_RELEASE)
-                       if not(self.hook_state & HOOK_FIRING)
-                       if (time > self.hook_refire)
-                       if (weapon_prepareattack(0, -1))
-                       {
-                               W_DecreaseAmmo(ammo_fuel, autocvar_g_balance_hook_primary_fuel, FALSE);
-                               self.hook_state |= HOOK_FIRING;
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hook_primary_animtime, w_ready);                         
-                       }
-               }
-
-               if (self.BUTTON_ATCK2)
-               {
-                       if (weapon_prepareattack(1, autocvar_g_balance_hook_secondary_refire))
-                       {
-                               W_Hook_Attack2();
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hook_secondary_animtime, w_ready);
-                       }
-               }
-
-               if(self.hook)
-               {
-                       // if hooked, no bombs, and increase the timer
-                       self.hook_refire = max(self.hook_refire, time + autocvar_g_balance_hook_primary_refire * W_WeaponRateFactor());
-
-                       // hook also inhibits health regeneration, but only for 1 second
-                       if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-                               self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
-               }
-
-               if(self.hook && self.hook.state == 1)
-               {
-                       hooked_time_max = autocvar_g_balance_hook_primary_hooked_time_max;                      
-                       if (hooked_time_max > 0)
-                       {
-                               if ( time > self.hook_time_hooked + hooked_time_max )
-                                       self.hook_state |= HOOK_REMOVING;
-                       }
-                       
-                       hooked_fuel = autocvar_g_balance_hook_primary_hooked_fuel;
-                       if (hooked_fuel > 0)
-                       {
-                               if ( time > self.hook_time_fueldecrease )
-                               {
-                                       if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-                                       {
-                                               if ( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel )
-                                               {
-                                                       W_DecreaseAmmo(ammo_fuel, (time - self.hook_time_fueldecrease) * hooked_fuel, FALSE);
-                                                       self.hook_time_fueldecrease = time;
-                                                       // decrease next frame again
-                                               }
-                                               else
-                                               {
-                                                       self.ammo_fuel = 0;
-                                                       self.hook_state |= HOOK_REMOVING;
-                                                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                                               }
-                                       }
-                               }
-                       }
-               }
-               else
-               {
-                       self.hook_time_hooked = time;                           
-                       self.hook_time_fueldecrease = time + autocvar_g_balance_hook_primary_hooked_time_free;
-               }
-
-               if (self.BUTTON_CROUCH)
-               {
-                       self.hook_state &~= HOOK_PULLING;
-                       if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK))
-                               self.hook_state &~= HOOK_RELEASING;
-                       else
-                               self.hook_state |= HOOK_RELEASING;
-               }
-               else
-               {
-                       self.hook_state |= HOOK_PULLING;
-                       self.hook_state &~= HOOK_RELEASING;
-
-                       if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK))
-                       {
-                               // already fired
-                               if(self.hook)
-                                       self.hook_state |= HOOK_WAITING_FOR_RELEASE;
-                       }
-                       else
-                       {
-                               self.hook_state |= HOOK_REMOVING;
-                               self.hook_state &~= HOOK_WAITING_FOR_RELEASE;
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_hookgun.md3");
-               precache_model ("models/weapons/v_hookgun.md3");
-               precache_model ("models/weapons/h_hookgun.iqm");
-               precache_sound ("weapons/hook_impact.wav"); // done by g_hook.qc
-               precache_sound ("weapons/hook_fire.wav");
-               precache_sound ("weapons/hookbomb_fire.wav");
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_HOOK);
-               self.current_ammo = ammo_fuel;
-               self.hook_state &~= HOOK_WAITING_FOR_RELEASE;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               if(self.hook)
-                       return self.ammo_fuel > 0;
-               else
-                       return self.ammo_fuel >= autocvar_g_balance_hook_primary_fuel;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               return self.ammo_cells >= autocvar_g_balance_hook_secondary_ammo;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.hook_refire = time;
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return FALSE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               return WEAPON_HOOK_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_hook(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 2;
-               pointparticles(particleeffectnum("hookbomb_explode"), org2, '0 0 0', 1);
-               if(!w_issilent)
-                       sound(self, CH_SHOTS, "weapons/hookbomb_impact.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/hookbomb_impact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/machinegun.qc b/qcsrc/common/weapons/machinegun.qc
deleted file mode 100644 (file)
index 923ed95..0000000
+++ /dev/null
@@ -1,341 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ UZI,
-/* function  */ w_uzi,
-/* ammotype  */ IT_NAILS,
-/* impulse   */ 3,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "uzi",
-/* shortname */ "uzi",
-/* fullname  */ _("Machine Gun")
-);
-#else
-#ifdef SVQC
-
-// leilei's fancy muzzleflash stuff
-void UZI_Flash_Go()
-{
-       self.frame = self.frame + 2;
-       self.scale = self.scale * 0.5;
-       self.alpha = self.alpha - 0.25;
-       self.nextthink = time + 0.05;
-
-       if (self.alpha <= 0)
-       {
-               self.think = SUB_Remove;
-               self.nextthink = time;
-               self.realowner.muzzle_flash = world;
-               return;
-       }
-
-}
-
-void UziFlash()
-{
-       if (self.muzzle_flash == world)
-               self.muzzle_flash = spawn();
-
-       // muzzle flash for 1st person view
-       setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below
-
-       self.muzzle_flash.scale = 0.75;
-       self.muzzle_flash.think = UZI_Flash_Go;
-       self.muzzle_flash.nextthink = time + 0.02;
-       self.muzzle_flash.frame = 2;
-       self.muzzle_flash.alpha = 0.75;
-       self.muzzle_flash.angles_z = random() * 180;
-       self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       self.muzzle_flash.owner = self.muzzle_flash.realowner = self;
-}
-
-void W_UZI_Attack (float deathtype)
-{
-       W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? autocvar_g_balance_uzi_first_damage : autocvar_g_balance_uzi_sustained_damage));
-       if (!g_norecoil)
-       {
-               self.punchangle_x = random () - 0.5;
-               self.punchangle_y = random () - 0.5;
-       }
-
-       // this attack_finished just enforces a cooldown at the end of a burst
-       ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor();
-
-       if (self.misc_bulletcounter == 1)
-               fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_first_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_first_damage, autocvar_g_balance_uzi_first_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant);
-       else
-               fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_sustained_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant);
-       endFireBallisticBullet();
-
-       pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       UziFlash();
-       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
-
-       // casing code
-       if (autocvar_g_casings >= 2)
-               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
-
-       if (self.misc_bulletcounter == 1)
-               W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_first_ammo, autocvar_g_balance_uzi_reload_ammo);
-       else
-               W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo);
-}
-
-// weapon frames
-void uzi_fire1_02()
-{
-       if(self.weapon != self.switchweapon) // abort immediately if switching
-       {
-               w_ready();
-               return;
-       }
-       if (self.BUTTON_ATCK)
-       {
-               if (!weapon_action(self.weapon, WR_CHECKAMMO2))
-               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-               {
-                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                       w_ready();
-                       return;
-               }
-               self.misc_bulletcounter = self.misc_bulletcounter + 1;
-               W_UZI_Attack(WEP_UZI);
-               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02);
-       }
-       else
-               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, w_ready);
-}
-
-
-void uzi_mode1_fire_auto()
-{
-       float uzi_spread;
-
-       if (!self.BUTTON_ATCK)
-       {
-               w_ready();
-               return;
-       }
-
-       if (!weapon_action(self.weapon, WR_CHECKAMMO1))
-       if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-       {
-               W_SwitchWeapon_Force(self, w_getbestweapon(self));
-               w_ready();
-               return;
-       }
-
-       W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo);
-
-       W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage);
-       if (!g_norecoil)
-       {
-               self.punchangle_x = random () - 0.5;
-               self.punchangle_y = random () - 0.5;
-       }
-
-       uzi_spread = bound(autocvar_g_balance_uzi_spread_min, autocvar_g_balance_uzi_spread_min + (autocvar_g_balance_uzi_spread_add * self.misc_bulletcounter), autocvar_g_balance_uzi_spread_max);
-       fireBallisticBullet(w_shotorg, w_shotdir, uzi_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant);
-       endFireBallisticBullet();
-
-       self.misc_bulletcounter = self.misc_bulletcounter + 1;
-
-       pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       UziFlash();
-       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
-
-       if (autocvar_g_casings >= 2) // casing code
-               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
-
-       ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor();
-       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_mode1_fire_auto);
-}
-
-void uzi_mode1_fire_burst()
-{
-       W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage);
-       if (!g_norecoil)
-       {
-               self.punchangle_x = random () - 0.5;
-               self.punchangle_y = random () - 0.5;
-       }
-
-       fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_burst_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant);
-       endFireBallisticBullet();
-
-
-       pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       UziFlash();
-       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
-
-       if (autocvar_g_casings >= 2) // casing code
-               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
-
-       self.misc_bulletcounter = self.misc_bulletcounter + 1;
-       if (self.misc_bulletcounter == 0)
-       {
-               ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_burst_refire2 * W_WeaponRateFactor();
-               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_animtime, w_ready);
-       }
-       else
-       {
-               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_refire, uzi_mode1_fire_burst);
-       }
-
-}
-
-void spawnfunc_weapon_machinegun(); // defined in t_items.qc
-
-float w_uzi(float req)
-{
-       float ammo_amount;
-       if (req == WR_AIM)
-               if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200)
-                       self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
-               else
-               {
-                       self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
-               }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_uzi_reload_ammo && self.clip_load < min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else if(autocvar_g_balance_uzi_mode == 1)
-               {
-                       if (self.BUTTON_ATCK)
-                       if (weapon_prepareattack(0, 0))
-                       {
-                               self.misc_bulletcounter = 0;
-                               uzi_mode1_fire_auto();
-                       }
-
-                       if(self.BUTTON_ATCK2)
-                       if(weapon_prepareattack(1, 0))
-                       {
-                               if (!weapon_action(self.weapon, WR_CHECKAMMO2))
-                               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-                               {
-                                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
-                                       w_ready();
-                                       return FALSE;
-                               }
-
-                               W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_burst_ammo, autocvar_g_balance_uzi_reload_ammo);
-
-                               self.misc_bulletcounter = autocvar_g_balance_uzi_burst * -1;
-                               uzi_mode1_fire_burst();
-                       }
-               }
-               else
-               {
-
-                       if (self.BUTTON_ATCK)
-                       if (weapon_prepareattack(0, 0))
-                       {
-                               self.misc_bulletcounter = 1;
-                               W_UZI_Attack(WEP_UZI); // sets attack_finished
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02);
-                       }
-
-                       if (self.BUTTON_ATCK2 && autocvar_g_balance_uzi_first)
-                       if (weapon_prepareattack(1, 0))
-                       {
-                               self.misc_bulletcounter = 1;
-                               W_UZI_Attack(WEP_UZI | HITTYPE_SECONDARY); // sets attack_finished
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_first_refire, w_ready);
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/uziflash.md3");
-               precache_model ("models/weapons/g_uzi.md3");
-               precache_model ("models/weapons/v_uzi.md3");
-               precache_model ("models/weapons/h_uzi.iqm");
-               precache_sound ("weapons/uzi_fire.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_UZI);
-               self.current_ammo = ammo_nails;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               if(autocvar_g_balance_uzi_mode == 1)
-                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_sustained_ammo;
-               else
-                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo;
-
-               if(autocvar_g_balance_uzi_reload_ammo)
-               {
-                       if(autocvar_g_balance_uzi_mode == 1)
-                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_sustained_ammo;
-                       else
-                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo;
-               }
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               if(autocvar_g_balance_uzi_mode == 1)
-                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_burst_ammo;
-               else
-                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo;
-
-               if(autocvar_g_balance_uzi_reload_ammo)
-               {
-                       if(autocvar_g_balance_uzi_mode == 1)
-                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_burst_ammo;
-                       else
-                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo;
-               }
-               return ammo_amount;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo), autocvar_g_balance_uzi_reload_ammo, autocvar_g_balance_uzi_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_THINKING_WITH_PORTALS;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_UZI_MURDER_SNIPE;
-               else
-                       return WEAPON_UZI_MURDER_SPRAY;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_uzi(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 2;
-               pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1);
-               if(!w_issilent)
-                       if(w_random < 0.05)
-                               sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
-                       else if(w_random < 0.1)
-                               sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
-                       else if(w_random < 0.2)
-                               sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/ric1.wav");
-               precache_sound("weapons/ric2.wav");
-               precache_sound("weapons/ric3.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/minelayer.qc b/qcsrc/common/weapons/minelayer.qc
deleted file mode 100644 (file)
index b27d069..0000000
+++ /dev/null
@@ -1,563 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ MINE_LAYER,
-/* function  */ w_minelayer,
-/* ammotype  */ IT_ROCKETS,
-/* impulse   */ 4,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* model     */ "minelayer",
-/* shortname */ "minelayer",
-/* fullname  */ _("Mine Layer")
-);
-#else
-#ifdef SVQC
-void W_Mine_Think (void);
-.float minelayer_detonate, mine_explodeanyway;
-.float mine_time;
-.vector mine_orientation;
-
-void spawnfunc_weapon_minelayer (void)
-{
-       weapon_defaultspawnfunc(WEP_MINE_LAYER);
-}
-
-void W_Mine_Stick (entity to)
-{
-       spamsound (self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
-
-       // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile
-
-       entity newmine;
-       newmine = spawn();
-       newmine.classname = self.classname;
-
-       newmine.bot_dodge = self.bot_dodge;
-       newmine.bot_dodgerating = self.bot_dodgerating;
-
-       newmine.owner = self.owner;
-       newmine.realowner = self.realowner;
-       setsize(newmine, '-4 -4 -4', '4 4 4');
-       setorigin(newmine, self.origin);
-       setmodel(newmine, "models/mine.md3");
-       newmine.angles = vectoangles(-trace_plane_normal); // face against the surface
-
-       newmine.mine_orientation = -trace_plane_normal;
-
-       newmine.takedamage = self.takedamage;
-       newmine.damageforcescale = self.damageforcescale;
-       newmine.health = self.health;
-       newmine.event_damage = self.event_damage;
-       newmine.spawnshieldtime = self.spawnshieldtime;
-       newmine.damagedbycontents = TRUE;
-
-       newmine.movetype = MOVETYPE_NONE; // lock the mine in place
-       newmine.projectiledeathtype = self.projectiledeathtype;
-
-       newmine.mine_time = self.mine_time;
-
-       newmine.touch = func_null;
-       newmine.think = W_Mine_Think;
-       newmine.nextthink = time;
-       newmine.cnt = self.cnt;
-       newmine.flags = self.flags;
-
-       remove(self);
-       self = newmine;
-
-       if(to)
-               SetMovetypeFollow(self, to);
-}
-
-void W_Mine_Explode ()
-{
-       if(other.takedamage == DAMAGE_AIM)
-               if(IS_PLAYER(other))
-                       if(IsDifferentTeam(self.realowner, other))
-                               if(other.deadflag == DEAD_NO)
-                                       if(IsFlying(other))
-                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_damage, autocvar_g_balance_minelayer_edgedamage, autocvar_g_balance_minelayer_radius, world, world, autocvar_g_balance_minelayer_force, self.projectiledeathtype, other);
-
-       if (self.realowner.weapon == WEP_MINE_LAYER)
-       {
-               entity oldself;
-               oldself = self;
-               self = self.realowner;
-               if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1))
-               {
-                       self.cnt = WEP_MINE_LAYER;
-                       ATTACK_FINISHED(self) = time;
-                       self.switchweapon = w_getbestweapon(self);
-               }
-               self = oldself;
-       }
-       self.realowner.minelayer_mines -= 1;
-       remove (self);
-}
-
-void W_Mine_DoRemoteExplode ()
-{
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
-               self.velocity = self.mine_orientation; // particle fx and decals need .velocity
-
-       RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_remote_damage, autocvar_g_balance_minelayer_remote_edgedamage, autocvar_g_balance_minelayer_remote_radius, world, world, autocvar_g_balance_minelayer_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world);
-
-       if (self.realowner.weapon == WEP_MINE_LAYER)
-       {
-               entity oldself;
-               oldself = self;
-               self = self.realowner;
-               if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1))
-               {
-                       self.cnt = WEP_MINE_LAYER;
-                       ATTACK_FINISHED(self) = time;
-                       self.switchweapon = w_getbestweapon(self);
-               }
-               self = oldself;
-       }
-       self.realowner.minelayer_mines -= 1;
-       remove (self);
-}
-
-void W_Mine_RemoteExplode ()
-{
-       if(self.realowner.deadflag == DEAD_NO)
-               if((self.spawnshieldtime >= 0)
-                       ? (time >= self.spawnshieldtime) // timer
-                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_minelayer_remote_radius) // safety device
-               )
-               {
-                       W_Mine_DoRemoteExplode();
-               }
-}
-
-void W_Mine_ProximityExplode ()
-{
-       // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
-       if(autocvar_g_balance_minelayer_protection && self.mine_explodeanyway == 0)
-       {
-               entity head;
-               head = findradius(self.origin, autocvar_g_balance_minelayer_radius);
-               while(head)
-               {
-                       if(head == self.realowner || !IsDifferentTeam(head, self.realowner))
-                               return;
-                       head = head.chain;
-               }
-       }
-
-       self.mine_time = 0;
-       W_Mine_Explode();
-}
-
-float W_Mine_Count(entity e)
-{
-       float minecount = 0;
-       entity mine;
-       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e)
-               minecount += 1;
-
-       return minecount;
-}
-
-void W_Mine_Think (void)
-{
-       entity head;
-
-       self.nextthink = time;
-
-       if(self.movetype == MOVETYPE_FOLLOW)
-       {
-               if(LostMovetypeFollow(self))
-               {
-                       UnsetMovetypeFollow(self);
-                       self.movetype = MOVETYPE_NONE;
-               }
-       }
-       
-       // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this
-       // TODO: replace this mine_trigger.wav sound with a real countdown
-       if ((time > self.cnt) && (!self.mine_time))
-       {
-               if(autocvar_g_balance_minelayer_lifetime_countdown > 0)
-                       spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
-               self.mine_time = time + autocvar_g_balance_minelayer_lifetime_countdown;
-               self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near.
-       }
-
-       // a player's mines shall explode if he disconnects or dies
-       // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams?
-       if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO)
-       {
-               other = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               W_Mine_Explode();
-               return;
-       }
-
-       // set the mine for detonation when a foe gets close enough
-       head = findradius(self.origin, autocvar_g_balance_minelayer_proximityradius);
-       while(head)
-       {
-               if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
-               if(head != self.realowner && IsDifferentTeam(head, self.realowner)) // don't trigger for team mates
-               if(!self.mine_time)
-               {
-                       spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
-                       self.mine_time = time + autocvar_g_balance_minelayer_time;
-               }
-               head = head.chain;
-       }
-
-       // explode if it's time to
-       if(self.mine_time && time >= self.mine_time)
-       {
-               W_Mine_ProximityExplode();
-               return;
-       }
-
-       // remote detonation
-       if (self.realowner.weapon == WEP_MINE_LAYER)
-       if (self.realowner.deadflag == DEAD_NO)
-       if (self.minelayer_detonate)
-               W_Mine_RemoteExplode();
-}
-
-void W_Mine_Touch (void)
-{
-       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
-               return; // we're already a stuck mine, why do we get called? TODO does this even happen?
-
-       if(WarpZone_Projectile_Touch())
-       {
-               if(wasfreed(self))
-                       self.realowner.minelayer_mines -= 1;
-               return;
-       }
-
-       if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO)
-       {
-               // hit a player
-               // don't stick
-       }
-       else
-       {
-               W_Mine_Stick(other);
-       }
-}
-
-void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-               
-       float is_from_enemy = (inflictor.realowner != self.realowner);
-               
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1)))
-               return; // g_projectiles_damage says to halt
-               
-       self.health = self.health - damage;
-       self.angles = vectoangles(self.velocity);
-       
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
-}
-
-void W_Mine_Attack (void)
-{
-       entity mine;
-       entity flash;
-
-       // scan how many mines we placed, and return if we reached our limit
-       if(autocvar_g_balance_minelayer_limit)
-       {
-               if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit)
-               {
-                       // the refire delay keeps this message from being spammed
-                       sprint(self, strcat("minelayer: You cannot place more than ^2", ftos(autocvar_g_balance_minelayer_limit), " ^7mines at a time\n") );
-                       play2(self, "weapons/unavailable.wav");
-                       return;
-               }
-       }
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo);
-
-       W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, autocvar_g_balance_minelayer_damage);
-       pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       mine = WarpZone_RefSys_SpawnSameRefSys(self);
-       mine.owner = mine.realowner = self;
-       if(autocvar_g_balance_minelayer_detonatedelay >= 0)
-               mine.spawnshieldtime = time + autocvar_g_balance_minelayer_detonatedelay;
-       else
-               mine.spawnshieldtime = -1;
-       mine.classname = "mine";
-       mine.bot_dodge = TRUE;
-       mine.bot_dodgerating = autocvar_g_balance_minelayer_damage * 2; // * 2 because it can detonate inflight which makes it even more dangerous
-
-       mine.takedamage = DAMAGE_YES;
-       mine.damageforcescale = autocvar_g_balance_minelayer_damageforcescale;
-       mine.health = autocvar_g_balance_minelayer_health;
-       mine.event_damage = W_Mine_Damage;
-       mine.damagedbycontents = TRUE;
-
-       mine.movetype = MOVETYPE_TOSS;
-       PROJECTILE_MAKETRIGGER(mine);
-       mine.projectiledeathtype = WEP_MINE_LAYER;
-       setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
-
-       setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
-       W_SetupProjectileVelocity(mine, autocvar_g_balance_minelayer_speed, 0);
-       mine.angles = vectoangles (mine.velocity);
-
-       mine.touch = W_Mine_Touch;
-       mine.think = W_Mine_Think;
-       mine.nextthink = time;
-       mine.cnt = time + (autocvar_g_balance_minelayer_lifetime - autocvar_g_balance_minelayer_lifetime_countdown);
-       mine.flags = FL_PROJECTILE;
-       mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY;
-
-       CSQCProjectile(mine, TRUE, PROJECTILE_MINE, TRUE);
-
-       // muzzle flash for 1st person view
-       flash = spawn ();
-       setmodel (flash, "models/flash.md3"); // precision set below
-       SUB_SetFade (flash, time, 0.1);
-       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
-
-       // common properties
-
-       other = mine; MUTATOR_CALLHOOK(EditProjectile);
-       
-       self.minelayer_mines = W_Mine_Count(self);
-}
-
-float W_PlacedMines(float detonate)
-{
-       entity mine;
-       float minfound = 0;
-
-       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self)
-       {
-               if(detonate)
-               {
-                       if(!mine.minelayer_detonate)
-                       {
-                               mine.minelayer_detonate = TRUE;
-                               minfound = 1;
-                       }
-               }
-               else
-                       minfound = 1;
-       }
-       return minfound;
-}
-
-float w_minelayer(float req)
-{
-       entity mine;
-       float ammo_amount;
-
-       if (req == WR_AIM)
-       {
-               // aim and decide to fire if appropriate
-               if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit)
-                       self.BUTTON_ATCK = FALSE;
-               else
-                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_minelayer_speed, 0, autocvar_g_balance_minelayer_lifetime, FALSE);
-               if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
-               {
-                       // decide whether to detonate mines
-                       entity targetlist, targ;
-                       float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
-                       float selfdamage, teamdamage, enemydamage;
-                       edgedamage = autocvar_g_balance_minelayer_edgedamage;
-                       coredamage = autocvar_g_balance_minelayer_damage;
-                       edgeradius = autocvar_g_balance_minelayer_radius;
-                       recipricoledgeradius = 1 / edgeradius;
-                       selfdamage = 0;
-                       teamdamage = 0;
-                       enemydamage = 0;
-                       targetlist = findchainfloat(bot_attack, TRUE);
-                       mine = find(world, classname, "mine");
-                       while (mine)
-                       {
-                               if (mine.realowner != self)
-                               {
-                                       mine = find(mine, classname, "mine");
-                                       continue;
-                               }
-                               targ = targetlist;
-                               while (targ)
-                               {
-                                       d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
-                                       d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
-                                       // count potential damage according to type of target
-                                       if (targ == self)
-                                               selfdamage = selfdamage + d;
-                                       else if (targ.team == self.team && teamplay)
-                                               teamdamage = teamdamage + d;
-                                       else if (bot_shouldattack(targ))
-                                               enemydamage = enemydamage + d;
-                                       targ = targ.chain;
-                               }
-                               mine = find(mine, classname, "mine");
-                       }
-                       float desirabledamage;
-                       desirabledamage = enemydamage;
-                       if (time > self.invincible_finished && time > self.spawnshieldtime)
-                               desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
-                       if (teamplay && self.team)
-                               desirabledamage = desirabledamage - teamdamage;
-
-                       mine = find(world, classname, "mine");
-                       while (mine)
-                       {
-                               if (mine.realowner != self)
-                               {
-                                       mine = find(mine, classname, "mine");
-                                       continue;
-                               }
-                               makevectors(mine.v_angle);
-                               targ = targetlist;
-                               if (skill > 9) // normal players only do this for the target they are tracking
-                               {
-                                       targ = targetlist;
-                                       while (targ)
-                                       {
-                                               if (
-                                                       (v_forward * normalize(mine.origin - targ.origin)< 0.1)
-                                                       && desirabledamage > 0.1*coredamage
-                                               )self.BUTTON_ATCK2 = TRUE;
-                                               targ = targ.chain;
-                                       }
-                               }else{
-                                       float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
-                                       //As the distance gets larger, a correct detonation gets near imposible
-                                       //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
-                                       if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
-                                               if(IS_PLAYER(self.enemy))
-                                                       if(desirabledamage >= 0.1*coredamage)
-                                                               if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
-                                                                       self.BUTTON_ATCK2 = TRUE;
-                               //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
-                               }
-
-                               mine = find(mine, classname, "mine");
-                       }
-                       // if we would be doing at X percent of the core damage, detonate it
-                       // but don't fire a new shot at the same time!
-                       if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
-                               self.BUTTON_ATCK2 = TRUE;
-                       if ((skill > 6.5) && (selfdamage > self.health))
-                               self.BUTTON_ATCK2 = FALSE;
-                       //if(self.BUTTON_ATCK2 == TRUE)
-                       //      dprint(ftos(desirabledamage),"\n");
-                       if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < autocvar_g_balance_minelayer_ammo) // forced reload
-               {
-                       // not if we're holding the minelayer without enough ammo, but can detonate existing mines
-                       if not (W_PlacedMines(FALSE) && self.ammo_rockets < autocvar_g_balance_minelayer_ammo)
-                               weapon_action(self.weapon, WR_RELOAD);
-               }
-               else if (self.BUTTON_ATCK)
-               {
-                       if(weapon_prepareattack(0, autocvar_g_balance_minelayer_refire))
-                       {
-                               W_Mine_Attack();
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minelayer_animtime, w_ready);
-                       }
-               }
-
-               if (self.BUTTON_ATCK2)
-               {
-                       if(W_PlacedMines(TRUE))
-                               sound (self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/flash.md3");
-               precache_model ("models/mine.md3");
-               precache_model ("models/weapons/g_minelayer.md3");
-               precache_model ("models/weapons/v_minelayer.md3");
-               precache_model ("models/weapons/h_minelayer.iqm");
-               precache_sound ("weapons/mine_det.wav");
-               precache_sound ("weapons/mine_fire.wav");
-               precache_sound ("weapons/mine_stick.wav");
-               precache_sound ("weapons/mine_trigger.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_MINE_LAYER);
-               self.current_ammo = ammo_rockets;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               // don't switch while placing a mine
-               if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
-               {
-                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_minelayer_ammo;
-                       ammo_amount += self.(weapon_load[WEP_MINE_LAYER]) >= autocvar_g_balance_minelayer_ammo;
-                       return ammo_amount;
-               }
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               if (W_PlacedMines(FALSE))
-                       return TRUE;
-               else
-                       return FALSE;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.minelayer_mines = 0;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo, autocvar_g_balance_minelayer_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_MINELAYER_SUICIDE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               return WEAPON_MINELAYER_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_minelayer(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 12;
-               pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
-               if(!w_issilent)
-                       sound(self, CH_SHOTS, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/mine_exp.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/minstanex.qc b/qcsrc/common/weapons/minstanex.qc
deleted file mode 100644 (file)
index 2abb668..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ MINSTANEX,
-/* function  */ w_minstanex,
-/* ammotype  */ IT_CELLS,
-/* impulse   */ 7,
-/* flags     */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* model     */ "minstanex",
-/* shortname */ "minstanex",
-/* fullname  */ _("MinstaNex")
-);
-#else
-#ifdef SVQC
-.float minstanex_lasthit;
-.float jump_interval;
-
-void W_MinstaNex_Attack (void)
-{
-       float flying;
-       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
-
-       W_SetupShot (self, TRUE, 0, "weapons/minstanexfire.wav", CH_WEAPON_A, 10000);
-
-       yoda = 0;
-       damage_goodhits = 0;
-       FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, 10000, 800, 0, 0, 0, 0, WEP_MINSTANEX);
-
-       if(yoda && flying)
-               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
-       if(damage_goodhits && self.minstanex_lasthit)
-       {
-               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE);
-               damage_goodhits = 0; // only every second time
-       }
-
-       self.minstanex_lasthit = damage_goodhits;
-
-       pointparticles(particleeffectnum("nex_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       // teamcolor / hit beam effect
-       vector v;
-       v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
-       switch(self.team)
-       {
-               case NUM_TEAM_1:   // Red
-                       if(damage_goodhits)
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED_HIT"), w_shotorg, v);
-                       else
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED"), w_shotorg, v);
-                       break;
-               case NUM_TEAM_2:   // Blue
-                       if(damage_goodhits)
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE_HIT"), w_shotorg, v);
-                       else
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE"), w_shotorg, v);
-                       break;
-               case NUM_TEAM_3:   // Yellow
-                       if(damage_goodhits)
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW_HIT"), w_shotorg, v);
-                       else
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW"), w_shotorg, v);
-                       break;
-               case NUM_TEAM_4:   // Pink
-                       if(damage_goodhits)
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK_HIT"), w_shotorg, v);
-                       else
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK"), w_shotorg, v);
-                       break;
-               default:
-                       if(damage_goodhits)
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3_HIT"), w_shotorg, v);
-                       else
-                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), w_shotorg, v);
-                       break;
-       }
-       
-       W_DecreaseAmmo(ammo_cells, ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo), autocvar_g_balance_minstanex_reload_ammo);
-}
-
-void spawnfunc_weapon_minstanex (void); // defined in t_items.qc
-
-float w_minstanex(float req)
-{
-       float ammo_amount;
-       float minstanex_ammo;
-
-       // now multiple WR_s use this
-       minstanex_ammo = ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo);
-
-       if (req == WR_AIM)
-       {
-               if(self.ammo_cells > 0)
-                       self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE);
-               else
-                       self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
-       }
-       else if (req == WR_THINK)
-       {
-               // if the laser uses load, we also consider its ammo for reloading
-               if(autocvar_g_balance_minstanex_reload_ammo && autocvar_g_balance_minstanex_laser_ammo && self.clip_load < min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else if(autocvar_g_balance_minstanex_reload_ammo && self.clip_load < minstanex_ammo) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else if (self.BUTTON_ATCK)
-               {
-                       if (weapon_prepareattack(0, autocvar_g_balance_minstanex_refire))
-                       {
-                               W_MinstaNex_Attack();
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minstanex_animtime, w_ready);
-                       }
-               }
-               else if (self.BUTTON_ATCK2)
-               {
-                       if (self.jump_interval <= time)
-                       if (weapon_prepareattack(1, -1))
-                       {
-                               // handle refire manually, so that primary and secondary can be fired without conflictions (important for minstagib)
-                               self.jump_interval = time + autocvar_g_balance_minstanex_laser_refire * W_WeaponRateFactor();
-                               
-                               // decrease ammo for the laser?
-                               if(autocvar_g_balance_minstanex_laser_ammo)
-                                       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_minstanex_laser_ammo, autocvar_g_balance_minstanex_reload_ammo);
-
-                               // ugly minstagib hack to reuse the fire mode of the laser
-                               float w;
-                               w = self.weapon;
-                               self.weapon = WEP_LASER;
-                               W_Laser_Shockwave();
-                               self.weapon = w;
-                               
-                               // now do normal refire
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_minstanex_laser_animtime, w_ready);
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/nexflash.md3");
-               precache_model ("models/weapons/g_minstanex.md3");
-               precache_model ("models/weapons/v_minstanex.md3");
-               precache_model ("models/weapons/h_minstanex.iqm");
-               precache_sound ("weapons/minstanexfire.wav");
-               precache_sound ("weapons/nexwhoosh1.wav");
-               precache_sound ("weapons/nexwhoosh2.wav");
-               precache_sound ("weapons/nexwhoosh3.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-               W_Laser(WR_PRECACHE);
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_MINSTANEX);
-               self.current_ammo = ammo_cells;
-               self.minstanex_lasthit = 0;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               ammo_amount = self.ammo_cells >= minstanex_ammo;
-               ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= minstanex_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               if(!autocvar_g_balance_minstanex_laser_ammo)
-                       return TRUE;
-               ammo_amount = self.ammo_cells >= autocvar_g_balance_minstanex_laser_ammo;
-               ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= autocvar_g_balance_minstanex_laser_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.minstanex_lasthit = 0;
-       }
-       else if (req == WR_RELOAD)
-       {
-               float used_ammo;
-               if(autocvar_g_balance_minstanex_laser_ammo)
-                       used_ammo = min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo);
-               else
-                       used_ammo = minstanex_ammo;
-
-               W_Reload(used_ammo, autocvar_g_balance_minstanex_reload_ammo, autocvar_g_balance_minstanex_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_THINKING_WITH_PORTALS;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               return WEAPON_MINSTANEX_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_minstanex(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 6;
-               pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1);
-               if(!w_issilent)
-                       sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/neximpact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/mortar.qc b/qcsrc/common/weapons/mortar.qc
deleted file mode 100644 (file)
index 8b8a1a0..0000000
+++ /dev/null
@@ -1,414 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ GRENADE_LAUNCHER,
-/* function  */ w_glauncher,
-/* ammotype  */ IT_ROCKETS,
-/* impulse   */ 4,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "gl",
-/* shortname */ "grenadelauncher",
-/* fullname  */ _("Mortar")
-);
-#else
-#ifdef SVQC
-.float gl_detonate_later;
-.float gl_bouncecnt;
-
-void W_Grenade_Explode (void)
-{
-       if(other.takedamage == DAMAGE_AIM)
-               if(IS_PLAYER(other))
-                       if(IsDifferentTeam(self.realowner, other))
-                               if(other.deadflag == DEAD_NO)
-                                       if(IsFlying(other))
-                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_NONE)
-               self.velocity = self.oldvelocity;
-
-       RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_primary_damage, autocvar_g_balance_grenadelauncher_primary_edgedamage, autocvar_g_balance_grenadelauncher_primary_radius, world, world, autocvar_g_balance_grenadelauncher_primary_force, self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void W_Grenade_Explode2 (void)
-{
-       if(other.takedamage == DAMAGE_AIM)
-               if(IS_PLAYER(other))
-                       if(IsDifferentTeam(self.realowner, other))
-                               if(other.deadflag == DEAD_NO)
-                                       if(IsFlying(other))
-                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_NONE)
-               self.velocity = self.oldvelocity;
-
-       RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_secondary_damage, autocvar_g_balance_grenadelauncher_secondary_edgedamage, autocvar_g_balance_grenadelauncher_secondary_radius, world, world, autocvar_g_balance_grenadelauncher_secondary_force, self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void W_Grenade_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-               
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-               
-       self.health = self.health - damage;
-       
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, self.use);
-}
-
-void W_Grenade_Think1 (void)
-{
-       self.nextthink = time;
-       if (time > self.cnt)
-       {
-               other = world;
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               W_Grenade_Explode ();
-               return;
-       }
-       if(self.gl_detonate_later && self.gl_bouncecnt >= autocvar_g_balance_grenadelauncher_primary_remote_minbouncecnt)
-               W_Grenade_Explode();
-}
-
-void W_Grenade_Touch1 (void)
-{
-       PROJECTILE_TOUCH;
-       if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_primary_type == 0) // always explode when hitting a player, or if normal mortar projectile
-       {
-               self.use ();
-       }
-       else if (autocvar_g_balance_grenadelauncher_primary_type == 1) // bounce
-       {
-               float r;
-               r = random() * 6;
-               if(r < 1)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 2)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 3)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 4)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 5)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
-               else
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               self.gl_bouncecnt += 1;
-       }
-       else if(autocvar_g_balance_grenadelauncher_primary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
-       {
-               spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
-
-               // let it stick whereever it is
-               self.oldvelocity = self.velocity;
-               self.velocity = '0 0 0';
-               self.movetype = MOVETYPE_NONE; // also disables gravity
-               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
-               UpdateCSQCProjectile(self);
-
-               // do not respond to any more touches
-               self.solid = SOLID_NOT;
-
-               self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_primary_lifetime_stick);
-       }
-}
-
-void W_Grenade_Touch2 (void)
-{
-       PROJECTILE_TOUCH;
-       if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_secondary_type == 0) // always explode when hitting a player, or if normal mortar projectile
-       {
-               self.use ();
-       }
-       else if (autocvar_g_balance_grenadelauncher_secondary_type == 1) // bounce
-       {
-               float r;
-               r = random() * 6;
-               if(r < 1)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 2)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 3)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 4)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
-               else if(r < 5)
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
-               else
-                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
-               self.projectiledeathtype |= HITTYPE_BOUNCE;
-               self.gl_bouncecnt += 1;
-               
-               if (autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce && self.gl_bouncecnt == 1)
-                       self.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce;
-                       
-       }
-       else if(autocvar_g_balance_grenadelauncher_secondary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
-       {
-               spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
-
-               // let it stick whereever it is
-               self.oldvelocity = self.velocity;
-               self.velocity = '0 0 0';
-               self.movetype = MOVETYPE_NONE; // also disables gravity
-               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
-               UpdateCSQCProjectile(self);
-
-               // do not respond to any more touches
-               self.solid = SOLID_NOT;
-
-               self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_secondary_lifetime_stick);
-       }
-}
-
-void W_Grenade_Attack (void)
-{
-       entity gren;
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo);
-
-       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_primary_damage);
-       w_shotdir = v_forward; // no TrueAim for grenades please
-
-       pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       gren = spawn ();
-       gren.owner = gren.realowner = self;
-       gren.classname = "grenade";
-       gren.bot_dodge = TRUE;
-       gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_primary_damage;
-       gren.movetype = MOVETYPE_BOUNCE;
-       gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor;
-       gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop;
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_GRENADE_LAUNCHER;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '-3 -3 -3', '3 3 3');
-
-       gren.cnt = time + autocvar_g_balance_grenadelauncher_primary_lifetime;
-       gren.nextthink = time;
-       gren.think = W_Grenade_Think1;
-       gren.use = W_Grenade_Explode;
-       gren.touch = W_Grenade_Touch1;
-
-       gren.takedamage = DAMAGE_YES;
-       gren.health = autocvar_g_balance_grenadelauncher_primary_health;
-       gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale;
-       gren.event_damage = W_Grenade_Damage;
-       gren.damagedbycontents = TRUE;
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-       W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary);
-
-       gren.angles = vectoangles (gren.velocity);
-       gren.flags = FL_PROJECTILE;
-
-       if(autocvar_g_balance_grenadelauncher_primary_type == 0 || autocvar_g_balance_grenadelauncher_primary_type == 2)
-               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
-       else
-               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
-
-       other = gren; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void W_Grenade_Attack2 (void)
-{
-       entity gren;
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_secondary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo);
-
-       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_secondary_damage);
-       w_shotdir = v_forward; // no TrueAim for grenades please
-
-       pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       gren = spawn ();
-       gren.owner = gren.realowner = self;
-       gren.classname = "grenade";
-       gren.bot_dodge = TRUE;
-       gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_secondary_damage;
-       gren.movetype = MOVETYPE_BOUNCE;
-       gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor;
-       gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop;
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '-3 -3 -3', '3 3 3');
-
-       gren.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime;
-       gren.think = adaptor_think2use_hittype_splash;
-       gren.use = W_Grenade_Explode2;
-       gren.touch = W_Grenade_Touch2;
-
-       gren.takedamage = DAMAGE_YES;
-       gren.health = autocvar_g_balance_grenadelauncher_secondary_health;
-       gren.damageforcescale = autocvar_g_balance_grenadelauncher_secondary_damageforcescale;
-       gren.event_damage = W_Grenade_Damage;
-       gren.damagedbycontents = TRUE;
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-       W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_secondary);
-
-       gren.angles = vectoangles (gren.velocity);
-       gren.flags = FL_PROJECTILE;
-
-       if(autocvar_g_balance_grenadelauncher_secondary_type == 0 || autocvar_g_balance_grenadelauncher_secondary_type == 2)
-               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
-       else
-               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
-
-       other = gren; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void spawnfunc_weapon_grenadelauncher (void)
-{
-       weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER);
-}
-
-.float bot_secondary_grenademooth;
-float w_glauncher(float req)
-{
-       entity nade;
-       float nadefound;
-       float ammo_amount;
-
-       if (req == WR_AIM)
-       {
-               self.BUTTON_ATCK = FALSE;
-               self.BUTTON_ATCK2 = FALSE;
-               if (self.bot_secondary_grenademooth == 0)
-               {
-                       if(bot_aim(autocvar_g_balance_grenadelauncher_primary_speed, autocvar_g_balance_grenadelauncher_primary_speed_up, autocvar_g_balance_grenadelauncher_primary_lifetime, TRUE))
-                       {
-                               self.BUTTON_ATCK = TRUE;
-                               if(random() < 0.01) self.bot_secondary_grenademooth = 1;
-                       }
-               }
-               else
-               {
-                       if(bot_aim(autocvar_g_balance_grenadelauncher_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_grenadelauncher_secondary_lifetime, TRUE))
-                       {
-                               self.BUTTON_ATCK2 = TRUE;
-                               if(random() < 0.02) self.bot_secondary_grenademooth = 0;
-                       }
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_grenadelauncher_reload_ammo && self.clip_load < min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else if (self.BUTTON_ATCK)
-               {
-                       if (weapon_prepareattack(0, autocvar_g_balance_grenadelauncher_primary_refire))
-                       {
-                               W_Grenade_Attack();
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_grenadelauncher_primary_animtime, w_ready);
-                       }
-               }
-               else if (self.BUTTON_ATCK2)
-               {
-                       if (cvar("g_balance_grenadelauncher_secondary_remote_detonateprimary"))
-                       {
-                               nadefound = 0;
-                               for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self)
-                               {
-                                       if(!nade.gl_detonate_later)
-                                       {
-                                               nade.gl_detonate_later = TRUE;
-                                               nadefound = 1;
-                                       }
-                               }
-                               if(nadefound)
-                                       sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
-                       }
-                       else if (weapon_prepareattack(1, autocvar_g_balance_grenadelauncher_secondary_refire))
-                       {
-                               W_Grenade_Attack2();
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_grenadelauncher_secondary_animtime, w_ready);
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_gl.md3");
-               precache_model ("models/weapons/v_gl.md3");
-               precache_model ("models/weapons/h_gl.iqm");
-               precache_sound ("weapons/grenade_bounce1.wav");
-               precache_sound ("weapons/grenade_bounce2.wav");
-               precache_sound ("weapons/grenade_bounce3.wav");
-               precache_sound ("weapons/grenade_bounce4.wav");
-               precache_sound ("weapons/grenade_bounce5.wav");
-               precache_sound ("weapons/grenade_bounce6.wav");
-               precache_sound ("weapons/grenade_stick.wav");
-               precache_sound ("weapons/grenade_fire.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_GRENADE_LAUNCHER);
-               self.current_ammo = ammo_rockets;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_primary_ammo;
-               ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_primary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_secondary_ammo;
-               ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_secondary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo), autocvar_g_balance_grenadelauncher_reload_ammo, autocvar_g_balance_grenadelauncher_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_MORTAR_SUICIDE_BOUNCE;
-               else
-                       return WEAPON_MORTAR_SUICIDE_EXPLODE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_MORTAR_MURDER_BOUNCE;
-               else
-                       return WEAPON_MORTAR_MURDER_EXPLODE;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_glauncher(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 12;
-               pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1);
-               if(!w_issilent)
-                       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/grenade_impact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/nex.qc b/qcsrc/common/weapons/nex.qc
deleted file mode 100644 (file)
index dc3c30f..0000000
+++ /dev/null
@@ -1,275 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ NEX,
-/* function  */ w_nex,
-/* ammotype  */ IT_CELLS,
-/* impulse   */ 7,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* model     */ "nex",
-/* shortname */ "nex",
-/* fullname  */ _("Nex")
-);
-#else
-#ifdef SVQC
-
-void SendCSQCNexBeamParticle(float charge) {
-       vector v;
-       v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
-       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
-       WriteByte(MSG_BROADCAST, TE_CSQC_NEXGUNBEAMPARTICLE);
-       WriteCoord(MSG_BROADCAST, w_shotorg_x);
-       WriteCoord(MSG_BROADCAST, w_shotorg_y);
-       WriteCoord(MSG_BROADCAST, w_shotorg_z);
-       WriteCoord(MSG_BROADCAST, v_x);
-       WriteCoord(MSG_BROADCAST, v_y);
-       WriteCoord(MSG_BROADCAST, v_z);
-       WriteByte(MSG_BROADCAST, bound(0, 255 * charge, 255));
-}
-
-void W_Nex_Attack (float issecondary)
-{
-       float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge;
-       if(issecondary)
-       {
-               mydmg = autocvar_g_balance_nex_secondary_damage;
-               myforce = autocvar_g_balance_nex_secondary_force;
-               mymindist = autocvar_g_balance_nex_secondary_damagefalloff_mindist;
-               mymaxdist = autocvar_g_balance_nex_secondary_damagefalloff_maxdist;
-               myhalflife = autocvar_g_balance_nex_secondary_damagefalloff_halflife;
-               myforcehalflife = autocvar_g_balance_nex_secondary_damagefalloff_forcehalflife;
-               myammo = autocvar_g_balance_nex_secondary_ammo;
-       }
-       else
-       {
-               mydmg = autocvar_g_balance_nex_primary_damage;
-               myforce = autocvar_g_balance_nex_primary_force;
-               mymindist = autocvar_g_balance_nex_primary_damagefalloff_mindist;
-               mymaxdist = autocvar_g_balance_nex_primary_damagefalloff_maxdist;
-               myhalflife = autocvar_g_balance_nex_primary_damagefalloff_halflife;
-               myforcehalflife = autocvar_g_balance_nex_primary_damagefalloff_forcehalflife;
-               myammo = autocvar_g_balance_nex_primary_ammo;
-       }
-
-       float flying;
-       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
-
-       if(autocvar_g_balance_nex_charge)
-       {
-               charge = autocvar_g_balance_nex_charge_mindmg / mydmg + (1 - autocvar_g_balance_nex_charge_mindmg / mydmg) * self.nex_charge;
-               self.nex_charge *= autocvar_g_balance_nex_charge_shot_multiplier; // do this AFTER setting mydmg/myforce
-               // O RLY? -- divVerent
-               // YA RLY -- FruitieX
-       }
-       else
-               charge = 1;
-       mydmg *= charge;
-       myforce *= charge;
-
-       W_SetupShot (self, TRUE, 5, "weapons/nexfire.wav", CH_WEAPON_A, mydmg);
-       if(charge > autocvar_g_balance_nex_charge_animlimit && autocvar_g_balance_nex_charge_animlimit) // if the Nex is overcharged, we play an extra sound
-       {
-               sound (self, CH_WEAPON_B, "weapons/nexcharge.wav", VOL_BASE * (charge - 0.5 * autocvar_g_balance_nex_charge_animlimit) / (1 - 0.5 * autocvar_g_balance_nex_charge_animlimit), ATTN_NORM);
-       }
-
-       yoda = 0;
-       FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_NEX);
-
-       if(yoda && flying)
-               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); 
-
-       //beam and muzzle flash done on client
-       SendCSQCNexBeamParticle(charge);
-
-       W_DecreaseAmmo(ammo_cells, myammo, autocvar_g_balance_nex_reload_ammo);
-}
-
-void spawnfunc_weapon_nex (void); // defined in t_items.qc
-
-.float nex_chargepool_pauseregen_finished;
-float w_nex(float req)
-{
-       float dt;
-       float ammo_amount;
-       if (req == WR_AIM)
-       {
-               if(bot_aim(1000000, 0, 1, FALSE))
-                       self.BUTTON_ATCK = TRUE;
-               else
-               {
-                       if(autocvar_g_balance_nex_charge)
-                               self.BUTTON_ATCK2 = TRUE;
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_nex_charge && self.nex_charge < autocvar_g_balance_nex_charge_limit)
-                       self.nex_charge = min(1, self.nex_charge + autocvar_g_balance_nex_charge_rate * frametime / W_TICSPERFRAME);
-
-               if(autocvar_g_balance_nex_secondary_chargepool)
-                       if(self.nex_chargepool_ammo < 1)
-                       {
-                               if(self.nex_chargepool_pauseregen_finished < time)
-                                       self.nex_chargepool_ammo = min(1, self.nex_chargepool_ammo + autocvar_g_balance_nex_secondary_chargepool_regen * frametime / W_TICSPERFRAME);
-                               self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_nex_secondary_chargepool_pause_health_regen);
-                       }
-
-               if(autocvar_g_balance_nex_reload_ammo && self.clip_load < min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-               else
-               {
-                       if (self.BUTTON_ATCK)
-                       {
-                               if (weapon_prepareattack(0, autocvar_g_balance_nex_primary_refire))
-                               {
-                                       W_Nex_Attack(0);
-                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_primary_animtime, w_ready);
-                               }
-                       }
-                       if ((autocvar_g_balance_nex_secondary_charge && !autocvar_g_balance_nex_secondary) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2)
-                       {
-                               if(autocvar_g_balance_nex_secondary_charge)
-                               {
-                                       self.nex_charge_rottime = time + autocvar_g_balance_nex_charge_rot_pause;
-                                       dt = frametime / W_TICSPERFRAME;
-
-                                       if(self.nex_charge < 1)
-                                       {
-                                               if(autocvar_g_balance_nex_secondary_chargepool)
-                                               {
-                                                       if(autocvar_g_balance_nex_secondary_ammo)
-                                                       {
-                                                               // always deplete if secondary is held
-                                                               self.nex_chargepool_ammo = max(0, self.nex_chargepool_ammo - autocvar_g_balance_nex_secondary_ammo * dt);
-
-                                                               dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate);
-                                                               self.nex_chargepool_pauseregen_finished = time + autocvar_g_balance_nex_secondary_chargepool_pause_regen;
-                                                               dt = min(dt, self.nex_chargepool_ammo);
-                                                               dt = max(0, dt);
-
-                                                               self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate;
-                                                       }
-                                               }
-
-                                               else if(autocvar_g_balance_nex_secondary_ammo)
-                                               {
-                                                       if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed
-                                                       {
-                                                               dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate);
-                                                               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
-                                                               {
-                                                                       // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
-                                                                       if(autocvar_g_balance_nex_reload_ammo)
-                                                                       {
-                                                                               dt = min(dt, (self.clip_load - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo);
-                                                                               dt = max(0, dt);
-                                                                               if(dt > 0)
-                                                                               {
-                                                                                       self.clip_load = max(autocvar_g_balance_nex_secondary_ammo, self.clip_load - autocvar_g_balance_nex_secondary_ammo * dt);
-                                                                               }
-                                                                               self.(weapon_load[WEP_NEX]) = self.clip_load;
-                                                                       }
-                                                                       else
-                                                                       {
-                                                                               dt = min(dt, (self.ammo_cells - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo);
-                                                                               dt = max(0, dt);
-                                                                               if(dt > 0)
-                                                                               {
-                                                                                       self.ammo_cells = max(autocvar_g_balance_nex_secondary_ammo, self.ammo_cells - autocvar_g_balance_nex_secondary_ammo * dt);
-                                                                               }
-                                                                       }
-                                                               }
-                                                               self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate;
-                                                       }
-                                               }
-
-                                               else
-                                               {
-                                                       dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate);
-                                                       self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate;
-                                               }
-                                       }
-                               }
-                               else if(autocvar_g_balance_nex_secondary)
-                               {
-                                       if (weapon_prepareattack(0, autocvar_g_balance_nex_secondary_refire))
-                                       {
-                                               W_Nex_Attack(1);
-                                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_secondary_animtime, w_ready);
-                                       }
-                               }
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/nexflash.md3");
-               precache_model ("models/weapons/g_nex.md3");
-               precache_model ("models/weapons/v_nex.md3");
-               precache_model ("models/weapons/h_nex.iqm");
-               precache_sound ("weapons/nexfire.wav");
-               precache_sound ("weapons/nexcharge.wav");
-               precache_sound ("weapons/nexwhoosh1.wav");
-               precache_sound ("weapons/nexwhoosh2.wav");
-               precache_sound ("weapons/nexwhoosh3.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_NEX);
-               self.current_ammo = ammo_cells;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_primary_ammo;
-               ammo_amount += (autocvar_g_balance_nex_reload_ammo && self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_primary_ammo);
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               if(autocvar_g_balance_nex_secondary)
-               {
-                       // don't allow charging if we don't have enough ammo
-                       ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_secondary_ammo;
-                       ammo_amount += self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_secondary_ammo;    
-                       return ammo_amount;
-               }
-               else
-               {
-                       return FALSE; // zoom is not a fire mode
-               }
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo), autocvar_g_balance_nex_reload_ammo, autocvar_g_balance_nex_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_THINKING_WITH_PORTALS;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               return WEAPON_NEX_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_nex(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 6;
-               pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1);
-               if(!w_issilent)
-                       sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM);
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/neximpact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/porto.qc b/qcsrc/common/weapons/porto.qc
deleted file mode 100644 (file)
index fad480d..0000000
+++ /dev/null
@@ -1,391 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ PORTO,
-/* function  */ w_porto,
-/* ammotype  */ 0,
-/* impulse   */ 0,
-/* flags     */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON,
-/* rating    */ 0,
-/* model     */ "porto" ,
-/* shortname */ "porto",
-/* fullname  */ _("Port-O-Launch")
-);
-#else
-#ifdef SVQC
-.entity porto_current;
-.vector porto_v_angle; // holds "held" view angles
-.float porto_v_angle_held;
-.vector right_vector;
-
-void W_Porto_Success (void)
-{
-       if(self.realowner == world)
-       {
-               objerror("Cannot succeed successfully: no owner\n");
-               return;
-       }
-
-       self.realowner.porto_current = world;
-       remove(self);
-}
-
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo);
-void W_Porto_Fail (float failhard)
-{
-       if(self.realowner == world)
-       {
-               objerror("Cannot fail successfully: no owner\n");
-               return;
-       }
-
-       // no portals here!
-       if(self.cnt < 0)
-       {
-               Portal_ClearWithID(self.realowner, self.portal_id);
-       }
-
-       self.realowner.porto_current = world;
-
-       if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !WEPSET_CONTAINS_EW(self.realowner, WEP_PORTO))
-       {
-               setsize (self, '-16 -16 0', '16 16 32');
-               setorigin(self, self.origin + trace_plane_normal);
-               if(move_out_of_solid(self))
-               {
-                       self.flags = FL_ITEM;
-                       self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128);
-                       tracetoss(self, self);
-                       if(vlen(trace_endpos - self.realowner.origin) < 128)
-                       {
-                               W_ThrowNewWeapon(self.realowner, WEP_PORTO, 0, self.origin, self.velocity);
-                               centerprint(self.realowner, "^1Portal deployment failed.\n\n^2Catch it to try again!");
-                       }
-               }
-       }
-       remove(self);
-}
-
-void W_Porto_Remove (entity p)
-{
-       if(p.porto_current.realowner == p && p.porto_current.classname == "porto")
-       {
-               entity oldself;
-               oldself = self;
-               self = p.porto_current;
-               W_Porto_Fail(1);
-               self = oldself;
-       }
-}
-
-void W_Porto_Think (void)
-{
-       trace_plane_normal = '0 0 0';
-       if(self.realowner.playerid != self.playerid)
-               remove(self);
-       else
-               W_Porto_Fail(0);
-}
-
-void W_Porto_Touch (void)
-{
-       vector norm;
-
-       // do not use PROJECTILE_TOUCH here
-       // FIXME but DO handle warpzones!
-
-       if(other.classname == "portal")
-               return; // handled by the portal
-
-       norm = trace_plane_normal;
-       if(trace_ent.iscreature)
-       {
-               traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_z, MOVE_WORLDONLY, self);
-               if(trace_fraction >= 1)
-                       return;
-               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
-                       return;
-               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-                       return;
-       }
-
-       if(self.realowner.playerid != self.playerid)
-       {
-               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
-               remove(self);
-       }
-       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
-       {
-               spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTN_NORM);
-               // just reflect
-               self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal);
-               self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal));
-       }
-       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-       {
-               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
-               W_Porto_Fail(0);
-               if(self.cnt < 0)
-                       Portal_ClearAll_PortalsOnly(self.realowner);
-       }
-       else if(self.cnt == 0)
-       {
-               // in-portal only
-               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-               {
-                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
-                       trace_plane_normal = norm;
-                       centerprint(self.realowner, "^1In^7-portal created.");
-                       W_Porto_Success();
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
-                       trace_plane_normal = norm;
-                       W_Porto_Fail(0);
-               }
-       }
-       else if(self.cnt == 1)
-       {
-               // out-portal only
-               if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-               {
-                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
-                       trace_plane_normal = norm;
-                       centerprint(self.realowner, "^4Out^7-portal created.");
-                       W_Porto_Success();
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
-                       trace_plane_normal = norm;
-                       W_Porto_Fail(0);
-               }
-       }
-       else if(self.effects & EF_RED)
-       {
-               self.effects += EF_BLUE - EF_RED;
-               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-               {
-                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
-                       trace_plane_normal = norm;
-                       centerprint(self.realowner, "^1In^7-portal created.");
-                       self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm);
-                       self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm));
-                       CSQCProjectile(self, TRUE, PROJECTILE_PORTO_BLUE, TRUE); // change type
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
-                       trace_plane_normal = norm;
-                       Portal_ClearAll_PortalsOnly(self.realowner);
-                       W_Porto_Fail(0);
-               }
-       }
-       else
-       {
-               if(self.realowner.portal_in.portal_id == self.portal_id)
-               {
-                       if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
-                       {
-                               sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
-                               trace_plane_normal = norm;
-                               centerprint(self.realowner, "^4Out^7-portal created.");
-                               W_Porto_Success();
-                       }
-                       else
-                       {
-                               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
-                               Portal_ClearAll_PortalsOnly(self.realowner);
-                               W_Porto_Fail(0);
-                       }
-               }
-               else
-               {
-                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
-                       Portal_ClearAll_PortalsOnly(self.realowner);
-                       W_Porto_Fail(0);
-               }
-       }
-}
-
-void W_Porto_Attack (float type)
-{
-       entity gren;
-
-       W_SetupShot (self, FALSE, 4, "porto/fire.wav", CH_WEAPON_A, 0);
-       // always shoot from the eye
-       w_shotdir = v_forward;
-       w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
-
-       //pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       gren = spawn ();
-       gren.cnt = type;
-       gren.owner = gren.realowner = self;
-       gren.playerid = self.playerid;
-       gren.classname = "porto";
-       gren.bot_dodge = TRUE;
-       gren.bot_dodgerating = 200;
-       gren.movetype = MOVETYPE_BOUNCEMISSILE;
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.effects = EF_RED;
-       gren.scale = 4;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '0 0 0', '0 0 0');
-
-       if(type > 0)
-               gren.nextthink = time + autocvar_g_balance_porto_secondary_lifetime;
-       else
-               gren.nextthink = time + autocvar_g_balance_porto_primary_lifetime;
-       gren.think = W_Porto_Think;
-       gren.touch = W_Porto_Touch;
-
-       if(type > 0)
-       {
-               if(self.items & IT_STRENGTH)
-                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed * autocvar_g_balance_powerup_strength_force, 0);
-               else
-                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed, 0);
-       }
-       else
-       {
-               if(self.items & IT_STRENGTH)
-                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed * autocvar_g_balance_powerup_strength_force, 0);
-               else
-                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed, 0);
-       }
-
-       gren.angles = vectoangles (gren.velocity);
-       gren.flags = FL_PROJECTILE;
-
-       gren.portal_id = time;
-       self.porto_current = gren;
-       gren.playerid = self.playerid;
-       fixedmakevectors(fixedvectoangles(gren.velocity));
-       gren.right_vector = v_right;
-
-       gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
-
-       if(type > 0)
-               CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_BLUE, TRUE);
-       else
-               CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE);
-
-       other = gren; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-void spawnfunc_weapon_porto (void)
-{
-       weapon_defaultspawnfunc(WEP_PORTO);
-}
-
-float w_nexball_weapon(float req);
-float w_porto(float req)
-{
-       //vector v_angle_save;
-
-       if (g_nexball) { return w_nexball_weapon(req); }
-       if (req == WR_AIM)
-       {
-               self.BUTTON_ATCK = FALSE;
-               self.BUTTON_ATCK2 = FALSE;
-               if(!autocvar_g_balance_porto_secondary)
-                       if(bot_aim(autocvar_g_balance_porto_primary_speed, 0, autocvar_g_balance_grenadelauncher_primary_lifetime, FALSE))
-                               self.BUTTON_ATCK = TRUE;
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_porto_secondary)
-               {
-                       if (self.BUTTON_ATCK)
-                       if (!self.porto_current)
-                       if (!self.porto_forbidden)
-                       if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire))
-                       {
-                               W_Porto_Attack(0);
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready);
-                       }
-
-                       if (self.BUTTON_ATCK2)
-                       if (!self.porto_current)
-                       if (!self.porto_forbidden)
-                       if (weapon_prepareattack(1, autocvar_g_balance_porto_secondary_refire))
-                       {
-                               W_Porto_Attack(1);
-                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_porto_secondary_animtime, w_ready);
-                       }
-               }
-               else
-               {
-                       if(self.porto_v_angle_held)
-                       {
-                               if(!self.BUTTON_ATCK2)
-                               {
-                                       self.porto_v_angle_held = 0;
-
-                                       ClientData_Touch(self);
-                               }
-                       }
-                       else
-                       {
-                               if(self.BUTTON_ATCK2)
-                               {
-                                       self.porto_v_angle = self.v_angle;
-                                       self.porto_v_angle_held = 1;
-
-                                       ClientData_Touch(self);
-                               }
-                       }
-                       if(self.porto_v_angle_held)
-                               makevectors(self.porto_v_angle); // override the previously set angles
-
-                       if (self.BUTTON_ATCK)
-                       if (!self.porto_current)
-                       if (!self.porto_forbidden)
-                       if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire))
-                       {
-                               W_Porto_Attack(-1);
-                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready);
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_porto.md3");
-               precache_model ("models/weapons/v_porto.md3");
-               precache_model ("models/weapons/h_porto.iqm");
-               precache_model ("models/portal.md3");
-               precache_sound ("porto/bounce.wav");
-               precache_sound ("porto/create.wav");
-               precache_sound ("porto/expire.wav");
-               precache_sound ("porto/explode.wav");
-               precache_sound ("porto/fire.wav");
-               precache_sound ("porto/unsupported.wav");
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_PORTO);
-               self.current_ammo = ammo_none;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.porto_current = world;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_porto(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               print("Since when does Porto send DamageInfo?\n");
-       }
-       else if(req == WR_PRECACHE)
-       {
-               // nothing to do
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/rifle.qc b/qcsrc/common/weapons/rifle.qc
deleted file mode 100644 (file)
index 8ed4491..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ RIFLE,
-/* function  */ w_rifle,
-/* ammotype  */ IT_NAILS,
-/* impulse   */ 7,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "campingrifle",
-/* shortname */ "rifle",
-/* fullname  */ _("Rifle")
-);
-#else
-#ifdef SVQC
-
-.float rifle_accumulator;
-
-void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSpeed, float pLifetime, float pAmmo, float deathtype, float pBulletConstant, float pTracer, float pShots, string pSound)
-{
-       float i;
-
-       W_DecreaseAmmo(ammo_nails, pAmmo, autocvar_g_balance_rifle_reload_ammo);
-
-       W_SetupShot (self, autocvar_g_antilag_bullets && pSpeed >= autocvar_g_antilag_bullets, 2, pSound, CH_WEAPON_A, pDamage * pShots);
-
-       pointparticles(particleeffectnum("rifle_muzzleflash"), w_shotorg, w_shotdir * 2000, 1);
-
-       if(self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) // if zoomed, shoot from the eye
-       {
-               w_shotdir = v_forward;
-               w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
-       }
-
-       for(i = 0; i < pShots; ++i)
-               fireBallisticBullet(w_shotorg, w_shotdir, pSpread, pSpeed, pLifetime, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE), 1, pBulletConstant);
-       endFireBallisticBullet();
-
-       if (autocvar_g_casings >= 2)
-               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
-}
-
-void W_Rifle_Attack()
-{
-       W_Rifle_FireBullet(autocvar_g_balance_rifle_primary_spread, autocvar_g_balance_rifle_primary_damage, autocvar_g_balance_rifle_primary_force, autocvar_g_balance_rifle_primary_speed, autocvar_g_balance_rifle_primary_lifetime, autocvar_g_balance_rifle_primary_ammo, WEP_RIFLE, autocvar_g_balance_rifle_primary_bulletconstant, autocvar_g_balance_rifle_primary_tracer, autocvar_g_balance_rifle_primary_shots, "weapons/campingrifle_fire.wav");
-}
-
-void W_Rifle_Attack2()
-{
-       W_Rifle_FireBullet(autocvar_g_balance_rifle_secondary_spread, autocvar_g_balance_rifle_secondary_damage, autocvar_g_balance_rifle_secondary_force, autocvar_g_balance_rifle_secondary_speed, autocvar_g_balance_rifle_secondary_lifetime, autocvar_g_balance_rifle_secondary_ammo, WEP_RIFLE | HITTYPE_SECONDARY, autocvar_g_balance_rifle_secondary_bulletconstant, autocvar_g_balance_rifle_secondary_tracer, autocvar_g_balance_rifle_secondary_shots, "weapons/campingrifle_fire2.wav");
-}
-
-void spawnfunc_weapon_rifle (void)
-{
-       weapon_defaultspawnfunc(WEP_RIFLE);
-}
-
-// compatibility alias
-void spawnfunc_weapon_campingrifle (void)
-{
-       spawnfunc_weapon_rifle();
-}
-void spawnfunc_weapon_sniperrifle (void)
-{
-       spawnfunc_weapon_rifle();
-}
-
-.void(void) rifle_bullethail_attackfunc;
-.float rifle_bullethail_frame;
-.float rifle_bullethail_animtime;
-.float rifle_bullethail_refire;
-void W_Rifle_BulletHail_Continue()
-{
-       float r, sw, af;
-
-       sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing
-       af = ATTACK_FINISHED(self);
-       self.switchweapon = self.weapon;
-       ATTACK_FINISHED(self) = time;
-       print(ftos(self.ammo_nails), "\n");
-       r = weapon_prepareattack(self.rifle_bullethail_frame == WFRAME_FIRE2, self.rifle_bullethail_refire);
-       if(self.switchweapon == self.weapon)
-               self.switchweapon = sw;
-       if(r)
-       {
-               self.rifle_bullethail_attackfunc();
-               weapon_thinkf(self.rifle_bullethail_frame, self.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue);
-               print("thinkf set\n");
-       }
-       else
-       {
-               ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
-               print("out of ammo... ", ftos(self.weaponentity.state), "\n");
-       }
-}
-
-void W_Rifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire)
-{
-       // if we get here, we have at least one bullet to fire
-       AttackFunc();
-       if(mode)
-       {
-               // continue hail
-               self.rifle_bullethail_attackfunc = AttackFunc;
-               self.rifle_bullethail_frame = fr;
-               self.rifle_bullethail_animtime = animtime;
-               self.rifle_bullethail_refire = refire;
-               weapon_thinkf(fr, animtime, W_Rifle_BulletHail_Continue);
-       }
-       else
-       {
-               // just one shot
-               weapon_thinkf(fr, animtime, w_ready);
-       }
-}
-
-.float bot_secondary_riflemooth;
-float w_rifle(float req)
-{
-       float ammo_amount;
-
-       if (req == WR_AIM)
-       {
-               self.BUTTON_ATCK=FALSE;
-               self.BUTTON_ATCK2=FALSE;
-               if(vlen(self.origin-self.enemy.origin) > 1000)
-                       self.bot_secondary_riflemooth = 0;
-               if(self.bot_secondary_riflemooth == 0)
-               {
-                       if(bot_aim(autocvar_g_balance_rifle_primary_speed, 0, autocvar_g_balance_rifle_primary_lifetime, FALSE))
-                       {
-                               self.BUTTON_ATCK = TRUE;
-                               if(random() < 0.01) self.bot_secondary_riflemooth = 1;
-                       }
-               }
-               else
-               {
-                       if(bot_aim(autocvar_g_balance_rifle_secondary_speed, 0, autocvar_g_balance_rifle_secondary_lifetime, FALSE))
-                       {
-                               self.BUTTON_ATCK2 = TRUE;
-                               if(random() < 0.03) self.bot_secondary_riflemooth = 0;
-                       }
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo)) // forced reload
-            weapon_action(self.weapon, WR_RELOAD);
-               else
-               {
-                       self.rifle_accumulator = bound(time - autocvar_g_balance_rifle_bursttime, self.rifle_accumulator, time);
-                       if (self.BUTTON_ATCK)
-                       if (weapon_prepareattack_check(0, autocvar_g_balance_rifle_primary_refire))
-                       if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_primary_burstcost)
-                       {
-                               weapon_prepareattack_do(0, autocvar_g_balance_rifle_primary_refire);
-                               W_Rifle_BulletHail(autocvar_g_balance_rifle_primary_bullethail, W_Rifle_Attack, WFRAME_FIRE1, autocvar_g_balance_rifle_primary_animtime, autocvar_g_balance_rifle_primary_refire);
-                               self.rifle_accumulator += autocvar_g_balance_rifle_primary_burstcost;
-                       }
-                       if (self.BUTTON_ATCK2)
-                       {
-                               if (autocvar_g_balance_rifle_secondary)
-                               {
-                    if(autocvar_g_balance_rifle_secondary_reload)
-                        weapon_action(self.weapon, WR_RELOAD);
-                    else
-                    {
-                        if (weapon_prepareattack_check(1, autocvar_g_balance_rifle_secondary_refire))
-                        if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_secondary_burstcost)
-                        {
-                            weapon_prepareattack_do(1, autocvar_g_balance_rifle_secondary_refire);
-                            W_Rifle_BulletHail(autocvar_g_balance_rifle_secondary_bullethail, W_Rifle_Attack2, WFRAME_FIRE2, autocvar_g_balance_rifle_secondary_animtime, autocvar_g_balance_rifle_primary_refire);
-                            self.rifle_accumulator += autocvar_g_balance_rifle_secondary_burstcost;
-                        }
-                    }
-                               }
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_campingrifle.md3");
-               precache_model ("models/weapons/v_campingrifle.md3");
-               precache_model ("models/weapons/h_campingrifle.iqm");
-               precache_sound ("weapons/campingrifle_fire.wav");
-               precache_sound ("weapons/campingrifle_fire2.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_RIFLE);
-               self.current_ammo = ammo_nails;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_primary_ammo;
-               ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_primary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_secondary_ammo;
-               ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_secondary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_RESETPLAYER)
-       {
-               self.rifle_accumulator = time - autocvar_g_balance_rifle_bursttime;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo), autocvar_g_balance_rifle_reload_ammo, autocvar_g_balance_rifle_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_THINKING_WITH_PORTALS;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-               {
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                               return WEAPON_RIFLE_MURDER_HAIL_PIERCING;
-                       else
-                               return WEAPON_RIFLE_MURDER_HAIL;
-               }
-               else
-               {
-                       if(w_deathtype & HITTYPE_BOUNCE)
-                               return WEAPON_RIFLE_MURDER_PIERCING;
-                       else
-                               return WEAPON_RIFLE_MURDER;
-               }
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_rifle(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 2;
-               pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1);
-               if(!w_issilent)
-               {
-                       if(w_random < 0.2)
-                               sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
-                       else if(w_random < 0.4)
-                               sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
-                       else if(w_random < 0.5)
-                               sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/ric1.wav");
-               precache_sound("weapons/ric2.wav");
-               precache_sound("weapons/ric3.wav");
-       }
-
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/seeker.qc b/qcsrc/common/weapons/seeker.qc
deleted file mode 100644 (file)
index 1683a37..0000000
+++ /dev/null
@@ -1,713 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ SEEKER,
-/* function  */ w_seeker,
-/* ammotype  */ IT_ROCKETS,
-/* impulse   */ 8,
-/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "seeker",
-/* shortname */ "seeker",
-/* fullname  */ _("T.A.G. Seeker")
-);
-#else
-#ifdef SVQC
-//.float proxytime; = autoswitch
-//.float tl; = wait
-.entity tag_target, wps_tag_tracker;
-.float tag_time;
-
-// ============================
-// Begin: Missile functions, these are general functions to be manipulated by other code
-// ============================
-void Seeker_Missile_Explode ()
-{
-       self.event_damage = func_null;
-       RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_missile_damage, autocvar_g_balance_seeker_missile_edgedamage, autocvar_g_balance_seeker_missile_radius, world, world, autocvar_g_balance_seeker_missile_force, self.projectiledeathtype, other);
-
-
-       remove (self);
-}
-
-void Seeker_Missile_Touch()
-{
-       PROJECTILE_TOUCH;
-
-       Seeker_Missile_Explode();
-}
-
-void Seeker_Missile_Think()
-{
-       entity e;
-       vector desireddir, olddir, newdir, eorg;
-       float turnrate;
-       float dist;
-       float spd;
-
-       if (time > self.cnt)
-       {
-               self.projectiledeathtype |= HITTYPE_SPLASH;
-               Seeker_Missile_Explode();
-       }
-
-       spd = vlen(self.velocity);
-       spd = bound(
-               spd - autocvar_g_balance_seeker_missile_decel * frametime,
-               autocvar_g_balance_seeker_missile_speed_max,
-               spd + autocvar_g_balance_seeker_missile_accel * frametime
-       );
-
-       if (self.enemy != world)
-               if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
-                       self.enemy = world;
-
-       if (self.enemy != world)
-       {
-               e               = self.enemy;
-               eorg            = 0.5 * (e.absmin + e.absmax);
-               turnrate        = autocvar_g_balance_seeker_missile_turnrate; // how fast to turn
-               desireddir      = normalize(eorg - self.origin);
-               olddir          = normalize(self.velocity); // get my current direction
-               dist            = vlen(eorg - self.origin);
-
-               // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
-               if (autocvar_g_balance_seeker_missile_smart && (dist > autocvar_g_balance_seeker_missile_smart_mindist))
-               {
-                       // Is it a better idea (shorter distance) to trace to the target itself?
-                       if ( vlen(self.origin + olddir * self.wait) < dist)
-                               traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
-                       else
-                               traceline(self.origin, eorg, FALSE, self);
-
-                       // Setup adaptive tracelength
-                       self.wait = bound(autocvar_g_balance_seeker_missile_smart_trace_min, vlen(self.origin - trace_endpos), self.wait = autocvar_g_balance_seeker_missile_smart_trace_max);
-
-                       // Calc how important it is that we turn and add this to the desierd (enemy) dir.
-                       desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
-               }
-               
-               newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
-               self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
-       }
-       else
-               dist = 0;
-
-       // Proxy
-       if (autocvar_g_balance_seeker_missile_proxy)
-       {
-               if ( dist <= autocvar_g_balance_seeker_missile_proxy_maxrange)
-               {
-                       if (self.autoswitch == 0)
-                       {
-                               self.autoswitch = time + autocvar_g_balance_seeker_missile_proxy_delay;
-                       }
-                       else
-                       {
-                               if (self.autoswitch <= time)
-                               {
-                                       Seeker_Missile_Explode();
-                                       self.autoswitch = 0;
-                               }
-                       }
-               }
-               else
-               {
-                       if (self.autoswitch != 0)
-                               self.autoswitch = 0;
-               }
-       }
-       ///////////////
-
-       if (self.enemy.deadflag != DEAD_NO)
-       {
-               self.enemy = world;
-               self.cnt = time + 1 + (random() * 4);
-               self.nextthink = self.cnt;
-               return;
-       }
-
-       //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
-       self.nextthink = time;// + 0.05; // csqc projectiles
-       UpdateCSQCProjectile(self);
-}
-
-
-
-void Seeker_Missile_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-               
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-
-       if (self.realowner == attacker)
-               self.health = self.health - (damage * 0.25);
-       else
-               self.health = self.health - damage;
-               
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, Seeker_Missile_Explode);
-}
-
-/*
-void Seeker_Missile_Animate()
-{
-       self.frame = self.frame +1;
-       self.nextthink = time + 0.05;
-
-       if (self.enemy != world)
-               if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
-                       self.enemy = world;
-
-       if(self.frame == 5)
-       {
-               self.think           = Seeker_Missile_Think;
-               self.nextthink       = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles
-
-               if (autocvar_g_balance_seeker_missile_proxy)
-                       self.movetype    = MOVETYPE_BOUNCEMISSILE;
-               else
-                       self.movetype    = MOVETYPE_FLYMISSILE;
-       }
-
-       UpdateCSQCProjectile(self);
-}
-*/
-
-void Seeker_Fire_Missile(vector f_diff, entity m_target)
-{
-       entity missile;
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_reload_ammo);
-
-       makevectors(self.v_angle);
-       W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/seeker_fire.wav", CH_WEAPON_A, 0);
-       w_shotorg += f_diff;
-       pointparticles(particleeffectnum("seeker_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       //self.detornator         = FALSE;
-
-       missile                 = spawn();
-       missile.owner           = missile.realowner = self;
-       missile.classname       = "seeker_missile";
-       missile.bot_dodge       = TRUE;
-       missile.bot_dodgerating = autocvar_g_balance_seeker_missile_damage;
-
-       missile.think           = Seeker_Missile_Think;
-       missile.touch           = Seeker_Missile_Touch;
-       missile.event_damage    = Seeker_Missile_Damage;
-       missile.nextthink       = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay");
-       missile.cnt             = time + autocvar_g_balance_seeker_missile_lifetime;
-       missile.enemy           = m_target;
-       missile.solid           = SOLID_BBOX;
-       missile.scale           = 2;
-       missile.takedamage      = DAMAGE_YES;
-       missile.health          = autocvar_g_balance_seeker_missile_health;
-       missile.damageforcescale = autocvar_g_balance_seeker_missile_damageforcescale;
-       missile.damagedbycontents = TRUE;
-       //missile.think           = Seeker_Missile_Animate; // csqc projectiles.
-       
-       if (missile.enemy != world)
-               missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY;
-       else 
-               missile.projectiledeathtype = WEP_SEEKER;
-
-
-       setorigin (missile, w_shotorg);
-       setsize (missile, '-4 -4 -4', '4 4 4');
-       missile.movetype    = MOVETYPE_FLYMISSILE;
-       missile.flags       = FL_PROJECTILE;
-       missile.missile_flags = MIF_SPLASH | MIF_GUIDED_TAG;
-       
-       W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_missile);
-
-       missile.angles = vectoangles (missile.velocity);
-
-       CSQCProjectile(missile, FALSE, PROJECTILE_SEEKER, TRUE);
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-// ============================
-// Begin: FLAC, close range attack meant for defeating rockets which are coming at you. 
-// ============================
-void Seeker_Flac_Explode ()
-{
-       self.event_damage = func_null;
-
-       RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_flac_damage, autocvar_g_balance_seeker_flac_edgedamage, autocvar_g_balance_seeker_flac_radius, world, world, autocvar_g_balance_seeker_flac_force, self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void Seeker_Flac_Touch()
-{
-       PROJECTILE_TOUCH;
-
-       Seeker_Flac_Explode();
-}
-
-void Seeker_Fire_Flac()
-{
-       entity missile;
-       vector f_diff;
-       float c;
-
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_flac_ammo, autocvar_g_balance_seeker_reload_ammo);
-
-       c = mod(self.bulletcounter, 4);
-       switch(c)
-       {
-               case 0:
-                       f_diff = '-1.25 -3.75 0';
-                       break;
-               case 1:
-                       f_diff = '+1.25 -3.75 0';
-                       break;
-               case 2:
-                       f_diff = '-1.25 +3.75 0';
-                       break;
-               case 3:
-               default:
-                       f_diff = '+1.25 +3.75 0';
-                       break;
-       }
-       W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/flac_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_flac_damage);
-       w_shotorg += f_diff;
-
-       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
-
-       missile                                 = spawn ();
-       missile.owner                   = missile.realowner = self;
-       missile.classname               = "missile";
-       missile.bot_dodge               = TRUE;
-       missile.bot_dodgerating = autocvar_g_balance_seeker_flac_damage;
-       missile.touch                   = Seeker_Flac_Explode;
-       missile.use                     = Seeker_Flac_Explode; 
-       missile.think                   = adaptor_think2use_hittype_splash;
-       missile.nextthink               = time + autocvar_g_balance_seeker_flac_lifetime + autocvar_g_balance_seeker_flac_lifetime_rand;
-       missile.solid                   = SOLID_BBOX;
-       missile.movetype                = MOVETYPE_FLY; 
-       missile.projectiledeathtype = WEP_SEEKER;
-       missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY;
-       missile.flags                           = FL_PROJECTILE;
-       missile.missile_flags       = MIF_SPLASH; 
-       
-       // csqc projectiles
-       //missile.angles                                = vectoangles (missile.velocity);       
-       //missile.scale = 0.4; // BUG: the model is too big 
-       
-       setorigin (missile, w_shotorg);
-       setsize (missile, '-2 -2 -2', '2 2 2');
-               
-       W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_flac);
-       CSQCProjectile(missile, TRUE, PROJECTILE_FLAC, TRUE);
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-// ============================
-// Begin: Tag and rocket controllers 
-// ============================
-entity Seeker_Tagged_Info(entity isowner, entity istarget)
-{
-       entity tag;
-       for(tag = world; (tag = find(tag, classname, "tag_tracker")); ) 
-               if ((tag.realowner == isowner) && (tag.tag_target == istarget))
-                       return tag;
-               
-       return world;
-}
-
-void Seeker_Attack()
-{
-       entity tracker, closest_target;
-       
-       closest_target = world;
-       for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.realowner == self)
-       {
-               if (closest_target)
-               {
-                       if (vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin))
-                               closest_target = tracker.tag_target;
-               }
-               else 
-                       closest_target = tracker.tag_target;
-       }
-               
-       traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self);
-       if ((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target)))
-               closest_target = world;
-       
-       Seeker_Fire_Missile('0 0 0', closest_target);
-}
-
-void Seeker_Vollycontroller_Think() // TODO: Merge this with Seeker_Attack
-{
-       float c;
-       entity oldself,oldenemy;
-       self.cnt = self.cnt - 1;
-
-       if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.ammo_rockets < autocvar_g_balance_seeker_missile_ammo) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER))
-       {
-               remove(self);
-               return;
-       }
-
-       self.nextthink = time + autocvar_g_balance_seeker_missile_delay * W_WeaponRateFactor();
-       
-       oldself = self;
-       self = self.realowner;
-       
-       oldenemy = self.enemy;
-       self.enemy = oldself.enemy;
-       
-       c = mod(self.cnt, 4);
-       switch(c)
-       {
-               case 0:
-                       Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy);
-                       break;
-               case 1:
-                       Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy);
-                       break;
-               case 2:
-                       Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy);
-                       break;
-               case 3:
-               default:
-                       Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy);
-                       break;
-       }
-
-       self.enemy = oldenemy;
-       self = oldself;
-}
-
-void Seeker_Tracker_Think() 
-{
-       // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up
-       if ((self.realowner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER)
-       || (time > self.tag_time + autocvar_g_balance_seeker_tag_tracker_lifetime))
-       {
-               if (self)
-               {
-                       WaypointSprite_Kill(self.tag_target.wps_tag_tracker);
-                       remove(self);
-               }
-               return;
-       }
-       
-       // Update the think method information
-       self.nextthink = time;
-}
-
-// ============================
-// Begin: Tag projectile 
-// ============================
-void Seeker_Tag_Explode ()
-{
-       //if(other==self.realowner)
-       //    return;
-       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE, other.species, self);
-
-       remove (self);
-}
-
-void Seeker_Tag_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-       self.health = self.health - damage;
-       if (self.health <= 0)
-               Seeker_Tag_Explode();
-}
-
-void Seeker_Tag_Touch()
-{
-       vector dir;
-       vector org2;
-       entity e;
-       
-       PROJECTILE_TOUCH;
-
-       dir     = normalize (self.realowner.origin - self.origin);
-       org2    = findbetterlocation (self.origin, 8);
-
-       te_knightspike(org2);
-
-       self.event_damage = func_null;
-       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE | HITTYPE_SECONDARY, other.species, self);
-
-       if (other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO)
-       {
-               // check to see if this person is already tagged by me
-               entity tag = Seeker_Tagged_Info(self.realowner, other);
-               
-               if (tag != world)
-               {
-                       if (other.wps_tag_tracker && (autocvar_g_balance_seeker_type == 1)) // don't attach another waypointsprite without killing the old one first
-                               WaypointSprite_Kill(other.wps_tag_tracker);
-                               
-                       tag.tag_time = time;
-               }
-               else
-               {               
-                       //sprint(self.realowner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n"));
-                       e             = spawn();
-                       e.cnt         = autocvar_g_balance_seeker_missile_count;
-                       e.classname   = "tag_tracker";
-                       e.owner       = self.owner;
-                       e.realowner   = self.realowner;
-                       
-                       if      (autocvar_g_balance_seeker_type == 1)
-                       {
-                               e.tag_target  = other;
-                               e.tag_time    = time;
-                               e.think       = Seeker_Tracker_Think;
-                       }
-                       else 
-                       {
-                               e.enemy     = other;
-                               e.think     = Seeker_Vollycontroller_Think;
-                       }
-                       
-                       e.nextthink   = time;
-               }
-               
-               if      (autocvar_g_balance_seeker_type == 1)
-               {
-                       WaypointSprite_Spawn("tagged-target", autocvar_g_balance_seeker_tag_tracker_lifetime, 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, TRUE, RADARICON_TAGGED, '0.5 1 0');
-                       WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT);
-               }
-       }
-
-       remove(self);
-       return;
-}
-
-void Seeker_Fire_Tag()
-{
-       entity missile;
-       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_tag_ammo, autocvar_g_balance_seeker_reload_ammo);
-
-       W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/tag_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_missile_damage * autocvar_g_balance_seeker_missile_count);
-
-       missile                 = spawn();
-       missile.owner           = missile.realowner = self;
-       missile.classname       = "seeker_tag";
-       missile.bot_dodge       = TRUE;
-       missile.bot_dodgerating = 50;
-       missile.touch           = Seeker_Tag_Touch;
-       missile.think           = SUB_Remove;
-       missile.nextthink       = time + autocvar_g_balance_seeker_tag_lifetime;
-       missile.movetype        = MOVETYPE_FLY;
-       missile.solid           = SOLID_BBOX;
-
-       missile.takedamage       = DAMAGE_YES;
-       missile.event_damage     = Seeker_Tag_Damage;
-       missile.health           = autocvar_g_balance_seeker_tag_health;
-       missile.damageforcescale = autocvar_g_balance_seeker_tag_damageforcescale;
-
-       setorigin (missile, w_shotorg);
-       setsize (missile, '-2 -2 -2', '2 2 2');
-
-       missile.flags       = FL_PROJECTILE;
-       //missile.missile_flags = MIF_..?; 
-
-       missile.movetype    = MOVETYPE_FLY;
-       W_SETUPPROJECTILEVELOCITY(missile, g_balance_seeker_tag);
-       missile.angles = vectoangles (missile.velocity);
-
-       CSQCProjectile(missile, TRUE, PROJECTILE_TAG, FALSE); // has sound
-
-       other = missile; MUTATOR_CALLHOOK(EditProjectile);
-}
-
-// ============================
-// Begin: Genereal weapon functions
-// ============================
-void spawnfunc_weapon_seeker (void)
-{
-       weapon_defaultspawnfunc(WEP_SEEKER);
-}
-
-float w_seeker(float req)
-{
-       float ammo_amount;
-
-       if (req == WR_AIM)
-       {
-               if (autocvar_g_balance_seeker_type == 1) 
-                       if (Seeker_Tagged_Info(self, self.enemy) != world)
-                               self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_missile_speed_max, 0, autocvar_g_balance_seeker_missile_lifetime, FALSE);
-                       else
-                               self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE);
-               else
-                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE);
-       }
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo)) // forced reload
-                       weapon_action(self.weapon, WR_RELOAD);
-                       
-               else if (self.BUTTON_ATCK)
-               {
-                       if (autocvar_g_balance_seeker_type == 1) 
-                       {
-                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_missile_refire))
-                               {
-                                       Seeker_Attack();
-                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_missile_animtime, w_ready);
-                               }
-                       }
-                       else 
-                       {
-                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire))
-                               {
-                                       Seeker_Fire_Tag();
-                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready);
-                               }
-                       }
-               }
-
-               else if (self.BUTTON_ATCK2)
-               {
-                       if (autocvar_g_balance_seeker_type == 1) 
-                       {
-                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire))
-                               {
-                                       Seeker_Fire_Tag();
-                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready);
-                               }
-                       }
-                       else 
-                       {
-                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_flac_refire))
-                               {
-                                       Seeker_Fire_Flac();
-                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_flac_animtime, w_ready);
-                               }
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_seeker.md3");
-               precache_model ("models/weapons/v_seeker.md3");
-               precache_model ("models/weapons/h_seeker.iqm");
-               precache_sound ("weapons/tag_fire.wav");
-               precache_sound ("weapons/flac_fire.wav");
-               precache_sound ("weapons/seeker_fire.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_SEEKER);
-               self.current_ammo = ammo_rockets;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               if (autocvar_g_balance_seeker_type == 1) 
-               {
-                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_missile_ammo;
-                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_missile_ammo;
-               }
-               else
-               {
-                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo;
-                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo;
-               }
-               
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               if (autocvar_g_balance_seeker_type == 1) 
-               {
-                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo;
-                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo;
-               }
-               else
-               {
-                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_flac_ammo;
-                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_flac_ammo;
-               }
-               
-               return ammo_amount;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo), autocvar_g_balance_seeker_reload_ammo, autocvar_g_balance_seeker_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_SEEKER_SUICIDE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_SEEKER_MURDER_TAG;
-               else
-                       return WEAPON_SEEKER_MURDER_SPRAY;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_seeker(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 6;
-               if(w_deathtype & HITTYPE_BOUNCE)
-               {
-                       if(w_deathtype & HITTYPE_SECONDARY)
-                       {
-                               if(!w_issilent)
-                                       sound(self, CH_SHOTS, "weapons/tag_impact.wav", 1, ATTN_NORM);
-                       }
-                       else
-                       {
-                               pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
-                               if(!w_issilent)
-                               {
-                                       if (w_random<0.15)
-                                               sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTN_NORM);
-                                       else if (w_random<0.7)
-                                               sound(self, CH_SHOTS, "weapons/tagexp2.wav", 1, ATTN_NORM);
-                                       else
-                                               sound(self, CH_SHOTS, "weapons/tagexp3.wav", 1, ATTN_NORM);
-                               }
-                       }
-               }
-               else
-               {
-                       pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                       {
-                               if (w_random<0.15)
-                                       sound(self, CH_SHOTS, "weapons/seekerexp1.wav", 1, ATTN_NORM);
-                               else if (w_random<0.7)
-                                       sound(self, CH_SHOTS, "weapons/seekerexp2.wav", 1, ATTN_NORM);
-                               else
-                                       sound(self, CH_SHOTS, "weapons/seekerexp3.wav", 1, ATTN_NORM);
-                       }
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/seekerexp1.wav");
-               precache_sound("weapons/seekerexp2.wav");
-               precache_sound("weapons/seekerexp3.wav");
-               precache_sound("weapons/tagexp1.wav");
-               precache_sound("weapons/tagexp2.wav");
-               precache_sound("weapons/tagexp3.wav");
-               precache_sound("weapons/tag_impact.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/shockwave.qc b/qcsrc/common/weapons/shockwave.qc
deleted file mode 100644 (file)
index 6c6658d..0000000
+++ /dev/null
@@ -1,294 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ SHOTGUN,
-/* function  */ w_shotgun,
-/* ammotype  */ IT_SHELLS,
-/* impulse   */ 2,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
-/* rating    */ BOT_PICKUP_RATING_LOW,
-/* model     */ "shotgun",
-/* shortname */ "shotgun",
-/* fullname  */ _("Shotgun")
-);
-#else
-#ifdef SVQC
-
-void W_Shotgun_Attack (void)
-{
-       float   sc;
-       float   ammoamount;
-       float   bullets;
-       float   d;
-       float   f;
-       float   spread;
-       float   bulletspeed;
-       float   bulletconstant;
-       entity flash;
-
-       ammoamount = autocvar_g_balance_shotgun_primary_ammo;
-       bullets = autocvar_g_balance_shotgun_primary_bullets;
-       d = autocvar_g_balance_shotgun_primary_damage;
-       f = autocvar_g_balance_shotgun_primary_force;
-       spread = autocvar_g_balance_shotgun_primary_spread;
-       bulletspeed = autocvar_g_balance_shotgun_primary_speed;
-       bulletconstant = autocvar_g_balance_shotgun_primary_bulletconstant;
-
-       W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo);
-
-       W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets);
-       for (sc = 0;sc < bullets;sc = sc + 1)
-               fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant);
-       endFireBallisticBullet();
-
-       pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo);
-
-       // casing code
-       if (autocvar_g_casings >= 1)
-               for (sc = 0;sc < ammoamount;sc = sc + 1)
-                       SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self);
-
-       // muzzle flash for 1st person view
-       flash = spawn();
-       setmodel(flash, "models/uziflash.md3"); // precision set below
-       flash.think = SUB_Remove;
-       flash.nextthink = time + 0.06;
-       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
-}
-
-.float swing_prev;
-.entity swing_alreadyhit;
-void shotgun_meleethink (void)
-{
-       // declarations
-       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
-       entity target_victim;
-       vector targpos;
-
-       if(!self.cnt) // set start time of melee
-       {
-               self.cnt = time; 
-               W_PlayStrengthSound(self.realowner);
-       }
-
-       makevectors(self.realowner.v_angle); // update values for v_* vectors
-       
-       // calculate swing percentage based on time
-       meleetime = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor();
-       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
-       f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces);
-       
-       // check to see if we can still continue, otherwise give up now
-       if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_melee_no_doubleslap)
-       {
-               remove(self);
-               return;
-       }
-       
-       // if okay, perform the traces needed for this frame 
-       for(i=self.swing_prev; i < f; ++i)
-       {
-               swing_factor = ((1 - (i / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1);
-               
-               targpos = (self.realowner.origin + self.realowner.view_ofs 
-                       + (v_forward * autocvar_g_balance_shotgun_secondary_melee_range)
-                       + (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up)
-                       + (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_side));
-
-               WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner));
-               
-               // draw lightning beams for debugging
-               //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); 
-               //te_customflash(targpos, 40,  2, '1 1 1');
-               
-               is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body");
-
-               if((trace_fraction < 1) // if trace is good, apply the damage and remove self
-                       && (trace_ent.takedamage == DAMAGE_AIM)  
-                       && (trace_ent != self.swing_alreadyhit)
-                       && (is_player || autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage))
-               {
-                       target_victim = trace_ent; // so it persists through other calls
-                       
-                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
-                               swing_damage = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1));
-                       else
-                               swing_damage = (autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage * min(1, swing_factor + 1));
-                       
-                       //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
-                       
-                       Damage(target_victim, self.realowner, self.realowner, 
-                               swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY, 
-                               self.realowner.origin + self.realowner.view_ofs, 
-                               v_forward * autocvar_g_balance_shotgun_secondary_force);
-                               
-                       if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); }
-                               
-                       // draw large red flash for debugging
-                       //te_customflash(targpos, 200, 2, '15 0 0');
-                       
-                       if(autocvar_g_balance_shotgun_secondary_melee_multihit) // allow multiple hits with one swing, but not against the same player twice.
-                       {
-                               self.swing_alreadyhit = target_victim;
-                               continue; // move along to next trace
-                       }
-                       else
-                       {
-                               remove(self);
-                               return;
-                       }
-               }
-       }
-       
-       if(time >= self.cnt + meleetime)
-       {
-               // melee is finished
-               remove(self);
-               return;
-       }
-       else
-       {
-               // set up next frame 
-               self.swing_prev = i;
-               self.nextthink = time;
-       }
-}
-
-void W_Shotgun_Attack2 (void)
-{
-       sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM);
-       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_shotgun_secondary_animtime, w_ready);
-
-       entity meleetemp;
-       meleetemp = spawn();
-       meleetemp.realowner = self;
-       meleetemp.think = shotgun_meleethink;
-       meleetemp.nextthink = time + autocvar_g_balance_shotgun_secondary_melee_delay * W_WeaponRateFactor();
-       W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_shotgun_secondary_damage, autocvar_g_balance_shotgun_secondary_melee_range);
-}
-
-void spawnfunc_weapon_shotgun(); // defined in t_items.qc
-
-.float shotgun_primarytime;
-
-float w_shotgun(float req)
-{
-       float ammo_amount;
-       if (req == WR_AIM)
-               if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range)
-                       self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
-               else
-               {
-                       if(autocvar_g_antilag_bullets)
-                               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
-                       else
-                               self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE);
-               }
-
-       else if (req == WR_THINK)
-       {
-               if(autocvar_g_balance_shotgun_reload_ammo && self.clip_load < autocvar_g_balance_shotgun_primary_ammo) // forced reload
-               {
-                       // don't force reload an empty shotgun if its melee attack is active
-                       if not(autocvar_g_balance_shotgun_secondary && self.ammo_shells < autocvar_g_balance_shotgun_primary_ammo)
-                               weapon_action(self.weapon, WR_RELOAD);
-               }
-               else
-               {
-                       if (self.BUTTON_ATCK)
-                       {
-                               if (time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
-                               {
-                                       if(weapon_prepareattack(0, autocvar_g_balance_shotgun_primary_animtime))
-                                       {
-                                               W_Shotgun_Attack();
-                                               self.shotgun_primarytime = time + autocvar_g_balance_shotgun_primary_refire * W_WeaponRateFactor();
-                                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_shotgun_primary_animtime, w_ready);
-                                       }
-                               }
-                       }
-               }
-               if (self.clip_load >= 0) // we are not currently reloading
-               if (!self.crouch) // no crouchmelee please
-               if (self.BUTTON_ATCK2 && autocvar_g_balance_shotgun_secondary)
-               if (weapon_prepareattack(1, autocvar_g_balance_shotgun_secondary_refire))
-               {
-                       // attempt forcing playback of the anim by switching to another anim (that we never play) here...
-                       weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2);
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/uziflash.md3");
-               precache_model ("models/weapons/g_shotgun.md3");
-               precache_model ("models/weapons/v_shotgun.md3");
-               precache_model ("models/weapons/h_shotgun.iqm");
-               precache_sound ("misc/itempickup.wav");
-               precache_sound ("weapons/shotgun_fire.wav");
-               precache_sound ("weapons/shotgun_melee.wav");
-               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_SHOTGUN);
-               self.current_ammo = ammo_shells;
-       }
-       else if (req == WR_CHECKAMMO1)
-       {
-               ammo_amount = self.ammo_shells >= autocvar_g_balance_shotgun_primary_ammo;
-               ammo_amount += self.(weapon_load[WEP_SHOTGUN]) >= autocvar_g_balance_shotgun_primary_ammo;
-               return ammo_amount;
-       }
-       else if (req == WR_CHECKAMMO2)
-       {
-               // melee attack is always available
-               return TRUE;
-       }
-       else if (req == WR_RELOAD)
-       {
-               W_Reload(autocvar_g_balance_shotgun_primary_ammo, autocvar_g_balance_shotgun_reload_ammo, autocvar_g_balance_shotgun_reload_time, "weapons/reload.wav");
-       }
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               return WEAPON_THINKING_WITH_PORTALS;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_SHOTGUN_MURDER_SLAP;
-               else
-                       return WEAPON_SHOTGUN_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-.float prevric;
-float w_shotgun(float req)
-{
-       if(req == WR_IMPACTEFFECT)
-       {
-               vector org2;
-               org2 = w_org + w_backoff * 2;
-               pointparticles(particleeffectnum("shotgun_impact"), org2, w_backoff * 1000, 1);
-               if(!w_issilent && time - self.prevric > 0.25)
-               {
-                       if(w_random < 0.0165)
-                               sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
-                       else if(w_random < 0.033)
-                               sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
-                       else if(w_random < 0.05)
-                               sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
-                       self.prevric = time;
-               }
-       }
-       else if(req == WR_PRECACHE)
-       {
-               precache_sound("weapons/ric1.wav");
-               precache_sound("weapons/ric2.wav");
-               precache_sound("weapons/ric3.wav");
-       }
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/tuba.qc b/qcsrc/common/weapons/tuba.qc
deleted file mode 100644 (file)
index 48b696d..0000000
+++ /dev/null
@@ -1,465 +0,0 @@
-#ifdef REGISTER_WEAPON
-REGISTER_WEAPON(
-/* WEP_##id  */ TUBA,
-/* function  */ w_tuba,
-/* ammotype  */ 0,
-/* impulse   */ 1,
-/* flags     */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_MID,
-/* model     */ "tuba",
-/* shortname */ "tuba",
-/* fullname  */ _("@!#%'n Tuba")
-);
-#else
-#ifdef SVQC
-//#define TUBA_NOTE(n) strcat("weapons/tuba_note", ftos(n), ".wav")
-.entity tuba_note;
-.float tuba_smoketime;
-.float tuba_instrument;
-
-#define MAX_TUBANOTES 32
-.float tuba_lastnotes_last;
-.float tuba_lastnotes_cnt; // over
-.vector tuba_lastnotes[MAX_TUBANOTES];
-
-float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo)
-{
-       float i, j, mmin, mmax, nolength;
-       float n = tokenize_console(melody);
-       if(n > pl.tuba_lastnotes_cnt)
-               return FALSE;
-       float pitchshift = 0;
-
-       if(instrument >= 0)
-               if(pl.tuba_instrument != instrument)
-                       return FALSE;
-
-       // verify notes...
-       nolength = FALSE;
-       for(i = 0; i < n; ++i)
-       {
-               vector v = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
-               float ai = stof(argv(n - i - 1));
-               float np = floor(ai);
-               if(ai == np)
-                       nolength = TRUE;
-               // n counts the last played notes BACKWARDS
-               // _x is start
-               // _y is end
-               // _z is note pitch
-               if(ignorepitch && i == 0)
-               {
-                       pitchshift = np - v_z;
-               }
-               else
-               {
-                       if(v_z + pitchshift != np)
-                               return FALSE;
-               }
-       }
-
-       // now we know the right NOTES were played
-       if(!nolength)
-       {
-               // verify rhythm...
-               float ti = 0;
-               if(maxtempo > 0)
-                       mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
-               else
-                       mmin = 0;
-               if(mintempo > 0)
-                       mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
-               else
-                       mmax = 240; // you won't try THAT hard... (tempo 1)
-               //print(sprintf("initial tempo rules: %f %f\n", mmin, mmax));
-
-               for(i = 0; i < n; ++i)
-               {
-                       vector vi = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
-                       float ai = stof(argv(n - i - 1));
-                       ti -= 1 / (ai - floor(ai));
-                       float tj = ti;
-                       for(j = i+1; j < n; ++j)
-                       {
-                               vector vj = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - j + MAX_TUBANOTES, MAX_TUBANOTES)]);
-                               float aj = stof(argv(n - j - 1));
-                               tj -= (aj - floor(aj));
-
-                               // note i should be at m*ti+b
-                               // note j should be at m*tj+b
-                               // so:
-                               // we have a LINE l, so that
-                               // vi_x <= l(ti) <= vi_y
-                               // vj_x <= l(tj) <= vj_y
-                               // what is m?
-
-                               // vi_x <= vi_y <= vj_x <= vj_y
-                               // ti <= tj
-                               //print(sprintf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti));
-                               //print(sprintf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj));
-                               //print(sprintf("m1 = %f\n", (vi_x - vj_y) / (ti - tj)));
-                               //print(sprintf("m2 = %f\n", (vi_y - vj_x) / (ti - tj)));
-                               mmin = max(mmin, (vi_x - vj_y) / (ti - tj)); // lower bound
-                               mmax = min(mmax, (vi_y - vj_x) / (ti - tj)); // upper bound
-                       }
-               }
-
-               if(mmin > mmax) // rhythm fail
-                       return FALSE;
-       }
-
-       pl.tuba_lastnotes_cnt = 0;
-
-       return TRUE;
-}
-
-void W_Tuba_NoteOff()
-{
-       // we have a note:
-       //   on: self.spawnshieldtime
-       //   off: time
-       //   note: self.cnt
-       if(self.owner.tuba_note == self)
-       {
-               self.owner.tuba_lastnotes_last = mod(self.owner.tuba_lastnotes_last + 1, MAX_TUBANOTES);
-               self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt;
-               self.owner.tuba_note = world;
-               self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES);
-
-               string s;
-               s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null);
-               if(s != "")
-               {
-                       // simulate a server message
-                       switch(self.tuba_instrument)
-                       {
-                               default:
-                               case 0: // Tuba
-                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n"));
-                                       break;
-                               case 1:
-                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n"));
-                                       break;
-                               case 2:
-                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n"));
-                                       break;
-                       }
-               }
-       }
-       remove(self);
-}
-
-float Tuba_GetNote(entity pl, float hittype)
-{
-       float note;
-       float movestate;
-       movestate = 5;
-       if(pl.movement_x < 0) movestate -= 3;
-       if(pl.movement_x > 0) movestate += 3;
-       if(pl.movement_y < 0) movestate -= 1;
-       if(pl.movement_y > 0) movestate += 1;
-#ifdef GMQCC
-       note = 0;
-#endif
-       switch(movestate)
-       {
-       // layout: originally I wanted
-       //   eb e  e#=f
-       //   B  c  d
-       //   Gb G  G#
-       // but then you only use forward and right key. So to make things more
-       // interesting, I swapped B with e#. Har har har...
-       //   eb e  B
-       // f=e# c  d
-       //   Gb G  G#
-               case 1: note = -6; break; // Gb
-               case 2: note = -5; break; // G
-               case 3: note = -4; break; // G#
-               case 4: note = +5; break; // e#
-               default:
-               case 5: note =  0; break; // c
-               case 6: note = +2; break; // d
-               case 7: note = +3; break; // eb
-               case 8: note = +4; break; // e
-               case 9: note = -1; break; // B
-       }
-       if(pl.BUTTON_CROUCH)
-               note -= 12;
-       if(pl.BUTTON_JUMP)
-               note += 12;
-       if(hittype & HITTYPE_SECONDARY)
-               note += 7;
-       
-       // we support two kinds of tubas, those tuned in Eb and those tuned in C
-       // kind of tuba currently is player slot number, or team number if in
-       // teamplay
-       // that way, holes in the range of notes are "plugged"
-       if(teamplay)
-       {
-               if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4)
-                       note += 3;
-       }
-       else
-       {
-               if(pl.clientcolors & 1)
-                       note += 3;
-       }
-       
-       // total range of notes:
-       //                       0
-       //                 ***  ** ****
-       //                        ***  ** ****
-       //     ***  ** ****
-       //            ***  ** ****
-       //     ***  ********************* ****
-       //     -18.........................+12
-       //        ***  ********************* ****
-       //     -18............................+15
-       //     with jump: ... +24
-       //     ... +27
-       return note;
-}
-
-float W_Tuba_NoteSendEntity(entity to, float sf)
-{
-       float f;
-
-       msg_entity = to;
-       if(!sound_allowed(MSG_ONE, self.realowner))
-               return FALSE;
-
-       WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE);
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & 1)
-       {
-               WriteChar(MSG_ENTITY, self.cnt);
-               f = 0;
-               if(self.realowner != to)
-                       f |= 1;
-               f |= 2 * self.tuba_instrument;
-               WriteByte(MSG_ENTITY, f);
-       }
-       if(sf & 2)
-       {
-               WriteCoord(MSG_ENTITY, self.origin_x);
-               WriteCoord(MSG_ENTITY, self.origin_y);
-               WriteCoord(MSG_ENTITY, self.origin_z);
-       }
-       return TRUE;
-}
-
-void W_Tuba_NoteThink()
-{
-       float dist_mult;
-       float vol0, vol1;
-       vector dir0, dir1;
-       vector v;
-       entity e;
-       if(time > self.teleport_time)
-       {
-               W_Tuba_NoteOff();
-               return;
-       }
-       self.nextthink = time;
-       dist_mult = autocvar_g_balance_tuba_attenuation / autocvar_snd_soundradius;
-       FOR_EACH_REALCLIENT(e)
-       if(e != self.realowner)
-       {
-               v = self.origin - (e.origin + e.view_ofs);
-               vol0 = max(0, 1 - vlen(v) * dist_mult);
-               dir0 = normalize(v);
-               v = self.realowner.origin - (e.origin + e.view_ofs);
-               vol1 = max(0, 1 - vlen(v) * dist_mult);
-               dir1 = normalize(v);
-               if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume
-               {
-                       setorigin(self, self.realowner.origin);
-                       self.SendFlags |= 2;
-                       break;
-               }
-               if(dir0 * dir1 < 0.9994) // 2 degrees change in angle
-               {
-                       setorigin(self, self.realowner.origin);
-                       self.SendFlags |= 2;
-                       break;
-               }
-       }
-}
-
-void W_Tuba_NoteOn(float hittype)
-{
-       vector o;
-       float n;
-
-       W_SetupShot(self, FALSE, 2, "", 0, autocvar_g_balance_tuba_damage);
-
-       n = Tuba_GetNote(self, hittype);
-
-       hittype = 0;
-       if(self.tuba_instrument & 1)
-               hittype |= HITTYPE_SECONDARY;
-       if(self.tuba_instrument & 2)
-               hittype |= HITTYPE_BOUNCE;
-
-       if(self.tuba_note)
-       {
-               if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument)
-               {
-                       entity oldself = self;
-                       self = self.tuba_note;
-                       W_Tuba_NoteOff();
-                       self = oldself;
-               }
-       }
-
-       if not(self.tuba_note)
-       {
-               self.tuba_note = spawn();
-               self.tuba_note.owner = self.tuba_note.realowner = self;
-               self.tuba_note.cnt = n;
-               self.tuba_note.tuba_instrument = self.tuba_instrument;
-               self.tuba_note.think = W_Tuba_NoteThink;
-               self.tuba_note.nextthink = time;
-               self.tuba_note.spawnshieldtime = time;
-               Net_LinkEntity(self.tuba_note, FALSE, 0, W_Tuba_NoteSendEntity);
-       }
-
-       self.tuba_note.teleport_time = time + autocvar_g_balance_tuba_refire * 2 * W_WeaponRateFactor(); // so it can get prolonged safely
-
-       //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation);
-       RadiusDamage(self, self, autocvar_g_balance_tuba_damage, autocvar_g_balance_tuba_edgedamage, autocvar_g_balance_tuba_radius, world, world, autocvar_g_balance_tuba_force, hittype | WEP_TUBA, world);
-
-       o = gettaginfo(self.exteriorweaponentity, 0);
-       if(time > self.tuba_smoketime)
-       {
-               pointparticles(particleeffectnum("smoke_ring"), o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1);
-               self.tuba_smoketime = time + 0.25;
-       }
-}
-
-void spawnfunc_weapon_tuba (void)
-{
-       weapon_defaultspawnfunc(WEP_TUBA);
-}
-
-float w_tuba(float req)
-{
-       if (req == WR_AIM)
-       {
-               // bots cannot play the Tuba well yet
-               // I think they should start with the recorder first
-               if(vlen(self.origin - self.enemy.origin) < autocvar_g_balance_tuba_radius)
-               {
-                       if(random() > 0.5)
-                               self.BUTTON_ATCK = 1;
-                       else
-                               self.BUTTON_ATCK2 = 1;
-               }
-       }
-       else if (req == WR_THINK)
-       {
-               if (self.BUTTON_ATCK)
-               if (weapon_prepareattack(0, autocvar_g_balance_tuba_refire))
-               {
-                       W_Tuba_NoteOn(0);
-                       //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready);
-                       weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready);
-               }
-               if (self.BUTTON_ATCK2)
-               if (weapon_prepareattack(1, autocvar_g_balance_tuba_refire))
-               {
-                       W_Tuba_NoteOn(HITTYPE_SECONDARY);
-                       //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready);
-                       weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready);
-               }
-               if(self.tuba_note)
-               {
-                       if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2)
-                       {
-                               entity oldself = self;
-                               self = self.tuba_note;
-                               W_Tuba_NoteOff();
-                               self = oldself;
-                       }
-               }
-       }
-       else if (req == WR_PRECACHE)
-       {
-               precache_model ("models/weapons/g_tuba.md3");
-               precache_model ("models/weapons/v_tuba.md3");
-               precache_model ("models/weapons/h_tuba.iqm");
-               precache_model ("models/weapons/v_akordeon.md3");
-               precache_model ("models/weapons/h_akordeon.iqm");
-               precache_model ("models/weapons/v_kleinbottle.md3");
-               precache_model ("models/weapons/h_kleinbottle.iqm");
-
-               //float i;
-               //for(i = -18; i <= +27; ++i)
-               //      precache_sound(TUBA_NOTE(i));
-       }
-       else if (req == WR_SETUP)
-       {
-               weapon_setup(WEP_TUBA);
-               self.current_ammo = ammo_none;
-               self.tuba_instrument = 0;
-       }
-       else if (req == WR_RELOAD)
-       {
-               // switch to alternate instruments :)
-               if(self.weaponentity.state == WS_READY)
-               {
-                       switch(self.tuba_instrument)
-                       {
-                               case 0:
-                                       self.tuba_instrument = 1;
-                                       self.weaponname = "akordeon";
-                                       break;
-                               case 1:
-                                       self.tuba_instrument = 2;
-                                       self.weaponname = "kleinbottle";
-                                       break;
-                               case 2:
-                                       self.tuba_instrument = 0;
-                                       self.weaponname = "tuba";
-                                       break;
-                       }
-                       W_SetupShot(self, FALSE, 0, "", 0, 0);
-                       pointparticles(particleeffectnum("teleport"), w_shotorg, '0 0 0', 1);
-                       self.weaponentity.state = WS_INUSE;
-                       weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready);
-               }
-       }
-       else if (req == WR_CHECKAMMO1)
-               return TRUE; // TODO use fuel?
-       else if (req == WR_CHECKAMMO2)
-               return TRUE; // TODO use fuel?
-       else if (req == WR_SUICIDEMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_BOUNCE)
-                       return WEAPON_KLEINBOTTLE_SUICIDE;
-               else if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_ACCORDEON_SUICIDE;
-               else
-                       return WEAPON_TUBA_SUICIDE;
-       }
-       else if (req == WR_KILLMESSAGE)
-       {
-               if(w_deathtype & HITTYPE_BOUNCE)
-                       return WEAPON_KLEINBOTTLE_MURDER;
-               else if(w_deathtype & HITTYPE_SECONDARY)
-                       return WEAPON_ACCORDEON_MURDER;
-               else
-                       return WEAPON_TUBA_MURDER;
-       }
-       return TRUE;
-}
-#endif
-#ifdef CSQC
-float w_tuba(float req)
-{
-       // nothing to do here; particles of tuba are handled differently
-
-       return TRUE;
-}
-#endif
-#endif
diff --git a/qcsrc/common/weapons/w_arc.qc b/qcsrc/common/weapons/w_arc.qc
new file mode 100644 (file)
index 0000000..e4b7230
--- /dev/null
@@ -0,0 +1,296 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ LIGHTNING,
+/* function  */ w_lightning,
+/* ammotype  */ IT_CELLS,
+/* impulse   */ 5,
+/* flags     */ WEP_FLAG_NORMAL | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "lightning",
+/* shortname */ "lightning",
+/* fullname  */ _("Lightning")
+);
+#else
+#ifdef SVQC
+
+// Declarations ========================= 
+.vector hook_start, hook_end; // used for beam
+.entity lightning_beam; // used for beam
+.float BUTTON_ATCK_prev; // for better animation control
+.float lg_fire_prev; // for better animation control
+
+// Lightning functions ========================= 
+float W_Lightning_Beam_Send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_LIGHTNING_BEAM);
+       sf = sf & 0x7F;
+       if(sound_allowed(MSG_BROADCAST, self.owner))
+               sf |= 0x80;
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 1)
+       {
+               WriteByte(MSG_ENTITY, num_for_edict(self.owner));
+               WriteCoord(MSG_ENTITY, autocvar_g_balance_lightning_primary_range);
+       }
+       if(sf & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_start_x);
+               WriteCoord(MSG_ENTITY, self.hook_start_y);
+               WriteCoord(MSG_ENTITY, self.hook_start_z);
+       }
+       if(sf & 4)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_end_x);
+               WriteCoord(MSG_ENTITY, self.hook_end_y);
+               WriteCoord(MSG_ENTITY, self.hook_end_z);
+       }
+       return TRUE;
+}
+
+void W_Lightning_Beam_Think()
+{
+       self.owner.lg_fire_prev = time;
+       if (self != self.owner.lightning_beam)
+       {
+               remove(self);
+               return;
+       }
+       if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen)
+       {
+               if(self == self.owner.lightning_beam)
+                       self.owner.lightning_beam = world;
+               remove(self);
+               return;
+       }
+
+       self.nextthink = time;
+
+       makevectors(self.owner.v_angle);
+
+       float dt, f;
+       dt = frametime;
+       if not(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)
+       {
+               if(autocvar_g_balance_lightning_primary_ammo)
+               {
+                       dt = min(dt, self.owner.ammo_cells / autocvar_g_balance_lightning_primary_ammo);
+                       self.owner.ammo_cells = max(0, self.owner.ammo_cells - autocvar_g_balance_lightning_primary_ammo * frametime);
+               }
+       }
+
+       W_SetupShot_Range(self.owner, TRUE, 0, "", 0, autocvar_g_balance_lightning_primary_damage * dt, autocvar_g_balance_lightning_primary_range);
+       WarpZone_traceline_antilag(self.owner, w_shotorg, w_shotend, MOVE_NORMAL, self.owner, ANTILAG_LATENCY(self.owner));
+
+       // apply the damage
+       if(trace_ent)
+       {
+               vector force;
+               force = w_shotdir * autocvar_g_balance_lightning_primary_force;
+
+               f = ExponentialFalloff(autocvar_g_balance_lightning_primary_falloff_mindist, autocvar_g_balance_lightning_primary_falloff_maxdist, autocvar_g_balance_lightning_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg));
+
+               if(accuracy_isgooddamage(self.owner, trace_ent))
+                       accuracy_add(self.owner, WEP_LIGHTNING, 0, autocvar_g_balance_lightning_primary_damage * dt * f);
+               Damage (trace_ent, self.owner, self.owner, autocvar_g_balance_lightning_primary_damage * dt * f, WEP_LIGHTNING, trace_endpos, force * dt);
+       }
+
+       // draw effect
+       if(w_shotorg != self.hook_start)
+       {
+               self.SendFlags |= 2;
+               self.hook_start = w_shotorg;
+       }
+       if(w_shotend != self.hook_end)
+       {
+               self.SendFlags |= 4;
+               self.hook_end = w_shotend;
+       }
+}
+
+// Attack functions ========================= 
+void W_Lightning_Attack1 (void)
+{
+       // only play fire sound if 0.5 sec has passed since player let go the fire button
+       if(time - self.lg_fire_prev > 0.5)
+               sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
+
+       entity beam, oldself;
+
+       self.lightning_beam = beam = spawn();
+       beam.classname = "W_Lightning_Beam";
+       beam.solid = SOLID_NOT;
+       beam.think = W_Lightning_Beam_Think;
+       beam.owner = self;
+       beam.movetype = MOVETYPE_NONE;
+       beam.shot_spread = 1;
+       beam.bot_dodge = TRUE;
+       beam.bot_dodgerating = autocvar_g_balance_lightning_primary_damage;
+       Net_LinkEntity(beam, FALSE, 0, W_Lightning_Beam_Send);
+
+       oldself = self;
+       self = beam;
+       self.think();
+       self = oldself;
+}
+
+float w_lightning(float req)
+{
+       if (req == WR_AIM)
+       {
+               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
+               /*
+               self.BUTTON_ATCK=FALSE;
+               self.BUTTON_ATCK2=FALSE;
+               if(vlen(self.origin-self.enemy.origin) > 1000)
+                       self.bot_aim_whichfiretype = 0;
+               if(self.bot_aim_whichfiretype == 0)
+               {
+                       float shoot;
+
+                       if(autocvar_g_balance_lightning_primary_speed)
+                               shoot = bot_aim(autocvar_g_balance_lightning_primary_speed, 0, autocvar_g_balance_lightning_primary_lifetime, FALSE);
+                       else
+                               shoot = bot_aim(1000000, 0, 0.001, FALSE);
+
+                       if(shoot)
+                       {
+                               self.BUTTON_ATCK = TRUE;
+                               if(random() < 0.01) self.bot_aim_whichfiretype = 1;
+                       }
+               }
+               else // todo
+               {
+                       //if(bot_aim(autocvar_g_balance_lightning_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_lightning_secondary_lifetime, TRUE))
+                       //{
+                       //      self.BUTTON_ATCK2 = TRUE;
+                       //      if(random() < 0.03) self.bot_aim_whichfiretype = 0;
+                       //}
+               }
+               */
+       }
+       else if (req == WR_THINK)
+       {
+               if (self.BUTTON_ATCK)
+               {
+                       if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this!
+                               /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
+                                       weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_lightning_primary_animtime, w_ready);
+                               else*/
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready);
+                       
+                       if (weapon_prepareattack(0, 0))
+                       {
+                               if ((!self.lightning_beam) || wasfreed(self.lightning_beam))
+                                       W_Lightning_Attack1();
+                               
+                               if(!self.BUTTON_ATCK_prev)
+                               {
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready);
+                                       self.BUTTON_ATCK_prev = 1;
+                               }
+                       }
+               } 
+               else // todo
+               {
+                       if (self.BUTTON_ATCK_prev != 0)
+                       {
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready);
+                               ATTACK_FINISHED(self) = time + autocvar_g_balance_lightning_primary_refire * W_WeaponRateFactor();
+                       }
+                       self.BUTTON_ATCK_prev = 0;
+               }
+
+               //if (self.BUTTON_ATCK2)
+                       //if (weapon_prepareattack(1, autocvar_g_balance_lightning_secondary_refire))
+                       //{
+                       //      W_Lightning_Attack2();
+                       //      self.lightning_count = autocvar_g_balance_lightning_secondary_count;
+                       //      weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_lightning_secondary_animtime, w_lightning_checkattack);
+                       //      self.lightning_secondarytime = time + autocvar_g_balance_lightning_secondary_refire2 * W_WeaponRateFactor();
+                       //}
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_lightning.md3");
+               precache_model ("models/weapons/v_lightning.md3");
+               precache_model ("models/weapons/h_lightning.iqm");
+               //precache_sound ("weapons/lightning_bounce.wav");
+               precache_sound ("weapons/lightning_fire.wav");
+               precache_sound ("weapons/lightning_fire2.wav");
+               precache_sound ("weapons/lightning_impact.wav");
+               //precache_sound ("weapons/lightning_impact_combo.wav");
+               //precache_sound ("weapons/W_Lightning_Beam_fire.wav");
+       }
+       else if (req == WR_SETUP)
+               weapon_setup(WEP_LIGHTNING);
+       else if (req == WR_CHECKAMMO1)
+       {
+               return !autocvar_g_balance_lightning_primary_ammo || (self.ammo_cells > 0);
+       }
+       else if (req == WR_CHECKAMMO2)
+               return self.ammo_cells >= autocvar_g_balance_lightning_secondary_ammo;
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       return WEAPON_ELECTRO_MURDER_ORBS;
+               }
+               else
+               {
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                               return WEAPON_ELECTRO_MURDER_COMBO;
+                       else
+                               return WEAPON_ELECTRO_MURDER_BOLT;
+               }
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               //self.lightning_secondarytime = time;
+       }
+       return TRUE;
+};
+
+void LightningInit()
+{
+       weapon_action(WEP_LIGHTNING, WR_PRECACHE);
+       lightning_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 1);
+       lightning_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 2);
+       lightning_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 3);
+       lightning_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 4);
+}
+
+void spawnfunc_weapon_lightning (void) // should this really be here?
+{
+       weapon_defaultspawnfunc(WEP_LIGHTNING);
+}
+#endif
+#ifdef CSQC
+float w_lightning(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 6;
+               
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       pointparticles(particleeffectnum("lightning_ballexplode"), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM);
+               }
+               else
+               {
+                       pointparticles(particleeffectnum("lightning_impact"), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM);
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/lightning_impact.wav");
+               precache_sound("weapons/lightning_impact_combo.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_arc.qh b/qcsrc/common/weapons/w_arc.qh
new file mode 100644 (file)
index 0000000..57d6ceb
--- /dev/null
@@ -0,0 +1,2 @@
+void LightningInit();
+vector lightning_shotorigin[4];
diff --git a/qcsrc/common/weapons/w_blaster.qc b/qcsrc/common/weapons/w_blaster.qc
new file mode 100644 (file)
index 0000000..c331c5b
--- /dev/null
@@ -0,0 +1,576 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ LASER,
+/* function  */ W_Laser,
+/* ammotype  */ 0,
+/* impulse   */ 1,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ 0,
+/* model     */ "laser",
+/* shortname */ "laser",
+/* fullname  */ _("Blaster")
+);
+#else
+#ifdef SVQC
+void(float imp) W_SwitchWeapon;
+void() W_LastWeapon;
+.float swing_prev;
+.entity swing_alreadyhit;
+
+void SendCSQCShockwaveParticle(vector endpos) 
+{
+       //endpos = WarpZone_UnTransformOrigin(transform, endpos);
+       
+       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
+       WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE);
+       WriteCoord(MSG_BROADCAST, w_shotorg_x);
+       WriteCoord(MSG_BROADCAST, w_shotorg_y);
+       WriteCoord(MSG_BROADCAST, w_shotorg_z);
+       WriteCoord(MSG_BROADCAST, endpos_x);
+       WriteCoord(MSG_BROADCAST, endpos_y);
+       WriteCoord(MSG_BROADCAST, endpos_z);
+       WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_max, 255));
+       WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_min, 255));
+       WriteByte(MSG_BROADCAST, num_for_edict(self));
+}
+
+void W_Laser_Touch()
+{
+       PROJECTILE_TOUCH;
+
+       self.event_damage = func_null;
+       
+       if(self.dmg)
+               RadiusDamage(self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other);
+       else
+               RadiusDamage(self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other);
+
+       remove(self);
+}
+
+void W_Laser_Think()
+{
+       self.movetype = MOVETYPE_FLY;
+       self.think = SUB_Remove;
+       
+       if(self.dmg)
+               self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime;
+       else
+               self.nextthink = time + autocvar_g_balance_laser_primary_lifetime;
+               
+       CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE);
+}
+
+
+float W_Laser_Shockwave_CheckSpread(vector targetorg, vector nearest_on_line, vector sw_shotorg, vector attack_endpos)
+{
+       float spreadlimit;
+       float distance_of_attack = vlen(sw_shotorg - attack_endpos);
+       float distance_from_line = vlen(targetorg - nearest_on_line);
+       
+       spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1);
+       spreadlimit = (autocvar_g_balance_laser_shockwave_spread_min * (1 - spreadlimit) + autocvar_g_balance_laser_shockwave_spread_max * spreadlimit);
+       
+       if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90))
+               return bound(0, (distance_from_line / spreadlimit), 1);
+       else
+               return FALSE;
+}
+
+float W_Laser_Shockwave_IsVisible(entity head, vector nearest_on_line, vector sw_shotorg, vector attack_endpos)
+{
+       vector nearest_to_attacker = head.WarpZone_findradius_nearest;
+       vector center = (head.origin + (head.mins + head.maxs) * 0.5);
+       vector corner;
+       float i;
+
+       // STEP ONE: Check if the nearest point is clear
+       if(W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos))
+       {
+               WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self);
+               if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage
+       }
+
+       // STEP TWO: Check if shotorg to center point is clear
+       if(W_Laser_Shockwave_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos))
+       {
+               WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self);
+               if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage
+       }
+
+       // STEP THREE: Check each corner to see if they are clear
+       for(i=1; i<=8; ++i)
+       {
+               corner = get_corner_position(head, i);
+               if(W_Laser_Shockwave_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos))
+               {
+                       WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self);
+                       if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage
+               }
+       }
+
+       return FALSE;
+}
+
+#define PLAYER_CENTER(ent) (ent.origin + ((ent.classname == "player") ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
+
+entity shockwave_hit[32];
+float shockwave_hit_damage[32];
+vector shockwave_hit_force[32];
+
+float W_Laser_Shockwave_CheckHit(float queue, entity head, vector final_force, float final_damage)
+{
+       if not(head) { return FALSE; }
+       float i;
+
+       ++queue;
+       
+       for(i = 1; i <= queue; ++i)
+       {
+               if(shockwave_hit[i] == head)
+               {
+                       if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; }
+                       if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; }
+                       return FALSE;
+               }
+       }
+
+       shockwave_hit[queue] = head;
+       shockwave_hit_force[queue] = final_force;
+       shockwave_hit_damage[queue] = final_damage;
+       return TRUE;
+}
+
+void W_Laser_Shockwave()
+{
+       // declarations
+       float multiplier, multiplier_from_accuracy, multiplier_from_distance;
+       float final_damage; //, final_spread;
+       vector final_force, center, vel;
+       entity head, next;
+
+       float i, queue = 0;
+       
+       // set up the shot direction
+       W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_shockwave_damage);
+       vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_shockwave_distance));
+       WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self);
+       vector attack_hitpos = trace_endpos;
+       float distance_to_end = vlen(w_shotorg - attack_endpos);
+       float distance_to_hit = vlen(w_shotorg - attack_hitpos);
+       //entity transform = WarpZone_trace_transform;
+
+       // do the firing effect now
+       SendCSQCShockwaveParticle(attack_endpos);
+       Damage_DamageInfo(attack_hitpos, autocvar_g_balance_laser_shockwave_splash_damage, autocvar_g_balance_laser_shockwave_splash_edgedamage, autocvar_g_balance_laser_shockwave_splash_radius, w_shotdir * autocvar_g_balance_laser_shockwave_splash_force, WEP_LASER, 0, self);
+
+       // splash damage/jumping trace
+       head = WarpZone_FindRadius(attack_hitpos, max(autocvar_g_balance_laser_shockwave_splash_radius, autocvar_g_balance_laser_shockwave_jump_radius), FALSE);
+       while(head)
+       {
+               next = head.chain;
+
+               if(head.takedamage)
+               {
+                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+                       center = PLAYER_CENTER(head);
+
+                       float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest);
+                       
+                       if((head == self) && (distance_to_head <= autocvar_g_balance_laser_shockwave_jump_radius))
+                       {
+                               multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_jump_radius)) : 0));
+                               multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0));
+                               multiplier = max(autocvar_g_balance_laser_shockwave_jump_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_jump_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_jump_multiplier_distance)));
+
+                               final_force = ((normalize(center - attack_hitpos) * autocvar_g_balance_laser_shockwave_jump_force) * multiplier);
+                               vel = head.velocity; vel_z = 0;
+                               vel = normalize(vel) * bound(0, vlen(vel) / autocvar_sv_maxspeed, 1) * autocvar_g_balance_laser_shockwave_jump_force_velocitybias;
+                               final_force = (vlen(final_force) * normalize(normalize(final_force) + vel));
+                               final_force_z *= autocvar_g_balance_laser_shockwave_jump_force_zscale;
+                               final_damage = (autocvar_g_balance_laser_shockwave_jump_damage * multiplier + autocvar_g_balance_laser_shockwave_jump_edgedamage * (1 - multiplier));
+
+                               Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force);
+                               //print("SELF HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n");
+                       }
+                       else if (distance_to_head <= autocvar_g_balance_laser_shockwave_splash_radius)
+                       {       
+                               multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_splash_radius)) : 0));
+                               multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0));
+                               multiplier = max(autocvar_g_balance_laser_shockwave_splash_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_splash_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_splash_multiplier_distance)));
+
+                               final_force = normalize(center - (attack_hitpos - (w_shotdir * autocvar_g_balance_laser_shockwave_splash_force_forwardbias)));
+                               //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200)));
+                               final_force = ((final_force * autocvar_g_balance_laser_shockwave_splash_force) * multiplier);
+                               final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale;
+                               final_damage = (autocvar_g_balance_laser_shockwave_splash_damage * multiplier + autocvar_g_balance_laser_shockwave_splash_edgedamage * (1 - multiplier));
+
+                               if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; }
+                               //print("SPLASH HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n");
+                       }
+               }
+               head = next;
+       }
+
+       // cone damage trace
+       head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_shockwave_distance, FALSE);
+       while(head)
+       {
+               next = head.chain;
+               
+               if((head != self) && head.takedamage)
+               {
+                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
+                       center = PLAYER_CENTER(head);
+
+                       // find the closest point on the enemy to the center of the attack
+                       float ang; // angle between shotdir and h
+                       float h; // hypotenuse, which is the distance between attacker to head
+                       float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
+                       
+                       h = vlen(center - self.origin);
+                       ang = acos(dotproduct(normalize(center - self.origin), w_shotdir));
+                       a = h * cos(ang);
+
+                       vector nearest_on_line = (w_shotorg + a * w_shotdir);
+                       vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
+                       float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this
+
+                       if((distance_to_target <= autocvar_g_balance_laser_shockwave_distance) 
+                               && (W_Laser_Shockwave_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos)))
+                       {
+                               multiplier_from_accuracy = (1 - W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_endpos));
+                               multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_target / distance_to_end)) : 0));
+                               multiplier = max(autocvar_g_balance_laser_shockwave_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_multiplier_distance)));
+
+                               final_force = normalize(center - (nearest_on_line - (w_shotdir * autocvar_g_balance_laser_shockwave_force_forwardbias)));
+                               //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200)));
+                               final_force = ((final_force * autocvar_g_balance_laser_shockwave_force) * multiplier);
+                               final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale;
+                               final_damage = (autocvar_g_balance_laser_shockwave_damage * multiplier + autocvar_g_balance_laser_shockwave_edgedamage * (1 - multiplier));
+
+                               if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; }
+                               //print("CONE HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n");
+                       }
+               }
+               head = next;
+       }
+
+       for(i = 1; i <= queue; ++i)
+       {
+               head = shockwave_hit[i];
+               final_force = shockwave_hit_force[i];
+               final_damage = shockwave_hit_damage[i];
+               
+               Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force);
+               print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n");
+               
+               shockwave_hit[i] = world;
+               shockwave_hit_force[i] = '0 0 0';
+               shockwave_hit_damage[i] = 0;
+       }
+       //print("queue was ", ftos(queue), ".\n\n");
+}
+
+void W_Laser_Melee_Think()
+{
+       // declarations
+       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
+       entity target_victim;
+       vector targpos;
+
+       if(!self.cnt) // set start time of melee
+       {
+               self.cnt = time; 
+               W_PlayStrengthSound(self.realowner);
+       }
+
+       makevectors(self.realowner.v_angle); // update values for v_* vectors
+       
+       // calculate swing percentage based on time
+       meleetime = autocvar_g_balance_laser_melee_time * W_WeaponRateFactor();
+       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
+       f = ((1 - swing) * autocvar_g_balance_laser_melee_traces);
+       
+       // check to see if we can still continue, otherwise give up now
+       if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_laser_melee_no_doubleslap)
+       {
+               remove(self);
+               return;
+       }
+       
+       // if okay, perform the traces needed for this frame 
+       for(i=self.swing_prev; i < f; ++i)
+       {
+               swing_factor = ((1 - (i / autocvar_g_balance_laser_melee_traces)) * 2 - 1);
+               
+               targpos = (self.realowner.origin + self.realowner.view_ofs 
+                       + (v_forward * autocvar_g_balance_laser_melee_range)
+                       + (v_up * swing_factor * autocvar_g_balance_laser_melee_swing_up)
+                       + (v_right * swing_factor * autocvar_g_balance_laser_melee_swing_side));
+
+               WarpZone_traceline_antilag(self.realowner, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, ANTILAG_LATENCY(self.realowner));
+               
+               // draw lightning beams for debugging
+               te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); 
+               te_customflash(targpos, 40,  2, '1 1 1');
+               
+               is_player = (trace_ent.classname == "player" || trace_ent.classname == "body");
+
+               if((trace_fraction < 1) // if trace is good, apply the damage and remove self
+                       && (trace_ent.takedamage == DAMAGE_AIM)  
+                       && (trace_ent != self.swing_alreadyhit)
+                       && (is_player || autocvar_g_balance_laser_melee_nonplayerdamage))
+               {
+                       target_victim = trace_ent; // so it persists through other calls
+                       
+                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
+                               swing_damage = (autocvar_g_balance_laser_melee_damage * min(1, swing_factor + 1));
+                       else
+                               swing_damage = (autocvar_g_balance_laser_melee_nonplayerdamage * min(1, swing_factor + 1));
+                       
+                       //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
+                       
+                       Damage(target_victim, self.realowner, self.realowner, 
+                               swing_damage, WEP_LASER | HITTYPE_SECONDARY, 
+                               self.realowner.origin + self.realowner.view_ofs, 
+                               v_forward * autocvar_g_balance_laser_melee_force);
+                               
+                       if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LASER, 0, swing_damage); }
+                       
+                       if(autocvar_g_balance_laser_melee_multihit) // allow multiple hits with one swing, but not against the same player twice.
+                       {
+                               self.swing_alreadyhit = target_victim;
+                               continue; // move along to next trace
+                       }
+                       else
+                       {
+                               remove(self);
+                               return;
+                       }
+               }
+       }
+       
+       if(time >= self.cnt + meleetime)
+       {
+               // melee is finished
+               remove(self);
+               return;
+       }
+       else
+       {
+               // set up next frame 
+               self.swing_prev = i;
+               self.nextthink = time;
+       }
+}
+
+void W_Laser_Melee()
+{
+       sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM);
+       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_melee_animtime, w_ready);
+
+       entity meleetemp;
+       meleetemp = spawn();
+       meleetemp.owner = meleetemp.realowner = self;
+       meleetemp.think = W_Laser_Melee_Think;
+       meleetemp.nextthink = time + autocvar_g_balance_laser_melee_delay * W_WeaponRateFactor();
+       W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_laser_melee_damage, autocvar_g_balance_laser_melee_range);
+}
+
+void W_Laser_Attack(float issecondary)
+{
+       entity missile;
+       vector s_forward;
+       float a;
+
+       a = autocvar_g_balance_laser_primary_shotangle;
+       s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD);
+
+       //if(nodamage)
+       //      W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0);
+       /*else*/if(issecondary == 1)
+               W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage);
+       else
+               W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage);
+       pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       missile = spawn();
+       missile.owner = missile.realowner = self;
+       missile.classname = "laserbolt";
+       missile.dmg = 0;
+       missile.bot_dodge = TRUE;
+       missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage;
+
+       PROJECTILE_MAKETRIGGER(missile);
+       missile.projectiledeathtype = WEP_LASER;
+
+       setorigin(missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary);
+       missile.angles = vectoangles(missile.velocity);
+       //missile.glow_color = 250; // 244, 250
+       //missile.glow_size = 120;
+       missile.touch = W_Laser_Touch;
+
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH; 
+
+       missile.think = W_Laser_Think;
+       missile.nextthink = time + autocvar_g_balance_laser_primary_delay;
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+
+       if(time >= missile.nextthink)
+       {
+               entity oldself;
+               oldself = self;
+               self = missile;
+               self.think();
+               self = oldself;
+       }
+}
+
+void spawnfunc_weapon_laser(void)
+{
+       weapon_defaultspawnfunc(WEP_LASER);
+}
+
+float W_Laser(float request)
+{
+       switch(request)
+       {
+               case WR_AIM:
+               {
+                       if((autocvar_g_balance_laser_secondary == 2) && (vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_laser_melee_range))
+                               self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
+                       else
+                               self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE);
+                       return TRUE;
+               }
+               
+               case WR_THINK:
+               {
+                       if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload
+                               weapon_action(self.weapon, WR_RELOAD);
+                       else if(self.BUTTON_ATCK)
+                       {
+                               if(weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire))
+                               {
+                                       W_DecreaseAmmo(ammo_none, 1, TRUE);
+
+                                       if not(autocvar_g_balance_laser_primary)
+                                               W_Laser_Shockwave();
+                                       else
+                                               W_Laser_Attack(FALSE);
+
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready);
+                               }
+                       }
+                       else if(self.BUTTON_ATCK2)
+                       {
+                               switch(autocvar_g_balance_laser_secondary)
+                               {
+                                       case 0: // switch to last used weapon
+                                       {
+                                               if(self.switchweapon == WEP_LASER) // don't do this if already switching
+                                                       W_LastWeapon();
+
+                                               break;
+                                       }
+
+                                       case 1: // normal projectile secondary
+                                       {
+                                               if(weapon_prepareattack(1, autocvar_g_balance_laser_secondary_refire))
+                                               {
+                                                       W_DecreaseAmmo(ammo_none, 1, TRUE);
+                                                       W_Laser_Attack(TRUE);
+                                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
+                                               }
+
+                                               break;
+                                       }
+
+                                       case 2: // melee attack secondary
+                                       {
+                                               if(!self.crouch) // we are not currently crouching; this fixes an exploit where your melee anim is not visible, and besides wouldn't make much sense
+                                               if(weapon_prepareattack(1, autocvar_g_balance_laser_melee_refire))
+                                               {
+                                                       // attempt forcing playback of the anim by switching to another anim (that we never play) here...
+                                                       W_Laser_Melee();
+                                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_melee_animtime, w_ready);
+                                               }
+                                       }
+                               }
+                       }
+                       return TRUE;
+               }
+               
+               case WR_PRECACHE: 
+               {
+                       precache_model("models/weapons/g_laser.md3");
+                       precache_model("models/weapons/v_laser.md3");
+                       precache_model("models/weapons/h_laser.iqm");
+                       precache_sound("weapons/lasergun_fire.wav");
+                       return TRUE;
+               }
+               
+               case WR_SETUP:
+               {
+                       weapon_setup(WEP_LASER);
+                       self.current_ammo = ammo_none;
+                       return TRUE;
+               }
+               
+               case WR_CHECKAMMO1:
+               case WR_CHECKAMMO2:
+               {
+                       return TRUE; // laser has infinite ammo
+               }
+               
+               case WR_RELOAD:
+               {
+                       W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav");
+                       return TRUE;
+               }
+               
+               case WR_SUICIDEMESSAGE:
+               {
+                       return WEAPON_LASER_SUICIDE;
+               }
+               
+               case WR_KILLMESSAGE:
+               {
+                       return WEAPON_LASER_MURDER;
+               }
+       }
+       
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float W_Laser(float request)
+{
+       switch(request)
+       {
+               case WR_IMPACTEFFECT:
+               {
+                       vector org2;
+                       org2 = w_org + w_backoff * 6;
+                       pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1);
+                       if(!w_issilent) { sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); }
+                       return TRUE;
+               }
+               
+               case WR_PRECACHE:
+               {
+                       precache_sound("weapons/laserimpact.wav");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_crylink.qc b/qcsrc/common/weapons/w_crylink.qc
new file mode 100644 (file)
index 0000000..7cfc6c3
--- /dev/null
@@ -0,0 +1,736 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ CRYLINK,
+/* function  */ w_crylink,
+/* ammotype  */ IT_CELLS,
+/* impulse   */ 6,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "crylink",
+/* shortname */ "crylink",
+/* fullname  */ _("Crylink")
+);
+#else
+#ifdef SVQC
+.float gravity;
+.float crylink_waitrelease;
+.entity crylink_lastgroup;
+
+.entity queuenext;
+.entity queueprev;
+
+void W_Crylink_CheckLinks(entity e)
+{
+       float i;
+       entity p;
+
+       if(e == world)
+               error("W_Crylink_CheckLinks: entity is world");
+       if(e.classname != "spike" || wasfreed(e))
+               error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e)));
+
+       p = e;
+       for(i = 0; i < 1000; ++i)
+       {
+               if(p.queuenext.queueprev != p || p.queueprev.queuenext != p)
+                       error("W_Crylink_CheckLinks: queue is inconsistent");
+               p = p.queuenext;
+               if(p == e)
+                       break;
+       }
+       if(i >= 1000)
+               error("W_Crylink_CheckLinks: infinite chain");
+}
+
+void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next)
+{
+       W_Crylink_CheckLinks(next);
+       if(me == own.crylink_lastgroup)
+               own.crylink_lastgroup = ((me == next) ? world : next);
+       prev.queuenext = next;
+       next.queueprev = prev;
+       me.classname = "spike_oktoremove";
+       if(me != next)
+               W_Crylink_CheckLinks(next);
+}
+
+void W_Crylink_Dequeue(entity e)
+{
+       W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext);
+}
+
+void W_Crylink_Reset(void)
+{
+       W_Crylink_Dequeue(self);
+       remove(self);
+}
+
+// force projectile to explode
+void W_Crylink_LinkExplode (entity e, entity e2)
+{
+       float a;
+
+       if(e == e2)
+               return;
+
+       a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1);
+
+       if(e == e.realowner.crylink_lastgroup)
+               e.realowner.crylink_lastgroup = world;
+
+       if(e.projectiledeathtype & HITTYPE_SECONDARY)
+               RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_damage * a, autocvar_g_balance_crylink_secondary_edgedamage * a, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * a, e.projectiledeathtype, other);
+       else
+               RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_damage * a, autocvar_g_balance_crylink_primary_edgedamage * a, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * a, e.projectiledeathtype, other);
+
+       W_Crylink_LinkExplode(e.queuenext, e2);
+
+       e.classname = "spike_oktoremove";
+       remove (e);
+}
+
+// adjust towards center
+// returns the origin where they will meet... and the time till the meeting is
+// stored in w_crylink_linkjoin_time.
+// could possibly network this origin and time, and display a special particle
+// effect when projectiles meet there :P
+// jspeed: MINIMUM jing speed
+// jtime: MAXIMUM jing time (0: none)
+float w_crylink_linkjoin_time;
+vector W_Crylink_LinkJoin(entity e, float jspeed, float jtime)
+{
+       vector avg_origin, avg_velocity;
+       vector targ_origin;
+       float avg_dist, n;
+       entity p;
+
+       // FIXME remove this debug code
+       W_Crylink_CheckLinks(e);
+
+       w_crylink_linkjoin_time = 0;
+
+       avg_origin = e.origin;
+       avg_velocity = e.velocity;
+       n = 1;
+       for(p = e; (p = p.queuenext) != e; )
+       {
+               avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin);
+               avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity);
+               ++n;
+       }
+       avg_origin *= (1.0 / n);
+       avg_velocity *= (1.0 / n);
+
+       if(n < 2)
+               return avg_origin; // nothing to do
+
+       // yes, mathematically we can do this in ONE step, but beware of 32bit floats...
+       avg_dist = pow(vlen(e.origin - avg_origin), 2);
+       for(p = e; (p = p.queuenext) != e; )
+               avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2);
+       avg_dist *= (1.0 / n);
+       avg_dist = sqrt(avg_dist);
+
+       if(avg_dist == 0)
+               return avg_origin; // no change needed
+
+       if(jspeed == 0 && jtime == 0)
+       {
+               e.velocity = avg_velocity;
+               UpdateCSQCProjectile(e);
+               for(p = e; (p = p.queuenext) != e; )
+               {
+                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity);
+                       UpdateCSQCProjectile(p);
+               }
+               targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE
+       }
+       else
+       {
+               if(jtime)
+               {
+                       if(jspeed)
+                               w_crylink_linkjoin_time = min(jtime, avg_dist / jspeed);
+                       else
+                               w_crylink_linkjoin_time = jtime;
+               }
+               else
+                       w_crylink_linkjoin_time = avg_dist / jspeed;
+               targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity;
+
+               e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time);
+               UpdateCSQCProjectile(e);
+               for(p = e; (p = p.queuenext) != e; )
+               {
+                       p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time));
+                       UpdateCSQCProjectile(p);
+               }
+
+               // analysis:
+               //   jspeed -> +infinity:
+               //      w_crylink_linkjoin_time -> +0
+               //      targ_origin -> avg_origin
+               //      p->velocity -> HUEG towards center
+               //   jspeed -> 0:
+               //      w_crylink_linkjoin_time -> +/- infinity
+               //      targ_origin -> avg_velocity * +/- infinity
+               //      p->velocity -> avg_velocity
+               //   jspeed -> -infinity:
+               //      w_crylink_linkjoin_time -> -0
+               //      targ_origin -> avg_origin
+               //      p->velocity -> HUEG away from center
+       }
+
+       W_Crylink_CheckLinks(e);
+
+       return targ_origin;
+}
+
+void W_Crylink_LinkJoinEffect_Think()
+{
+       // is there at least 2 projectiles very close?
+       entity e, p;
+       float n;
+       e = self.owner.crylink_lastgroup;
+       n = 0;
+       if(e)
+       {
+               if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime)
+                       ++n;
+               for(p = e; (p = p.queuenext) != e; )
+               {
+                       if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime)
+                               ++n;
+               }
+               if(n >= 2)
+               {
+                       if(e.projectiledeathtype & HITTYPE_SECONDARY)
+                       {
+                               if(autocvar_g_balance_crylink_secondary_joinexplode)
+                               {
+                                       n = n / autocvar_g_balance_crylink_secondary_shots;
+                                       RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_joinexplode_damage * n,
+                                                                       autocvar_g_balance_crylink_secondary_joinexplode_edgedamage * n,
+                                                                       autocvar_g_balance_crylink_secondary_joinexplode_radius * n, e.realowner, world,
+                                                                       autocvar_g_balance_crylink_secondary_joinexplode_force * n, e.projectiledeathtype, other);
+
+                                       pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n);
+                               }
+                       }
+                       else
+                       {
+                               if(autocvar_g_balance_crylink_primary_joinexplode)
+                               {
+                                       n = n / autocvar_g_balance_crylink_primary_shots;
+                                       RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_joinexplode_damage * n,
+                                                                       autocvar_g_balance_crylink_primary_joinexplode_edgedamage * n,
+                                                                       autocvar_g_balance_crylink_primary_joinexplode_radius * n, e.realowner, world,
+                                                                       autocvar_g_balance_crylink_primary_joinexplode_force * n, e.projectiledeathtype, other);
+
+                                       pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n);
+                               }
+                       }
+               }
+       }
+       remove(self);
+}
+
+float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad)
+{
+       entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, FALSE);
+       float hit_friendly = 0;
+       float hit_enemy = 0;
+
+       while(head)
+       {
+               if((head.takedamage != DAMAGE_NO) && (head.deadflag == DEAD_NO))
+               {
+                       if(IsDifferentTeam(head, projectile.realowner))
+                               ++hit_enemy;
+                       else
+                               ++hit_friendly;
+               }
+                       
+               head = head.chain;
+       }
+
+       return (hit_enemy ? FALSE : hit_friendly);
+}
+
+// NO bounce protection, as bounces are limited!
+void W_Crylink_Touch (void)
+{
+       float finalhit;
+       float f;
+       PROJECTILE_TOUCH;
+
+       float a;
+       a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
+
+       finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
+       if(finalhit)
+               f = 1;
+       else
+               f = autocvar_g_balance_crylink_primary_bouncedamagefactor;
+       if(a)
+               f *= a;
+
+       float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_primary_damage * f, autocvar_g_balance_crylink_primary_edgedamage * f, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * f, self.projectiledeathtype, other);
+       
+       if(totaldamage && ((autocvar_g_balance_crylink_primary_linkexplode == 2) || ((autocvar_g_balance_crylink_primary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_primary_radius))))
+       {
+               if(self == self.realowner.crylink_lastgroup)
+                       self.realowner.crylink_lastgroup = world;
+               W_Crylink_LinkExplode(self.queuenext, self);
+               self.classname = "spike_oktoremove";
+               remove (self);
+               return;
+       }
+       else if(finalhit)
+       {
+               // just unlink
+               W_Crylink_Dequeue(self);
+               remove(self);
+               return;
+       }
+       self.cnt = self.cnt - 1;
+       self.angles = vectoangles(self.velocity);
+       self.owner = world;
+       self.projectiledeathtype |= HITTYPE_BOUNCE;
+       // commented out as it causes a little hitch...
+       //if(proj.cnt == 0)
+       //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
+}
+
+void W_Crylink_Touch2 (void)
+{
+       float finalhit;
+       float f;
+       PROJECTILE_TOUCH;
+
+       float a;
+       a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1);
+
+       finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO));
+       if(finalhit)
+               f = 1;
+       else
+               f = autocvar_g_balance_crylink_secondary_bouncedamagefactor;
+       if(a)
+               f *= a;
+
+       float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_secondary_damage * f, autocvar_g_balance_crylink_secondary_edgedamage * f, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * f, self.projectiledeathtype, other);
+               
+       if(totaldamage && ((autocvar_g_balance_crylink_secondary_linkexplode == 2) || ((autocvar_g_balance_crylink_secondary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_secondary_radius))))
+       {
+               if(self == self.realowner.crylink_lastgroup)
+                       self.realowner.crylink_lastgroup = world;
+               W_Crylink_LinkExplode(self.queuenext, self);
+               self.classname = "spike_oktoremove";
+               remove (self);
+               return;
+       }
+       else if(finalhit)
+       {
+               // just unlink
+               W_Crylink_Dequeue(self);
+               remove(self);
+               return;
+       }
+       self.cnt = self.cnt - 1;
+       self.angles = vectoangles(self.velocity);
+       self.owner = world;
+       self.projectiledeathtype |= HITTYPE_BOUNCE;
+       // commented out as it causes a little hitch...
+       //if(proj.cnt == 0)
+       //      CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE);
+}
+
+void W_Crylink_Fadethink (void)
+{
+       W_Crylink_Dequeue(self);
+       remove(self);
+}
+
+void W_Crylink_Attack (void)
+{
+       float counter, shots;
+       entity proj, prevproj, firstproj;
+       vector s;
+       vector forward, right, up;
+       float maxdmg;
+
+       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_reload_ammo);
+
+       maxdmg = autocvar_g_balance_crylink_primary_damage*autocvar_g_balance_crylink_primary_shots;
+       maxdmg *= 1 + autocvar_g_balance_crylink_primary_bouncedamagefactor * autocvar_g_balance_crylink_primary_bounces;
+       if(autocvar_g_balance_crylink_primary_joinexplode)
+               maxdmg += autocvar_g_balance_crylink_primary_joinexplode_damage;
+
+       W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", CH_WEAPON_A, maxdmg);
+       forward = v_forward;
+       right = v_right;
+       up = v_up;
+
+       shots = autocvar_g_balance_crylink_primary_shots;
+       pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
+       proj = prevproj = firstproj = world;
+       for(counter = 0; counter < shots; ++counter)
+       {
+               proj = spawn ();
+               proj.reset = W_Crylink_Reset;
+               proj.realowner = proj.owner = self;
+               proj.classname = "spike";
+               proj.bot_dodge = TRUE;
+               proj.bot_dodgerating = autocvar_g_balance_crylink_primary_damage;
+               if(shots == 1) {
+                       proj.queuenext = proj;
+                       proj.queueprev = proj;
+               }
+               else if(counter == 0) { // first projectile, store in firstproj for now
+                       firstproj = proj;
+               }
+               else if(counter == shots - 1) { // last projectile, link up with first projectile
+                       prevproj.queuenext = proj;
+                       firstproj.queueprev = proj;
+                       proj.queuenext = firstproj;
+                       proj.queueprev = prevproj;
+               }
+               else { // else link up with previous projectile
+                       prevproj.queuenext = proj;
+                       proj.queueprev = prevproj;
+               }
+
+               prevproj = proj;
+
+               proj.movetype = MOVETYPE_BOUNCEMISSILE;
+               PROJECTILE_MAKETRIGGER(proj);
+               proj.projectiledeathtype = WEP_CRYLINK;
+               //proj.gravity = 0.001;
+
+               setorigin (proj, w_shotorg);
+               setsize(proj, '0 0 0', '0 0 0');
+
+
+               s = '0 0 0';
+               if (counter == 0)
+                       s = '0 0 0';
+               else
+               {
+                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
+                       s_y = v_forward_x;
+                       s_z = v_forward_y;
+               }
+               s = s * autocvar_g_balance_crylink_primary_spread * g_weaponspreadfactor;
+               W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_crylink_primary_speed, 0, 0, 0, FALSE);
+               proj.touch = W_Crylink_Touch;
+
+               proj.think = W_Crylink_Fadethink;
+               if(counter == 0)
+               {
+                       proj.fade_time = time + autocvar_g_balance_crylink_primary_middle_lifetime;
+                       proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_middle_fadetime;
+                       proj.nextthink = time + autocvar_g_balance_crylink_primary_middle_lifetime + autocvar_g_balance_crylink_primary_middle_fadetime;
+               }
+               else
+               {
+                       proj.fade_time = time + autocvar_g_balance_crylink_primary_other_lifetime;
+                       proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_other_fadetime;
+                       proj.nextthink = time + autocvar_g_balance_crylink_primary_other_lifetime + autocvar_g_balance_crylink_primary_other_fadetime;
+               }
+               proj.teleport_time = time + autocvar_g_balance_crylink_primary_joindelay;
+               proj.cnt = autocvar_g_balance_crylink_primary_bounces;
+               //proj.scale = 1 + 1 * proj.cnt;
+
+               proj.angles = vectoangles (proj.velocity);
+
+               //proj.glow_size = 20;
+
+               proj.flags = FL_PROJECTILE;
+    proj.missile_flags = MIF_SPLASH;
+    
+               CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
+
+               other = proj; MUTATOR_CALLHOOK(EditProjectile);
+       }
+       if(autocvar_g_balance_crylink_primary_joinspread != 0 || autocvar_g_balance_crylink_primary_jointime != 0)
+       {
+               self.crylink_lastgroup = proj;
+               W_Crylink_CheckLinks(proj);
+               self.crylink_waitrelease = 1;
+       }
+}
+
+void W_Crylink_Attack2 (void)
+{
+       float counter, shots;
+       entity proj, prevproj, firstproj;
+       vector s;
+       vector forward, right, up;
+       float maxdmg;
+
+       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_secondary_ammo, autocvar_g_balance_crylink_reload_ammo);
+
+       maxdmg = autocvar_g_balance_crylink_secondary_damage*autocvar_g_balance_crylink_secondary_shots;
+       maxdmg *= 1 + autocvar_g_balance_crylink_secondary_bouncedamagefactor * autocvar_g_balance_crylink_secondary_bounces;
+       if(autocvar_g_balance_crylink_secondary_joinexplode)
+               maxdmg += autocvar_g_balance_crylink_secondary_joinexplode_damage;
+
+       W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", CH_WEAPON_A, maxdmg);
+       forward = v_forward;
+       right = v_right;
+       up = v_up;
+
+       shots = autocvar_g_balance_crylink_secondary_shots;
+       pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots);
+       proj = prevproj = firstproj = world;
+       for(counter = 0; counter < shots; ++counter)
+       {
+               proj = spawn ();
+               proj.reset = W_Crylink_Reset;
+               proj.realowner = proj.owner = self;
+               proj.classname = "spike";
+               proj.bot_dodge = TRUE;
+               proj.bot_dodgerating = autocvar_g_balance_crylink_secondary_damage;
+               if(shots == 1) {
+                       proj.queuenext = proj;
+                       proj.queueprev = proj;
+               }
+               else if(counter == 0) { // first projectile, store in firstproj for now
+                       firstproj = proj;
+               }
+               else if(counter == shots - 1) { // last projectile, link up with first projectile
+                       prevproj.queuenext = proj;
+                       firstproj.queueprev = proj;
+                       proj.queuenext = firstproj;
+                       proj.queueprev = prevproj;
+               }
+               else { // else link up with previous projectile
+                       prevproj.queuenext = proj;
+                       proj.queueprev = prevproj;
+               }
+
+               prevproj = proj;
+
+               proj.movetype = MOVETYPE_BOUNCEMISSILE;
+               PROJECTILE_MAKETRIGGER(proj);
+               proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY;
+               //proj.gravity = 0.001;
+
+               setorigin (proj, w_shotorg);
+               setsize(proj, '0 0 0', '0 0 0');
+
+               if(autocvar_g_balance_crylink_secondary_spreadtype == 1)
+               {
+                       s = '0 0 0';
+                       if (counter == 0)
+                               s = '0 0 0';
+                       else
+                       {
+                               makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
+                               s_y = v_forward_x;
+                               s_z = v_forward_y;
+                       }
+                       s = s * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor;
+                       s = w_shotdir + right * s_y + up * s_z;
+               }
+               else
+               {
+                       s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor);
+               }
+
+               W_SetupProjectileVelocityEx(proj, s, v_up, autocvar_g_balance_crylink_secondary_speed, 0, 0, 0, FALSE);
+               proj.touch = W_Crylink_Touch2;
+               proj.think = W_Crylink_Fadethink;
+               if(counter == (shots - 1) / 2)
+               {
+                       proj.fade_time = time + autocvar_g_balance_crylink_secondary_middle_lifetime;
+                       proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_middle_fadetime;
+                       proj.nextthink = time + autocvar_g_balance_crylink_secondary_middle_lifetime + autocvar_g_balance_crylink_secondary_middle_fadetime;
+               }
+               else
+               {
+                       proj.fade_time = time + autocvar_g_balance_crylink_secondary_line_lifetime;
+                       proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_line_fadetime;
+                       proj.nextthink = time + autocvar_g_balance_crylink_secondary_line_lifetime + autocvar_g_balance_crylink_secondary_line_fadetime;
+               }
+               proj.teleport_time = time + autocvar_g_balance_crylink_secondary_joindelay;
+               proj.cnt = autocvar_g_balance_crylink_secondary_bounces;
+               //proj.scale = 1 + 1 * proj.cnt;
+
+               proj.angles = vectoangles (proj.velocity);
+
+               //proj.glow_size = 20;
+
+               proj.flags = FL_PROJECTILE;
+        proj.missile_flags = MIF_SPLASH;
+        
+               CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE);
+
+               other = proj; MUTATOR_CALLHOOK(EditProjectile);
+       }
+       if(autocvar_g_balance_crylink_secondary_joinspread != 0 || autocvar_g_balance_crylink_secondary_jointime != 0)
+       {
+               self.crylink_lastgroup = proj;
+               W_Crylink_CheckLinks(proj);
+               self.crylink_waitrelease = 2;
+       }
+}
+
+void spawnfunc_weapon_crylink (void)
+{
+       weapon_defaultspawnfunc(WEP_CRYLINK);
+}
+
+float w_crylink(float req)
+{
+       float ammo_amount;
+       if (req == WR_AIM)
+       {
+               if (random() < 0.10)
+                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_crylink_primary_speed, 0, autocvar_g_balance_crylink_primary_middle_lifetime, FALSE);
+               else
+                       self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_crylink_secondary_speed, 0, autocvar_g_balance_crylink_secondary_middle_lifetime, FALSE);
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+
+               if (self.BUTTON_ATCK)
+               {
+                       if (self.crylink_waitrelease != 1)
+                       if (weapon_prepareattack(0, autocvar_g_balance_crylink_primary_refire))
+                       {
+                               W_Crylink_Attack();
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_crylink_primary_animtime, w_ready);
+                       }
+               }
+
+               if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary)
+               {
+                       if (self.crylink_waitrelease != 2)
+                       if (weapon_prepareattack(1, autocvar_g_balance_crylink_secondary_refire))
+                       {
+                               W_Crylink_Attack2();
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_crylink_secondary_animtime, w_ready);
+                       }
+               }
+
+               if ((self.crylink_waitrelease == 1 && !self.BUTTON_ATCK) || (self.crylink_waitrelease == 2 && !self.BUTTON_ATCK2))
+               {
+                       if (!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time)
+                       {
+                               // fired and released now!
+                               if(self.crylink_lastgroup)
+                               {
+                                       vector pos;
+                                       entity linkjoineffect;
+
+                                       if(self.crylink_waitrelease == 1)
+                                       {
+                                               pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_primary_joinspread * autocvar_g_balance_crylink_primary_speed, autocvar_g_balance_crylink_primary_jointime);
+
+                                       }
+                                       else
+                                       {
+                                               pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_secondary_joinspread * autocvar_g_balance_crylink_secondary_speed, autocvar_g_balance_crylink_secondary_jointime);
+                                       }
+
+                                       linkjoineffect = spawn();
+                                       linkjoineffect.think = W_Crylink_LinkJoinEffect_Think;
+                                       linkjoineffect.classname = "linkjoineffect";
+                                       linkjoineffect.nextthink = time + w_crylink_linkjoin_time;
+                                       linkjoineffect.owner = self;
+                                       setorigin(linkjoineffect, pos);
+                               }
+                               self.crylink_waitrelease = 0;
+                               if(!w_crylink(WR_CHECKAMMO1) && !w_crylink(WR_CHECKAMMO2))
+                               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+                               {
+                                       // ran out of ammo!
+                                       self.cnt = WEP_CRYLINK;
+                                       self.switchweapon = w_getbestweapon(self);
+                               }
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_crylink.md3");
+               precache_model ("models/weapons/v_crylink.md3");
+               precache_model ("models/weapons/h_crylink.iqm");
+               precache_sound ("weapons/crylink_fire.wav");
+               precache_sound ("weapons/crylink_fire2.wav");
+               precache_sound ("weapons/crylink_linkjoin.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_CRYLINK);
+               self.current_ammo = ammo_cells;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               // don't "run out of ammo" and switch weapons while waiting for release
+               if(self.crylink_lastgroup && self.crylink_waitrelease)
+                       return TRUE;
+
+               ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_primary_ammo;
+               ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_primary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               // don't "run out of ammo" and switch weapons while waiting for release
+               if(self.crylink_lastgroup && self.crylink_waitrelease)
+                       return TRUE;
+
+               ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_secondary_ammo;
+               ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_secondary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo), autocvar_g_balance_crylink_reload_ammo, autocvar_g_balance_crylink_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_CRYLINK_SUICIDE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               return WEAPON_CRYLINK_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_crylink(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 2;
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM);
+               }
+               else
+               {
+                       pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM);
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/crylink_impact2.wav");
+               precache_sound("weapons/crylink_impact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_devastator.qc b/qcsrc/common/weapons/w_devastator.qc
new file mode 100644 (file)
index 0000000..504167c
--- /dev/null
@@ -0,0 +1,491 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ ROCKET_LAUNCHER,
+/* function  */ w_rlauncher,
+/* ammotype  */ IT_ROCKETS,
+/* impulse   */ 9,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* model     */ "rl",
+/* shortname */ "rocketlauncher",
+/* fullname  */ _("Rocket Launcher")
+);
+#else
+#ifdef SVQC
+.float rl_release;
+.float rl_detonate_later;
+
+void W_Rocket_Unregister()
+{
+       if(self.realowner && self.realowner.lastrocket == self)
+       {
+               self.realowner.lastrocket = world;
+               // self.realowner.rl_release = 1;
+       }
+}
+
+void W_Rocket_Explode ()
+{
+       W_Rocket_Unregister();
+
+       if(other.takedamage == DAMAGE_AIM)
+               if(IS_PLAYER(other))
+                       if(IsDifferentTeam(self.realowner, other))
+                               if(other.deadflag == DEAD_NO)
+                                       if(IsFlying(other))
+                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other);
+
+       if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
+       {
+               if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
+               {
+                       self.realowner.cnt = WEP_ROCKET_LAUNCHER;
+                       ATTACK_FINISHED(self.realowner) = time;
+                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
+               }
+       }
+       remove (self);
+}
+
+void W_Rocket_DoRemoteExplode ()
+{
+       W_Rocket_Unregister();
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world);
+
+       if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
+       {
+               if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
+               {
+                       self.realowner.cnt = WEP_ROCKET_LAUNCHER;
+                       ATTACK_FINISHED(self.realowner) = time;
+                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
+               }
+       }
+       remove (self);
+}
+
+void W_Rocket_RemoteExplode()
+{
+       if(self.realowner.deadflag == DEAD_NO)
+       if(self.realowner.lastrocket)
+       {
+               if((self.spawnshieldtime >= 0)
+                       ? (time >= self.spawnshieldtime) // timer
+                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device
+               )
+               {
+                       W_Rocket_DoRemoteExplode();
+               }
+       }
+}
+
+vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos)
+{
+       if(thisdir * goaldir > maxturn_cos)
+               return goaldir;
+       if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite
+               return thisdir; // refuse to guide (better than letting a numerical error happen)
+       float f, m2;
+       vector v;
+       // solve:
+       //   g = normalize(thisdir + goaldir * X)
+       //   thisdir * g = maxturn
+       //
+       //   gg = thisdir + goaldir * X
+       //   (thisdir * gg)^2 = maxturn^2 * (gg * gg)
+       //
+       //   (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir)
+       f = thisdir * goaldir;
+       //   (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f)
+       //   0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1)
+       m2 = maxturn_cos * maxturn_cos;
+       v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1);
+       return normalize(thisdir + goaldir * v_y); // the larger solution!
+}
+// assume thisdir == -goaldir:
+//   f == -1
+//   v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1)
+//   (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0
+//   x^2 - 2 * x + 1 = 0
+//   (x - 1)^2 = 0
+//   x = 1
+//   normalize(thisdir + goaldir)
+//   normalize(0)
+
+void W_Rocket_Think (void)
+{
+       vector desireddir, olddir, newdir, desiredorigin, goal;
+#if 0
+       float cosminang, cosmaxang, cosang;
+#endif
+       float velspeed, f;
+       self.nextthink = time;
+       if (time > self.cnt)
+       {
+               other = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               W_Rocket_Explode ();
+               return;
+       }
+
+       // accelerate
+       makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
+       velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward);
+       if (velspeed > 0)
+               self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed);
+
+       // laser guided, or remote detonation
+       if (self.realowner.weapon == WEP_ROCKET_LAUNCHER)
+       {
+               if(self == self.realowner.lastrocket)
+               if not(self.realowner.rl_release)
+               if not(self.BUTTON_ATCK2)
+               if(autocvar_g_balance_rocketlauncher_guiderate)
+               if(time > self.pushltime)
+               if(self.realowner.deadflag == DEAD_NO)
+               {
+                       f = autocvar_g_balance_rocketlauncher_guideratedelay;
+                       if(f)
+                               f = bound(0, (time - self.pushltime) / f, 1);
+                       else
+                               f = 1;
+
+                       velspeed = vlen(self.velocity);
+
+                       makevectors(self.realowner.v_angle);
+                       desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward);
+                       desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs);
+                       olddir = normalize(self.velocity);
+
+                       // now it gets tricky... we want to move like some curve to approximate the target direction
+                       // but we are limiting the rate at which we can turn!
+                       goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir;
+                       newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD));
+
+                       self.velocity = newdir * velspeed;
+                       self.angles = vectoangles(self.velocity);
+
+                       if(!self.count)
+                       {
+                               pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1);
+                               // TODO add a better sound here
+                               sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM);
+                               self.count = 1;
+                       }
+               }
+
+               if(self.rl_detonate_later)
+                       W_Rocket_RemoteExplode();
+       }
+
+       if(self.csqcprojectile_clientanimate == 0)
+               UpdateCSQCProjectile(self);
+}
+
+void W_Rocket_Touch (void)
+{
+       if(WarpZone_Projectile_Touch())
+       {
+               if(wasfreed(self))
+                       W_Rocket_Unregister();
+               return;
+       }
+       W_Rocket_Unregister();
+       W_Rocket_Explode ();
+}
+
+void W_Rocket_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+       
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+               
+       self.health = self.health - damage;
+       self.angles = vectoangles(self.velocity);
+       
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, W_Rocket_Explode);
+}
+
+void W_Rocket_Attack (void)
+{
+       entity missile;
+       entity flash;
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo);
+
+       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_balance_rocketlauncher_damage);
+       pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       missile = WarpZone_RefSys_SpawnSameRefSys(self);
+       missile.owner = missile.realowner = self;
+       self.lastrocket = missile;
+       if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0)
+               missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay;
+       else
+               missile.spawnshieldtime = -1;
+       missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay;
+       missile.classname = "rocket";
+       missile.bot_dodge = TRUE;
+       missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous
+
+       missile.takedamage = DAMAGE_YES;
+       missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale;
+       missile.health = autocvar_g_balance_rocketlauncher_health;
+       missile.event_damage = W_Rocket_Damage;
+       missile.damagedbycontents = TRUE;
+
+       missile.movetype = MOVETYPE_FLY;
+       PROJECTILE_MAKETRIGGER(missile);
+       missile.projectiledeathtype = WEP_ROCKET_LAUNCHER;
+       setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot
+
+       setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point
+       W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0);
+       missile.angles = vectoangles (missile.velocity);
+
+       missile.touch = W_Rocket_Touch;
+       missile.think = W_Rocket_Think;
+       missile.nextthink = time;
+       missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime;
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH; 
+
+       CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound
+
+       // muzzle flash for 1st person view
+       flash = spawn ();
+       setmodel (flash, "models/flash.md3"); // precision set below
+       SUB_SetFade (flash, time, 0.1);
+       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       W_AttachToShotorg(flash, '5 0 0');
+
+       // common properties
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc
+
+float w_rlauncher(float req)
+{
+       entity rock;
+       float rockfound;
+       float ammo_amount;
+
+       if (req == WR_AIM)
+       {
+               // aim and decide to fire if appropriate
+               self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE);
+               if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
+               {
+                       // decide whether to detonate rockets
+                       entity missile, targetlist, targ;
+                       float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
+                       float selfdamage, teamdamage, enemydamage;
+                       edgedamage = autocvar_g_balance_rocketlauncher_edgedamage;
+                       coredamage = autocvar_g_balance_rocketlauncher_damage;
+                       edgeradius = autocvar_g_balance_rocketlauncher_radius;
+                       recipricoledgeradius = 1 / edgeradius;
+                       selfdamage = 0;
+                       teamdamage = 0;
+                       enemydamage = 0;
+                       targetlist = findchainfloat(bot_attack, TRUE);
+                       missile = find(world, classname, "rocket");
+                       while (missile)
+                       {
+                               if (missile.realowner != self)
+                               {
+                                       missile = find(missile, classname, "rocket");
+                                       continue;
+                               }
+                               targ = targetlist;
+                               while (targ)
+                               {
+                                       d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin);
+                                       d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
+                                       // count potential damage according to type of target
+                                       if (targ == self)
+                                               selfdamage = selfdamage + d;
+                                       else if (targ.team == self.team && teamplay)
+                                               teamdamage = teamdamage + d;
+                                       else if (bot_shouldattack(targ))
+                                               enemydamage = enemydamage + d;
+                                       targ = targ.chain;
+                               }
+                               missile = find(missile, classname, "rocket");
+                       }
+                       float desirabledamage;
+                       desirabledamage = enemydamage;
+                       if (time > self.invincible_finished && time > self.spawnshieldtime)
+                               desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
+                       if (teamplay && self.team)
+                               desirabledamage = desirabledamage - teamdamage;
+
+                       missile = find(world, classname, "rocket");
+                       while (missile)
+                       {
+                               if (missile.realowner != self)
+                               {
+                                       missile = find(missile, classname, "rocket");
+                                       continue;
+                               }
+                               makevectors(missile.v_angle);
+                               targ = targetlist;
+                               if (skill > 9) // normal players only do this for the target they are tracking
+                               {
+                                       targ = targetlist;
+                                       while (targ)
+                                       {
+                                               if (
+                                                       (v_forward * normalize(missile.origin - targ.origin)< 0.1)
+                                                       && desirabledamage > 0.1*coredamage
+                                               )self.BUTTON_ATCK2 = TRUE;
+                                               targ = targ.chain;
+                                       }
+                               }else{
+                                       float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
+                                       //As the distance gets larger, a correct detonation gets near imposible
+                                       //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player
+                                       if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1)
+                                               if(IS_PLAYER(self.enemy))
+                                                       if(desirabledamage >= 0.1*coredamage)
+                                                               if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
+                                                                       self.BUTTON_ATCK2 = TRUE;
+                               //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
+                               }
+
+                               missile = find(missile, classname, "rocket");
+                       }
+                       // if we would be doing at X percent of the core damage, detonate it
+                       // but don't fire a new shot at the same time!
+                       if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
+                               self.BUTTON_ATCK2 = TRUE;
+                       if ((skill > 6.5) && (selfdamage > self.health))
+                               self.BUTTON_ATCK2 = FALSE;
+                       //if(self.BUTTON_ATCK2 == TRUE)
+                       //      dprint(ftos(desirabledamage),"\n");
+                       if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else
+               {
+                       if (self.BUTTON_ATCK)
+                       {
+                               if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop)
+                               if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire))
+                               {
+                                       W_Rocket_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready);
+                                       self.rl_release = 0;
+                               }
+                       }
+                       else
+                               self.rl_release = 1;
+
+                       if (self.BUTTON_ATCK2)
+                       {
+                               rockfound = 0;
+                               for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self)
+                               {
+                                       if(!rock.rl_detonate_later)
+                                       {
+                                               rock.rl_detonate_later = TRUE;
+                                               rockfound = 1;
+                                       }
+                               }
+                               if(rockfound)
+                                       sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/flash.md3");
+               precache_model ("models/weapons/g_rl.md3");
+               precache_model ("models/weapons/v_rl.md3");
+               precache_model ("models/weapons/h_rl.iqm");
+               precache_sound ("weapons/rocket_det.wav");
+               precache_sound ("weapons/rocket_fire.wav");
+               precache_sound ("weapons/rocket_mode.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_ROCKET_LAUNCHER);
+               self.current_ammo = ammo_rockets;
+               self.rl_release = 1;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               // don't switch while guiding a missile
+               if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER)
+               {
+                       ammo_amount = FALSE;
+                       if(autocvar_g_balance_rocketlauncher_reload_ammo)
+                       {
+                               if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo && self.(weapon_load[WEP_ROCKET_LAUNCHER]) < autocvar_g_balance_rocketlauncher_ammo)
+                                       ammo_amount = TRUE;
+                       }
+                       else if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo)
+                               ammo_amount = TRUE;
+                       return !ammo_amount;
+               }
+       }
+       else if (req == WR_CHECKAMMO2)
+               return FALSE;
+       else if (req == WR_RESETPLAYER)
+       {
+               self.rl_release = 0;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo, autocvar_g_balance_rocketlauncher_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_ROCKETLAUNCHER_SUICIDE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
+                       return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH;
+               else
+                       return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_rlauncher(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 12;
+               pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
+               if(!w_issilent)
+                       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/rocket_impact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_electro.qc b/qcsrc/common/weapons/w_electro.qc
new file mode 100644 (file)
index 0000000..bfd9ebe
--- /dev/null
@@ -0,0 +1,619 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ ELECTRO,
+/* function  */ w_electro,
+/* ammotype  */ IT_CELLS,
+/* impulse   */ 5,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "electro",
+/* shortname */ "electro",
+/* fullname  */ _("Electro")
+);
+#else
+#ifdef SVQC
+.float electro_count;
+.float electro_secondarytime;
+
+void W_Plasma_Explode_Combo (void);
+
+void W_Plasma_TriggerCombo(vector org, float rad, entity own)
+{
+       entity e;
+       e = WarpZone_FindRadius(org, rad, TRUE);
+       while (e)
+       {
+               if (e.classname == "plasma")
+               {
+                       // change owner to whoever caused the combo explosion
+                       e.realowner = own;
+                       e.takedamage = DAMAGE_NO;
+                       e.classname = "plasma_chain";
+                       e.think = W_Plasma_Explode_Combo;
+                       e.nextthink = time + vlen(e.WarpZone_findradius_dist) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
+               }
+               e = e.chain;
+       }
+}
+
+void W_Plasma_Explode (void)
+{
+       if(other.takedamage == DAMAGE_AIM)
+               if(IS_PLAYER(other))
+                       if(IsDifferentTeam(self.realowner, other))
+                               if(other.deadflag == DEAD_NO)
+                                       if(IsFlying(other))
+                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+       if (self.movetype == MOVETYPE_BOUNCE)
+       {
+               RadiusDamage (self, self.realowner, autocvar_g_balance_electro_secondary_damage, autocvar_g_balance_electro_secondary_edgedamage, autocvar_g_balance_electro_secondary_radius, world, world, autocvar_g_balance_electro_secondary_force, self.projectiledeathtype, other);
+       }
+       else
+       {
+               W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_primary_comboradius, self.realowner);
+               RadiusDamage (self, self.realowner, autocvar_g_balance_electro_primary_damage, autocvar_g_balance_electro_primary_edgedamage, autocvar_g_balance_electro_primary_radius, world, world, autocvar_g_balance_electro_primary_force, self.projectiledeathtype, other);
+       }
+
+       remove (self);
+}
+
+void W_Plasma_Explode_Combo (void)
+{
+       W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_combo_comboradius, self.realowner);
+
+       self.event_damage = func_null;
+       RadiusDamage (self, self.realowner, autocvar_g_balance_electro_combo_damage, autocvar_g_balance_electro_combo_edgedamage, autocvar_g_balance_electro_combo_radius, world, world, autocvar_g_balance_electro_combo_force, WEP_ELECTRO | HITTYPE_BOUNCE, world); // use THIS type for a combo because primary can't bounce
+
+       remove (self);
+}
+
+void W_Plasma_Touch (void)
+{
+       //self.velocity = self.velocity  * 0.1;
+
+       PROJECTILE_TOUCH;
+       if (other.takedamage == DAMAGE_AIM) {
+               W_Plasma_Explode ();
+       } else {
+               //UpdateCSQCProjectile(self);
+               spamsound (self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTN_NORM);
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+       }
+}
+
+void W_Plasma_TouchExplode (void)
+{
+       PROJECTILE_TOUCH;
+       W_Plasma_Explode ();
+}
+
+void W_Plasma_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+
+       // note: combos are usually triggered by W_Plasma_TriggerCombo, not damage
+       float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim");
+       
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1)))
+               return; // g_projectiles_damage says to halt    
+       
+       self.health = self.health - damage;
+       if (self.health <= 0)
+       {
+               self.takedamage = DAMAGE_NO;
+               self.nextthink = time;
+               if (is_combo)
+               {
+                       // change owner to whoever caused the combo explosion
+                       self.realowner = inflictor.realowner;
+                       self.classname = "plasma_chain";
+                       self.think = W_Plasma_Explode_Combo;
+                       self.nextthink = time + min(autocvar_g_balance_electro_combo_radius, vlen(self.origin - inflictor.origin)) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler
+                               //                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bounding the length, because inflictor may be in a galaxy far far away (warpzones)
+               }
+               else
+               {
+                       self.use = W_Plasma_Explode;
+                       self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately"
+               }
+       }
+}
+
+void W_Electro_Attack()
+{
+       entity proj;
+
+       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_reload_ammo);
+
+       W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, "weapons/electro_fire.wav", CH_WEAPON_A, autocvar_g_balance_electro_primary_damage);
+
+       pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       proj = spawn ();
+       proj.classname = "plasma_prim";
+       proj.owner = proj.realowner = self;
+       proj.bot_dodge = TRUE;
+       proj.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
+       proj.use = W_Plasma_Explode;
+       proj.think = adaptor_think2use_hittype_splash;
+       proj.nextthink = time + autocvar_g_balance_electro_primary_lifetime;
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = WEP_ELECTRO;
+       setorigin(proj, w_shotorg);
+
+       proj.movetype = MOVETYPE_FLY;
+       W_SETUPPROJECTILEVELOCITY(proj, g_balance_electro_primary);
+       proj.angles = vectoangles(proj.velocity);
+       proj.touch = W_Plasma_TouchExplode;
+       setsize(proj, '0 0 -3', '0 0 -3');
+       proj.flags = FL_PROJECTILE;
+       proj.missile_flags = MIF_SPLASH;
+
+       CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO_BEAM, TRUE);
+
+       other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_Electro_Attack2()
+{
+       entity proj;
+
+       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_secondary_ammo, autocvar_g_balance_electro_reload_ammo);
+
+       W_SetupShot_ProjectileSize (self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, autocvar_g_balance_electro_secondary_damage);
+
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       proj = spawn ();
+       proj.classname = "plasma";
+       proj.owner = proj.realowner = self;
+       proj.use = W_Plasma_Explode;
+       proj.think = adaptor_think2use_hittype_splash;
+       proj.bot_dodge = TRUE;
+       proj.bot_dodgerating = autocvar_g_balance_electro_secondary_damage;
+       proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime;
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY;
+       setorigin(proj, w_shotorg);
+
+       //proj.glow_size = 50;
+       //proj.glow_color = 45;
+       proj.movetype = MOVETYPE_BOUNCE;
+       W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary);
+       proj.touch = W_Plasma_Touch;
+       setsize(proj, '0 0 -4', '0 0 -4');
+       proj.takedamage = DAMAGE_YES;
+       proj.damageforcescale = autocvar_g_balance_electro_secondary_damageforcescale;
+       proj.health = autocvar_g_balance_electro_secondary_health;
+       proj.event_damage = W_Plasma_Damage;
+       proj.flags = FL_PROJECTILE;
+       proj.damagedbycontents = (autocvar_g_balance_electro_secondary_damagedbycontents);
+
+       proj.bouncefactor = autocvar_g_balance_electro_secondary_bouncefactor;
+       proj.bouncestop = autocvar_g_balance_electro_secondary_bouncestop;
+       proj.missile_flags = MIF_SPLASH | MIF_ARC;
+
+#if 0
+       entity p2;
+       p2 = spawn();
+       copyentity(proj, p2);
+       setmodel(p2, "models/ebomb.mdl");
+       setsize(p2, proj.mins, proj.maxs);
+#endif
+
+       CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound
+
+       other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+.vector hook_start, hook_end;
+float lgbeam_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_ELECTRO_BEAM);
+       sf = sf & 0x7F;
+       if(sound_allowed(MSG_BROADCAST, self.realowner))
+               sf |= 0x80;
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 1)
+       {
+               WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
+               WriteCoord(MSG_ENTITY, autocvar_g_balance_electro_primary_range);
+       }
+       if(sf & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_start_x);
+               WriteCoord(MSG_ENTITY, self.hook_start_y);
+               WriteCoord(MSG_ENTITY, self.hook_start_z);
+       }
+       if(sf & 4)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_end_x);
+               WriteCoord(MSG_ENTITY, self.hook_end_y);
+               WriteCoord(MSG_ENTITY, self.hook_end_z);
+       }
+       return TRUE;
+}
+.entity lgbeam;
+.float prevlgfire;
+float lgbeam_checkammo()
+{
+       if(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)
+               return TRUE;
+       else if(autocvar_g_balance_electro_reload_ammo)
+               return self.realowner.clip_load > 0;
+       else
+               return self.realowner.ammo_cells > 0;
+}
+
+entity lgbeam_owner_ent;
+void lgbeam_think()
+{
+       entity owner_player;
+       owner_player = self.realowner;
+
+       owner_player.prevlgfire = time;
+       if (self != owner_player.lgbeam)
+       {
+               remove(self);
+               return;
+       }
+
+       if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen)
+       {
+               if(self == owner_player.lgbeam)
+                       owner_player.lgbeam = world;
+               remove(self);
+               return;
+       }
+
+       self.nextthink = time;
+
+       makevectors(owner_player.v_angle);
+
+       float dt, f;
+       dt = frametime;
+
+       // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
+       if not(owner_player.items & IT_UNLIMITED_WEAPON_AMMO)
+       {
+               if(autocvar_g_balance_electro_primary_ammo)
+               {
+                       if(autocvar_g_balance_electro_reload_ammo)
+                       {
+                               dt = min(dt, owner_player.clip_load / autocvar_g_balance_electro_primary_ammo);
+                               owner_player.clip_load = max(0, owner_player.clip_load - autocvar_g_balance_electro_primary_ammo * frametime);
+                               owner_player.(weapon_load[WEP_ELECTRO]) = owner_player.clip_load;
+                       }
+                       else
+                       {
+                               dt = min(dt, owner_player.ammo_cells / autocvar_g_balance_electro_primary_ammo);
+                               owner_player.ammo_cells = max(0, owner_player.ammo_cells - autocvar_g_balance_electro_primary_ammo * frametime);
+                       }
+               }
+       }
+
+       W_SetupShot_Range(owner_player, TRUE, 0, "", 0, autocvar_g_balance_electro_primary_damage * dt, autocvar_g_balance_electro_primary_range);
+       if(!lgbeam_owner_ent)
+       {
+               lgbeam_owner_ent = spawn();
+               lgbeam_owner_ent.classname = "lgbeam_owner_ent";
+       }
+       WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(owner_player));
+
+       // apply the damage
+       if(trace_ent)
+       {
+               vector force;
+               force = w_shotdir * autocvar_g_balance_electro_primary_force + '0 0 1' * autocvar_g_balance_electro_primary_force_up;
+
+               f = ExponentialFalloff(autocvar_g_balance_electro_primary_falloff_mindist, autocvar_g_balance_electro_primary_falloff_maxdist, autocvar_g_balance_electro_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg));
+
+               if(accuracy_isgooddamage(owner_player, trace_ent))
+                       accuracy_add(owner_player, WEP_ELECTRO, 0, autocvar_g_balance_electro_primary_damage * dt * f);
+               Damage (trace_ent, owner_player, owner_player, autocvar_g_balance_electro_primary_damage * dt * f, WEP_ELECTRO, trace_endpos, force * dt);
+       }
+       W_Plasma_TriggerCombo(trace_endpos, autocvar_g_balance_electro_primary_comboradius, owner_player);
+
+       // draw effect
+       if(w_shotorg != self.hook_start)
+       {
+               self.SendFlags |= 2;
+               self.hook_start = w_shotorg;
+       }
+       if(w_shotend != self.hook_end)
+       {
+               self.SendFlags |= 4;
+               self.hook_end = w_shotend;
+       }
+}
+
+// experimental lightning gun
+void W_Electro_Attack3 (void)
+{
+       // only play fire sound if 0.5 sec has passed since player let go the fire button
+       if(time - self.prevlgfire > 0.5)
+               sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
+
+       entity beam, oldself;
+
+       self.lgbeam = beam = spawn();
+       beam.classname = "lgbeam";
+       beam.solid = SOLID_NOT;
+       beam.think = lgbeam_think;
+       beam.owner = beam.realowner = self;
+       beam.movetype = MOVETYPE_NONE;
+       beam.shot_spread = 0;
+       beam.bot_dodge = TRUE;
+       beam.bot_dodgerating = autocvar_g_balance_electro_primary_damage;
+       Net_LinkEntity(beam, FALSE, 0, lgbeam_send);
+
+       oldself = self;
+       self = beam;
+       self.think();
+       self = oldself;
+}
+
+void ElectroInit()
+{
+       weapon_action(WEP_ELECTRO, WR_PRECACHE);
+       electro_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 1);
+       electro_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 2);
+       electro_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 3);
+       electro_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 4);
+}
+
+void spawnfunc_weapon_electro (void)
+{
+       weapon_defaultspawnfunc(WEP_ELECTRO);
+}
+
+void w_electro_checkattack()
+{
+       if(self.electro_count > 1)
+       if(self.BUTTON_ATCK2)
+       if(weapon_prepareattack(1, -1))
+       {
+               W_Electro_Attack2();
+               self.electro_count -= 1;
+               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
+               return;
+       }
+
+       w_ready();
+}
+
+.float bot_secondary_electromooth;
+.float BUTTON_ATCK_prev;
+float w_electro(float req)
+{
+       float ammo_amount;
+       if (req == WR_AIM)
+       {
+               self.BUTTON_ATCK=FALSE;
+               self.BUTTON_ATCK2=FALSE;
+               if(vlen(self.origin-self.enemy.origin) > 1000)
+                       self.bot_secondary_electromooth = 0;
+               if(self.bot_secondary_electromooth == 0)
+               {
+                       float shoot;
+
+                       if(autocvar_g_balance_electro_primary_speed)
+                               shoot = bot_aim(autocvar_g_balance_electro_primary_speed, 0, autocvar_g_balance_electro_primary_lifetime, FALSE);
+                       else
+                               shoot = bot_aim(1000000, 0, 0.001, FALSE);
+
+                       if(shoot)
+                       {
+                               self.BUTTON_ATCK = TRUE;
+                               if(random() < 0.01) self.bot_secondary_electromooth = 1;
+                       }
+               }
+               else
+               {
+                       if(bot_aim(autocvar_g_balance_electro_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_electro_secondary_lifetime, TRUE))
+                       {
+                               self.BUTTON_ATCK2 = TRUE;
+                               if(random() < 0.03) self.bot_secondary_electromooth = 0;
+                       }
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_electro_reload_ammo) // forced reload
+               {
+                       ammo_amount = 0;
+                       if(autocvar_g_balance_electro_lightning)
+                       {
+                               if(self.clip_load > 0)
+                                       ammo_amount = 1;
+                       }
+                       else if(self.clip_load >= autocvar_g_balance_electro_primary_ammo)
+                               ammo_amount = 1;
+                       if(self.clip_load >= autocvar_g_balance_electro_secondary_ammo)
+                               ammo_amount += 1;
+
+                       if(!ammo_amount)
+                       {
+                               weapon_action(self.weapon, WR_RELOAD);
+                               return FALSE;
+                       }
+               }
+               if (self.BUTTON_ATCK)
+               {
+                       if(autocvar_g_balance_electro_lightning)
+                               if(self.BUTTON_ATCK_prev)
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+
+                       if (weapon_prepareattack(0, (autocvar_g_balance_electro_lightning ? 0 : autocvar_g_balance_electro_primary_refire)))
+                       {
+                               if(autocvar_g_balance_electro_lightning)
+                               {
+                                       if ((!self.lgbeam) || wasfreed(self.lgbeam))
+                                       {
+                                               W_Electro_Attack3();
+                                       }
+                                       if(!self.BUTTON_ATCK_prev)
+                                       {
+                                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+                                               self.BUTTON_ATCK_prev = 1;
+                                       }
+                               }
+                               else
+                               {
+                                       W_Electro_Attack();
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+                               }
+                       }
+               } else {
+                       if(autocvar_g_balance_electro_lightning)
+                       {
+                               if (self.BUTTON_ATCK_prev != 0)
+                               {
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready);
+                                       ATTACK_FINISHED(self) = time + autocvar_g_balance_electro_primary_refire * W_WeaponRateFactor();
+                               }
+                               self.BUTTON_ATCK_prev = 0;
+                       }
+
+                       if (self.BUTTON_ATCK2)
+                       {
+                               if (time >= self.electro_secondarytime)
+                               if (weapon_prepareattack(1, autocvar_g_balance_electro_secondary_refire))
+                               {
+                                       W_Electro_Attack2();
+                                       self.electro_count = autocvar_g_balance_electro_secondary_count;
+                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack);
+                                       self.electro_secondarytime = time + autocvar_g_balance_electro_secondary_refire2 * W_WeaponRateFactor();
+                               }
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_electro.md3");
+               precache_model ("models/weapons/v_electro.md3");
+               precache_model ("models/weapons/h_electro.iqm");
+               precache_sound ("weapons/electro_bounce.wav");
+               precache_sound ("weapons/electro_fire.wav");
+               precache_sound ("weapons/electro_fire2.wav");
+               precache_sound ("weapons/electro_impact.wav");
+               precache_sound ("weapons/electro_impact_combo.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+               if(autocvar_g_balance_electro_lightning)
+               {
+                       precache_sound ("weapons/lgbeam_fire.wav");
+               }
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_ELECTRO);
+               self.current_ammo = ammo_cells;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               if(autocvar_g_balance_electro_lightning)
+               {
+                       if(!autocvar_g_balance_electro_primary_ammo)
+                               ammo_amount = 1;
+                       else
+                               ammo_amount = self.ammo_cells > 0;
+                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) > 0;
+               }
+               else
+               {
+                       ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_primary_ammo;
+                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_primary_ammo;
+               }
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               if(autocvar_g_balance_electro_combo_safeammocheck) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false.
+               {
+                       ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
+                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo;
+               }
+               else
+               {
+                       ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo;
+                       ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo;
+               }
+               return ammo_amount;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.electro_secondarytime = time;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_secondary_ammo), autocvar_g_balance_electro_reload_ammo, autocvar_g_balance_electro_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_ELECTRO_SUICIDE_ORBS;
+               else
+                       return WEAPON_ELECTRO_SUICIDE_BOLT;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       return WEAPON_ELECTRO_MURDER_ORBS;
+               }
+               else
+               {
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                               return WEAPON_ELECTRO_MURDER_COMBO;
+                       else
+                               return WEAPON_ELECTRO_MURDER_BOLT;
+               }
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_electro(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 6;
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       pointparticles(particleeffectnum("electro_ballexplode"), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM);
+               }
+               else
+               {
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                       {
+                               // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls
+                               pointparticles(particleeffectnum("electro_combo"), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                                       sound(self, CH_SHOTS, "weapons/electro_impact_combo.wav", VOL_BASE, ATTN_NORM);
+                       }
+                       else
+                       {
+                               pointparticles(particleeffectnum("electro_impact"), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                                       sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM);
+                       }
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/electro_impact.wav");
+               precache_sound("weapons/electro_impact_combo.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_electro.qh b/qcsrc/common/weapons/w_electro.qh
new file mode 100644 (file)
index 0000000..98c0be1
--- /dev/null
@@ -0,0 +1,2 @@
+void ElectroInit();
+vector electro_shotorigin[4];
diff --git a/qcsrc/common/weapons/w_fireball.qc b/qcsrc/common/weapons/w_fireball.qc
new file mode 100644 (file)
index 0000000..3d84e8e
--- /dev/null
@@ -0,0 +1,434 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ FIREBALL,
+/* function  */ w_fireball,
+/* ammotype  */ 0,
+/* impulse   */ 9,
+/* flags     */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "fireball",
+/* shortname */ "fireball",
+/* fullname  */ _("Fireball")
+);
+#else
+#ifdef SVQC
+.float bot_primary_fireballmooth; // whatever a mooth is
+.vector fireball_impactvec;
+.float fireball_primarytime;
+
+void W_Fireball_Explode (void)
+{
+       entity e;
+       float dist;
+       float points;
+       vector dir;
+       float d;
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       // 1. dist damage
+       d = (self.realowner.health + self.realowner.armorvalue);
+       RadiusDamage (self, self.realowner, autocvar_g_balance_fireball_primary_damage, autocvar_g_balance_fireball_primary_edgedamage, autocvar_g_balance_fireball_primary_radius, world, world, autocvar_g_balance_fireball_primary_force, self.projectiledeathtype, other);
+       if(self.realowner.health + self.realowner.armorvalue >= d)
+       if(!self.cnt)
+       {
+               modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, autocvar_g_balance_fireball_primary_bfgradius, 0.2, 0.05, 0.25);
+
+               // 2. bfg effect
+               // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here.
+               for(e = findradius(self.origin, autocvar_g_balance_fireball_primary_bfgradius); e; e = e.chain)
+               if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self))
+               {
+                       // can we see fireball?
+                       traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e);
+                       if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway
+                               continue;
+                       // can we see player who shot fireball?
+                       traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e);
+                       if(trace_ent != self.realowner)
+                       if(/* trace_startsolid || */ trace_fraction != 1)
+                               continue;
+                       dist = vlen(self.origin - e.origin - e.view_ofs);
+                       points = (1 - sqrt(dist / autocvar_g_balance_fireball_primary_bfgradius));
+                       if(points <= 0)
+                               continue;
+                       dir = normalize(e.origin + e.view_ofs - self.origin);
+
+                       if(accuracy_isgooddamage(self.realowner, e))
+                               accuracy_add(self.realowner, WEP_FIREBALL, 0, autocvar_g_balance_fireball_primary_bfgdamage * points);
+
+                       Damage(e, self, self.realowner, autocvar_g_balance_fireball_primary_bfgdamage * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, autocvar_g_balance_fireball_primary_bfgforce * dir);
+                       pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1);
+               }
+       }
+
+       remove (self);
+}
+
+void W_Fireball_TouchExplode (void)
+{
+       PROJECTILE_TOUCH;
+       W_Fireball_Explode ();
+}
+
+void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime)
+{
+       entity e;
+       float d;
+       vector p;
+
+       if(damage <= 0)
+               return;
+
+       RandomSelection_Init();
+       for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain)
+       if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self))
+       {
+               p = e.origin;
+               p_x += e.mins_x + random() * (e.maxs_x - e.mins_x);
+               p_y += e.mins_y + random() * (e.maxs_y - e.mins_y);
+               p_z += e.mins_z + random() * (e.maxs_z - e.mins_z);
+               d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
+               if(d < dist)
+               {
+                       e.fireball_impactvec = p;
+                       RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
+               }
+       }
+       if(RandomSelection_chosen_ent)
+       {
+               d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
+               d = damage + (edgedamage - damage) * (d / dist);
+               Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
+               //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
+               pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
+       }
+}
+
+void W_Fireball_Think()
+{
+       if(time > self.pushltime)
+       {
+               self.cnt = 1;
+               self.projectiledeathtype |= HITTYPE_SPLASH;
+               W_Fireball_Explode();
+               return;
+       }
+
+       W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_primary_laserradius, autocvar_g_balance_fireball_primary_laserdamage, autocvar_g_balance_fireball_primary_laseredgedamage, autocvar_g_balance_fireball_primary_laserburntime);
+
+       self.nextthink = time + 0.1;
+}
+
+void W_Fireball_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if(self.health <= 0)
+               return;
+               
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+               
+       self.health = self.health - damage;
+       if (self.health <= 0)
+       {
+               self.cnt = 1;
+               W_PrepareExplosionByDamage(attacker, W_Fireball_Explode);
+       }
+}
+
+void W_Fireball_Attack1()
+{
+       entity proj;
+
+       W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 2, "weapons/fireball_fire2.wav", CH_WEAPON_A, autocvar_g_balance_fireball_primary_damage + autocvar_g_balance_fireball_primary_bfgdamage);
+
+       pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       proj = spawn ();
+       proj.classname = "plasma_prim";
+       proj.owner = proj.realowner = self;
+       proj.bot_dodge = TRUE;
+       proj.bot_dodgerating = autocvar_g_balance_fireball_primary_damage;
+       proj.pushltime = time + autocvar_g_balance_fireball_primary_lifetime;
+       proj.use = W_Fireball_Explode;
+       proj.think = W_Fireball_Think;
+       proj.nextthink = time;
+       proj.health = autocvar_g_balance_fireball_primary_health;
+       proj.team = self.team;
+       proj.event_damage = W_Fireball_Damage;
+       proj.takedamage = DAMAGE_YES;
+       proj.damageforcescale = autocvar_g_balance_fireball_primary_damageforcescale;
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = WEP_FIREBALL;
+       setorigin(proj, w_shotorg);
+
+       proj.movetype = MOVETYPE_FLY;
+       W_SETUPPROJECTILEVELOCITY(proj, g_balance_fireball_primary);
+       proj.angles = vectoangles(proj.velocity);
+       proj.touch = W_Fireball_TouchExplode;
+       setsize(proj, '-16 -16 -16', '16 16 16');
+       proj.flags = FL_PROJECTILE;
+    proj.missile_flags = MIF_SPLASH | MIF_PROXY;
+    
+       CSQCProjectile(proj, TRUE, PROJECTILE_FIREBALL, TRUE);
+
+       other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_Fireball_AttackEffect(float i, vector f_diff)
+{
+       W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 0, "", 0, 0);
+       w_shotorg += f_diff_x * v_up + f_diff_y * v_right;
+       pointparticles(particleeffectnum("fireball_preattack_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+}
+
+void W_Fireball_Attack1_Frame4()
+{
+       W_Fireball_Attack1();
+       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, w_ready);
+}
+
+void W_Fireball_Attack1_Frame3()
+{
+       W_Fireball_AttackEffect(0, '+1.25 +3.75 0');
+       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame4);
+}
+
+void W_Fireball_Attack1_Frame2()
+{
+       W_Fireball_AttackEffect(0, '-1.25 +3.75 0');
+       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame3);
+}
+
+void W_Fireball_Attack1_Frame1()
+{
+       W_Fireball_AttackEffect(1, '+1.25 -3.75 0');
+       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame2);
+}
+
+void W_Fireball_Attack1_Frame0()
+{
+       W_Fireball_AttackEffect(0, '-1.25 -3.75 0');
+       sound (self, CH_WEAPON_SINGLE, "weapons/fireball_prefire2.wav", VOL_BASE, ATTN_NORM);
+       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame1);
+}
+
+void W_Firemine_Think()
+{
+       if(time > self.pushltime)
+       {
+               remove(self);
+               return;
+       }
+
+       // make it "hot" once it leaves its owner
+       if(self.owner)
+       {
+               if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > autocvar_g_balance_fireball_secondary_laserradius)
+               {
+                       self.cnt += 1;
+                       if(self.cnt == 3)
+                               self.owner = world;
+               }
+               else
+                       self.cnt = 0;
+       }
+
+       W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_secondary_laserradius, autocvar_g_balance_fireball_secondary_laserdamage, autocvar_g_balance_fireball_secondary_laseredgedamage, autocvar_g_balance_fireball_secondary_laserburntime);
+
+       self.nextthink = time + 0.1;
+}
+
+void W_Firemine_Touch (void)
+{
+       PROJECTILE_TOUCH;
+       if (other.takedamage == DAMAGE_AIM)
+       if(Fire_AddDamage(other, self.realowner, autocvar_g_balance_fireball_secondary_damage, autocvar_g_balance_fireball_secondary_damagetime, self.projectiledeathtype) >= 0)
+       {
+               remove(self);
+               return;
+       }
+       self.projectiledeathtype |= HITTYPE_BOUNCE;
+}
+
+void W_Fireball_Attack2()
+{
+       entity proj;
+       vector f_diff;
+       float c;
+
+       c = mod(self.bulletcounter, 4);
+       switch(c)
+       {
+               case 0:
+                       f_diff = '-1.25 -3.75 0';
+                       break;
+               case 1:
+                       f_diff = '+1.25 -3.75 0';
+                       break;
+               case 2:
+                       f_diff = '-1.25 +3.75 0';
+                       break;
+               case 3:
+               default:
+                       f_diff = '+1.25 +3.75 0';
+                       break;
+       }
+       W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, "weapons/fireball_fire.wav", CH_WEAPON_A, autocvar_g_balance_fireball_secondary_damage);
+       traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self);
+       w_shotorg = trace_endpos;
+
+       pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       proj = spawn ();
+       proj.owner = proj.realowner = self;
+       proj.classname = "grenade";
+       proj.bot_dodge = TRUE;
+       proj.bot_dodgerating = autocvar_g_balance_fireball_secondary_damage;
+       proj.movetype = MOVETYPE_BOUNCE;
+       proj.projectiledeathtype = WEP_FIREBALL | HITTYPE_SECONDARY;
+       proj.touch = W_Firemine_Touch;
+       PROJECTILE_MAKETRIGGER(proj);
+       setsize(proj, '-4 -4 -4', '4 4 4');
+       setorigin(proj, w_shotorg);
+       proj.think = W_Firemine_Think;
+       proj.nextthink = time;
+       proj.damageforcescale = autocvar_g_balance_fireball_secondary_damageforcescale;
+       proj.pushltime = time + autocvar_g_balance_fireball_secondary_lifetime;
+       W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_fireball_secondary);
+
+       proj.angles = vectoangles(proj.velocity);
+       proj.flags = FL_PROJECTILE;
+    proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
+    
+       CSQCProjectile(proj, TRUE, PROJECTILE_FIREMINE, TRUE);
+
+       other = proj; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void spawnfunc_weapon_fireball (void)
+{
+       weapon_defaultspawnfunc(WEP_FIREBALL);
+}
+
+float w_fireball(float req)
+{
+       //float ammo_amount;
+       if (req == WR_AIM)
+       {
+               self.BUTTON_ATCK = FALSE;
+               self.BUTTON_ATCK2 = FALSE;
+               if (self.bot_primary_fireballmooth == 0)
+               {
+                       if(bot_aim(autocvar_g_balance_fireball_primary_speed, 0, autocvar_g_balance_fireball_primary_lifetime, FALSE))
+                       {
+                               self.BUTTON_ATCK = TRUE;
+                               if(random() < 0.02) self.bot_primary_fireballmooth = 0;
+                       }
+               }
+               else
+               {
+                       if(bot_aim(autocvar_g_balance_fireball_secondary_speed, autocvar_g_balance_fireball_secondary_speed_up, autocvar_g_balance_fireball_secondary_lifetime, TRUE))
+                       {
+                               self.BUTTON_ATCK2 = TRUE;
+                               if(random() < 0.01) self.bot_primary_fireballmooth = 1;
+                       }
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if (self.BUTTON_ATCK)
+               {
+                       if (time >= self.fireball_primarytime)
+                       if (weapon_prepareattack(0, autocvar_g_balance_fireball_primary_refire))
+                       {
+                               W_Fireball_Attack1_Frame0();
+                               self.fireball_primarytime = time + autocvar_g_balance_fireball_primary_refire2 * W_WeaponRateFactor();
+                       }
+               }
+               else if (self.BUTTON_ATCK2)
+               {
+                       if (weapon_prepareattack(1, autocvar_g_balance_fireball_secondary_refire))
+                       {
+                               W_Fireball_Attack2();
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_fireball_secondary_animtime, w_ready);
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_fireball.md3");
+               precache_model ("models/weapons/v_fireball.md3");
+               precache_model ("models/weapons/h_fireball.iqm");
+               precache_model ("models/sphere/sphere.md3");
+               precache_sound ("weapons/fireball_fire.wav");
+               precache_sound ("weapons/fireball_fire2.wav");
+               precache_sound ("weapons/fireball_prefire2.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_FIREBALL);
+               self.current_ammo = ammo_none;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               return 1;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               return 1;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.fireball_primarytime = time;
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_FIREBALL_SUICIDE_FIREMINE;
+               else
+                       return WEAPON_FIREBALL_SUICIDE_BLAST;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       return WEAPON_FIREBALL_MURDER_FIREMINE;
+               }
+               else
+               {
+                       return WEAPON_FIREBALL_MURDER_BLAST;
+               }
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_fireball(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       // firemine goes out silently
+               }
+               else
+               {
+                       org2 = w_org + w_backoff * 16;
+                       pointparticles(particleeffectnum("fireball_explode"), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                               sound(self, CH_SHOTS, "weapons/fireball_impact2.wav", VOL_BASE, ATTN_NORM * 0.25); // long range boom
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/fireball_impact2.wav");
+       }
+
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_hagar.qc b/qcsrc/common/weapons/w_hagar.qc
new file mode 100644 (file)
index 0000000..01a7169
--- /dev/null
@@ -0,0 +1,489 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ HAGAR,
+/* function  */ w_hagar,
+/* ammotype  */ IT_ROCKETS,
+/* impulse   */ 8,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "hagar",
+/* shortname */ "hagar",
+/* fullname  */ _("Hagar")
+);
+#else
+#ifdef SVQC
+// NO bounce protection, as bounces are limited!
+
+void W_Hagar_Explode (void)
+{
+       self.event_damage = func_null;
+       RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_primary_damage, autocvar_g_balance_hagar_primary_edgedamage, autocvar_g_balance_hagar_primary_radius, world, world, autocvar_g_balance_hagar_primary_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void W_Hagar_Explode2 (void)
+{
+       self.event_damage = func_null;
+       RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_secondary_damage, autocvar_g_balance_hagar_secondary_edgedamage, autocvar_g_balance_hagar_secondary_radius, world, world, autocvar_g_balance_hagar_secondary_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void W_Hagar_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+               
+       float is_linkexplode = ( ((inflictor.owner != world) ? (inflictor.owner == self.owner) : TRUE)
+               && (inflictor.projectiledeathtype & HITTYPE_SECONDARY) 
+               && (self.projectiledeathtype & HITTYPE_SECONDARY));
+       
+       if(is_linkexplode)
+               is_linkexplode = (is_linkexplode && autocvar_g_balance_hagar_secondary_load_linkexplode);
+       else
+               is_linkexplode = -1; // not secondary load, so continue as normal without exception.
+
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, is_linkexplode))
+               return; // g_projectiles_damage says to halt
+
+       self.health = self.health - damage;
+       self.angles = vectoangles(self.velocity);
+       
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, self.think);
+}
+
+void W_Hagar_Touch (void)
+{
+       PROJECTILE_TOUCH;
+       self.use ();
+}
+
+void W_Hagar_Touch2 (void)
+{
+       PROJECTILE_TOUCH;
+
+       if(self.cnt > 0 || other.takedamage == DAMAGE_AIM) {
+               self.use();
+       } else {
+               self.cnt++;
+               pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1);
+               self.angles = vectoangles (self.velocity);
+               self.owner = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+       }
+}
+
+void W_Hagar_Attack (void)
+{
+       entity missile;
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_reload_ammo);
+
+       W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_primary_damage);
+
+       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       missile.classname = "missile";
+       missile.bot_dodge = TRUE;
+       missile.bot_dodgerating = autocvar_g_balance_hagar_primary_damage;
+       
+       missile.takedamage = DAMAGE_YES;
+       missile.health = autocvar_g_balance_hagar_primary_health;
+       missile.damageforcescale = autocvar_g_balance_hagar_primary_damageforcescale;
+       missile.event_damage = W_Hagar_Damage;
+       missile.damagedbycontents = TRUE;
+       
+       missile.touch = W_Hagar_Touch;
+       missile.use = W_Hagar_Explode;
+       missile.think = adaptor_think2use_hittype_splash;
+       missile.nextthink = time + autocvar_g_balance_hagar_primary_lifetime;
+       PROJECTILE_MAKETRIGGER(missile);
+       missile.projectiledeathtype = WEP_HAGAR;
+       setorigin (missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       missile.movetype = MOVETYPE_FLY;
+       W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_primary);
+
+       missile.angles = vectoangles (missile.velocity);
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH; 
+
+       CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE);
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_Hagar_Attack2 (void)
+{
+       entity missile;
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo);
+
+       W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage);
+
+       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       missile.classname = "missile";
+       missile.bot_dodge = TRUE;
+       missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage;
+       
+       missile.takedamage = DAMAGE_YES;
+       missile.health = autocvar_g_balance_hagar_secondary_health;
+       missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale;
+       missile.event_damage = W_Hagar_Damage;
+       missile.damagedbycontents = TRUE;
+
+       missile.touch = W_Hagar_Touch2;
+       missile.cnt = 0;
+       missile.use = W_Hagar_Explode2;
+       missile.think = adaptor_think2use_hittype_splash;
+       missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand;
+       PROJECTILE_MAKETRIGGER(missile);
+       missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY;
+       setorigin (missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       missile.movetype = MOVETYPE_BOUNCEMISSILE;
+       W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_secondary);
+
+       missile.angles = vectoangles (missile.velocity);
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH; 
+
+       CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR_BOUNCING, TRUE);
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+.float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning;
+void W_Hagar_Attack2_Load_Release (void)
+{
+       // time to release the rockets we've loaded
+
+       entity missile;
+       float counter, shots, spread_pershot;
+       vector s;
+       vector forward, right, up;
+
+       if(!self.hagar_load)
+               return;
+
+       weapon_prepareattack_do(1, autocvar_g_balance_hagar_secondary_refire);
+
+       W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage);
+       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       forward = v_forward;
+       right = v_right;
+       up = v_up;
+
+       shots = self.hagar_load;
+       missile = world;
+       for(counter = 0; counter < shots; ++counter)
+       {
+               missile = spawn ();
+               missile.owner = missile.realowner = self;
+               missile.classname = "missile";
+               missile.bot_dodge = TRUE;
+               missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage;
+               
+               missile.takedamage = DAMAGE_YES;
+               missile.health = autocvar_g_balance_hagar_secondary_health;
+               missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale;
+               missile.event_damage = W_Hagar_Damage;
+               missile.damagedbycontents = TRUE;
+
+               missile.touch = W_Hagar_Touch; // not bouncy
+               missile.use = W_Hagar_Explode2;
+               missile.think = adaptor_think2use_hittype_splash;
+               missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand;
+               PROJECTILE_MAKETRIGGER(missile);
+               missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY;
+               setorigin (missile, w_shotorg);
+               setsize(missile, '0 0 0', '0 0 0');
+               missile.movetype = MOVETYPE_FLY;
+               missile.missile_flags = MIF_SPLASH; 
+               
+               // per-shot spread calculation: the more shots there are, the less spread is applied (based on the bias cvar)
+               spread_pershot = ((shots - 1) / (autocvar_g_balance_hagar_secondary_load_max - 1)); 
+               spread_pershot = (1 - (spread_pershot * autocvar_g_balance_hagar_secondary_load_spread_bias));
+               spread_pershot = (autocvar_g_balance_hagar_secondary_spread * spread_pershot * g_weaponspreadfactor);
+               
+               // pattern spread calculation
+               s = '0 0 0';
+               if (counter == 0)
+                       s = '0 0 0';
+               else
+               {
+                       makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1)));
+                       s_y = v_forward_x;
+                       s_z = v_forward_y;
+               }
+               s = s * autocvar_g_balance_hagar_secondary_load_spread * g_weaponspreadfactor;
+               
+               W_SetupProjectileVelocityEx(missile, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_hagar_secondary_speed, 0, 0, spread_pershot, FALSE);
+
+               missile.angles = vectoangles (missile.velocity);
+               missile.flags = FL_PROJECTILE;
+
+               CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE);
+
+               other = missile; MUTATOR_CALLHOOK(EditProjectile);
+       }
+
+       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_load_animtime, w_ready);
+       self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_refire * W_WeaponRateFactor();
+       self.hagar_load = 0;
+}
+
+void W_Hagar_Attack2_Load (void)
+{
+       // loadable hagar secondary attack, must always run each frame
+       
+       if(time < game_starttime)
+               return;
+
+       float loaded, enough_ammo;
+       loaded = self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max;
+
+       // this is different than WR_CHECKAMMO when it comes to reloading
+       if(autocvar_g_balance_hagar_reload_ammo)
+               enough_ammo = self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo;
+       else
+               enough_ammo = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo;
+
+       if(self.BUTTON_ATCK2)
+       {
+               if(self.BUTTON_ATCK && autocvar_g_balance_hagar_secondary_load_abort)
+               {
+                       if(self.hagar_load)
+                       {
+                               // if we pressed primary fire while loading, unload all rockets and abort
+                               self.weaponentity.state = WS_READY;
+                               W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo
+                               self.hagar_load = 0;
+                               sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM);
+
+                               // pause until we can load rockets again, once we re-press the alt fire button
+                               self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor();
+
+                               // require letting go of the alt fire button before we can load again
+                               self.hagar_loadblock = TRUE;
+                       }
+               }
+               else
+               {
+                       // check if we can attempt to load another rocket
+                       if(!loaded && enough_ammo)
+                       {
+                               if(!self.hagar_loadblock && self.hagar_loadstep < time)
+                               {
+                                       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo);
+                                       self.weaponentity.state = WS_INUSE;
+                                       self.hagar_load += 1;
+                                       sound(self, CH_WEAPON_B, "weapons/hagar_load.wav", VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most
+
+                                       if (self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max)
+                                               self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_hold * W_WeaponRateFactor();
+                                       else
+                                               self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor();
+                               }
+                       }
+                       else if(!self.hagar_loadbeep && self.hagar_load) // prevents the beep from playing each frame
+                       {
+                               // if this is the last rocket we can load, play a beep sound to notify the player
+                               sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM);
+                               self.hagar_loadbeep = TRUE;
+                       }
+               }
+       }
+       else if(self.hagar_loadblock)
+       {
+               // the alt fire button has been released, so re-enable loading if blocked
+               self.hagar_loadblock = FALSE;
+       }
+
+       if(self.hagar_load)
+       {
+               // play warning sound if we're about to release
+               if((loaded || !enough_ammo) && self.hagar_loadstep - 0.5 < time && autocvar_g_balance_hagar_secondary_load_hold >= 0)
+               {
+                       if(!self.hagar_warning && self.hagar_load) // prevents the beep from playing each frame
+                       {
+                               // we're about to automatically release after holding time, play a beep sound to notify the player
+                               sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM);
+                               self.hagar_warning = TRUE;
+                       }
+               }
+               
+               // release if player let go of button or if they've held it in too long
+               if(!self.BUTTON_ATCK2 || ((loaded || !enough_ammo) && self.hagar_loadstep < time && autocvar_g_balance_hagar_secondary_load_hold >= 0))
+               {
+                       self.weaponentity.state = WS_READY;
+                       W_Hagar_Attack2_Load_Release();
+               }
+       }
+       else
+       {
+               self.hagar_loadbeep = FALSE;
+               self.hagar_warning = FALSE;
+       }
+
+       // we aren't checking ammo during an attack, so we must do it here
+       if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2))
+       {
+               // note: this doesn't force the switch
+               W_SwitchToOtherWeapon(self);
+               return;
+       }
+}
+
+void spawnfunc_weapon_hagar (void)
+{
+       weapon_defaultspawnfunc(WEP_HAGAR);
+}
+
+float w_hagar(float req)
+{
+       float ammo_amount;
+       if (req == WR_AIM)
+               if (random()>0.15)
+                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE);
+               else
+               {
+                       // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming
+                       self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE);
+               }
+       else if (req == WR_THINK)
+       {
+               float loadable_secondary;
+               loadable_secondary = (autocvar_g_balance_hagar_secondary_load && autocvar_g_balance_hagar_secondary);
+
+               if (loadable_secondary)
+                       W_Hagar_Attack2_Load(); // must always run each frame
+               if(autocvar_g_balance_hagar_reload_ammo && self.clip_load < min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else if (self.BUTTON_ATCK && !self.hagar_load && !self.hagar_loadblock) // not while secondary is loaded or awaiting reset
+               {
+                       if (weapon_prepareattack(0, autocvar_g_balance_hagar_primary_refire))
+                       {
+                               W_Hagar_Attack();
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hagar_primary_refire, w_ready);
+                       }
+               }
+               else if (self.BUTTON_ATCK2 && !loadable_secondary && autocvar_g_balance_hagar_secondary)
+               {
+                       if (weapon_prepareattack(1, autocvar_g_balance_hagar_secondary_refire))
+                       {
+                               W_Hagar_Attack2();
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_refire, w_ready);
+                       }
+               }
+       }
+       else if (req == WR_GONETHINK)
+       {
+               // we lost the weapon and want to prepare switching away
+               if(self.hagar_load)
+               {
+                       self.weaponentity.state = WS_READY;
+                       W_Hagar_Attack2_Load_Release();
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_hagar.md3");
+               precache_model ("models/weapons/v_hagar.md3");
+               precache_model ("models/weapons/h_hagar.iqm");
+               precache_sound ("weapons/hagar_fire.wav");
+               precache_sound ("weapons/hagar_load.wav");
+               precache_sound ("weapons/hagar_beep.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_HAGAR);
+               self.current_ammo = ammo_rockets;
+               self.hagar_loadblock = FALSE;
+
+               if(self.hagar_load)
+               {
+                       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo if necessary
+                       self.hagar_load = 0;
+               }
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_primary_ammo;
+               ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_primary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo;
+               ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.hagar_load = 0;
+       }
+       else if (req == WR_PLAYERDEATH)
+       {
+               // if we have any rockets loaded when we die, release them
+               if(self.hagar_load && autocvar_g_balance_hagar_secondary_load_releasedeath)
+                       W_Hagar_Attack2_Load_Release();
+       }
+       else if (req == WR_RELOAD)
+       {
+               if not(self.hagar_load) // require releasing loaded rockets first
+                       W_Reload(min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo), autocvar_g_balance_hagar_reload_ammo, autocvar_g_balance_hagar_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_HAGAR_SUICIDE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_HAGAR_MURDER_BURST;
+               else
+                       return WEAPON_HAGAR_MURDER_SPRAY;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_hagar(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 6;
+               pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
+               if(!w_issilent)
+               {
+                       if (w_random<0.15)
+                               sound(self, CH_SHOTS, "weapons/hagexp1.wav", VOL_BASE, ATTN_NORM);
+                       else if (w_random<0.7)
+                               sound(self, CH_SHOTS, "weapons/hagexp2.wav", VOL_BASE, ATTN_NORM);
+                       else
+                               sound(self, CH_SHOTS, "weapons/hagexp3.wav", VOL_BASE, ATTN_NORM);
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/hagexp1.wav");
+               precache_sound("weapons/hagexp2.wav");
+               precache_sound("weapons/hagexp3.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_hlac.qc b/qcsrc/common/weapons/w_hlac.qc
new file mode 100644 (file)
index 0000000..cc7f053
--- /dev/null
@@ -0,0 +1,261 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ HLAC,
+/* function  */ w_hlac,
+/* ammotype  */ IT_CELLS,
+/* impulse   */ 6,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "hlac",
+/* shortname */ "hlac",
+/* fullname  */ _("Heavy Laser Assault Cannon")
+);
+#else
+#ifdef SVQC
+
+void W_HLAC_Touch (void)
+{
+       PROJECTILE_TOUCH;
+
+       self.event_damage = func_null;
+       
+       if(self.projectiledeathtype & HITTYPE_SECONDARY)
+               RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_secondary_damage, autocvar_g_balance_hlac_secondary_edgedamage, autocvar_g_balance_hlac_secondary_radius, world, world, autocvar_g_balance_hlac_secondary_force, self.projectiledeathtype, other);
+       else
+               RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_primary_damage, autocvar_g_balance_hlac_primary_edgedamage, autocvar_g_balance_hlac_primary_radius, world, world, autocvar_g_balance_hlac_primary_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void W_HLAC_Attack (void)
+{
+       entity missile;
+    float spread;
+
+       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_reload_ammo);
+
+    spread = autocvar_g_balance_hlac_primary_spread_min + (autocvar_g_balance_hlac_primary_spread_add * self.misc_bulletcounter);
+    spread = min(spread,autocvar_g_balance_hlac_primary_spread_max);
+    if(self.crouch)
+        spread = spread * autocvar_g_balance_hlac_primary_spread_crouchmod;
+
+       W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_primary_damage);
+       pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+       if (!g_norecoil)
+       {
+               self.punchangle_x = random () - 0.5;
+               self.punchangle_y = random () - 0.5;
+       }
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       missile.classname = "hlacbolt";
+       missile.bot_dodge = TRUE;
+
+    missile.bot_dodgerating = autocvar_g_balance_hlac_primary_damage;
+
+       missile.movetype = MOVETYPE_FLY;
+       PROJECTILE_MAKETRIGGER(missile);
+
+       setorigin (missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_primary_speed, spread);
+       //missile.angles = vectoangles (missile.velocity); // csqc
+
+       missile.touch = W_HLAC_Touch;
+       missile.think = SUB_Remove;
+
+    missile.nextthink = time + autocvar_g_balance_hlac_primary_lifetime;
+
+       missile.flags = FL_PROJECTILE;
+       missile.projectiledeathtype = WEP_HLAC;
+
+       CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE);
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_HLAC_Attack2f (void)
+{
+       entity missile;
+    float spread;
+
+    spread = autocvar_g_balance_hlac_secondary_spread;
+
+
+    if(self.crouch)
+        spread = spread * autocvar_g_balance_hlac_secondary_spread_crouchmod;
+
+       W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_secondary_damage);
+       pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       missile.classname = "hlacbolt";
+       missile.bot_dodge = TRUE;
+
+    missile.bot_dodgerating = autocvar_g_balance_hlac_secondary_damage;
+
+       missile.movetype = MOVETYPE_FLY;
+       PROJECTILE_MAKETRIGGER(missile);
+
+       setorigin (missile, w_shotorg);
+       setsize(missile, '0 0 0', '0 0 0');
+
+       W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_secondary_speed, spread);
+       //missile.angles = vectoangles (missile.velocity); // csqc
+
+       missile.touch = W_HLAC_Touch;
+       missile.think = SUB_Remove;
+
+    missile.nextthink = time + autocvar_g_balance_hlac_secondary_lifetime;
+
+       missile.flags = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH; 
+       missile.projectiledeathtype = WEP_HLAC | HITTYPE_SECONDARY;
+
+       CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE);
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_HLAC_Attack2 (void)
+{
+    float i;
+
+       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_secondary_ammo, autocvar_g_balance_hlac_reload_ammo);
+
+    for(i=autocvar_g_balance_hlac_secondary_shots;i>0;--i)
+        W_HLAC_Attack2f();
+
+       if (!g_norecoil)
+       {
+               self.punchangle_x = random () - 0.5;
+               self.punchangle_y = random () - 0.5;
+       }
+}
+
+// weapon frames
+void HLAC_fire1_02()
+{
+       if(self.weapon != self.switchweapon) // abort immediately if switching
+       {
+               w_ready();
+               return;
+       }
+
+       if (self.BUTTON_ATCK)
+       {
+               if (!weapon_action(self.weapon, WR_CHECKAMMO1))
+               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+               {
+                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                       w_ready();
+                       return;
+               }
+
+               ATTACK_FINISHED(self) = time + autocvar_g_balance_hlac_primary_refire * W_WeaponRateFactor();
+               W_HLAC_Attack();
+               self.misc_bulletcounter = self.misc_bulletcounter + 1;
+        weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02);
+       }
+       else
+       {
+               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_animtime, w_ready);
+       }
+}
+
+void spawnfunc_weapon_hlac (void)
+{
+       weapon_defaultspawnfunc(WEP_HLAC);
+}
+
+float w_hlac(float req)
+{
+       float ammo_amount;
+       if (req == WR_AIM)
+        self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hlac_primary_speed, 0, autocvar_g_balance_hlac_primary_lifetime, FALSE);
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else if (self.BUTTON_ATCK)
+               {
+                       if (weapon_prepareattack(0, autocvar_g_balance_hlac_primary_refire))
+                       {
+                               self.misc_bulletcounter = 0;
+                               W_HLAC_Attack();
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02);
+                       }
+               }
+
+               else if (self.BUTTON_ATCK2 && autocvar_g_balance_hlac_secondary)
+               {
+                       if (weapon_prepareattack(1, autocvar_g_balance_hlac_secondary_refire))
+                       {
+                               W_HLAC_Attack2();
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hlac_secondary_animtime, w_ready);
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+        precache_model ("models/weapons/g_hlac.md3");
+               precache_model ("models/weapons/v_hlac.md3");
+               precache_model ("models/weapons/h_hlac.iqm");
+               precache_sound ("weapons/lasergun_fire.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_HLAC);
+               self.current_ammo = ammo_cells;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_primary_ammo;
+               ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_primary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_secondary_ammo;
+               ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_secondary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo), autocvar_g_balance_hlac_reload_ammo, autocvar_g_balance_hlac_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_HLAC_SUICIDE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               return WEAPON_HLAC_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_hlac(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 6;
+               pointparticles(particleeffectnum("laser_impact"), org2, w_backoff * 1000, 1);
+               if(!w_issilent)
+                       sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/laserimpact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_hook.qc b/qcsrc/common/weapons/w_hook.qc
new file mode 100644 (file)
index 0000000..7f03744
--- /dev/null
@@ -0,0 +1,307 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ HOOK,
+/* function  */ w_hook,
+/* ammotype  */ IT_CELLS|IT_FUEL,
+/* impulse   */ 0,
+/* flags     */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ 0,
+/* model     */ "hookgun",
+/* shortname */ "hook",
+/* fullname  */ _("Grappling Hook")
+);
+#else
+#ifdef SVQC
+.float dmg;
+.float dmg_edge;
+.float dmg_radius;
+.float dmg_force;
+.float dmg_power;
+.float dmg_duration;
+.float dmg_last;
+.float hook_refire;
+.float hook_time_hooked;
+.float hook_time_fueldecrease;
+
+void W_Hook_ExplodeThink (void)
+{
+       float dt, dmg_remaining_next, f;
+
+       dt = time - self.teleport_time;
+       dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power);
+
+       f = self.dmg_last - dmg_remaining_next;
+       self.dmg_last = dmg_remaining_next;
+
+       RadiusDamage (self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world);
+       self.projectiledeathtype |= HITTYPE_BOUNCE;
+       //RadiusDamage (self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world);
+
+       if(dt < self.dmg_duration)
+               self.nextthink = time + 0.05; // soon
+       else
+               remove(self);
+}
+
+void W_Hook_Explode2 (void)
+{
+       self.event_damage = func_null;
+       self.touch = func_null;
+       self.effects |= EF_NODRAW;
+
+       self.think = W_Hook_ExplodeThink;
+       self.nextthink = time;
+       self.dmg = autocvar_g_balance_hook_secondary_damage;
+       self.dmg_edge = autocvar_g_balance_hook_secondary_edgedamage;
+       self.dmg_radius = autocvar_g_balance_hook_secondary_radius;
+       self.dmg_force = autocvar_g_balance_hook_secondary_force;
+       self.dmg_power = autocvar_g_balance_hook_secondary_power;
+       self.dmg_duration = autocvar_g_balance_hook_secondary_duration;
+       self.teleport_time = time;
+       self.dmg_last = 1;
+       self.movetype = MOVETYPE_NONE;
+}
+
+void W_Hook_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+               
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt    
+       
+       self.health = self.health - damage;
+       
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2);
+}
+
+void W_Hook_Touch2 (void)
+{
+       PROJECTILE_TOUCH;
+       self.use();
+}
+
+void W_Hook_Attack2()
+{
+       entity gren;
+
+       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hook_secondary_ammo, FALSE);
+       W_SetupShot (self, FALSE, 4, "weapons/hookbomb_fire.wav", CH_WEAPON_A, autocvar_g_balance_hook_secondary_damage);
+
+       gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "hookbomb";
+       gren.bot_dodge = TRUE;
+       gren.bot_dodgerating = autocvar_g_balance_hook_secondary_damage;
+       gren.movetype = MOVETYPE_TOSS;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = WEP_HOOK | HITTYPE_SECONDARY;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '0 0 0', '0 0 0');
+
+       gren.nextthink = time + autocvar_g_balance_hook_secondary_lifetime;
+       gren.think = adaptor_think2use_hittype_splash;
+       gren.use = W_Hook_Explode2;
+       gren.touch = W_Hook_Touch2;
+       
+       gren.takedamage = DAMAGE_YES;
+       gren.health = autocvar_g_balance_hook_secondary_health;
+       gren.damageforcescale = autocvar_g_balance_hook_secondary_damageforcescale;
+       gren.event_damage = W_Hook_Damage;
+       gren.damagedbycontents = TRUE;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+
+       gren.velocity = '0 0 1' * autocvar_g_balance_hook_secondary_speed;
+       if(autocvar_g_projectiles_newton_style)
+               gren.velocity = gren.velocity + self.velocity;
+
+       gren.gravity = autocvar_g_balance_hook_secondary_gravity;
+       //W_SetupProjectileVelocity(gren); // just falling down!
+
+       gren.angles = '0 0 0';
+       gren.flags = FL_PROJECTILE;
+
+       CSQCProjectile(gren, TRUE, PROJECTILE_HOOKBOMB, TRUE);
+
+       other = gren; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void spawnfunc_weapon_hook (void)
+{
+       if(g_grappling_hook) // offhand hook
+       {
+               startitem_failed = TRUE;
+               remove(self);
+               return;
+       }
+       weapon_defaultspawnfunc(WEP_HOOK);
+}
+
+float w_hook(float req)
+{
+       float hooked_time_max, hooked_fuel;
+               
+       if (req == WR_AIM)
+       {
+               // ... sorry ...
+       }
+       else if (req == WR_THINK)
+       {
+               if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK))
+               {
+                       if(!self.hook)
+                       if not(self.hook_state & HOOK_WAITING_FOR_RELEASE)
+                       if not(self.hook_state & HOOK_FIRING)
+                       if (time > self.hook_refire)
+                       if (weapon_prepareattack(0, -1))
+                       {
+                               W_DecreaseAmmo(ammo_fuel, autocvar_g_balance_hook_primary_fuel, FALSE);
+                               self.hook_state |= HOOK_FIRING;
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hook_primary_animtime, w_ready);                         
+                       }
+               }
+
+               if (self.BUTTON_ATCK2)
+               {
+                       if (weapon_prepareattack(1, autocvar_g_balance_hook_secondary_refire))
+                       {
+                               W_Hook_Attack2();
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hook_secondary_animtime, w_ready);
+                       }
+               }
+
+               if(self.hook)
+               {
+                       // if hooked, no bombs, and increase the timer
+                       self.hook_refire = max(self.hook_refire, time + autocvar_g_balance_hook_primary_refire * W_WeaponRateFactor());
+
+                       // hook also inhibits health regeneration, but only for 1 second
+                       if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+                               self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
+               }
+
+               if(self.hook && self.hook.state == 1)
+               {
+                       hooked_time_max = autocvar_g_balance_hook_primary_hooked_time_max;                      
+                       if (hooked_time_max > 0)
+                       {
+                               if ( time > self.hook_time_hooked + hooked_time_max )
+                                       self.hook_state |= HOOK_REMOVING;
+                       }
+                       
+                       hooked_fuel = autocvar_g_balance_hook_primary_hooked_fuel;
+                       if (hooked_fuel > 0)
+                       {
+                               if ( time > self.hook_time_fueldecrease )
+                               {
+                                       if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+                                       {
+                                               if ( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel )
+                                               {
+                                                       W_DecreaseAmmo(ammo_fuel, (time - self.hook_time_fueldecrease) * hooked_fuel, FALSE);
+                                                       self.hook_time_fueldecrease = time;
+                                                       // decrease next frame again
+                                               }
+                                               else
+                                               {
+                                                       self.ammo_fuel = 0;
+                                                       self.hook_state |= HOOK_REMOVING;
+                                                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                                               }
+                                       }
+                               }
+                       }
+               }
+               else
+               {
+                       self.hook_time_hooked = time;                           
+                       self.hook_time_fueldecrease = time + autocvar_g_balance_hook_primary_hooked_time_free;
+               }
+
+               if (self.BUTTON_CROUCH)
+               {
+                       self.hook_state &~= HOOK_PULLING;
+                       if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK))
+                               self.hook_state &~= HOOK_RELEASING;
+                       else
+                               self.hook_state |= HOOK_RELEASING;
+               }
+               else
+               {
+                       self.hook_state |= HOOK_PULLING;
+                       self.hook_state &~= HOOK_RELEASING;
+
+                       if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK))
+                       {
+                               // already fired
+                               if(self.hook)
+                                       self.hook_state |= HOOK_WAITING_FOR_RELEASE;
+                       }
+                       else
+                       {
+                               self.hook_state |= HOOK_REMOVING;
+                               self.hook_state &~= HOOK_WAITING_FOR_RELEASE;
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_hookgun.md3");
+               precache_model ("models/weapons/v_hookgun.md3");
+               precache_model ("models/weapons/h_hookgun.iqm");
+               precache_sound ("weapons/hook_impact.wav"); // done by g_hook.qc
+               precache_sound ("weapons/hook_fire.wav");
+               precache_sound ("weapons/hookbomb_fire.wav");
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_HOOK);
+               self.current_ammo = ammo_fuel;
+               self.hook_state &~= HOOK_WAITING_FOR_RELEASE;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               if(self.hook)
+                       return self.ammo_fuel > 0;
+               else
+                       return self.ammo_fuel >= autocvar_g_balance_hook_primary_fuel;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               return self.ammo_cells >= autocvar_g_balance_hook_secondary_ammo;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.hook_refire = time;
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return FALSE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               return WEAPON_HOOK_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_hook(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 2;
+               pointparticles(particleeffectnum("hookbomb_explode"), org2, '0 0 0', 1);
+               if(!w_issilent)
+                       sound(self, CH_SHOTS, "weapons/hookbomb_impact.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/hookbomb_impact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_machinegun.qc b/qcsrc/common/weapons/w_machinegun.qc
new file mode 100644 (file)
index 0000000..923ed95
--- /dev/null
@@ -0,0 +1,341 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ UZI,
+/* function  */ w_uzi,
+/* ammotype  */ IT_NAILS,
+/* impulse   */ 3,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "uzi",
+/* shortname */ "uzi",
+/* fullname  */ _("Machine Gun")
+);
+#else
+#ifdef SVQC
+
+// leilei's fancy muzzleflash stuff
+void UZI_Flash_Go()
+{
+       self.frame = self.frame + 2;
+       self.scale = self.scale * 0.5;
+       self.alpha = self.alpha - 0.25;
+       self.nextthink = time + 0.05;
+
+       if (self.alpha <= 0)
+       {
+               self.think = SUB_Remove;
+               self.nextthink = time;
+               self.realowner.muzzle_flash = world;
+               return;
+       }
+
+}
+
+void UziFlash()
+{
+       if (self.muzzle_flash == world)
+               self.muzzle_flash = spawn();
+
+       // muzzle flash for 1st person view
+       setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below
+
+       self.muzzle_flash.scale = 0.75;
+       self.muzzle_flash.think = UZI_Flash_Go;
+       self.muzzle_flash.nextthink = time + 0.02;
+       self.muzzle_flash.frame = 2;
+       self.muzzle_flash.alpha = 0.75;
+       self.muzzle_flash.angles_z = random() * 180;
+       self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       self.muzzle_flash.owner = self.muzzle_flash.realowner = self;
+}
+
+void W_UZI_Attack (float deathtype)
+{
+       W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? autocvar_g_balance_uzi_first_damage : autocvar_g_balance_uzi_sustained_damage));
+       if (!g_norecoil)
+       {
+               self.punchangle_x = random () - 0.5;
+               self.punchangle_y = random () - 0.5;
+       }
+
+       // this attack_finished just enforces a cooldown at the end of a burst
+       ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor();
+
+       if (self.misc_bulletcounter == 1)
+               fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_first_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_first_damage, autocvar_g_balance_uzi_first_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant);
+       else
+               fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_sustained_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant);
+       endFireBallisticBullet();
+
+       pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       UziFlash();
+       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
+
+       // casing code
+       if (autocvar_g_casings >= 2)
+               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
+
+       if (self.misc_bulletcounter == 1)
+               W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_first_ammo, autocvar_g_balance_uzi_reload_ammo);
+       else
+               W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo);
+}
+
+// weapon frames
+void uzi_fire1_02()
+{
+       if(self.weapon != self.switchweapon) // abort immediately if switching
+       {
+               w_ready();
+               return;
+       }
+       if (self.BUTTON_ATCK)
+       {
+               if (!weapon_action(self.weapon, WR_CHECKAMMO2))
+               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+               {
+                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                       w_ready();
+                       return;
+               }
+               self.misc_bulletcounter = self.misc_bulletcounter + 1;
+               W_UZI_Attack(WEP_UZI);
+               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02);
+       }
+       else
+               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, w_ready);
+}
+
+
+void uzi_mode1_fire_auto()
+{
+       float uzi_spread;
+
+       if (!self.BUTTON_ATCK)
+       {
+               w_ready();
+               return;
+       }
+
+       if (!weapon_action(self.weapon, WR_CHECKAMMO1))
+       if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+       {
+               W_SwitchWeapon_Force(self, w_getbestweapon(self));
+               w_ready();
+               return;
+       }
+
+       W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo);
+
+       W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage);
+       if (!g_norecoil)
+       {
+               self.punchangle_x = random () - 0.5;
+               self.punchangle_y = random () - 0.5;
+       }
+
+       uzi_spread = bound(autocvar_g_balance_uzi_spread_min, autocvar_g_balance_uzi_spread_min + (autocvar_g_balance_uzi_spread_add * self.misc_bulletcounter), autocvar_g_balance_uzi_spread_max);
+       fireBallisticBullet(w_shotorg, w_shotdir, uzi_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant);
+       endFireBallisticBullet();
+
+       self.misc_bulletcounter = self.misc_bulletcounter + 1;
+
+       pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       UziFlash();
+       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
+
+       if (autocvar_g_casings >= 2) // casing code
+               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
+
+       ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor();
+       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_mode1_fire_auto);
+}
+
+void uzi_mode1_fire_burst()
+{
+       W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage);
+       if (!g_norecoil)
+       {
+               self.punchangle_x = random () - 0.5;
+               self.punchangle_y = random () - 0.5;
+       }
+
+       fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_burst_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant);
+       endFireBallisticBullet();
+
+
+       pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       UziFlash();
+       W_AttachToShotorg(self.muzzle_flash, '5 0 0');
+
+       if (autocvar_g_casings >= 2) // casing code
+               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
+
+       self.misc_bulletcounter = self.misc_bulletcounter + 1;
+       if (self.misc_bulletcounter == 0)
+       {
+               ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_burst_refire2 * W_WeaponRateFactor();
+               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_animtime, w_ready);
+       }
+       else
+       {
+               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_refire, uzi_mode1_fire_burst);
+       }
+
+}
+
+void spawnfunc_weapon_machinegun(); // defined in t_items.qc
+
+float w_uzi(float req)
+{
+       float ammo_amount;
+       if (req == WR_AIM)
+               if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200)
+                       self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
+               else
+               {
+                       self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
+               }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_uzi_reload_ammo && self.clip_load < min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else if(autocvar_g_balance_uzi_mode == 1)
+               {
+                       if (self.BUTTON_ATCK)
+                       if (weapon_prepareattack(0, 0))
+                       {
+                               self.misc_bulletcounter = 0;
+                               uzi_mode1_fire_auto();
+                       }
+
+                       if(self.BUTTON_ATCK2)
+                       if(weapon_prepareattack(1, 0))
+                       {
+                               if (!weapon_action(self.weapon, WR_CHECKAMMO2))
+                               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+                               {
+                                       W_SwitchWeapon_Force(self, w_getbestweapon(self));
+                                       w_ready();
+                                       return FALSE;
+                               }
+
+                               W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_burst_ammo, autocvar_g_balance_uzi_reload_ammo);
+
+                               self.misc_bulletcounter = autocvar_g_balance_uzi_burst * -1;
+                               uzi_mode1_fire_burst();
+                       }
+               }
+               else
+               {
+
+                       if (self.BUTTON_ATCK)
+                       if (weapon_prepareattack(0, 0))
+                       {
+                               self.misc_bulletcounter = 1;
+                               W_UZI_Attack(WEP_UZI); // sets attack_finished
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02);
+                       }
+
+                       if (self.BUTTON_ATCK2 && autocvar_g_balance_uzi_first)
+                       if (weapon_prepareattack(1, 0))
+                       {
+                               self.misc_bulletcounter = 1;
+                               W_UZI_Attack(WEP_UZI | HITTYPE_SECONDARY); // sets attack_finished
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_first_refire, w_ready);
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/uziflash.md3");
+               precache_model ("models/weapons/g_uzi.md3");
+               precache_model ("models/weapons/v_uzi.md3");
+               precache_model ("models/weapons/h_uzi.iqm");
+               precache_sound ("weapons/uzi_fire.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_UZI);
+               self.current_ammo = ammo_nails;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               if(autocvar_g_balance_uzi_mode == 1)
+                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_sustained_ammo;
+               else
+                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo;
+
+               if(autocvar_g_balance_uzi_reload_ammo)
+               {
+                       if(autocvar_g_balance_uzi_mode == 1)
+                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_sustained_ammo;
+                       else
+                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo;
+               }
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               if(autocvar_g_balance_uzi_mode == 1)
+                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_burst_ammo;
+               else
+                       ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo;
+
+               if(autocvar_g_balance_uzi_reload_ammo)
+               {
+                       if(autocvar_g_balance_uzi_mode == 1)
+                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_burst_ammo;
+                       else
+                               ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo;
+               }
+               return ammo_amount;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo), autocvar_g_balance_uzi_reload_ammo, autocvar_g_balance_uzi_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_THINKING_WITH_PORTALS;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_UZI_MURDER_SNIPE;
+               else
+                       return WEAPON_UZI_MURDER_SPRAY;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_uzi(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 2;
+               pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1);
+               if(!w_issilent)
+                       if(w_random < 0.05)
+                               sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
+                       else if(w_random < 0.1)
+                               sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
+                       else if(w_random < 0.2)
+                               sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/ric1.wav");
+               precache_sound("weapons/ric2.wav");
+               precache_sound("weapons/ric3.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_minelayer.qc b/qcsrc/common/weapons/w_minelayer.qc
new file mode 100644 (file)
index 0000000..b27d069
--- /dev/null
@@ -0,0 +1,563 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ MINE_LAYER,
+/* function  */ w_minelayer,
+/* ammotype  */ IT_ROCKETS,
+/* impulse   */ 4,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* model     */ "minelayer",
+/* shortname */ "minelayer",
+/* fullname  */ _("Mine Layer")
+);
+#else
+#ifdef SVQC
+void W_Mine_Think (void);
+.float minelayer_detonate, mine_explodeanyway;
+.float mine_time;
+.vector mine_orientation;
+
+void spawnfunc_weapon_minelayer (void)
+{
+       weapon_defaultspawnfunc(WEP_MINE_LAYER);
+}
+
+void W_Mine_Stick (entity to)
+{
+       spamsound (self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
+
+       // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile
+
+       entity newmine;
+       newmine = spawn();
+       newmine.classname = self.classname;
+
+       newmine.bot_dodge = self.bot_dodge;
+       newmine.bot_dodgerating = self.bot_dodgerating;
+
+       newmine.owner = self.owner;
+       newmine.realowner = self.realowner;
+       setsize(newmine, '-4 -4 -4', '4 4 4');
+       setorigin(newmine, self.origin);
+       setmodel(newmine, "models/mine.md3");
+       newmine.angles = vectoangles(-trace_plane_normal); // face against the surface
+
+       newmine.mine_orientation = -trace_plane_normal;
+
+       newmine.takedamage = self.takedamage;
+       newmine.damageforcescale = self.damageforcescale;
+       newmine.health = self.health;
+       newmine.event_damage = self.event_damage;
+       newmine.spawnshieldtime = self.spawnshieldtime;
+       newmine.damagedbycontents = TRUE;
+
+       newmine.movetype = MOVETYPE_NONE; // lock the mine in place
+       newmine.projectiledeathtype = self.projectiledeathtype;
+
+       newmine.mine_time = self.mine_time;
+
+       newmine.touch = func_null;
+       newmine.think = W_Mine_Think;
+       newmine.nextthink = time;
+       newmine.cnt = self.cnt;
+       newmine.flags = self.flags;
+
+       remove(self);
+       self = newmine;
+
+       if(to)
+               SetMovetypeFollow(self, to);
+}
+
+void W_Mine_Explode ()
+{
+       if(other.takedamage == DAMAGE_AIM)
+               if(IS_PLAYER(other))
+                       if(IsDifferentTeam(self.realowner, other))
+                               if(other.deadflag == DEAD_NO)
+                                       if(IsFlying(other))
+                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_damage, autocvar_g_balance_minelayer_edgedamage, autocvar_g_balance_minelayer_radius, world, world, autocvar_g_balance_minelayer_force, self.projectiledeathtype, other);
+
+       if (self.realowner.weapon == WEP_MINE_LAYER)
+       {
+               entity oldself;
+               oldself = self;
+               self = self.realowner;
+               if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1))
+               {
+                       self.cnt = WEP_MINE_LAYER;
+                       ATTACK_FINISHED(self) = time;
+                       self.switchweapon = w_getbestweapon(self);
+               }
+               self = oldself;
+       }
+       self.realowner.minelayer_mines -= 1;
+       remove (self);
+}
+
+void W_Mine_DoRemoteExplode ()
+{
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
+               self.velocity = self.mine_orientation; // particle fx and decals need .velocity
+
+       RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_remote_damage, autocvar_g_balance_minelayer_remote_edgedamage, autocvar_g_balance_minelayer_remote_radius, world, world, autocvar_g_balance_minelayer_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world);
+
+       if (self.realowner.weapon == WEP_MINE_LAYER)
+       {
+               entity oldself;
+               oldself = self;
+               self = self.realowner;
+               if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1))
+               {
+                       self.cnt = WEP_MINE_LAYER;
+                       ATTACK_FINISHED(self) = time;
+                       self.switchweapon = w_getbestweapon(self);
+               }
+               self = oldself;
+       }
+       self.realowner.minelayer_mines -= 1;
+       remove (self);
+}
+
+void W_Mine_RemoteExplode ()
+{
+       if(self.realowner.deadflag == DEAD_NO)
+               if((self.spawnshieldtime >= 0)
+                       ? (time >= self.spawnshieldtime) // timer
+                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_minelayer_remote_radius) // safety device
+               )
+               {
+                       W_Mine_DoRemoteExplode();
+               }
+}
+
+void W_Mine_ProximityExplode ()
+{
+       // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
+       if(autocvar_g_balance_minelayer_protection && self.mine_explodeanyway == 0)
+       {
+               entity head;
+               head = findradius(self.origin, autocvar_g_balance_minelayer_radius);
+               while(head)
+               {
+                       if(head == self.realowner || !IsDifferentTeam(head, self.realowner))
+                               return;
+                       head = head.chain;
+               }
+       }
+
+       self.mine_time = 0;
+       W_Mine_Explode();
+}
+
+float W_Mine_Count(entity e)
+{
+       float minecount = 0;
+       entity mine;
+       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e)
+               minecount += 1;
+
+       return minecount;
+}
+
+void W_Mine_Think (void)
+{
+       entity head;
+
+       self.nextthink = time;
+
+       if(self.movetype == MOVETYPE_FOLLOW)
+       {
+               if(LostMovetypeFollow(self))
+               {
+                       UnsetMovetypeFollow(self);
+                       self.movetype = MOVETYPE_NONE;
+               }
+       }
+       
+       // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this
+       // TODO: replace this mine_trigger.wav sound with a real countdown
+       if ((time > self.cnt) && (!self.mine_time))
+       {
+               if(autocvar_g_balance_minelayer_lifetime_countdown > 0)
+                       spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
+               self.mine_time = time + autocvar_g_balance_minelayer_lifetime_countdown;
+               self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near.
+       }
+
+       // a player's mines shall explode if he disconnects or dies
+       // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams?
+       if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO)
+       {
+               other = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               W_Mine_Explode();
+               return;
+       }
+
+       // set the mine for detonation when a foe gets close enough
+       head = findradius(self.origin, autocvar_g_balance_minelayer_proximityradius);
+       while(head)
+       {
+               if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
+               if(head != self.realowner && IsDifferentTeam(head, self.realowner)) // don't trigger for team mates
+               if(!self.mine_time)
+               {
+                       spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
+                       self.mine_time = time + autocvar_g_balance_minelayer_time;
+               }
+               head = head.chain;
+       }
+
+       // explode if it's time to
+       if(self.mine_time && time >= self.mine_time)
+       {
+               W_Mine_ProximityExplode();
+               return;
+       }
+
+       // remote detonation
+       if (self.realowner.weapon == WEP_MINE_LAYER)
+       if (self.realowner.deadflag == DEAD_NO)
+       if (self.minelayer_detonate)
+               W_Mine_RemoteExplode();
+}
+
+void W_Mine_Touch (void)
+{
+       if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
+               return; // we're already a stuck mine, why do we get called? TODO does this even happen?
+
+       if(WarpZone_Projectile_Touch())
+       {
+               if(wasfreed(self))
+                       self.realowner.minelayer_mines -= 1;
+               return;
+       }
+
+       if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO)
+       {
+               // hit a player
+               // don't stick
+       }
+       else
+       {
+               W_Mine_Stick(other);
+       }
+}
+
+void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+               
+       float is_from_enemy = (inflictor.realowner != self.realowner);
+               
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1)))
+               return; // g_projectiles_damage says to halt
+               
+       self.health = self.health - damage;
+       self.angles = vectoangles(self.velocity);
+       
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
+}
+
+void W_Mine_Attack (void)
+{
+       entity mine;
+       entity flash;
+
+       // scan how many mines we placed, and return if we reached our limit
+       if(autocvar_g_balance_minelayer_limit)
+       {
+               if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit)
+               {
+                       // the refire delay keeps this message from being spammed
+                       sprint(self, strcat("minelayer: You cannot place more than ^2", ftos(autocvar_g_balance_minelayer_limit), " ^7mines at a time\n") );
+                       play2(self, "weapons/unavailable.wav");
+                       return;
+               }
+       }
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo);
+
+       W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, autocvar_g_balance_minelayer_damage);
+       pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       mine = WarpZone_RefSys_SpawnSameRefSys(self);
+       mine.owner = mine.realowner = self;
+       if(autocvar_g_balance_minelayer_detonatedelay >= 0)
+               mine.spawnshieldtime = time + autocvar_g_balance_minelayer_detonatedelay;
+       else
+               mine.spawnshieldtime = -1;
+       mine.classname = "mine";
+       mine.bot_dodge = TRUE;
+       mine.bot_dodgerating = autocvar_g_balance_minelayer_damage * 2; // * 2 because it can detonate inflight which makes it even more dangerous
+
+       mine.takedamage = DAMAGE_YES;
+       mine.damageforcescale = autocvar_g_balance_minelayer_damageforcescale;
+       mine.health = autocvar_g_balance_minelayer_health;
+       mine.event_damage = W_Mine_Damage;
+       mine.damagedbycontents = TRUE;
+
+       mine.movetype = MOVETYPE_TOSS;
+       PROJECTILE_MAKETRIGGER(mine);
+       mine.projectiledeathtype = WEP_MINE_LAYER;
+       setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
+
+       setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
+       W_SetupProjectileVelocity(mine, autocvar_g_balance_minelayer_speed, 0);
+       mine.angles = vectoangles (mine.velocity);
+
+       mine.touch = W_Mine_Touch;
+       mine.think = W_Mine_Think;
+       mine.nextthink = time;
+       mine.cnt = time + (autocvar_g_balance_minelayer_lifetime - autocvar_g_balance_minelayer_lifetime_countdown);
+       mine.flags = FL_PROJECTILE;
+       mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY;
+
+       CSQCProjectile(mine, TRUE, PROJECTILE_MINE, TRUE);
+
+       // muzzle flash for 1st person view
+       flash = spawn ();
+       setmodel (flash, "models/flash.md3"); // precision set below
+       SUB_SetFade (flash, time, 0.1);
+       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       W_AttachToShotorg(flash, '5 0 0');
+
+       // common properties
+
+       other = mine; MUTATOR_CALLHOOK(EditProjectile);
+       
+       self.minelayer_mines = W_Mine_Count(self);
+}
+
+float W_PlacedMines(float detonate)
+{
+       entity mine;
+       float minfound = 0;
+
+       for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self)
+       {
+               if(detonate)
+               {
+                       if(!mine.minelayer_detonate)
+                       {
+                               mine.minelayer_detonate = TRUE;
+                               minfound = 1;
+                       }
+               }
+               else
+                       minfound = 1;
+       }
+       return minfound;
+}
+
+float w_minelayer(float req)
+{
+       entity mine;
+       float ammo_amount;
+
+       if (req == WR_AIM)
+       {
+               // aim and decide to fire if appropriate
+               if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit)
+                       self.BUTTON_ATCK = FALSE;
+               else
+                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_minelayer_speed, 0, autocvar_g_balance_minelayer_lifetime, FALSE);
+               if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
+               {
+                       // decide whether to detonate mines
+                       entity targetlist, targ;
+                       float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
+                       float selfdamage, teamdamage, enemydamage;
+                       edgedamage = autocvar_g_balance_minelayer_edgedamage;
+                       coredamage = autocvar_g_balance_minelayer_damage;
+                       edgeradius = autocvar_g_balance_minelayer_radius;
+                       recipricoledgeradius = 1 / edgeradius;
+                       selfdamage = 0;
+                       teamdamage = 0;
+                       enemydamage = 0;
+                       targetlist = findchainfloat(bot_attack, TRUE);
+                       mine = find(world, classname, "mine");
+                       while (mine)
+                       {
+                               if (mine.realowner != self)
+                               {
+                                       mine = find(mine, classname, "mine");
+                                       continue;
+                               }
+                               targ = targetlist;
+                               while (targ)
+                               {
+                                       d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
+                                       d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
+                                       // count potential damage according to type of target
+                                       if (targ == self)
+                                               selfdamage = selfdamage + d;
+                                       else if (targ.team == self.team && teamplay)
+                                               teamdamage = teamdamage + d;
+                                       else if (bot_shouldattack(targ))
+                                               enemydamage = enemydamage + d;
+                                       targ = targ.chain;
+                               }
+                               mine = find(mine, classname, "mine");
+                       }
+                       float desirabledamage;
+                       desirabledamage = enemydamage;
+                       if (time > self.invincible_finished && time > self.spawnshieldtime)
+                               desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
+                       if (teamplay && self.team)
+                               desirabledamage = desirabledamage - teamdamage;
+
+                       mine = find(world, classname, "mine");
+                       while (mine)
+                       {
+                               if (mine.realowner != self)
+                               {
+                                       mine = find(mine, classname, "mine");
+                                       continue;
+                               }
+                               makevectors(mine.v_angle);
+                               targ = targetlist;
+                               if (skill > 9) // normal players only do this for the target they are tracking
+                               {
+                                       targ = targetlist;
+                                       while (targ)
+                                       {
+                                               if (
+                                                       (v_forward * normalize(mine.origin - targ.origin)< 0.1)
+                                                       && desirabledamage > 0.1*coredamage
+                                               )self.BUTTON_ATCK2 = TRUE;
+                                               targ = targ.chain;
+                                       }
+                               }else{
+                                       float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
+                                       //As the distance gets larger, a correct detonation gets near imposible
+                                       //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
+                                       if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
+                                               if(IS_PLAYER(self.enemy))
+                                                       if(desirabledamage >= 0.1*coredamage)
+                                                               if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
+                                                                       self.BUTTON_ATCK2 = TRUE;
+                               //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
+                               }
+
+                               mine = find(mine, classname, "mine");
+                       }
+                       // if we would be doing at X percent of the core damage, detonate it
+                       // but don't fire a new shot at the same time!
+                       if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
+                               self.BUTTON_ATCK2 = TRUE;
+                       if ((skill > 6.5) && (selfdamage > self.health))
+                               self.BUTTON_ATCK2 = FALSE;
+                       //if(self.BUTTON_ATCK2 == TRUE)
+                       //      dprint(ftos(desirabledamage),"\n");
+                       if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < autocvar_g_balance_minelayer_ammo) // forced reload
+               {
+                       // not if we're holding the minelayer without enough ammo, but can detonate existing mines
+                       if not (W_PlacedMines(FALSE) && self.ammo_rockets < autocvar_g_balance_minelayer_ammo)
+                               weapon_action(self.weapon, WR_RELOAD);
+               }
+               else if (self.BUTTON_ATCK)
+               {
+                       if(weapon_prepareattack(0, autocvar_g_balance_minelayer_refire))
+                       {
+                               W_Mine_Attack();
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minelayer_animtime, w_ready);
+                       }
+               }
+
+               if (self.BUTTON_ATCK2)
+               {
+                       if(W_PlacedMines(TRUE))
+                               sound (self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/flash.md3");
+               precache_model ("models/mine.md3");
+               precache_model ("models/weapons/g_minelayer.md3");
+               precache_model ("models/weapons/v_minelayer.md3");
+               precache_model ("models/weapons/h_minelayer.iqm");
+               precache_sound ("weapons/mine_det.wav");
+               precache_sound ("weapons/mine_fire.wav");
+               precache_sound ("weapons/mine_stick.wav");
+               precache_sound ("weapons/mine_trigger.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_MINE_LAYER);
+               self.current_ammo = ammo_rockets;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               // don't switch while placing a mine
+               if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
+               {
+                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_minelayer_ammo;
+                       ammo_amount += self.(weapon_load[WEP_MINE_LAYER]) >= autocvar_g_balance_minelayer_ammo;
+                       return ammo_amount;
+               }
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               if (W_PlacedMines(FALSE))
+                       return TRUE;
+               else
+                       return FALSE;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.minelayer_mines = 0;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo, autocvar_g_balance_minelayer_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_MINELAYER_SUICIDE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               return WEAPON_MINELAYER_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_minelayer(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 12;
+               pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
+               if(!w_issilent)
+                       sound(self, CH_SHOTS, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/mine_exp.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_minstanex.qc b/qcsrc/common/weapons/w_minstanex.qc
new file mode 100644 (file)
index 0000000..2abb668
--- /dev/null
@@ -0,0 +1,213 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ MINSTANEX,
+/* function  */ w_minstanex,
+/* ammotype  */ IT_CELLS,
+/* impulse   */ 7,
+/* flags     */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* model     */ "minstanex",
+/* shortname */ "minstanex",
+/* fullname  */ _("MinstaNex")
+);
+#else
+#ifdef SVQC
+.float minstanex_lasthit;
+.float jump_interval;
+
+void W_MinstaNex_Attack (void)
+{
+       float flying;
+       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
+
+       W_SetupShot (self, TRUE, 0, "weapons/minstanexfire.wav", CH_WEAPON_A, 10000);
+
+       yoda = 0;
+       damage_goodhits = 0;
+       FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, 10000, 800, 0, 0, 0, 0, WEP_MINSTANEX);
+
+       if(yoda && flying)
+               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
+       if(damage_goodhits && self.minstanex_lasthit)
+       {
+               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE);
+               damage_goodhits = 0; // only every second time
+       }
+
+       self.minstanex_lasthit = damage_goodhits;
+
+       pointparticles(particleeffectnum("nex_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       // teamcolor / hit beam effect
+       vector v;
+       v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
+       switch(self.team)
+       {
+               case NUM_TEAM_1:   // Red
+                       if(damage_goodhits)
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED_HIT"), w_shotorg, v);
+                       else
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED"), w_shotorg, v);
+                       break;
+               case NUM_TEAM_2:   // Blue
+                       if(damage_goodhits)
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE_HIT"), w_shotorg, v);
+                       else
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE"), w_shotorg, v);
+                       break;
+               case NUM_TEAM_3:   // Yellow
+                       if(damage_goodhits)
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW_HIT"), w_shotorg, v);
+                       else
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW"), w_shotorg, v);
+                       break;
+               case NUM_TEAM_4:   // Pink
+                       if(damage_goodhits)
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK_HIT"), w_shotorg, v);
+                       else
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK"), w_shotorg, v);
+                       break;
+               default:
+                       if(damage_goodhits)
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3_HIT"), w_shotorg, v);
+                       else
+                               WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), w_shotorg, v);
+                       break;
+       }
+       
+       W_DecreaseAmmo(ammo_cells, ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo), autocvar_g_balance_minstanex_reload_ammo);
+}
+
+void spawnfunc_weapon_minstanex (void); // defined in t_items.qc
+
+float w_minstanex(float req)
+{
+       float ammo_amount;
+       float minstanex_ammo;
+
+       // now multiple WR_s use this
+       minstanex_ammo = ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo);
+
+       if (req == WR_AIM)
+       {
+               if(self.ammo_cells > 0)
+                       self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE);
+               else
+                       self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
+       }
+       else if (req == WR_THINK)
+       {
+               // if the laser uses load, we also consider its ammo for reloading
+               if(autocvar_g_balance_minstanex_reload_ammo && autocvar_g_balance_minstanex_laser_ammo && self.clip_load < min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else if(autocvar_g_balance_minstanex_reload_ammo && self.clip_load < minstanex_ammo) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else if (self.BUTTON_ATCK)
+               {
+                       if (weapon_prepareattack(0, autocvar_g_balance_minstanex_refire))
+                       {
+                               W_MinstaNex_Attack();
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minstanex_animtime, w_ready);
+                       }
+               }
+               else if (self.BUTTON_ATCK2)
+               {
+                       if (self.jump_interval <= time)
+                       if (weapon_prepareattack(1, -1))
+                       {
+                               // handle refire manually, so that primary and secondary can be fired without conflictions (important for minstagib)
+                               self.jump_interval = time + autocvar_g_balance_minstanex_laser_refire * W_WeaponRateFactor();
+                               
+                               // decrease ammo for the laser?
+                               if(autocvar_g_balance_minstanex_laser_ammo)
+                                       W_DecreaseAmmo(ammo_cells, autocvar_g_balance_minstanex_laser_ammo, autocvar_g_balance_minstanex_reload_ammo);
+
+                               // ugly minstagib hack to reuse the fire mode of the laser
+                               float w;
+                               w = self.weapon;
+                               self.weapon = WEP_LASER;
+                               W_Laser_Shockwave();
+                               self.weapon = w;
+                               
+                               // now do normal refire
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_minstanex_laser_animtime, w_ready);
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/nexflash.md3");
+               precache_model ("models/weapons/g_minstanex.md3");
+               precache_model ("models/weapons/v_minstanex.md3");
+               precache_model ("models/weapons/h_minstanex.iqm");
+               precache_sound ("weapons/minstanexfire.wav");
+               precache_sound ("weapons/nexwhoosh1.wav");
+               precache_sound ("weapons/nexwhoosh2.wav");
+               precache_sound ("weapons/nexwhoosh3.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+               W_Laser(WR_PRECACHE);
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_MINSTANEX);
+               self.current_ammo = ammo_cells;
+               self.minstanex_lasthit = 0;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               ammo_amount = self.ammo_cells >= minstanex_ammo;
+               ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= minstanex_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               if(!autocvar_g_balance_minstanex_laser_ammo)
+                       return TRUE;
+               ammo_amount = self.ammo_cells >= autocvar_g_balance_minstanex_laser_ammo;
+               ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= autocvar_g_balance_minstanex_laser_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.minstanex_lasthit = 0;
+       }
+       else if (req == WR_RELOAD)
+       {
+               float used_ammo;
+               if(autocvar_g_balance_minstanex_laser_ammo)
+                       used_ammo = min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo);
+               else
+                       used_ammo = minstanex_ammo;
+
+               W_Reload(used_ammo, autocvar_g_balance_minstanex_reload_ammo, autocvar_g_balance_minstanex_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_THINKING_WITH_PORTALS;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               return WEAPON_MINSTANEX_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_minstanex(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 6;
+               pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1);
+               if(!w_issilent)
+                       sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/neximpact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_mortar.qc b/qcsrc/common/weapons/w_mortar.qc
new file mode 100644 (file)
index 0000000..8b8a1a0
--- /dev/null
@@ -0,0 +1,414 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ GRENADE_LAUNCHER,
+/* function  */ w_glauncher,
+/* ammotype  */ IT_ROCKETS,
+/* impulse   */ 4,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "gl",
+/* shortname */ "grenadelauncher",
+/* fullname  */ _("Mortar")
+);
+#else
+#ifdef SVQC
+.float gl_detonate_later;
+.float gl_bouncecnt;
+
+void W_Grenade_Explode (void)
+{
+       if(other.takedamage == DAMAGE_AIM)
+               if(IS_PLAYER(other))
+                       if(IsDifferentTeam(self.realowner, other))
+                               if(other.deadflag == DEAD_NO)
+                                       if(IsFlying(other))
+                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE)
+               self.velocity = self.oldvelocity;
+
+       RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_primary_damage, autocvar_g_balance_grenadelauncher_primary_edgedamage, autocvar_g_balance_grenadelauncher_primary_radius, world, world, autocvar_g_balance_grenadelauncher_primary_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void W_Grenade_Explode2 (void)
+{
+       if(other.takedamage == DAMAGE_AIM)
+               if(IS_PLAYER(other))
+                       if(IsDifferentTeam(self.realowner, other))
+                               if(other.deadflag == DEAD_NO)
+                                       if(IsFlying(other))
+                                               Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE)
+               self.velocity = self.oldvelocity;
+
+       RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_secondary_damage, autocvar_g_balance_grenadelauncher_secondary_edgedamage, autocvar_g_balance_grenadelauncher_secondary_radius, world, world, autocvar_g_balance_grenadelauncher_secondary_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void W_Grenade_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+               
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+               
+       self.health = self.health - damage;
+       
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, self.use);
+}
+
+void W_Grenade_Think1 (void)
+{
+       self.nextthink = time;
+       if (time > self.cnt)
+       {
+               other = world;
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               W_Grenade_Explode ();
+               return;
+       }
+       if(self.gl_detonate_later && self.gl_bouncecnt >= autocvar_g_balance_grenadelauncher_primary_remote_minbouncecnt)
+               W_Grenade_Explode();
+}
+
+void W_Grenade_Touch1 (void)
+{
+       PROJECTILE_TOUCH;
+       if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_primary_type == 0) // always explode when hitting a player, or if normal mortar projectile
+       {
+               self.use ();
+       }
+       else if (autocvar_g_balance_grenadelauncher_primary_type == 1) // bounce
+       {
+               float r;
+               r = random() * 6;
+               if(r < 1)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 2)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 3)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 4)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 5)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
+               else
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               self.gl_bouncecnt += 1;
+       }
+       else if(autocvar_g_balance_grenadelauncher_primary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
+       {
+               spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
+
+               // let it stick whereever it is
+               self.oldvelocity = self.velocity;
+               self.velocity = '0 0 0';
+               self.movetype = MOVETYPE_NONE; // also disables gravity
+               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
+               UpdateCSQCProjectile(self);
+
+               // do not respond to any more touches
+               self.solid = SOLID_NOT;
+
+               self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_primary_lifetime_stick);
+       }
+}
+
+void W_Grenade_Touch2 (void)
+{
+       PROJECTILE_TOUCH;
+       if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_secondary_type == 0) // always explode when hitting a player, or if normal mortar projectile
+       {
+               self.use ();
+       }
+       else if (autocvar_g_balance_grenadelauncher_secondary_type == 1) // bounce
+       {
+               float r;
+               r = random() * 6;
+               if(r < 1)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 2)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 3)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 4)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM);
+               else if(r < 5)
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM);
+               else
+                       spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM);
+               self.projectiledeathtype |= HITTYPE_BOUNCE;
+               self.gl_bouncecnt += 1;
+               
+               if (autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce && self.gl_bouncecnt == 1)
+                       self.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce;
+                       
+       }
+       else if(autocvar_g_balance_grenadelauncher_secondary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick
+       {
+               spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM);
+
+               // let it stick whereever it is
+               self.oldvelocity = self.velocity;
+               self.velocity = '0 0 0';
+               self.movetype = MOVETYPE_NONE; // also disables gravity
+               self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO
+               UpdateCSQCProjectile(self);
+
+               // do not respond to any more touches
+               self.solid = SOLID_NOT;
+
+               self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_secondary_lifetime_stick);
+       }
+}
+
+void W_Grenade_Attack (void)
+{
+       entity gren;
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo);
+
+       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_primary_damage);
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = TRUE;
+       gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_primary_damage;
+       gren.movetype = MOVETYPE_BOUNCE;
+       gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor;
+       gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = WEP_GRENADE_LAUNCHER;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-3 -3 -3', '3 3 3');
+
+       gren.cnt = time + autocvar_g_balance_grenadelauncher_primary_lifetime;
+       gren.nextthink = time;
+       gren.think = W_Grenade_Think1;
+       gren.use = W_Grenade_Explode;
+       gren.touch = W_Grenade_Touch1;
+
+       gren.takedamage = DAMAGE_YES;
+       gren.health = autocvar_g_balance_grenadelauncher_primary_health;
+       gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale;
+       gren.event_damage = W_Grenade_Damage;
+       gren.damagedbycontents = TRUE;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary);
+
+       gren.angles = vectoangles (gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       if(autocvar_g_balance_grenadelauncher_primary_type == 0 || autocvar_g_balance_grenadelauncher_primary_type == 2)
+               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
+       else
+               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
+
+       other = gren; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void W_Grenade_Attack2 (void)
+{
+       entity gren;
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_secondary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo);
+
+       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_secondary_damage);
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = TRUE;
+       gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_secondary_damage;
+       gren.movetype = MOVETYPE_BOUNCE;
+       gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor;
+       gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-3 -3 -3', '3 3 3');
+
+       gren.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime;
+       gren.think = adaptor_think2use_hittype_splash;
+       gren.use = W_Grenade_Explode2;
+       gren.touch = W_Grenade_Touch2;
+
+       gren.takedamage = DAMAGE_YES;
+       gren.health = autocvar_g_balance_grenadelauncher_secondary_health;
+       gren.damageforcescale = autocvar_g_balance_grenadelauncher_secondary_damageforcescale;
+       gren.event_damage = W_Grenade_Damage;
+       gren.damagedbycontents = TRUE;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_secondary);
+
+       gren.angles = vectoangles (gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       if(autocvar_g_balance_grenadelauncher_secondary_type == 0 || autocvar_g_balance_grenadelauncher_secondary_type == 2)
+               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
+       else
+               CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE);
+
+       other = gren; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void spawnfunc_weapon_grenadelauncher (void)
+{
+       weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER);
+}
+
+.float bot_secondary_grenademooth;
+float w_glauncher(float req)
+{
+       entity nade;
+       float nadefound;
+       float ammo_amount;
+
+       if (req == WR_AIM)
+       {
+               self.BUTTON_ATCK = FALSE;
+               self.BUTTON_ATCK2 = FALSE;
+               if (self.bot_secondary_grenademooth == 0)
+               {
+                       if(bot_aim(autocvar_g_balance_grenadelauncher_primary_speed, autocvar_g_balance_grenadelauncher_primary_speed_up, autocvar_g_balance_grenadelauncher_primary_lifetime, TRUE))
+                       {
+                               self.BUTTON_ATCK = TRUE;
+                               if(random() < 0.01) self.bot_secondary_grenademooth = 1;
+                       }
+               }
+               else
+               {
+                       if(bot_aim(autocvar_g_balance_grenadelauncher_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_grenadelauncher_secondary_lifetime, TRUE))
+                       {
+                               self.BUTTON_ATCK2 = TRUE;
+                               if(random() < 0.02) self.bot_secondary_grenademooth = 0;
+                       }
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_grenadelauncher_reload_ammo && self.clip_load < min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else if (self.BUTTON_ATCK)
+               {
+                       if (weapon_prepareattack(0, autocvar_g_balance_grenadelauncher_primary_refire))
+                       {
+                               W_Grenade_Attack();
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_grenadelauncher_primary_animtime, w_ready);
+                       }
+               }
+               else if (self.BUTTON_ATCK2)
+               {
+                       if (cvar("g_balance_grenadelauncher_secondary_remote_detonateprimary"))
+                       {
+                               nadefound = 0;
+                               for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self)
+                               {
+                                       if(!nade.gl_detonate_later)
+                                       {
+                                               nade.gl_detonate_later = TRUE;
+                                               nadefound = 1;
+                                       }
+                               }
+                               if(nadefound)
+                                       sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM);
+                       }
+                       else if (weapon_prepareattack(1, autocvar_g_balance_grenadelauncher_secondary_refire))
+                       {
+                               W_Grenade_Attack2();
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_grenadelauncher_secondary_animtime, w_ready);
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_gl.md3");
+               precache_model ("models/weapons/v_gl.md3");
+               precache_model ("models/weapons/h_gl.iqm");
+               precache_sound ("weapons/grenade_bounce1.wav");
+               precache_sound ("weapons/grenade_bounce2.wav");
+               precache_sound ("weapons/grenade_bounce3.wav");
+               precache_sound ("weapons/grenade_bounce4.wav");
+               precache_sound ("weapons/grenade_bounce5.wav");
+               precache_sound ("weapons/grenade_bounce6.wav");
+               precache_sound ("weapons/grenade_stick.wav");
+               precache_sound ("weapons/grenade_fire.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_GRENADE_LAUNCHER);
+               self.current_ammo = ammo_rockets;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_primary_ammo;
+               ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_primary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_secondary_ammo;
+               ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_secondary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo), autocvar_g_balance_grenadelauncher_reload_ammo, autocvar_g_balance_grenadelauncher_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_MORTAR_SUICIDE_BOUNCE;
+               else
+                       return WEAPON_MORTAR_SUICIDE_EXPLODE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_MORTAR_MURDER_BOUNCE;
+               else
+                       return WEAPON_MORTAR_MURDER_EXPLODE;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_glauncher(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 12;
+               pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1);
+               if(!w_issilent)
+                       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/grenade_impact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_nex.qc b/qcsrc/common/weapons/w_nex.qc
new file mode 100644 (file)
index 0000000..dc3c30f
--- /dev/null
@@ -0,0 +1,275 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ NEX,
+/* function  */ w_nex,
+/* ammotype  */ IT_CELLS,
+/* impulse   */ 7,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_HIGH,
+/* model     */ "nex",
+/* shortname */ "nex",
+/* fullname  */ _("Nex")
+);
+#else
+#ifdef SVQC
+
+void SendCSQCNexBeamParticle(float charge) {
+       vector v;
+       v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
+       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
+       WriteByte(MSG_BROADCAST, TE_CSQC_NEXGUNBEAMPARTICLE);
+       WriteCoord(MSG_BROADCAST, w_shotorg_x);
+       WriteCoord(MSG_BROADCAST, w_shotorg_y);
+       WriteCoord(MSG_BROADCAST, w_shotorg_z);
+       WriteCoord(MSG_BROADCAST, v_x);
+       WriteCoord(MSG_BROADCAST, v_y);
+       WriteCoord(MSG_BROADCAST, v_z);
+       WriteByte(MSG_BROADCAST, bound(0, 255 * charge, 255));
+}
+
+void W_Nex_Attack (float issecondary)
+{
+       float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge;
+       if(issecondary)
+       {
+               mydmg = autocvar_g_balance_nex_secondary_damage;
+               myforce = autocvar_g_balance_nex_secondary_force;
+               mymindist = autocvar_g_balance_nex_secondary_damagefalloff_mindist;
+               mymaxdist = autocvar_g_balance_nex_secondary_damagefalloff_maxdist;
+               myhalflife = autocvar_g_balance_nex_secondary_damagefalloff_halflife;
+               myforcehalflife = autocvar_g_balance_nex_secondary_damagefalloff_forcehalflife;
+               myammo = autocvar_g_balance_nex_secondary_ammo;
+       }
+       else
+       {
+               mydmg = autocvar_g_balance_nex_primary_damage;
+               myforce = autocvar_g_balance_nex_primary_force;
+               mymindist = autocvar_g_balance_nex_primary_damagefalloff_mindist;
+               mymaxdist = autocvar_g_balance_nex_primary_damagefalloff_maxdist;
+               myhalflife = autocvar_g_balance_nex_primary_damagefalloff_halflife;
+               myforcehalflife = autocvar_g_balance_nex_primary_damagefalloff_forcehalflife;
+               myammo = autocvar_g_balance_nex_primary_ammo;
+       }
+
+       float flying;
+       flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last
+
+       if(autocvar_g_balance_nex_charge)
+       {
+               charge = autocvar_g_balance_nex_charge_mindmg / mydmg + (1 - autocvar_g_balance_nex_charge_mindmg / mydmg) * self.nex_charge;
+               self.nex_charge *= autocvar_g_balance_nex_charge_shot_multiplier; // do this AFTER setting mydmg/myforce
+               // O RLY? -- divVerent
+               // YA RLY -- FruitieX
+       }
+       else
+               charge = 1;
+       mydmg *= charge;
+       myforce *= charge;
+
+       W_SetupShot (self, TRUE, 5, "weapons/nexfire.wav", CH_WEAPON_A, mydmg);
+       if(charge > autocvar_g_balance_nex_charge_animlimit && autocvar_g_balance_nex_charge_animlimit) // if the Nex is overcharged, we play an extra sound
+       {
+               sound (self, CH_WEAPON_B, "weapons/nexcharge.wav", VOL_BASE * (charge - 0.5 * autocvar_g_balance_nex_charge_animlimit) / (1 - 0.5 * autocvar_g_balance_nex_charge_animlimit), ATTN_NORM);
+       }
+
+       yoda = 0;
+       FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_NEX);
+
+       if(yoda && flying)
+               Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); 
+
+       //beam and muzzle flash done on client
+       SendCSQCNexBeamParticle(charge);
+
+       W_DecreaseAmmo(ammo_cells, myammo, autocvar_g_balance_nex_reload_ammo);
+}
+
+void spawnfunc_weapon_nex (void); // defined in t_items.qc
+
+.float nex_chargepool_pauseregen_finished;
+float w_nex(float req)
+{
+       float dt;
+       float ammo_amount;
+       if (req == WR_AIM)
+       {
+               if(bot_aim(1000000, 0, 1, FALSE))
+                       self.BUTTON_ATCK = TRUE;
+               else
+               {
+                       if(autocvar_g_balance_nex_charge)
+                               self.BUTTON_ATCK2 = TRUE;
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_nex_charge && self.nex_charge < autocvar_g_balance_nex_charge_limit)
+                       self.nex_charge = min(1, self.nex_charge + autocvar_g_balance_nex_charge_rate * frametime / W_TICSPERFRAME);
+
+               if(autocvar_g_balance_nex_secondary_chargepool)
+                       if(self.nex_chargepool_ammo < 1)
+                       {
+                               if(self.nex_chargepool_pauseregen_finished < time)
+                                       self.nex_chargepool_ammo = min(1, self.nex_chargepool_ammo + autocvar_g_balance_nex_secondary_chargepool_regen * frametime / W_TICSPERFRAME);
+                               self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_nex_secondary_chargepool_pause_health_regen);
+                       }
+
+               if(autocvar_g_balance_nex_reload_ammo && self.clip_load < min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+               else
+               {
+                       if (self.BUTTON_ATCK)
+                       {
+                               if (weapon_prepareattack(0, autocvar_g_balance_nex_primary_refire))
+                               {
+                                       W_Nex_Attack(0);
+                                       weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_primary_animtime, w_ready);
+                               }
+                       }
+                       if ((autocvar_g_balance_nex_secondary_charge && !autocvar_g_balance_nex_secondary) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2)
+                       {
+                               if(autocvar_g_balance_nex_secondary_charge)
+                               {
+                                       self.nex_charge_rottime = time + autocvar_g_balance_nex_charge_rot_pause;
+                                       dt = frametime / W_TICSPERFRAME;
+
+                                       if(self.nex_charge < 1)
+                                       {
+                                               if(autocvar_g_balance_nex_secondary_chargepool)
+                                               {
+                                                       if(autocvar_g_balance_nex_secondary_ammo)
+                                                       {
+                                                               // always deplete if secondary is held
+                                                               self.nex_chargepool_ammo = max(0, self.nex_chargepool_ammo - autocvar_g_balance_nex_secondary_ammo * dt);
+
+                                                               dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate);
+                                                               self.nex_chargepool_pauseregen_finished = time + autocvar_g_balance_nex_secondary_chargepool_pause_regen;
+                                                               dt = min(dt, self.nex_chargepool_ammo);
+                                                               dt = max(0, dt);
+
+                                                               self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate;
+                                                       }
+                                               }
+
+                                               else if(autocvar_g_balance_nex_secondary_ammo)
+                                               {
+                                                       if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed
+                                                       {
+                                                               dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate);
+                                                               if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+                                                               {
+                                                                       // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
+                                                                       if(autocvar_g_balance_nex_reload_ammo)
+                                                                       {
+                                                                               dt = min(dt, (self.clip_load - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo);
+                                                                               dt = max(0, dt);
+                                                                               if(dt > 0)
+                                                                               {
+                                                                                       self.clip_load = max(autocvar_g_balance_nex_secondary_ammo, self.clip_load - autocvar_g_balance_nex_secondary_ammo * dt);
+                                                                               }
+                                                                               self.(weapon_load[WEP_NEX]) = self.clip_load;
+                                                                       }
+                                                                       else
+                                                                       {
+                                                                               dt = min(dt, (self.ammo_cells - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo);
+                                                                               dt = max(0, dt);
+                                                                               if(dt > 0)
+                                                                               {
+                                                                                       self.ammo_cells = max(autocvar_g_balance_nex_secondary_ammo, self.ammo_cells - autocvar_g_balance_nex_secondary_ammo * dt);
+                                                                               }
+                                                                       }
+                                                               }
+                                                               self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate;
+                                                       }
+                                               }
+
+                                               else
+                                               {
+                                                       dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate);
+                                                       self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate;
+                                               }
+                                       }
+                               }
+                               else if(autocvar_g_balance_nex_secondary)
+                               {
+                                       if (weapon_prepareattack(0, autocvar_g_balance_nex_secondary_refire))
+                                       {
+                                               W_Nex_Attack(1);
+                                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_secondary_animtime, w_ready);
+                                       }
+                               }
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/nexflash.md3");
+               precache_model ("models/weapons/g_nex.md3");
+               precache_model ("models/weapons/v_nex.md3");
+               precache_model ("models/weapons/h_nex.iqm");
+               precache_sound ("weapons/nexfire.wav");
+               precache_sound ("weapons/nexcharge.wav");
+               precache_sound ("weapons/nexwhoosh1.wav");
+               precache_sound ("weapons/nexwhoosh2.wav");
+               precache_sound ("weapons/nexwhoosh3.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_NEX);
+               self.current_ammo = ammo_cells;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_primary_ammo;
+               ammo_amount += (autocvar_g_balance_nex_reload_ammo && self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_primary_ammo);
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               if(autocvar_g_balance_nex_secondary)
+               {
+                       // don't allow charging if we don't have enough ammo
+                       ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_secondary_ammo;
+                       ammo_amount += self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_secondary_ammo;    
+                       return ammo_amount;
+               }
+               else
+               {
+                       return FALSE; // zoom is not a fire mode
+               }
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo), autocvar_g_balance_nex_reload_ammo, autocvar_g_balance_nex_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_THINKING_WITH_PORTALS;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               return WEAPON_NEX_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_nex(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 6;
+               pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1);
+               if(!w_issilent)
+                       sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM);
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/neximpact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_porto.qc b/qcsrc/common/weapons/w_porto.qc
new file mode 100644 (file)
index 0000000..fad480d
--- /dev/null
@@ -0,0 +1,391 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ PORTO,
+/* function  */ w_porto,
+/* ammotype  */ 0,
+/* impulse   */ 0,
+/* flags     */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON,
+/* rating    */ 0,
+/* model     */ "porto" ,
+/* shortname */ "porto",
+/* fullname  */ _("Port-O-Launch")
+);
+#else
+#ifdef SVQC
+.entity porto_current;
+.vector porto_v_angle; // holds "held" view angles
+.float porto_v_angle_held;
+.vector right_vector;
+
+void W_Porto_Success (void)
+{
+       if(self.realowner == world)
+       {
+               objerror("Cannot succeed successfully: no owner\n");
+               return;
+       }
+
+       self.realowner.porto_current = world;
+       remove(self);
+}
+
+string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo);
+void W_Porto_Fail (float failhard)
+{
+       if(self.realowner == world)
+       {
+               objerror("Cannot fail successfully: no owner\n");
+               return;
+       }
+
+       // no portals here!
+       if(self.cnt < 0)
+       {
+               Portal_ClearWithID(self.realowner, self.portal_id);
+       }
+
+       self.realowner.porto_current = world;
+
+       if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !WEPSET_CONTAINS_EW(self.realowner, WEP_PORTO))
+       {
+               setsize (self, '-16 -16 0', '16 16 32');
+               setorigin(self, self.origin + trace_plane_normal);
+               if(move_out_of_solid(self))
+               {
+                       self.flags = FL_ITEM;
+                       self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128);
+                       tracetoss(self, self);
+                       if(vlen(trace_endpos - self.realowner.origin) < 128)
+                       {
+                               W_ThrowNewWeapon(self.realowner, WEP_PORTO, 0, self.origin, self.velocity);
+                               centerprint(self.realowner, "^1Portal deployment failed.\n\n^2Catch it to try again!");
+                       }
+               }
+       }
+       remove(self);
+}
+
+void W_Porto_Remove (entity p)
+{
+       if(p.porto_current.realowner == p && p.porto_current.classname == "porto")
+       {
+               entity oldself;
+               oldself = self;
+               self = p.porto_current;
+               W_Porto_Fail(1);
+               self = oldself;
+       }
+}
+
+void W_Porto_Think (void)
+{
+       trace_plane_normal = '0 0 0';
+       if(self.realowner.playerid != self.playerid)
+               remove(self);
+       else
+               W_Porto_Fail(0);
+}
+
+void W_Porto_Touch (void)
+{
+       vector norm;
+
+       // do not use PROJECTILE_TOUCH here
+       // FIXME but DO handle warpzones!
+
+       if(other.classname == "portal")
+               return; // handled by the portal
+
+       norm = trace_plane_normal;
+       if(trace_ent.iscreature)
+       {
+               traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_z, MOVE_WORLDONLY, self);
+               if(trace_fraction >= 1)
+                       return;
+               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
+                       return;
+               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+                       return;
+       }
+
+       if(self.realowner.playerid != self.playerid)
+       {
+               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+               remove(self);
+       }
+       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP)
+       {
+               spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTN_NORM);
+               // just reflect
+               self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal);
+               self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal));
+       }
+       else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+       {
+               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+               W_Porto_Fail(0);
+               if(self.cnt < 0)
+                       Portal_ClearAll_PortalsOnly(self.realowner);
+       }
+       else if(self.cnt == 0)
+       {
+               // in-portal only
+               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       centerprint(self.realowner, "^1In^7-portal created.");
+                       W_Porto_Success();
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       W_Porto_Fail(0);
+               }
+       }
+       else if(self.cnt == 1)
+       {
+               // out-portal only
+               if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       centerprint(self.realowner, "^4Out^7-portal created.");
+                       W_Porto_Success();
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       W_Porto_Fail(0);
+               }
+       }
+       else if(self.effects & EF_RED)
+       {
+               self.effects += EF_BLUE - EF_RED;
+               if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+               {
+                       sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       centerprint(self.realowner, "^1In^7-portal created.");
+                       self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm);
+                       self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm));
+                       CSQCProjectile(self, TRUE, PROJECTILE_PORTO_BLUE, TRUE); // change type
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                       trace_plane_normal = norm;
+                       Portal_ClearAll_PortalsOnly(self.realowner);
+                       W_Porto_Fail(0);
+               }
+       }
+       else
+       {
+               if(self.realowner.portal_in.portal_id == self.portal_id)
+               {
+                       if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id))
+                       {
+                               sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM);
+                               trace_plane_normal = norm;
+                               centerprint(self.realowner, "^4Out^7-portal created.");
+                               W_Porto_Success();
+                       }
+                       else
+                       {
+                               sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                               Portal_ClearAll_PortalsOnly(self.realowner);
+                               W_Porto_Fail(0);
+                       }
+               }
+               else
+               {
+                       sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM);
+                       Portal_ClearAll_PortalsOnly(self.realowner);
+                       W_Porto_Fail(0);
+               }
+       }
+}
+
+void W_Porto_Attack (float type)
+{
+       entity gren;
+
+       W_SetupShot (self, FALSE, 4, "porto/fire.wav", CH_WEAPON_A, 0);
+       // always shoot from the eye
+       w_shotdir = v_forward;
+       w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
+
+       //pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       gren = spawn ();
+       gren.cnt = type;
+       gren.owner = gren.realowner = self;
+       gren.playerid = self.playerid;
+       gren.classname = "porto";
+       gren.bot_dodge = TRUE;
+       gren.bot_dodgerating = 200;
+       gren.movetype = MOVETYPE_BOUNCEMISSILE;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.effects = EF_RED;
+       gren.scale = 4;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '0 0 0', '0 0 0');
+
+       if(type > 0)
+               gren.nextthink = time + autocvar_g_balance_porto_secondary_lifetime;
+       else
+               gren.nextthink = time + autocvar_g_balance_porto_primary_lifetime;
+       gren.think = W_Porto_Think;
+       gren.touch = W_Porto_Touch;
+
+       if(type > 0)
+       {
+               if(self.items & IT_STRENGTH)
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed * autocvar_g_balance_powerup_strength_force, 0);
+               else
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed, 0);
+       }
+       else
+       {
+               if(self.items & IT_STRENGTH)
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed * autocvar_g_balance_powerup_strength_force, 0);
+               else
+                       W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed, 0);
+       }
+
+       gren.angles = vectoangles (gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       gren.portal_id = time;
+       self.porto_current = gren;
+       gren.playerid = self.playerid;
+       fixedmakevectors(fixedvectoangles(gren.velocity));
+       gren.right_vector = v_right;
+
+       gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP;
+
+       if(type > 0)
+               CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_BLUE, TRUE);
+       else
+               CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE);
+
+       other = gren; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+void spawnfunc_weapon_porto (void)
+{
+       weapon_defaultspawnfunc(WEP_PORTO);
+}
+
+float w_nexball_weapon(float req);
+float w_porto(float req)
+{
+       //vector v_angle_save;
+
+       if (g_nexball) { return w_nexball_weapon(req); }
+       if (req == WR_AIM)
+       {
+               self.BUTTON_ATCK = FALSE;
+               self.BUTTON_ATCK2 = FALSE;
+               if(!autocvar_g_balance_porto_secondary)
+                       if(bot_aim(autocvar_g_balance_porto_primary_speed, 0, autocvar_g_balance_grenadelauncher_primary_lifetime, FALSE))
+                               self.BUTTON_ATCK = TRUE;
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_porto_secondary)
+               {
+                       if (self.BUTTON_ATCK)
+                       if (!self.porto_current)
+                       if (!self.porto_forbidden)
+                       if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire))
+                       {
+                               W_Porto_Attack(0);
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready);
+                       }
+
+                       if (self.BUTTON_ATCK2)
+                       if (!self.porto_current)
+                       if (!self.porto_forbidden)
+                       if (weapon_prepareattack(1, autocvar_g_balance_porto_secondary_refire))
+                       {
+                               W_Porto_Attack(1);
+                               weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_porto_secondary_animtime, w_ready);
+                       }
+               }
+               else
+               {
+                       if(self.porto_v_angle_held)
+                       {
+                               if(!self.BUTTON_ATCK2)
+                               {
+                                       self.porto_v_angle_held = 0;
+
+                                       ClientData_Touch(self);
+                               }
+                       }
+                       else
+                       {
+                               if(self.BUTTON_ATCK2)
+                               {
+                                       self.porto_v_angle = self.v_angle;
+                                       self.porto_v_angle_held = 1;
+
+                                       ClientData_Touch(self);
+                               }
+                       }
+                       if(self.porto_v_angle_held)
+                               makevectors(self.porto_v_angle); // override the previously set angles
+
+                       if (self.BUTTON_ATCK)
+                       if (!self.porto_current)
+                       if (!self.porto_forbidden)
+                       if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire))
+                       {
+                               W_Porto_Attack(-1);
+                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready);
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_porto.md3");
+               precache_model ("models/weapons/v_porto.md3");
+               precache_model ("models/weapons/h_porto.iqm");
+               precache_model ("models/portal.md3");
+               precache_sound ("porto/bounce.wav");
+               precache_sound ("porto/create.wav");
+               precache_sound ("porto/expire.wav");
+               precache_sound ("porto/explode.wav");
+               precache_sound ("porto/fire.wav");
+               precache_sound ("porto/unsupported.wav");
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_PORTO);
+               self.current_ammo = ammo_none;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.porto_current = world;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_porto(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               print("Since when does Porto send DamageInfo?\n");
+       }
+       else if(req == WR_PRECACHE)
+       {
+               // nothing to do
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_rifle.qc b/qcsrc/common/weapons/w_rifle.qc
new file mode 100644 (file)
index 0000000..8ed4491
--- /dev/null
@@ -0,0 +1,265 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ RIFLE,
+/* function  */ w_rifle,
+/* ammotype  */ IT_NAILS,
+/* impulse   */ 7,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "campingrifle",
+/* shortname */ "rifle",
+/* fullname  */ _("Rifle")
+);
+#else
+#ifdef SVQC
+
+.float rifle_accumulator;
+
+void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSpeed, float pLifetime, float pAmmo, float deathtype, float pBulletConstant, float pTracer, float pShots, string pSound)
+{
+       float i;
+
+       W_DecreaseAmmo(ammo_nails, pAmmo, autocvar_g_balance_rifle_reload_ammo);
+
+       W_SetupShot (self, autocvar_g_antilag_bullets && pSpeed >= autocvar_g_antilag_bullets, 2, pSound, CH_WEAPON_A, pDamage * pShots);
+
+       pointparticles(particleeffectnum("rifle_muzzleflash"), w_shotorg, w_shotdir * 2000, 1);
+
+       if(self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) // if zoomed, shoot from the eye
+       {
+               w_shotdir = v_forward;
+               w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward;
+       }
+
+       for(i = 0; i < pShots; ++i)
+               fireBallisticBullet(w_shotorg, w_shotdir, pSpread, pSpeed, pLifetime, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE), 1, pBulletConstant);
+       endFireBallisticBullet();
+
+       if (autocvar_g_casings >= 2)
+               SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self);
+}
+
+void W_Rifle_Attack()
+{
+       W_Rifle_FireBullet(autocvar_g_balance_rifle_primary_spread, autocvar_g_balance_rifle_primary_damage, autocvar_g_balance_rifle_primary_force, autocvar_g_balance_rifle_primary_speed, autocvar_g_balance_rifle_primary_lifetime, autocvar_g_balance_rifle_primary_ammo, WEP_RIFLE, autocvar_g_balance_rifle_primary_bulletconstant, autocvar_g_balance_rifle_primary_tracer, autocvar_g_balance_rifle_primary_shots, "weapons/campingrifle_fire.wav");
+}
+
+void W_Rifle_Attack2()
+{
+       W_Rifle_FireBullet(autocvar_g_balance_rifle_secondary_spread, autocvar_g_balance_rifle_secondary_damage, autocvar_g_balance_rifle_secondary_force, autocvar_g_balance_rifle_secondary_speed, autocvar_g_balance_rifle_secondary_lifetime, autocvar_g_balance_rifle_secondary_ammo, WEP_RIFLE | HITTYPE_SECONDARY, autocvar_g_balance_rifle_secondary_bulletconstant, autocvar_g_balance_rifle_secondary_tracer, autocvar_g_balance_rifle_secondary_shots, "weapons/campingrifle_fire2.wav");
+}
+
+void spawnfunc_weapon_rifle (void)
+{
+       weapon_defaultspawnfunc(WEP_RIFLE);
+}
+
+// compatibility alias
+void spawnfunc_weapon_campingrifle (void)
+{
+       spawnfunc_weapon_rifle();
+}
+void spawnfunc_weapon_sniperrifle (void)
+{
+       spawnfunc_weapon_rifle();
+}
+
+.void(void) rifle_bullethail_attackfunc;
+.float rifle_bullethail_frame;
+.float rifle_bullethail_animtime;
+.float rifle_bullethail_refire;
+void W_Rifle_BulletHail_Continue()
+{
+       float r, sw, af;
+
+       sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing
+       af = ATTACK_FINISHED(self);
+       self.switchweapon = self.weapon;
+       ATTACK_FINISHED(self) = time;
+       print(ftos(self.ammo_nails), "\n");
+       r = weapon_prepareattack(self.rifle_bullethail_frame == WFRAME_FIRE2, self.rifle_bullethail_refire);
+       if(self.switchweapon == self.weapon)
+               self.switchweapon = sw;
+       if(r)
+       {
+               self.rifle_bullethail_attackfunc();
+               weapon_thinkf(self.rifle_bullethail_frame, self.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue);
+               print("thinkf set\n");
+       }
+       else
+       {
+               ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time
+               print("out of ammo... ", ftos(self.weaponentity.state), "\n");
+       }
+}
+
+void W_Rifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire)
+{
+       // if we get here, we have at least one bullet to fire
+       AttackFunc();
+       if(mode)
+       {
+               // continue hail
+               self.rifle_bullethail_attackfunc = AttackFunc;
+               self.rifle_bullethail_frame = fr;
+               self.rifle_bullethail_animtime = animtime;
+               self.rifle_bullethail_refire = refire;
+               weapon_thinkf(fr, animtime, W_Rifle_BulletHail_Continue);
+       }
+       else
+       {
+               // just one shot
+               weapon_thinkf(fr, animtime, w_ready);
+       }
+}
+
+.float bot_secondary_riflemooth;
+float w_rifle(float req)
+{
+       float ammo_amount;
+
+       if (req == WR_AIM)
+       {
+               self.BUTTON_ATCK=FALSE;
+               self.BUTTON_ATCK2=FALSE;
+               if(vlen(self.origin-self.enemy.origin) > 1000)
+                       self.bot_secondary_riflemooth = 0;
+               if(self.bot_secondary_riflemooth == 0)
+               {
+                       if(bot_aim(autocvar_g_balance_rifle_primary_speed, 0, autocvar_g_balance_rifle_primary_lifetime, FALSE))
+                       {
+                               self.BUTTON_ATCK = TRUE;
+                               if(random() < 0.01) self.bot_secondary_riflemooth = 1;
+                       }
+               }
+               else
+               {
+                       if(bot_aim(autocvar_g_balance_rifle_secondary_speed, 0, autocvar_g_balance_rifle_secondary_lifetime, FALSE))
+                       {
+                               self.BUTTON_ATCK2 = TRUE;
+                               if(random() < 0.03) self.bot_secondary_riflemooth = 0;
+                       }
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo)) // forced reload
+            weapon_action(self.weapon, WR_RELOAD);
+               else
+               {
+                       self.rifle_accumulator = bound(time - autocvar_g_balance_rifle_bursttime, self.rifle_accumulator, time);
+                       if (self.BUTTON_ATCK)
+                       if (weapon_prepareattack_check(0, autocvar_g_balance_rifle_primary_refire))
+                       if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_primary_burstcost)
+                       {
+                               weapon_prepareattack_do(0, autocvar_g_balance_rifle_primary_refire);
+                               W_Rifle_BulletHail(autocvar_g_balance_rifle_primary_bullethail, W_Rifle_Attack, WFRAME_FIRE1, autocvar_g_balance_rifle_primary_animtime, autocvar_g_balance_rifle_primary_refire);
+                               self.rifle_accumulator += autocvar_g_balance_rifle_primary_burstcost;
+                       }
+                       if (self.BUTTON_ATCK2)
+                       {
+                               if (autocvar_g_balance_rifle_secondary)
+                               {
+                    if(autocvar_g_balance_rifle_secondary_reload)
+                        weapon_action(self.weapon, WR_RELOAD);
+                    else
+                    {
+                        if (weapon_prepareattack_check(1, autocvar_g_balance_rifle_secondary_refire))
+                        if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_secondary_burstcost)
+                        {
+                            weapon_prepareattack_do(1, autocvar_g_balance_rifle_secondary_refire);
+                            W_Rifle_BulletHail(autocvar_g_balance_rifle_secondary_bullethail, W_Rifle_Attack2, WFRAME_FIRE2, autocvar_g_balance_rifle_secondary_animtime, autocvar_g_balance_rifle_primary_refire);
+                            self.rifle_accumulator += autocvar_g_balance_rifle_secondary_burstcost;
+                        }
+                    }
+                               }
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_campingrifle.md3");
+               precache_model ("models/weapons/v_campingrifle.md3");
+               precache_model ("models/weapons/h_campingrifle.iqm");
+               precache_sound ("weapons/campingrifle_fire.wav");
+               precache_sound ("weapons/campingrifle_fire2.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_RIFLE);
+               self.current_ammo = ammo_nails;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_primary_ammo;
+               ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_primary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_secondary_ammo;
+               ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_secondary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_RESETPLAYER)
+       {
+               self.rifle_accumulator = time - autocvar_g_balance_rifle_bursttime;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo), autocvar_g_balance_rifle_reload_ammo, autocvar_g_balance_rifle_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_THINKING_WITH_PORTALS;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+               {
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                               return WEAPON_RIFLE_MURDER_HAIL_PIERCING;
+                       else
+                               return WEAPON_RIFLE_MURDER_HAIL;
+               }
+               else
+               {
+                       if(w_deathtype & HITTYPE_BOUNCE)
+                               return WEAPON_RIFLE_MURDER_PIERCING;
+                       else
+                               return WEAPON_RIFLE_MURDER;
+               }
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_rifle(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 2;
+               pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1);
+               if(!w_issilent)
+               {
+                       if(w_random < 0.2)
+                               sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
+                       else if(w_random < 0.4)
+                               sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
+                       else if(w_random < 0.5)
+                               sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/ric1.wav");
+               precache_sound("weapons/ric2.wav");
+               precache_sound("weapons/ric3.wav");
+       }
+
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_seeker.qc b/qcsrc/common/weapons/w_seeker.qc
new file mode 100644 (file)
index 0000000..1683a37
--- /dev/null
@@ -0,0 +1,713 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ SEEKER,
+/* function  */ w_seeker,
+/* ammotype  */ IT_ROCKETS,
+/* impulse   */ 8,
+/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "seeker",
+/* shortname */ "seeker",
+/* fullname  */ _("T.A.G. Seeker")
+);
+#else
+#ifdef SVQC
+//.float proxytime; = autoswitch
+//.float tl; = wait
+.entity tag_target, wps_tag_tracker;
+.float tag_time;
+
+// ============================
+// Begin: Missile functions, these are general functions to be manipulated by other code
+// ============================
+void Seeker_Missile_Explode ()
+{
+       self.event_damage = func_null;
+       RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_missile_damage, autocvar_g_balance_seeker_missile_edgedamage, autocvar_g_balance_seeker_missile_radius, world, world, autocvar_g_balance_seeker_missile_force, self.projectiledeathtype, other);
+
+
+       remove (self);
+}
+
+void Seeker_Missile_Touch()
+{
+       PROJECTILE_TOUCH;
+
+       Seeker_Missile_Explode();
+}
+
+void Seeker_Missile_Think()
+{
+       entity e;
+       vector desireddir, olddir, newdir, eorg;
+       float turnrate;
+       float dist;
+       float spd;
+
+       if (time > self.cnt)
+       {
+               self.projectiledeathtype |= HITTYPE_SPLASH;
+               Seeker_Missile_Explode();
+       }
+
+       spd = vlen(self.velocity);
+       spd = bound(
+               spd - autocvar_g_balance_seeker_missile_decel * frametime,
+               autocvar_g_balance_seeker_missile_speed_max,
+               spd + autocvar_g_balance_seeker_missile_accel * frametime
+       );
+
+       if (self.enemy != world)
+               if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+                       self.enemy = world;
+
+       if (self.enemy != world)
+       {
+               e               = self.enemy;
+               eorg            = 0.5 * (e.absmin + e.absmax);
+               turnrate        = autocvar_g_balance_seeker_missile_turnrate; // how fast to turn
+               desireddir      = normalize(eorg - self.origin);
+               olddir          = normalize(self.velocity); // get my current direction
+               dist            = vlen(eorg - self.origin);
+
+               // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
+               if (autocvar_g_balance_seeker_missile_smart && (dist > autocvar_g_balance_seeker_missile_smart_mindist))
+               {
+                       // Is it a better idea (shorter distance) to trace to the target itself?
+                       if ( vlen(self.origin + olddir * self.wait) < dist)
+                               traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
+                       else
+                               traceline(self.origin, eorg, FALSE, self);
+
+                       // Setup adaptive tracelength
+                       self.wait = bound(autocvar_g_balance_seeker_missile_smart_trace_min, vlen(self.origin - trace_endpos), self.wait = autocvar_g_balance_seeker_missile_smart_trace_max);
+
+                       // Calc how important it is that we turn and add this to the desierd (enemy) dir.
+                       desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
+               }
+               
+               newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
+               self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
+       }
+       else
+               dist = 0;
+
+       // Proxy
+       if (autocvar_g_balance_seeker_missile_proxy)
+       {
+               if ( dist <= autocvar_g_balance_seeker_missile_proxy_maxrange)
+               {
+                       if (self.autoswitch == 0)
+                       {
+                               self.autoswitch = time + autocvar_g_balance_seeker_missile_proxy_delay;
+                       }
+                       else
+                       {
+                               if (self.autoswitch <= time)
+                               {
+                                       Seeker_Missile_Explode();
+                                       self.autoswitch = 0;
+                               }
+                       }
+               }
+               else
+               {
+                       if (self.autoswitch != 0)
+                               self.autoswitch = 0;
+               }
+       }
+       ///////////////
+
+       if (self.enemy.deadflag != DEAD_NO)
+       {
+               self.enemy = world;
+               self.cnt = time + 1 + (random() * 4);
+               self.nextthink = self.cnt;
+               return;
+       }
+
+       //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
+       self.nextthink = time;// + 0.05; // csqc projectiles
+       UpdateCSQCProjectile(self);
+}
+
+
+
+void Seeker_Missile_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+               
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+
+       if (self.realowner == attacker)
+               self.health = self.health - (damage * 0.25);
+       else
+               self.health = self.health - damage;
+               
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, Seeker_Missile_Explode);
+}
+
+/*
+void Seeker_Missile_Animate()
+{
+       self.frame = self.frame +1;
+       self.nextthink = time + 0.05;
+
+       if (self.enemy != world)
+               if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
+                       self.enemy = world;
+
+       if(self.frame == 5)
+       {
+               self.think           = Seeker_Missile_Think;
+               self.nextthink       = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles
+
+               if (autocvar_g_balance_seeker_missile_proxy)
+                       self.movetype    = MOVETYPE_BOUNCEMISSILE;
+               else
+                       self.movetype    = MOVETYPE_FLYMISSILE;
+       }
+
+       UpdateCSQCProjectile(self);
+}
+*/
+
+void Seeker_Fire_Missile(vector f_diff, entity m_target)
+{
+       entity missile;
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_reload_ammo);
+
+       makevectors(self.v_angle);
+       W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/seeker_fire.wav", CH_WEAPON_A, 0);
+       w_shotorg += f_diff;
+       pointparticles(particleeffectnum("seeker_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       //self.detornator         = FALSE;
+
+       missile                 = spawn();
+       missile.owner           = missile.realowner = self;
+       missile.classname       = "seeker_missile";
+       missile.bot_dodge       = TRUE;
+       missile.bot_dodgerating = autocvar_g_balance_seeker_missile_damage;
+
+       missile.think           = Seeker_Missile_Think;
+       missile.touch           = Seeker_Missile_Touch;
+       missile.event_damage    = Seeker_Missile_Damage;
+       missile.nextthink       = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay");
+       missile.cnt             = time + autocvar_g_balance_seeker_missile_lifetime;
+       missile.enemy           = m_target;
+       missile.solid           = SOLID_BBOX;
+       missile.scale           = 2;
+       missile.takedamage      = DAMAGE_YES;
+       missile.health          = autocvar_g_balance_seeker_missile_health;
+       missile.damageforcescale = autocvar_g_balance_seeker_missile_damageforcescale;
+       missile.damagedbycontents = TRUE;
+       //missile.think           = Seeker_Missile_Animate; // csqc projectiles.
+       
+       if (missile.enemy != world)
+               missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY;
+       else 
+               missile.projectiledeathtype = WEP_SEEKER;
+
+
+       setorigin (missile, w_shotorg);
+       setsize (missile, '-4 -4 -4', '4 4 4');
+       missile.movetype    = MOVETYPE_FLYMISSILE;
+       missile.flags       = FL_PROJECTILE;
+       missile.missile_flags = MIF_SPLASH | MIF_GUIDED_TAG;
+       
+       W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_missile);
+
+       missile.angles = vectoangles (missile.velocity);
+
+       CSQCProjectile(missile, FALSE, PROJECTILE_SEEKER, TRUE);
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+// ============================
+// Begin: FLAC, close range attack meant for defeating rockets which are coming at you. 
+// ============================
+void Seeker_Flac_Explode ()
+{
+       self.event_damage = func_null;
+
+       RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_flac_damage, autocvar_g_balance_seeker_flac_edgedamage, autocvar_g_balance_seeker_flac_radius, world, world, autocvar_g_balance_seeker_flac_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void Seeker_Flac_Touch()
+{
+       PROJECTILE_TOUCH;
+
+       Seeker_Flac_Explode();
+}
+
+void Seeker_Fire_Flac()
+{
+       entity missile;
+       vector f_diff;
+       float c;
+
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_flac_ammo, autocvar_g_balance_seeker_reload_ammo);
+
+       c = mod(self.bulletcounter, 4);
+       switch(c)
+       {
+               case 0:
+                       f_diff = '-1.25 -3.75 0';
+                       break;
+               case 1:
+                       f_diff = '+1.25 -3.75 0';
+                       break;
+               case 2:
+                       f_diff = '-1.25 +3.75 0';
+                       break;
+               case 3:
+               default:
+                       f_diff = '+1.25 +3.75 0';
+                       break;
+       }
+       W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/flac_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_flac_damage);
+       w_shotorg += f_diff;
+
+       pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
+
+       missile                                 = spawn ();
+       missile.owner                   = missile.realowner = self;
+       missile.classname               = "missile";
+       missile.bot_dodge               = TRUE;
+       missile.bot_dodgerating = autocvar_g_balance_seeker_flac_damage;
+       missile.touch                   = Seeker_Flac_Explode;
+       missile.use                     = Seeker_Flac_Explode; 
+       missile.think                   = adaptor_think2use_hittype_splash;
+       missile.nextthink               = time + autocvar_g_balance_seeker_flac_lifetime + autocvar_g_balance_seeker_flac_lifetime_rand;
+       missile.solid                   = SOLID_BBOX;
+       missile.movetype                = MOVETYPE_FLY; 
+       missile.projectiledeathtype = WEP_SEEKER;
+       missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY;
+       missile.flags                           = FL_PROJECTILE;
+       missile.missile_flags       = MIF_SPLASH; 
+       
+       // csqc projectiles
+       //missile.angles                                = vectoangles (missile.velocity);       
+       //missile.scale = 0.4; // BUG: the model is too big 
+       
+       setorigin (missile, w_shotorg);
+       setsize (missile, '-2 -2 -2', '2 2 2');
+               
+       W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_flac);
+       CSQCProjectile(missile, TRUE, PROJECTILE_FLAC, TRUE);
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+// ============================
+// Begin: Tag and rocket controllers 
+// ============================
+entity Seeker_Tagged_Info(entity isowner, entity istarget)
+{
+       entity tag;
+       for(tag = world; (tag = find(tag, classname, "tag_tracker")); ) 
+               if ((tag.realowner == isowner) && (tag.tag_target == istarget))
+                       return tag;
+               
+       return world;
+}
+
+void Seeker_Attack()
+{
+       entity tracker, closest_target;
+       
+       closest_target = world;
+       for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.realowner == self)
+       {
+               if (closest_target)
+               {
+                       if (vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin))
+                               closest_target = tracker.tag_target;
+               }
+               else 
+                       closest_target = tracker.tag_target;
+       }
+               
+       traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self);
+       if ((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target)))
+               closest_target = world;
+       
+       Seeker_Fire_Missile('0 0 0', closest_target);
+}
+
+void Seeker_Vollycontroller_Think() // TODO: Merge this with Seeker_Attack
+{
+       float c;
+       entity oldself,oldenemy;
+       self.cnt = self.cnt - 1;
+
+       if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.ammo_rockets < autocvar_g_balance_seeker_missile_ammo) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER))
+       {
+               remove(self);
+               return;
+       }
+
+       self.nextthink = time + autocvar_g_balance_seeker_missile_delay * W_WeaponRateFactor();
+       
+       oldself = self;
+       self = self.realowner;
+       
+       oldenemy = self.enemy;
+       self.enemy = oldself.enemy;
+       
+       c = mod(self.cnt, 4);
+       switch(c)
+       {
+               case 0:
+                       Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy);
+                       break;
+               case 1:
+                       Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy);
+                       break;
+               case 2:
+                       Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy);
+                       break;
+               case 3:
+               default:
+                       Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy);
+                       break;
+       }
+
+       self.enemy = oldenemy;
+       self = oldself;
+}
+
+void Seeker_Tracker_Think() 
+{
+       // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up
+       if ((self.realowner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER)
+       || (time > self.tag_time + autocvar_g_balance_seeker_tag_tracker_lifetime))
+       {
+               if (self)
+               {
+                       WaypointSprite_Kill(self.tag_target.wps_tag_tracker);
+                       remove(self);
+               }
+               return;
+       }
+       
+       // Update the think method information
+       self.nextthink = time;
+}
+
+// ============================
+// Begin: Tag projectile 
+// ============================
+void Seeker_Tag_Explode ()
+{
+       //if(other==self.realowner)
+       //    return;
+       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE, other.species, self);
+
+       remove (self);
+}
+
+void Seeker_Tag_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+       self.health = self.health - damage;
+       if (self.health <= 0)
+               Seeker_Tag_Explode();
+}
+
+void Seeker_Tag_Touch()
+{
+       vector dir;
+       vector org2;
+       entity e;
+       
+       PROJECTILE_TOUCH;
+
+       dir     = normalize (self.realowner.origin - self.origin);
+       org2    = findbetterlocation (self.origin, 8);
+
+       te_knightspike(org2);
+
+       self.event_damage = func_null;
+       Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE | HITTYPE_SECONDARY, other.species, self);
+
+       if (other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO)
+       {
+               // check to see if this person is already tagged by me
+               entity tag = Seeker_Tagged_Info(self.realowner, other);
+               
+               if (tag != world)
+               {
+                       if (other.wps_tag_tracker && (autocvar_g_balance_seeker_type == 1)) // don't attach another waypointsprite without killing the old one first
+                               WaypointSprite_Kill(other.wps_tag_tracker);
+                               
+                       tag.tag_time = time;
+               }
+               else
+               {               
+                       //sprint(self.realowner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n"));
+                       e             = spawn();
+                       e.cnt         = autocvar_g_balance_seeker_missile_count;
+                       e.classname   = "tag_tracker";
+                       e.owner       = self.owner;
+                       e.realowner   = self.realowner;
+                       
+                       if      (autocvar_g_balance_seeker_type == 1)
+                       {
+                               e.tag_target  = other;
+                               e.tag_time    = time;
+                               e.think       = Seeker_Tracker_Think;
+                       }
+                       else 
+                       {
+                               e.enemy     = other;
+                               e.think     = Seeker_Vollycontroller_Think;
+                       }
+                       
+                       e.nextthink   = time;
+               }
+               
+               if      (autocvar_g_balance_seeker_type == 1)
+               {
+                       WaypointSprite_Spawn("tagged-target", autocvar_g_balance_seeker_tag_tracker_lifetime, 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, TRUE, RADARICON_TAGGED, '0.5 1 0');
+                       WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT);
+               }
+       }
+
+       remove(self);
+       return;
+}
+
+void Seeker_Fire_Tag()
+{
+       entity missile;
+       W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_tag_ammo, autocvar_g_balance_seeker_reload_ammo);
+
+       W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/tag_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_missile_damage * autocvar_g_balance_seeker_missile_count);
+
+       missile                 = spawn();
+       missile.owner           = missile.realowner = self;
+       missile.classname       = "seeker_tag";
+       missile.bot_dodge       = TRUE;
+       missile.bot_dodgerating = 50;
+       missile.touch           = Seeker_Tag_Touch;
+       missile.think           = SUB_Remove;
+       missile.nextthink       = time + autocvar_g_balance_seeker_tag_lifetime;
+       missile.movetype        = MOVETYPE_FLY;
+       missile.solid           = SOLID_BBOX;
+
+       missile.takedamage       = DAMAGE_YES;
+       missile.event_damage     = Seeker_Tag_Damage;
+       missile.health           = autocvar_g_balance_seeker_tag_health;
+       missile.damageforcescale = autocvar_g_balance_seeker_tag_damageforcescale;
+
+       setorigin (missile, w_shotorg);
+       setsize (missile, '-2 -2 -2', '2 2 2');
+
+       missile.flags       = FL_PROJECTILE;
+       //missile.missile_flags = MIF_..?; 
+
+       missile.movetype    = MOVETYPE_FLY;
+       W_SETUPPROJECTILEVELOCITY(missile, g_balance_seeker_tag);
+       missile.angles = vectoangles (missile.velocity);
+
+       CSQCProjectile(missile, TRUE, PROJECTILE_TAG, FALSE); // has sound
+
+       other = missile; MUTATOR_CALLHOOK(EditProjectile);
+}
+
+// ============================
+// Begin: Genereal weapon functions
+// ============================
+void spawnfunc_weapon_seeker (void)
+{
+       weapon_defaultspawnfunc(WEP_SEEKER);
+}
+
+float w_seeker(float req)
+{
+       float ammo_amount;
+
+       if (req == WR_AIM)
+       {
+               if (autocvar_g_balance_seeker_type == 1) 
+                       if (Seeker_Tagged_Info(self, self.enemy) != world)
+                               self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_missile_speed_max, 0, autocvar_g_balance_seeker_missile_lifetime, FALSE);
+                       else
+                               self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE);
+               else
+                       self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE);
+       }
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo)) // forced reload
+                       weapon_action(self.weapon, WR_RELOAD);
+                       
+               else if (self.BUTTON_ATCK)
+               {
+                       if (autocvar_g_balance_seeker_type == 1) 
+                       {
+                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_missile_refire))
+                               {
+                                       Seeker_Attack();
+                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_missile_animtime, w_ready);
+                               }
+                       }
+                       else 
+                       {
+                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire))
+                               {
+                                       Seeker_Fire_Tag();
+                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready);
+                               }
+                       }
+               }
+
+               else if (self.BUTTON_ATCK2)
+               {
+                       if (autocvar_g_balance_seeker_type == 1) 
+                       {
+                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire))
+                               {
+                                       Seeker_Fire_Tag();
+                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready);
+                               }
+                       }
+                       else 
+                       {
+                               if (weapon_prepareattack(0, autocvar_g_balance_seeker_flac_refire))
+                               {
+                                       Seeker_Fire_Flac();
+                                       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_flac_animtime, w_ready);
+                               }
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_seeker.md3");
+               precache_model ("models/weapons/v_seeker.md3");
+               precache_model ("models/weapons/h_seeker.iqm");
+               precache_sound ("weapons/tag_fire.wav");
+               precache_sound ("weapons/flac_fire.wav");
+               precache_sound ("weapons/seeker_fire.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_SEEKER);
+               self.current_ammo = ammo_rockets;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               if (autocvar_g_balance_seeker_type == 1) 
+               {
+                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_missile_ammo;
+                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_missile_ammo;
+               }
+               else
+               {
+                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo;
+                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo;
+               }
+               
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               if (autocvar_g_balance_seeker_type == 1) 
+               {
+                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo;
+                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo;
+               }
+               else
+               {
+                       ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_flac_ammo;
+                       ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_flac_ammo;
+               }
+               
+               return ammo_amount;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo), autocvar_g_balance_seeker_reload_ammo, autocvar_g_balance_seeker_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_SEEKER_SUICIDE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_SEEKER_MURDER_TAG;
+               else
+                       return WEAPON_SEEKER_MURDER_SPRAY;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_seeker(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 6;
+               if(w_deathtype & HITTYPE_BOUNCE)
+               {
+                       if(w_deathtype & HITTYPE_SECONDARY)
+                       {
+                               if(!w_issilent)
+                                       sound(self, CH_SHOTS, "weapons/tag_impact.wav", 1, ATTN_NORM);
+                       }
+                       else
+                       {
+                               pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
+                               if(!w_issilent)
+                               {
+                                       if (w_random<0.15)
+                                               sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTN_NORM);
+                                       else if (w_random<0.7)
+                                               sound(self, CH_SHOTS, "weapons/tagexp2.wav", 1, ATTN_NORM);
+                                       else
+                                               sound(self, CH_SHOTS, "weapons/tagexp3.wav", 1, ATTN_NORM);
+                               }
+                       }
+               }
+               else
+               {
+                       pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1);
+                       if(!w_issilent)
+                       {
+                               if (w_random<0.15)
+                                       sound(self, CH_SHOTS, "weapons/seekerexp1.wav", 1, ATTN_NORM);
+                               else if (w_random<0.7)
+                                       sound(self, CH_SHOTS, "weapons/seekerexp2.wav", 1, ATTN_NORM);
+                               else
+                                       sound(self, CH_SHOTS, "weapons/seekerexp3.wav", 1, ATTN_NORM);
+                       }
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/seekerexp1.wav");
+               precache_sound("weapons/seekerexp2.wav");
+               precache_sound("weapons/seekerexp3.wav");
+               precache_sound("weapons/tagexp1.wav");
+               precache_sound("weapons/tagexp2.wav");
+               precache_sound("weapons/tagexp3.wav");
+               precache_sound("weapons/tag_impact.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_shockwave.qc b/qcsrc/common/weapons/w_shockwave.qc
new file mode 100644 (file)
index 0000000..6c6658d
--- /dev/null
@@ -0,0 +1,294 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ SHOTGUN,
+/* function  */ w_shotgun,
+/* ammotype  */ IT_SHELLS,
+/* impulse   */ 2,
+/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
+/* rating    */ BOT_PICKUP_RATING_LOW,
+/* model     */ "shotgun",
+/* shortname */ "shotgun",
+/* fullname  */ _("Shotgun")
+);
+#else
+#ifdef SVQC
+
+void W_Shotgun_Attack (void)
+{
+       float   sc;
+       float   ammoamount;
+       float   bullets;
+       float   d;
+       float   f;
+       float   spread;
+       float   bulletspeed;
+       float   bulletconstant;
+       entity flash;
+
+       ammoamount = autocvar_g_balance_shotgun_primary_ammo;
+       bullets = autocvar_g_balance_shotgun_primary_bullets;
+       d = autocvar_g_balance_shotgun_primary_damage;
+       f = autocvar_g_balance_shotgun_primary_force;
+       spread = autocvar_g_balance_shotgun_primary_spread;
+       bulletspeed = autocvar_g_balance_shotgun_primary_speed;
+       bulletconstant = autocvar_g_balance_shotgun_primary_bulletconstant;
+
+       W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo);
+
+       W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets);
+       for (sc = 0;sc < bullets;sc = sc + 1)
+               fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant);
+       endFireBallisticBullet();
+
+       pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo);
+
+       // casing code
+       if (autocvar_g_casings >= 1)
+               for (sc = 0;sc < ammoamount;sc = sc + 1)
+                       SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self);
+
+       // muzzle flash for 1st person view
+       flash = spawn();
+       setmodel(flash, "models/uziflash.md3"); // precision set below
+       flash.think = SUB_Remove;
+       flash.nextthink = time + 0.06;
+       flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
+       W_AttachToShotorg(flash, '5 0 0');
+}
+
+.float swing_prev;
+.entity swing_alreadyhit;
+void shotgun_meleethink (void)
+{
+       // declarations
+       float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
+       entity target_victim;
+       vector targpos;
+
+       if(!self.cnt) // set start time of melee
+       {
+               self.cnt = time; 
+               W_PlayStrengthSound(self.realowner);
+       }
+
+       makevectors(self.realowner.v_angle); // update values for v_* vectors
+       
+       // calculate swing percentage based on time
+       meleetime = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor();
+       swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
+       f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces);
+       
+       // check to see if we can still continue, otherwise give up now
+       if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_melee_no_doubleslap)
+       {
+               remove(self);
+               return;
+       }
+       
+       // if okay, perform the traces needed for this frame 
+       for(i=self.swing_prev; i < f; ++i)
+       {
+               swing_factor = ((1 - (i / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1);
+               
+               targpos = (self.realowner.origin + self.realowner.view_ofs 
+                       + (v_forward * autocvar_g_balance_shotgun_secondary_melee_range)
+                       + (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up)
+                       + (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_side));
+
+               WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner));
+               
+               // draw lightning beams for debugging
+               //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); 
+               //te_customflash(targpos, 40,  2, '1 1 1');
+               
+               is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body");
+
+               if((trace_fraction < 1) // if trace is good, apply the damage and remove self
+                       && (trace_ent.takedamage == DAMAGE_AIM)  
+                       && (trace_ent != self.swing_alreadyhit)
+                       && (is_player || autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage))
+               {
+                       target_victim = trace_ent; // so it persists through other calls
+                       
+                       if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
+                               swing_damage = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1));
+                       else
+                               swing_damage = (autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage * min(1, swing_factor + 1));
+                       
+                       //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
+                       
+                       Damage(target_victim, self.realowner, self.realowner, 
+                               swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY, 
+                               self.realowner.origin + self.realowner.view_ofs, 
+                               v_forward * autocvar_g_balance_shotgun_secondary_force);
+                               
+                       if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); }
+                               
+                       // draw large red flash for debugging
+                       //te_customflash(targpos, 200, 2, '15 0 0');
+                       
+                       if(autocvar_g_balance_shotgun_secondary_melee_multihit) // allow multiple hits with one swing, but not against the same player twice.
+                       {
+                               self.swing_alreadyhit = target_victim;
+                               continue; // move along to next trace
+                       }
+                       else
+                       {
+                               remove(self);
+                               return;
+                       }
+               }
+       }
+       
+       if(time >= self.cnt + meleetime)
+       {
+               // melee is finished
+               remove(self);
+               return;
+       }
+       else
+       {
+               // set up next frame 
+               self.swing_prev = i;
+               self.nextthink = time;
+       }
+}
+
+void W_Shotgun_Attack2 (void)
+{
+       sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM);
+       weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_shotgun_secondary_animtime, w_ready);
+
+       entity meleetemp;
+       meleetemp = spawn();
+       meleetemp.realowner = self;
+       meleetemp.think = shotgun_meleethink;
+       meleetemp.nextthink = time + autocvar_g_balance_shotgun_secondary_melee_delay * W_WeaponRateFactor();
+       W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_shotgun_secondary_damage, autocvar_g_balance_shotgun_secondary_melee_range);
+}
+
+void spawnfunc_weapon_shotgun(); // defined in t_items.qc
+
+.float shotgun_primarytime;
+
+float w_shotgun(float req)
+{
+       float ammo_amount;
+       if (req == WR_AIM)
+               if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range)
+                       self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
+               else
+               {
+                       if(autocvar_g_antilag_bullets)
+                               self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
+                       else
+                               self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE);
+               }
+
+       else if (req == WR_THINK)
+       {
+               if(autocvar_g_balance_shotgun_reload_ammo && self.clip_load < autocvar_g_balance_shotgun_primary_ammo) // forced reload
+               {
+                       // don't force reload an empty shotgun if its melee attack is active
+                       if not(autocvar_g_balance_shotgun_secondary && self.ammo_shells < autocvar_g_balance_shotgun_primary_ammo)
+                               weapon_action(self.weapon, WR_RELOAD);
+               }
+               else
+               {
+                       if (self.BUTTON_ATCK)
+                       {
+                               if (time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
+                               {
+                                       if(weapon_prepareattack(0, autocvar_g_balance_shotgun_primary_animtime))
+                                       {
+                                               W_Shotgun_Attack();
+                                               self.shotgun_primarytime = time + autocvar_g_balance_shotgun_primary_refire * W_WeaponRateFactor();
+                                               weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_shotgun_primary_animtime, w_ready);
+                                       }
+                               }
+                       }
+               }
+               if (self.clip_load >= 0) // we are not currently reloading
+               if (!self.crouch) // no crouchmelee please
+               if (self.BUTTON_ATCK2 && autocvar_g_balance_shotgun_secondary)
+               if (weapon_prepareattack(1, autocvar_g_balance_shotgun_secondary_refire))
+               {
+                       // attempt forcing playback of the anim by switching to another anim (that we never play) here...
+                       weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2);
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/uziflash.md3");
+               precache_model ("models/weapons/g_shotgun.md3");
+               precache_model ("models/weapons/v_shotgun.md3");
+               precache_model ("models/weapons/h_shotgun.iqm");
+               precache_sound ("misc/itempickup.wav");
+               precache_sound ("weapons/shotgun_fire.wav");
+               precache_sound ("weapons/shotgun_melee.wav");
+               //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_SHOTGUN);
+               self.current_ammo = ammo_shells;
+       }
+       else if (req == WR_CHECKAMMO1)
+       {
+               ammo_amount = self.ammo_shells >= autocvar_g_balance_shotgun_primary_ammo;
+               ammo_amount += self.(weapon_load[WEP_SHOTGUN]) >= autocvar_g_balance_shotgun_primary_ammo;
+               return ammo_amount;
+       }
+       else if (req == WR_CHECKAMMO2)
+       {
+               // melee attack is always available
+               return TRUE;
+       }
+       else if (req == WR_RELOAD)
+       {
+               W_Reload(autocvar_g_balance_shotgun_primary_ammo, autocvar_g_balance_shotgun_reload_ammo, autocvar_g_balance_shotgun_reload_time, "weapons/reload.wav");
+       }
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               return WEAPON_THINKING_WITH_PORTALS;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_SHOTGUN_MURDER_SLAP;
+               else
+                       return WEAPON_SHOTGUN_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+.float prevric;
+float w_shotgun(float req)
+{
+       if(req == WR_IMPACTEFFECT)
+       {
+               vector org2;
+               org2 = w_org + w_backoff * 2;
+               pointparticles(particleeffectnum("shotgun_impact"), org2, w_backoff * 1000, 1);
+               if(!w_issilent && time - self.prevric > 0.25)
+               {
+                       if(w_random < 0.0165)
+                               sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM);
+                       else if(w_random < 0.033)
+                               sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM);
+                       else if(w_random < 0.05)
+                               sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM);
+                       self.prevric = time;
+               }
+       }
+       else if(req == WR_PRECACHE)
+       {
+               precache_sound("weapons/ric1.wav");
+               precache_sound("weapons/ric2.wav");
+               precache_sound("weapons/ric3.wav");
+       }
+       return TRUE;
+}
+#endif
+#endif
diff --git a/qcsrc/common/weapons/w_tuba.qc b/qcsrc/common/weapons/w_tuba.qc
new file mode 100644 (file)
index 0000000..48b696d
--- /dev/null
@@ -0,0 +1,465 @@
+#ifdef REGISTER_WEAPON
+REGISTER_WEAPON(
+/* WEP_##id  */ TUBA,
+/* function  */ w_tuba,
+/* ammotype  */ 0,
+/* impulse   */ 1,
+/* flags     */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH,
+/* rating    */ BOT_PICKUP_RATING_MID,
+/* model     */ "tuba",
+/* shortname */ "tuba",
+/* fullname  */ _("@!#%'n Tuba")
+);
+#else
+#ifdef SVQC
+//#define TUBA_NOTE(n) strcat("weapons/tuba_note", ftos(n), ".wav")
+.entity tuba_note;
+.float tuba_smoketime;
+.float tuba_instrument;
+
+#define MAX_TUBANOTES 32
+.float tuba_lastnotes_last;
+.float tuba_lastnotes_cnt; // over
+.vector tuba_lastnotes[MAX_TUBANOTES];
+
+float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo)
+{
+       float i, j, mmin, mmax, nolength;
+       float n = tokenize_console(melody);
+       if(n > pl.tuba_lastnotes_cnt)
+               return FALSE;
+       float pitchshift = 0;
+
+       if(instrument >= 0)
+               if(pl.tuba_instrument != instrument)
+                       return FALSE;
+
+       // verify notes...
+       nolength = FALSE;
+       for(i = 0; i < n; ++i)
+       {
+               vector v = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
+               float ai = stof(argv(n - i - 1));
+               float np = floor(ai);
+               if(ai == np)
+                       nolength = TRUE;
+               // n counts the last played notes BACKWARDS
+               // _x is start
+               // _y is end
+               // _z is note pitch
+               if(ignorepitch && i == 0)
+               {
+                       pitchshift = np - v_z;
+               }
+               else
+               {
+                       if(v_z + pitchshift != np)
+                               return FALSE;
+               }
+       }
+
+       // now we know the right NOTES were played
+       if(!nolength)
+       {
+               // verify rhythm...
+               float ti = 0;
+               if(maxtempo > 0)
+                       mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
+               else
+                       mmin = 0;
+               if(mintempo > 0)
+                       mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec
+               else
+                       mmax = 240; // you won't try THAT hard... (tempo 1)
+               //print(sprintf("initial tempo rules: %f %f\n", mmin, mmax));
+
+               for(i = 0; i < n; ++i)
+               {
+                       vector vi = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]);
+                       float ai = stof(argv(n - i - 1));
+                       ti -= 1 / (ai - floor(ai));
+                       float tj = ti;
+                       for(j = i+1; j < n; ++j)
+                       {
+                               vector vj = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - j + MAX_TUBANOTES, MAX_TUBANOTES)]);
+                               float aj = stof(argv(n - j - 1));
+                               tj -= (aj - floor(aj));
+
+                               // note i should be at m*ti+b
+                               // note j should be at m*tj+b
+                               // so:
+                               // we have a LINE l, so that
+                               // vi_x <= l(ti) <= vi_y
+                               // vj_x <= l(tj) <= vj_y
+                               // what is m?
+
+                               // vi_x <= vi_y <= vj_x <= vj_y
+                               // ti <= tj
+                               //print(sprintf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti));
+                               //print(sprintf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj));
+                               //print(sprintf("m1 = %f\n", (vi_x - vj_y) / (ti - tj)));
+                               //print(sprintf("m2 = %f\n", (vi_y - vj_x) / (ti - tj)));
+                               mmin = max(mmin, (vi_x - vj_y) / (ti - tj)); // lower bound
+                               mmax = min(mmax, (vi_y - vj_x) / (ti - tj)); // upper bound
+                       }
+               }
+
+               if(mmin > mmax) // rhythm fail
+                       return FALSE;
+       }
+
+       pl.tuba_lastnotes_cnt = 0;
+
+       return TRUE;
+}
+
+void W_Tuba_NoteOff()
+{
+       // we have a note:
+       //   on: self.spawnshieldtime
+       //   off: time
+       //   note: self.cnt
+       if(self.owner.tuba_note == self)
+       {
+               self.owner.tuba_lastnotes_last = mod(self.owner.tuba_lastnotes_last + 1, MAX_TUBANOTES);
+               self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt;
+               self.owner.tuba_note = world;
+               self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES);
+
+               string s;
+               s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null);
+               if(s != "")
+               {
+                       // simulate a server message
+                       switch(self.tuba_instrument)
+                       {
+                               default:
+                               case 0: // Tuba
+                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n"));
+                                       break;
+                               case 1:
+                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n"));
+                                       break;
+                               case 2:
+                                       bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n"));
+                                       break;
+                       }
+               }
+       }
+       remove(self);
+}
+
+float Tuba_GetNote(entity pl, float hittype)
+{
+       float note;
+       float movestate;
+       movestate = 5;
+       if(pl.movement_x < 0) movestate -= 3;
+       if(pl.movement_x > 0) movestate += 3;
+       if(pl.movement_y < 0) movestate -= 1;
+       if(pl.movement_y > 0) movestate += 1;
+#ifdef GMQCC
+       note = 0;
+#endif
+       switch(movestate)
+       {
+       // layout: originally I wanted
+       //   eb e  e#=f
+       //   B  c  d
+       //   Gb G  G#
+       // but then you only use forward and right key. So to make things more
+       // interesting, I swapped B with e#. Har har har...
+       //   eb e  B
+       // f=e# c  d
+       //   Gb G  G#
+               case 1: note = -6; break; // Gb
+               case 2: note = -5; break; // G
+               case 3: note = -4; break; // G#
+               case 4: note = +5; break; // e#
+               default:
+               case 5: note =  0; break; // c
+               case 6: note = +2; break; // d
+               case 7: note = +3; break; // eb
+               case 8: note = +4; break; // e
+               case 9: note = -1; break; // B
+       }
+       if(pl.BUTTON_CROUCH)
+               note -= 12;
+       if(pl.BUTTON_JUMP)
+               note += 12;
+       if(hittype & HITTYPE_SECONDARY)
+               note += 7;
+       
+       // we support two kinds of tubas, those tuned in Eb and those tuned in C
+       // kind of tuba currently is player slot number, or team number if in
+       // teamplay
+       // that way, holes in the range of notes are "plugged"
+       if(teamplay)
+       {
+               if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4)
+                       note += 3;
+       }
+       else
+       {
+               if(pl.clientcolors & 1)
+                       note += 3;
+       }
+       
+       // total range of notes:
+       //                       0
+       //                 ***  ** ****
+       //                        ***  ** ****
+       //     ***  ** ****
+       //            ***  ** ****
+       //     ***  ********************* ****
+       //     -18.........................+12
+       //        ***  ********************* ****
+       //     -18............................+15
+       //     with jump: ... +24
+       //     ... +27
+       return note;
+}
+
+float W_Tuba_NoteSendEntity(entity to, float sf)
+{
+       float f;
+
+       msg_entity = to;
+       if(!sound_allowed(MSG_ONE, self.realowner))
+               return FALSE;
+
+       WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE);
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 1)
+       {
+               WriteChar(MSG_ENTITY, self.cnt);
+               f = 0;
+               if(self.realowner != to)
+                       f |= 1;
+               f |= 2 * self.tuba_instrument;
+               WriteByte(MSG_ENTITY, f);
+       }
+       if(sf & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.origin_x);
+               WriteCoord(MSG_ENTITY, self.origin_y);
+               WriteCoord(MSG_ENTITY, self.origin_z);
+       }
+       return TRUE;
+}
+
+void W_Tuba_NoteThink()
+{
+       float dist_mult;
+       float vol0, vol1;
+       vector dir0, dir1;
+       vector v;
+       entity e;
+       if(time > self.teleport_time)
+       {
+               W_Tuba_NoteOff();
+               return;
+       }
+       self.nextthink = time;
+       dist_mult = autocvar_g_balance_tuba_attenuation / autocvar_snd_soundradius;
+       FOR_EACH_REALCLIENT(e)
+       if(e != self.realowner)
+       {
+               v = self.origin - (e.origin + e.view_ofs);
+               vol0 = max(0, 1 - vlen(v) * dist_mult);
+               dir0 = normalize(v);
+               v = self.realowner.origin - (e.origin + e.view_ofs);
+               vol1 = max(0, 1 - vlen(v) * dist_mult);
+               dir1 = normalize(v);
+               if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume
+               {
+                       setorigin(self, self.realowner.origin);
+                       self.SendFlags |= 2;
+                       break;
+               }
+               if(dir0 * dir1 < 0.9994) // 2 degrees change in angle
+               {
+                       setorigin(self, self.realowner.origin);
+                       self.SendFlags |= 2;
+                       break;
+               }
+       }
+}
+
+void W_Tuba_NoteOn(float hittype)
+{
+       vector o;
+       float n;
+
+       W_SetupShot(self, FALSE, 2, "", 0, autocvar_g_balance_tuba_damage);
+
+       n = Tuba_GetNote(self, hittype);
+
+       hittype = 0;
+       if(self.tuba_instrument & 1)
+               hittype |= HITTYPE_SECONDARY;
+       if(self.tuba_instrument & 2)
+               hittype |= HITTYPE_BOUNCE;
+
+       if(self.tuba_note)
+       {
+               if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument)
+               {
+                       entity oldself = self;
+                       self = self.tuba_note;
+                       W_Tuba_NoteOff();
+                       self = oldself;
+               }
+       }
+
+       if not(self.tuba_note)
+       {
+               self.tuba_note = spawn();
+               self.tuba_note.owner = self.tuba_note.realowner = self;
+               self.tuba_note.cnt = n;
+               self.tuba_note.tuba_instrument = self.tuba_instrument;
+               self.tuba_note.think = W_Tuba_NoteThink;
+               self.tuba_note.nextthink = time;
+               self.tuba_note.spawnshieldtime = time;
+               Net_LinkEntity(self.tuba_note, FALSE, 0, W_Tuba_NoteSendEntity);
+       }
+
+       self.tuba_note.teleport_time = time + autocvar_g_balance_tuba_refire * 2 * W_WeaponRateFactor(); // so it can get prolonged safely
+
+       //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation);
+       RadiusDamage(self, self, autocvar_g_balance_tuba_damage, autocvar_g_balance_tuba_edgedamage, autocvar_g_balance_tuba_radius, world, world, autocvar_g_balance_tuba_force, hittype | WEP_TUBA, world);
+
+       o = gettaginfo(self.exteriorweaponentity, 0);
+       if(time > self.tuba_smoketime)
+       {
+               pointparticles(particleeffectnum("smoke_ring"), o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1);
+               self.tuba_smoketime = time + 0.25;
+       }
+}
+
+void spawnfunc_weapon_tuba (void)
+{
+       weapon_defaultspawnfunc(WEP_TUBA);
+}
+
+float w_tuba(float req)
+{
+       if (req == WR_AIM)
+       {
+               // bots cannot play the Tuba well yet
+               // I think they should start with the recorder first
+               if(vlen(self.origin - self.enemy.origin) < autocvar_g_balance_tuba_radius)
+               {
+                       if(random() > 0.5)
+                               self.BUTTON_ATCK = 1;
+                       else
+                               self.BUTTON_ATCK2 = 1;
+               }
+       }
+       else if (req == WR_THINK)
+       {
+               if (self.BUTTON_ATCK)
+               if (weapon_prepareattack(0, autocvar_g_balance_tuba_refire))
+               {
+                       W_Tuba_NoteOn(0);
+                       //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready);
+                       weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready);
+               }
+               if (self.BUTTON_ATCK2)
+               if (weapon_prepareattack(1, autocvar_g_balance_tuba_refire))
+               {
+                       W_Tuba_NoteOn(HITTYPE_SECONDARY);
+                       //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready);
+                       weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready);
+               }
+               if(self.tuba_note)
+               {
+                       if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2)
+                       {
+                               entity oldself = self;
+                               self = self.tuba_note;
+                               W_Tuba_NoteOff();
+                               self = oldself;
+                       }
+               }
+       }
+       else if (req == WR_PRECACHE)
+       {
+               precache_model ("models/weapons/g_tuba.md3");
+               precache_model ("models/weapons/v_tuba.md3");
+               precache_model ("models/weapons/h_tuba.iqm");
+               precache_model ("models/weapons/v_akordeon.md3");
+               precache_model ("models/weapons/h_akordeon.iqm");
+               precache_model ("models/weapons/v_kleinbottle.md3");
+               precache_model ("models/weapons/h_kleinbottle.iqm");
+
+               //float i;
+               //for(i = -18; i <= +27; ++i)
+               //      precache_sound(TUBA_NOTE(i));
+       }
+       else if (req == WR_SETUP)
+       {
+               weapon_setup(WEP_TUBA);
+               self.current_ammo = ammo_none;
+               self.tuba_instrument = 0;
+       }
+       else if (req == WR_RELOAD)
+       {
+               // switch to alternate instruments :)
+               if(self.weaponentity.state == WS_READY)
+               {
+                       switch(self.tuba_instrument)
+                       {
+                               case 0:
+                                       self.tuba_instrument = 1;
+                                       self.weaponname = "akordeon";
+                                       break;
+                               case 1:
+                                       self.tuba_instrument = 2;
+                                       self.weaponname = "kleinbottle";
+                                       break;
+                               case 2:
+                                       self.tuba_instrument = 0;
+                                       self.weaponname = "tuba";
+                                       break;
+                       }
+                       W_SetupShot(self, FALSE, 0, "", 0, 0);
+                       pointparticles(particleeffectnum("teleport"), w_shotorg, '0 0 0', 1);
+                       self.weaponentity.state = WS_INUSE;
+                       weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready);
+               }
+       }
+       else if (req == WR_CHECKAMMO1)
+               return TRUE; // TODO use fuel?
+       else if (req == WR_CHECKAMMO2)
+               return TRUE; // TODO use fuel?
+       else if (req == WR_SUICIDEMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_BOUNCE)
+                       return WEAPON_KLEINBOTTLE_SUICIDE;
+               else if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_ACCORDEON_SUICIDE;
+               else
+                       return WEAPON_TUBA_SUICIDE;
+       }
+       else if (req == WR_KILLMESSAGE)
+       {
+               if(w_deathtype & HITTYPE_BOUNCE)
+                       return WEAPON_KLEINBOTTLE_MURDER;
+               else if(w_deathtype & HITTYPE_SECONDARY)
+                       return WEAPON_ACCORDEON_MURDER;
+               else
+                       return WEAPON_TUBA_MURDER;
+       }
+       return TRUE;
+}
+#endif
+#ifdef CSQC
+float w_tuba(float req)
+{
+       // nothing to do here; particles of tuba are handled differently
+
+       return TRUE;
+}
+#endif
+#endif