../common/command/generic.qc
../common/mapinfo.qc
../common/items.qc
-../server/w_all.qc
+../server/weapons/w_all.qc
../common/explosion_equation.qc
../common/urllib.qc
command/cl_cmd.qc
REGISTER_WEAPON_2(WEP_##id,func,ammotype,i,weapontype,pickupbasevalue,modelname,shortname,wname)
#endif
-#include "../server/w_all.qc"
+#include "../server/weapons/w_all.qc"
#undef REGISTER_WEAPON
ACCUMULATE_FUNCTION(RegisterWeapons, register_weapons_done)
--- /dev/null
+// ONLY EVER ADD NEW WEAPONS AT THE END. IF YOU REMOVE ONE, PUT THE LAST ONE ON
+// ITS PLACE. THIS IS TO AVOID UNNECESSARY RENUMBERING OF WEAPON IMPULSES.
+// IF YOU DISREGARD THIS NOTICE, I'LL KILL YOU WITH THE @!#%'N TUBA
+#include "w_laser.qc"
+#include "w_shotgun.qc"
+#include "w_uzi.qc"
+#include "w_grenadelauncher.qc"
+#include "w_minelayer.qc"
+#include "w_electro.qc"
+#include "w_lightning.qc"
+#include "w_crylink.qc"
+#include "w_nex.qc"
+#include "w_hagar.qc"
+#include "w_rocketlauncher.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"
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+void ElectroInit();
+vector electro_shotorigin[4];
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+void LightningInit();
+vector lightning_shotorigin[4];
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
+++ /dev/null
-void W_TriggerReload()
-{
- weapon_action(self.weapon, WR_RELOAD);
-}
-
-// switch between weapons
-void W_SwitchWeapon(float imp)
-{
- if (self.switchweapon != imp)
- {
- if (client_hasweapon(self, imp, TRUE, TRUE))
- W_SwitchWeapon_Force(self, imp);
- else
- self.selectweapon = imp; // update selectweapon ANYWAY
- }
- else
- {
- W_TriggerReload();
- }
-}
-
-.float weaponcomplainindex;
-float W_GetCycleWeapon(entity pl, string weaponorder, float dir, float imp, float complain, float skipmissing)
-{
- // We cannot tokenize in this function, as GiveItems calls this
- // function. Thus we must use car/cdr.
- float weaponwant, first_valid, prev_valid, switchtonext, switchtolast, c;
- string rest;
- switchtonext = switchtolast = 0;
- first_valid = prev_valid = 0;
- float weaponcur;
-
- if(skipmissing || pl.selectweapon == 0)
- weaponcur = pl.switchweapon;
- else
- weaponcur = pl.selectweapon;
-
- if(dir == 0)
- switchtonext = 1;
-
- c = 0;
-
- rest = weaponorder;
- while(rest != "")
- {
- weaponwant = stof(car(rest)); rest = cdr(rest);
- if(imp >= 0)
- if((get_weaponinfo(weaponwant)).impulse != imp)
- continue;
-
- ++c;
-
- if(!skipmissing || client_hasweapon(pl, weaponwant, TRUE, FALSE))
- {
- if(switchtonext)
- return weaponwant;
- if(!first_valid)
- first_valid = weaponwant;
- if(weaponwant == weaponcur)
- {
- if(dir >= 0)
- switchtonext = 1;
- else if(prev_valid)
- return prev_valid;
- else
- switchtolast = 1;
- }
- prev_valid = weaponwant;
- }
- }
- if(first_valid)
- {
- if(switchtolast)
- return prev_valid;
- else
- return first_valid;
- }
- // complain (but only for one weapon on the button that has been pressed)
- if(complain)
- {
- self.weaponcomplainindex += 1;
- c = mod(self.weaponcomplainindex, c) + 1;
- rest = weaponorder;
- while(rest != "")
- {
- weaponwant = stof(car(rest)); rest = cdr(rest);
- if(imp >= 0)
- if((get_weaponinfo(weaponwant)).impulse != imp)
- continue;
-
- --c;
- if(c == 0)
- {
- client_hasweapon(pl, weaponwant, TRUE, TRUE);
- break;
- }
- }
- }
- return 0;
-}
-
-void W_CycleWeapon(string weaponorder, float dir)
-{
- float w;
- w = W_GetCycleWeapon(self, weaponorder, dir, -1, 1, TRUE);
- if(w > 0)
- W_SwitchWeapon(w);
-}
-
-void W_NextWeaponOnImpulse(float imp)
-{
- float w;
- w = W_GetCycleWeapon(self, self.cvar_cl_weaponpriority, +1, imp, 1, (self.cvar_cl_weaponimpulsemode == 0));
- if(w > 0)
- W_SwitchWeapon(w);
-}
-
-// next weapon
-void W_NextWeapon(float list)
-{
- if(list == 0)
- W_CycleWeapon(weaponorder_byid, -1);
- else if(list == 1)
- W_CycleWeapon(self.weaponorder_byimpulse, -1);
- else if(list == 2)
- W_CycleWeapon(self.cvar_cl_weaponpriority, -1);
-}
-
-// prev weapon
-void W_PreviousWeapon(float list)
-{
- if(list == 0)
- W_CycleWeapon(weaponorder_byid, +1);
- else if(list == 1)
- W_CycleWeapon(self.weaponorder_byimpulse, +1);
- else if(list == 2)
- W_CycleWeapon(self.cvar_cl_weaponpriority, +1);
-}
-
-// previously used if exists and has ammo, (second) best otherwise
-void W_LastWeapon()
-{
- if(client_hasweapon(self, self.cnt, TRUE, FALSE))
- W_SwitchWeapon(self.cnt);
- else
- W_SwitchToOtherWeapon(self);
-}
-
-float w_getbestweapon(entity e)
-{
- return W_GetCycleWeapon(e, e.cvar_cl_weaponpriority, 0, -1, FALSE, TRUE);
-}
-
-// generic weapons table
-// TODO should they be macros instead?
-float weapon_action(float wpn, float wrequest)
-{
- return (get_weaponinfo(wpn)).weapon_func(wrequest);
-}
-
-.float savenextthink;
-void thrown_wep_think()
-{
- self.owner = world;
- float timeleft = self.savenextthink - time;
- if(timeleft > 1)
- SUB_SetFade(self, self.savenextthink - 1, 1);
- else if(timeleft > 0)
- SUB_SetFade(self, time, timeleft);
- else
- SUB_VanishOrRemove(self);
-}
-
-// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo)
-{
- entity oldself, wep;
- float wa, thisammo, i, j;
- string s;
- var .float ammofield;
-
- wep = spawn();
-
- setorigin(wep, org);
- wep.classname = "droppedweapon";
- wep.velocity = velo;
- wep.owner = wep.enemy = own;
- wep.flags |= FL_TOSSED;
- wep.colormap = own.colormap;
-
- if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, wpn))
- {
- if(own.items & IT_UNLIMITED_SUPERWEAPONS)
- {
- wep.superweapons_finished = time + autocvar_g_balance_superweapons_time;
- }
- else
- {
- float superweapons = 1;
- for(i = WEP_FIRST; i <= WEP_LAST; ++i)
- if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, i))
- if(WEPSET_CONTAINS_EW(own, i))
- ++superweapons;
- if(superweapons <= 1)
- {
- wep.superweapons_finished = own.superweapons_finished;
- own.superweapons_finished = 0;
- }
- else
- {
- float timeleft = own.superweapons_finished - time;
- float weptimeleft = timeleft / superweapons;
- wep.superweapons_finished = time + weptimeleft;
- own.superweapons_finished -= weptimeleft;
- }
- }
- }
-
- wa = W_AmmoItemCode(wpn);
- if(wa == 0)
- {
- oldself = self;
- self = wep;
- weapon_defaultspawnfunc(wpn);
- self = oldself;
- if(startitem_failed)
- return string_null;
- wep.glowmod = own.weaponentity_glowmod;
- wep.think = thrown_wep_think;
- wep.savenextthink = wep.nextthink;
- wep.nextthink = min(wep.nextthink, time + 0.5);
- wep.pickup_anyway = TRUE; // these are ALWAYS pickable
- return "";
- }
- else
- {
- s = "";
- oldself = self;
- self = wep;
- weapon_defaultspawnfunc(wpn);
- self = oldself;
- if(startitem_failed)
- return string_null;
- if(doreduce && g_weapon_stay == 2)
- {
- for(i = 0, j = 1; i < 24; ++i, j *= 2)
- {
- if(wa & j)
- {
- ammofield = Item_CounterField(j);
-
- // if our weapon is loaded, give its load back to the player
- if(self.(weapon_load[self.weapon]) > 0)
- {
- own.ammofield += self.(weapon_load[self.weapon]);
- self.(weapon_load[self.weapon]) = -1; // schedule the weapon for reloading
- }
-
- wep.ammofield = 0;
- }
- }
- }
- else if(doreduce)
- {
- for(i = 0, j = 1; i < 24; ++i, j *= 2)
- {
- if(wa & j)
- {
- ammofield = Item_CounterField(j);
-
- // if our weapon is loaded, give its load back to the player
- if(self.(weapon_load[self.weapon]) > 0)
- {
- own.ammofield += self.(weapon_load[self.weapon]);
- self.(weapon_load[self.weapon]) = -1; // schedule the weapon for reloading
- }
-
- thisammo = min(own.ammofield, wep.ammofield);
- wep.ammofield = thisammo;
- own.ammofield -= thisammo;
- s = strcat(s, " and ", ftos(thisammo), " ", Item_CounterFieldName(j));
- }
- }
- s = substring(s, 5, -1);
- }
- wep.glowmod = own.weaponentity_glowmod;
- wep.think = thrown_wep_think;
- wep.savenextthink = wep.nextthink;
- wep.nextthink = min(wep.nextthink, time + 0.5);
- wep.pickup_anyway = TRUE; // these are ALWAYS pickable
-
- return s;
- }
-}
-
-float W_IsWeaponThrowable(float w)
-{
- float wa;
-
- if (!autocvar_g_pickup_items)
- return 0;
- if (g_weaponarena)
- return 0;
- if (g_cts)
- return 0;
- if (g_nexball && w == WEP_GRENADE_LAUNCHER)
- return 0;
- if(w == 0)
- return 0;
-
- wa = W_AmmoItemCode(w);
- if(WEPSET_CONTAINS_AW(start_weapons, w))
- {
- // start weapons that take no ammo can't be dropped (this prevents dropping the laser, as long as it continues to use no ammo)
- if(start_items & IT_UNLIMITED_WEAPON_AMMO)
- return 0;
- if(wa == 0)
- return 0;
- }
-
- return 1;
-}
-
-// toss current weapon
-void W_ThrowWeapon(vector velo, vector delta, float doreduce)
-{
- float w;
- string a;
-
- w = self.weapon;
- if (w == 0)
- return; // just in case
- if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon))
- return;
- if(!autocvar_g_weapon_throwable)
- return;
- if(self.weaponentity.state != WS_READY)
- return;
- if(!W_IsWeaponThrowable(w))
- return;
-
- if(!WEPSET_CONTAINS_EW(self, w))
- return;
- WEPSET_ANDNOT_EW(self, w);
-
- W_SwitchWeapon_Force(self, w_getbestweapon(self));
- a = W_ThrowNewWeapon(self, w, doreduce, self.origin + delta, velo);
-
- if not(a) return;
- Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w);
-}
-
-float forbidWeaponUse()
-{
- if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
- return 1;
- if(round_handler_IsActive() && !round_handler_IsRoundStarted())
- return 1;
- if(self.player_blocked)
- return 1;
- if(self.freezetag_frozen)
- return 1;
- return 0;
-}
-
-void W_WeaponFrame()
-{
- vector fo, ri, up;
-
- if (frametime)
- self.weapon_frametime = frametime;
-
- if (!self.weaponentity || self.health < 1)
- return; // Dead player can't use weapons and injure impulse commands
-
- if(forbidWeaponUse())
- if(self.weaponentity.state != WS_CLEAR)
- {
- w_ready();
- return;
- }
-
- if(!self.switchweapon)
- {
- self.weapon = 0;
- self.switchingweapon = 0;
- self.weaponentity.state = WS_CLEAR;
- self.weaponname = "";
- self.items &~= IT_AMMO;
- return;
- }
-
- makevectors(self.v_angle);
- fo = v_forward; // save them in case the weapon think functions change it
- ri = v_right;
- up = v_up;
-
- // Change weapon
- if (self.weapon != self.switchweapon)
- {
- if (self.weaponentity.state == WS_CLEAR)
- {
- // end switching!
- self.switchingweapon = self.switchweapon;
-
- entity newwep = get_weaponinfo(self.switchweapon);
-
- //setanim(self, self.anim_draw, FALSE, TRUE, TRUE);
- self.weaponentity.state = WS_RAISE;
- weapon_action(self.switchweapon, WR_SETUP);
-
- // set our clip load to the load of the weapon we switched to, if it's reloadable
- if(newwep.spawnflags & WEP_FLAG_RELOADABLE && cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"))) // prevent accessing undefined cvars
- {
- self.clip_load = self.(weapon_load[self.switchweapon]);
- self.clip_size = cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"));
- }
- else
- self.clip_load = self.clip_size = 0;
-
- // VorteX: add player model weapon select frame here
- // setcustomframe(PlayerWeaponRaise);
- weapon_thinkf(WFRAME_IDLE, cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), w_ready);
- //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname))));
- weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, '0 0 0');
- }
- else if (self.weaponentity.state == WS_DROP)
- {
- // in dropping phase we can switch at any time
- self.switchingweapon = self.switchweapon;
- }
- else if (self.weaponentity.state == WS_READY)
- {
- // start switching!
- self.switchingweapon = self.switchweapon;
-
- entity oldwep = get_weaponinfo(self.weapon);
-
-#ifndef INDEPENDENT_ATTACK_FINISHED
- if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
- {
-#endif
- sound (self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
- self.weaponentity.state = WS_DROP;
- // set up weapon switch think in the future, and start drop anim
- weapon_thinkf(WFRAME_DONTCHANGE, cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), w_clear);
- //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname))));
- weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, PLAYER_WEAPONSELECTION_RANGE);
-#ifndef INDEPENDENT_ATTACK_FINISHED
- }
-#endif
- }
- }
-
- // LordHavoc: network timing test code
- //if (self.button0)
- // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
-
- float w;
- w = self.weapon;
-
- // call the think code which may fire the weapon
- // and do so multiple times to resolve framerate dependency issues if the
- // server framerate is very low and the weapon fire rate very high
- float c;
- c = 0;
- while (c < W_TICSPERFRAME)
- {
- c = c + 1;
- if(w && !WEPSET_CONTAINS_EW(self, w))
- {
- if(self.weapon == self.switchweapon)
- W_SwitchWeapon_Force(self, w_getbestweapon(self));
- w = 0;
- }
-
- v_forward = fo;
- v_right = ri;
- v_up = up;
-
- if(w)
- weapon_action(self.weapon, WR_THINK);
- else
- weapon_action(self.weapon, WR_GONETHINK);
-
- if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
- {
- if(self.weapon_think)
- {
- v_forward = fo;
- v_right = ri;
- v_up = up;
- self.weapon_think();
- }
- else
- bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
- }
- }
-
- // don't let attack_finished fall behind when not firing (must be after weapon_setup calls!)
- //if (ATTACK_FINISHED(self) < time)
- // ATTACK_FINISHED(self) = time;
-
- //if (self.weapon_nextthink < time)
- // self.weapon_nextthink = time;
-
- // update currentammo incase it has changed
-#if 0
- if (self.items & IT_CELLS)
- self.currentammo = self.ammo_cells;
- else if (self.items & IT_ROCKETS)
- self.currentammo = self.ammo_rockets;
- else if (self.items & IT_NAILS)
- self.currentammo = self.ammo_nails;
- else if (self.items & IT_SHELLS)
- self.currentammo = self.ammo_shells;
- else
- self.currentammo = 1;
-#endif
-}
-
-string W_Apply_Weaponreplace(string in)
-{
- float n = tokenize_console(in);
- string out = "";
- float i;
- for(i = 0; i < n; ++i)
- {
- string s = argv(i);
- string r = cvar_string(strcat("g_weaponreplace_", s));
- if(r == "")
- out = strcat(out, " ", s);
- else if(r != "0")
- out = strcat(out, " ", r);
- }
- return substring(out, 1, -1);
-}
+++ /dev/null
-/*
-===========================================================================
-
- CLIENT WEAPONSYSTEM CODE
- Bring back W_Weaponframe
-
-===========================================================================
-*/
-
-.float weapon_frametime;
-
-float W_WeaponRateFactor()
-{
- float t;
- t = 1.0 / g_weaponratefactor;
-
- return t;
-}
-
-void W_SwitchWeapon_Force(entity e, float w)
-{
- e.cnt = e.switchweapon;
- e.switchweapon = w;
- e.selectweapon = w;
-}
-
-.float antilag_debug;
-
-// VorteX: static frame globals
-float WFRAME_DONTCHANGE = -1;
-float WFRAME_FIRE1 = 0;
-float WFRAME_FIRE2 = 1;
-float WFRAME_IDLE = 2;
-float WFRAME_RELOAD = 3;
-.float wframe;
-
-void(float fr, float t, void() func) weapon_thinkf;
-
-vector W_HitPlotUnnormalizedUntransform(vector screenforward, vector screenright, vector screenup, vector v)
-{
- vector ret;
- ret_x = screenright * v;
- ret_y = screenup * v;
- ret_z = screenforward * v;
- return ret;
-}
-
-vector W_HitPlotNormalizedUntransform(vector org, entity targ, vector screenforward, vector screenright, vector screenup, vector v)
-{
- float i, j, k;
- vector mi, ma, thisv, myv, ret;
-
- myv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, org);
-
- // x = 0..1 relative to hitbox; y = 0..1 relative to hitbox; z = distance
-
- mi = ma = targ.origin + 0.5 * (targ.mins + targ.maxs);
- for(i = 0; i < 2; ++i) for(j = 0; j < 2; ++j) for(k = 0; k < 2; ++k)
- {
- thisv = targ.origin;
- if(i) thisv_x += targ.maxs_x; else thisv_x += targ.mins_x;
- if(j) thisv_y += targ.maxs_y; else thisv_y += targ.mins_y;
- if(k) thisv_z += targ.maxs_z; else thisv_z += targ.mins_z;
- thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, thisv);
- if(i || j || k)
- {
- if(mi_x > thisv_x) mi_x = thisv_x; if(ma_x < thisv_x) ma_x = thisv_x;
- if(mi_y > thisv_y) mi_y = thisv_y; if(ma_y < thisv_y) ma_y = thisv_y;
- //if(mi_z > thisv_z) mi_z = thisv_z; if(ma_z < thisv_z) ma_y = thisv_z;
- }
- else
- {
- // first run
- mi = ma = thisv;
- }
- }
-
- thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, v);
- ret_x = (thisv_x - mi_x) / (ma_x - mi_x);
- ret_y = (thisv_y - mi_y) / (ma_y - mi_y);
- ret_z = thisv_z - myv_z;
- return ret;
-}
-
-void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright, vector screenup)
-{
- vector hitplot;
- vector org;
- float lag;
-
- if(player.hitplotfh >= 0)
- {
- lag = ANTILAG_LATENCY(player);
- if(lag < 0.001)
- lag = 0;
- if not(IS_REAL_CLIENT(player))
- lag = 0; // only antilag for clients
-
- org = player.origin + player.view_ofs;
- traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag);
- if(IS_CLIENT(trace_ent))
- {
- antilag_takeback(trace_ent, time - lag);
- hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos);
- antilag_restore(trace_ent);
- fputs(player.hitplotfh, strcat(ftos(hitplot_x), " ", ftos(hitplot_y), " ", ftos(hitplot_z), " ", ftos(player.switchweapon), "\n"));
- //print(strcat(ftos(hitplot_x), " ", ftos(hitplot_y), " ", ftos(hitplot_z), "\n"));
- }
- }
-}
-
-vector w_shotorg;
-vector w_shotdir;
-vector w_shotend;
-
-.float prevstrengthsound;
-.float prevstrengthsoundattempt;
-void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound
-{
- if((player.items & IT_STRENGTH)
- && ((time > player.prevstrengthsound + autocvar_sv_strengthsound_antispam_time) // prevent insane sound spam
- || (time > player.prevstrengthsoundattempt + autocvar_sv_strengthsound_antispam_refire_threshold)))
- {
- sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
- player.prevstrengthsound = time;
- }
- player.prevstrengthsoundattempt = time;
-}
-
-// this function calculates w_shotorg and w_shotdir based on the weapon model
-// offset, trueaim and antilag, and won't put w_shotorg inside a wall.
-// make sure you call makevectors first (FIXME?)
-void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector mi, vector ma, float antilag, float recoil, string snd, float chan, float maxdamage, float range)
-{
- float nudge = 1; // added to traceline target and subtracted from result
- float oldsolid;
- vector vecs, dv;
- oldsolid = ent.dphitcontentsmask;
- if(ent.weapon == WEP_RIFLE)
- ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
- else
- ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
- if(antilag)
- WarpZone_traceline_antilag(world, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
- // passing world, because we do NOT want it to touch dphitcontentsmask
- else
- WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent);
- ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
-
- vector vf, vr, vu;
- vf = v_forward;
- vr = v_right;
- vu = v_up;
- w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
- v_forward = vf;
- v_right = vr;
- v_up = vu;
-
- // un-adjust trueaim if shotend is too close
- if(vlen(w_shotend - (ent.origin + ent.view_ofs)) < autocvar_g_trueaim_minrange)
- w_shotend = ent.origin + ent.view_ofs + s_forward * autocvar_g_trueaim_minrange;
-
- // track max damage
- if(accuracy_canbegooddamage(ent))
- accuracy_add(ent, ent.weapon, maxdamage, 0);
-
- W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
-
- if(ent.weaponentity.movedir_x > 0)
- vecs = ent.weaponentity.movedir;
- else
- vecs = '0 0 0';
-
- dv = v_right * -vecs_y + v_up * vecs_z;
- w_shotorg = ent.origin + ent.view_ofs + dv;
-
- // now move the shotorg forward as much as requested if possible
- if(antilag)
- {
- if(ent.antilag_debug)
- tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ent.antilag_debug);
- else
- tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
- }
- else
- tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent);
- w_shotorg = trace_endpos - v_forward * nudge;
- // calculate the shotdir from the chosen shotorg
- w_shotdir = normalize(w_shotend - w_shotorg);
-
- if (antilag)
- if (!ent.cvar_cl_noantilag)
- {
- if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
- {
- traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
- if (!trace_ent.takedamage)
- {
- traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
- if (trace_ent.takedamage && IS_PLAYER(trace_ent))
- {
- entity e;
- e = trace_ent;
- traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
- if(trace_ent == e)
- w_shotdir = normalize(trace_ent.origin - w_shotorg);
- }
- }
- }
- else if(autocvar_g_antilag == 3) // client side hitscan
- {
- // this part MUST use prydon cursor
- if (ent.cursor_trace_ent) // client was aiming at someone
- if (ent.cursor_trace_ent != ent) // just to make sure
- if (ent.cursor_trace_ent.takedamage) // and that person is killable
- if (IS_PLAYER(ent.cursor_trace_ent)) // and actually a player
- {
- // verify that the shot would miss without antilag
- // (avoids an issue where guns would always shoot at their origin)
- traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
- if (!trace_ent.takedamage)
- {
- // verify that the shot would hit if altered
- traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
- if (trace_ent == ent.cursor_trace_ent)
- w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
- else
- print("antilag fail\n");
- }
- }
- }
- }
-
- ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
-
- if (!g_norecoil)
- ent.punchangle_x = recoil * -1;
-
- if (snd != "")
- {
- sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
- W_PlayStrengthSound(ent);
- }
-
- // nudge w_shotend so a trace to w_shotend hits
- w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
-}
-
-#define W_SetupShot_Dir_ProjectileSize(ent,s_forward,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize_Range(ent, s_forward, mi, ma, antilag, recoil, snd, chan, maxdamage, MAX_SHOT_DISTANCE)
-#define W_SetupShot_ProjectileSize(ent,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, v_forward, mi, ma, antilag, recoil, snd, chan, maxdamage)
-#define W_SetupShot_Dir(ent,s_forward,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, s_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
-#define W_SetupShot(ent,antilag,recoil,snd,chan,maxdamage) W_SetupShot_ProjectileSize(ent, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
-#define W_SetupShot_Range(ent,antilag,recoil,snd,chan,maxdamage,range) W_SetupShot_Dir_ProjectileSize_Range(ent, v_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage, range)
-
-float CL_Weaponentity_CustomizeEntityForClient()
-{
- self.viewmodelforclient = self.owner;
- if(IS_SPEC(other))
- if(other.enemy == self.owner)
- self.viewmodelforclient = other;
- return TRUE;
-}
-
-/*
- * supported formats:
- *
- * 1. simple animated model, muzzle flash handling on h_ model:
- * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
- * tags:
- * shot = muzzle end (shot origin, also used for muzzle flashes)
- * shell = casings ejection point (must be on the right hand side of the gun)
- * weapon = attachment for v_tuba.md3
- * v_tuba.md3 - first and third person model
- * g_tuba.md3 - pickup model
- *
- * 2. simple animated model, muzzle flash handling on v_ model:
- * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
- * tags:
- * weapon = attachment for v_tuba.md3
- * v_tuba.md3 - first and third person model
- * tags:
- * shot = muzzle end (shot origin, also used for muzzle flashes)
- * shell = casings ejection point (must be on the right hand side of the gun)
- * g_tuba.md3 - pickup model
- *
- * 3. fully animated model, muzzle flash handling on h_ model:
- * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
- * tags:
- * shot = muzzle end (shot origin, also used for muzzle flashes)
- * shell = casings ejection point (must be on the right hand side of the gun)
- * handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
- * v_tuba.md3 - third person model
- * g_tuba.md3 - pickup model
- *
- * 4. fully animated model, muzzle flash handling on v_ model:
- * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
- * tags:
- * shot = muzzle end (shot origin)
- * shell = casings ejection point (must be on the right hand side of the gun)
- * v_tuba.md3 - third person model
- * tags:
- * shot = muzzle end (for muzzle flashes)
- * g_tuba.md3 - pickup model
- */
-
-// writes:
-// self.origin, self.angles
-// self.weaponentity
-// self.movedir, self.view_ofs
-// attachment stuff
-// anim stuff
-// to free:
-// call again with ""
-// remove the ent
-void CL_WeaponEntity_SetModel(string name)
-{
- float v_shot_idx;
- if (name != "")
- {
- // if there is a child entity, hide it until we're sure we use it
- if (self.weaponentity)
- self.weaponentity.model = "";
- setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
- v_shot_idx = gettagindex(self, "shot"); // used later
- if(!v_shot_idx)
- v_shot_idx = gettagindex(self, "tag_shot");
-
- setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
- // preset some defaults that work great for renamed zym files (which don't need an animinfo)
- self.anim_fire1 = animfixfps(self, '0 1 0.01', '0 0 0');
- self.anim_fire2 = animfixfps(self, '1 1 0.01', '0 0 0');
- self.anim_idle = animfixfps(self, '2 1 0.01', '0 0 0');
- self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
-
- // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
- // if we don't, this is a "real" animated model
- if(gettagindex(self, "weapon"))
- {
- if (!self.weaponentity)
- self.weaponentity = spawn();
- setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
- setattachment(self.weaponentity, self, "weapon");
- }
- else if(gettagindex(self, "tag_weapon"))
- {
- if (!self.weaponentity)
- self.weaponentity = spawn();
- setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
- setattachment(self.weaponentity, self, "tag_weapon");
- }
- else
- {
- if(self.weaponentity)
- remove(self.weaponentity);
- self.weaponentity = world;
- }
-
- setorigin(self,'0 0 0');
- self.angles = '0 0 0';
- self.frame = 0;
- self.viewmodelforclient = world;
-
- float idx;
-
- if(v_shot_idx) // v_ model attached to invisible h_ model
- {
- self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
- }
- else
- {
- idx = gettagindex(self, "shot");
- if(!idx)
- idx = gettagindex(self, "tag_shot");
- if(idx)
- self.movedir = gettaginfo(self, idx);
- else
- {
- print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
- self.movedir = '0 0 0';
- }
- }
-
- if(self.weaponentity) // v_ model attached to invisible h_ model
- {
- idx = gettagindex(self.weaponentity, "shell");
- if(!idx)
- idx = gettagindex(self.weaponentity, "tag_shell");
- if(idx)
- self.spawnorigin = gettaginfo(self.weaponentity, idx);
- }
- else
- idx = 0;
- if(!idx)
- {
- idx = gettagindex(self, "shell");
- if(!idx)
- idx = gettagindex(self, "tag_shell");
- if(idx)
- self.spawnorigin = gettaginfo(self, idx);
- else
- {
- print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
- self.spawnorigin = self.movedir;
- }
- }
-
- if(v_shot_idx)
- {
- self.oldorigin = '0 0 0'; // use regular attachment
- }
- else
- {
- if(self.weaponentity)
- {
- idx = gettagindex(self, "weapon");
- if(!idx)
- idx = gettagindex(self, "tag_weapon");
- }
- else
- {
- idx = gettagindex(self, "handle");
- if(!idx)
- idx = gettagindex(self, "tag_handle");
- }
- if(idx)
- {
- self.oldorigin = self.movedir - gettaginfo(self, idx);
- }
- else
- {
- print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n");
- self.oldorigin = '0 0 0'; // there is no way to recover from this
- }
- }
-
- self.viewmodelforclient = self.owner;
- }
- else
- {
- self.model = "";
- if(self.weaponentity)
- remove(self.weaponentity);
- self.weaponentity = world;
- self.movedir = '0 0 0';
- self.spawnorigin = '0 0 0';
- self.oldorigin = '0 0 0';
- self.anim_fire1 = '0 1 0.01';
- self.anim_fire2 = '0 1 0.01';
- self.anim_idle = '0 1 0.01';
- self.anim_reload = '0 1 0.01';
- }
-
- self.view_ofs = '0 0 0';
-
- if(self.movedir_x >= 0)
- {
- vector v0;
- v0 = self.movedir;
- self.movedir = shotorg_adjust(v0, FALSE, FALSE);
- self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
- }
- self.owner.stat_shotorg = compressShotOrigin(self.movedir);
- self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
-
- self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
-
- // check if an instant weapon switch occurred
- setorigin(self, self.view_ofs);
- // reset animstate now
- self.wframe = WFRAME_IDLE;
- setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
-}
-
-vector CL_Weapon_GetShotOrg(float wpn)
-{
- entity wi, oldself;
- vector ret;
- wi = get_weaponinfo(wpn);
- oldself = self;
- self = spawn();
- CL_WeaponEntity_SetModel(wi.mdl);
- ret = self.movedir;
- CL_WeaponEntity_SetModel("");
- remove(self);
- self = oldself;
- return ret;
-}
-
-void CL_Weaponentity_Think()
-{
- float tb;
- self.nextthink = time;
- if (intermission_running)
- self.frame = self.anim_idle_x;
- if (self.owner.weaponentity != self)
- {
- if (self.weaponentity)
- remove(self.weaponentity);
- remove(self);
- return;
- }
- if (self.owner.deadflag != DEAD_NO)
- {
- self.model = "";
- if (self.weaponentity)
- self.weaponentity.model = "";
- return;
- }
- if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
- {
- self.weaponname = self.owner.weaponname;
- self.dmg = self.owner.modelindex;
- self.deadflag = self.owner.deadflag;
-
- CL_WeaponEntity_SetModel(self.owner.weaponname);
- }
-
- tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
- self.effects = self.owner.effects & EFMASK_CHEAP;
- self.effects &~= EF_LOWPRECISION;
- self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it
- self.effects &~= EF_TELEPORT_BIT;
- self.effects &~= EF_RESTARTANIM_BIT;
- self.effects |= tb;
-
- if(self.owner.alpha == default_player_alpha)
- self.alpha = default_weapon_alpha;
- else if(self.owner.alpha != 0)
- self.alpha = self.owner.alpha;
- else
- self.alpha = 1;
-
- self.glowmod = self.owner.weaponentity_glowmod;
- self.colormap = self.owner.colormap;
- if (self.weaponentity)
- {
- self.weaponentity.effects = self.effects;
- self.weaponentity.alpha = self.alpha;
- self.weaponentity.colormap = self.colormap;
- self.weaponentity.glowmod = self.glowmod;
- }
-
- self.angles = '0 0 0';
-
- float f = (self.owner.weapon_nextthink - time);
- if (self.state == WS_RAISE && !intermission_running)
- {
- entity newwep = get_weaponinfo(self.owner.switchweapon);
- f = f * g_weaponratefactor / max(f, cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)));
- //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), (self.owner.weapon_nextthink - time)));
- self.angles_x = -90 * f * f;
- }
- else if (self.state == WS_DROP && !intermission_running)
- {
- entity oldwep = get_weaponinfo(self.owner.weapon);
- f = 1 - f * g_weaponratefactor / max(f, cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)));
- //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), (self.owner.weapon_nextthink - time)));
- self.angles_x = -90 * f * f;
- }
- else if (self.state == WS_CLEAR)
- {
- f = 1;
- self.angles_x = -90 * f * f;
- }
-}
-
-void CL_ExteriorWeaponentity_Think()
-{
- float tag_found;
- self.nextthink = time;
- if (self.owner.exteriorweaponentity != self)
- {
- remove(self);
- return;
- }
- if (self.owner.deadflag != DEAD_NO)
- {
- self.model = "";
- return;
- }
- if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
- {
- self.weaponname = self.owner.weaponname;
- self.dmg = self.owner.modelindex;
- self.deadflag = self.owner.deadflag;
- if (self.owner.weaponname != "")
- setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
- else
- self.model = "";
-
- if((tag_found = gettagindex(self.owner, "tag_weapon")))
- {
- self.tag_index = tag_found;
- self.tag_entity = self.owner;
- }
- else
- setattachment(self, self.owner, "bip01 r hand");
- }
- self.effects = self.owner.effects;
- self.effects |= EF_LOWPRECISION;
- self.effects = self.effects & EFMASK_CHEAP; // eat performance
- if(self.owner.alpha == default_player_alpha)
- self.alpha = default_weapon_alpha;
- else if(self.owner.alpha != 0)
- self.alpha = self.owner.alpha;
- else
- self.alpha = 1;
-
- self.glowmod = self.owner.weaponentity_glowmod;
- self.colormap = self.owner.colormap;
-
- CSQCMODEL_AUTOUPDATE();
-}
-
-// spawning weaponentity for client
-void CL_SpawnWeaponentity()
-{
- self.weaponentity = spawn();
- self.weaponentity.classname = "weaponentity";
- self.weaponentity.solid = SOLID_NOT;
- self.weaponentity.owner = self;
- setmodel(self.weaponentity, ""); // precision set when changed
- setorigin(self.weaponentity, '0 0 0');
- self.weaponentity.angles = '0 0 0';
- self.weaponentity.viewmodelforclient = self;
- self.weaponentity.flags = 0;
- self.weaponentity.think = CL_Weaponentity_Think;
- self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
- self.weaponentity.nextthink = time;
-
- self.exteriorweaponentity = spawn();
- self.exteriorweaponentity.classname = "exteriorweaponentity";
- self.exteriorweaponentity.solid = SOLID_NOT;
- self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
- self.exteriorweaponentity.owner = self;
- setorigin(self.exteriorweaponentity, '0 0 0');
- self.exteriorweaponentity.angles = '0 0 0';
- self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
- self.exteriorweaponentity.nextthink = time;
-
- {
- entity oldself = self;
- self = self.exteriorweaponentity;
- CSQCMODEL_AUTOINIT();
- self = oldself;
- }
-}
-
-void Send_WeaponComplain (entity e, float wpn, string wpnname, float type)
-{
- msg_entity = e;
- WriteByte(MSG_ONE, SVC_TEMPENTITY);
- WriteByte(MSG_ONE, TE_CSQC_WEAPONCOMPLAIN);
- WriteByte(MSG_ONE, wpn);
- WriteString(MSG_ONE, wpnname);
- WriteByte(MSG_ONE, type);
-}
-
-.float hasweapon_complain_spam;
-
-float client_hasweapon(entity cl, float wpn, float andammo, float complain)
-{
- float f;
- entity oldself;
-
- if(time < self.hasweapon_complain_spam)
- complain = 0;
- if(complain)
- self.hasweapon_complain_spam = time + 0.2;
-
- if (wpn < WEP_FIRST || wpn > WEP_LAST)
- {
- if (complain)
- sprint(self, "Invalid weapon\n");
- return FALSE;
- }
- if (WEPSET_CONTAINS_EW(cl, wpn))
- {
- if (andammo)
- {
- if(cl.items & IT_UNLIMITED_WEAPON_AMMO)
- {
- f = 1;
- }
- else
- {
- oldself = self;
- self = cl;
- f = weapon_action(wpn, WR_CHECKAMMO1);
- f = f + weapon_action(wpn, WR_CHECKAMMO2);
-
- // always allow selecting the Mine Layer if we placed mines, so that we can detonate them
- entity mine;
- if(wpn == WEP_MINE_LAYER)
- for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
- f = 1;
-
- self = oldself;
- }
- if (!f)
- {
- if (complain)
- if(IS_REAL_CLIENT(cl))
- {
- play2(cl, "weapons/unavailable.wav");
- Send_WeaponComplain (cl, wpn, W_Name(wpn), 0);
- }
- return FALSE;
- }
- }
- return TRUE;
- }
- if (complain)
- {
- // DRESK - 3/16/07
- // Report Proper Weapon Status / Modified Weapon Ownership Message
- if (WEPSET_CONTAINS_AW(weaponsInMap, wpn))
- {
- Send_WeaponComplain(cl, wpn, W_Name(wpn), 1);
-
- if(autocvar_g_showweaponspawns)
- {
- entity e;
- string s;
-
- e = get_weaponinfo(wpn);
- s = e.model2;
-
- for(e = world; (e = findfloat(e, weapon, wpn)); )
- {
- if(e.classname == "droppedweapon")
- continue;
- if not(e.flags & FL_ITEM)
- continue;
- WaypointSprite_Spawn(
- s,
- 1, 0,
- world, e.origin,
- self, 0,
- world, enemy,
- 0,
- RADARICON_NONE, '0 0 0'
- );
- }
- }
- }
- else
- {
- Send_WeaponComplain (cl, wpn, W_Name(wpn), 2);
- }
-
- play2(cl, "weapons/unavailable.wav");
- }
- return FALSE;
-}
-
-// Weapon subs
-void w_clear()
-{
- if (self.weapon != -1)
- {
- self.weapon = 0;
- self.switchingweapon = 0;
- }
- if (self.weaponentity)
- {
- self.weaponentity.state = WS_CLEAR;
- self.weaponentity.effects = 0;
- }
-}
-
-void w_ready()
-{
- if (self.weaponentity)
- self.weaponentity.state = WS_READY;
- weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
-}
-
-// Setup weapon for client (after this raise frame will be launched)
-void weapon_setup(float windex)
-{
- entity e;
- e = get_weaponinfo(windex);
- self.items &~= IT_AMMO;
- self.items = self.items | (e.items & IT_AMMO);
-
- // the two weapon entities will notice this has changed and update their models
- self.weapon = windex;
- self.switchingweapon = windex; // to make sure
- self.weaponname = e.mdl;
- self.bulletcounter = 0;
-}
-
-// perform weapon to attack (weaponstate and attack_finished check is here)
-void W_SwitchToOtherWeapon(entity pl)
-{
- // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)
- float w, ww;
- w = pl.weapon;
- if(WEPSET_CONTAINS_EW(pl, w))
- {
- WEPSET_ANDNOT_EW(pl, w);
- ww = w_getbestweapon(pl);
- WEPSET_OR_EW(pl, w);
- }
- else
- ww = w_getbestweapon(pl);
- if(ww)
- W_SwitchWeapon_Force(pl, ww);
-}
-
-.float prevdryfire;
-.float prevwarntime;
-float weapon_prepareattack_checkammo(float secondary)
-{
- if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
- if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))
- {
- // always keep the Mine Layer if we placed mines, so that we can detonate them
- entity mine;
- if(self.weapon == WEP_MINE_LAYER)
- for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
- return FALSE;
-
- if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
- {
- sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM);
- self.prevdryfire = time;
- }
-
- if(weapon_action(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
- {
- if(time - self.prevwarntime > 1)
- {
- Send_Notification(
- NOTIF_ONE,
- self,
- MSG_MULTI,
- ITEM_WEAPON_PRIMORSEC,
- self.weapon,
- secondary,
- (1 - secondary)
- );
- }
- self.prevwarntime = time;
- }
- else // this weapon is totally unable to fire, switch to another one
- {
- W_SwitchToOtherWeapon(self);
- }
-
- return FALSE;
- }
- return TRUE;
-}
-.float race_penalty;
-float weapon_prepareattack_check(float secondary, float attacktime)
-{
- if(!weapon_prepareattack_checkammo(secondary))
- return FALSE;
-
- //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
- //if all players readied up and the countdown is running
- if(time < game_starttime || time < self.race_penalty) {
- return FALSE;
- }
-
- if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
- return FALSE;
-
- // do not even think about shooting if switching
- if(self.switchweapon != self.weapon)
- return FALSE;
-
- if(attacktime >= 0)
- {
- // don't fire if previous attack is not finished
- if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
- return FALSE;
- // don't fire while changing weapon
- if (self.weaponentity.state != WS_READY)
- return FALSE;
- }
-
- return TRUE;
-}
-float weapon_prepareattack_do(float secondary, float attacktime)
-{
- self.weaponentity.state = WS_INUSE;
-
- self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
-
- // if the weapon hasn't been firing continuously, reset the timer
- if(attacktime >= 0)
- {
- if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
- {
- ATTACK_FINISHED(self) = time;
- //dprint("resetting attack finished to ", ftos(time), "\n");
- }
- ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
- }
- self.bulletcounter += 1;
- //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
- return TRUE;
-}
-float weapon_prepareattack(float secondary, float attacktime)
-{
- if(weapon_prepareattack_check(secondary, attacktime))
- {
- weapon_prepareattack_do(secondary, attacktime);
- return TRUE;
- }
- else
- return FALSE;
-}
-
-void weapon_thinkf(float fr, float t, void() func)
-{
- vector a;
- vector of, or, ou;
- float restartanim;
-
- if(fr == WFRAME_DONTCHANGE)
- {
- fr = self.weaponentity.wframe;
- restartanim = FALSE;
- }
- else if (fr == WFRAME_IDLE)
- restartanim = FALSE;
- else
- restartanim = TRUE;
-
- of = v_forward;
- or = v_right;
- ou = v_up;
-
- if (self.weaponentity)
- {
- self.weaponentity.wframe = fr;
- a = '0 0 0';
- if (fr == WFRAME_IDLE)
- a = self.weaponentity.anim_idle;
- else if (fr == WFRAME_FIRE1)
- a = self.weaponentity.anim_fire1;
- else if (fr == WFRAME_FIRE2)
- a = self.weaponentity.anim_fire2;
- else // if (fr == WFRAME_RELOAD)
- a = self.weaponentity.anim_reload;
- a_z *= g_weaponratefactor;
- setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
- }
-
- v_forward = of;
- v_right = or;
- v_up = ou;
-
- if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
- {
- backtrace("Tried to override initial weapon think function - should this really happen?");
- }
-
- t *= W_WeaponRateFactor();
-
- // VorteX: haste can be added here
- if (self.weapon_think == w_ready)
- {
- self.weapon_nextthink = time;
- //dprint("started firing at ", ftos(time), "\n");
- }
- if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
- {
- self.weapon_nextthink = time;
- //dprint("reset weapon animation timer at ", ftos(time), "\n");
- }
- self.weapon_nextthink = self.weapon_nextthink + t;
- self.weapon_think = func;
- //dprint("next ", ftos(self.weapon_nextthink), "\n");
-
- if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
- {
- if(self.weapon == WEP_SHOTGUN && fr == WFRAME_FIRE2)
- animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
- else
- animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
- }
- else
- {
- if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
- self.anim_upper_action = 0;
- }
-}
-
-void weapon_boblayer1(float spd, vector org)
-{
- // VorteX: haste can be added here
-}
-
-vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity, float forceAbsolute)
-{
- vector mdirection;
- float mspeed;
- vector outvelocity;
-
- mvelocity = mvelocity * g_weaponspeedfactor;
-
- mdirection = normalize(mvelocity);
- mspeed = vlen(mvelocity);
-
- outvelocity = get_shotvelocity(pvelocity, mdirection, mspeed, (forceAbsolute ? 0 : autocvar_g_projectiles_newton_style), autocvar_g_projectiles_newton_style_2_minfactor, autocvar_g_projectiles_newton_style_2_maxfactor);
-
- return outvelocity;
-}
-
-void W_AttachToShotorg(entity flash, vector offset)
-{
- entity xflash;
- flash.owner = self;
- flash.angles_z = random() * 360;
-
- if(gettagindex(self.weaponentity, "shot"))
- setattachment(flash, self.weaponentity, "shot");
- else
- setattachment(flash, self.weaponentity, "tag_shot");
- setorigin(flash, offset);
-
- xflash = spawn();
- copyentity(flash, xflash);
-
- flash.viewmodelforclient = self;
-
- if(self.weaponentity.oldorigin_x > 0)
- {
- setattachment(xflash, self.exteriorweaponentity, "");
- setorigin(xflash, self.weaponentity.oldorigin + offset);
- }
- else
- {
- if(gettagindex(self.exteriorweaponentity, "shot"))
- setattachment(xflash, self.exteriorweaponentity, "shot");
- else
- setattachment(xflash, self.exteriorweaponentity, "tag_shot");
- setorigin(xflash, offset);
- }
-}
-
-#if 0
-float mspercallsum;
-float mspercallsstyle;
-float mspercallcount;
-#endif
-void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
-{
- if(missile.owner == world)
- error("Unowned missile");
-
- dir = dir + upDir * (pUpSpeed / pSpeed);
- dir_z += pZSpeed / pSpeed;
- pSpeed *= vlen(dir);
- dir = normalize(dir);
-
-#if 0
- if(autocvar_g_projectiles_spread_style != mspercallsstyle)
- {
- mspercallsum = mspercallcount = 0;
- mspercallsstyle = autocvar_g_projectiles_spread_style;
- }
- mspercallsum -= gettime(GETTIME_HIRES);
-#endif
- dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
-#if 0
- mspercallsum += gettime(GETTIME_HIRES);
- mspercallcount += 1;
- print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
-#endif
-
- missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir, forceAbsolute);
-}
-
-void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)
-{
- W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread, FALSE);
-}
-
-#define W_SETUPPROJECTILEVELOCITY_UP(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), cvar(#s "_speed_up"), cvar(#s "_speed_z"), cvar(#s "_spread"), FALSE)
-#define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"), FALSE)
-
-void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload)
-{
- if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
- return;
-
- // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
- if(ammo_reload)
- {
- self.clip_load -= ammo_use;
- self.(weapon_load[self.weapon]) = self.clip_load;
- }
- else
- self.(self.current_ammo) -= ammo_use;
-}
-
-// weapon reloading code
-
-.float reload_ammo_amount, reload_ammo_min, reload_time;
-.float reload_complain;
-.string reload_sound;
-
-void W_ReloadedAndReady()
-{
- // finish the reloading process, and do the ammo transfer
-
- self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
-
- // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
- if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)
- self.clip_load = self.reload_ammo_amount;
- else
- {
- while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have
- {
- self.clip_load += 1;
- self.(self.current_ammo) -= 1;
- }
- }
- self.(weapon_load[self.weapon]) = self.clip_load;
-
- // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
- // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
- // so your weapon is disabled for a few seconds without reason
-
- //ATTACK_FINISHED(self) -= self.reload_time - 1;
-
- w_ready();
-}
-
-void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)
-{
- // set global values to work with
-
- self.reload_ammo_min = sent_ammo_min;
- self.reload_ammo_amount = sent_ammo_amount;
- self.reload_time = sent_time;
- self.reload_sound = sent_sound;
-
- // check if we meet the necessary conditions to reload
-
- entity e;
- e = get_weaponinfo(self.weapon);
-
- // don't reload weapons that don't have the RELOADABLE flag
- if not(e.spawnflags & WEP_FLAG_RELOADABLE)
- {
- dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
- return;
- }
-
- // return if reloading is disabled for this weapon
- if(!self.reload_ammo_amount)
- return;
-
- // our weapon is fully loaded, no need to reload
- if (self.clip_load >= self.reload_ammo_amount)
- return;
-
- // no ammo, so nothing to load
- if(!self.(self.current_ammo) && self.reload_ammo_min)
- if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
- {
- if(IS_REAL_CLIENT(self) && self.reload_complain < time)
- {
- play2(self, "weapons/unavailable.wav");
- sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
- self.reload_complain = time + 1;
- }
- // switch away if the amount of ammo is not enough to keep using this weapon
- if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2))
- {
- self.clip_load = -1; // reload later
- W_SwitchToOtherWeapon(self);
- }
- return;
- }
-
- if (self.weaponentity)
- {
- if (self.weaponentity.wframe == WFRAME_RELOAD)
- return;
-
- // allow switching away while reloading, but this will cause a new reload!
- self.weaponentity.state = WS_READY;
- }
-
- // now begin the reloading process
-
- sound (self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTN_NORM);
-
- // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
- // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
- // so your weapon is disabled for a few seconds without reason
-
- //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
-
- weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
-
- if(self.clip_load < 0)
- self.clip_load = 0;
- self.old_clip_load = self.clip_load;
- self.clip_load = self.(weapon_load[self.weapon]) = -1;
-}
portals.qh
g_hook.qh
-w_electro.qh
-w_lightning.qh
+weapons/w_electro.qh
+weapons/w_lightning.qh
scores.qh
item_key.qc
secret.qc
-cl_weaponsystem.qc
-w_common.qc
+weapons/cl_weaponsystem.qc
+weapons/w_common.qc
-w_all.qc
+weapons/w_all.qc
t_items.qc
-cl_weapons.qc
+weapons/cl_weapons.qc
cl_impulse.qc
ent_cs.qc
+++ /dev/null
-// ONLY EVER ADD NEW WEAPONS AT THE END. IF YOU REMOVE ONE, PUT THE LAST ONE ON
-// ITS PLACE. THIS IS TO AVOID UNNECESSARY RENUMBERING OF WEAPON IMPULSES.
-// IF YOU DISREGARD THIS NOTICE, I'LL KILL YOU WITH THE @!#%'N TUBA
-#include "w_laser.qc"
-#include "w_shotgun.qc"
-#include "w_uzi.qc"
-#include "w_grenadelauncher.qc"
-#include "w_minelayer.qc"
-#include "w_electro.qc"
-#include "w_lightning.qc"
-#include "w_crylink.qc"
-#include "w_nex.qc"
-#include "w_hagar.qc"
-#include "w_rocketlauncher.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"
+++ /dev/null
-
-void W_GiveWeapon (entity e, float wep)
-{
- entity oldself;
-
- if (!wep)
- return;
-
- WEPSET_OR_EW(e, wep);
-
- oldself = self;
- self = e;
-
- if(IS_PLAYER(other))
- { Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_WEAPON_GOT, wep); }
-
- self = oldself;
-}
-
-.float railgundistance;
-.vector railgunforce;
-void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype)
-{
- vector hitloc, force, endpoint, dir;
- entity ent, endent;
- float endq3surfaceflags;
- float totaldmg;
- entity o;
-
- float length;
- vector beampos;
- string snd;
- entity pseudoprojectile;
- float f, ffs;
-
- pseudoprojectile = world;
-
- railgun_start = start;
- railgun_end = end;
-
- dir = normalize(end - start);
- length = vlen(end - start);
- force = dir * bforce;
-
- // go a little bit into the wall because we need to hit this wall later
- end = end + dir;
-
- totaldmg = 0;
-
- // trace multiple times until we hit a wall, each obstacle will be made
- // non-solid so we can hit the next, while doing this we spawn effects and
- // note down which entities were hit so we can damage them later
- o = self;
- while (1)
- {
- if(self.antilag_debug)
- WarpZone_traceline_antilag (self, start, end, FALSE, o, self.antilag_debug);
- else
- WarpZone_traceline_antilag (self, start, end, FALSE, o, ANTILAG_LATENCY(self));
- if(o && WarpZone_trace_firstzone)
- {
- o = world;
- continue;
- }
-
- if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
- Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, self);
-
- // if it is world we can't hurt it so stop now
- if (trace_ent == world || trace_fraction == 1)
- break;
-
- // make the entity non-solid so we can hit the next one
- trace_ent.railgunhit = TRUE;
- trace_ent.railgunhitloc = end;
- trace_ent.railgunhitsolidbackup = trace_ent.solid;
- trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
- trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
-
- // stop if this is a wall
- if (trace_ent.solid == SOLID_BSP)
- break;
-
- // make the entity non-solid
- trace_ent.solid = SOLID_NOT;
- }
-
- endpoint = trace_endpos;
- endent = trace_ent;
- endq3surfaceflags = trace_dphitq3surfaceflags;
-
- // find all the entities the railgun hit and restore their solid state
- ent = findfloat(world, railgunhit, TRUE);
- while (ent)
- {
- // restore their solid type
- ent.solid = ent.railgunhitsolidbackup;
- ent = findfloat(ent, railgunhit, TRUE);
- }
-
- // spawn a temporary explosion entity for RadiusDamage calls
- //explosion = spawn();
-
- // Find all non-hit players the beam passed close by
- if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
- {
- FOR_EACH_REALCLIENT(msg_entity) if(msg_entity != self) if(!msg_entity.railgunhit) if not(IS_SPEC(msg_entity) && msg_entity.enemy == self) // we use realclient, so spectators can hear the whoosh too
- {
- // nearest point on the beam
- beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
-
- f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
- if(f <= 0)
- continue;
-
- snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
-
- if(!pseudoprojectile)
- pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
- soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTN_NONE);
- }
-
- if(pseudoprojectile)
- remove(pseudoprojectile);
- }
-
- // find all the entities the railgun hit and hurt them
- ent = findfloat(world, railgunhit, TRUE);
- while (ent)
- {
- // get the details we need to call the damage function
- hitloc = ent.railgunhitloc;
-
- f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
- ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
-
- if(accuracy_isgooddamage(self.realowner, ent))
- totaldmg += bdamage * f;
-
- // apply the damage
- if (ent.takedamage)
- Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
-
- // create a small explosion to throw gibs around (if applicable)
- //setorigin (explosion, hitloc);
- //RadiusDamage (explosion, self, 10, 0, 50, world, world, 300, deathtype);
-
- ent.railgunhitloc = '0 0 0';
- ent.railgunhitsolidbackup = SOLID_NOT;
- ent.railgunhit = FALSE;
- ent.railgundistance = 0;
-
- // advance to the next entity
- ent = findfloat(ent, railgunhit, TRUE);
- }
-
- // calculate hits and fired shots for hitscan
- accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
-
- trace_endpos = endpoint;
- trace_ent = endent;
- trace_dphitq3surfaceflags = endq3surfaceflags;
-}
-
-.float dmg_force;
-.float dmg_radius;
-.float dmg_total;
-//.float last_yoda;
-void W_BallisticBullet_Hit (void)
-{
- float f, q, g;
-
- f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
- q = 1 + self.dmg_edge / self.dmg;
-
- if(other.solid == SOLID_BSP || other.solid == SOLID_SLIDEBOX)
- Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, other.species, self);
-
- if(other && other != self.enemy)
- {
- endzcurveparticles();
-
- yoda = 0;
- railgun_start = self.origin - 2 * frametime * self.velocity;
- railgun_end = self.origin + 2 * frametime * self.velocity;
- g = accuracy_isgooddamage(self.realowner, other);
- Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
-
- /*if(yoda && (time > (self.last_yoda + 5)))
- {
- Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
- self.last_yoda = time;
- }*/
-
- // calculate hits for ballistic weapons
- if(g)
- {
- // do not exceed 100%
- q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
- self.dmg_total += f * self.dmg;
- accuracy_add(self.realowner, self.realowner.weapon, 0, q);
- }
- }
-
- self.enemy = other; // don't hit the same player twice with the same bullet
-}
-
-.void(void) W_BallisticBullet_LeaveSolid_think_save;
-.float W_BallisticBullet_LeaveSolid_nextthink_save;
-.vector W_BallisticBullet_LeaveSolid_origin;
-.vector W_BallisticBullet_LeaveSolid_velocity;
-
-void W_BallisticBullet_LeaveSolid_think()
-{
- setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
- self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
-
- self.think = self.W_BallisticBullet_LeaveSolid_think_save;
- self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
- self.W_BallisticBullet_LeaveSolid_think_save = func_null;
-
- self.flags &~= FL_ONGROUND;
-
- if(self.enemy.solid == SOLID_BSP)
- {
- float f;
- f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
- Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, 0, self);
- }
-
- UpdateCSQCProjectile(self);
-}
-
-float W_BallisticBullet_LeaveSolid(float eff)
-{
- // move the entity along its velocity until it's out of solid, then let it resume
- vector vel = self.velocity;
- float dt, dst, velfactor, v0, vs;
- float maxdist;
- float E0_m, Es_m;
- float constant = self.dmg_radius * (other.ballistics_density ? other.ballistics_density : 1);
-
- // outside the world? forget it
- if(self.origin_x > world.maxs_x || self.origin_y > world.maxs_y || self.origin_z > world.maxs_z || self.origin_x < world.mins_x || self.origin_y < world.mins_y || self.origin_z < world.mins_z)
- return 0;
-
- // special case for zero density and zero bullet constant:
-
- if(self.dmg_radius == 0)
- {
- if(other.ballistics_density < 0)
- constant = 0; // infinite travel distance
- else
- return 0; // no penetration
- }
- else
- {
- if(other.ballistics_density < 0)
- constant = 0; // infinite travel distance
- else if(other.ballistics_density == 0)
- constant = self.dmg_radius;
- else
- constant = self.dmg_radius * other.ballistics_density;
- }
-
- // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
- v0 = vlen(vel);
-
- E0_m = 0.5 * v0 * v0;
-
- if(constant)
- {
- maxdist = E0_m / constant;
- // maxdist = 0.5 * v0 * v0 / constant
- // dprint("max dist = ", ftos(maxdist), "\n");
-
- if(maxdist <= autocvar_g_ballistics_mindistance)
- return 0;
- }
- else
- {
- maxdist = vlen(other.maxs - other.mins) + 1; // any distance, as long as we leave the entity
- }
-
- traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self, TRUE);
- if(trace_fraction == 1) // 1: we never got out of solid
- return 0;
-
- self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
-
- dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
- // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
- Es_m = E0_m - constant * dst;
- if(Es_m <= 0)
- {
- // roundoff errors got us
- return 0;
- }
- vs = sqrt(2 * Es_m);
- velfactor = vs / v0;
-
- dt = dst / (0.5 * (v0 + vs));
- // this is not correct, but the differential equations have no analytic
- // solution - and these times are very small anyway
- //print("dt = ", ftos(dt), "\n");
-
- self.W_BallisticBullet_LeaveSolid_think_save = self.think;
- self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
- self.think = W_BallisticBullet_LeaveSolid_think;
- self.nextthink = time + dt;
-
- vel = vel * velfactor;
-
- self.velocity = '0 0 0';
- self.flags |= FL_ONGROUND; // prevent moving
- self.W_BallisticBullet_LeaveSolid_velocity = vel;
-
- if(eff >= 0)
- if(vlen(trace_endpos - self.origin) > 4)
- {
- endzcurveparticles();
- trailparticles(self, eff, self.origin, trace_endpos);
- }
-
- return 1;
-}
-
-void W_BallisticBullet_Touch (void)
-{
- //float density;
-
- if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
- return;
-
- PROJECTILE_TOUCH;
- W_BallisticBullet_Hit ();
-
- if(self.dmg_radius < 0) // these NEVER penetrate solid
- {
- remove(self);
- return;
- }
-
- // if we hit "weapclip", bail out
- //
- // rationale of this check:
- //
- // any shader that is solid, nodraw AND trans is meant to clip weapon
- // shots and players, but has no other effect!
- //
- // if it is not trans, it is caulk and should not have this side effect
- //
- // matching shaders:
- // common/weapclip (intended)
- // common/noimpact (is supposed to eat projectiles, but is erased farther above)
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
- if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
- if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
- {
- remove(self);
- return;
- }
-
- // go through solid!
- if(!W_BallisticBullet_LeaveSolid(-1))
- {
- remove(self);
- return;
- }
-
- self.projectiledeathtype |= HITTYPE_BOUNCE;
-}
-
-void endFireBallisticBullet()
-{
- endzcurveparticles();
-}
-
-entity fireBallisticBullet_trace_callback_ent;
-float fireBallisticBullet_trace_callback_eff;
-void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
-{
- if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
- zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
- WarpZone_trace_forent = world;
- self.owner = world;
-}
-
-void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float gravityfactor, float bulletconstant)
-{
- float lag, dt, savetime; //, density;
- entity pl, oldself;
- float antilagging;
-
- antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
-
- entity proj;
- proj = spawn();
- proj.classname = "bullet";
- proj.owner = proj.realowner = self;
- PROJECTILE_MAKETRIGGER(proj);
- if(gravityfactor > 0)
- {
- proj.movetype = MOVETYPE_TOSS;
- proj.gravity = gravityfactor;
- }
- else
- proj.movetype = MOVETYPE_FLY;
- proj.think = SUB_Remove;
- proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
- W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
- proj.angles = vectoangles(proj.velocity);
- if(bulletconstant > 0)
- proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
- else if(bulletconstant == 0)
- proj.dmg_radius = 0;
- else
- proj.dmg_radius = -1;
- // so: bulletconstant = bullet mass / area of bullet circle
- setorigin(proj, start);
- proj.flags = FL_PROJECTILE;
-
- proj.touch = W_BallisticBullet_Touch;
- proj.dmg = damage;
- proj.dmg_force = force;
- proj.projectiledeathtype = dtype;
-
- proj.oldvelocity = proj.velocity;
-
- other = proj; MUTATOR_CALLHOOK(EditProjectile);
-
- if(antilagging)
- {
- float eff;
-
- if(tracereffects & EF_RED)
- eff = particleeffectnum("tr_rifle");
- else if(tracereffects & EF_BLUE)
- eff = particleeffectnum("tr_rifle_weak");
- else
- eff = particleeffectnum("tr_bullet");
-
- // NOTE: this may severely throw off weapon balance
- lag = ANTILAG_LATENCY(self);
- if(lag < 0.001)
- lag = 0;
- if not(IS_REAL_CLIENT(self))
- lag = 0;
- if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
- lag = 0; // only do hitscan, but no antilag
-
- if(lag)
- FOR_EACH_PLAYER(pl)
- if(pl != self)
- antilag_takeback(pl, time - lag);
-
- oldself = self;
- self = proj;
-
- savetime = frametime;
- frametime = 0.05;
-
- for(;;)
- {
- // DP tracetoss is stupid and always traces in 0.05s
- // ticks. This makes it trace in 0.05*0.125s ticks
- // instead.
- vector v0;
- float g0;
- v0 = self.velocity;
- g0 = self.gravity;
- self.velocity = self.velocity * 0.125;
- self.gravity *= 0.125 * 0.125;
- trace_fraction = 0;
- fireBallisticBullet_trace_callback_ent = self;
- fireBallisticBullet_trace_callback_eff = eff;
- WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
- self.velocity = v0;
- self.gravity = g0;
-
- if(trace_fraction == 1)
- break;
- // won't hit anything anytime soon (DP's
- // tracetoss does 200 tics of, here,
- // 0.05*0.125s, that is, 1.25 seconds
-
- other = trace_ent;
- dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
- setorigin(self, trace_endpos);
- self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
-
- if(!SUB_OwnerCheck())
- {
- if(SUB_NoImpactCheck())
- break;
-
- // hit the player
- W_BallisticBullet_Hit();
- }
-
- if(proj.dmg_radius < 0) // these NEVER penetrate solid
- break;
-
- // if we hit "weapclip", bail out
- //
- // rationale of this check:
- //
- // any shader that is solid, nodraw AND trans is meant to clip weapon
- // shots and players, but has no other effect!
- //
- // if it is not trans, it is caulk and should not have this side effect
- //
- // matching shaders:
- // common/weapclip (intended)
- // common/noimpact (is supposed to eat projectiles, but is erased farther above)
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
- if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
- if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
- break;
-
- // go through solid!
- if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
- break;
-
- W_BallisticBullet_LeaveSolid_think();
-
- self.projectiledeathtype |= HITTYPE_BOUNCE;
- }
- frametime = savetime;
- self = oldself;
-
- if(lag)
- FOR_EACH_PLAYER(pl)
- if(pl != self)
- antilag_restore(pl);
-
- remove(proj);
-
- return;
- }
-
- if(tracereffects & EF_RED)
- CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
- else if(tracereffects & EF_BLUE)
- CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
- else
- CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
-}
-
-void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
-{
- vector end;
-
- dir = normalize(dir + randomvec() * spread);
- end = start + dir * MAX_SHOT_DISTANCE;
- if(self.antilag_debug)
- traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
- else
- traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
-
- end = trace_endpos;
-
- if (pointcontents (trace_endpos) != CONTENT_SKY)
- {
- if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
- Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
-
- Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
- }
- trace_endpos = end;
-}
-
-float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
-{
- float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
- float is_from_owner = (inflictor == projowner);
- float is_from_exception = (exception != -1);
-
- //dprint(strcat("W_CheckProjectileDamage: from_contents ", ftos(is_from_contents), " : from_owner ", ftos(is_from_owner), " : exception ", strcat(ftos(is_from_exception), " (", ftos(exception), "). \n")));
-
- if(autocvar_g_projectiles_damage <= -2)
- {
- return FALSE; // no damage to projectiles at all, not even with the exceptions
- }
- else if(autocvar_g_projectiles_damage == -1)
- {
- if(is_from_exception)
- return (exception); // if exception is detected, allow it to override
- else
- return FALSE; // otherwise, no other damage is allowed
- }
- else if(autocvar_g_projectiles_damage == 0)
- {
- if(is_from_exception)
- return (exception); // if exception is detected, allow it to override
- else if not(is_from_contents)
- return FALSE; // otherwise, only allow damage from contents
- }
- else if(autocvar_g_projectiles_damage == 1)
- {
- if(is_from_exception)
- return (exception); // if exception is detected, allow it to override
- else if not(is_from_contents || is_from_owner)
- return FALSE; // otherwise, only allow self damage and damage from contents
- }
- else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
- {
- if(is_from_exception)
- return (exception); // if exception is detected, allow it to override
- }
-
- return TRUE; // if none of these return, then allow damage anyway.
-}
-
-void W_PrepareExplosionByDamage(entity attacker, void() explode)
-{
- self.takedamage = DAMAGE_NO;
- self.event_damage = func_null;
-
- if(IS_CLIENT(attacker) && !autocvar_g_projectiles_keep_owner)
- {
- self.owner = attacker;
- self.realowner = attacker;
- }
-
- // do not explode NOW but in the NEXT FRAME!
- // because recursive calls to RadiusDamage are not allowed
- self.nextthink = time;
- self.think = explode;
-}
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-void ElectroInit();
-vector electro_shotorigin[4];
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-void LightningInit();
-vector lightning_shotorigin[4];
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
--- /dev/null
+void W_TriggerReload()
+{
+ weapon_action(self.weapon, WR_RELOAD);
+}
+
+// switch between weapons
+void W_SwitchWeapon(float imp)
+{
+ if (self.switchweapon != imp)
+ {
+ if (client_hasweapon(self, imp, TRUE, TRUE))
+ W_SwitchWeapon_Force(self, imp);
+ else
+ self.selectweapon = imp; // update selectweapon ANYWAY
+ }
+ else
+ {
+ W_TriggerReload();
+ }
+}
+
+.float weaponcomplainindex;
+float W_GetCycleWeapon(entity pl, string weaponorder, float dir, float imp, float complain, float skipmissing)
+{
+ // We cannot tokenize in this function, as GiveItems calls this
+ // function. Thus we must use car/cdr.
+ float weaponwant, first_valid, prev_valid, switchtonext, switchtolast, c;
+ string rest;
+ switchtonext = switchtolast = 0;
+ first_valid = prev_valid = 0;
+ float weaponcur;
+
+ if(skipmissing || pl.selectweapon == 0)
+ weaponcur = pl.switchweapon;
+ else
+ weaponcur = pl.selectweapon;
+
+ if(dir == 0)
+ switchtonext = 1;
+
+ c = 0;
+
+ rest = weaponorder;
+ while(rest != "")
+ {
+ weaponwant = stof(car(rest)); rest = cdr(rest);
+ if(imp >= 0)
+ if((get_weaponinfo(weaponwant)).impulse != imp)
+ continue;
+
+ ++c;
+
+ if(!skipmissing || client_hasweapon(pl, weaponwant, TRUE, FALSE))
+ {
+ if(switchtonext)
+ return weaponwant;
+ if(!first_valid)
+ first_valid = weaponwant;
+ if(weaponwant == weaponcur)
+ {
+ if(dir >= 0)
+ switchtonext = 1;
+ else if(prev_valid)
+ return prev_valid;
+ else
+ switchtolast = 1;
+ }
+ prev_valid = weaponwant;
+ }
+ }
+ if(first_valid)
+ {
+ if(switchtolast)
+ return prev_valid;
+ else
+ return first_valid;
+ }
+ // complain (but only for one weapon on the button that has been pressed)
+ if(complain)
+ {
+ self.weaponcomplainindex += 1;
+ c = mod(self.weaponcomplainindex, c) + 1;
+ rest = weaponorder;
+ while(rest != "")
+ {
+ weaponwant = stof(car(rest)); rest = cdr(rest);
+ if(imp >= 0)
+ if((get_weaponinfo(weaponwant)).impulse != imp)
+ continue;
+
+ --c;
+ if(c == 0)
+ {
+ client_hasweapon(pl, weaponwant, TRUE, TRUE);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+void W_CycleWeapon(string weaponorder, float dir)
+{
+ float w;
+ w = W_GetCycleWeapon(self, weaponorder, dir, -1, 1, TRUE);
+ if(w > 0)
+ W_SwitchWeapon(w);
+}
+
+void W_NextWeaponOnImpulse(float imp)
+{
+ float w;
+ w = W_GetCycleWeapon(self, self.cvar_cl_weaponpriority, +1, imp, 1, (self.cvar_cl_weaponimpulsemode == 0));
+ if(w > 0)
+ W_SwitchWeapon(w);
+}
+
+// next weapon
+void W_NextWeapon(float list)
+{
+ if(list == 0)
+ W_CycleWeapon(weaponorder_byid, -1);
+ else if(list == 1)
+ W_CycleWeapon(self.weaponorder_byimpulse, -1);
+ else if(list == 2)
+ W_CycleWeapon(self.cvar_cl_weaponpriority, -1);
+}
+
+// prev weapon
+void W_PreviousWeapon(float list)
+{
+ if(list == 0)
+ W_CycleWeapon(weaponorder_byid, +1);
+ else if(list == 1)
+ W_CycleWeapon(self.weaponorder_byimpulse, +1);
+ else if(list == 2)
+ W_CycleWeapon(self.cvar_cl_weaponpriority, +1);
+}
+
+// previously used if exists and has ammo, (second) best otherwise
+void W_LastWeapon()
+{
+ if(client_hasweapon(self, self.cnt, TRUE, FALSE))
+ W_SwitchWeapon(self.cnt);
+ else
+ W_SwitchToOtherWeapon(self);
+}
+
+float w_getbestweapon(entity e)
+{
+ return W_GetCycleWeapon(e, e.cvar_cl_weaponpriority, 0, -1, FALSE, TRUE);
+}
+
+// generic weapons table
+// TODO should they be macros instead?
+float weapon_action(float wpn, float wrequest)
+{
+ return (get_weaponinfo(wpn)).weapon_func(wrequest);
+}
+
+.float savenextthink;
+void thrown_wep_think()
+{
+ self.owner = world;
+ float timeleft = self.savenextthink - time;
+ if(timeleft > 1)
+ SUB_SetFade(self, self.savenextthink - 1, 1);
+ else if(timeleft > 0)
+ SUB_SetFade(self, time, timeleft);
+ else
+ SUB_VanishOrRemove(self);
+}
+
+// returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
+string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo)
+{
+ entity oldself, wep;
+ float wa, thisammo, i, j;
+ string s;
+ var .float ammofield;
+
+ wep = spawn();
+
+ setorigin(wep, org);
+ wep.classname = "droppedweapon";
+ wep.velocity = velo;
+ wep.owner = wep.enemy = own;
+ wep.flags |= FL_TOSSED;
+ wep.colormap = own.colormap;
+
+ if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, wpn))
+ {
+ if(own.items & IT_UNLIMITED_SUPERWEAPONS)
+ {
+ wep.superweapons_finished = time + autocvar_g_balance_superweapons_time;
+ }
+ else
+ {
+ float superweapons = 1;
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+ if(WEPSET_CONTAINS_AW(WEPBIT_SUPERWEAPONS, i))
+ if(WEPSET_CONTAINS_EW(own, i))
+ ++superweapons;
+ if(superweapons <= 1)
+ {
+ wep.superweapons_finished = own.superweapons_finished;
+ own.superweapons_finished = 0;
+ }
+ else
+ {
+ float timeleft = own.superweapons_finished - time;
+ float weptimeleft = timeleft / superweapons;
+ wep.superweapons_finished = time + weptimeleft;
+ own.superweapons_finished -= weptimeleft;
+ }
+ }
+ }
+
+ wa = W_AmmoItemCode(wpn);
+ if(wa == 0)
+ {
+ oldself = self;
+ self = wep;
+ weapon_defaultspawnfunc(wpn);
+ self = oldself;
+ if(startitem_failed)
+ return string_null;
+ wep.glowmod = own.weaponentity_glowmod;
+ wep.think = thrown_wep_think;
+ wep.savenextthink = wep.nextthink;
+ wep.nextthink = min(wep.nextthink, time + 0.5);
+ wep.pickup_anyway = TRUE; // these are ALWAYS pickable
+ return "";
+ }
+ else
+ {
+ s = "";
+ oldself = self;
+ self = wep;
+ weapon_defaultspawnfunc(wpn);
+ self = oldself;
+ if(startitem_failed)
+ return string_null;
+ if(doreduce && g_weapon_stay == 2)
+ {
+ for(i = 0, j = 1; i < 24; ++i, j *= 2)
+ {
+ if(wa & j)
+ {
+ ammofield = Item_CounterField(j);
+
+ // if our weapon is loaded, give its load back to the player
+ if(self.(weapon_load[self.weapon]) > 0)
+ {
+ own.ammofield += self.(weapon_load[self.weapon]);
+ self.(weapon_load[self.weapon]) = -1; // schedule the weapon for reloading
+ }
+
+ wep.ammofield = 0;
+ }
+ }
+ }
+ else if(doreduce)
+ {
+ for(i = 0, j = 1; i < 24; ++i, j *= 2)
+ {
+ if(wa & j)
+ {
+ ammofield = Item_CounterField(j);
+
+ // if our weapon is loaded, give its load back to the player
+ if(self.(weapon_load[self.weapon]) > 0)
+ {
+ own.ammofield += self.(weapon_load[self.weapon]);
+ self.(weapon_load[self.weapon]) = -1; // schedule the weapon for reloading
+ }
+
+ thisammo = min(own.ammofield, wep.ammofield);
+ wep.ammofield = thisammo;
+ own.ammofield -= thisammo;
+ s = strcat(s, " and ", ftos(thisammo), " ", Item_CounterFieldName(j));
+ }
+ }
+ s = substring(s, 5, -1);
+ }
+ wep.glowmod = own.weaponentity_glowmod;
+ wep.think = thrown_wep_think;
+ wep.savenextthink = wep.nextthink;
+ wep.nextthink = min(wep.nextthink, time + 0.5);
+ wep.pickup_anyway = TRUE; // these are ALWAYS pickable
+
+ return s;
+ }
+}
+
+float W_IsWeaponThrowable(float w)
+{
+ float wa;
+
+ if (!autocvar_g_pickup_items)
+ return 0;
+ if (g_weaponarena)
+ return 0;
+ if (g_cts)
+ return 0;
+ if (g_nexball && w == WEP_GRENADE_LAUNCHER)
+ return 0;
+ if(w == 0)
+ return 0;
+
+ wa = W_AmmoItemCode(w);
+ if(WEPSET_CONTAINS_AW(start_weapons, w))
+ {
+ // start weapons that take no ammo can't be dropped (this prevents dropping the laser, as long as it continues to use no ammo)
+ if(start_items & IT_UNLIMITED_WEAPON_AMMO)
+ return 0;
+ if(wa == 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+// toss current weapon
+void W_ThrowWeapon(vector velo, vector delta, float doreduce)
+{
+ float w;
+ string a;
+
+ w = self.weapon;
+ if (w == 0)
+ return; // just in case
+ if(MUTATOR_CALLHOOK(ForbidThrowCurrentWeapon))
+ return;
+ if(!autocvar_g_weapon_throwable)
+ return;
+ if(self.weaponentity.state != WS_READY)
+ return;
+ if(!W_IsWeaponThrowable(w))
+ return;
+
+ if(!WEPSET_CONTAINS_EW(self, w))
+ return;
+ WEPSET_ANDNOT_EW(self, w);
+
+ W_SwitchWeapon_Force(self, w_getbestweapon(self));
+ a = W_ThrowNewWeapon(self, w, doreduce, self.origin + delta, velo);
+
+ if not(a) return;
+ Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w);
+}
+
+float forbidWeaponUse()
+{
+ if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
+ return 1;
+ if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+ return 1;
+ if(self.player_blocked)
+ return 1;
+ if(self.freezetag_frozen)
+ return 1;
+ return 0;
+}
+
+void W_WeaponFrame()
+{
+ vector fo, ri, up;
+
+ if (frametime)
+ self.weapon_frametime = frametime;
+
+ if (!self.weaponentity || self.health < 1)
+ return; // Dead player can't use weapons and injure impulse commands
+
+ if(forbidWeaponUse())
+ if(self.weaponentity.state != WS_CLEAR)
+ {
+ w_ready();
+ return;
+ }
+
+ if(!self.switchweapon)
+ {
+ self.weapon = 0;
+ self.switchingweapon = 0;
+ self.weaponentity.state = WS_CLEAR;
+ self.weaponname = "";
+ self.items &~= IT_AMMO;
+ return;
+ }
+
+ makevectors(self.v_angle);
+ fo = v_forward; // save them in case the weapon think functions change it
+ ri = v_right;
+ up = v_up;
+
+ // Change weapon
+ if (self.weapon != self.switchweapon)
+ {
+ if (self.weaponentity.state == WS_CLEAR)
+ {
+ // end switching!
+ self.switchingweapon = self.switchweapon;
+
+ entity newwep = get_weaponinfo(self.switchweapon);
+
+ //setanim(self, self.anim_draw, FALSE, TRUE, TRUE);
+ self.weaponentity.state = WS_RAISE;
+ weapon_action(self.switchweapon, WR_SETUP);
+
+ // set our clip load to the load of the weapon we switched to, if it's reloadable
+ if(newwep.spawnflags & WEP_FLAG_RELOADABLE && cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"))) // prevent accessing undefined cvars
+ {
+ self.clip_load = self.(weapon_load[self.switchweapon]);
+ self.clip_size = cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"));
+ }
+ else
+ self.clip_load = self.clip_size = 0;
+
+ // VorteX: add player model weapon select frame here
+ // setcustomframe(PlayerWeaponRaise);
+ weapon_thinkf(WFRAME_IDLE, cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), w_ready);
+ //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname))));
+ weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, '0 0 0');
+ }
+ else if (self.weaponentity.state == WS_DROP)
+ {
+ // in dropping phase we can switch at any time
+ self.switchingweapon = self.switchweapon;
+ }
+ else if (self.weaponentity.state == WS_READY)
+ {
+ // start switching!
+ self.switchingweapon = self.switchweapon;
+
+ entity oldwep = get_weaponinfo(self.weapon);
+
+#ifndef INDEPENDENT_ATTACK_FINISHED
+ if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
+ {
+#endif
+ sound (self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
+ self.weaponentity.state = WS_DROP;
+ // set up weapon switch think in the future, and start drop anim
+ weapon_thinkf(WFRAME_DONTCHANGE, cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), w_clear);
+ //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname))));
+ weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, PLAYER_WEAPONSELECTION_RANGE);
+#ifndef INDEPENDENT_ATTACK_FINISHED
+ }
+#endif
+ }
+ }
+
+ // LordHavoc: network timing test code
+ //if (self.button0)
+ // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
+
+ float w;
+ w = self.weapon;
+
+ // call the think code which may fire the weapon
+ // and do so multiple times to resolve framerate dependency issues if the
+ // server framerate is very low and the weapon fire rate very high
+ float c;
+ c = 0;
+ while (c < W_TICSPERFRAME)
+ {
+ c = c + 1;
+ if(w && !WEPSET_CONTAINS_EW(self, w))
+ {
+ if(self.weapon == self.switchweapon)
+ W_SwitchWeapon_Force(self, w_getbestweapon(self));
+ w = 0;
+ }
+
+ v_forward = fo;
+ v_right = ri;
+ v_up = up;
+
+ if(w)
+ weapon_action(self.weapon, WR_THINK);
+ else
+ weapon_action(self.weapon, WR_GONETHINK);
+
+ if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
+ {
+ if(self.weapon_think)
+ {
+ v_forward = fo;
+ v_right = ri;
+ v_up = up;
+ self.weapon_think();
+ }
+ else
+ bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
+ }
+ }
+
+ // don't let attack_finished fall behind when not firing (must be after weapon_setup calls!)
+ //if (ATTACK_FINISHED(self) < time)
+ // ATTACK_FINISHED(self) = time;
+
+ //if (self.weapon_nextthink < time)
+ // self.weapon_nextthink = time;
+
+ // update currentammo incase it has changed
+#if 0
+ if (self.items & IT_CELLS)
+ self.currentammo = self.ammo_cells;
+ else if (self.items & IT_ROCKETS)
+ self.currentammo = self.ammo_rockets;
+ else if (self.items & IT_NAILS)
+ self.currentammo = self.ammo_nails;
+ else if (self.items & IT_SHELLS)
+ self.currentammo = self.ammo_shells;
+ else
+ self.currentammo = 1;
+#endif
+}
+
+string W_Apply_Weaponreplace(string in)
+{
+ float n = tokenize_console(in);
+ string out = "";
+ float i;
+ for(i = 0; i < n; ++i)
+ {
+ string s = argv(i);
+ string r = cvar_string(strcat("g_weaponreplace_", s));
+ if(r == "")
+ out = strcat(out, " ", s);
+ else if(r != "0")
+ out = strcat(out, " ", r);
+ }
+ return substring(out, 1, -1);
+}
--- /dev/null
+/*
+===========================================================================
+
+ CLIENT WEAPONSYSTEM CODE
+ Bring back W_Weaponframe
+
+===========================================================================
+*/
+
+.float weapon_frametime;
+
+float W_WeaponRateFactor()
+{
+ float t;
+ t = 1.0 / g_weaponratefactor;
+
+ return t;
+}
+
+void W_SwitchWeapon_Force(entity e, float w)
+{
+ e.cnt = e.switchweapon;
+ e.switchweapon = w;
+ e.selectweapon = w;
+}
+
+.float antilag_debug;
+
+// VorteX: static frame globals
+float WFRAME_DONTCHANGE = -1;
+float WFRAME_FIRE1 = 0;
+float WFRAME_FIRE2 = 1;
+float WFRAME_IDLE = 2;
+float WFRAME_RELOAD = 3;
+.float wframe;
+
+void(float fr, float t, void() func) weapon_thinkf;
+
+vector W_HitPlotUnnormalizedUntransform(vector screenforward, vector screenright, vector screenup, vector v)
+{
+ vector ret;
+ ret_x = screenright * v;
+ ret_y = screenup * v;
+ ret_z = screenforward * v;
+ return ret;
+}
+
+vector W_HitPlotNormalizedUntransform(vector org, entity targ, vector screenforward, vector screenright, vector screenup, vector v)
+{
+ float i, j, k;
+ vector mi, ma, thisv, myv, ret;
+
+ myv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, org);
+
+ // x = 0..1 relative to hitbox; y = 0..1 relative to hitbox; z = distance
+
+ mi = ma = targ.origin + 0.5 * (targ.mins + targ.maxs);
+ for(i = 0; i < 2; ++i) for(j = 0; j < 2; ++j) for(k = 0; k < 2; ++k)
+ {
+ thisv = targ.origin;
+ if(i) thisv_x += targ.maxs_x; else thisv_x += targ.mins_x;
+ if(j) thisv_y += targ.maxs_y; else thisv_y += targ.mins_y;
+ if(k) thisv_z += targ.maxs_z; else thisv_z += targ.mins_z;
+ thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, thisv);
+ if(i || j || k)
+ {
+ if(mi_x > thisv_x) mi_x = thisv_x; if(ma_x < thisv_x) ma_x = thisv_x;
+ if(mi_y > thisv_y) mi_y = thisv_y; if(ma_y < thisv_y) ma_y = thisv_y;
+ //if(mi_z > thisv_z) mi_z = thisv_z; if(ma_z < thisv_z) ma_y = thisv_z;
+ }
+ else
+ {
+ // first run
+ mi = ma = thisv;
+ }
+ }
+
+ thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, v);
+ ret_x = (thisv_x - mi_x) / (ma_x - mi_x);
+ ret_y = (thisv_y - mi_y) / (ma_y - mi_y);
+ ret_z = thisv_z - myv_z;
+ return ret;
+}
+
+void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright, vector screenup)
+{
+ vector hitplot;
+ vector org;
+ float lag;
+
+ if(player.hitplotfh >= 0)
+ {
+ lag = ANTILAG_LATENCY(player);
+ if(lag < 0.001)
+ lag = 0;
+ if not(IS_REAL_CLIENT(player))
+ lag = 0; // only antilag for clients
+
+ org = player.origin + player.view_ofs;
+ traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag);
+ if(IS_CLIENT(trace_ent))
+ {
+ antilag_takeback(trace_ent, time - lag);
+ hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos);
+ antilag_restore(trace_ent);
+ fputs(player.hitplotfh, strcat(ftos(hitplot_x), " ", ftos(hitplot_y), " ", ftos(hitplot_z), " ", ftos(player.switchweapon), "\n"));
+ //print(strcat(ftos(hitplot_x), " ", ftos(hitplot_y), " ", ftos(hitplot_z), "\n"));
+ }
+ }
+}
+
+vector w_shotorg;
+vector w_shotdir;
+vector w_shotend;
+
+.float prevstrengthsound;
+.float prevstrengthsoundattempt;
+void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound
+{
+ if((player.items & IT_STRENGTH)
+ && ((time > player.prevstrengthsound + autocvar_sv_strengthsound_antispam_time) // prevent insane sound spam
+ || (time > player.prevstrengthsoundattempt + autocvar_sv_strengthsound_antispam_refire_threshold)))
+ {
+ sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
+ player.prevstrengthsound = time;
+ }
+ player.prevstrengthsoundattempt = time;
+}
+
+// this function calculates w_shotorg and w_shotdir based on the weapon model
+// offset, trueaim and antilag, and won't put w_shotorg inside a wall.
+// make sure you call makevectors first (FIXME?)
+void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector mi, vector ma, float antilag, float recoil, string snd, float chan, float maxdamage, float range)
+{
+ float nudge = 1; // added to traceline target and subtracted from result
+ float oldsolid;
+ vector vecs, dv;
+ oldsolid = ent.dphitcontentsmask;
+ if(ent.weapon == WEP_RIFLE)
+ ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+ else
+ ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+ if(antilag)
+ WarpZone_traceline_antilag(world, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+ // passing world, because we do NOT want it to touch dphitcontentsmask
+ else
+ WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent);
+ ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+
+ vector vf, vr, vu;
+ vf = v_forward;
+ vr = v_right;
+ vu = v_up;
+ w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
+ v_forward = vf;
+ v_right = vr;
+ v_up = vu;
+
+ // un-adjust trueaim if shotend is too close
+ if(vlen(w_shotend - (ent.origin + ent.view_ofs)) < autocvar_g_trueaim_minrange)
+ w_shotend = ent.origin + ent.view_ofs + s_forward * autocvar_g_trueaim_minrange;
+
+ // track max damage
+ if(accuracy_canbegooddamage(ent))
+ accuracy_add(ent, ent.weapon, maxdamage, 0);
+
+ W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
+
+ if(ent.weaponentity.movedir_x > 0)
+ vecs = ent.weaponentity.movedir;
+ else
+ vecs = '0 0 0';
+
+ dv = v_right * -vecs_y + v_up * vecs_z;
+ w_shotorg = ent.origin + ent.view_ofs + dv;
+
+ // now move the shotorg forward as much as requested if possible
+ if(antilag)
+ {
+ if(ent.antilag_debug)
+ tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ent.antilag_debug);
+ else
+ tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+ }
+ else
+ tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent);
+ w_shotorg = trace_endpos - v_forward * nudge;
+ // calculate the shotdir from the chosen shotorg
+ w_shotdir = normalize(w_shotend - w_shotorg);
+
+ if (antilag)
+ if (!ent.cvar_cl_noantilag)
+ {
+ if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
+ {
+ traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
+ if (!trace_ent.takedamage)
+ {
+ traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
+ if (trace_ent.takedamage && IS_PLAYER(trace_ent))
+ {
+ entity e;
+ e = trace_ent;
+ traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
+ if(trace_ent == e)
+ w_shotdir = normalize(trace_ent.origin - w_shotorg);
+ }
+ }
+ }
+ else if(autocvar_g_antilag == 3) // client side hitscan
+ {
+ // this part MUST use prydon cursor
+ if (ent.cursor_trace_ent) // client was aiming at someone
+ if (ent.cursor_trace_ent != ent) // just to make sure
+ if (ent.cursor_trace_ent.takedamage) // and that person is killable
+ if (IS_PLAYER(ent.cursor_trace_ent)) // and actually a player
+ {
+ // verify that the shot would miss without antilag
+ // (avoids an issue where guns would always shoot at their origin)
+ traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
+ if (!trace_ent.takedamage)
+ {
+ // verify that the shot would hit if altered
+ traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
+ if (trace_ent == ent.cursor_trace_ent)
+ w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
+ else
+ print("antilag fail\n");
+ }
+ }
+ }
+ }
+
+ ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
+
+ if (!g_norecoil)
+ ent.punchangle_x = recoil * -1;
+
+ if (snd != "")
+ {
+ sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
+ W_PlayStrengthSound(ent);
+ }
+
+ // nudge w_shotend so a trace to w_shotend hits
+ w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
+}
+
+#define W_SetupShot_Dir_ProjectileSize(ent,s_forward,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize_Range(ent, s_forward, mi, ma, antilag, recoil, snd, chan, maxdamage, MAX_SHOT_DISTANCE)
+#define W_SetupShot_ProjectileSize(ent,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, v_forward, mi, ma, antilag, recoil, snd, chan, maxdamage)
+#define W_SetupShot_Dir(ent,s_forward,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, s_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
+#define W_SetupShot(ent,antilag,recoil,snd,chan,maxdamage) W_SetupShot_ProjectileSize(ent, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
+#define W_SetupShot_Range(ent,antilag,recoil,snd,chan,maxdamage,range) W_SetupShot_Dir_ProjectileSize_Range(ent, v_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage, range)
+
+float CL_Weaponentity_CustomizeEntityForClient()
+{
+ self.viewmodelforclient = self.owner;
+ if(IS_SPEC(other))
+ if(other.enemy == self.owner)
+ self.viewmodelforclient = other;
+ return TRUE;
+}
+
+/*
+ * supported formats:
+ *
+ * 1. simple animated model, muzzle flash handling on h_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * weapon = attachment for v_tuba.md3
+ * v_tuba.md3 - first and third person model
+ * g_tuba.md3 - pickup model
+ *
+ * 2. simple animated model, muzzle flash handling on v_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
+ * tags:
+ * weapon = attachment for v_tuba.md3
+ * v_tuba.md3 - first and third person model
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * g_tuba.md3 - pickup model
+ *
+ * 3. fully animated model, muzzle flash handling on h_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
+ * tags:
+ * shot = muzzle end (shot origin, also used for muzzle flashes)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
+ * v_tuba.md3 - third person model
+ * g_tuba.md3 - pickup model
+ *
+ * 4. fully animated model, muzzle flash handling on v_ model:
+ * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
+ * tags:
+ * shot = muzzle end (shot origin)
+ * shell = casings ejection point (must be on the right hand side of the gun)
+ * v_tuba.md3 - third person model
+ * tags:
+ * shot = muzzle end (for muzzle flashes)
+ * g_tuba.md3 - pickup model
+ */
+
+// writes:
+// self.origin, self.angles
+// self.weaponentity
+// self.movedir, self.view_ofs
+// attachment stuff
+// anim stuff
+// to free:
+// call again with ""
+// remove the ent
+void CL_WeaponEntity_SetModel(string name)
+{
+ float v_shot_idx;
+ if (name != "")
+ {
+ // if there is a child entity, hide it until we're sure we use it
+ if (self.weaponentity)
+ self.weaponentity.model = "";
+ setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
+ v_shot_idx = gettagindex(self, "shot"); // used later
+ if(!v_shot_idx)
+ v_shot_idx = gettagindex(self, "tag_shot");
+
+ setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
+ // preset some defaults that work great for renamed zym files (which don't need an animinfo)
+ self.anim_fire1 = animfixfps(self, '0 1 0.01', '0 0 0');
+ self.anim_fire2 = animfixfps(self, '1 1 0.01', '0 0 0');
+ self.anim_idle = animfixfps(self, '2 1 0.01', '0 0 0');
+ self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
+
+ // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
+ // if we don't, this is a "real" animated model
+ if(gettagindex(self, "weapon"))
+ {
+ if (!self.weaponentity)
+ self.weaponentity = spawn();
+ setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
+ setattachment(self.weaponentity, self, "weapon");
+ }
+ else if(gettagindex(self, "tag_weapon"))
+ {
+ if (!self.weaponentity)
+ self.weaponentity = spawn();
+ setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
+ setattachment(self.weaponentity, self, "tag_weapon");
+ }
+ else
+ {
+ if(self.weaponentity)
+ remove(self.weaponentity);
+ self.weaponentity = world;
+ }
+
+ setorigin(self,'0 0 0');
+ self.angles = '0 0 0';
+ self.frame = 0;
+ self.viewmodelforclient = world;
+
+ float idx;
+
+ if(v_shot_idx) // v_ model attached to invisible h_ model
+ {
+ self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
+ }
+ else
+ {
+ idx = gettagindex(self, "shot");
+ if(!idx)
+ idx = gettagindex(self, "tag_shot");
+ if(idx)
+ self.movedir = gettaginfo(self, idx);
+ else
+ {
+ print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
+ self.movedir = '0 0 0';
+ }
+ }
+
+ if(self.weaponentity) // v_ model attached to invisible h_ model
+ {
+ idx = gettagindex(self.weaponentity, "shell");
+ if(!idx)
+ idx = gettagindex(self.weaponentity, "tag_shell");
+ if(idx)
+ self.spawnorigin = gettaginfo(self.weaponentity, idx);
+ }
+ else
+ idx = 0;
+ if(!idx)
+ {
+ idx = gettagindex(self, "shell");
+ if(!idx)
+ idx = gettagindex(self, "tag_shell");
+ if(idx)
+ self.spawnorigin = gettaginfo(self, idx);
+ else
+ {
+ print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
+ self.spawnorigin = self.movedir;
+ }
+ }
+
+ if(v_shot_idx)
+ {
+ self.oldorigin = '0 0 0'; // use regular attachment
+ }
+ else
+ {
+ if(self.weaponentity)
+ {
+ idx = gettagindex(self, "weapon");
+ if(!idx)
+ idx = gettagindex(self, "tag_weapon");
+ }
+ else
+ {
+ idx = gettagindex(self, "handle");
+ if(!idx)
+ idx = gettagindex(self, "tag_handle");
+ }
+ if(idx)
+ {
+ self.oldorigin = self.movedir - gettaginfo(self, idx);
+ }
+ else
+ {
+ print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n");
+ self.oldorigin = '0 0 0'; // there is no way to recover from this
+ }
+ }
+
+ self.viewmodelforclient = self.owner;
+ }
+ else
+ {
+ self.model = "";
+ if(self.weaponentity)
+ remove(self.weaponentity);
+ self.weaponentity = world;
+ self.movedir = '0 0 0';
+ self.spawnorigin = '0 0 0';
+ self.oldorigin = '0 0 0';
+ self.anim_fire1 = '0 1 0.01';
+ self.anim_fire2 = '0 1 0.01';
+ self.anim_idle = '0 1 0.01';
+ self.anim_reload = '0 1 0.01';
+ }
+
+ self.view_ofs = '0 0 0';
+
+ if(self.movedir_x >= 0)
+ {
+ vector v0;
+ v0 = self.movedir;
+ self.movedir = shotorg_adjust(v0, FALSE, FALSE);
+ self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
+ }
+ self.owner.stat_shotorg = compressShotOrigin(self.movedir);
+ self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
+
+ self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
+
+ // check if an instant weapon switch occurred
+ setorigin(self, self.view_ofs);
+ // reset animstate now
+ self.wframe = WFRAME_IDLE;
+ setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
+}
+
+vector CL_Weapon_GetShotOrg(float wpn)
+{
+ entity wi, oldself;
+ vector ret;
+ wi = get_weaponinfo(wpn);
+ oldself = self;
+ self = spawn();
+ CL_WeaponEntity_SetModel(wi.mdl);
+ ret = self.movedir;
+ CL_WeaponEntity_SetModel("");
+ remove(self);
+ self = oldself;
+ return ret;
+}
+
+void CL_Weaponentity_Think()
+{
+ float tb;
+ self.nextthink = time;
+ if (intermission_running)
+ self.frame = self.anim_idle_x;
+ if (self.owner.weaponentity != self)
+ {
+ if (self.weaponentity)
+ remove(self.weaponentity);
+ remove(self);
+ return;
+ }
+ if (self.owner.deadflag != DEAD_NO)
+ {
+ self.model = "";
+ if (self.weaponentity)
+ self.weaponentity.model = "";
+ return;
+ }
+ if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
+ {
+ self.weaponname = self.owner.weaponname;
+ self.dmg = self.owner.modelindex;
+ self.deadflag = self.owner.deadflag;
+
+ CL_WeaponEntity_SetModel(self.owner.weaponname);
+ }
+
+ tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
+ self.effects = self.owner.effects & EFMASK_CHEAP;
+ self.effects &~= EF_LOWPRECISION;
+ self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it
+ self.effects &~= EF_TELEPORT_BIT;
+ self.effects &~= EF_RESTARTANIM_BIT;
+ self.effects |= tb;
+
+ if(self.owner.alpha == default_player_alpha)
+ self.alpha = default_weapon_alpha;
+ else if(self.owner.alpha != 0)
+ self.alpha = self.owner.alpha;
+ else
+ self.alpha = 1;
+
+ self.glowmod = self.owner.weaponentity_glowmod;
+ self.colormap = self.owner.colormap;
+ if (self.weaponentity)
+ {
+ self.weaponentity.effects = self.effects;
+ self.weaponentity.alpha = self.alpha;
+ self.weaponentity.colormap = self.colormap;
+ self.weaponentity.glowmod = self.glowmod;
+ }
+
+ self.angles = '0 0 0';
+
+ float f = (self.owner.weapon_nextthink - time);
+ if (self.state == WS_RAISE && !intermission_running)
+ {
+ entity newwep = get_weaponinfo(self.owner.switchweapon);
+ f = f * g_weaponratefactor / max(f, cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)));
+ //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), (self.owner.weapon_nextthink - time)));
+ self.angles_x = -90 * f * f;
+ }
+ else if (self.state == WS_DROP && !intermission_running)
+ {
+ entity oldwep = get_weaponinfo(self.owner.weapon);
+ f = 1 - f * g_weaponratefactor / max(f, cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)));
+ //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), (self.owner.weapon_nextthink - time)));
+ self.angles_x = -90 * f * f;
+ }
+ else if (self.state == WS_CLEAR)
+ {
+ f = 1;
+ self.angles_x = -90 * f * f;
+ }
+}
+
+void CL_ExteriorWeaponentity_Think()
+{
+ float tag_found;
+ self.nextthink = time;
+ if (self.owner.exteriorweaponentity != self)
+ {
+ remove(self);
+ return;
+ }
+ if (self.owner.deadflag != DEAD_NO)
+ {
+ self.model = "";
+ return;
+ }
+ if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
+ {
+ self.weaponname = self.owner.weaponname;
+ self.dmg = self.owner.modelindex;
+ self.deadflag = self.owner.deadflag;
+ if (self.owner.weaponname != "")
+ setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
+ else
+ self.model = "";
+
+ if((tag_found = gettagindex(self.owner, "tag_weapon")))
+ {
+ self.tag_index = tag_found;
+ self.tag_entity = self.owner;
+ }
+ else
+ setattachment(self, self.owner, "bip01 r hand");
+ }
+ self.effects = self.owner.effects;
+ self.effects |= EF_LOWPRECISION;
+ self.effects = self.effects & EFMASK_CHEAP; // eat performance
+ if(self.owner.alpha == default_player_alpha)
+ self.alpha = default_weapon_alpha;
+ else if(self.owner.alpha != 0)
+ self.alpha = self.owner.alpha;
+ else
+ self.alpha = 1;
+
+ self.glowmod = self.owner.weaponentity_glowmod;
+ self.colormap = self.owner.colormap;
+
+ CSQCMODEL_AUTOUPDATE();
+}
+
+// spawning weaponentity for client
+void CL_SpawnWeaponentity()
+{
+ self.weaponentity = spawn();
+ self.weaponentity.classname = "weaponentity";
+ self.weaponentity.solid = SOLID_NOT;
+ self.weaponentity.owner = self;
+ setmodel(self.weaponentity, ""); // precision set when changed
+ setorigin(self.weaponentity, '0 0 0');
+ self.weaponentity.angles = '0 0 0';
+ self.weaponentity.viewmodelforclient = self;
+ self.weaponentity.flags = 0;
+ self.weaponentity.think = CL_Weaponentity_Think;
+ self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
+ self.weaponentity.nextthink = time;
+
+ self.exteriorweaponentity = spawn();
+ self.exteriorweaponentity.classname = "exteriorweaponentity";
+ self.exteriorweaponentity.solid = SOLID_NOT;
+ self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
+ self.exteriorweaponentity.owner = self;
+ setorigin(self.exteriorweaponentity, '0 0 0');
+ self.exteriorweaponentity.angles = '0 0 0';
+ self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
+ self.exteriorweaponentity.nextthink = time;
+
+ {
+ entity oldself = self;
+ self = self.exteriorweaponentity;
+ CSQCMODEL_AUTOINIT();
+ self = oldself;
+ }
+}
+
+void Send_WeaponComplain (entity e, float wpn, string wpnname, float type)
+{
+ msg_entity = e;
+ WriteByte(MSG_ONE, SVC_TEMPENTITY);
+ WriteByte(MSG_ONE, TE_CSQC_WEAPONCOMPLAIN);
+ WriteByte(MSG_ONE, wpn);
+ WriteString(MSG_ONE, wpnname);
+ WriteByte(MSG_ONE, type);
+}
+
+.float hasweapon_complain_spam;
+
+float client_hasweapon(entity cl, float wpn, float andammo, float complain)
+{
+ float f;
+ entity oldself;
+
+ if(time < self.hasweapon_complain_spam)
+ complain = 0;
+ if(complain)
+ self.hasweapon_complain_spam = time + 0.2;
+
+ if (wpn < WEP_FIRST || wpn > WEP_LAST)
+ {
+ if (complain)
+ sprint(self, "Invalid weapon\n");
+ return FALSE;
+ }
+ if (WEPSET_CONTAINS_EW(cl, wpn))
+ {
+ if (andammo)
+ {
+ if(cl.items & IT_UNLIMITED_WEAPON_AMMO)
+ {
+ f = 1;
+ }
+ else
+ {
+ oldself = self;
+ self = cl;
+ f = weapon_action(wpn, WR_CHECKAMMO1);
+ f = f + weapon_action(wpn, WR_CHECKAMMO2);
+
+ // always allow selecting the Mine Layer if we placed mines, so that we can detonate them
+ entity mine;
+ if(wpn == WEP_MINE_LAYER)
+ for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
+ f = 1;
+
+ self = oldself;
+ }
+ if (!f)
+ {
+ if (complain)
+ if(IS_REAL_CLIENT(cl))
+ {
+ play2(cl, "weapons/unavailable.wav");
+ Send_WeaponComplain (cl, wpn, W_Name(wpn), 0);
+ }
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+ if (complain)
+ {
+ // DRESK - 3/16/07
+ // Report Proper Weapon Status / Modified Weapon Ownership Message
+ if (WEPSET_CONTAINS_AW(weaponsInMap, wpn))
+ {
+ Send_WeaponComplain(cl, wpn, W_Name(wpn), 1);
+
+ if(autocvar_g_showweaponspawns)
+ {
+ entity e;
+ string s;
+
+ e = get_weaponinfo(wpn);
+ s = e.model2;
+
+ for(e = world; (e = findfloat(e, weapon, wpn)); )
+ {
+ if(e.classname == "droppedweapon")
+ continue;
+ if not(e.flags & FL_ITEM)
+ continue;
+ WaypointSprite_Spawn(
+ s,
+ 1, 0,
+ world, e.origin,
+ self, 0,
+ world, enemy,
+ 0,
+ RADARICON_NONE, '0 0 0'
+ );
+ }
+ }
+ }
+ else
+ {
+ Send_WeaponComplain (cl, wpn, W_Name(wpn), 2);
+ }
+
+ play2(cl, "weapons/unavailable.wav");
+ }
+ return FALSE;
+}
+
+// Weapon subs
+void w_clear()
+{
+ if (self.weapon != -1)
+ {
+ self.weapon = 0;
+ self.switchingweapon = 0;
+ }
+ if (self.weaponentity)
+ {
+ self.weaponentity.state = WS_CLEAR;
+ self.weaponentity.effects = 0;
+ }
+}
+
+void w_ready()
+{
+ if (self.weaponentity)
+ self.weaponentity.state = WS_READY;
+ weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
+}
+
+// Setup weapon for client (after this raise frame will be launched)
+void weapon_setup(float windex)
+{
+ entity e;
+ e = get_weaponinfo(windex);
+ self.items &~= IT_AMMO;
+ self.items = self.items | (e.items & IT_AMMO);
+
+ // the two weapon entities will notice this has changed and update their models
+ self.weapon = windex;
+ self.switchingweapon = windex; // to make sure
+ self.weaponname = e.mdl;
+ self.bulletcounter = 0;
+}
+
+// perform weapon to attack (weaponstate and attack_finished check is here)
+void W_SwitchToOtherWeapon(entity pl)
+{
+ // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)
+ float w, ww;
+ w = pl.weapon;
+ if(WEPSET_CONTAINS_EW(pl, w))
+ {
+ WEPSET_ANDNOT_EW(pl, w);
+ ww = w_getbestweapon(pl);
+ WEPSET_OR_EW(pl, w);
+ }
+ else
+ ww = w_getbestweapon(pl);
+ if(ww)
+ W_SwitchWeapon_Force(pl, ww);
+}
+
+.float prevdryfire;
+.float prevwarntime;
+float weapon_prepareattack_checkammo(float secondary)
+{
+ if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+ if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))
+ {
+ // always keep the Mine Layer if we placed mines, so that we can detonate them
+ entity mine;
+ if(self.weapon == WEP_MINE_LAYER)
+ for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
+ return FALSE;
+
+ if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
+ {
+ sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM);
+ self.prevdryfire = time;
+ }
+
+ if(weapon_action(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
+ {
+ if(time - self.prevwarntime > 1)
+ {
+ Send_Notification(
+ NOTIF_ONE,
+ self,
+ MSG_MULTI,
+ ITEM_WEAPON_PRIMORSEC,
+ self.weapon,
+ secondary,
+ (1 - secondary)
+ );
+ }
+ self.prevwarntime = time;
+ }
+ else // this weapon is totally unable to fire, switch to another one
+ {
+ W_SwitchToOtherWeapon(self);
+ }
+
+ return FALSE;
+ }
+ return TRUE;
+}
+.float race_penalty;
+float weapon_prepareattack_check(float secondary, float attacktime)
+{
+ if(!weapon_prepareattack_checkammo(secondary))
+ return FALSE;
+
+ //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
+ //if all players readied up and the countdown is running
+ if(time < game_starttime || time < self.race_penalty) {
+ return FALSE;
+ }
+
+ if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
+ return FALSE;
+
+ // do not even think about shooting if switching
+ if(self.switchweapon != self.weapon)
+ return FALSE;
+
+ if(attacktime >= 0)
+ {
+ // don't fire if previous attack is not finished
+ if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
+ return FALSE;
+ // don't fire while changing weapon
+ if (self.weaponentity.state != WS_READY)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+float weapon_prepareattack_do(float secondary, float attacktime)
+{
+ self.weaponentity.state = WS_INUSE;
+
+ self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
+
+ // if the weapon hasn't been firing continuously, reset the timer
+ if(attacktime >= 0)
+ {
+ if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
+ {
+ ATTACK_FINISHED(self) = time;
+ //dprint("resetting attack finished to ", ftos(time), "\n");
+ }
+ ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
+ }
+ self.bulletcounter += 1;
+ //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
+ return TRUE;
+}
+float weapon_prepareattack(float secondary, float attacktime)
+{
+ if(weapon_prepareattack_check(secondary, attacktime))
+ {
+ weapon_prepareattack_do(secondary, attacktime);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+void weapon_thinkf(float fr, float t, void() func)
+{
+ vector a;
+ vector of, or, ou;
+ float restartanim;
+
+ if(fr == WFRAME_DONTCHANGE)
+ {
+ fr = self.weaponentity.wframe;
+ restartanim = FALSE;
+ }
+ else if (fr == WFRAME_IDLE)
+ restartanim = FALSE;
+ else
+ restartanim = TRUE;
+
+ of = v_forward;
+ or = v_right;
+ ou = v_up;
+
+ if (self.weaponentity)
+ {
+ self.weaponentity.wframe = fr;
+ a = '0 0 0';
+ if (fr == WFRAME_IDLE)
+ a = self.weaponentity.anim_idle;
+ else if (fr == WFRAME_FIRE1)
+ a = self.weaponentity.anim_fire1;
+ else if (fr == WFRAME_FIRE2)
+ a = self.weaponentity.anim_fire2;
+ else // if (fr == WFRAME_RELOAD)
+ a = self.weaponentity.anim_reload;
+ a_z *= g_weaponratefactor;
+ setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
+ }
+
+ v_forward = of;
+ v_right = or;
+ v_up = ou;
+
+ if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
+ {
+ backtrace("Tried to override initial weapon think function - should this really happen?");
+ }
+
+ t *= W_WeaponRateFactor();
+
+ // VorteX: haste can be added here
+ if (self.weapon_think == w_ready)
+ {
+ self.weapon_nextthink = time;
+ //dprint("started firing at ", ftos(time), "\n");
+ }
+ if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
+ {
+ self.weapon_nextthink = time;
+ //dprint("reset weapon animation timer at ", ftos(time), "\n");
+ }
+ self.weapon_nextthink = self.weapon_nextthink + t;
+ self.weapon_think = func;
+ //dprint("next ", ftos(self.weapon_nextthink), "\n");
+
+ if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
+ {
+ if(self.weapon == WEP_SHOTGUN && fr == WFRAME_FIRE2)
+ animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
+ else
+ animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
+ }
+ else
+ {
+ if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
+ self.anim_upper_action = 0;
+ }
+}
+
+void weapon_boblayer1(float spd, vector org)
+{
+ // VorteX: haste can be added here
+}
+
+vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity, float forceAbsolute)
+{
+ vector mdirection;
+ float mspeed;
+ vector outvelocity;
+
+ mvelocity = mvelocity * g_weaponspeedfactor;
+
+ mdirection = normalize(mvelocity);
+ mspeed = vlen(mvelocity);
+
+ outvelocity = get_shotvelocity(pvelocity, mdirection, mspeed, (forceAbsolute ? 0 : autocvar_g_projectiles_newton_style), autocvar_g_projectiles_newton_style_2_minfactor, autocvar_g_projectiles_newton_style_2_maxfactor);
+
+ return outvelocity;
+}
+
+void W_AttachToShotorg(entity flash, vector offset)
+{
+ entity xflash;
+ flash.owner = self;
+ flash.angles_z = random() * 360;
+
+ if(gettagindex(self.weaponentity, "shot"))
+ setattachment(flash, self.weaponentity, "shot");
+ else
+ setattachment(flash, self.weaponentity, "tag_shot");
+ setorigin(flash, offset);
+
+ xflash = spawn();
+ copyentity(flash, xflash);
+
+ flash.viewmodelforclient = self;
+
+ if(self.weaponentity.oldorigin_x > 0)
+ {
+ setattachment(xflash, self.exteriorweaponentity, "");
+ setorigin(xflash, self.weaponentity.oldorigin + offset);
+ }
+ else
+ {
+ if(gettagindex(self.exteriorweaponentity, "shot"))
+ setattachment(xflash, self.exteriorweaponentity, "shot");
+ else
+ setattachment(xflash, self.exteriorweaponentity, "tag_shot");
+ setorigin(xflash, offset);
+ }
+}
+
+#if 0
+float mspercallsum;
+float mspercallsstyle;
+float mspercallcount;
+#endif
+void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
+{
+ if(missile.owner == world)
+ error("Unowned missile");
+
+ dir = dir + upDir * (pUpSpeed / pSpeed);
+ dir_z += pZSpeed / pSpeed;
+ pSpeed *= vlen(dir);
+ dir = normalize(dir);
+
+#if 0
+ if(autocvar_g_projectiles_spread_style != mspercallsstyle)
+ {
+ mspercallsum = mspercallcount = 0;
+ mspercallsstyle = autocvar_g_projectiles_spread_style;
+ }
+ mspercallsum -= gettime(GETTIME_HIRES);
+#endif
+ dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
+#if 0
+ mspercallsum += gettime(GETTIME_HIRES);
+ mspercallcount += 1;
+ print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
+#endif
+
+ missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir, forceAbsolute);
+}
+
+void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)
+{
+ W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread, FALSE);
+}
+
+#define W_SETUPPROJECTILEVELOCITY_UP(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), cvar(#s "_speed_up"), cvar(#s "_speed_z"), cvar(#s "_spread"), FALSE)
+#define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"), FALSE)
+
+void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload)
+{
+ if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
+ return;
+
+ // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
+ if(ammo_reload)
+ {
+ self.clip_load -= ammo_use;
+ self.(weapon_load[self.weapon]) = self.clip_load;
+ }
+ else
+ self.(self.current_ammo) -= ammo_use;
+}
+
+// weapon reloading code
+
+.float reload_ammo_amount, reload_ammo_min, reload_time;
+.float reload_complain;
+.string reload_sound;
+
+void W_ReloadedAndReady()
+{
+ // finish the reloading process, and do the ammo transfer
+
+ self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
+
+ // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
+ if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)
+ self.clip_load = self.reload_ammo_amount;
+ else
+ {
+ while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have
+ {
+ self.clip_load += 1;
+ self.(self.current_ammo) -= 1;
+ }
+ }
+ self.(weapon_load[self.weapon]) = self.clip_load;
+
+ // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
+ // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
+ // so your weapon is disabled for a few seconds without reason
+
+ //ATTACK_FINISHED(self) -= self.reload_time - 1;
+
+ w_ready();
+}
+
+void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)
+{
+ // set global values to work with
+
+ self.reload_ammo_min = sent_ammo_min;
+ self.reload_ammo_amount = sent_ammo_amount;
+ self.reload_time = sent_time;
+ self.reload_sound = sent_sound;
+
+ // check if we meet the necessary conditions to reload
+
+ entity e;
+ e = get_weaponinfo(self.weapon);
+
+ // don't reload weapons that don't have the RELOADABLE flag
+ if not(e.spawnflags & WEP_FLAG_RELOADABLE)
+ {
+ dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
+ return;
+ }
+
+ // return if reloading is disabled for this weapon
+ if(!self.reload_ammo_amount)
+ return;
+
+ // our weapon is fully loaded, no need to reload
+ if (self.clip_load >= self.reload_ammo_amount)
+ return;
+
+ // no ammo, so nothing to load
+ if(!self.(self.current_ammo) && self.reload_ammo_min)
+ if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
+ {
+ if(IS_REAL_CLIENT(self) && self.reload_complain < time)
+ {
+ play2(self, "weapons/unavailable.wav");
+ sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
+ self.reload_complain = time + 1;
+ }
+ // switch away if the amount of ammo is not enough to keep using this weapon
+ if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2))
+ {
+ self.clip_load = -1; // reload later
+ W_SwitchToOtherWeapon(self);
+ }
+ return;
+ }
+
+ if (self.weaponentity)
+ {
+ if (self.weaponentity.wframe == WFRAME_RELOAD)
+ return;
+
+ // allow switching away while reloading, but this will cause a new reload!
+ self.weaponentity.state = WS_READY;
+ }
+
+ // now begin the reloading process
+
+ sound (self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTN_NORM);
+
+ // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
+ // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
+ // so your weapon is disabled for a few seconds without reason
+
+ //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
+
+ weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
+
+ if(self.clip_load < 0)
+ self.clip_load = 0;
+ self.old_clip_load = self.clip_load;
+ self.clip_load = self.(weapon_load[self.weapon]) = -1;
+}
--- /dev/null
+
+void W_GiveWeapon (entity e, float wep)
+{
+ entity oldself;
+
+ if (!wep)
+ return;
+
+ WEPSET_OR_EW(e, wep);
+
+ oldself = self;
+ self = e;
+
+ if(IS_PLAYER(other))
+ { Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_WEAPON_GOT, wep); }
+
+ self = oldself;
+}
+
+.float railgundistance;
+.vector railgunforce;
+void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype)
+{
+ vector hitloc, force, endpoint, dir;
+ entity ent, endent;
+ float endq3surfaceflags;
+ float totaldmg;
+ entity o;
+
+ float length;
+ vector beampos;
+ string snd;
+ entity pseudoprojectile;
+ float f, ffs;
+
+ pseudoprojectile = world;
+
+ railgun_start = start;
+ railgun_end = end;
+
+ dir = normalize(end - start);
+ length = vlen(end - start);
+ force = dir * bforce;
+
+ // go a little bit into the wall because we need to hit this wall later
+ end = end + dir;
+
+ totaldmg = 0;
+
+ // trace multiple times until we hit a wall, each obstacle will be made
+ // non-solid so we can hit the next, while doing this we spawn effects and
+ // note down which entities were hit so we can damage them later
+ o = self;
+ while (1)
+ {
+ if(self.antilag_debug)
+ WarpZone_traceline_antilag (self, start, end, FALSE, o, self.antilag_debug);
+ else
+ WarpZone_traceline_antilag (self, start, end, FALSE, o, ANTILAG_LATENCY(self));
+ if(o && WarpZone_trace_firstzone)
+ {
+ o = world;
+ continue;
+ }
+
+ if(trace_ent.solid == SOLID_BSP || trace_ent.solid == SOLID_SLIDEBOX)
+ Damage_DamageInfo(trace_endpos, bdamage, 0, 0, force, deathtype, trace_ent.species, self);
+
+ // if it is world we can't hurt it so stop now
+ if (trace_ent == world || trace_fraction == 1)
+ break;
+
+ // make the entity non-solid so we can hit the next one
+ trace_ent.railgunhit = TRUE;
+ trace_ent.railgunhitloc = end;
+ trace_ent.railgunhitsolidbackup = trace_ent.solid;
+ trace_ent.railgundistance = vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - start);
+ trace_ent.railgunforce = WarpZone_TransformVelocity(WarpZone_trace_transform, force);
+
+ // stop if this is a wall
+ if (trace_ent.solid == SOLID_BSP)
+ break;
+
+ // make the entity non-solid
+ trace_ent.solid = SOLID_NOT;
+ }
+
+ endpoint = trace_endpos;
+ endent = trace_ent;
+ endq3surfaceflags = trace_dphitq3surfaceflags;
+
+ // find all the entities the railgun hit and restore their solid state
+ ent = findfloat(world, railgunhit, TRUE);
+ while (ent)
+ {
+ // restore their solid type
+ ent.solid = ent.railgunhitsolidbackup;
+ ent = findfloat(ent, railgunhit, TRUE);
+ }
+
+ // spawn a temporary explosion entity for RadiusDamage calls
+ //explosion = spawn();
+
+ // Find all non-hit players the beam passed close by
+ if(deathtype == WEP_MINSTANEX || deathtype == WEP_NEX)
+ {
+ FOR_EACH_REALCLIENT(msg_entity) if(msg_entity != self) if(!msg_entity.railgunhit) if not(IS_SPEC(msg_entity) && msg_entity.enemy == self) // we use realclient, so spectators can hear the whoosh too
+ {
+ // nearest point on the beam
+ beampos = start + dir * bound(0, (msg_entity.origin - start) * dir, length);
+
+ f = bound(0, 1 - vlen(beampos - msg_entity.origin) / 512, 1);
+ if(f <= 0)
+ continue;
+
+ snd = strcat("weapons/nexwhoosh", ftos(floor(random() * 3) + 1), ".wav");
+
+ if(!pseudoprojectile)
+ pseudoprojectile = spawn(); // we need this so the sound uses the "entchannel4" volume
+ soundtoat(MSG_ONE, pseudoprojectile, beampos, CH_SHOTS, snd, VOL_BASE * f, ATTN_NONE);
+ }
+
+ if(pseudoprojectile)
+ remove(pseudoprojectile);
+ }
+
+ // find all the entities the railgun hit and hurt them
+ ent = findfloat(world, railgunhit, TRUE);
+ while (ent)
+ {
+ // get the details we need to call the damage function
+ hitloc = ent.railgunhitloc;
+
+ f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance);
+ ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance);
+
+ if(accuracy_isgooddamage(self.realowner, ent))
+ totaldmg += bdamage * f;
+
+ // apply the damage
+ if (ent.takedamage)
+ Damage (ent, self, self, bdamage * f, deathtype, hitloc, ent.railgunforce * ffs);
+
+ // create a small explosion to throw gibs around (if applicable)
+ //setorigin (explosion, hitloc);
+ //RadiusDamage (explosion, self, 10, 0, 50, world, world, 300, deathtype);
+
+ ent.railgunhitloc = '0 0 0';
+ ent.railgunhitsolidbackup = SOLID_NOT;
+ ent.railgunhit = FALSE;
+ ent.railgundistance = 0;
+
+ // advance to the next entity
+ ent = findfloat(ent, railgunhit, TRUE);
+ }
+
+ // calculate hits and fired shots for hitscan
+ accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg));
+
+ trace_endpos = endpoint;
+ trace_ent = endent;
+ trace_dphitq3surfaceflags = endq3surfaceflags;
+}
+
+.float dmg_force;
+.float dmg_radius;
+.float dmg_total;
+//.float last_yoda;
+void W_BallisticBullet_Hit (void)
+{
+ float f, q, g;
+
+ f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
+ q = 1 + self.dmg_edge / self.dmg;
+
+ if(other.solid == SOLID_BSP || other.solid == SOLID_SLIDEBOX)
+ Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, other.species, self);
+
+ if(other && other != self.enemy)
+ {
+ endzcurveparticles();
+
+ yoda = 0;
+ railgun_start = self.origin - 2 * frametime * self.velocity;
+ railgun_end = self.origin + 2 * frametime * self.velocity;
+ g = accuracy_isgooddamage(self.realowner, other);
+ Damage(other, self, self.realowner, self.dmg * f, self.projectiledeathtype, self.origin, self.dmg_force * normalize(self.velocity) * f);
+
+ /*if(yoda && (time > (self.last_yoda + 5)))
+ {
+ Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA);
+ self.last_yoda = time;
+ }*/
+
+ // calculate hits for ballistic weapons
+ if(g)
+ {
+ // do not exceed 100%
+ q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total;
+ self.dmg_total += f * self.dmg;
+ accuracy_add(self.realowner, self.realowner.weapon, 0, q);
+ }
+ }
+
+ self.enemy = other; // don't hit the same player twice with the same bullet
+}
+
+.void(void) W_BallisticBullet_LeaveSolid_think_save;
+.float W_BallisticBullet_LeaveSolid_nextthink_save;
+.vector W_BallisticBullet_LeaveSolid_origin;
+.vector W_BallisticBullet_LeaveSolid_velocity;
+
+void W_BallisticBullet_LeaveSolid_think()
+{
+ setorigin(self, self.W_BallisticBullet_LeaveSolid_origin);
+ self.velocity = self.W_BallisticBullet_LeaveSolid_velocity;
+
+ self.think = self.W_BallisticBullet_LeaveSolid_think_save;
+ self.nextthink = max(time, self.W_BallisticBullet_LeaveSolid_nextthink_save);
+ self.W_BallisticBullet_LeaveSolid_think_save = func_null;
+
+ self.flags &~= FL_ONGROUND;
+
+ if(self.enemy.solid == SOLID_BSP)
+ {
+ float f;
+ f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier
+ Damage_DamageInfo(self.origin, 0, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * -f, self.projectiledeathtype, 0, self);
+ }
+
+ UpdateCSQCProjectile(self);
+}
+
+float W_BallisticBullet_LeaveSolid(float eff)
+{
+ // move the entity along its velocity until it's out of solid, then let it resume
+ vector vel = self.velocity;
+ float dt, dst, velfactor, v0, vs;
+ float maxdist;
+ float E0_m, Es_m;
+ float constant = self.dmg_radius * (other.ballistics_density ? other.ballistics_density : 1);
+
+ // outside the world? forget it
+ if(self.origin_x > world.maxs_x || self.origin_y > world.maxs_y || self.origin_z > world.maxs_z || self.origin_x < world.mins_x || self.origin_y < world.mins_y || self.origin_z < world.mins_z)
+ return 0;
+
+ // special case for zero density and zero bullet constant:
+
+ if(self.dmg_radius == 0)
+ {
+ if(other.ballistics_density < 0)
+ constant = 0; // infinite travel distance
+ else
+ return 0; // no penetration
+ }
+ else
+ {
+ if(other.ballistics_density < 0)
+ constant = 0; // infinite travel distance
+ else if(other.ballistics_density == 0)
+ constant = self.dmg_radius;
+ else
+ constant = self.dmg_radius * other.ballistics_density;
+ }
+
+ // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
+ v0 = vlen(vel);
+
+ E0_m = 0.5 * v0 * v0;
+
+ if(constant)
+ {
+ maxdist = E0_m / constant;
+ // maxdist = 0.5 * v0 * v0 / constant
+ // dprint("max dist = ", ftos(maxdist), "\n");
+
+ if(maxdist <= autocvar_g_ballistics_mindistance)
+ return 0;
+ }
+ else
+ {
+ maxdist = vlen(other.maxs - other.mins) + 1; // any distance, as long as we leave the entity
+ }
+
+ traceline_inverted (self.origin, self.origin + normalize(vel) * maxdist, MOVE_NORMAL, self, TRUE);
+ if(trace_fraction == 1) // 1: we never got out of solid
+ return 0;
+
+ self.W_BallisticBullet_LeaveSolid_origin = trace_endpos;
+
+ dst = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - self.origin));
+ // E(s) = E0 - constant * s, constant = area of bullet circle * material constant / mass
+ Es_m = E0_m - constant * dst;
+ if(Es_m <= 0)
+ {
+ // roundoff errors got us
+ return 0;
+ }
+ vs = sqrt(2 * Es_m);
+ velfactor = vs / v0;
+
+ dt = dst / (0.5 * (v0 + vs));
+ // this is not correct, but the differential equations have no analytic
+ // solution - and these times are very small anyway
+ //print("dt = ", ftos(dt), "\n");
+
+ self.W_BallisticBullet_LeaveSolid_think_save = self.think;
+ self.W_BallisticBullet_LeaveSolid_nextthink_save = self.nextthink;
+ self.think = W_BallisticBullet_LeaveSolid_think;
+ self.nextthink = time + dt;
+
+ vel = vel * velfactor;
+
+ self.velocity = '0 0 0';
+ self.flags |= FL_ONGROUND; // prevent moving
+ self.W_BallisticBullet_LeaveSolid_velocity = vel;
+
+ if(eff >= 0)
+ if(vlen(trace_endpos - self.origin) > 4)
+ {
+ endzcurveparticles();
+ trailparticles(self, eff, self.origin, trace_endpos);
+ }
+
+ return 1;
+}
+
+void W_BallisticBullet_Touch (void)
+{
+ //float density;
+
+ if(self.think == W_BallisticBullet_LeaveSolid_think) // skip this!
+ return;
+
+ PROJECTILE_TOUCH;
+ W_BallisticBullet_Hit ();
+
+ if(self.dmg_radius < 0) // these NEVER penetrate solid
+ {
+ remove(self);
+ return;
+ }
+
+ // if we hit "weapclip", bail out
+ //
+ // rationale of this check:
+ //
+ // any shader that is solid, nodraw AND trans is meant to clip weapon
+ // shots and players, but has no other effect!
+ //
+ // if it is not trans, it is caulk and should not have this side effect
+ //
+ // matching shaders:
+ // common/weapclip (intended)
+ // common/noimpact (is supposed to eat projectiles, but is erased farther above)
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
+ if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
+ if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
+ {
+ remove(self);
+ return;
+ }
+
+ // go through solid!
+ if(!W_BallisticBullet_LeaveSolid(-1))
+ {
+ remove(self);
+ return;
+ }
+
+ self.projectiledeathtype |= HITTYPE_BOUNCE;
+}
+
+void endFireBallisticBullet()
+{
+ endzcurveparticles();
+}
+
+entity fireBallisticBullet_trace_callback_ent;
+float fireBallisticBullet_trace_callback_eff;
+void fireBallisticBullet_trace_callback(vector start, vector hit, vector end)
+{
+ if(vlen(trace_endpos - fireBallisticBullet_trace_callback_ent.origin) > 16)
+ zcurveparticles_from_tracetoss(fireBallisticBullet_trace_callback_eff, fireBallisticBullet_trace_callback_ent.origin, trace_endpos, fireBallisticBullet_trace_callback_ent.velocity);
+ WarpZone_trace_forent = world;
+ self.owner = world;
+}
+
+void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, float lifetime, float damage, float force, float dtype, float tracereffects, float gravityfactor, float bulletconstant)
+{
+ float lag, dt, savetime; //, density;
+ entity pl, oldself;
+ float antilagging;
+
+ antilagging = (autocvar_g_antilag_bullets && (pSpeed >= autocvar_g_antilag_bullets));
+
+ entity proj;
+ proj = spawn();
+ proj.classname = "bullet";
+ proj.owner = proj.realowner = self;
+ PROJECTILE_MAKETRIGGER(proj);
+ if(gravityfactor > 0)
+ {
+ proj.movetype = MOVETYPE_TOSS;
+ proj.gravity = gravityfactor;
+ }
+ else
+ proj.movetype = MOVETYPE_FLY;
+ proj.think = SUB_Remove;
+ proj.nextthink = time + lifetime; // min(pLifetime, vlen(world.maxs - world.mins) / pSpeed);
+ W_SetupProjectileVelocityEx(proj, dir, v_up, pSpeed, 0, 0, spread, antilagging);
+ proj.angles = vectoangles(proj.velocity);
+ if(bulletconstant > 0)
+ proj.dmg_radius = autocvar_g_ballistics_materialconstant / bulletconstant;
+ else if(bulletconstant == 0)
+ proj.dmg_radius = 0;
+ else
+ proj.dmg_radius = -1;
+ // so: bulletconstant = bullet mass / area of bullet circle
+ setorigin(proj, start);
+ proj.flags = FL_PROJECTILE;
+
+ proj.touch = W_BallisticBullet_Touch;
+ proj.dmg = damage;
+ proj.dmg_force = force;
+ proj.projectiledeathtype = dtype;
+
+ proj.oldvelocity = proj.velocity;
+
+ other = proj; MUTATOR_CALLHOOK(EditProjectile);
+
+ if(antilagging)
+ {
+ float eff;
+
+ if(tracereffects & EF_RED)
+ eff = particleeffectnum("tr_rifle");
+ else if(tracereffects & EF_BLUE)
+ eff = particleeffectnum("tr_rifle_weak");
+ else
+ eff = particleeffectnum("tr_bullet");
+
+ // NOTE: this may severely throw off weapon balance
+ lag = ANTILAG_LATENCY(self);
+ if(lag < 0.001)
+ lag = 0;
+ if not(IS_REAL_CLIENT(self))
+ lag = 0;
+ if(autocvar_g_antilag == 0 || self.cvar_cl_noantilag)
+ lag = 0; // only do hitscan, but no antilag
+
+ if(lag)
+ FOR_EACH_PLAYER(pl)
+ if(pl != self)
+ antilag_takeback(pl, time - lag);
+
+ oldself = self;
+ self = proj;
+
+ savetime = frametime;
+ frametime = 0.05;
+
+ for(;;)
+ {
+ // DP tracetoss is stupid and always traces in 0.05s
+ // ticks. This makes it trace in 0.05*0.125s ticks
+ // instead.
+ vector v0;
+ float g0;
+ v0 = self.velocity;
+ g0 = self.gravity;
+ self.velocity = self.velocity * 0.125;
+ self.gravity *= 0.125 * 0.125;
+ trace_fraction = 0;
+ fireBallisticBullet_trace_callback_ent = self;
+ fireBallisticBullet_trace_callback_eff = eff;
+ WarpZone_TraceToss_ThroughZone(self, self.owner, world, fireBallisticBullet_trace_callback);
+ self.velocity = v0;
+ self.gravity = g0;
+
+ if(trace_fraction == 1)
+ break;
+ // won't hit anything anytime soon (DP's
+ // tracetoss does 200 tics of, here,
+ // 0.05*0.125s, that is, 1.25 seconds
+
+ other = trace_ent;
+ dt = WarpZone_tracetoss_time * 0.125; // this is only approximate!
+ setorigin(self, trace_endpos);
+ self.velocity = WarpZone_tracetoss_velocity * (1 / 0.125);
+
+ if(!SUB_OwnerCheck())
+ {
+ if(SUB_NoImpactCheck())
+ break;
+
+ // hit the player
+ W_BallisticBullet_Hit();
+ }
+
+ if(proj.dmg_radius < 0) // these NEVER penetrate solid
+ break;
+
+ // if we hit "weapclip", bail out
+ //
+ // rationale of this check:
+ //
+ // any shader that is solid, nodraw AND trans is meant to clip weapon
+ // shots and players, but has no other effect!
+ //
+ // if it is not trans, it is caulk and should not have this side effect
+ //
+ // matching shaders:
+ // common/weapclip (intended)
+ // common/noimpact (is supposed to eat projectiles, but is erased farther above)
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW)
+ if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)
+ if not(trace_dphitcontents & DPCONTENTS_OPAQUE)
+ break;
+
+ // go through solid!
+ if(!W_BallisticBullet_LeaveSolid((other && (other.solid != SOLID_BSP)) ? eff : -1))
+ break;
+
+ W_BallisticBullet_LeaveSolid_think();
+
+ self.projectiledeathtype |= HITTYPE_BOUNCE;
+ }
+ frametime = savetime;
+ self = oldself;
+
+ if(lag)
+ FOR_EACH_PLAYER(pl)
+ if(pl != self)
+ antilag_restore(pl);
+
+ remove(proj);
+
+ return;
+ }
+
+ if(tracereffects & EF_RED)
+ CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE);
+ else if(tracereffects & EF_BLUE)
+ CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING, TRUE);
+ else
+ CSQCProjectile(proj, TRUE, PROJECTILE_BULLET, TRUE);
+}
+
+void fireBullet (vector start, vector dir, float spread, float damage, float force, float dtype, float tracer)
+{
+ vector end;
+
+ dir = normalize(dir + randomvec() * spread);
+ end = start + dir * MAX_SHOT_DISTANCE;
+ if(self.antilag_debug)
+ traceline_antilag (self, start, end, FALSE, self, self.antilag_debug);
+ else
+ traceline_antilag (self, start, end, FALSE, self, ANTILAG_LATENCY(self));
+
+ end = trace_endpos;
+
+ if (pointcontents (trace_endpos) != CONTENT_SKY)
+ {
+ if not (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+ Damage_DamageInfo(trace_endpos, damage, 0, 0, dir * max(1, force), dtype, trace_ent.species, self);
+
+ Damage (trace_ent, self, self, damage, dtype, trace_endpos, dir * force);
+ }
+ trace_endpos = end;
+}
+
+float W_CheckProjectileDamage(entity inflictor, entity projowner, float deathtype, float exception)
+{
+ float is_from_contents = (deathtype == DEATH_SLIME || deathtype == DEATH_LAVA);
+ float is_from_owner = (inflictor == projowner);
+ float is_from_exception = (exception != -1);
+
+ //dprint(strcat("W_CheckProjectileDamage: from_contents ", ftos(is_from_contents), " : from_owner ", ftos(is_from_owner), " : exception ", strcat(ftos(is_from_exception), " (", ftos(exception), "). \n")));
+
+ if(autocvar_g_projectiles_damage <= -2)
+ {
+ return FALSE; // no damage to projectiles at all, not even with the exceptions
+ }
+ else if(autocvar_g_projectiles_damage == -1)
+ {
+ if(is_from_exception)
+ return (exception); // if exception is detected, allow it to override
+ else
+ return FALSE; // otherwise, no other damage is allowed
+ }
+ else if(autocvar_g_projectiles_damage == 0)
+ {
+ if(is_from_exception)
+ return (exception); // if exception is detected, allow it to override
+ else if not(is_from_contents)
+ return FALSE; // otherwise, only allow damage from contents
+ }
+ else if(autocvar_g_projectiles_damage == 1)
+ {
+ if(is_from_exception)
+ return (exception); // if exception is detected, allow it to override
+ else if not(is_from_contents || is_from_owner)
+ return FALSE; // otherwise, only allow self damage and damage from contents
+ }
+ else if(autocvar_g_projectiles_damage == 2) // allow any damage, but override for exceptions
+ {
+ if(is_from_exception)
+ return (exception); // if exception is detected, allow it to override
+ }
+
+ return TRUE; // if none of these return, then allow damage anyway.
+}
+
+void W_PrepareExplosionByDamage(entity attacker, void() explode)
+{
+ self.takedamage = DAMAGE_NO;
+ self.event_damage = func_null;
+
+ if(IS_CLIENT(attacker) && !autocvar_g_projectiles_keep_owner)
+ {
+ self.owner = attacker;
+ self.realowner = attacker;
+ }
+
+ // do not explode NOW but in the NEXT FRAME!
+ // because recursive calls to RadiusDamage are not allowed
+ self.nextthink = time;
+ self.think = explode;
+}