From: Samual Lenks Date: Mon, 10 Jun 2013 05:33:06 +0000 (-0400) Subject: Working on moving the weapon files to proper directories (plus cleanup) X-Git-Tag: xonotic-v0.8.0~152^2~404 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=d22c6120af8d0f5e794bde4c4c68a76ad23e3445;p=xonotic%2Fxonotic-data.pk3dir.git Working on moving the weapon files to proper directories (plus cleanup) --- diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index 3b8aa1bac..e409f7749 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -106,7 +106,7 @@ noise.qc ../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 diff --git a/qcsrc/common/items.qh b/qcsrc/common/items.qh index ba42d55ac..be17042aa 100644 --- a/qcsrc/common/items.qh +++ b/qcsrc/common/items.qh @@ -228,7 +228,7 @@ WEPSET_DECLARE_A(WEPBIT_SUPERWEAPONS); 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) diff --git a/qcsrc/common/weapons/w_all.qc b/qcsrc/common/weapons/w_all.qc new file mode 100644 index 000000000..46af3565e --- /dev/null +++ b/qcsrc/common/weapons/w_all.qc @@ -0,0 +1,22 @@ +// 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" diff --git a/qcsrc/common/weapons/w_crylink.qc b/qcsrc/common/weapons/w_crylink.qc new file mode 100644 index 000000000..7cfc6c385 --- /dev/null +++ b/qcsrc/common/weapons/w_crylink.qc @@ -0,0 +1,736 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ CRYLINK, +/* function */ w_crylink, +/* ammotype */ IT_CELLS, +/* impulse */ 6, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "crylink", +/* shortname */ "crylink", +/* fullname */ _("Crylink") +); +#else +#ifdef SVQC +.float gravity; +.float crylink_waitrelease; +.entity crylink_lastgroup; + +.entity queuenext; +.entity queueprev; + +void W_Crylink_CheckLinks(entity e) +{ + float i; + entity p; + + if(e == world) + error("W_Crylink_CheckLinks: entity is world"); + if(e.classname != "spike" || wasfreed(e)) + error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e))); + + p = e; + for(i = 0; i < 1000; ++i) + { + if(p.queuenext.queueprev != p || p.queueprev.queuenext != p) + error("W_Crylink_CheckLinks: queue is inconsistent"); + p = p.queuenext; + if(p == e) + break; + } + if(i >= 1000) + error("W_Crylink_CheckLinks: infinite chain"); +} + +void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next) +{ + W_Crylink_CheckLinks(next); + if(me == own.crylink_lastgroup) + own.crylink_lastgroup = ((me == next) ? world : next); + prev.queuenext = next; + next.queueprev = prev; + me.classname = "spike_oktoremove"; + if(me != next) + W_Crylink_CheckLinks(next); +} + +void W_Crylink_Dequeue(entity e) +{ + W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext); +} + +void W_Crylink_Reset(void) +{ + W_Crylink_Dequeue(self); + remove(self); +} + +// force projectile to explode +void W_Crylink_LinkExplode (entity e, entity e2) +{ + float a; + + if(e == e2) + return; + + a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1); + + if(e == e.realowner.crylink_lastgroup) + e.realowner.crylink_lastgroup = world; + + if(e.projectiledeathtype & HITTYPE_SECONDARY) + RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_damage * a, autocvar_g_balance_crylink_secondary_edgedamage * a, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * a, e.projectiledeathtype, other); + else + RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_damage * a, autocvar_g_balance_crylink_primary_edgedamage * a, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * a, e.projectiledeathtype, other); + + W_Crylink_LinkExplode(e.queuenext, e2); + + e.classname = "spike_oktoremove"; + remove (e); +} + +// adjust towards center +// returns the origin where they will meet... and the time till the meeting is +// stored in w_crylink_linkjoin_time. +// could possibly network this origin and time, and display a special particle +// effect when projectiles meet there :P +// jspeed: MINIMUM jing speed +// jtime: MAXIMUM jing time (0: none) +float w_crylink_linkjoin_time; +vector W_Crylink_LinkJoin(entity e, float jspeed, float jtime) +{ + vector avg_origin, avg_velocity; + vector targ_origin; + float avg_dist, n; + entity p; + + // FIXME remove this debug code + W_Crylink_CheckLinks(e); + + w_crylink_linkjoin_time = 0; + + avg_origin = e.origin; + avg_velocity = e.velocity; + n = 1; + for(p = e; (p = p.queuenext) != e; ) + { + avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin); + avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity); + ++n; + } + avg_origin *= (1.0 / n); + avg_velocity *= (1.0 / n); + + if(n < 2) + return avg_origin; // nothing to do + + // yes, mathematically we can do this in ONE step, but beware of 32bit floats... + avg_dist = pow(vlen(e.origin - avg_origin), 2); + for(p = e; (p = p.queuenext) != e; ) + avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2); + avg_dist *= (1.0 / n); + avg_dist = sqrt(avg_dist); + + if(avg_dist == 0) + return avg_origin; // no change needed + + if(jspeed == 0 && jtime == 0) + { + e.velocity = avg_velocity; + UpdateCSQCProjectile(e); + for(p = e; (p = p.queuenext) != e; ) + { + p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity); + UpdateCSQCProjectile(p); + } + targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE + } + else + { + if(jtime) + { + if(jspeed) + w_crylink_linkjoin_time = min(jtime, avg_dist / jspeed); + else + w_crylink_linkjoin_time = jtime; + } + else + w_crylink_linkjoin_time = avg_dist / jspeed; + targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity; + + e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time); + UpdateCSQCProjectile(e); + for(p = e; (p = p.queuenext) != e; ) + { + p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time)); + UpdateCSQCProjectile(p); + } + + // analysis: + // jspeed -> +infinity: + // w_crylink_linkjoin_time -> +0 + // targ_origin -> avg_origin + // p->velocity -> HUEG towards center + // jspeed -> 0: + // w_crylink_linkjoin_time -> +/- infinity + // targ_origin -> avg_velocity * +/- infinity + // p->velocity -> avg_velocity + // jspeed -> -infinity: + // w_crylink_linkjoin_time -> -0 + // targ_origin -> avg_origin + // p->velocity -> HUEG away from center + } + + W_Crylink_CheckLinks(e); + + return targ_origin; +} + +void W_Crylink_LinkJoinEffect_Think() +{ + // is there at least 2 projectiles very close? + entity e, p; + float n; + e = self.owner.crylink_lastgroup; + n = 0; + if(e) + { + if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime) + ++n; + for(p = e; (p = p.queuenext) != e; ) + { + if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime) + ++n; + } + if(n >= 2) + { + if(e.projectiledeathtype & HITTYPE_SECONDARY) + { + if(autocvar_g_balance_crylink_secondary_joinexplode) + { + n = n / autocvar_g_balance_crylink_secondary_shots; + RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_joinexplode_damage * n, + autocvar_g_balance_crylink_secondary_joinexplode_edgedamage * n, + autocvar_g_balance_crylink_secondary_joinexplode_radius * n, e.realowner, world, + autocvar_g_balance_crylink_secondary_joinexplode_force * n, e.projectiledeathtype, other); + + pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n); + } + } + else + { + if(autocvar_g_balance_crylink_primary_joinexplode) + { + n = n / autocvar_g_balance_crylink_primary_shots; + RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_joinexplode_damage * n, + autocvar_g_balance_crylink_primary_joinexplode_edgedamage * n, + autocvar_g_balance_crylink_primary_joinexplode_radius * n, e.realowner, world, + autocvar_g_balance_crylink_primary_joinexplode_force * n, e.projectiledeathtype, other); + + pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n); + } + } + } + } + remove(self); +} + +float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad) +{ + entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, FALSE); + float hit_friendly = 0; + float hit_enemy = 0; + + while(head) + { + if((head.takedamage != DAMAGE_NO) && (head.deadflag == DEAD_NO)) + { + if(IsDifferentTeam(head, projectile.realowner)) + ++hit_enemy; + else + ++hit_friendly; + } + + head = head.chain; + } + + return (hit_enemy ? FALSE : hit_friendly); +} + +// NO bounce protection, as bounces are limited! +void W_Crylink_Touch (void) +{ + float finalhit; + float f; + PROJECTILE_TOUCH; + + float a; + a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1); + + finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO)); + if(finalhit) + f = 1; + else + f = autocvar_g_balance_crylink_primary_bouncedamagefactor; + if(a) + f *= a; + + float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_primary_damage * f, autocvar_g_balance_crylink_primary_edgedamage * f, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * f, self.projectiledeathtype, other); + + if(totaldamage && ((autocvar_g_balance_crylink_primary_linkexplode == 2) || ((autocvar_g_balance_crylink_primary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_primary_radius)))) + { + if(self == self.realowner.crylink_lastgroup) + self.realowner.crylink_lastgroup = world; + W_Crylink_LinkExplode(self.queuenext, self); + self.classname = "spike_oktoremove"; + remove (self); + return; + } + else if(finalhit) + { + // just unlink + W_Crylink_Dequeue(self); + remove(self); + return; + } + self.cnt = self.cnt - 1; + self.angles = vectoangles(self.velocity); + self.owner = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + // commented out as it causes a little hitch... + //if(proj.cnt == 0) + // CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE); +} + +void W_Crylink_Touch2 (void) +{ + float finalhit; + float f; + PROJECTILE_TOUCH; + + float a; + a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1); + + finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO)); + if(finalhit) + f = 1; + else + f = autocvar_g_balance_crylink_secondary_bouncedamagefactor; + if(a) + f *= a; + + float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_secondary_damage * f, autocvar_g_balance_crylink_secondary_edgedamage * f, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * f, self.projectiledeathtype, other); + + if(totaldamage && ((autocvar_g_balance_crylink_secondary_linkexplode == 2) || ((autocvar_g_balance_crylink_secondary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_secondary_radius)))) + { + if(self == self.realowner.crylink_lastgroup) + self.realowner.crylink_lastgroup = world; + W_Crylink_LinkExplode(self.queuenext, self); + self.classname = "spike_oktoremove"; + remove (self); + return; + } + else if(finalhit) + { + // just unlink + W_Crylink_Dequeue(self); + remove(self); + return; + } + self.cnt = self.cnt - 1; + self.angles = vectoangles(self.velocity); + self.owner = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + // commented out as it causes a little hitch... + //if(proj.cnt == 0) + // CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE); +} + +void W_Crylink_Fadethink (void) +{ + W_Crylink_Dequeue(self); + remove(self); +} + +void W_Crylink_Attack (void) +{ + float counter, shots; + entity proj, prevproj, firstproj; + vector s; + vector forward, right, up; + float maxdmg; + + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_reload_ammo); + + maxdmg = autocvar_g_balance_crylink_primary_damage*autocvar_g_balance_crylink_primary_shots; + maxdmg *= 1 + autocvar_g_balance_crylink_primary_bouncedamagefactor * autocvar_g_balance_crylink_primary_bounces; + if(autocvar_g_balance_crylink_primary_joinexplode) + maxdmg += autocvar_g_balance_crylink_primary_joinexplode_damage; + + W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", CH_WEAPON_A, maxdmg); + forward = v_forward; + right = v_right; + up = v_up; + + shots = autocvar_g_balance_crylink_primary_shots; + pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots); + proj = prevproj = firstproj = world; + for(counter = 0; counter < shots; ++counter) + { + proj = spawn (); + proj.reset = W_Crylink_Reset; + proj.realowner = proj.owner = self; + proj.classname = "spike"; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_balance_crylink_primary_damage; + if(shots == 1) { + proj.queuenext = proj; + proj.queueprev = proj; + } + else if(counter == 0) { // first projectile, store in firstproj for now + firstproj = proj; + } + else if(counter == shots - 1) { // last projectile, link up with first projectile + prevproj.queuenext = proj; + firstproj.queueprev = proj; + proj.queuenext = firstproj; + proj.queueprev = prevproj; + } + else { // else link up with previous projectile + prevproj.queuenext = proj; + proj.queueprev = prevproj; + } + + prevproj = proj; + + proj.movetype = MOVETYPE_BOUNCEMISSILE; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_CRYLINK; + //proj.gravity = 0.001; + + setorigin (proj, w_shotorg); + setsize(proj, '0 0 0', '0 0 0'); + + + s = '0 0 0'; + if (counter == 0) + s = '0 0 0'; + else + { + makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1))); + s_y = v_forward_x; + s_z = v_forward_y; + } + s = s * autocvar_g_balance_crylink_primary_spread * g_weaponspreadfactor; + W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_crylink_primary_speed, 0, 0, 0, FALSE); + proj.touch = W_Crylink_Touch; + + proj.think = W_Crylink_Fadethink; + if(counter == 0) + { + proj.fade_time = time + autocvar_g_balance_crylink_primary_middle_lifetime; + proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_middle_fadetime; + proj.nextthink = time + autocvar_g_balance_crylink_primary_middle_lifetime + autocvar_g_balance_crylink_primary_middle_fadetime; + } + else + { + proj.fade_time = time + autocvar_g_balance_crylink_primary_other_lifetime; + proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_other_fadetime; + proj.nextthink = time + autocvar_g_balance_crylink_primary_other_lifetime + autocvar_g_balance_crylink_primary_other_fadetime; + } + proj.teleport_time = time + autocvar_g_balance_crylink_primary_joindelay; + proj.cnt = autocvar_g_balance_crylink_primary_bounces; + //proj.scale = 1 + 1 * proj.cnt; + + proj.angles = vectoangles (proj.velocity); + + //proj.glow_size = 20; + + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH; + + CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE); + + other = proj; MUTATOR_CALLHOOK(EditProjectile); + } + if(autocvar_g_balance_crylink_primary_joinspread != 0 || autocvar_g_balance_crylink_primary_jointime != 0) + { + self.crylink_lastgroup = proj; + W_Crylink_CheckLinks(proj); + self.crylink_waitrelease = 1; + } +} + +void W_Crylink_Attack2 (void) +{ + float counter, shots; + entity proj, prevproj, firstproj; + vector s; + vector forward, right, up; + float maxdmg; + + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_secondary_ammo, autocvar_g_balance_crylink_reload_ammo); + + maxdmg = autocvar_g_balance_crylink_secondary_damage*autocvar_g_balance_crylink_secondary_shots; + maxdmg *= 1 + autocvar_g_balance_crylink_secondary_bouncedamagefactor * autocvar_g_balance_crylink_secondary_bounces; + if(autocvar_g_balance_crylink_secondary_joinexplode) + maxdmg += autocvar_g_balance_crylink_secondary_joinexplode_damage; + + W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", CH_WEAPON_A, maxdmg); + forward = v_forward; + right = v_right; + up = v_up; + + shots = autocvar_g_balance_crylink_secondary_shots; + pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots); + proj = prevproj = firstproj = world; + for(counter = 0; counter < shots; ++counter) + { + proj = spawn (); + proj.reset = W_Crylink_Reset; + proj.realowner = proj.owner = self; + proj.classname = "spike"; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_balance_crylink_secondary_damage; + if(shots == 1) { + proj.queuenext = proj; + proj.queueprev = proj; + } + else if(counter == 0) { // first projectile, store in firstproj for now + firstproj = proj; + } + else if(counter == shots - 1) { // last projectile, link up with first projectile + prevproj.queuenext = proj; + firstproj.queueprev = proj; + proj.queuenext = firstproj; + proj.queueprev = prevproj; + } + else { // else link up with previous projectile + prevproj.queuenext = proj; + proj.queueprev = prevproj; + } + + prevproj = proj; + + proj.movetype = MOVETYPE_BOUNCEMISSILE; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY; + //proj.gravity = 0.001; + + setorigin (proj, w_shotorg); + setsize(proj, '0 0 0', '0 0 0'); + + if(autocvar_g_balance_crylink_secondary_spreadtype == 1) + { + s = '0 0 0'; + if (counter == 0) + s = '0 0 0'; + else + { + makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1))); + s_y = v_forward_x; + s_z = v_forward_y; + } + s = s * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor; + s = w_shotdir + right * s_y + up * s_z; + } + else + { + s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor); + } + + W_SetupProjectileVelocityEx(proj, s, v_up, autocvar_g_balance_crylink_secondary_speed, 0, 0, 0, FALSE); + proj.touch = W_Crylink_Touch2; + proj.think = W_Crylink_Fadethink; + if(counter == (shots - 1) / 2) + { + proj.fade_time = time + autocvar_g_balance_crylink_secondary_middle_lifetime; + proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_middle_fadetime; + proj.nextthink = time + autocvar_g_balance_crylink_secondary_middle_lifetime + autocvar_g_balance_crylink_secondary_middle_fadetime; + } + else + { + proj.fade_time = time + autocvar_g_balance_crylink_secondary_line_lifetime; + proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_line_fadetime; + proj.nextthink = time + autocvar_g_balance_crylink_secondary_line_lifetime + autocvar_g_balance_crylink_secondary_line_fadetime; + } + proj.teleport_time = time + autocvar_g_balance_crylink_secondary_joindelay; + proj.cnt = autocvar_g_balance_crylink_secondary_bounces; + //proj.scale = 1 + 1 * proj.cnt; + + proj.angles = vectoangles (proj.velocity); + + //proj.glow_size = 20; + + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH; + + CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE); + + other = proj; MUTATOR_CALLHOOK(EditProjectile); + } + if(autocvar_g_balance_crylink_secondary_joinspread != 0 || autocvar_g_balance_crylink_secondary_jointime != 0) + { + self.crylink_lastgroup = proj; + W_Crylink_CheckLinks(proj); + self.crylink_waitrelease = 2; + } +} + +void spawnfunc_weapon_crylink (void) +{ + weapon_defaultspawnfunc(WEP_CRYLINK); +} + +float w_crylink(float req) +{ + float ammo_amount; + if (req == WR_AIM) + { + if (random() < 0.10) + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_crylink_primary_speed, 0, autocvar_g_balance_crylink_primary_middle_lifetime, FALSE); + else + self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_crylink_secondary_speed, 0, autocvar_g_balance_crylink_secondary_middle_lifetime, FALSE); + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + + if (self.BUTTON_ATCK) + { + if (self.crylink_waitrelease != 1) + if (weapon_prepareattack(0, autocvar_g_balance_crylink_primary_refire)) + { + W_Crylink_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_crylink_primary_animtime, w_ready); + } + } + + if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary) + { + if (self.crylink_waitrelease != 2) + if (weapon_prepareattack(1, autocvar_g_balance_crylink_secondary_refire)) + { + W_Crylink_Attack2(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_crylink_secondary_animtime, w_ready); + } + } + + if ((self.crylink_waitrelease == 1 && !self.BUTTON_ATCK) || (self.crylink_waitrelease == 2 && !self.BUTTON_ATCK2)) + { + if (!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time) + { + // fired and released now! + if(self.crylink_lastgroup) + { + vector pos; + entity linkjoineffect; + + if(self.crylink_waitrelease == 1) + { + pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_primary_joinspread * autocvar_g_balance_crylink_primary_speed, autocvar_g_balance_crylink_primary_jointime); + + } + else + { + pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_secondary_joinspread * autocvar_g_balance_crylink_secondary_speed, autocvar_g_balance_crylink_secondary_jointime); + } + + linkjoineffect = spawn(); + linkjoineffect.think = W_Crylink_LinkJoinEffect_Think; + linkjoineffect.classname = "linkjoineffect"; + linkjoineffect.nextthink = time + w_crylink_linkjoin_time; + linkjoineffect.owner = self; + setorigin(linkjoineffect, pos); + } + self.crylink_waitrelease = 0; + if(!w_crylink(WR_CHECKAMMO1) && !w_crylink(WR_CHECKAMMO2)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + // ran out of ammo! + self.cnt = WEP_CRYLINK; + self.switchweapon = w_getbestweapon(self); + } + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_crylink.md3"); + precache_model ("models/weapons/v_crylink.md3"); + precache_model ("models/weapons/h_crylink.iqm"); + precache_sound ("weapons/crylink_fire.wav"); + precache_sound ("weapons/crylink_fire2.wav"); + precache_sound ("weapons/crylink_linkjoin.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_CRYLINK); + self.current_ammo = ammo_cells; + } + else if (req == WR_CHECKAMMO1) + { + // don't "run out of ammo" and switch weapons while waiting for release + if(self.crylink_lastgroup && self.crylink_waitrelease) + return TRUE; + + ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_primary_ammo; + ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + // don't "run out of ammo" and switch weapons while waiting for release + if(self.crylink_lastgroup && self.crylink_waitrelease) + return TRUE; + + ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_secondary_ammo; + return ammo_amount; + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo), autocvar_g_balance_crylink_reload_ammo, autocvar_g_balance_crylink_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_CRYLINK_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + return WEAPON_CRYLINK_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_crylink(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 2; + if(w_deathtype & HITTYPE_SECONDARY) + { + pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM); + } + else + { + pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM); + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/crylink_impact2.wav"); + precache_sound("weapons/crylink_impact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_electro.qc b/qcsrc/common/weapons/w_electro.qc new file mode 100644 index 000000000..bfd9ebec8 --- /dev/null +++ b/qcsrc/common/weapons/w_electro.qc @@ -0,0 +1,619 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ ELECTRO, +/* function */ w_electro, +/* ammotype */ IT_CELLS, +/* impulse */ 5, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "electro", +/* shortname */ "electro", +/* fullname */ _("Electro") +); +#else +#ifdef SVQC +.float electro_count; +.float electro_secondarytime; + +void W_Plasma_Explode_Combo (void); + +void W_Plasma_TriggerCombo(vector org, float rad, entity own) +{ + entity e; + e = WarpZone_FindRadius(org, rad, TRUE); + while (e) + { + if (e.classname == "plasma") + { + // change owner to whoever caused the combo explosion + e.realowner = own; + e.takedamage = DAMAGE_NO; + e.classname = "plasma_chain"; + e.think = W_Plasma_Explode_Combo; + e.nextthink = time + vlen(e.WarpZone_findradius_dist) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler + } + e = e.chain; + } +} + +void W_Plasma_Explode (void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + if (self.movetype == MOVETYPE_BOUNCE) + { + RadiusDamage (self, self.realowner, autocvar_g_balance_electro_secondary_damage, autocvar_g_balance_electro_secondary_edgedamage, autocvar_g_balance_electro_secondary_radius, world, world, autocvar_g_balance_electro_secondary_force, self.projectiledeathtype, other); + } + else + { + W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_primary_comboradius, self.realowner); + RadiusDamage (self, self.realowner, autocvar_g_balance_electro_primary_damage, autocvar_g_balance_electro_primary_edgedamage, autocvar_g_balance_electro_primary_radius, world, world, autocvar_g_balance_electro_primary_force, self.projectiledeathtype, other); + } + + remove (self); +} + +void W_Plasma_Explode_Combo (void) +{ + W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_combo_comboradius, self.realowner); + + self.event_damage = func_null; + RadiusDamage (self, self.realowner, autocvar_g_balance_electro_combo_damage, autocvar_g_balance_electro_combo_edgedamage, autocvar_g_balance_electro_combo_radius, world, world, autocvar_g_balance_electro_combo_force, WEP_ELECTRO | HITTYPE_BOUNCE, world); // use THIS type for a combo because primary can't bounce + + remove (self); +} + +void W_Plasma_Touch (void) +{ + //self.velocity = self.velocity * 0.1; + + PROJECTILE_TOUCH; + if (other.takedamage == DAMAGE_AIM) { + W_Plasma_Explode (); + } else { + //UpdateCSQCProjectile(self); + spamsound (self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTN_NORM); + self.projectiledeathtype |= HITTYPE_BOUNCE; + } +} + +void W_Plasma_TouchExplode (void) +{ + PROJECTILE_TOUCH; + W_Plasma_Explode (); +} + +void W_Plasma_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if(self.health <= 0) + return; + + // note: combos are usually triggered by W_Plasma_TriggerCombo, not damage + float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim"); + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1))) + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + if (self.health <= 0) + { + self.takedamage = DAMAGE_NO; + self.nextthink = time; + if (is_combo) + { + // change owner to whoever caused the combo explosion + self.realowner = inflictor.realowner; + self.classname = "plasma_chain"; + self.think = W_Plasma_Explode_Combo; + self.nextthink = time + min(autocvar_g_balance_electro_combo_radius, vlen(self.origin - inflictor.origin)) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bounding the length, because inflictor may be in a galaxy far far away (warpzones) + } + else + { + self.use = W_Plasma_Explode; + self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately" + } + } +} + +void W_Electro_Attack() +{ + entity proj; + + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_reload_ammo); + + W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, "weapons/electro_fire.wav", CH_WEAPON_A, autocvar_g_balance_electro_primary_damage); + + pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + proj = spawn (); + proj.classname = "plasma_prim"; + proj.owner = proj.realowner = self; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_balance_electro_primary_damage; + proj.use = W_Plasma_Explode; + proj.think = adaptor_think2use_hittype_splash; + proj.nextthink = time + autocvar_g_balance_electro_primary_lifetime; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO; + setorigin(proj, w_shotorg); + + proj.movetype = MOVETYPE_FLY; + W_SETUPPROJECTILEVELOCITY(proj, g_balance_electro_primary); + proj.angles = vectoangles(proj.velocity); + proj.touch = W_Plasma_TouchExplode; + setsize(proj, '0 0 -3', '0 0 -3'); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH; + + CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO_BEAM, TRUE); + + other = proj; MUTATOR_CALLHOOK(EditProjectile); +} + +void W_Electro_Attack2() +{ + entity proj; + + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_secondary_ammo, autocvar_g_balance_electro_reload_ammo); + + W_SetupShot_ProjectileSize (self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, autocvar_g_balance_electro_secondary_damage); + + w_shotdir = v_forward; // no TrueAim for grenades please + + pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + proj = spawn (); + proj.classname = "plasma"; + proj.owner = proj.realowner = self; + proj.use = W_Plasma_Explode; + proj.think = adaptor_think2use_hittype_splash; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_balance_electro_secondary_damage; + proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY; + setorigin(proj, w_shotorg); + + //proj.glow_size = 50; + //proj.glow_color = 45; + proj.movetype = MOVETYPE_BOUNCE; + W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary); + proj.touch = W_Plasma_Touch; + setsize(proj, '0 0 -4', '0 0 -4'); + proj.takedamage = DAMAGE_YES; + proj.damageforcescale = autocvar_g_balance_electro_secondary_damageforcescale; + proj.health = autocvar_g_balance_electro_secondary_health; + proj.event_damage = W_Plasma_Damage; + proj.flags = FL_PROJECTILE; + proj.damagedbycontents = (autocvar_g_balance_electro_secondary_damagedbycontents); + + proj.bouncefactor = autocvar_g_balance_electro_secondary_bouncefactor; + proj.bouncestop = autocvar_g_balance_electro_secondary_bouncestop; + proj.missile_flags = MIF_SPLASH | MIF_ARC; + +#if 0 + entity p2; + p2 = spawn(); + copyentity(proj, p2); + setmodel(p2, "models/ebomb.mdl"); + setsize(p2, proj.mins, proj.maxs); +#endif + + CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound + + other = proj; MUTATOR_CALLHOOK(EditProjectile); +} + +.vector hook_start, hook_end; +float lgbeam_send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_ELECTRO_BEAM); + sf = sf & 0x7F; + if(sound_allowed(MSG_BROADCAST, self.realowner)) + sf |= 0x80; + WriteByte(MSG_ENTITY, sf); + if(sf & 1) + { + WriteByte(MSG_ENTITY, num_for_edict(self.realowner)); + WriteCoord(MSG_ENTITY, autocvar_g_balance_electro_primary_range); + } + if(sf & 2) + { + WriteCoord(MSG_ENTITY, self.hook_start_x); + WriteCoord(MSG_ENTITY, self.hook_start_y); + WriteCoord(MSG_ENTITY, self.hook_start_z); + } + if(sf & 4) + { + WriteCoord(MSG_ENTITY, self.hook_end_x); + WriteCoord(MSG_ENTITY, self.hook_end_y); + WriteCoord(MSG_ENTITY, self.hook_end_z); + } + return TRUE; +} +.entity lgbeam; +.float prevlgfire; +float lgbeam_checkammo() +{ + if(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO) + return TRUE; + else if(autocvar_g_balance_electro_reload_ammo) + return self.realowner.clip_load > 0; + else + return self.realowner.ammo_cells > 0; +} + +entity lgbeam_owner_ent; +void lgbeam_think() +{ + entity owner_player; + owner_player = self.realowner; + + owner_player.prevlgfire = time; + if (self != owner_player.lgbeam) + { + remove(self); + return; + } + + if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen) + { + if(self == owner_player.lgbeam) + owner_player.lgbeam = world; + remove(self); + return; + } + + self.nextthink = time; + + makevectors(owner_player.v_angle); + + float dt, f; + dt = frametime; + + // if this weapon is reloadable, decrease its load. Else decrease the player's ammo + if not(owner_player.items & IT_UNLIMITED_WEAPON_AMMO) + { + if(autocvar_g_balance_electro_primary_ammo) + { + if(autocvar_g_balance_electro_reload_ammo) + { + dt = min(dt, owner_player.clip_load / autocvar_g_balance_electro_primary_ammo); + owner_player.clip_load = max(0, owner_player.clip_load - autocvar_g_balance_electro_primary_ammo * frametime); + owner_player.(weapon_load[WEP_ELECTRO]) = owner_player.clip_load; + } + else + { + dt = min(dt, owner_player.ammo_cells / autocvar_g_balance_electro_primary_ammo); + owner_player.ammo_cells = max(0, owner_player.ammo_cells - autocvar_g_balance_electro_primary_ammo * frametime); + } + } + } + + W_SetupShot_Range(owner_player, TRUE, 0, "", 0, autocvar_g_balance_electro_primary_damage * dt, autocvar_g_balance_electro_primary_range); + if(!lgbeam_owner_ent) + { + lgbeam_owner_ent = spawn(); + lgbeam_owner_ent.classname = "lgbeam_owner_ent"; + } + WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(owner_player)); + + // apply the damage + if(trace_ent) + { + vector force; + force = w_shotdir * autocvar_g_balance_electro_primary_force + '0 0 1' * autocvar_g_balance_electro_primary_force_up; + + f = ExponentialFalloff(autocvar_g_balance_electro_primary_falloff_mindist, autocvar_g_balance_electro_primary_falloff_maxdist, autocvar_g_balance_electro_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg)); + + if(accuracy_isgooddamage(owner_player, trace_ent)) + accuracy_add(owner_player, WEP_ELECTRO, 0, autocvar_g_balance_electro_primary_damage * dt * f); + Damage (trace_ent, owner_player, owner_player, autocvar_g_balance_electro_primary_damage * dt * f, WEP_ELECTRO, trace_endpos, force * dt); + } + W_Plasma_TriggerCombo(trace_endpos, autocvar_g_balance_electro_primary_comboradius, owner_player); + + // draw effect + if(w_shotorg != self.hook_start) + { + self.SendFlags |= 2; + self.hook_start = w_shotorg; + } + if(w_shotend != self.hook_end) + { + self.SendFlags |= 4; + self.hook_end = w_shotend; + } +} + +// experimental lightning gun +void W_Electro_Attack3 (void) +{ + // only play fire sound if 0.5 sec has passed since player let go the fire button + if(time - self.prevlgfire > 0.5) + sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM); + + entity beam, oldself; + + self.lgbeam = beam = spawn(); + beam.classname = "lgbeam"; + beam.solid = SOLID_NOT; + beam.think = lgbeam_think; + beam.owner = beam.realowner = self; + beam.movetype = MOVETYPE_NONE; + beam.shot_spread = 0; + beam.bot_dodge = TRUE; + beam.bot_dodgerating = autocvar_g_balance_electro_primary_damage; + Net_LinkEntity(beam, FALSE, 0, lgbeam_send); + + oldself = self; + self = beam; + self.think(); + self = oldself; +} + +void ElectroInit() +{ + weapon_action(WEP_ELECTRO, WR_PRECACHE); + electro_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 1); + electro_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 2); + electro_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 3); + electro_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 4); +} + +void spawnfunc_weapon_electro (void) +{ + weapon_defaultspawnfunc(WEP_ELECTRO); +} + +void w_electro_checkattack() +{ + if(self.electro_count > 1) + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, -1)) + { + W_Electro_Attack2(); + self.electro_count -= 1; + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack); + return; + } + + w_ready(); +} + +.float bot_secondary_electromooth; +.float BUTTON_ATCK_prev; +float w_electro(float req) +{ + float ammo_amount; + if (req == WR_AIM) + { + self.BUTTON_ATCK=FALSE; + self.BUTTON_ATCK2=FALSE; + if(vlen(self.origin-self.enemy.origin) > 1000) + self.bot_secondary_electromooth = 0; + if(self.bot_secondary_electromooth == 0) + { + float shoot; + + if(autocvar_g_balance_electro_primary_speed) + shoot = bot_aim(autocvar_g_balance_electro_primary_speed, 0, autocvar_g_balance_electro_primary_lifetime, FALSE); + else + shoot = bot_aim(1000000, 0, 0.001, FALSE); + + if(shoot) + { + self.BUTTON_ATCK = TRUE; + if(random() < 0.01) self.bot_secondary_electromooth = 1; + } + } + else + { + if(bot_aim(autocvar_g_balance_electro_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_electro_secondary_lifetime, TRUE)) + { + self.BUTTON_ATCK2 = TRUE; + if(random() < 0.03) self.bot_secondary_electromooth = 0; + } + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_electro_reload_ammo) // forced reload + { + ammo_amount = 0; + if(autocvar_g_balance_electro_lightning) + { + if(self.clip_load > 0) + ammo_amount = 1; + } + else if(self.clip_load >= autocvar_g_balance_electro_primary_ammo) + ammo_amount = 1; + if(self.clip_load >= autocvar_g_balance_electro_secondary_ammo) + ammo_amount += 1; + + if(!ammo_amount) + { + weapon_action(self.weapon, WR_RELOAD); + return FALSE; + } + } + if (self.BUTTON_ATCK) + { + if(autocvar_g_balance_electro_lightning) + if(self.BUTTON_ATCK_prev) + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); + + if (weapon_prepareattack(0, (autocvar_g_balance_electro_lightning ? 0 : autocvar_g_balance_electro_primary_refire))) + { + if(autocvar_g_balance_electro_lightning) + { + if ((!self.lgbeam) || wasfreed(self.lgbeam)) + { + W_Electro_Attack3(); + } + if(!self.BUTTON_ATCK_prev) + { + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); + self.BUTTON_ATCK_prev = 1; + } + } + else + { + W_Electro_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); + } + } + } else { + if(autocvar_g_balance_electro_lightning) + { + if (self.BUTTON_ATCK_prev != 0) + { + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); + ATTACK_FINISHED(self) = time + autocvar_g_balance_electro_primary_refire * W_WeaponRateFactor(); + } + self.BUTTON_ATCK_prev = 0; + } + + if (self.BUTTON_ATCK2) + { + if (time >= self.electro_secondarytime) + if (weapon_prepareattack(1, autocvar_g_balance_electro_secondary_refire)) + { + W_Electro_Attack2(); + self.electro_count = autocvar_g_balance_electro_secondary_count; + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack); + self.electro_secondarytime = time + autocvar_g_balance_electro_secondary_refire2 * W_WeaponRateFactor(); + } + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_electro.md3"); + precache_model ("models/weapons/v_electro.md3"); + precache_model ("models/weapons/h_electro.iqm"); + precache_sound ("weapons/electro_bounce.wav"); + precache_sound ("weapons/electro_fire.wav"); + precache_sound ("weapons/electro_fire2.wav"); + precache_sound ("weapons/electro_impact.wav"); + precache_sound ("weapons/electro_impact_combo.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + if(autocvar_g_balance_electro_lightning) + { + precache_sound ("weapons/lgbeam_fire.wav"); + } + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_ELECTRO); + self.current_ammo = ammo_cells; + } + else if (req == WR_CHECKAMMO1) + { + if(autocvar_g_balance_electro_lightning) + { + if(!autocvar_g_balance_electro_primary_ammo) + ammo_amount = 1; + else + ammo_amount = self.ammo_cells > 0; + ammo_amount += self.(weapon_load[WEP_ELECTRO]) > 0; + } + else + { + ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_primary_ammo; + ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_primary_ammo; + } + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + if(autocvar_g_balance_electro_combo_safeammocheck) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false. + { + ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo; + ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo; + } + else + { + ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo; + } + return ammo_amount; + } + else if (req == WR_RESETPLAYER) + { + self.electro_secondarytime = time; + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_secondary_ammo), autocvar_g_balance_electro_reload_ammo, autocvar_g_balance_electro_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_ELECTRO_SUICIDE_ORBS; + else + return WEAPON_ELECTRO_SUICIDE_BOLT; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + { + return WEAPON_ELECTRO_MURDER_ORBS; + } + else + { + if(w_deathtype & HITTYPE_BOUNCE) + return WEAPON_ELECTRO_MURDER_COMBO; + else + return WEAPON_ELECTRO_MURDER_BOLT; + } + } + return TRUE; +} +#endif +#ifdef CSQC +float w_electro(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + if(w_deathtype & HITTYPE_SECONDARY) + { + pointparticles(particleeffectnum("electro_ballexplode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM); + } + else + { + if(w_deathtype & HITTYPE_BOUNCE) + { + // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls + pointparticles(particleeffectnum("electro_combo"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/electro_impact_combo.wav", VOL_BASE, ATTN_NORM); + } + else + { + pointparticles(particleeffectnum("electro_impact"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM); + } + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/electro_impact.wav"); + precache_sound("weapons/electro_impact_combo.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_electro.qh b/qcsrc/common/weapons/w_electro.qh new file mode 100644 index 000000000..98c0be13e --- /dev/null +++ b/qcsrc/common/weapons/w_electro.qh @@ -0,0 +1,2 @@ +void ElectroInit(); +vector electro_shotorigin[4]; diff --git a/qcsrc/common/weapons/w_fireball.qc b/qcsrc/common/weapons/w_fireball.qc new file mode 100644 index 000000000..3d84e8e7d --- /dev/null +++ b/qcsrc/common/weapons/w_fireball.qc @@ -0,0 +1,434 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ FIREBALL, +/* function */ w_fireball, +/* ammotype */ 0, +/* impulse */ 9, +/* flags */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "fireball", +/* shortname */ "fireball", +/* fullname */ _("Fireball") +); +#else +#ifdef SVQC +.float bot_primary_fireballmooth; // whatever a mooth is +.vector fireball_impactvec; +.float fireball_primarytime; + +void W_Fireball_Explode (void) +{ + entity e; + float dist; + float points; + vector dir; + float d; + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + // 1. dist damage + d = (self.realowner.health + self.realowner.armorvalue); + RadiusDamage (self, self.realowner, autocvar_g_balance_fireball_primary_damage, autocvar_g_balance_fireball_primary_edgedamage, autocvar_g_balance_fireball_primary_radius, world, world, autocvar_g_balance_fireball_primary_force, self.projectiledeathtype, other); + if(self.realowner.health + self.realowner.armorvalue >= d) + if(!self.cnt) + { + modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, autocvar_g_balance_fireball_primary_bfgradius, 0.2, 0.05, 0.25); + + // 2. bfg effect + // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here. + for(e = findradius(self.origin, autocvar_g_balance_fireball_primary_bfgradius); e; e = e.chain) + if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self)) + { + // can we see fireball? + traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e); + if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway + continue; + // can we see player who shot fireball? + traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e); + if(trace_ent != self.realowner) + if(/* trace_startsolid || */ trace_fraction != 1) + continue; + dist = vlen(self.origin - e.origin - e.view_ofs); + points = (1 - sqrt(dist / autocvar_g_balance_fireball_primary_bfgradius)); + if(points <= 0) + continue; + dir = normalize(e.origin + e.view_ofs - self.origin); + + if(accuracy_isgooddamage(self.realowner, e)) + accuracy_add(self.realowner, WEP_FIREBALL, 0, autocvar_g_balance_fireball_primary_bfgdamage * points); + + Damage(e, self, self.realowner, autocvar_g_balance_fireball_primary_bfgdamage * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, autocvar_g_balance_fireball_primary_bfgforce * dir); + pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1); + } + } + + remove (self); +} + +void W_Fireball_TouchExplode (void) +{ + PROJECTILE_TOUCH; + W_Fireball_Explode (); +} + +void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime) +{ + entity e; + float d; + vector p; + + if(damage <= 0) + return; + + RandomSelection_Init(); + for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain) + if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self)) + { + p = e.origin; + p_x += e.mins_x + random() * (e.maxs_x - e.mins_x); + p_y += e.mins_y + random() * (e.maxs_y - e.mins_y); + p_z += e.mins_z + random() * (e.maxs_z - e.mins_z); + d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p); + if(d < dist) + { + e.fireball_impactvec = p; + RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e)); + } + } + if(RandomSelection_chosen_ent) + { + d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec); + d = damage + (edgedamage - damage) * (d / dist); + Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE); + //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec); + pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); + } +} + +void W_Fireball_Think() +{ + if(time > self.pushltime) + { + self.cnt = 1; + self.projectiledeathtype |= HITTYPE_SPLASH; + W_Fireball_Explode(); + return; + } + + W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_primary_laserradius, autocvar_g_balance_fireball_primary_laserdamage, autocvar_g_balance_fireball_primary_laseredgedamage, autocvar_g_balance_fireball_primary_laserburntime); + + self.nextthink = time + 0.1; +} + +void W_Fireball_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if(self.health <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + if (self.health <= 0) + { + self.cnt = 1; + W_PrepareExplosionByDamage(attacker, W_Fireball_Explode); + } +} + +void W_Fireball_Attack1() +{ + entity proj; + + W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 2, "weapons/fireball_fire2.wav", CH_WEAPON_A, autocvar_g_balance_fireball_primary_damage + autocvar_g_balance_fireball_primary_bfgdamage); + + pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + proj = spawn (); + proj.classname = "plasma_prim"; + proj.owner = proj.realowner = self; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_balance_fireball_primary_damage; + proj.pushltime = time + autocvar_g_balance_fireball_primary_lifetime; + proj.use = W_Fireball_Explode; + proj.think = W_Fireball_Think; + proj.nextthink = time; + proj.health = autocvar_g_balance_fireball_primary_health; + proj.team = self.team; + proj.event_damage = W_Fireball_Damage; + proj.takedamage = DAMAGE_YES; + proj.damageforcescale = autocvar_g_balance_fireball_primary_damageforcescale; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_FIREBALL; + setorigin(proj, w_shotorg); + + proj.movetype = MOVETYPE_FLY; + W_SETUPPROJECTILEVELOCITY(proj, g_balance_fireball_primary); + proj.angles = vectoangles(proj.velocity); + proj.touch = W_Fireball_TouchExplode; + setsize(proj, '-16 -16 -16', '16 16 16'); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH | MIF_PROXY; + + CSQCProjectile(proj, TRUE, PROJECTILE_FIREBALL, TRUE); + + other = proj; MUTATOR_CALLHOOK(EditProjectile); +} + +void W_Fireball_AttackEffect(float i, vector f_diff) +{ + W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 0, "", 0, 0); + w_shotorg += f_diff_x * v_up + f_diff_y * v_right; + pointparticles(particleeffectnum("fireball_preattack_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); +} + +void W_Fireball_Attack1_Frame4() +{ + W_Fireball_Attack1(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, w_ready); +} + +void W_Fireball_Attack1_Frame3() +{ + W_Fireball_AttackEffect(0, '+1.25 +3.75 0'); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame4); +} + +void W_Fireball_Attack1_Frame2() +{ + W_Fireball_AttackEffect(0, '-1.25 +3.75 0'); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame3); +} + +void W_Fireball_Attack1_Frame1() +{ + W_Fireball_AttackEffect(1, '+1.25 -3.75 0'); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame2); +} + +void W_Fireball_Attack1_Frame0() +{ + W_Fireball_AttackEffect(0, '-1.25 -3.75 0'); + sound (self, CH_WEAPON_SINGLE, "weapons/fireball_prefire2.wav", VOL_BASE, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame1); +} + +void W_Firemine_Think() +{ + if(time > self.pushltime) + { + remove(self); + return; + } + + // make it "hot" once it leaves its owner + if(self.owner) + { + if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > autocvar_g_balance_fireball_secondary_laserradius) + { + self.cnt += 1; + if(self.cnt == 3) + self.owner = world; + } + else + self.cnt = 0; + } + + W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_secondary_laserradius, autocvar_g_balance_fireball_secondary_laserdamage, autocvar_g_balance_fireball_secondary_laseredgedamage, autocvar_g_balance_fireball_secondary_laserburntime); + + self.nextthink = time + 0.1; +} + +void W_Firemine_Touch (void) +{ + PROJECTILE_TOUCH; + if (other.takedamage == DAMAGE_AIM) + if(Fire_AddDamage(other, self.realowner, autocvar_g_balance_fireball_secondary_damage, autocvar_g_balance_fireball_secondary_damagetime, self.projectiledeathtype) >= 0) + { + remove(self); + return; + } + self.projectiledeathtype |= HITTYPE_BOUNCE; +} + +void W_Fireball_Attack2() +{ + entity proj; + vector f_diff; + float c; + + c = mod(self.bulletcounter, 4); + switch(c) + { + case 0: + f_diff = '-1.25 -3.75 0'; + break; + case 1: + f_diff = '+1.25 -3.75 0'; + break; + case 2: + f_diff = '-1.25 +3.75 0'; + break; + case 3: + default: + f_diff = '+1.25 +3.75 0'; + break; + } + W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, "weapons/fireball_fire.wav", CH_WEAPON_A, autocvar_g_balance_fireball_secondary_damage); + traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self); + w_shotorg = trace_endpos; + + pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + proj = spawn (); + proj.owner = proj.realowner = self; + proj.classname = "grenade"; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_balance_fireball_secondary_damage; + proj.movetype = MOVETYPE_BOUNCE; + proj.projectiledeathtype = WEP_FIREBALL | HITTYPE_SECONDARY; + proj.touch = W_Firemine_Touch; + PROJECTILE_MAKETRIGGER(proj); + setsize(proj, '-4 -4 -4', '4 4 4'); + setorigin(proj, w_shotorg); + proj.think = W_Firemine_Think; + proj.nextthink = time; + proj.damageforcescale = autocvar_g_balance_fireball_secondary_damageforcescale; + proj.pushltime = time + autocvar_g_balance_fireball_secondary_lifetime; + W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_fireball_secondary); + + proj.angles = vectoangles(proj.velocity); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; + + CSQCProjectile(proj, TRUE, PROJECTILE_FIREMINE, TRUE); + + other = proj; MUTATOR_CALLHOOK(EditProjectile); +} + +void spawnfunc_weapon_fireball (void) +{ + weapon_defaultspawnfunc(WEP_FIREBALL); +} + +float w_fireball(float req) +{ + //float ammo_amount; + if (req == WR_AIM) + { + self.BUTTON_ATCK = FALSE; + self.BUTTON_ATCK2 = FALSE; + if (self.bot_primary_fireballmooth == 0) + { + if(bot_aim(autocvar_g_balance_fireball_primary_speed, 0, autocvar_g_balance_fireball_primary_lifetime, FALSE)) + { + self.BUTTON_ATCK = TRUE; + if(random() < 0.02) self.bot_primary_fireballmooth = 0; + } + } + else + { + if(bot_aim(autocvar_g_balance_fireball_secondary_speed, autocvar_g_balance_fireball_secondary_speed_up, autocvar_g_balance_fireball_secondary_lifetime, TRUE)) + { + self.BUTTON_ATCK2 = TRUE; + if(random() < 0.01) self.bot_primary_fireballmooth = 1; + } + } + } + else if (req == WR_THINK) + { + if (self.BUTTON_ATCK) + { + if (time >= self.fireball_primarytime) + if (weapon_prepareattack(0, autocvar_g_balance_fireball_primary_refire)) + { + W_Fireball_Attack1_Frame0(); + self.fireball_primarytime = time + autocvar_g_balance_fireball_primary_refire2 * W_WeaponRateFactor(); + } + } + else if (self.BUTTON_ATCK2) + { + if (weapon_prepareattack(1, autocvar_g_balance_fireball_secondary_refire)) + { + W_Fireball_Attack2(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_fireball_secondary_animtime, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_fireball.md3"); + precache_model ("models/weapons/v_fireball.md3"); + precache_model ("models/weapons/h_fireball.iqm"); + precache_model ("models/sphere/sphere.md3"); + precache_sound ("weapons/fireball_fire.wav"); + precache_sound ("weapons/fireball_fire2.wav"); + precache_sound ("weapons/fireball_prefire2.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_FIREBALL); + self.current_ammo = ammo_none; + } + else if (req == WR_CHECKAMMO1) + { + return 1; + } + else if (req == WR_CHECKAMMO2) + { + return 1; + } + else if (req == WR_RESETPLAYER) + { + self.fireball_primarytime = time; + } + else if (req == WR_SUICIDEMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_FIREBALL_SUICIDE_FIREMINE; + else + return WEAPON_FIREBALL_SUICIDE_BLAST; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + { + return WEAPON_FIREBALL_MURDER_FIREMINE; + } + else + { + return WEAPON_FIREBALL_MURDER_BLAST; + } + } + return TRUE; +} +#endif +#ifdef CSQC +float w_fireball(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + if(w_deathtype & HITTYPE_SECONDARY) + { + // firemine goes out silently + } + else + { + org2 = w_org + w_backoff * 16; + pointparticles(particleeffectnum("fireball_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/fireball_impact2.wav", VOL_BASE, ATTN_NORM * 0.25); // long range boom + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/fireball_impact2.wav"); + } + + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_grenadelauncher.qc b/qcsrc/common/weapons/w_grenadelauncher.qc new file mode 100644 index 000000000..8b8a1a062 --- /dev/null +++ b/qcsrc/common/weapons/w_grenadelauncher.qc @@ -0,0 +1,414 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ GRENADE_LAUNCHER, +/* function */ w_glauncher, +/* ammotype */ IT_ROCKETS, +/* impulse */ 4, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "gl", +/* shortname */ "grenadelauncher", +/* fullname */ _("Mortar") +); +#else +#ifdef SVQC +.float gl_detonate_later; +.float gl_bouncecnt; + +void W_Grenade_Explode (void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + + RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_primary_damage, autocvar_g_balance_grenadelauncher_primary_edgedamage, autocvar_g_balance_grenadelauncher_primary_radius, world, world, autocvar_g_balance_grenadelauncher_primary_force, self.projectiledeathtype, other); + + remove (self); +} + +void W_Grenade_Explode2 (void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + + RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_secondary_damage, autocvar_g_balance_grenadelauncher_secondary_edgedamage, autocvar_g_balance_grenadelauncher_secondary_radius, world, world, autocvar_g_balance_grenadelauncher_secondary_force, self.projectiledeathtype, other); + + remove (self); +} + +void W_Grenade_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, self.use); +} + +void W_Grenade_Think1 (void) +{ + self.nextthink = time; + if (time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Grenade_Explode (); + return; + } + if(self.gl_detonate_later && self.gl_bouncecnt >= autocvar_g_balance_grenadelauncher_primary_remote_minbouncecnt) + W_Grenade_Explode(); +} + +void W_Grenade_Touch1 (void) +{ + PROJECTILE_TOUCH; + if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_primary_type == 0) // always explode when hitting a player, or if normal mortar projectile + { + self.use (); + } + else if (autocvar_g_balance_grenadelauncher_primary_type == 1) // bounce + { + float r; + r = random() * 6; + if(r < 1) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); + else if(r < 2) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); + else if(r < 3) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); + else if(r < 4) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); + else if(r < 5) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); + else + spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); + self.projectiledeathtype |= HITTYPE_BOUNCE; + self.gl_bouncecnt += 1; + } + else if(autocvar_g_balance_grenadelauncher_primary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick + { + spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); + + // let it stick whereever it is + self.oldvelocity = self.velocity; + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; // also disables gravity + self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO + UpdateCSQCProjectile(self); + + // do not respond to any more touches + self.solid = SOLID_NOT; + + self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_primary_lifetime_stick); + } +} + +void W_Grenade_Touch2 (void) +{ + PROJECTILE_TOUCH; + if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_secondary_type == 0) // always explode when hitting a player, or if normal mortar projectile + { + self.use (); + } + else if (autocvar_g_balance_grenadelauncher_secondary_type == 1) // bounce + { + float r; + r = random() * 6; + if(r < 1) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); + else if(r < 2) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); + else if(r < 3) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); + else if(r < 4) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); + else if(r < 5) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); + else + spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); + self.projectiledeathtype |= HITTYPE_BOUNCE; + self.gl_bouncecnt += 1; + + if (autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce && self.gl_bouncecnt == 1) + self.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce; + + } + else if(autocvar_g_balance_grenadelauncher_secondary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick + { + spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); + + // let it stick whereever it is + self.oldvelocity = self.velocity; + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; // also disables gravity + self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO + UpdateCSQCProjectile(self); + + // do not respond to any more touches + self.solid = SOLID_NOT; + + self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_secondary_lifetime_stick); + } +} + +void W_Grenade_Attack (void) +{ + entity gren; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_primary_damage); + w_shotdir = v_forward; // no TrueAim for grenades please + + pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_primary_damage; + gren.movetype = MOVETYPE_BOUNCE; + gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; + gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_GRENADE_LAUNCHER; + setorigin(gren, w_shotorg); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.cnt = time + autocvar_g_balance_grenadelauncher_primary_lifetime; + gren.nextthink = time; + gren.think = W_Grenade_Think1; + gren.use = W_Grenade_Explode; + gren.touch = W_Grenade_Touch1; + + gren.takedamage = DAMAGE_YES; + gren.health = autocvar_g_balance_grenadelauncher_primary_health; + gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale; + gren.event_damage = W_Grenade_Damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + if(autocvar_g_balance_grenadelauncher_primary_type == 0 || autocvar_g_balance_grenadelauncher_primary_type == 2) + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); + else + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +void W_Grenade_Attack2 (void) +{ + entity gren; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_secondary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_secondary_damage); + w_shotdir = v_forward; // no TrueAim for grenades please + + pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_secondary_damage; + gren.movetype = MOVETYPE_BOUNCE; + gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; + gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY; + setorigin(gren, w_shotorg); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime; + gren.think = adaptor_think2use_hittype_splash; + gren.use = W_Grenade_Explode2; + gren.touch = W_Grenade_Touch2; + + gren.takedamage = DAMAGE_YES; + gren.health = autocvar_g_balance_grenadelauncher_secondary_health; + gren.damageforcescale = autocvar_g_balance_grenadelauncher_secondary_damageforcescale; + gren.event_damage = W_Grenade_Damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_secondary); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + if(autocvar_g_balance_grenadelauncher_secondary_type == 0 || autocvar_g_balance_grenadelauncher_secondary_type == 2) + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); + else + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +void spawnfunc_weapon_grenadelauncher (void) +{ + weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER); +} + +.float bot_secondary_grenademooth; +float w_glauncher(float req) +{ + entity nade; + float nadefound; + float ammo_amount; + + if (req == WR_AIM) + { + self.BUTTON_ATCK = FALSE; + self.BUTTON_ATCK2 = FALSE; + if (self.bot_secondary_grenademooth == 0) + { + if(bot_aim(autocvar_g_balance_grenadelauncher_primary_speed, autocvar_g_balance_grenadelauncher_primary_speed_up, autocvar_g_balance_grenadelauncher_primary_lifetime, TRUE)) + { + self.BUTTON_ATCK = TRUE; + if(random() < 0.01) self.bot_secondary_grenademooth = 1; + } + } + else + { + if(bot_aim(autocvar_g_balance_grenadelauncher_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_grenadelauncher_secondary_lifetime, TRUE)) + { + self.BUTTON_ATCK2 = TRUE; + if(random() < 0.02) self.bot_secondary_grenademooth = 0; + } + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_grenadelauncher_reload_ammo && self.clip_load < min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if (self.BUTTON_ATCK) + { + if (weapon_prepareattack(0, autocvar_g_balance_grenadelauncher_primary_refire)) + { + W_Grenade_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_grenadelauncher_primary_animtime, w_ready); + } + } + else if (self.BUTTON_ATCK2) + { + if (cvar("g_balance_grenadelauncher_secondary_remote_detonateprimary")) + { + nadefound = 0; + for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self) + { + if(!nade.gl_detonate_later) + { + nade.gl_detonate_later = TRUE; + nadefound = 1; + } + } + if(nadefound) + sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); + } + else if (weapon_prepareattack(1, autocvar_g_balance_grenadelauncher_secondary_refire)) + { + W_Grenade_Attack2(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_grenadelauncher_secondary_animtime, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_gl.md3"); + precache_model ("models/weapons/v_gl.md3"); + precache_model ("models/weapons/h_gl.iqm"); + precache_sound ("weapons/grenade_bounce1.wav"); + precache_sound ("weapons/grenade_bounce2.wav"); + precache_sound ("weapons/grenade_bounce3.wav"); + precache_sound ("weapons/grenade_bounce4.wav"); + precache_sound ("weapons/grenade_bounce5.wav"); + precache_sound ("weapons/grenade_bounce6.wav"); + precache_sound ("weapons/grenade_stick.wav"); + precache_sound ("weapons/grenade_fire.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_GRENADE_LAUNCHER); + self.current_ammo = ammo_rockets; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_primary_ammo; + ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_secondary_ammo; + return ammo_amount; + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo), autocvar_g_balance_grenadelauncher_reload_ammo, autocvar_g_balance_grenadelauncher_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_MORTAR_SUICIDE_BOUNCE; + else + return WEAPON_MORTAR_SUICIDE_EXPLODE; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_MORTAR_MURDER_BOUNCE; + else + return WEAPON_MORTAR_MURDER_EXPLODE; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_glauncher(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/grenade_impact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_hagar.qc b/qcsrc/common/weapons/w_hagar.qc new file mode 100644 index 000000000..01a716949 --- /dev/null +++ b/qcsrc/common/weapons/w_hagar.qc @@ -0,0 +1,489 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ HAGAR, +/* function */ w_hagar, +/* ammotype */ IT_ROCKETS, +/* impulse */ 8, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "hagar", +/* shortname */ "hagar", +/* fullname */ _("Hagar") +); +#else +#ifdef SVQC +// NO bounce protection, as bounces are limited! + +void W_Hagar_Explode (void) +{ + self.event_damage = func_null; + RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_primary_damage, autocvar_g_balance_hagar_primary_edgedamage, autocvar_g_balance_hagar_primary_radius, world, world, autocvar_g_balance_hagar_primary_force, self.projectiledeathtype, other); + + remove (self); +} + +void W_Hagar_Explode2 (void) +{ + self.event_damage = func_null; + RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_secondary_damage, autocvar_g_balance_hagar_secondary_edgedamage, autocvar_g_balance_hagar_secondary_radius, world, world, autocvar_g_balance_hagar_secondary_force, self.projectiledeathtype, other); + + remove (self); +} + +void W_Hagar_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + + float is_linkexplode = ( ((inflictor.owner != world) ? (inflictor.owner == self.owner) : TRUE) + && (inflictor.projectiledeathtype & HITTYPE_SECONDARY) + && (self.projectiledeathtype & HITTYPE_SECONDARY)); + + if(is_linkexplode) + is_linkexplode = (is_linkexplode && autocvar_g_balance_hagar_secondary_load_linkexplode); + else + is_linkexplode = -1; // not secondary load, so continue as normal without exception. + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, is_linkexplode)) + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + self.angles = vectoangles(self.velocity); + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, self.think); +} + +void W_Hagar_Touch (void) +{ + PROJECTILE_TOUCH; + self.use (); +} + +void W_Hagar_Touch2 (void) +{ + PROJECTILE_TOUCH; + + if(self.cnt > 0 || other.takedamage == DAMAGE_AIM) { + self.use(); + } else { + self.cnt++; + pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1); + self.angles = vectoangles (self.velocity); + self.owner = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + } +} + +void W_Hagar_Attack (void) +{ + entity missile; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_reload_ammo); + + W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_primary_damage); + + pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.classname = "missile"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_hagar_primary_damage; + + missile.takedamage = DAMAGE_YES; + missile.health = autocvar_g_balance_hagar_primary_health; + missile.damageforcescale = autocvar_g_balance_hagar_primary_damageforcescale; + missile.event_damage = W_Hagar_Damage; + missile.damagedbycontents = TRUE; + + missile.touch = W_Hagar_Touch; + missile.use = W_Hagar_Explode; + missile.think = adaptor_think2use_hittype_splash; + missile.nextthink = time + autocvar_g_balance_hagar_primary_lifetime; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_HAGAR; + setorigin (missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + missile.movetype = MOVETYPE_FLY; + W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_primary); + + missile.angles = vectoangles (missile.velocity); + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +void W_Hagar_Attack2 (void) +{ + entity missile; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo); + + W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage); + + pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.classname = "missile"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage; + + missile.takedamage = DAMAGE_YES; + missile.health = autocvar_g_balance_hagar_secondary_health; + missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale; + missile.event_damage = W_Hagar_Damage; + missile.damagedbycontents = TRUE; + + missile.touch = W_Hagar_Touch2; + missile.cnt = 0; + missile.use = W_Hagar_Explode2; + missile.think = adaptor_think2use_hittype_splash; + missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY; + setorigin (missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + missile.movetype = MOVETYPE_BOUNCEMISSILE; + W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_secondary); + + missile.angles = vectoangles (missile.velocity); + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR_BOUNCING, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +.float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning; +void W_Hagar_Attack2_Load_Release (void) +{ + // time to release the rockets we've loaded + + entity missile; + float counter, shots, spread_pershot; + vector s; + vector forward, right, up; + + if(!self.hagar_load) + return; + + weapon_prepareattack_do(1, autocvar_g_balance_hagar_secondary_refire); + + W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage); + pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + forward = v_forward; + right = v_right; + up = v_up; + + shots = self.hagar_load; + missile = world; + for(counter = 0; counter < shots; ++counter) + { + missile = spawn (); + missile.owner = missile.realowner = self; + missile.classname = "missile"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage; + + missile.takedamage = DAMAGE_YES; + missile.health = autocvar_g_balance_hagar_secondary_health; + missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale; + missile.event_damage = W_Hagar_Damage; + missile.damagedbycontents = TRUE; + + missile.touch = W_Hagar_Touch; // not bouncy + missile.use = W_Hagar_Explode2; + missile.think = adaptor_think2use_hittype_splash; + missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY; + setorigin (missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + missile.movetype = MOVETYPE_FLY; + missile.missile_flags = MIF_SPLASH; + + // per-shot spread calculation: the more shots there are, the less spread is applied (based on the bias cvar) + spread_pershot = ((shots - 1) / (autocvar_g_balance_hagar_secondary_load_max - 1)); + spread_pershot = (1 - (spread_pershot * autocvar_g_balance_hagar_secondary_load_spread_bias)); + spread_pershot = (autocvar_g_balance_hagar_secondary_spread * spread_pershot * g_weaponspreadfactor); + + // pattern spread calculation + s = '0 0 0'; + if (counter == 0) + s = '0 0 0'; + else + { + makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1))); + s_y = v_forward_x; + s_z = v_forward_y; + } + s = s * autocvar_g_balance_hagar_secondary_load_spread * g_weaponspreadfactor; + + W_SetupProjectileVelocityEx(missile, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_hagar_secondary_speed, 0, 0, spread_pershot, FALSE); + + missile.angles = vectoangles (missile.velocity); + missile.flags = FL_PROJECTILE; + + CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); + } + + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_load_animtime, w_ready); + self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_refire * W_WeaponRateFactor(); + self.hagar_load = 0; +} + +void W_Hagar_Attack2_Load (void) +{ + // loadable hagar secondary attack, must always run each frame + + if(time < game_starttime) + return; + + float loaded, enough_ammo; + loaded = self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max; + + // this is different than WR_CHECKAMMO when it comes to reloading + if(autocvar_g_balance_hagar_reload_ammo) + enough_ammo = self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo; + else + enough_ammo = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo; + + if(self.BUTTON_ATCK2) + { + if(self.BUTTON_ATCK && autocvar_g_balance_hagar_secondary_load_abort) + { + if(self.hagar_load) + { + // if we pressed primary fire while loading, unload all rockets and abort + self.weaponentity.state = WS_READY; + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo + self.hagar_load = 0; + sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); + + // pause until we can load rockets again, once we re-press the alt fire button + self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor(); + + // require letting go of the alt fire button before we can load again + self.hagar_loadblock = TRUE; + } + } + else + { + // check if we can attempt to load another rocket + if(!loaded && enough_ammo) + { + if(!self.hagar_loadblock && self.hagar_loadstep < time) + { + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo); + self.weaponentity.state = WS_INUSE; + self.hagar_load += 1; + sound(self, CH_WEAPON_B, "weapons/hagar_load.wav", VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most + + if (self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max) + self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_hold * W_WeaponRateFactor(); + else + self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor(); + } + } + else if(!self.hagar_loadbeep && self.hagar_load) // prevents the beep from playing each frame + { + // if this is the last rocket we can load, play a beep sound to notify the player + sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); + self.hagar_loadbeep = TRUE; + } + } + } + else if(self.hagar_loadblock) + { + // the alt fire button has been released, so re-enable loading if blocked + self.hagar_loadblock = FALSE; + } + + if(self.hagar_load) + { + // play warning sound if we're about to release + if((loaded || !enough_ammo) && self.hagar_loadstep - 0.5 < time && autocvar_g_balance_hagar_secondary_load_hold >= 0) + { + if(!self.hagar_warning && self.hagar_load) // prevents the beep from playing each frame + { + // we're about to automatically release after holding time, play a beep sound to notify the player + sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); + self.hagar_warning = TRUE; + } + } + + // release if player let go of button or if they've held it in too long + if(!self.BUTTON_ATCK2 || ((loaded || !enough_ammo) && self.hagar_loadstep < time && autocvar_g_balance_hagar_secondary_load_hold >= 0)) + { + self.weaponentity.state = WS_READY; + W_Hagar_Attack2_Load_Release(); + } + } + else + { + self.hagar_loadbeep = FALSE; + self.hagar_warning = FALSE; + } + + // we aren't checking ammo during an attack, so we must do it here + if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2)) + { + // note: this doesn't force the switch + W_SwitchToOtherWeapon(self); + return; + } +} + +void spawnfunc_weapon_hagar (void) +{ + weapon_defaultspawnfunc(WEP_HAGAR); +} + +float w_hagar(float req) +{ + float ammo_amount; + if (req == WR_AIM) + if (random()>0.15) + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE); + else + { + // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming + self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE); + } + else if (req == WR_THINK) + { + float loadable_secondary; + loadable_secondary = (autocvar_g_balance_hagar_secondary_load && autocvar_g_balance_hagar_secondary); + + if (loadable_secondary) + W_Hagar_Attack2_Load(); // must always run each frame + if(autocvar_g_balance_hagar_reload_ammo && self.clip_load < min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if (self.BUTTON_ATCK && !self.hagar_load && !self.hagar_loadblock) // not while secondary is loaded or awaiting reset + { + if (weapon_prepareattack(0, autocvar_g_balance_hagar_primary_refire)) + { + W_Hagar_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hagar_primary_refire, w_ready); + } + } + else if (self.BUTTON_ATCK2 && !loadable_secondary && autocvar_g_balance_hagar_secondary) + { + if (weapon_prepareattack(1, autocvar_g_balance_hagar_secondary_refire)) + { + W_Hagar_Attack2(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_refire, w_ready); + } + } + } + else if (req == WR_GONETHINK) + { + // we lost the weapon and want to prepare switching away + if(self.hagar_load) + { + self.weaponentity.state = WS_READY; + W_Hagar_Attack2_Load_Release(); + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_hagar.md3"); + precache_model ("models/weapons/v_hagar.md3"); + precache_model ("models/weapons/h_hagar.iqm"); + precache_sound ("weapons/hagar_fire.wav"); + precache_sound ("weapons/hagar_load.wav"); + precache_sound ("weapons/hagar_beep.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_HAGAR); + self.current_ammo = ammo_rockets; + self.hagar_loadblock = FALSE; + + if(self.hagar_load) + { + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo if necessary + self.hagar_load = 0; + } + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_primary_ammo; + ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo; + return ammo_amount; + } + else if (req == WR_RESETPLAYER) + { + self.hagar_load = 0; + } + else if (req == WR_PLAYERDEATH) + { + // if we have any rockets loaded when we die, release them + if(self.hagar_load && autocvar_g_balance_hagar_secondary_load_releasedeath) + W_Hagar_Attack2_Load_Release(); + } + else if (req == WR_RELOAD) + { + if not(self.hagar_load) // require releasing loaded rockets first + W_Reload(min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo), autocvar_g_balance_hagar_reload_ammo, autocvar_g_balance_hagar_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_HAGAR_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_HAGAR_MURDER_BURST; + else + return WEAPON_HAGAR_MURDER_SPRAY; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_hagar(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1); + if(!w_issilent) + { + if (w_random<0.15) + sound(self, CH_SHOTS, "weapons/hagexp1.wav", VOL_BASE, ATTN_NORM); + else if (w_random<0.7) + sound(self, CH_SHOTS, "weapons/hagexp2.wav", VOL_BASE, ATTN_NORM); + else + sound(self, CH_SHOTS, "weapons/hagexp3.wav", VOL_BASE, ATTN_NORM); + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/hagexp1.wav"); + precache_sound("weapons/hagexp2.wav"); + precache_sound("weapons/hagexp3.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_hlac.qc b/qcsrc/common/weapons/w_hlac.qc new file mode 100644 index 000000000..cc7f05355 --- /dev/null +++ b/qcsrc/common/weapons/w_hlac.qc @@ -0,0 +1,261 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ HLAC, +/* function */ w_hlac, +/* ammotype */ IT_CELLS, +/* impulse */ 6, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "hlac", +/* shortname */ "hlac", +/* fullname */ _("Heavy Laser Assault Cannon") +); +#else +#ifdef SVQC + +void W_HLAC_Touch (void) +{ + PROJECTILE_TOUCH; + + self.event_damage = func_null; + + if(self.projectiledeathtype & HITTYPE_SECONDARY) + RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_secondary_damage, autocvar_g_balance_hlac_secondary_edgedamage, autocvar_g_balance_hlac_secondary_radius, world, world, autocvar_g_balance_hlac_secondary_force, self.projectiledeathtype, other); + else + RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_primary_damage, autocvar_g_balance_hlac_primary_edgedamage, autocvar_g_balance_hlac_primary_radius, world, world, autocvar_g_balance_hlac_primary_force, self.projectiledeathtype, other); + + remove (self); +} + +void W_HLAC_Attack (void) +{ + entity missile; + float spread; + + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_reload_ammo); + + spread = autocvar_g_balance_hlac_primary_spread_min + (autocvar_g_balance_hlac_primary_spread_add * self.misc_bulletcounter); + spread = min(spread,autocvar_g_balance_hlac_primary_spread_max); + if(self.crouch) + spread = spread * autocvar_g_balance_hlac_primary_spread_crouchmod; + + W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_primary_damage); + pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.classname = "hlacbolt"; + missile.bot_dodge = TRUE; + + missile.bot_dodgerating = autocvar_g_balance_hlac_primary_damage; + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + + setorigin (missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_primary_speed, spread); + //missile.angles = vectoangles (missile.velocity); // csqc + + missile.touch = W_HLAC_Touch; + missile.think = SUB_Remove; + + missile.nextthink = time + autocvar_g_balance_hlac_primary_lifetime; + + missile.flags = FL_PROJECTILE; + missile.projectiledeathtype = WEP_HLAC; + + CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +void W_HLAC_Attack2f (void) +{ + entity missile; + float spread; + + spread = autocvar_g_balance_hlac_secondary_spread; + + + if(self.crouch) + spread = spread * autocvar_g_balance_hlac_secondary_spread_crouchmod; + + W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_secondary_damage); + pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.classname = "hlacbolt"; + missile.bot_dodge = TRUE; + + missile.bot_dodgerating = autocvar_g_balance_hlac_secondary_damage; + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + + setorigin (missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_secondary_speed, spread); + //missile.angles = vectoangles (missile.velocity); // csqc + + missile.touch = W_HLAC_Touch; + missile.think = SUB_Remove; + + missile.nextthink = time + autocvar_g_balance_hlac_secondary_lifetime; + + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + missile.projectiledeathtype = WEP_HLAC | HITTYPE_SECONDARY; + + CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +void W_HLAC_Attack2 (void) +{ + float i; + + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_secondary_ammo, autocvar_g_balance_hlac_reload_ammo); + + for(i=autocvar_g_balance_hlac_secondary_shots;i>0;--i) + W_HLAC_Attack2f(); + + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } +} + +// weapon frames +void HLAC_fire1_02() +{ + if(self.weapon != self.switchweapon) // abort immediately if switching + { + w_ready(); + return; + } + + if (self.BUTTON_ATCK) + { + if (!weapon_action(self.weapon, WR_CHECKAMMO1)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + ATTACK_FINISHED(self) = time + autocvar_g_balance_hlac_primary_refire * W_WeaponRateFactor(); + W_HLAC_Attack(); + self.misc_bulletcounter = self.misc_bulletcounter + 1; + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02); + } + else + { + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_animtime, w_ready); + } +} + +void spawnfunc_weapon_hlac (void) +{ + weapon_defaultspawnfunc(WEP_HLAC); +} + +float w_hlac(float req) +{ + float ammo_amount; + if (req == WR_AIM) + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hlac_primary_speed, 0, autocvar_g_balance_hlac_primary_lifetime, FALSE); + else if (req == WR_THINK) + { + if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if (self.BUTTON_ATCK) + { + if (weapon_prepareattack(0, autocvar_g_balance_hlac_primary_refire)) + { + self.misc_bulletcounter = 0; + W_HLAC_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02); + } + } + + else if (self.BUTTON_ATCK2 && autocvar_g_balance_hlac_secondary) + { + if (weapon_prepareattack(1, autocvar_g_balance_hlac_secondary_refire)) + { + W_HLAC_Attack2(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hlac_secondary_animtime, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_hlac.md3"); + precache_model ("models/weapons/v_hlac.md3"); + precache_model ("models/weapons/h_hlac.iqm"); + precache_sound ("weapons/lasergun_fire.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_HLAC); + self.current_ammo = ammo_cells; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_primary_ammo; + ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_secondary_ammo; + return ammo_amount; + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo), autocvar_g_balance_hlac_reload_ammo, autocvar_g_balance_hlac_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_HLAC_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + return WEAPON_HLAC_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_hlac(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum("laser_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/laserimpact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_hook.qc b/qcsrc/common/weapons/w_hook.qc new file mode 100644 index 000000000..7f03744f9 --- /dev/null +++ b/qcsrc/common/weapons/w_hook.qc @@ -0,0 +1,307 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ HOOK, +/* function */ w_hook, +/* ammotype */ IT_CELLS|IT_FUEL, +/* impulse */ 0, +/* flags */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ 0, +/* model */ "hookgun", +/* shortname */ "hook", +/* fullname */ _("Grappling Hook") +); +#else +#ifdef SVQC +.float dmg; +.float dmg_edge; +.float dmg_radius; +.float dmg_force; +.float dmg_power; +.float dmg_duration; +.float dmg_last; +.float hook_refire; +.float hook_time_hooked; +.float hook_time_fueldecrease; + +void W_Hook_ExplodeThink (void) +{ + float dt, dmg_remaining_next, f; + + dt = time - self.teleport_time; + dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power); + + f = self.dmg_last - dmg_remaining_next; + self.dmg_last = dmg_remaining_next; + + RadiusDamage (self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world); + self.projectiledeathtype |= HITTYPE_BOUNCE; + //RadiusDamage (self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world); + + if(dt < self.dmg_duration) + self.nextthink = time + 0.05; // soon + else + remove(self); +} + +void W_Hook_Explode2 (void) +{ + self.event_damage = func_null; + self.touch = func_null; + self.effects |= EF_NODRAW; + + self.think = W_Hook_ExplodeThink; + self.nextthink = time; + self.dmg = autocvar_g_balance_hook_secondary_damage; + self.dmg_edge = autocvar_g_balance_hook_secondary_edgedamage; + self.dmg_radius = autocvar_g_balance_hook_secondary_radius; + self.dmg_force = autocvar_g_balance_hook_secondary_force; + self.dmg_power = autocvar_g_balance_hook_secondary_power; + self.dmg_duration = autocvar_g_balance_hook_secondary_duration; + self.teleport_time = time; + self.dmg_last = 1; + self.movetype = MOVETYPE_NONE; +} + +void W_Hook_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + + if (self.health <= 0) + W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2); +} + +void W_Hook_Touch2 (void) +{ + PROJECTILE_TOUCH; + self.use(); +} + +void W_Hook_Attack2() +{ + entity gren; + + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hook_secondary_ammo, FALSE); + W_SetupShot (self, FALSE, 4, "weapons/hookbomb_fire.wav", CH_WEAPON_A, autocvar_g_balance_hook_secondary_damage); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "hookbomb"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_balance_hook_secondary_damage; + gren.movetype = MOVETYPE_TOSS; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_HOOK | HITTYPE_SECONDARY; + setorigin(gren, w_shotorg); + setsize(gren, '0 0 0', '0 0 0'); + + gren.nextthink = time + autocvar_g_balance_hook_secondary_lifetime; + gren.think = adaptor_think2use_hittype_splash; + gren.use = W_Hook_Explode2; + gren.touch = W_Hook_Touch2; + + gren.takedamage = DAMAGE_YES; + gren.health = autocvar_g_balance_hook_secondary_health; + gren.damageforcescale = autocvar_g_balance_hook_secondary_damageforcescale; + gren.event_damage = W_Hook_Damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + + gren.velocity = '0 0 1' * autocvar_g_balance_hook_secondary_speed; + if(autocvar_g_projectiles_newton_style) + gren.velocity = gren.velocity + self.velocity; + + gren.gravity = autocvar_g_balance_hook_secondary_gravity; + //W_SetupProjectileVelocity(gren); // just falling down! + + gren.angles = '0 0 0'; + gren.flags = FL_PROJECTILE; + + CSQCProjectile(gren, TRUE, PROJECTILE_HOOKBOMB, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +void spawnfunc_weapon_hook (void) +{ + if(g_grappling_hook) // offhand hook + { + startitem_failed = TRUE; + remove(self); + return; + } + weapon_defaultspawnfunc(WEP_HOOK); +} + +float w_hook(float req) +{ + float hooked_time_max, hooked_fuel; + + if (req == WR_AIM) + { + // ... sorry ... + } + else if (req == WR_THINK) + { + if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) + { + if(!self.hook) + if not(self.hook_state & HOOK_WAITING_FOR_RELEASE) + if not(self.hook_state & HOOK_FIRING) + if (time > self.hook_refire) + if (weapon_prepareattack(0, -1)) + { + W_DecreaseAmmo(ammo_fuel, autocvar_g_balance_hook_primary_fuel, FALSE); + self.hook_state |= HOOK_FIRING; + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hook_primary_animtime, w_ready); + } + } + + if (self.BUTTON_ATCK2) + { + if (weapon_prepareattack(1, autocvar_g_balance_hook_secondary_refire)) + { + W_Hook_Attack2(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hook_secondary_animtime, w_ready); + } + } + + if(self.hook) + { + // if hooked, no bombs, and increase the timer + self.hook_refire = max(self.hook_refire, time + autocvar_g_balance_hook_primary_refire * W_WeaponRateFactor()); + + // hook also inhibits health regeneration, but only for 1 second + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen); + } + + if(self.hook && self.hook.state == 1) + { + hooked_time_max = autocvar_g_balance_hook_primary_hooked_time_max; + if (hooked_time_max > 0) + { + if ( time > self.hook_time_hooked + hooked_time_max ) + self.hook_state |= HOOK_REMOVING; + } + + hooked_fuel = autocvar_g_balance_hook_primary_hooked_fuel; + if (hooked_fuel > 0) + { + if ( time > self.hook_time_fueldecrease ) + { + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + if ( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel ) + { + W_DecreaseAmmo(ammo_fuel, (time - self.hook_time_fueldecrease) * hooked_fuel, FALSE); + self.hook_time_fueldecrease = time; + // decrease next frame again + } + else + { + self.ammo_fuel = 0; + self.hook_state |= HOOK_REMOVING; + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + } + } + } + } + } + else + { + self.hook_time_hooked = time; + self.hook_time_fueldecrease = time + autocvar_g_balance_hook_primary_hooked_time_free; + } + + if (self.BUTTON_CROUCH) + { + self.hook_state &~= HOOK_PULLING; + if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) + self.hook_state &~= HOOK_RELEASING; + else + self.hook_state |= HOOK_RELEASING; + } + else + { + self.hook_state |= HOOK_PULLING; + self.hook_state &~= HOOK_RELEASING; + + if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) + { + // already fired + if(self.hook) + self.hook_state |= HOOK_WAITING_FOR_RELEASE; + } + else + { + self.hook_state |= HOOK_REMOVING; + self.hook_state &~= HOOK_WAITING_FOR_RELEASE; + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_hookgun.md3"); + precache_model ("models/weapons/v_hookgun.md3"); + precache_model ("models/weapons/h_hookgun.iqm"); + precache_sound ("weapons/hook_impact.wav"); // done by g_hook.qc + precache_sound ("weapons/hook_fire.wav"); + precache_sound ("weapons/hookbomb_fire.wav"); + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_HOOK); + self.current_ammo = ammo_fuel; + self.hook_state &~= HOOK_WAITING_FOR_RELEASE; + } + else if (req == WR_CHECKAMMO1) + { + if(self.hook) + return self.ammo_fuel > 0; + else + return self.ammo_fuel >= autocvar_g_balance_hook_primary_fuel; + } + else if (req == WR_CHECKAMMO2) + { + return self.ammo_cells >= autocvar_g_balance_hook_secondary_ammo; + } + else if (req == WR_RESETPLAYER) + { + self.hook_refire = time; + } + else if (req == WR_SUICIDEMESSAGE) + { + return FALSE; + } + else if (req == WR_KILLMESSAGE) + { + return WEAPON_HOOK_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_hook(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("hookbomb_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/hookbomb_impact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/hookbomb_impact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_laser.qc b/qcsrc/common/weapons/w_laser.qc new file mode 100644 index 000000000..c331c5b52 --- /dev/null +++ b/qcsrc/common/weapons/w_laser.qc @@ -0,0 +1,576 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ LASER, +/* function */ W_Laser, +/* ammotype */ 0, +/* impulse */ 1, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ 0, +/* model */ "laser", +/* shortname */ "laser", +/* fullname */ _("Blaster") +); +#else +#ifdef SVQC +void(float imp) W_SwitchWeapon; +void() W_LastWeapon; +.float swing_prev; +.entity swing_alreadyhit; + +void SendCSQCShockwaveParticle(vector endpos) +{ + //endpos = WarpZone_UnTransformOrigin(transform, endpos); + + WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE); + WriteCoord(MSG_BROADCAST, w_shotorg_x); + WriteCoord(MSG_BROADCAST, w_shotorg_y); + WriteCoord(MSG_BROADCAST, w_shotorg_z); + WriteCoord(MSG_BROADCAST, endpos_x); + WriteCoord(MSG_BROADCAST, endpos_y); + WriteCoord(MSG_BROADCAST, endpos_z); + WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_max, 255)); + WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_min, 255)); + WriteByte(MSG_BROADCAST, num_for_edict(self)); +} + +void W_Laser_Touch() +{ + PROJECTILE_TOUCH; + + self.event_damage = func_null; + + if(self.dmg) + RadiusDamage(self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other); + else + RadiusDamage(self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other); + + remove(self); +} + +void W_Laser_Think() +{ + self.movetype = MOVETYPE_FLY; + self.think = SUB_Remove; + + if(self.dmg) + self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime; + else + self.nextthink = time + autocvar_g_balance_laser_primary_lifetime; + + CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE); +} + + +float W_Laser_Shockwave_CheckSpread(vector targetorg, vector nearest_on_line, vector sw_shotorg, vector attack_endpos) +{ + float spreadlimit; + float distance_of_attack = vlen(sw_shotorg - attack_endpos); + float distance_from_line = vlen(targetorg - nearest_on_line); + + spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1); + spreadlimit = (autocvar_g_balance_laser_shockwave_spread_min * (1 - spreadlimit) + autocvar_g_balance_laser_shockwave_spread_max * spreadlimit); + + if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90)) + return bound(0, (distance_from_line / spreadlimit), 1); + else + return FALSE; +} + +float W_Laser_Shockwave_IsVisible(entity head, vector nearest_on_line, vector sw_shotorg, vector attack_endpos) +{ + vector nearest_to_attacker = head.WarpZone_findradius_nearest; + vector center = (head.origin + (head.mins + head.maxs) * 0.5); + vector corner; + float i; + + // STEP ONE: Check if the nearest point is clear + if(W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage + } + + // STEP TWO: Check if shotorg to center point is clear + if(W_Laser_Shockwave_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage + } + + // STEP THREE: Check each corner to see if they are clear + for(i=1; i<=8; ++i) + { + corner = get_corner_position(head, i); + if(W_Laser_Shockwave_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage + } + } + + return FALSE; +} + +#define PLAYER_CENTER(ent) (ent.origin + ((ent.classname == "player") ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5))) + +entity shockwave_hit[32]; +float shockwave_hit_damage[32]; +vector shockwave_hit_force[32]; + +float W_Laser_Shockwave_CheckHit(float queue, entity head, vector final_force, float final_damage) +{ + if not(head) { return FALSE; } + float i; + + ++queue; + + for(i = 1; i <= queue; ++i) + { + if(shockwave_hit[i] == head) + { + if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; } + if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; } + return FALSE; + } + } + + shockwave_hit[queue] = head; + shockwave_hit_force[queue] = final_force; + shockwave_hit_damage[queue] = final_damage; + return TRUE; +} + +void W_Laser_Shockwave() +{ + // declarations + float multiplier, multiplier_from_accuracy, multiplier_from_distance; + float final_damage; //, final_spread; + vector final_force, center, vel; + entity head, next; + + float i, queue = 0; + + // set up the shot direction + W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_shockwave_damage); + vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_shockwave_distance)); + WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self); + vector attack_hitpos = trace_endpos; + float distance_to_end = vlen(w_shotorg - attack_endpos); + float distance_to_hit = vlen(w_shotorg - attack_hitpos); + //entity transform = WarpZone_trace_transform; + + // do the firing effect now + SendCSQCShockwaveParticle(attack_endpos); + Damage_DamageInfo(attack_hitpos, autocvar_g_balance_laser_shockwave_splash_damage, autocvar_g_balance_laser_shockwave_splash_edgedamage, autocvar_g_balance_laser_shockwave_splash_radius, w_shotdir * autocvar_g_balance_laser_shockwave_splash_force, WEP_LASER, 0, self); + + // splash damage/jumping trace + head = WarpZone_FindRadius(attack_hitpos, max(autocvar_g_balance_laser_shockwave_splash_radius, autocvar_g_balance_laser_shockwave_jump_radius), FALSE); + while(head) + { + next = head.chain; + + if(head.takedamage) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = PLAYER_CENTER(head); + + float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); + + if((head == self) && (distance_to_head <= autocvar_g_balance_laser_shockwave_jump_radius)) + { + multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_jump_radius)) : 0)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); + multiplier = max(autocvar_g_balance_laser_shockwave_jump_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_jump_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_jump_multiplier_distance))); + + final_force = ((normalize(center - attack_hitpos) * autocvar_g_balance_laser_shockwave_jump_force) * multiplier); + vel = head.velocity; vel_z = 0; + vel = normalize(vel) * bound(0, vlen(vel) / autocvar_sv_maxspeed, 1) * autocvar_g_balance_laser_shockwave_jump_force_velocitybias; + final_force = (vlen(final_force) * normalize(normalize(final_force) + vel)); + final_force_z *= autocvar_g_balance_laser_shockwave_jump_force_zscale; + final_damage = (autocvar_g_balance_laser_shockwave_jump_damage * multiplier + autocvar_g_balance_laser_shockwave_jump_edgedamage * (1 - multiplier)); + + Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); + //print("SELF HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + else if (distance_to_head <= autocvar_g_balance_laser_shockwave_splash_radius) + { + multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_splash_radius)) : 0)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); + multiplier = max(autocvar_g_balance_laser_shockwave_splash_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_splash_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_splash_multiplier_distance))); + + final_force = normalize(center - (attack_hitpos - (w_shotdir * autocvar_g_balance_laser_shockwave_splash_force_forwardbias))); + //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); + final_force = ((final_force * autocvar_g_balance_laser_shockwave_splash_force) * multiplier); + final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; + final_damage = (autocvar_g_balance_laser_shockwave_splash_damage * multiplier + autocvar_g_balance_laser_shockwave_splash_edgedamage * (1 - multiplier)); + + if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } + //print("SPLASH HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + } + head = next; + } + + // cone damage trace + head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_shockwave_distance, FALSE); + while(head) + { + next = head.chain; + + if((head != self) && head.takedamage) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = PLAYER_CENTER(head); + + // find the closest point on the enemy to the center of the attack + float ang; // angle between shotdir and h + float h; // hypotenuse, which is the distance between attacker to head + float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin + + h = vlen(center - self.origin); + ang = acos(dotproduct(normalize(center - self.origin), w_shotdir)); + a = h * cos(ang); + + vector nearest_on_line = (w_shotorg + a * w_shotdir); + vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); + float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this + + if((distance_to_target <= autocvar_g_balance_laser_shockwave_distance) + && (W_Laser_Shockwave_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) + { + multiplier_from_accuracy = (1 - W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_endpos)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_target / distance_to_end)) : 0)); + multiplier = max(autocvar_g_balance_laser_shockwave_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_multiplier_distance))); + + final_force = normalize(center - (nearest_on_line - (w_shotdir * autocvar_g_balance_laser_shockwave_force_forwardbias))); + //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); + final_force = ((final_force * autocvar_g_balance_laser_shockwave_force) * multiplier); + final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; + final_damage = (autocvar_g_balance_laser_shockwave_damage * multiplier + autocvar_g_balance_laser_shockwave_edgedamage * (1 - multiplier)); + + if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } + //print("CONE HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + } + head = next; + } + + for(i = 1; i <= queue; ++i) + { + head = shockwave_hit[i]; + final_force = shockwave_hit_force[i]; + final_damage = shockwave_hit_damage[i]; + + Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); + print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n"); + + shockwave_hit[i] = world; + shockwave_hit_force[i] = '0 0 0'; + shockwave_hit_damage[i] = 0; + } + //print("queue was ", ftos(queue), ".\n\n"); +} + +void W_Laser_Melee_Think() +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player; + entity target_victim; + vector targpos; + + if(!self.cnt) // set start time of melee + { + self.cnt = time; + W_PlayStrengthSound(self.realowner); + } + + makevectors(self.realowner.v_angle); // update values for v_* vectors + + // calculate swing percentage based on time + meleetime = autocvar_g_balance_laser_melee_time * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * autocvar_g_balance_laser_melee_traces); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_laser_melee_no_doubleslap) + { + remove(self); + return; + } + + // if okay, perform the traces needed for this frame + for(i=self.swing_prev; i < f; ++i) + { + swing_factor = ((1 - (i / autocvar_g_balance_laser_melee_traces)) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * autocvar_g_balance_laser_melee_range) + + (v_up * swing_factor * autocvar_g_balance_laser_melee_swing_up) + + (v_right * swing_factor * autocvar_g_balance_laser_melee_swing_side)); + + WarpZone_traceline_antilag(self.realowner, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, ANTILAG_LATENCY(self.realowner)); + + // draw lightning beams for debugging + te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); + te_customflash(targpos, 40, 2, '1 1 1'); + + is_player = (trace_ent.classname == "player" || trace_ent.classname == "body"); + + if((trace_fraction < 1) // if trace is good, apply the damage and remove self + && (trace_ent.takedamage == DAMAGE_AIM) + && (trace_ent != self.swing_alreadyhit) + && (is_player || autocvar_g_balance_laser_melee_nonplayerdamage)) + { + target_victim = trace_ent; // so it persists through other calls + + if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. + swing_damage = (autocvar_g_balance_laser_melee_damage * min(1, swing_factor + 1)); + else + swing_damage = (autocvar_g_balance_laser_melee_nonplayerdamage * min(1, swing_factor + 1)); + + //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n")); + + Damage(target_victim, self.realowner, self.realowner, + swing_damage, WEP_LASER | HITTYPE_SECONDARY, + self.realowner.origin + self.realowner.view_ofs, + v_forward * autocvar_g_balance_laser_melee_force); + + if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LASER, 0, swing_damage); } + + if(autocvar_g_balance_laser_melee_multihit) // allow multiple hits with one swing, but not against the same player twice. + { + self.swing_alreadyhit = target_victim; + continue; // move along to next trace + } + else + { + remove(self); + return; + } + } + } + + if(time >= self.cnt + meleetime) + { + // melee is finished + remove(self); + return; + } + else + { + // set up next frame + self.swing_prev = i; + self.nextthink = time; + } +} + +void W_Laser_Melee() +{ + sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_melee_animtime, w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.owner = meleetemp.realowner = self; + meleetemp.think = W_Laser_Melee_Think; + meleetemp.nextthink = time + autocvar_g_balance_laser_melee_delay * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_laser_melee_damage, autocvar_g_balance_laser_melee_range); +} + +void W_Laser_Attack(float issecondary) +{ + entity missile; + vector s_forward; + float a; + + a = autocvar_g_balance_laser_primary_shotangle; + s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD); + + //if(nodamage) + // W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0); + /*else*/if(issecondary == 1) + W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage); + else + W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage); + pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = spawn(); + missile.owner = missile.realowner = self; + missile.classname = "laserbolt"; + missile.dmg = 0; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage; + + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_LASER; + + setorigin(missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary); + missile.angles = vectoangles(missile.velocity); + //missile.glow_color = 250; // 244, 250 + //missile.glow_size = 120; + missile.touch = W_Laser_Touch; + + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + missile.think = W_Laser_Think; + missile.nextthink = time + autocvar_g_balance_laser_primary_delay; + + other = missile; MUTATOR_CALLHOOK(EditProjectile); + + if(time >= missile.nextthink) + { + entity oldself; + oldself = self; + self = missile; + self.think(); + self = oldself; + } +} + +void spawnfunc_weapon_laser(void) +{ + weapon_defaultspawnfunc(WEP_LASER); +} + +float W_Laser(float request) +{ + switch(request) + { + case WR_AIM: + { + if((autocvar_g_balance_laser_secondary == 2) && (vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_laser_melee_range)) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + else + self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE); + return TRUE; + } + + case WR_THINK: + { + if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire)) + { + W_DecreaseAmmo(ammo_none, 1, TRUE); + + if not(autocvar_g_balance_laser_primary) + W_Laser_Shockwave(); + else + W_Laser_Attack(FALSE); + + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready); + } + } + else if(self.BUTTON_ATCK2) + { + switch(autocvar_g_balance_laser_secondary) + { + case 0: // switch to last used weapon + { + if(self.switchweapon == WEP_LASER) // don't do this if already switching + W_LastWeapon(); + + break; + } + + case 1: // normal projectile secondary + { + if(weapon_prepareattack(1, autocvar_g_balance_laser_secondary_refire)) + { + W_DecreaseAmmo(ammo_none, 1, TRUE); + W_Laser_Attack(TRUE); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready); + } + + break; + } + + case 2: // melee attack secondary + { + if(!self.crouch) // we are not currently crouching; this fixes an exploit where your melee anim is not visible, and besides wouldn't make much sense + if(weapon_prepareattack(1, autocvar_g_balance_laser_melee_refire)) + { + // attempt forcing playback of the anim by switching to another anim (that we never play) here... + W_Laser_Melee(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_melee_animtime, w_ready); + } + } + } + } + return TRUE; + } + + case WR_PRECACHE: + { + precache_model("models/weapons/g_laser.md3"); + precache_model("models/weapons/v_laser.md3"); + precache_model("models/weapons/h_laser.iqm"); + precache_sound("weapons/lasergun_fire.wav"); + return TRUE; + } + + case WR_SETUP: + { + weapon_setup(WEP_LASER); + self.current_ammo = ammo_none; + return TRUE; + } + + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + return TRUE; // laser has infinite ammo + } + + case WR_RELOAD: + { + W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav"); + return TRUE; + } + + case WR_SUICIDEMESSAGE: + { + return WEAPON_LASER_SUICIDE; + } + + case WR_KILLMESSAGE: + { + return WEAPON_LASER_MURDER; + } + } + + return TRUE; +} +#endif +#ifdef CSQC +float W_Laser(float request) +{ + switch(request) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent) { sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); } + return TRUE; + } + + case WR_PRECACHE: + { + precache_sound("weapons/laserimpact.wav"); + return TRUE; + } + } + + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_lightning.qc b/qcsrc/common/weapons/w_lightning.qc new file mode 100644 index 000000000..e4b7230d6 --- /dev/null +++ b/qcsrc/common/weapons/w_lightning.qc @@ -0,0 +1,296 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ LIGHTNING, +/* function */ w_lightning, +/* ammotype */ IT_CELLS, +/* impulse */ 5, +/* flags */ WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "lightning", +/* shortname */ "lightning", +/* fullname */ _("Lightning") +); +#else +#ifdef SVQC + +// Declarations ========================= +.vector hook_start, hook_end; // used for beam +.entity lightning_beam; // used for beam +.float BUTTON_ATCK_prev; // for better animation control +.float lg_fire_prev; // for better animation control + +// Lightning functions ========================= +float W_Lightning_Beam_Send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_LIGHTNING_BEAM); + sf = sf & 0x7F; + if(sound_allowed(MSG_BROADCAST, self.owner)) + sf |= 0x80; + WriteByte(MSG_ENTITY, sf); + if(sf & 1) + { + WriteByte(MSG_ENTITY, num_for_edict(self.owner)); + WriteCoord(MSG_ENTITY, autocvar_g_balance_lightning_primary_range); + } + if(sf & 2) + { + WriteCoord(MSG_ENTITY, self.hook_start_x); + WriteCoord(MSG_ENTITY, self.hook_start_y); + WriteCoord(MSG_ENTITY, self.hook_start_z); + } + if(sf & 4) + { + WriteCoord(MSG_ENTITY, self.hook_end_x); + WriteCoord(MSG_ENTITY, self.hook_end_y); + WriteCoord(MSG_ENTITY, self.hook_end_z); + } + return TRUE; +} + +void W_Lightning_Beam_Think() +{ + self.owner.lg_fire_prev = time; + if (self != self.owner.lightning_beam) + { + remove(self); + return; + } + if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen) + { + if(self == self.owner.lightning_beam) + self.owner.lightning_beam = world; + remove(self); + return; + } + + self.nextthink = time; + + makevectors(self.owner.v_angle); + + float dt, f; + dt = frametime; + if not(self.owner.items & IT_UNLIMITED_WEAPON_AMMO) + { + if(autocvar_g_balance_lightning_primary_ammo) + { + dt = min(dt, self.owner.ammo_cells / autocvar_g_balance_lightning_primary_ammo); + self.owner.ammo_cells = max(0, self.owner.ammo_cells - autocvar_g_balance_lightning_primary_ammo * frametime); + } + } + + W_SetupShot_Range(self.owner, TRUE, 0, "", 0, autocvar_g_balance_lightning_primary_damage * dt, autocvar_g_balance_lightning_primary_range); + WarpZone_traceline_antilag(self.owner, w_shotorg, w_shotend, MOVE_NORMAL, self.owner, ANTILAG_LATENCY(self.owner)); + + // apply the damage + if(trace_ent) + { + vector force; + force = w_shotdir * autocvar_g_balance_lightning_primary_force; + + f = ExponentialFalloff(autocvar_g_balance_lightning_primary_falloff_mindist, autocvar_g_balance_lightning_primary_falloff_maxdist, autocvar_g_balance_lightning_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg)); + + if(accuracy_isgooddamage(self.owner, trace_ent)) + accuracy_add(self.owner, WEP_LIGHTNING, 0, autocvar_g_balance_lightning_primary_damage * dt * f); + Damage (trace_ent, self.owner, self.owner, autocvar_g_balance_lightning_primary_damage * dt * f, WEP_LIGHTNING, trace_endpos, force * dt); + } + + // draw effect + if(w_shotorg != self.hook_start) + { + self.SendFlags |= 2; + self.hook_start = w_shotorg; + } + if(w_shotend != self.hook_end) + { + self.SendFlags |= 4; + self.hook_end = w_shotend; + } +} + +// Attack functions ========================= +void W_Lightning_Attack1 (void) +{ + // only play fire sound if 0.5 sec has passed since player let go the fire button + if(time - self.lg_fire_prev > 0.5) + sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM); + + entity beam, oldself; + + self.lightning_beam = beam = spawn(); + beam.classname = "W_Lightning_Beam"; + beam.solid = SOLID_NOT; + beam.think = W_Lightning_Beam_Think; + beam.owner = self; + beam.movetype = MOVETYPE_NONE; + beam.shot_spread = 1; + beam.bot_dodge = TRUE; + beam.bot_dodgerating = autocvar_g_balance_lightning_primary_damage; + Net_LinkEntity(beam, FALSE, 0, W_Lightning_Beam_Send); + + oldself = self; + self = beam; + self.think(); + self = oldself; +} + +float w_lightning(float req) +{ + if (req == WR_AIM) + { + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + /* + self.BUTTON_ATCK=FALSE; + self.BUTTON_ATCK2=FALSE; + if(vlen(self.origin-self.enemy.origin) > 1000) + self.bot_aim_whichfiretype = 0; + if(self.bot_aim_whichfiretype == 0) + { + float shoot; + + if(autocvar_g_balance_lightning_primary_speed) + shoot = bot_aim(autocvar_g_balance_lightning_primary_speed, 0, autocvar_g_balance_lightning_primary_lifetime, FALSE); + else + shoot = bot_aim(1000000, 0, 0.001, FALSE); + + if(shoot) + { + self.BUTTON_ATCK = TRUE; + if(random() < 0.01) self.bot_aim_whichfiretype = 1; + } + } + else // todo + { + //if(bot_aim(autocvar_g_balance_lightning_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_lightning_secondary_lifetime, TRUE)) + //{ + // self.BUTTON_ATCK2 = TRUE; + // if(random() < 0.03) self.bot_aim_whichfiretype = 0; + //} + } + */ + } + else if (req == WR_THINK) + { + if (self.BUTTON_ATCK) + { + if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this! + /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y) + weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_lightning_primary_animtime, w_ready); + else*/ + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); + + if (weapon_prepareattack(0, 0)) + { + if ((!self.lightning_beam) || wasfreed(self.lightning_beam)) + W_Lightning_Attack1(); + + if(!self.BUTTON_ATCK_prev) + { + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); + self.BUTTON_ATCK_prev = 1; + } + } + } + else // todo + { + if (self.BUTTON_ATCK_prev != 0) + { + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); + ATTACK_FINISHED(self) = time + autocvar_g_balance_lightning_primary_refire * W_WeaponRateFactor(); + } + self.BUTTON_ATCK_prev = 0; + } + + //if (self.BUTTON_ATCK2) + //if (weapon_prepareattack(1, autocvar_g_balance_lightning_secondary_refire)) + //{ + // W_Lightning_Attack2(); + // self.lightning_count = autocvar_g_balance_lightning_secondary_count; + // weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_lightning_secondary_animtime, w_lightning_checkattack); + // self.lightning_secondarytime = time + autocvar_g_balance_lightning_secondary_refire2 * W_WeaponRateFactor(); + //} + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_lightning.md3"); + precache_model ("models/weapons/v_lightning.md3"); + precache_model ("models/weapons/h_lightning.iqm"); + //precache_sound ("weapons/lightning_bounce.wav"); + precache_sound ("weapons/lightning_fire.wav"); + precache_sound ("weapons/lightning_fire2.wav"); + precache_sound ("weapons/lightning_impact.wav"); + //precache_sound ("weapons/lightning_impact_combo.wav"); + //precache_sound ("weapons/W_Lightning_Beam_fire.wav"); + } + else if (req == WR_SETUP) + weapon_setup(WEP_LIGHTNING); + else if (req == WR_CHECKAMMO1) + { + return !autocvar_g_balance_lightning_primary_ammo || (self.ammo_cells > 0); + } + else if (req == WR_CHECKAMMO2) + return self.ammo_cells >= autocvar_g_balance_lightning_secondary_ammo; + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + { + return WEAPON_ELECTRO_MURDER_ORBS; + } + else + { + if(w_deathtype & HITTYPE_BOUNCE) + return WEAPON_ELECTRO_MURDER_COMBO; + else + return WEAPON_ELECTRO_MURDER_BOLT; + } + } + else if (req == WR_RESETPLAYER) + { + //self.lightning_secondarytime = time; + } + return TRUE; +}; + +void LightningInit() +{ + weapon_action(WEP_LIGHTNING, WR_PRECACHE); + lightning_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 1); + lightning_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 2); + lightning_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 3); + lightning_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 4); +} + +void spawnfunc_weapon_lightning (void) // should this really be here? +{ + weapon_defaultspawnfunc(WEP_LIGHTNING); +} +#endif +#ifdef CSQC +float w_lightning(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + + if(w_deathtype & HITTYPE_SECONDARY) + { + pointparticles(particleeffectnum("lightning_ballexplode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); + } + else + { + pointparticles(particleeffectnum("lightning_impact"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/lightning_impact.wav"); + precache_sound("weapons/lightning_impact_combo.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_lightning.qh b/qcsrc/common/weapons/w_lightning.qh new file mode 100644 index 000000000..57d6ceb0d --- /dev/null +++ b/qcsrc/common/weapons/w_lightning.qh @@ -0,0 +1,2 @@ +void LightningInit(); +vector lightning_shotorigin[4]; diff --git a/qcsrc/common/weapons/w_minelayer.qc b/qcsrc/common/weapons/w_minelayer.qc new file mode 100644 index 000000000..b27d069a3 --- /dev/null +++ b/qcsrc/common/weapons/w_minelayer.qc @@ -0,0 +1,563 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ MINE_LAYER, +/* function */ w_minelayer, +/* ammotype */ IT_ROCKETS, +/* impulse */ 4, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* model */ "minelayer", +/* shortname */ "minelayer", +/* fullname */ _("Mine Layer") +); +#else +#ifdef SVQC +void W_Mine_Think (void); +.float minelayer_detonate, mine_explodeanyway; +.float mine_time; +.vector mine_orientation; + +void spawnfunc_weapon_minelayer (void) +{ + weapon_defaultspawnfunc(WEP_MINE_LAYER); +} + +void W_Mine_Stick (entity to) +{ + spamsound (self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM); + + // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile + + entity newmine; + newmine = spawn(); + newmine.classname = self.classname; + + newmine.bot_dodge = self.bot_dodge; + newmine.bot_dodgerating = self.bot_dodgerating; + + newmine.owner = self.owner; + newmine.realowner = self.realowner; + setsize(newmine, '-4 -4 -4', '4 4 4'); + setorigin(newmine, self.origin); + setmodel(newmine, "models/mine.md3"); + newmine.angles = vectoangles(-trace_plane_normal); // face against the surface + + newmine.mine_orientation = -trace_plane_normal; + + newmine.takedamage = self.takedamage; + newmine.damageforcescale = self.damageforcescale; + newmine.health = self.health; + newmine.event_damage = self.event_damage; + newmine.spawnshieldtime = self.spawnshieldtime; + newmine.damagedbycontents = TRUE; + + newmine.movetype = MOVETYPE_NONE; // lock the mine in place + newmine.projectiledeathtype = self.projectiledeathtype; + + newmine.mine_time = self.mine_time; + + newmine.touch = func_null; + newmine.think = W_Mine_Think; + newmine.nextthink = time; + newmine.cnt = self.cnt; + newmine.flags = self.flags; + + remove(self); + self = newmine; + + if(to) + SetMovetypeFollow(self, to); +} + +void W_Mine_Explode () +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_damage, autocvar_g_balance_minelayer_edgedamage, autocvar_g_balance_minelayer_radius, world, world, autocvar_g_balance_minelayer_force, self.projectiledeathtype, other); + + if (self.realowner.weapon == WEP_MINE_LAYER) + { + entity oldself; + oldself = self; + self = self.realowner; + if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove (self); +} + +void W_Mine_DoRemoteExplode () +{ + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) + self.velocity = self.mine_orientation; // particle fx and decals need .velocity + + RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_remote_damage, autocvar_g_balance_minelayer_remote_edgedamage, autocvar_g_balance_minelayer_remote_radius, world, world, autocvar_g_balance_minelayer_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world); + + if (self.realowner.weapon == WEP_MINE_LAYER) + { + entity oldself; + oldself = self; + self = self.realowner; + if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove (self); +} + +void W_Mine_RemoteExplode () +{ + if(self.realowner.deadflag == DEAD_NO) + if((self.spawnshieldtime >= 0) + ? (time >= self.spawnshieldtime) // timer + : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_minelayer_remote_radius) // safety device + ) + { + W_Mine_DoRemoteExplode(); + } +} + +void W_Mine_ProximityExplode () +{ + // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance + if(autocvar_g_balance_minelayer_protection && self.mine_explodeanyway == 0) + { + entity head; + head = findradius(self.origin, autocvar_g_balance_minelayer_radius); + while(head) + { + if(head == self.realowner || !IsDifferentTeam(head, self.realowner)) + return; + head = head.chain; + } + } + + self.mine_time = 0; + W_Mine_Explode(); +} + +float W_Mine_Count(entity e) +{ + float minecount = 0; + entity mine; + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e) + minecount += 1; + + return minecount; +} + +void W_Mine_Think (void) +{ + entity head; + + self.nextthink = time; + + if(self.movetype == MOVETYPE_FOLLOW) + { + if(LostMovetypeFollow(self)) + { + UnsetMovetypeFollow(self); + self.movetype = MOVETYPE_NONE; + } + } + + // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this + // TODO: replace this mine_trigger.wav sound with a real countdown + if ((time > self.cnt) && (!self.mine_time)) + { + if(autocvar_g_balance_minelayer_lifetime_countdown > 0) + spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + self.mine_time = time + autocvar_g_balance_minelayer_lifetime_countdown; + self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near. + } + + // a player's mines shall explode if he disconnects or dies + // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams? + if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Mine_Explode(); + return; + } + + // set the mine for detonation when a foe gets close enough + head = findradius(self.origin, autocvar_g_balance_minelayer_proximityradius); + while(head) + { + if(IS_PLAYER(head) && head.deadflag == DEAD_NO) + if(head != self.realowner && IsDifferentTeam(head, self.realowner)) // don't trigger for team mates + if(!self.mine_time) + { + spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + self.mine_time = time + autocvar_g_balance_minelayer_time; + } + head = head.chain; + } + + // explode if it's time to + if(self.mine_time && time >= self.mine_time) + { + W_Mine_ProximityExplode(); + return; + } + + // remote detonation + if (self.realowner.weapon == WEP_MINE_LAYER) + if (self.realowner.deadflag == DEAD_NO) + if (self.minelayer_detonate) + W_Mine_RemoteExplode(); +} + +void W_Mine_Touch (void) +{ + if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) + return; // we're already a stuck mine, why do we get called? TODO does this even happen? + + if(WarpZone_Projectile_Touch()) + { + if(wasfreed(self)) + self.realowner.minelayer_mines -= 1; + return; + } + + if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO) + { + // hit a player + // don't stick + } + else + { + W_Mine_Stick(other); + } +} + +void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + + float is_from_enemy = (inflictor.realowner != self.realowner); + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1))) + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + self.angles = vectoangles(self.velocity); + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, W_Mine_Explode); +} + +void W_Mine_Attack (void) +{ + entity mine; + entity flash; + + // scan how many mines we placed, and return if we reached our limit + if(autocvar_g_balance_minelayer_limit) + { + if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit) + { + // the refire delay keeps this message from being spammed + sprint(self, strcat("minelayer: You cannot place more than ^2", ftos(autocvar_g_balance_minelayer_limit), " ^7mines at a time\n") ); + play2(self, "weapons/unavailable.wav"); + return; + } + } + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, autocvar_g_balance_minelayer_damage); + pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + mine = WarpZone_RefSys_SpawnSameRefSys(self); + mine.owner = mine.realowner = self; + if(autocvar_g_balance_minelayer_detonatedelay >= 0) + mine.spawnshieldtime = time + autocvar_g_balance_minelayer_detonatedelay; + else + mine.spawnshieldtime = -1; + mine.classname = "mine"; + mine.bot_dodge = TRUE; + mine.bot_dodgerating = autocvar_g_balance_minelayer_damage * 2; // * 2 because it can detonate inflight which makes it even more dangerous + + mine.takedamage = DAMAGE_YES; + mine.damageforcescale = autocvar_g_balance_minelayer_damageforcescale; + mine.health = autocvar_g_balance_minelayer_health; + mine.event_damage = W_Mine_Damage; + mine.damagedbycontents = TRUE; + + mine.movetype = MOVETYPE_TOSS; + PROJECTILE_MAKETRIGGER(mine); + mine.projectiledeathtype = WEP_MINE_LAYER; + setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot + + setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point + W_SetupProjectileVelocity(mine, autocvar_g_balance_minelayer_speed, 0); + mine.angles = vectoangles (mine.velocity); + + mine.touch = W_Mine_Touch; + mine.think = W_Mine_Think; + mine.nextthink = time; + mine.cnt = time + (autocvar_g_balance_minelayer_lifetime - autocvar_g_balance_minelayer_lifetime_countdown); + mine.flags = FL_PROJECTILE; + mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY; + + CSQCProjectile(mine, TRUE, PROJECTILE_MINE, TRUE); + + // muzzle flash for 1st person view + flash = spawn (); + setmodel (flash, "models/flash.md3"); // precision set below + SUB_SetFade (flash, time, 0.1); + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); + + // common properties + + other = mine; MUTATOR_CALLHOOK(EditProjectile); + + self.minelayer_mines = W_Mine_Count(self); +} + +float W_PlacedMines(float detonate) +{ + entity mine; + float minfound = 0; + + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self) + { + if(detonate) + { + if(!mine.minelayer_detonate) + { + mine.minelayer_detonate = TRUE; + minfound = 1; + } + } + else + minfound = 1; + } + return minfound; +} + +float w_minelayer(float req) +{ + entity mine; + float ammo_amount; + + if (req == WR_AIM) + { + // aim and decide to fire if appropriate + if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit) + self.BUTTON_ATCK = FALSE; + else + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_minelayer_speed, 0, autocvar_g_balance_minelayer_lifetime, FALSE); + if(skill >= 2) // skill 0 and 1 bots won't detonate mines! + { + // decide whether to detonate mines + entity targetlist, targ; + float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; + float selfdamage, teamdamage, enemydamage; + edgedamage = autocvar_g_balance_minelayer_edgedamage; + coredamage = autocvar_g_balance_minelayer_damage; + edgeradius = autocvar_g_balance_minelayer_radius; + recipricoledgeradius = 1 / edgeradius; + selfdamage = 0; + teamdamage = 0; + enemydamage = 0; + targetlist = findchainfloat(bot_attack, TRUE); + mine = find(world, classname, "mine"); + while (mine) + { + if (mine.realowner != self) + { + mine = find(mine, classname, "mine"); + continue; + } + targ = targetlist; + while (targ) + { + d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin); + d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); + // count potential damage according to type of target + if (targ == self) + selfdamage = selfdamage + d; + else if (targ.team == self.team && teamplay) + teamdamage = teamdamage + d; + else if (bot_shouldattack(targ)) + enemydamage = enemydamage + d; + targ = targ.chain; + } + mine = find(mine, classname, "mine"); + } + float desirabledamage; + desirabledamage = enemydamage; + if (time > self.invincible_finished && time > self.spawnshieldtime) + desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; + if (teamplay && self.team) + desirabledamage = desirabledamage - teamdamage; + + mine = find(world, classname, "mine"); + while (mine) + { + if (mine.realowner != self) + { + mine = find(mine, classname, "mine"); + continue; + } + makevectors(mine.v_angle); + targ = targetlist; + if (skill > 9) // normal players only do this for the target they are tracking + { + targ = targetlist; + while (targ) + { + if ( + (v_forward * normalize(mine.origin - targ.origin)< 0.1) + && desirabledamage > 0.1*coredamage + )self.BUTTON_ATCK2 = TRUE; + targ = targ.chain; + } + }else{ + float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); + //As the distance gets larger, a correct detonation gets near imposible + //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player + if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1) + if(IS_PLAYER(self.enemy)) + if(desirabledamage >= 0.1*coredamage) + if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) + self.BUTTON_ATCK2 = TRUE; + // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); + } + + mine = find(mine, classname, "mine"); + } + // if we would be doing at X percent of the core damage, detonate it + // but don't fire a new shot at the same time! + if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events + self.BUTTON_ATCK2 = TRUE; + if ((skill > 6.5) && (selfdamage > self.health)) + self.BUTTON_ATCK2 = FALSE; + //if(self.BUTTON_ATCK2 == TRUE) + // dprint(ftos(desirabledamage),"\n"); + if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < autocvar_g_balance_minelayer_ammo) // forced reload + { + // not if we're holding the minelayer without enough ammo, but can detonate existing mines + if not (W_PlacedMines(FALSE) && self.ammo_rockets < autocvar_g_balance_minelayer_ammo) + weapon_action(self.weapon, WR_RELOAD); + } + else if (self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, autocvar_g_balance_minelayer_refire)) + { + W_Mine_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minelayer_animtime, w_ready); + } + } + + if (self.BUTTON_ATCK2) + { + if(W_PlacedMines(TRUE)) + sound (self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM); + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/flash.md3"); + precache_model ("models/mine.md3"); + precache_model ("models/weapons/g_minelayer.md3"); + precache_model ("models/weapons/v_minelayer.md3"); + precache_model ("models/weapons/h_minelayer.iqm"); + precache_sound ("weapons/mine_det.wav"); + precache_sound ("weapons/mine_fire.wav"); + precache_sound ("weapons/mine_stick.wav"); + precache_sound ("weapons/mine_trigger.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_MINE_LAYER); + self.current_ammo = ammo_rockets; + } + else if (req == WR_CHECKAMMO1) + { + // don't switch while placing a mine + if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_minelayer_ammo; + ammo_amount += self.(weapon_load[WEP_MINE_LAYER]) >= autocvar_g_balance_minelayer_ammo; + return ammo_amount; + } + } + else if (req == WR_CHECKAMMO2) + { + if (W_PlacedMines(FALSE)) + return TRUE; + else + return FALSE; + } + else if (req == WR_RESETPLAYER) + { + self.minelayer_mines = 0; + } + else if (req == WR_RELOAD) + { + W_Reload(autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo, autocvar_g_balance_minelayer_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_MINELAYER_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + return WEAPON_MINELAYER_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_minelayer(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/mine_exp.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_minstanex.qc b/qcsrc/common/weapons/w_minstanex.qc new file mode 100644 index 000000000..2abb66875 --- /dev/null +++ b/qcsrc/common/weapons/w_minstanex.qc @@ -0,0 +1,213 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ MINSTANEX, +/* function */ w_minstanex, +/* ammotype */ IT_CELLS, +/* impulse */ 7, +/* flags */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* model */ "minstanex", +/* shortname */ "minstanex", +/* fullname */ _("MinstaNex") +); +#else +#ifdef SVQC +.float minstanex_lasthit; +.float jump_interval; + +void W_MinstaNex_Attack (void) +{ + float flying; + flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last + + W_SetupShot (self, TRUE, 0, "weapons/minstanexfire.wav", CH_WEAPON_A, 10000); + + yoda = 0; + damage_goodhits = 0; + FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, 10000, 800, 0, 0, 0, 0, WEP_MINSTANEX); + + if(yoda && flying) + Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); + if(damage_goodhits && self.minstanex_lasthit) + { + Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE); + damage_goodhits = 0; // only every second time + } + + self.minstanex_lasthit = damage_goodhits; + + pointparticles(particleeffectnum("nex_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + // teamcolor / hit beam effect + vector v; + v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + switch(self.team) + { + case NUM_TEAM_1: // Red + if(damage_goodhits) + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED_HIT"), w_shotorg, v); + else + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED"), w_shotorg, v); + break; + case NUM_TEAM_2: // Blue + if(damage_goodhits) + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE_HIT"), w_shotorg, v); + else + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE"), w_shotorg, v); + break; + case NUM_TEAM_3: // Yellow + if(damage_goodhits) + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW_HIT"), w_shotorg, v); + else + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW"), w_shotorg, v); + break; + case NUM_TEAM_4: // Pink + if(damage_goodhits) + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK_HIT"), w_shotorg, v); + else + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK"), w_shotorg, v); + break; + default: + if(damage_goodhits) + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3_HIT"), w_shotorg, v); + else + WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), w_shotorg, v); + break; + } + + W_DecreaseAmmo(ammo_cells, ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo), autocvar_g_balance_minstanex_reload_ammo); +} + +void spawnfunc_weapon_minstanex (void); // defined in t_items.qc + +float w_minstanex(float req) +{ + float ammo_amount; + float minstanex_ammo; + + // now multiple WR_s use this + minstanex_ammo = ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo); + + if (req == WR_AIM) + { + if(self.ammo_cells > 0) + self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE); + else + self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE); + } + else if (req == WR_THINK) + { + // if the laser uses load, we also consider its ammo for reloading + if(autocvar_g_balance_minstanex_reload_ammo && autocvar_g_balance_minstanex_laser_ammo && self.clip_load < min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if(autocvar_g_balance_minstanex_reload_ammo && self.clip_load < minstanex_ammo) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if (self.BUTTON_ATCK) + { + if (weapon_prepareattack(0, autocvar_g_balance_minstanex_refire)) + { + W_MinstaNex_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minstanex_animtime, w_ready); + } + } + else if (self.BUTTON_ATCK2) + { + if (self.jump_interval <= time) + if (weapon_prepareattack(1, -1)) + { + // handle refire manually, so that primary and secondary can be fired without conflictions (important for minstagib) + self.jump_interval = time + autocvar_g_balance_minstanex_laser_refire * W_WeaponRateFactor(); + + // decrease ammo for the laser? + if(autocvar_g_balance_minstanex_laser_ammo) + W_DecreaseAmmo(ammo_cells, autocvar_g_balance_minstanex_laser_ammo, autocvar_g_balance_minstanex_reload_ammo); + + // ugly minstagib hack to reuse the fire mode of the laser + float w; + w = self.weapon; + self.weapon = WEP_LASER; + W_Laser_Shockwave(); + self.weapon = w; + + // now do normal refire + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_minstanex_laser_animtime, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/nexflash.md3"); + precache_model ("models/weapons/g_minstanex.md3"); + precache_model ("models/weapons/v_minstanex.md3"); + precache_model ("models/weapons/h_minstanex.iqm"); + precache_sound ("weapons/minstanexfire.wav"); + precache_sound ("weapons/nexwhoosh1.wav"); + precache_sound ("weapons/nexwhoosh2.wav"); + precache_sound ("weapons/nexwhoosh3.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + W_Laser(WR_PRECACHE); + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_MINSTANEX); + self.current_ammo = ammo_cells; + self.minstanex_lasthit = 0; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_cells >= minstanex_ammo; + ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= minstanex_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + if(!autocvar_g_balance_minstanex_laser_ammo) + return TRUE; + ammo_amount = self.ammo_cells >= autocvar_g_balance_minstanex_laser_ammo; + ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= autocvar_g_balance_minstanex_laser_ammo; + return ammo_amount; + } + else if (req == WR_RESETPLAYER) + { + self.minstanex_lasthit = 0; + } + else if (req == WR_RELOAD) + { + float used_ammo; + if(autocvar_g_balance_minstanex_laser_ammo) + used_ammo = min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo); + else + used_ammo = minstanex_ammo; + + W_Reload(used_ammo, autocvar_g_balance_minstanex_reload_ammo, autocvar_g_balance_minstanex_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_THINKING_WITH_PORTALS; + } + else if (req == WR_KILLMESSAGE) + { + return WEAPON_MINSTANEX_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_minstanex(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/neximpact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_nex.qc b/qcsrc/common/weapons/w_nex.qc new file mode 100644 index 000000000..dc3c30ff6 --- /dev/null +++ b/qcsrc/common/weapons/w_nex.qc @@ -0,0 +1,275 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ NEX, +/* function */ w_nex, +/* ammotype */ IT_CELLS, +/* impulse */ 7, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* model */ "nex", +/* shortname */ "nex", +/* fullname */ _("Nex") +); +#else +#ifdef SVQC + +void SendCSQCNexBeamParticle(float charge) { + vector v; + v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte(MSG_BROADCAST, TE_CSQC_NEXGUNBEAMPARTICLE); + WriteCoord(MSG_BROADCAST, w_shotorg_x); + WriteCoord(MSG_BROADCAST, w_shotorg_y); + WriteCoord(MSG_BROADCAST, w_shotorg_z); + WriteCoord(MSG_BROADCAST, v_x); + WriteCoord(MSG_BROADCAST, v_y); + WriteCoord(MSG_BROADCAST, v_z); + WriteByte(MSG_BROADCAST, bound(0, 255 * charge, 255)); +} + +void W_Nex_Attack (float issecondary) +{ + float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge; + if(issecondary) + { + mydmg = autocvar_g_balance_nex_secondary_damage; + myforce = autocvar_g_balance_nex_secondary_force; + mymindist = autocvar_g_balance_nex_secondary_damagefalloff_mindist; + mymaxdist = autocvar_g_balance_nex_secondary_damagefalloff_maxdist; + myhalflife = autocvar_g_balance_nex_secondary_damagefalloff_halflife; + myforcehalflife = autocvar_g_balance_nex_secondary_damagefalloff_forcehalflife; + myammo = autocvar_g_balance_nex_secondary_ammo; + } + else + { + mydmg = autocvar_g_balance_nex_primary_damage; + myforce = autocvar_g_balance_nex_primary_force; + mymindist = autocvar_g_balance_nex_primary_damagefalloff_mindist; + mymaxdist = autocvar_g_balance_nex_primary_damagefalloff_maxdist; + myhalflife = autocvar_g_balance_nex_primary_damagefalloff_halflife; + myforcehalflife = autocvar_g_balance_nex_primary_damagefalloff_forcehalflife; + myammo = autocvar_g_balance_nex_primary_ammo; + } + + float flying; + flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last + + if(autocvar_g_balance_nex_charge) + { + charge = autocvar_g_balance_nex_charge_mindmg / mydmg + (1 - autocvar_g_balance_nex_charge_mindmg / mydmg) * self.nex_charge; + self.nex_charge *= autocvar_g_balance_nex_charge_shot_multiplier; // do this AFTER setting mydmg/myforce + // O RLY? -- divVerent + // YA RLY -- FruitieX + } + else + charge = 1; + mydmg *= charge; + myforce *= charge; + + W_SetupShot (self, TRUE, 5, "weapons/nexfire.wav", CH_WEAPON_A, mydmg); + if(charge > autocvar_g_balance_nex_charge_animlimit && autocvar_g_balance_nex_charge_animlimit) // if the Nex is overcharged, we play an extra sound + { + sound (self, CH_WEAPON_B, "weapons/nexcharge.wav", VOL_BASE * (charge - 0.5 * autocvar_g_balance_nex_charge_animlimit) / (1 - 0.5 * autocvar_g_balance_nex_charge_animlimit), ATTN_NORM); + } + + yoda = 0; + FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_NEX); + + if(yoda && flying) + Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); + + //beam and muzzle flash done on client + SendCSQCNexBeamParticle(charge); + + W_DecreaseAmmo(ammo_cells, myammo, autocvar_g_balance_nex_reload_ammo); +} + +void spawnfunc_weapon_nex (void); // defined in t_items.qc + +.float nex_chargepool_pauseregen_finished; +float w_nex(float req) +{ + float dt; + float ammo_amount; + if (req == WR_AIM) + { + if(bot_aim(1000000, 0, 1, FALSE)) + self.BUTTON_ATCK = TRUE; + else + { + if(autocvar_g_balance_nex_charge) + self.BUTTON_ATCK2 = TRUE; + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_nex_charge && self.nex_charge < autocvar_g_balance_nex_charge_limit) + self.nex_charge = min(1, self.nex_charge + autocvar_g_balance_nex_charge_rate * frametime / W_TICSPERFRAME); + + if(autocvar_g_balance_nex_secondary_chargepool) + if(self.nex_chargepool_ammo < 1) + { + if(self.nex_chargepool_pauseregen_finished < time) + self.nex_chargepool_ammo = min(1, self.nex_chargepool_ammo + autocvar_g_balance_nex_secondary_chargepool_regen * frametime / W_TICSPERFRAME); + self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_nex_secondary_chargepool_pause_health_regen); + } + + if(autocvar_g_balance_nex_reload_ammo && self.clip_load < min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else + { + if (self.BUTTON_ATCK) + { + if (weapon_prepareattack(0, autocvar_g_balance_nex_primary_refire)) + { + W_Nex_Attack(0); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_primary_animtime, w_ready); + } + } + if ((autocvar_g_balance_nex_secondary_charge && !autocvar_g_balance_nex_secondary) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2) + { + if(autocvar_g_balance_nex_secondary_charge) + { + self.nex_charge_rottime = time + autocvar_g_balance_nex_charge_rot_pause; + dt = frametime / W_TICSPERFRAME; + + if(self.nex_charge < 1) + { + if(autocvar_g_balance_nex_secondary_chargepool) + { + if(autocvar_g_balance_nex_secondary_ammo) + { + // always deplete if secondary is held + self.nex_chargepool_ammo = max(0, self.nex_chargepool_ammo - autocvar_g_balance_nex_secondary_ammo * dt); + + dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate); + self.nex_chargepool_pauseregen_finished = time + autocvar_g_balance_nex_secondary_chargepool_pause_regen; + dt = min(dt, self.nex_chargepool_ammo); + dt = max(0, dt); + + self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate; + } + } + + else if(autocvar_g_balance_nex_secondary_ammo) + { + if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed + { + dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate); + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + // if this weapon is reloadable, decrease its load. Else decrease the player's ammo + if(autocvar_g_balance_nex_reload_ammo) + { + dt = min(dt, (self.clip_load - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo); + dt = max(0, dt); + if(dt > 0) + { + self.clip_load = max(autocvar_g_balance_nex_secondary_ammo, self.clip_load - autocvar_g_balance_nex_secondary_ammo * dt); + } + self.(weapon_load[WEP_NEX]) = self.clip_load; + } + else + { + dt = min(dt, (self.ammo_cells - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo); + dt = max(0, dt); + if(dt > 0) + { + self.ammo_cells = max(autocvar_g_balance_nex_secondary_ammo, self.ammo_cells - autocvar_g_balance_nex_secondary_ammo * dt); + } + } + } + self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate; + } + } + + else + { + dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate); + self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate; + } + } + } + else if(autocvar_g_balance_nex_secondary) + { + if (weapon_prepareattack(0, autocvar_g_balance_nex_secondary_refire)) + { + W_Nex_Attack(1); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_secondary_animtime, w_ready); + } + } + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/nexflash.md3"); + precache_model ("models/weapons/g_nex.md3"); + precache_model ("models/weapons/v_nex.md3"); + precache_model ("models/weapons/h_nex.iqm"); + precache_sound ("weapons/nexfire.wav"); + precache_sound ("weapons/nexcharge.wav"); + precache_sound ("weapons/nexwhoosh1.wav"); + precache_sound ("weapons/nexwhoosh2.wav"); + precache_sound ("weapons/nexwhoosh3.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_NEX); + self.current_ammo = ammo_cells; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_primary_ammo; + ammo_amount += (autocvar_g_balance_nex_reload_ammo && self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_primary_ammo); + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + if(autocvar_g_balance_nex_secondary) + { + // don't allow charging if we don't have enough ammo + ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_secondary_ammo; + return ammo_amount; + } + else + { + return FALSE; // zoom is not a fire mode + } + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo), autocvar_g_balance_nex_reload_ammo, autocvar_g_balance_nex_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_THINKING_WITH_PORTALS; + } + else if (req == WR_KILLMESSAGE) + { + return WEAPON_NEX_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_nex(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/neximpact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_porto.qc b/qcsrc/common/weapons/w_porto.qc new file mode 100644 index 000000000..fad480d01 --- /dev/null +++ b/qcsrc/common/weapons/w_porto.qc @@ -0,0 +1,391 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ PORTO, +/* function */ w_porto, +/* ammotype */ 0, +/* impulse */ 0, +/* flags */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON, +/* rating */ 0, +/* model */ "porto" , +/* shortname */ "porto", +/* fullname */ _("Port-O-Launch") +); +#else +#ifdef SVQC +.entity porto_current; +.vector porto_v_angle; // holds "held" view angles +.float porto_v_angle_held; +.vector right_vector; + +void W_Porto_Success (void) +{ + if(self.realowner == world) + { + objerror("Cannot succeed successfully: no owner\n"); + return; + } + + self.realowner.porto_current = world; + remove(self); +} + +string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo); +void W_Porto_Fail (float failhard) +{ + if(self.realowner == world) + { + objerror("Cannot fail successfully: no owner\n"); + return; + } + + // no portals here! + if(self.cnt < 0) + { + Portal_ClearWithID(self.realowner, self.portal_id); + } + + self.realowner.porto_current = world; + + if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !WEPSET_CONTAINS_EW(self.realowner, WEP_PORTO)) + { + setsize (self, '-16 -16 0', '16 16 32'); + setorigin(self, self.origin + trace_plane_normal); + if(move_out_of_solid(self)) + { + self.flags = FL_ITEM; + self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128); + tracetoss(self, self); + if(vlen(trace_endpos - self.realowner.origin) < 128) + { + W_ThrowNewWeapon(self.realowner, WEP_PORTO, 0, self.origin, self.velocity); + centerprint(self.realowner, "^1Portal deployment failed.\n\n^2Catch it to try again!"); + } + } + } + remove(self); +} + +void W_Porto_Remove (entity p) +{ + if(p.porto_current.realowner == p && p.porto_current.classname == "porto") + { + entity oldself; + oldself = self; + self = p.porto_current; + W_Porto_Fail(1); + self = oldself; + } +} + +void W_Porto_Think (void) +{ + trace_plane_normal = '0 0 0'; + if(self.realowner.playerid != self.playerid) + remove(self); + else + W_Porto_Fail(0); +} + +void W_Porto_Touch (void) +{ + vector norm; + + // do not use PROJECTILE_TOUCH here + // FIXME but DO handle warpzones! + + if(other.classname == "portal") + return; // handled by the portal + + norm = trace_plane_normal; + if(trace_ent.iscreature) + { + traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_z, MOVE_WORLDONLY, self); + if(trace_fraction >= 1) + return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + return; + } + + if(self.realowner.playerid != self.playerid) + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); + remove(self); + } + else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + { + spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTN_NORM); + // just reflect + self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal); + self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal)); + } + else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); + W_Porto_Fail(0); + if(self.cnt < 0) + Portal_ClearAll_PortalsOnly(self.realowner); + } + else if(self.cnt == 0) + { + // in-portal only + if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^1In^7-portal created."); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); + trace_plane_normal = norm; + W_Porto_Fail(0); + } + } + else if(self.cnt == 1) + { + // out-portal only + if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^4Out^7-portal created."); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); + trace_plane_normal = norm; + W_Porto_Fail(0); + } + } + else if(self.effects & EF_RED) + { + self.effects += EF_BLUE - EF_RED; + if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^1In^7-portal created."); + self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm); + self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm)); + CSQCProjectile(self, TRUE, PROJECTILE_PORTO_BLUE, TRUE); // change type + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); + trace_plane_normal = norm; + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + else + { + if(self.realowner.portal_in.portal_id == self.portal_id) + { + if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) + { + sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); + trace_plane_normal = norm; + centerprint(self.realowner, "^4Out^7-portal created."); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } +} + +void W_Porto_Attack (float type) +{ + entity gren; + + W_SetupShot (self, FALSE, 4, "porto/fire.wav", CH_WEAPON_A, 0); + // always shoot from the eye + w_shotdir = v_forward; + w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward; + + //pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + gren = spawn (); + gren.cnt = type; + gren.owner = gren.realowner = self; + gren.playerid = self.playerid; + gren.classname = "porto"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = 200; + gren.movetype = MOVETYPE_BOUNCEMISSILE; + PROJECTILE_MAKETRIGGER(gren); + gren.effects = EF_RED; + gren.scale = 4; + setorigin(gren, w_shotorg); + setsize(gren, '0 0 0', '0 0 0'); + + if(type > 0) + gren.nextthink = time + autocvar_g_balance_porto_secondary_lifetime; + else + gren.nextthink = time + autocvar_g_balance_porto_primary_lifetime; + gren.think = W_Porto_Think; + gren.touch = W_Porto_Touch; + + if(type > 0) + { + if(self.items & IT_STRENGTH) + W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed * autocvar_g_balance_powerup_strength_force, 0); + else + W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed, 0); + } + else + { + if(self.items & IT_STRENGTH) + W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed * autocvar_g_balance_powerup_strength_force, 0); + else + W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed, 0); + } + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + gren.portal_id = time; + self.porto_current = gren; + gren.playerid = self.playerid; + fixedmakevectors(fixedvectoangles(gren.velocity)); + gren.right_vector = v_right; + + gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP; + + if(type > 0) + CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_BLUE, TRUE); + else + CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +void spawnfunc_weapon_porto (void) +{ + weapon_defaultspawnfunc(WEP_PORTO); +} + +float w_nexball_weapon(float req); +float w_porto(float req) +{ + //vector v_angle_save; + + if (g_nexball) { return w_nexball_weapon(req); } + if (req == WR_AIM) + { + self.BUTTON_ATCK = FALSE; + self.BUTTON_ATCK2 = FALSE; + if(!autocvar_g_balance_porto_secondary) + if(bot_aim(autocvar_g_balance_porto_primary_speed, 0, autocvar_g_balance_grenadelauncher_primary_lifetime, FALSE)) + self.BUTTON_ATCK = TRUE; + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_porto_secondary) + { + if (self.BUTTON_ATCK) + if (!self.porto_current) + if (!self.porto_forbidden) + if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire)) + { + W_Porto_Attack(0); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready); + } + + if (self.BUTTON_ATCK2) + if (!self.porto_current) + if (!self.porto_forbidden) + if (weapon_prepareattack(1, autocvar_g_balance_porto_secondary_refire)) + { + W_Porto_Attack(1); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_porto_secondary_animtime, w_ready); + } + } + else + { + if(self.porto_v_angle_held) + { + if(!self.BUTTON_ATCK2) + { + self.porto_v_angle_held = 0; + + ClientData_Touch(self); + } + } + else + { + if(self.BUTTON_ATCK2) + { + self.porto_v_angle = self.v_angle; + self.porto_v_angle_held = 1; + + ClientData_Touch(self); + } + } + if(self.porto_v_angle_held) + makevectors(self.porto_v_angle); // override the previously set angles + + if (self.BUTTON_ATCK) + if (!self.porto_current) + if (!self.porto_forbidden) + if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire)) + { + W_Porto_Attack(-1); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_porto.md3"); + precache_model ("models/weapons/v_porto.md3"); + precache_model ("models/weapons/h_porto.iqm"); + precache_model ("models/portal.md3"); + precache_sound ("porto/bounce.wav"); + precache_sound ("porto/create.wav"); + precache_sound ("porto/expire.wav"); + precache_sound ("porto/explode.wav"); + precache_sound ("porto/fire.wav"); + precache_sound ("porto/unsupported.wav"); + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_PORTO); + self.current_ammo = ammo_none; + } + else if (req == WR_RESETPLAYER) + { + self.porto_current = world; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_porto(float req) +{ + if(req == WR_IMPACTEFFECT) + { + print("Since when does Porto send DamageInfo?\n"); + } + else if(req == WR_PRECACHE) + { + // nothing to do + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_rifle.qc b/qcsrc/common/weapons/w_rifle.qc new file mode 100644 index 000000000..8ed4491d7 --- /dev/null +++ b/qcsrc/common/weapons/w_rifle.qc @@ -0,0 +1,265 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ RIFLE, +/* function */ w_rifle, +/* ammotype */ IT_NAILS, +/* impulse */ 7, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "campingrifle", +/* shortname */ "rifle", +/* fullname */ _("Rifle") +); +#else +#ifdef SVQC + +.float rifle_accumulator; + +void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSpeed, float pLifetime, float pAmmo, float deathtype, float pBulletConstant, float pTracer, float pShots, string pSound) +{ + float i; + + W_DecreaseAmmo(ammo_nails, pAmmo, autocvar_g_balance_rifle_reload_ammo); + + W_SetupShot (self, autocvar_g_antilag_bullets && pSpeed >= autocvar_g_antilag_bullets, 2, pSound, CH_WEAPON_A, pDamage * pShots); + + pointparticles(particleeffectnum("rifle_muzzleflash"), w_shotorg, w_shotdir * 2000, 1); + + if(self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) // if zoomed, shoot from the eye + { + w_shotdir = v_forward; + w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward; + } + + for(i = 0; i < pShots; ++i) + fireBallisticBullet(w_shotorg, w_shotdir, pSpread, pSpeed, pLifetime, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE), 1, pBulletConstant); + endFireBallisticBullet(); + + if (autocvar_g_casings >= 2) + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); +} + +void W_Rifle_Attack() +{ + W_Rifle_FireBullet(autocvar_g_balance_rifle_primary_spread, autocvar_g_balance_rifle_primary_damage, autocvar_g_balance_rifle_primary_force, autocvar_g_balance_rifle_primary_speed, autocvar_g_balance_rifle_primary_lifetime, autocvar_g_balance_rifle_primary_ammo, WEP_RIFLE, autocvar_g_balance_rifle_primary_bulletconstant, autocvar_g_balance_rifle_primary_tracer, autocvar_g_balance_rifle_primary_shots, "weapons/campingrifle_fire.wav"); +} + +void W_Rifle_Attack2() +{ + W_Rifle_FireBullet(autocvar_g_balance_rifle_secondary_spread, autocvar_g_balance_rifle_secondary_damage, autocvar_g_balance_rifle_secondary_force, autocvar_g_balance_rifle_secondary_speed, autocvar_g_balance_rifle_secondary_lifetime, autocvar_g_balance_rifle_secondary_ammo, WEP_RIFLE | HITTYPE_SECONDARY, autocvar_g_balance_rifle_secondary_bulletconstant, autocvar_g_balance_rifle_secondary_tracer, autocvar_g_balance_rifle_secondary_shots, "weapons/campingrifle_fire2.wav"); +} + +void spawnfunc_weapon_rifle (void) +{ + weapon_defaultspawnfunc(WEP_RIFLE); +} + +// compatibility alias +void spawnfunc_weapon_campingrifle (void) +{ + spawnfunc_weapon_rifle(); +} +void spawnfunc_weapon_sniperrifle (void) +{ + spawnfunc_weapon_rifle(); +} + +.void(void) rifle_bullethail_attackfunc; +.float rifle_bullethail_frame; +.float rifle_bullethail_animtime; +.float rifle_bullethail_refire; +void W_Rifle_BulletHail_Continue() +{ + float r, sw, af; + + sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing + af = ATTACK_FINISHED(self); + self.switchweapon = self.weapon; + ATTACK_FINISHED(self) = time; + print(ftos(self.ammo_nails), "\n"); + r = weapon_prepareattack(self.rifle_bullethail_frame == WFRAME_FIRE2, self.rifle_bullethail_refire); + if(self.switchweapon == self.weapon) + self.switchweapon = sw; + if(r) + { + self.rifle_bullethail_attackfunc(); + weapon_thinkf(self.rifle_bullethail_frame, self.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue); + print("thinkf set\n"); + } + else + { + ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time + print("out of ammo... ", ftos(self.weaponentity.state), "\n"); + } +} + +void W_Rifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire) +{ + // if we get here, we have at least one bullet to fire + AttackFunc(); + if(mode) + { + // continue hail + self.rifle_bullethail_attackfunc = AttackFunc; + self.rifle_bullethail_frame = fr; + self.rifle_bullethail_animtime = animtime; + self.rifle_bullethail_refire = refire; + weapon_thinkf(fr, animtime, W_Rifle_BulletHail_Continue); + } + else + { + // just one shot + weapon_thinkf(fr, animtime, w_ready); + } +} + +.float bot_secondary_riflemooth; +float w_rifle(float req) +{ + float ammo_amount; + + if (req == WR_AIM) + { + self.BUTTON_ATCK=FALSE; + self.BUTTON_ATCK2=FALSE; + if(vlen(self.origin-self.enemy.origin) > 1000) + self.bot_secondary_riflemooth = 0; + if(self.bot_secondary_riflemooth == 0) + { + if(bot_aim(autocvar_g_balance_rifle_primary_speed, 0, autocvar_g_balance_rifle_primary_lifetime, FALSE)) + { + self.BUTTON_ATCK = TRUE; + if(random() < 0.01) self.bot_secondary_riflemooth = 1; + } + } + else + { + if(bot_aim(autocvar_g_balance_rifle_secondary_speed, 0, autocvar_g_balance_rifle_secondary_lifetime, FALSE)) + { + self.BUTTON_ATCK2 = TRUE; + if(random() < 0.03) self.bot_secondary_riflemooth = 0; + } + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else + { + self.rifle_accumulator = bound(time - autocvar_g_balance_rifle_bursttime, self.rifle_accumulator, time); + if (self.BUTTON_ATCK) + if (weapon_prepareattack_check(0, autocvar_g_balance_rifle_primary_refire)) + if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_primary_burstcost) + { + weapon_prepareattack_do(0, autocvar_g_balance_rifle_primary_refire); + W_Rifle_BulletHail(autocvar_g_balance_rifle_primary_bullethail, W_Rifle_Attack, WFRAME_FIRE1, autocvar_g_balance_rifle_primary_animtime, autocvar_g_balance_rifle_primary_refire); + self.rifle_accumulator += autocvar_g_balance_rifle_primary_burstcost; + } + if (self.BUTTON_ATCK2) + { + if (autocvar_g_balance_rifle_secondary) + { + if(autocvar_g_balance_rifle_secondary_reload) + weapon_action(self.weapon, WR_RELOAD); + else + { + if (weapon_prepareattack_check(1, autocvar_g_balance_rifle_secondary_refire)) + if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_secondary_burstcost) + { + weapon_prepareattack_do(1, autocvar_g_balance_rifle_secondary_refire); + W_Rifle_BulletHail(autocvar_g_balance_rifle_secondary_bullethail, W_Rifle_Attack2, WFRAME_FIRE2, autocvar_g_balance_rifle_secondary_animtime, autocvar_g_balance_rifle_primary_refire); + self.rifle_accumulator += autocvar_g_balance_rifle_secondary_burstcost; + } + } + } + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_campingrifle.md3"); + precache_model ("models/weapons/v_campingrifle.md3"); + precache_model ("models/weapons/h_campingrifle.iqm"); + precache_sound ("weapons/campingrifle_fire.wav"); + precache_sound ("weapons/campingrifle_fire2.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_RIFLE); + self.current_ammo = ammo_nails; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_primary_ammo; + ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_secondary_ammo; + return ammo_amount; + } + else if (req == WR_RESETPLAYER) + { + self.rifle_accumulator = time - autocvar_g_balance_rifle_bursttime; + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo), autocvar_g_balance_rifle_reload_ammo, autocvar_g_balance_rifle_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_THINKING_WITH_PORTALS; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + { + if(w_deathtype & HITTYPE_BOUNCE) + return WEAPON_RIFLE_MURDER_HAIL_PIERCING; + else + return WEAPON_RIFLE_MURDER_HAIL; + } + else + { + if(w_deathtype & HITTYPE_BOUNCE) + return WEAPON_RIFLE_MURDER_PIERCING; + else + return WEAPON_RIFLE_MURDER; + } + } + return TRUE; +} +#endif +#ifdef CSQC +float w_rifle(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent) + { + if(w_random < 0.2) + sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.4) + sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.5) + sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + } + + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_rocketlauncher.qc b/qcsrc/common/weapons/w_rocketlauncher.qc new file mode 100644 index 000000000..504167cda --- /dev/null +++ b/qcsrc/common/weapons/w_rocketlauncher.qc @@ -0,0 +1,491 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ ROCKET_LAUNCHER, +/* function */ w_rlauncher, +/* ammotype */ IT_ROCKETS, +/* impulse */ 9, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* model */ "rl", +/* shortname */ "rocketlauncher", +/* fullname */ _("Rocket Launcher") +); +#else +#ifdef SVQC +.float rl_release; +.float rl_detonate_later; + +void W_Rocket_Unregister() +{ + if(self.realowner && self.realowner.lastrocket == self) + { + self.realowner.lastrocket = world; + // self.realowner.rl_release = 1; + } +} + +void W_Rocket_Explode () +{ + W_Rocket_Unregister(); + + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other); + + if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) + { + if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) + { + self.realowner.cnt = WEP_ROCKET_LAUNCHER; + ATTACK_FINISHED(self.realowner) = time; + self.realowner.switchweapon = w_getbestweapon(self.realowner); + } + } + remove (self); +} + +void W_Rocket_DoRemoteExplode () +{ + W_Rocket_Unregister(); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world); + + if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) + { + if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) + { + self.realowner.cnt = WEP_ROCKET_LAUNCHER; + ATTACK_FINISHED(self.realowner) = time; + self.realowner.switchweapon = w_getbestweapon(self.realowner); + } + } + remove (self); +} + +void W_Rocket_RemoteExplode() +{ + if(self.realowner.deadflag == DEAD_NO) + if(self.realowner.lastrocket) + { + if((self.spawnshieldtime >= 0) + ? (time >= self.spawnshieldtime) // timer + : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device + ) + { + W_Rocket_DoRemoteExplode(); + } + } +} + +vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos) +{ + if(thisdir * goaldir > maxturn_cos) + return goaldir; + if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite + return thisdir; // refuse to guide (better than letting a numerical error happen) + float f, m2; + vector v; + // solve: + // g = normalize(thisdir + goaldir * X) + // thisdir * g = maxturn + // + // gg = thisdir + goaldir * X + // (thisdir * gg)^2 = maxturn^2 * (gg * gg) + // + // (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir) + f = thisdir * goaldir; + // (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f) + // 0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1) + m2 = maxturn_cos * maxturn_cos; + v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1); + return normalize(thisdir + goaldir * v_y); // the larger solution! +} +// assume thisdir == -goaldir: +// f == -1 +// v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1) +// (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0 +// x^2 - 2 * x + 1 = 0 +// (x - 1)^2 = 0 +// x = 1 +// normalize(thisdir + goaldir) +// normalize(0) + +void W_Rocket_Think (void) +{ + vector desireddir, olddir, newdir, desiredorigin, goal; +#if 0 + float cosminang, cosmaxang, cosang; +#endif + float velspeed, f; + self.nextthink = time; + if (time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Rocket_Explode (); + return; + } + + // accelerate + makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0'); + velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward); + if (velspeed > 0) + self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed); + + // laser guided, or remote detonation + if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) + { + if(self == self.realowner.lastrocket) + if not(self.realowner.rl_release) + if not(self.BUTTON_ATCK2) + if(autocvar_g_balance_rocketlauncher_guiderate) + if(time > self.pushltime) + if(self.realowner.deadflag == DEAD_NO) + { + f = autocvar_g_balance_rocketlauncher_guideratedelay; + if(f) + f = bound(0, (time - self.pushltime) / f, 1); + else + f = 1; + + velspeed = vlen(self.velocity); + + makevectors(self.realowner.v_angle); + desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward); + desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs); + olddir = normalize(self.velocity); + + // now it gets tricky... we want to move like some curve to approximate the target direction + // but we are limiting the rate at which we can turn! + goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir; + newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD)); + + self.velocity = newdir * velspeed; + self.angles = vectoangles(self.velocity); + + if(!self.count) + { + pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1); + // TODO add a better sound here + sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM); + self.count = 1; + } + } + + if(self.rl_detonate_later) + W_Rocket_RemoteExplode(); + } + + if(self.csqcprojectile_clientanimate == 0) + UpdateCSQCProjectile(self); +} + +void W_Rocket_Touch (void) +{ + if(WarpZone_Projectile_Touch()) + { + if(wasfreed(self)) + W_Rocket_Unregister(); + return; + } + W_Rocket_Unregister(); + W_Rocket_Explode (); +} + +void W_Rocket_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + self.angles = vectoangles(self.velocity); + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, W_Rocket_Explode); +} + +void W_Rocket_Attack (void) +{ + entity missile; + entity flash; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_balance_rocketlauncher_damage); + pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = WarpZone_RefSys_SpawnSameRefSys(self); + missile.owner = missile.realowner = self; + self.lastrocket = missile; + if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0) + missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay; + else + missile.spawnshieldtime = -1; + missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay; + missile.classname = "rocket"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous + + missile.takedamage = DAMAGE_YES; + missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale; + missile.health = autocvar_g_balance_rocketlauncher_health; + missile.event_damage = W_Rocket_Damage; + missile.damagedbycontents = TRUE; + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_ROCKET_LAUNCHER; + setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot + + setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point + W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0); + missile.angles = vectoangles (missile.velocity); + + missile.touch = W_Rocket_Touch; + missile.think = W_Rocket_Think; + missile.nextthink = time; + missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime; + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound + + // muzzle flash for 1st person view + flash = spawn (); + setmodel (flash, "models/flash.md3"); // precision set below + SUB_SetFade (flash, time, 0.1); + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); + + // common properties + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc + +float w_rlauncher(float req) +{ + entity rock; + float rockfound; + float ammo_amount; + + if (req == WR_AIM) + { + // aim and decide to fire if appropriate + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE); + if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! + { + // decide whether to detonate rockets + entity missile, targetlist, targ; + float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; + float selfdamage, teamdamage, enemydamage; + edgedamage = autocvar_g_balance_rocketlauncher_edgedamage; + coredamage = autocvar_g_balance_rocketlauncher_damage; + edgeradius = autocvar_g_balance_rocketlauncher_radius; + recipricoledgeradius = 1 / edgeradius; + selfdamage = 0; + teamdamage = 0; + enemydamage = 0; + targetlist = findchainfloat(bot_attack, TRUE); + missile = find(world, classname, "rocket"); + while (missile) + { + if (missile.realowner != self) + { + missile = find(missile, classname, "rocket"); + continue; + } + targ = targetlist; + while (targ) + { + d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin); + d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); + // count potential damage according to type of target + if (targ == self) + selfdamage = selfdamage + d; + else if (targ.team == self.team && teamplay) + teamdamage = teamdamage + d; + else if (bot_shouldattack(targ)) + enemydamage = enemydamage + d; + targ = targ.chain; + } + missile = find(missile, classname, "rocket"); + } + float desirabledamage; + desirabledamage = enemydamage; + if (time > self.invincible_finished && time > self.spawnshieldtime) + desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; + if (teamplay && self.team) + desirabledamage = desirabledamage - teamdamage; + + missile = find(world, classname, "rocket"); + while (missile) + { + if (missile.realowner != self) + { + missile = find(missile, classname, "rocket"); + continue; + } + makevectors(missile.v_angle); + targ = targetlist; + if (skill > 9) // normal players only do this for the target they are tracking + { + targ = targetlist; + while (targ) + { + if ( + (v_forward * normalize(missile.origin - targ.origin)< 0.1) + && desirabledamage > 0.1*coredamage + )self.BUTTON_ATCK2 = TRUE; + targ = targ.chain; + } + }else{ + float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); + //As the distance gets larger, a correct detonation gets near imposible + //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player + if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1) + if(IS_PLAYER(self.enemy)) + if(desirabledamage >= 0.1*coredamage) + if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) + self.BUTTON_ATCK2 = TRUE; + // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); + } + + missile = find(missile, classname, "rocket"); + } + // if we would be doing at X percent of the core damage, detonate it + // but don't fire a new shot at the same time! + if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events + self.BUTTON_ATCK2 = TRUE; + if ((skill > 6.5) && (selfdamage > self.health)) + self.BUTTON_ATCK2 = FALSE; + //if(self.BUTTON_ATCK2 == TRUE) + // dprint(ftos(desirabledamage),"\n"); + if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else + { + if (self.BUTTON_ATCK) + { + if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop) + if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire)) + { + W_Rocket_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready); + self.rl_release = 0; + } + } + else + self.rl_release = 1; + + if (self.BUTTON_ATCK2) + { + rockfound = 0; + for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self) + { + if(!rock.rl_detonate_later) + { + rock.rl_detonate_later = TRUE; + rockfound = 1; + } + } + if(rockfound) + sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/flash.md3"); + precache_model ("models/weapons/g_rl.md3"); + precache_model ("models/weapons/v_rl.md3"); + precache_model ("models/weapons/h_rl.iqm"); + precache_sound ("weapons/rocket_det.wav"); + precache_sound ("weapons/rocket_fire.wav"); + precache_sound ("weapons/rocket_mode.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_ROCKET_LAUNCHER); + self.current_ammo = ammo_rockets; + self.rl_release = 1; + } + else if (req == WR_CHECKAMMO1) + { + // don't switch while guiding a missile + if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER) + { + ammo_amount = FALSE; + if(autocvar_g_balance_rocketlauncher_reload_ammo) + { + if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo && self.(weapon_load[WEP_ROCKET_LAUNCHER]) < autocvar_g_balance_rocketlauncher_ammo) + ammo_amount = TRUE; + } + else if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) + ammo_amount = TRUE; + return !ammo_amount; + } + } + else if (req == WR_CHECKAMMO2) + return FALSE; + else if (req == WR_RESETPLAYER) + { + self.rl_release = 0; + } + else if (req == WR_RELOAD) + { + W_Reload(autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo, autocvar_g_balance_rocketlauncher_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_ROCKETLAUNCHER_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) + return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH; + else + return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_rlauncher(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/rocket_impact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_seeker.qc b/qcsrc/common/weapons/w_seeker.qc new file mode 100644 index 000000000..1683a37e2 --- /dev/null +++ b/qcsrc/common/weapons/w_seeker.qc @@ -0,0 +1,713 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ SEEKER, +/* function */ w_seeker, +/* ammotype */ IT_ROCKETS, +/* impulse */ 8, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "seeker", +/* shortname */ "seeker", +/* fullname */ _("T.A.G. Seeker") +); +#else +#ifdef SVQC +//.float proxytime; = autoswitch +//.float tl; = wait +.entity tag_target, wps_tag_tracker; +.float tag_time; + +// ============================ +// Begin: Missile functions, these are general functions to be manipulated by other code +// ============================ +void Seeker_Missile_Explode () +{ + self.event_damage = func_null; + RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_missile_damage, autocvar_g_balance_seeker_missile_edgedamage, autocvar_g_balance_seeker_missile_radius, world, world, autocvar_g_balance_seeker_missile_force, self.projectiledeathtype, other); + + + remove (self); +} + +void Seeker_Missile_Touch() +{ + PROJECTILE_TOUCH; + + Seeker_Missile_Explode(); +} + +void Seeker_Missile_Think() +{ + entity e; + vector desireddir, olddir, newdir, eorg; + float turnrate; + float dist; + float spd; + + if (time > self.cnt) + { + self.projectiledeathtype |= HITTYPE_SPLASH; + Seeker_Missile_Explode(); + } + + spd = vlen(self.velocity); + spd = bound( + spd - autocvar_g_balance_seeker_missile_decel * frametime, + autocvar_g_balance_seeker_missile_speed_max, + spd + autocvar_g_balance_seeker_missile_accel * frametime + ); + + if (self.enemy != world) + if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO) + self.enemy = world; + + if (self.enemy != world) + { + e = self.enemy; + eorg = 0.5 * (e.absmin + e.absmax); + turnrate = autocvar_g_balance_seeker_missile_turnrate; // how fast to turn + desireddir = normalize(eorg - self.origin); + olddir = normalize(self.velocity); // get my current direction + dist = vlen(eorg - self.origin); + + // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P ) + if (autocvar_g_balance_seeker_missile_smart && (dist > autocvar_g_balance_seeker_missile_smart_mindist)) + { + // Is it a better idea (shorter distance) to trace to the target itself? + if ( vlen(self.origin + olddir * self.wait) < dist) + traceline(self.origin, self.origin + olddir * self.wait, FALSE, self); + else + traceline(self.origin, eorg, FALSE, self); + + // Setup adaptive tracelength + self.wait = bound(autocvar_g_balance_seeker_missile_smart_trace_min, vlen(self.origin - trace_endpos), self.wait = autocvar_g_balance_seeker_missile_smart_trace_max); + + // Calc how important it is that we turn and add this to the desierd (enemy) dir. + desireddir = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5); + } + + newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy + self.velocity = newdir * spd; // make me fly in the new direction at my flight speed + } + else + dist = 0; + + // Proxy + if (autocvar_g_balance_seeker_missile_proxy) + { + if ( dist <= autocvar_g_balance_seeker_missile_proxy_maxrange) + { + if (self.autoswitch == 0) + { + self.autoswitch = time + autocvar_g_balance_seeker_missile_proxy_delay; + } + else + { + if (self.autoswitch <= time) + { + Seeker_Missile_Explode(); + self.autoswitch = 0; + } + } + } + else + { + if (self.autoswitch != 0) + self.autoswitch = 0; + } + } + /////////////// + + if (self.enemy.deadflag != DEAD_NO) + { + self.enemy = world; + self.cnt = time + 1 + (random() * 4); + self.nextthink = self.cnt; + return; + } + + //self.angles = vectoangles(self.velocity); // turn model in the new flight direction + self.nextthink = time;// + 0.05; // csqc projectiles + UpdateCSQCProjectile(self); +} + + + +void Seeker_Missile_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions + return; // g_projectiles_damage says to halt + + if (self.realowner == attacker) + self.health = self.health - (damage * 0.25); + else + self.health = self.health - damage; + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, Seeker_Missile_Explode); +} + +/* +void Seeker_Missile_Animate() +{ + self.frame = self.frame +1; + self.nextthink = time + 0.05; + + if (self.enemy != world) + if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO) + self.enemy = world; + + if(self.frame == 5) + { + self.think = Seeker_Missile_Think; + self.nextthink = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles + + if (autocvar_g_balance_seeker_missile_proxy) + self.movetype = MOVETYPE_BOUNCEMISSILE; + else + self.movetype = MOVETYPE_FLYMISSILE; + } + + UpdateCSQCProjectile(self); +} +*/ + +void Seeker_Fire_Missile(vector f_diff, entity m_target) +{ + entity missile; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_reload_ammo); + + makevectors(self.v_angle); + W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/seeker_fire.wav", CH_WEAPON_A, 0); + w_shotorg += f_diff; + pointparticles(particleeffectnum("seeker_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + //self.detornator = FALSE; + + missile = spawn(); + missile.owner = missile.realowner = self; + missile.classname = "seeker_missile"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_seeker_missile_damage; + + missile.think = Seeker_Missile_Think; + missile.touch = Seeker_Missile_Touch; + missile.event_damage = Seeker_Missile_Damage; + missile.nextthink = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay"); + missile.cnt = time + autocvar_g_balance_seeker_missile_lifetime; + missile.enemy = m_target; + missile.solid = SOLID_BBOX; + missile.scale = 2; + missile.takedamage = DAMAGE_YES; + missile.health = autocvar_g_balance_seeker_missile_health; + missile.damageforcescale = autocvar_g_balance_seeker_missile_damageforcescale; + missile.damagedbycontents = TRUE; + //missile.think = Seeker_Missile_Animate; // csqc projectiles. + + if (missile.enemy != world) + missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY; + else + missile.projectiledeathtype = WEP_SEEKER; + + + setorigin (missile, w_shotorg); + setsize (missile, '-4 -4 -4', '4 4 4'); + missile.movetype = MOVETYPE_FLYMISSILE; + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH | MIF_GUIDED_TAG; + + W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_missile); + + missile.angles = vectoangles (missile.velocity); + + CSQCProjectile(missile, FALSE, PROJECTILE_SEEKER, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +// ============================ +// Begin: FLAC, close range attack meant for defeating rockets which are coming at you. +// ============================ +void Seeker_Flac_Explode () +{ + self.event_damage = func_null; + + RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_flac_damage, autocvar_g_balance_seeker_flac_edgedamage, autocvar_g_balance_seeker_flac_radius, world, world, autocvar_g_balance_seeker_flac_force, self.projectiledeathtype, other); + + remove (self); +} + +void Seeker_Flac_Touch() +{ + PROJECTILE_TOUCH; + + Seeker_Flac_Explode(); +} + +void Seeker_Fire_Flac() +{ + entity missile; + vector f_diff; + float c; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_flac_ammo, autocvar_g_balance_seeker_reload_ammo); + + c = mod(self.bulletcounter, 4); + switch(c) + { + case 0: + f_diff = '-1.25 -3.75 0'; + break; + case 1: + f_diff = '+1.25 -3.75 0'; + break; + case 2: + f_diff = '-1.25 +3.75 0'; + break; + case 3: + default: + f_diff = '+1.25 +3.75 0'; + break; + } + W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/flac_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_flac_damage); + w_shotorg += f_diff; + + pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.classname = "missile"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_seeker_flac_damage; + missile.touch = Seeker_Flac_Explode; + missile.use = Seeker_Flac_Explode; + missile.think = adaptor_think2use_hittype_splash; + missile.nextthink = time + autocvar_g_balance_seeker_flac_lifetime + autocvar_g_balance_seeker_flac_lifetime_rand; + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLY; + missile.projectiledeathtype = WEP_SEEKER; + missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY; + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + // csqc projectiles + //missile.angles = vectoangles (missile.velocity); + //missile.scale = 0.4; // BUG: the model is too big + + setorigin (missile, w_shotorg); + setsize (missile, '-2 -2 -2', '2 2 2'); + + W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_flac); + CSQCProjectile(missile, TRUE, PROJECTILE_FLAC, TRUE); + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +// ============================ +// Begin: Tag and rocket controllers +// ============================ +entity Seeker_Tagged_Info(entity isowner, entity istarget) +{ + entity tag; + for(tag = world; (tag = find(tag, classname, "tag_tracker")); ) + if ((tag.realowner == isowner) && (tag.tag_target == istarget)) + return tag; + + return world; +} + +void Seeker_Attack() +{ + entity tracker, closest_target; + + closest_target = world; + for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.realowner == self) + { + if (closest_target) + { + if (vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin)) + closest_target = tracker.tag_target; + } + else + closest_target = tracker.tag_target; + } + + traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self); + if ((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target))) + closest_target = world; + + Seeker_Fire_Missile('0 0 0', closest_target); +} + +void Seeker_Vollycontroller_Think() // TODO: Merge this with Seeker_Attack +{ + float c; + entity oldself,oldenemy; + self.cnt = self.cnt - 1; + + if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.ammo_rockets < autocvar_g_balance_seeker_missile_ammo) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER)) + { + remove(self); + return; + } + + self.nextthink = time + autocvar_g_balance_seeker_missile_delay * W_WeaponRateFactor(); + + oldself = self; + self = self.realowner; + + oldenemy = self.enemy; + self.enemy = oldself.enemy; + + c = mod(self.cnt, 4); + switch(c) + { + case 0: + Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy); + break; + case 1: + Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy); + break; + case 2: + Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy); + break; + case 3: + default: + Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy); + break; + } + + self.enemy = oldenemy; + self = oldself; +} + +void Seeker_Tracker_Think() +{ + // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up + if ((self.realowner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER) + || (time > self.tag_time + autocvar_g_balance_seeker_tag_tracker_lifetime)) + { + if (self) + { + WaypointSprite_Kill(self.tag_target.wps_tag_tracker); + remove(self); + } + return; + } + + // Update the think method information + self.nextthink = time; +} + +// ============================ +// Begin: Tag projectile +// ============================ +void Seeker_Tag_Explode () +{ + //if(other==self.realowner) + // return; + Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE, other.species, self); + + remove (self); +} + +void Seeker_Tag_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + self.health = self.health - damage; + if (self.health <= 0) + Seeker_Tag_Explode(); +} + +void Seeker_Tag_Touch() +{ + vector dir; + vector org2; + entity e; + + PROJECTILE_TOUCH; + + dir = normalize (self.realowner.origin - self.origin); + org2 = findbetterlocation (self.origin, 8); + + te_knightspike(org2); + + self.event_damage = func_null; + Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE | HITTYPE_SECONDARY, other.species, self); + + if (other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO) + { + // check to see if this person is already tagged by me + entity tag = Seeker_Tagged_Info(self.realowner, other); + + if (tag != world) + { + if (other.wps_tag_tracker && (autocvar_g_balance_seeker_type == 1)) // don't attach another waypointsprite without killing the old one first + WaypointSprite_Kill(other.wps_tag_tracker); + + tag.tag_time = time; + } + else + { + //sprint(self.realowner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n")); + e = spawn(); + e.cnt = autocvar_g_balance_seeker_missile_count; + e.classname = "tag_tracker"; + e.owner = self.owner; + e.realowner = self.realowner; + + if (autocvar_g_balance_seeker_type == 1) + { + e.tag_target = other; + e.tag_time = time; + e.think = Seeker_Tracker_Think; + } + else + { + e.enemy = other; + e.think = Seeker_Vollycontroller_Think; + } + + e.nextthink = time; + } + + if (autocvar_g_balance_seeker_type == 1) + { + WaypointSprite_Spawn("tagged-target", autocvar_g_balance_seeker_tag_tracker_lifetime, 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, TRUE, RADARICON_TAGGED, '0.5 1 0'); + WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT); + } + } + + remove(self); + return; +} + +void Seeker_Fire_Tag() +{ + entity missile; + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_tag_ammo, autocvar_g_balance_seeker_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/tag_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_missile_damage * autocvar_g_balance_seeker_missile_count); + + missile = spawn(); + missile.owner = missile.realowner = self; + missile.classname = "seeker_tag"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = 50; + missile.touch = Seeker_Tag_Touch; + missile.think = SUB_Remove; + missile.nextthink = time + autocvar_g_balance_seeker_tag_lifetime; + missile.movetype = MOVETYPE_FLY; + missile.solid = SOLID_BBOX; + + missile.takedamage = DAMAGE_YES; + missile.event_damage = Seeker_Tag_Damage; + missile.health = autocvar_g_balance_seeker_tag_health; + missile.damageforcescale = autocvar_g_balance_seeker_tag_damageforcescale; + + setorigin (missile, w_shotorg); + setsize (missile, '-2 -2 -2', '2 2 2'); + + missile.flags = FL_PROJECTILE; + //missile.missile_flags = MIF_..?; + + missile.movetype = MOVETYPE_FLY; + W_SETUPPROJECTILEVELOCITY(missile, g_balance_seeker_tag); + missile.angles = vectoangles (missile.velocity); + + CSQCProjectile(missile, TRUE, PROJECTILE_TAG, FALSE); // has sound + + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +// ============================ +// Begin: Genereal weapon functions +// ============================ +void spawnfunc_weapon_seeker (void) +{ + weapon_defaultspawnfunc(WEP_SEEKER); +} + +float w_seeker(float req) +{ + float ammo_amount; + + if (req == WR_AIM) + { + if (autocvar_g_balance_seeker_type == 1) + if (Seeker_Tagged_Info(self, self.enemy) != world) + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_missile_speed_max, 0, autocvar_g_balance_seeker_missile_lifetime, FALSE); + else + self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE); + else + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE); + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + + else if (self.BUTTON_ATCK) + { + if (autocvar_g_balance_seeker_type == 1) + { + if (weapon_prepareattack(0, autocvar_g_balance_seeker_missile_refire)) + { + Seeker_Attack(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_missile_animtime, w_ready); + } + } + else + { + if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire)) + { + Seeker_Fire_Tag(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready); + } + } + } + + else if (self.BUTTON_ATCK2) + { + if (autocvar_g_balance_seeker_type == 1) + { + if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire)) + { + Seeker_Fire_Tag(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready); + } + } + else + { + if (weapon_prepareattack(0, autocvar_g_balance_seeker_flac_refire)) + { + Seeker_Fire_Flac(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_flac_animtime, w_ready); + } + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_seeker.md3"); + precache_model ("models/weapons/v_seeker.md3"); + precache_model ("models/weapons/h_seeker.iqm"); + precache_sound ("weapons/tag_fire.wav"); + precache_sound ("weapons/flac_fire.wav"); + precache_sound ("weapons/seeker_fire.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_SEEKER); + self.current_ammo = ammo_rockets; + } + else if (req == WR_CHECKAMMO1) + { + if (autocvar_g_balance_seeker_type == 1) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_missile_ammo; + ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_missile_ammo; + } + else + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo; + ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo; + } + + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + if (autocvar_g_balance_seeker_type == 1) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo; + ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo; + } + else + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_flac_ammo; + ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_flac_ammo; + } + + return ammo_amount; + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo), autocvar_g_balance_seeker_reload_ammo, autocvar_g_balance_seeker_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_SEEKER_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SEEKER_MURDER_TAG; + else + return WEAPON_SEEKER_MURDER_SPRAY; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_seeker(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + if(w_deathtype & HITTYPE_BOUNCE) + { + if(w_deathtype & HITTYPE_SECONDARY) + { + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/tag_impact.wav", 1, ATTN_NORM); + } + else + { + pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1); + if(!w_issilent) + { + if (w_random<0.15) + sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTN_NORM); + else if (w_random<0.7) + sound(self, CH_SHOTS, "weapons/tagexp2.wav", 1, ATTN_NORM); + else + sound(self, CH_SHOTS, "weapons/tagexp3.wav", 1, ATTN_NORM); + } + } + } + else + { + pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1); + if(!w_issilent) + { + if (w_random<0.15) + sound(self, CH_SHOTS, "weapons/seekerexp1.wav", 1, ATTN_NORM); + else if (w_random<0.7) + sound(self, CH_SHOTS, "weapons/seekerexp2.wav", 1, ATTN_NORM); + else + sound(self, CH_SHOTS, "weapons/seekerexp3.wav", 1, ATTN_NORM); + } + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/seekerexp1.wav"); + precache_sound("weapons/seekerexp2.wav"); + precache_sound("weapons/seekerexp3.wav"); + precache_sound("weapons/tagexp1.wav"); + precache_sound("weapons/tagexp2.wav"); + precache_sound("weapons/tagexp3.wav"); + precache_sound("weapons/tag_impact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_shotgun.qc b/qcsrc/common/weapons/w_shotgun.qc new file mode 100644 index 000000000..6c6658d93 --- /dev/null +++ b/qcsrc/common/weapons/w_shotgun.qc @@ -0,0 +1,294 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ SHOTGUN, +/* function */ w_shotgun, +/* ammotype */ IT_SHELLS, +/* impulse */ 2, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_LOW, +/* model */ "shotgun", +/* shortname */ "shotgun", +/* fullname */ _("Shotgun") +); +#else +#ifdef SVQC + +void W_Shotgun_Attack (void) +{ + float sc; + float ammoamount; + float bullets; + float d; + float f; + float spread; + float bulletspeed; + float bulletconstant; + entity flash; + + ammoamount = autocvar_g_balance_shotgun_primary_ammo; + bullets = autocvar_g_balance_shotgun_primary_bullets; + d = autocvar_g_balance_shotgun_primary_damage; + f = autocvar_g_balance_shotgun_primary_force; + spread = autocvar_g_balance_shotgun_primary_spread; + bulletspeed = autocvar_g_balance_shotgun_primary_speed; + bulletconstant = autocvar_g_balance_shotgun_primary_bulletconstant; + + W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo); + + W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets); + for (sc = 0;sc < bullets;sc = sc + 1) + fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant); + endFireBallisticBullet(); + + pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo); + + // casing code + if (autocvar_g_casings >= 1) + for (sc = 0;sc < ammoamount;sc = sc + 1) + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self); + + // muzzle flash for 1st person view + flash = spawn(); + setmodel(flash, "models/uziflash.md3"); // precision set below + flash.think = SUB_Remove; + flash.nextthink = time + 0.06; + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); +} + +.float swing_prev; +.entity swing_alreadyhit; +void shotgun_meleethink (void) +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player; + entity target_victim; + vector targpos; + + if(!self.cnt) // set start time of melee + { + self.cnt = time; + W_PlayStrengthSound(self.realowner); + } + + makevectors(self.realowner.v_angle); // update values for v_* vectors + + // calculate swing percentage based on time + meleetime = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_melee_no_doubleslap) + { + remove(self); + return; + } + + // if okay, perform the traces needed for this frame + for(i=self.swing_prev; i < f; ++i) + { + swing_factor = ((1 - (i / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * autocvar_g_balance_shotgun_secondary_melee_range) + + (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up) + + (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_side)); + + WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner)); + + // draw lightning beams for debugging + //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); + //te_customflash(targpos, 40, 2, '1 1 1'); + + is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body"); + + if((trace_fraction < 1) // if trace is good, apply the damage and remove self + && (trace_ent.takedamage == DAMAGE_AIM) + && (trace_ent != self.swing_alreadyhit) + && (is_player || autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage)) + { + target_victim = trace_ent; // so it persists through other calls + + if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. + swing_damage = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1)); + else + swing_damage = (autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage * min(1, swing_factor + 1)); + + //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n")); + + Damage(target_victim, self.realowner, self.realowner, + swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY, + self.realowner.origin + self.realowner.view_ofs, + v_forward * autocvar_g_balance_shotgun_secondary_force); + + if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); } + + // draw large red flash for debugging + //te_customflash(targpos, 200, 2, '15 0 0'); + + if(autocvar_g_balance_shotgun_secondary_melee_multihit) // allow multiple hits with one swing, but not against the same player twice. + { + self.swing_alreadyhit = target_victim; + continue; // move along to next trace + } + else + { + remove(self); + return; + } + } + } + + if(time >= self.cnt + meleetime) + { + // melee is finished + remove(self); + return; + } + else + { + // set up next frame + self.swing_prev = i; + self.nextthink = time; + } +} + +void W_Shotgun_Attack2 (void) +{ + sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_shotgun_secondary_animtime, w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.realowner = self; + meleetemp.think = shotgun_meleethink; + meleetemp.nextthink = time + autocvar_g_balance_shotgun_secondary_melee_delay * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_shotgun_secondary_damage, autocvar_g_balance_shotgun_secondary_melee_range); +} + +void spawnfunc_weapon_shotgun(); // defined in t_items.qc + +.float shotgun_primarytime; + +float w_shotgun(float req) +{ + float ammo_amount; + if (req == WR_AIM) + if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + else + { + if(autocvar_g_antilag_bullets) + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + else + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE); + } + + else if (req == WR_THINK) + { + if(autocvar_g_balance_shotgun_reload_ammo && self.clip_load < autocvar_g_balance_shotgun_primary_ammo) // forced reload + { + // don't force reload an empty shotgun if its melee attack is active + if not(autocvar_g_balance_shotgun_secondary && self.ammo_shells < autocvar_g_balance_shotgun_primary_ammo) + weapon_action(self.weapon, WR_RELOAD); + } + else + { + if (self.BUTTON_ATCK) + { + if (time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary + { + if(weapon_prepareattack(0, autocvar_g_balance_shotgun_primary_animtime)) + { + W_Shotgun_Attack(); + self.shotgun_primarytime = time + autocvar_g_balance_shotgun_primary_refire * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_shotgun_primary_animtime, w_ready); + } + } + } + } + if (self.clip_load >= 0) // we are not currently reloading + if (!self.crouch) // no crouchmelee please + if (self.BUTTON_ATCK2 && autocvar_g_balance_shotgun_secondary) + if (weapon_prepareattack(1, autocvar_g_balance_shotgun_secondary_refire)) + { + // attempt forcing playback of the anim by switching to another anim (that we never play) here... + weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2); + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/uziflash.md3"); + precache_model ("models/weapons/g_shotgun.md3"); + precache_model ("models/weapons/v_shotgun.md3"); + precache_model ("models/weapons/h_shotgun.iqm"); + precache_sound ("misc/itempickup.wav"); + precache_sound ("weapons/shotgun_fire.wav"); + precache_sound ("weapons/shotgun_melee.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_SHOTGUN); + self.current_ammo = ammo_shells; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_shells >= autocvar_g_balance_shotgun_primary_ammo; + ammo_amount += self.(weapon_load[WEP_SHOTGUN]) >= autocvar_g_balance_shotgun_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + // melee attack is always available + return TRUE; + } + else if (req == WR_RELOAD) + { + W_Reload(autocvar_g_balance_shotgun_primary_ammo, autocvar_g_balance_shotgun_reload_ammo, autocvar_g_balance_shotgun_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_THINKING_WITH_PORTALS; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SHOTGUN_MURDER_SLAP; + else + return WEAPON_SHOTGUN_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +.float prevric; +float w_shotgun(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("shotgun_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent && time - self.prevric > 0.25) + { + if(w_random < 0.0165) + sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.033) + sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.05) + sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); + self.prevric = time; + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_tuba.qc b/qcsrc/common/weapons/w_tuba.qc new file mode 100644 index 000000000..48b696dd0 --- /dev/null +++ b/qcsrc/common/weapons/w_tuba.qc @@ -0,0 +1,465 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ TUBA, +/* function */ w_tuba, +/* ammotype */ 0, +/* impulse */ 1, +/* flags */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "tuba", +/* shortname */ "tuba", +/* fullname */ _("@!#%'n Tuba") +); +#else +#ifdef SVQC +//#define TUBA_NOTE(n) strcat("weapons/tuba_note", ftos(n), ".wav") +.entity tuba_note; +.float tuba_smoketime; +.float tuba_instrument; + +#define MAX_TUBANOTES 32 +.float tuba_lastnotes_last; +.float tuba_lastnotes_cnt; // over +.vector tuba_lastnotes[MAX_TUBANOTES]; + +float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo) +{ + float i, j, mmin, mmax, nolength; + float n = tokenize_console(melody); + if(n > pl.tuba_lastnotes_cnt) + return FALSE; + float pitchshift = 0; + + if(instrument >= 0) + if(pl.tuba_instrument != instrument) + return FALSE; + + // verify notes... + nolength = FALSE; + for(i = 0; i < n; ++i) + { + vector v = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]); + float ai = stof(argv(n - i - 1)); + float np = floor(ai); + if(ai == np) + nolength = TRUE; + // n counts the last played notes BACKWARDS + // _x is start + // _y is end + // _z is note pitch + if(ignorepitch && i == 0) + { + pitchshift = np - v_z; + } + else + { + if(v_z + pitchshift != np) + return FALSE; + } + } + + // now we know the right NOTES were played + if(!nolength) + { + // verify rhythm... + float ti = 0; + if(maxtempo > 0) + mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec + else + mmin = 0; + if(mintempo > 0) + mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec + else + mmax = 240; // you won't try THAT hard... (tempo 1) + //print(sprintf("initial tempo rules: %f %f\n", mmin, mmax)); + + for(i = 0; i < n; ++i) + { + vector vi = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]); + float ai = stof(argv(n - i - 1)); + ti -= 1 / (ai - floor(ai)); + float tj = ti; + for(j = i+1; j < n; ++j) + { + vector vj = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - j + MAX_TUBANOTES, MAX_TUBANOTES)]); + float aj = stof(argv(n - j - 1)); + tj -= (aj - floor(aj)); + + // note i should be at m*ti+b + // note j should be at m*tj+b + // so: + // we have a LINE l, so that + // vi_x <= l(ti) <= vi_y + // vj_x <= l(tj) <= vj_y + // what is m? + + // vi_x <= vi_y <= vj_x <= vj_y + // ti <= tj + //print(sprintf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti)); + //print(sprintf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj)); + //print(sprintf("m1 = %f\n", (vi_x - vj_y) / (ti - tj))); + //print(sprintf("m2 = %f\n", (vi_y - vj_x) / (ti - tj))); + mmin = max(mmin, (vi_x - vj_y) / (ti - tj)); // lower bound + mmax = min(mmax, (vi_y - vj_x) / (ti - tj)); // upper bound + } + } + + if(mmin > mmax) // rhythm fail + return FALSE; + } + + pl.tuba_lastnotes_cnt = 0; + + return TRUE; +} + +void W_Tuba_NoteOff() +{ + // we have a note: + // on: self.spawnshieldtime + // off: time + // note: self.cnt + if(self.owner.tuba_note == self) + { + self.owner.tuba_lastnotes_last = mod(self.owner.tuba_lastnotes_last + 1, MAX_TUBANOTES); + self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt; + self.owner.tuba_note = world; + self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES); + + string s; + s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null); + if(s != "") + { + // simulate a server message + switch(self.tuba_instrument) + { + default: + case 0: // Tuba + bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n")); + break; + case 1: + bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n")); + break; + case 2: + bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n")); + break; + } + } + } + remove(self); +} + +float Tuba_GetNote(entity pl, float hittype) +{ + float note; + float movestate; + movestate = 5; + if(pl.movement_x < 0) movestate -= 3; + if(pl.movement_x > 0) movestate += 3; + if(pl.movement_y < 0) movestate -= 1; + if(pl.movement_y > 0) movestate += 1; +#ifdef GMQCC + note = 0; +#endif + switch(movestate) + { + // layout: originally I wanted + // eb e e#=f + // B c d + // Gb G G# + // but then you only use forward and right key. So to make things more + // interesting, I swapped B with e#. Har har har... + // eb e B + // f=e# c d + // Gb G G# + case 1: note = -6; break; // Gb + case 2: note = -5; break; // G + case 3: note = -4; break; // G# + case 4: note = +5; break; // e# + default: + case 5: note = 0; break; // c + case 6: note = +2; break; // d + case 7: note = +3; break; // eb + case 8: note = +4; break; // e + case 9: note = -1; break; // B + } + if(pl.BUTTON_CROUCH) + note -= 12; + if(pl.BUTTON_JUMP) + note += 12; + if(hittype & HITTYPE_SECONDARY) + note += 7; + + // we support two kinds of tubas, those tuned in Eb and those tuned in C + // kind of tuba currently is player slot number, or team number if in + // teamplay + // that way, holes in the range of notes are "plugged" + if(teamplay) + { + if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4) + note += 3; + } + else + { + if(pl.clientcolors & 1) + note += 3; + } + + // total range of notes: + // 0 + // *** ** **** + // *** ** **** + // *** ** **** + // *** ** **** + // *** ********************* **** + // -18.........................+12 + // *** ********************* **** + // -18............................+15 + // with jump: ... +24 + // ... +27 + return note; +} + +float W_Tuba_NoteSendEntity(entity to, float sf) +{ + float f; + + msg_entity = to; + if(!sound_allowed(MSG_ONE, self.realowner)) + return FALSE; + + WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE); + WriteByte(MSG_ENTITY, sf); + if(sf & 1) + { + WriteChar(MSG_ENTITY, self.cnt); + f = 0; + if(self.realowner != to) + f |= 1; + f |= 2 * self.tuba_instrument; + WriteByte(MSG_ENTITY, f); + } + if(sf & 2) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + } + return TRUE; +} + +void W_Tuba_NoteThink() +{ + float dist_mult; + float vol0, vol1; + vector dir0, dir1; + vector v; + entity e; + if(time > self.teleport_time) + { + W_Tuba_NoteOff(); + return; + } + self.nextthink = time; + dist_mult = autocvar_g_balance_tuba_attenuation / autocvar_snd_soundradius; + FOR_EACH_REALCLIENT(e) + if(e != self.realowner) + { + v = self.origin - (e.origin + e.view_ofs); + vol0 = max(0, 1 - vlen(v) * dist_mult); + dir0 = normalize(v); + v = self.realowner.origin - (e.origin + e.view_ofs); + vol1 = max(0, 1 - vlen(v) * dist_mult); + dir1 = normalize(v); + if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume + { + setorigin(self, self.realowner.origin); + self.SendFlags |= 2; + break; + } + if(dir0 * dir1 < 0.9994) // 2 degrees change in angle + { + setorigin(self, self.realowner.origin); + self.SendFlags |= 2; + break; + } + } +} + +void W_Tuba_NoteOn(float hittype) +{ + vector o; + float n; + + W_SetupShot(self, FALSE, 2, "", 0, autocvar_g_balance_tuba_damage); + + n = Tuba_GetNote(self, hittype); + + hittype = 0; + if(self.tuba_instrument & 1) + hittype |= HITTYPE_SECONDARY; + if(self.tuba_instrument & 2) + hittype |= HITTYPE_BOUNCE; + + if(self.tuba_note) + { + if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument) + { + entity oldself = self; + self = self.tuba_note; + W_Tuba_NoteOff(); + self = oldself; + } + } + + if not(self.tuba_note) + { + self.tuba_note = spawn(); + self.tuba_note.owner = self.tuba_note.realowner = self; + self.tuba_note.cnt = n; + self.tuba_note.tuba_instrument = self.tuba_instrument; + self.tuba_note.think = W_Tuba_NoteThink; + self.tuba_note.nextthink = time; + self.tuba_note.spawnshieldtime = time; + Net_LinkEntity(self.tuba_note, FALSE, 0, W_Tuba_NoteSendEntity); + } + + self.tuba_note.teleport_time = time + autocvar_g_balance_tuba_refire * 2 * W_WeaponRateFactor(); // so it can get prolonged safely + + //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation); + RadiusDamage(self, self, autocvar_g_balance_tuba_damage, autocvar_g_balance_tuba_edgedamage, autocvar_g_balance_tuba_radius, world, world, autocvar_g_balance_tuba_force, hittype | WEP_TUBA, world); + + o = gettaginfo(self.exteriorweaponentity, 0); + if(time > self.tuba_smoketime) + { + pointparticles(particleeffectnum("smoke_ring"), o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1); + self.tuba_smoketime = time + 0.25; + } +} + +void spawnfunc_weapon_tuba (void) +{ + weapon_defaultspawnfunc(WEP_TUBA); +} + +float w_tuba(float req) +{ + if (req == WR_AIM) + { + // bots cannot play the Tuba well yet + // I think they should start with the recorder first + if(vlen(self.origin - self.enemy.origin) < autocvar_g_balance_tuba_radius) + { + if(random() > 0.5) + self.BUTTON_ATCK = 1; + else + self.BUTTON_ATCK2 = 1; + } + } + else if (req == WR_THINK) + { + if (self.BUTTON_ATCK) + if (weapon_prepareattack(0, autocvar_g_balance_tuba_refire)) + { + W_Tuba_NoteOn(0); + //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready); + weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready); + } + if (self.BUTTON_ATCK2) + if (weapon_prepareattack(1, autocvar_g_balance_tuba_refire)) + { + W_Tuba_NoteOn(HITTYPE_SECONDARY); + //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready); + weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready); + } + if(self.tuba_note) + { + if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2) + { + entity oldself = self; + self = self.tuba_note; + W_Tuba_NoteOff(); + self = oldself; + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_tuba.md3"); + precache_model ("models/weapons/v_tuba.md3"); + precache_model ("models/weapons/h_tuba.iqm"); + precache_model ("models/weapons/v_akordeon.md3"); + precache_model ("models/weapons/h_akordeon.iqm"); + precache_model ("models/weapons/v_kleinbottle.md3"); + precache_model ("models/weapons/h_kleinbottle.iqm"); + + //float i; + //for(i = -18; i <= +27; ++i) + // precache_sound(TUBA_NOTE(i)); + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_TUBA); + self.current_ammo = ammo_none; + self.tuba_instrument = 0; + } + else if (req == WR_RELOAD) + { + // switch to alternate instruments :) + if(self.weaponentity.state == WS_READY) + { + switch(self.tuba_instrument) + { + case 0: + self.tuba_instrument = 1; + self.weaponname = "akordeon"; + break; + case 1: + self.tuba_instrument = 2; + self.weaponname = "kleinbottle"; + break; + case 2: + self.tuba_instrument = 0; + self.weaponname = "tuba"; + break; + } + W_SetupShot(self, FALSE, 0, "", 0, 0); + pointparticles(particleeffectnum("teleport"), w_shotorg, '0 0 0', 1); + self.weaponentity.state = WS_INUSE; + weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready); + } + } + else if (req == WR_CHECKAMMO1) + return TRUE; // TODO use fuel? + else if (req == WR_CHECKAMMO2) + return TRUE; // TODO use fuel? + else if (req == WR_SUICIDEMESSAGE) + { + if(w_deathtype & HITTYPE_BOUNCE) + return WEAPON_KLEINBOTTLE_SUICIDE; + else if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_ACCORDEON_SUICIDE; + else + return WEAPON_TUBA_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_BOUNCE) + return WEAPON_KLEINBOTTLE_MURDER; + else if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_ACCORDEON_MURDER; + else + return WEAPON_TUBA_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_tuba(float req) +{ + // nothing to do here; particles of tuba are handled differently + + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/w_uzi.qc b/qcsrc/common/weapons/w_uzi.qc new file mode 100644 index 000000000..923ed9504 --- /dev/null +++ b/qcsrc/common/weapons/w_uzi.qc @@ -0,0 +1,341 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ UZI, +/* function */ w_uzi, +/* ammotype */ IT_NAILS, +/* impulse */ 3, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "uzi", +/* shortname */ "uzi", +/* fullname */ _("Machine Gun") +); +#else +#ifdef SVQC + +// leilei's fancy muzzleflash stuff +void UZI_Flash_Go() +{ + self.frame = self.frame + 2; + self.scale = self.scale * 0.5; + self.alpha = self.alpha - 0.25; + self.nextthink = time + 0.05; + + if (self.alpha <= 0) + { + self.think = SUB_Remove; + self.nextthink = time; + self.realowner.muzzle_flash = world; + return; + } + +} + +void UziFlash() +{ + if (self.muzzle_flash == world) + self.muzzle_flash = spawn(); + + // muzzle flash for 1st person view + setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below + + self.muzzle_flash.scale = 0.75; + self.muzzle_flash.think = UZI_Flash_Go; + self.muzzle_flash.nextthink = time + 0.02; + self.muzzle_flash.frame = 2; + self.muzzle_flash.alpha = 0.75; + self.muzzle_flash.angles_z = random() * 180; + self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + self.muzzle_flash.owner = self.muzzle_flash.realowner = self; +} + +void W_UZI_Attack (float deathtype) +{ + W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? autocvar_g_balance_uzi_first_damage : autocvar_g_balance_uzi_sustained_damage)); + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + // this attack_finished just enforces a cooldown at the end of a burst + ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); + + if (self.misc_bulletcounter == 1) + fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_first_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_first_damage, autocvar_g_balance_uzi_first_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); + else + fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_sustained_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); + endFireBallisticBullet(); + + pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + UziFlash(); + W_AttachToShotorg(self.muzzle_flash, '5 0 0'); + + // casing code + if (autocvar_g_casings >= 2) + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); + + if (self.misc_bulletcounter == 1) + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_first_ammo, autocvar_g_balance_uzi_reload_ammo); + else + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); +} + +// weapon frames +void uzi_fire1_02() +{ + if(self.weapon != self.switchweapon) // abort immediately if switching + { + w_ready(); + return; + } + if (self.BUTTON_ATCK) + { + if (!weapon_action(self.weapon, WR_CHECKAMMO2)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + self.misc_bulletcounter = self.misc_bulletcounter + 1; + W_UZI_Attack(WEP_UZI); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); + } + else + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, w_ready); +} + + +void uzi_mode1_fire_auto() +{ + float uzi_spread; + + if (!self.BUTTON_ATCK) + { + w_ready(); + return; + } + + if (!weapon_action(self.weapon, WR_CHECKAMMO1)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); + + W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + uzi_spread = bound(autocvar_g_balance_uzi_spread_min, autocvar_g_balance_uzi_spread_min + (autocvar_g_balance_uzi_spread_add * self.misc_bulletcounter), autocvar_g_balance_uzi_spread_max); + fireBallisticBullet(w_shotorg, w_shotdir, uzi_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); + endFireBallisticBullet(); + + self.misc_bulletcounter = self.misc_bulletcounter + 1; + + pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + UziFlash(); + W_AttachToShotorg(self.muzzle_flash, '5 0 0'); + + if (autocvar_g_casings >= 2) // casing code + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); + + ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_mode1_fire_auto); +} + +void uzi_mode1_fire_burst() +{ + W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_burst_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); + endFireBallisticBullet(); + + + pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + UziFlash(); + W_AttachToShotorg(self.muzzle_flash, '5 0 0'); + + if (autocvar_g_casings >= 2) // casing code + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); + + self.misc_bulletcounter = self.misc_bulletcounter + 1; + if (self.misc_bulletcounter == 0) + { + ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_burst_refire2 * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_animtime, w_ready); + } + else + { + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_refire, uzi_mode1_fire_burst); + } + +} + +void spawnfunc_weapon_machinegun(); // defined in t_items.qc + +float w_uzi(float req) +{ + float ammo_amount; + if (req == WR_AIM) + if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200) + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + else + { + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_uzi_reload_ammo && self.clip_load < min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if(autocvar_g_balance_uzi_mode == 1) + { + if (self.BUTTON_ATCK) + if (weapon_prepareattack(0, 0)) + { + self.misc_bulletcounter = 0; + uzi_mode1_fire_auto(); + } + + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, 0)) + { + if (!weapon_action(self.weapon, WR_CHECKAMMO2)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return FALSE; + } + + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_burst_ammo, autocvar_g_balance_uzi_reload_ammo); + + self.misc_bulletcounter = autocvar_g_balance_uzi_burst * -1; + uzi_mode1_fire_burst(); + } + } + else + { + + if (self.BUTTON_ATCK) + if (weapon_prepareattack(0, 0)) + { + self.misc_bulletcounter = 1; + W_UZI_Attack(WEP_UZI); // sets attack_finished + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); + } + + if (self.BUTTON_ATCK2 && autocvar_g_balance_uzi_first) + if (weapon_prepareattack(1, 0)) + { + self.misc_bulletcounter = 1; + W_UZI_Attack(WEP_UZI | HITTYPE_SECONDARY); // sets attack_finished + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_first_refire, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/uziflash.md3"); + precache_model ("models/weapons/g_uzi.md3"); + precache_model ("models/weapons/v_uzi.md3"); + precache_model ("models/weapons/h_uzi.iqm"); + precache_sound ("weapons/uzi_fire.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_UZI); + self.current_ammo = ammo_nails; + } + else if (req == WR_CHECKAMMO1) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_sustained_ammo; + else + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; + + if(autocvar_g_balance_uzi_reload_ammo) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_sustained_ammo; + else + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; + } + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_burst_ammo; + else + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; + + if(autocvar_g_balance_uzi_reload_ammo) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_burst_ammo; + else + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; + } + return ammo_amount; + } + else if (req == WR_RELOAD) + { + W_Reload(min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo), autocvar_g_balance_uzi_reload_ammo, autocvar_g_balance_uzi_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_THINKING_WITH_PORTALS; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_UZI_MURDER_SNIPE; + else + return WEAPON_UZI_MURDER_SPRAY; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_uzi(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent) + if(w_random < 0.05) + sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.1) + sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.2) + sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/server/cl_weapons.qc b/qcsrc/server/cl_weapons.qc deleted file mode 100644 index 34f17a613..000000000 --- a/qcsrc/server/cl_weapons.qc +++ /dev/null @@ -1,537 +0,0 @@ -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); -} diff --git a/qcsrc/server/cl_weaponsystem.qc b/qcsrc/server/cl_weaponsystem.qc deleted file mode 100644 index 23dfaedce..000000000 --- a/qcsrc/server/cl_weaponsystem.qc +++ /dev/null @@ -1,1210 +0,0 @@ -/* -=========================================================================== - - 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; -} diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 5b9c5917c..cf969eb4b 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -76,8 +76,8 @@ playerstats.qh portals.qh g_hook.qh -w_electro.qh -w_lightning.qh +weapons/w_electro.qh +weapons/w_lightning.qh scores.qh @@ -138,13 +138,13 @@ g_models.qc 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 diff --git a/qcsrc/server/w_all.qc b/qcsrc/server/w_all.qc deleted file mode 100644 index 46af3565e..000000000 --- a/qcsrc/server/w_all.qc +++ /dev/null @@ -1,22 +0,0 @@ -// 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" diff --git a/qcsrc/server/w_common.qc b/qcsrc/server/w_common.qc deleted file mode 100644 index 486673d4f..000000000 --- a/qcsrc/server/w_common.qc +++ /dev/null @@ -1,630 +0,0 @@ - -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; -} diff --git a/qcsrc/server/w_crylink.qc b/qcsrc/server/w_crylink.qc deleted file mode 100644 index 7cfc6c385..000000000 --- a/qcsrc/server/w_crylink.qc +++ /dev/null @@ -1,736 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ CRYLINK, -/* function */ w_crylink, -/* ammotype */ IT_CELLS, -/* impulse */ 6, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "crylink", -/* shortname */ "crylink", -/* fullname */ _("Crylink") -); -#else -#ifdef SVQC -.float gravity; -.float crylink_waitrelease; -.entity crylink_lastgroup; - -.entity queuenext; -.entity queueprev; - -void W_Crylink_CheckLinks(entity e) -{ - float i; - entity p; - - if(e == world) - error("W_Crylink_CheckLinks: entity is world"); - if(e.classname != "spike" || wasfreed(e)) - error(sprintf("W_Crylink_CheckLinks: entity is not a spike but a %s (freed: %d)", e.classname, wasfreed(e))); - - p = e; - for(i = 0; i < 1000; ++i) - { - if(p.queuenext.queueprev != p || p.queueprev.queuenext != p) - error("W_Crylink_CheckLinks: queue is inconsistent"); - p = p.queuenext; - if(p == e) - break; - } - if(i >= 1000) - error("W_Crylink_CheckLinks: infinite chain"); -} - -void W_Crylink_Dequeue_Raw(entity own, entity prev, entity me, entity next) -{ - W_Crylink_CheckLinks(next); - if(me == own.crylink_lastgroup) - own.crylink_lastgroup = ((me == next) ? world : next); - prev.queuenext = next; - next.queueprev = prev; - me.classname = "spike_oktoremove"; - if(me != next) - W_Crylink_CheckLinks(next); -} - -void W_Crylink_Dequeue(entity e) -{ - W_Crylink_Dequeue_Raw(e.realowner, e.queueprev, e, e.queuenext); -} - -void W_Crylink_Reset(void) -{ - W_Crylink_Dequeue(self); - remove(self); -} - -// force projectile to explode -void W_Crylink_LinkExplode (entity e, entity e2) -{ - float a; - - if(e == e2) - return; - - a = bound(0, 1 - (time - e.fade_time) * e.fade_rate, 1); - - if(e == e.realowner.crylink_lastgroup) - e.realowner.crylink_lastgroup = world; - - if(e.projectiledeathtype & HITTYPE_SECONDARY) - RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_damage * a, autocvar_g_balance_crylink_secondary_edgedamage * a, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * a, e.projectiledeathtype, other); - else - RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_damage * a, autocvar_g_balance_crylink_primary_edgedamage * a, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * a, e.projectiledeathtype, other); - - W_Crylink_LinkExplode(e.queuenext, e2); - - e.classname = "spike_oktoremove"; - remove (e); -} - -// adjust towards center -// returns the origin where they will meet... and the time till the meeting is -// stored in w_crylink_linkjoin_time. -// could possibly network this origin and time, and display a special particle -// effect when projectiles meet there :P -// jspeed: MINIMUM jing speed -// jtime: MAXIMUM jing time (0: none) -float w_crylink_linkjoin_time; -vector W_Crylink_LinkJoin(entity e, float jspeed, float jtime) -{ - vector avg_origin, avg_velocity; - vector targ_origin; - float avg_dist, n; - entity p; - - // FIXME remove this debug code - W_Crylink_CheckLinks(e); - - w_crylink_linkjoin_time = 0; - - avg_origin = e.origin; - avg_velocity = e.velocity; - n = 1; - for(p = e; (p = p.queuenext) != e; ) - { - avg_origin += WarpZone_RefSys_TransformOrigin(p, e, p.origin); - avg_velocity += WarpZone_RefSys_TransformVelocity(p, e, p.velocity); - ++n; - } - avg_origin *= (1.0 / n); - avg_velocity *= (1.0 / n); - - if(n < 2) - return avg_origin; // nothing to do - - // yes, mathematically we can do this in ONE step, but beware of 32bit floats... - avg_dist = pow(vlen(e.origin - avg_origin), 2); - for(p = e; (p = p.queuenext) != e; ) - avg_dist += pow(vlen(WarpZone_RefSys_TransformOrigin(p, e, p.origin) - avg_origin), 2); - avg_dist *= (1.0 / n); - avg_dist = sqrt(avg_dist); - - if(avg_dist == 0) - return avg_origin; // no change needed - - if(jspeed == 0 && jtime == 0) - { - e.velocity = avg_velocity; - UpdateCSQCProjectile(e); - for(p = e; (p = p.queuenext) != e; ) - { - p.velocity = WarpZone_RefSys_TransformVelocity(e, p, avg_velocity); - UpdateCSQCProjectile(p); - } - targ_origin = avg_origin + 1000000000 * normalize(avg_velocity); // HUUUUUUGE - } - else - { - if(jtime) - { - if(jspeed) - w_crylink_linkjoin_time = min(jtime, avg_dist / jspeed); - else - w_crylink_linkjoin_time = jtime; - } - else - w_crylink_linkjoin_time = avg_dist / jspeed; - targ_origin = avg_origin + w_crylink_linkjoin_time * avg_velocity; - - e.velocity = (targ_origin - e.origin) * (1.0 / w_crylink_linkjoin_time); - UpdateCSQCProjectile(e); - for(p = e; (p = p.queuenext) != e; ) - { - p.velocity = WarpZone_RefSys_TransformVelocity(e, p, (targ_origin - WarpZone_RefSys_TransformOrigin(p, e, p.origin)) * (1.0 / w_crylink_linkjoin_time)); - UpdateCSQCProjectile(p); - } - - // analysis: - // jspeed -> +infinity: - // w_crylink_linkjoin_time -> +0 - // targ_origin -> avg_origin - // p->velocity -> HUEG towards center - // jspeed -> 0: - // w_crylink_linkjoin_time -> +/- infinity - // targ_origin -> avg_velocity * +/- infinity - // p->velocity -> avg_velocity - // jspeed -> -infinity: - // w_crylink_linkjoin_time -> -0 - // targ_origin -> avg_origin - // p->velocity -> HUEG away from center - } - - W_Crylink_CheckLinks(e); - - return targ_origin; -} - -void W_Crylink_LinkJoinEffect_Think() -{ - // is there at least 2 projectiles very close? - entity e, p; - float n; - e = self.owner.crylink_lastgroup; - n = 0; - if(e) - { - if(vlen(e.origin - self.origin) < vlen(e.velocity) * frametime) - ++n; - for(p = e; (p = p.queuenext) != e; ) - { - if(vlen(p.origin - self.origin) < vlen(p.velocity) * frametime) - ++n; - } - if(n >= 2) - { - if(e.projectiledeathtype & HITTYPE_SECONDARY) - { - if(autocvar_g_balance_crylink_secondary_joinexplode) - { - n = n / autocvar_g_balance_crylink_secondary_shots; - RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_secondary_joinexplode_damage * n, - autocvar_g_balance_crylink_secondary_joinexplode_edgedamage * n, - autocvar_g_balance_crylink_secondary_joinexplode_radius * n, e.realowner, world, - autocvar_g_balance_crylink_secondary_joinexplode_force * n, e.projectiledeathtype, other); - - pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n); - } - } - else - { - if(autocvar_g_balance_crylink_primary_joinexplode) - { - n = n / autocvar_g_balance_crylink_primary_shots; - RadiusDamage (e, e.realowner, autocvar_g_balance_crylink_primary_joinexplode_damage * n, - autocvar_g_balance_crylink_primary_joinexplode_edgedamage * n, - autocvar_g_balance_crylink_primary_joinexplode_radius * n, e.realowner, world, - autocvar_g_balance_crylink_primary_joinexplode_force * n, e.projectiledeathtype, other); - - pointparticles(particleeffectnum("crylink_joinexplode"), self.origin, '0 0 0', n); - } - } - } - } - remove(self); -} - -float W_Crylink_Touch_WouldHitFriendly(entity projectile, float rad) -{ - entity head = WarpZone_FindRadius((projectile.origin + (projectile.mins + projectile.maxs) * 0.5), rad + MAX_DAMAGEEXTRARADIUS, FALSE); - float hit_friendly = 0; - float hit_enemy = 0; - - while(head) - { - if((head.takedamage != DAMAGE_NO) && (head.deadflag == DEAD_NO)) - { - if(IsDifferentTeam(head, projectile.realowner)) - ++hit_enemy; - else - ++hit_friendly; - } - - head = head.chain; - } - - return (hit_enemy ? FALSE : hit_friendly); -} - -// NO bounce protection, as bounces are limited! -void W_Crylink_Touch (void) -{ - float finalhit; - float f; - PROJECTILE_TOUCH; - - float a; - a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1); - - finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO)); - if(finalhit) - f = 1; - else - f = autocvar_g_balance_crylink_primary_bouncedamagefactor; - if(a) - f *= a; - - float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_primary_damage * f, autocvar_g_balance_crylink_primary_edgedamage * f, autocvar_g_balance_crylink_primary_radius, world, world, autocvar_g_balance_crylink_primary_force * f, self.projectiledeathtype, other); - - if(totaldamage && ((autocvar_g_balance_crylink_primary_linkexplode == 2) || ((autocvar_g_balance_crylink_primary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_primary_radius)))) - { - if(self == self.realowner.crylink_lastgroup) - self.realowner.crylink_lastgroup = world; - W_Crylink_LinkExplode(self.queuenext, self); - self.classname = "spike_oktoremove"; - remove (self); - return; - } - else if(finalhit) - { - // just unlink - W_Crylink_Dequeue(self); - remove(self); - return; - } - self.cnt = self.cnt - 1; - self.angles = vectoangles(self.velocity); - self.owner = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - // commented out as it causes a little hitch... - //if(proj.cnt == 0) - // CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE); -} - -void W_Crylink_Touch2 (void) -{ - float finalhit; - float f; - PROJECTILE_TOUCH; - - float a; - a = bound(0, 1 - (time - self.fade_time) * self.fade_rate, 1); - - finalhit = ((self.cnt <= 0) || (other.takedamage != DAMAGE_NO)); - if(finalhit) - f = 1; - else - f = autocvar_g_balance_crylink_secondary_bouncedamagefactor; - if(a) - f *= a; - - float totaldamage = RadiusDamage(self, self.realowner, autocvar_g_balance_crylink_secondary_damage * f, autocvar_g_balance_crylink_secondary_edgedamage * f, autocvar_g_balance_crylink_secondary_radius, world, world, autocvar_g_balance_crylink_secondary_force * f, self.projectiledeathtype, other); - - if(totaldamage && ((autocvar_g_balance_crylink_secondary_linkexplode == 2) || ((autocvar_g_balance_crylink_secondary_linkexplode == 1) && !W_Crylink_Touch_WouldHitFriendly(self, autocvar_g_balance_crylink_secondary_radius)))) - { - if(self == self.realowner.crylink_lastgroup) - self.realowner.crylink_lastgroup = world; - W_Crylink_LinkExplode(self.queuenext, self); - self.classname = "spike_oktoremove"; - remove (self); - return; - } - else if(finalhit) - { - // just unlink - W_Crylink_Dequeue(self); - remove(self); - return; - } - self.cnt = self.cnt - 1; - self.angles = vectoangles(self.velocity); - self.owner = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - // commented out as it causes a little hitch... - //if(proj.cnt == 0) - // CSQCProjectile(proj, TRUE, PROJECTILE_CRYLINK, TRUE); -} - -void W_Crylink_Fadethink (void) -{ - W_Crylink_Dequeue(self); - remove(self); -} - -void W_Crylink_Attack (void) -{ - float counter, shots; - entity proj, prevproj, firstproj; - vector s; - vector forward, right, up; - float maxdmg; - - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_reload_ammo); - - maxdmg = autocvar_g_balance_crylink_primary_damage*autocvar_g_balance_crylink_primary_shots; - maxdmg *= 1 + autocvar_g_balance_crylink_primary_bouncedamagefactor * autocvar_g_balance_crylink_primary_bounces; - if(autocvar_g_balance_crylink_primary_joinexplode) - maxdmg += autocvar_g_balance_crylink_primary_joinexplode_damage; - - W_SetupShot (self, FALSE, 2, "weapons/crylink_fire.wav", CH_WEAPON_A, maxdmg); - forward = v_forward; - right = v_right; - up = v_up; - - shots = autocvar_g_balance_crylink_primary_shots; - pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots); - proj = prevproj = firstproj = world; - for(counter = 0; counter < shots; ++counter) - { - proj = spawn (); - proj.reset = W_Crylink_Reset; - proj.realowner = proj.owner = self; - proj.classname = "spike"; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = autocvar_g_balance_crylink_primary_damage; - if(shots == 1) { - proj.queuenext = proj; - proj.queueprev = proj; - } - else if(counter == 0) { // first projectile, store in firstproj for now - firstproj = proj; - } - else if(counter == shots - 1) { // last projectile, link up with first projectile - prevproj.queuenext = proj; - firstproj.queueprev = proj; - proj.queuenext = firstproj; - proj.queueprev = prevproj; - } - else { // else link up with previous projectile - prevproj.queuenext = proj; - proj.queueprev = prevproj; - } - - prevproj = proj; - - proj.movetype = MOVETYPE_BOUNCEMISSILE; - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_CRYLINK; - //proj.gravity = 0.001; - - setorigin (proj, w_shotorg); - setsize(proj, '0 0 0', '0 0 0'); - - - s = '0 0 0'; - if (counter == 0) - s = '0 0 0'; - else - { - makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1))); - s_y = v_forward_x; - s_z = v_forward_y; - } - s = s * autocvar_g_balance_crylink_primary_spread * g_weaponspreadfactor; - W_SetupProjectileVelocityEx(proj, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_crylink_primary_speed, 0, 0, 0, FALSE); - proj.touch = W_Crylink_Touch; - - proj.think = W_Crylink_Fadethink; - if(counter == 0) - { - proj.fade_time = time + autocvar_g_balance_crylink_primary_middle_lifetime; - proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_middle_fadetime; - proj.nextthink = time + autocvar_g_balance_crylink_primary_middle_lifetime + autocvar_g_balance_crylink_primary_middle_fadetime; - } - else - { - proj.fade_time = time + autocvar_g_balance_crylink_primary_other_lifetime; - proj.fade_rate = 1 / autocvar_g_balance_crylink_primary_other_fadetime; - proj.nextthink = time + autocvar_g_balance_crylink_primary_other_lifetime + autocvar_g_balance_crylink_primary_other_fadetime; - } - proj.teleport_time = time + autocvar_g_balance_crylink_primary_joindelay; - proj.cnt = autocvar_g_balance_crylink_primary_bounces; - //proj.scale = 1 + 1 * proj.cnt; - - proj.angles = vectoangles (proj.velocity); - - //proj.glow_size = 20; - - proj.flags = FL_PROJECTILE; - proj.missile_flags = MIF_SPLASH; - - CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE); - - other = proj; MUTATOR_CALLHOOK(EditProjectile); - } - if(autocvar_g_balance_crylink_primary_joinspread != 0 || autocvar_g_balance_crylink_primary_jointime != 0) - { - self.crylink_lastgroup = proj; - W_Crylink_CheckLinks(proj); - self.crylink_waitrelease = 1; - } -} - -void W_Crylink_Attack2 (void) -{ - float counter, shots; - entity proj, prevproj, firstproj; - vector s; - vector forward, right, up; - float maxdmg; - - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_crylink_secondary_ammo, autocvar_g_balance_crylink_reload_ammo); - - maxdmg = autocvar_g_balance_crylink_secondary_damage*autocvar_g_balance_crylink_secondary_shots; - maxdmg *= 1 + autocvar_g_balance_crylink_secondary_bouncedamagefactor * autocvar_g_balance_crylink_secondary_bounces; - if(autocvar_g_balance_crylink_secondary_joinexplode) - maxdmg += autocvar_g_balance_crylink_secondary_joinexplode_damage; - - W_SetupShot (self, FALSE, 2, "weapons/crylink_fire2.wav", CH_WEAPON_A, maxdmg); - forward = v_forward; - right = v_right; - up = v_up; - - shots = autocvar_g_balance_crylink_secondary_shots; - pointparticles(particleeffectnum("crylink_muzzleflash"), w_shotorg, w_shotdir * 1000, shots); - proj = prevproj = firstproj = world; - for(counter = 0; counter < shots; ++counter) - { - proj = spawn (); - proj.reset = W_Crylink_Reset; - proj.realowner = proj.owner = self; - proj.classname = "spike"; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = autocvar_g_balance_crylink_secondary_damage; - if(shots == 1) { - proj.queuenext = proj; - proj.queueprev = proj; - } - else if(counter == 0) { // first projectile, store in firstproj for now - firstproj = proj; - } - else if(counter == shots - 1) { // last projectile, link up with first projectile - prevproj.queuenext = proj; - firstproj.queueprev = proj; - proj.queuenext = firstproj; - proj.queueprev = prevproj; - } - else { // else link up with previous projectile - prevproj.queuenext = proj; - proj.queueprev = prevproj; - } - - prevproj = proj; - - proj.movetype = MOVETYPE_BOUNCEMISSILE; - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_CRYLINK | HITTYPE_SECONDARY; - //proj.gravity = 0.001; - - setorigin (proj, w_shotorg); - setsize(proj, '0 0 0', '0 0 0'); - - if(autocvar_g_balance_crylink_secondary_spreadtype == 1) - { - s = '0 0 0'; - if (counter == 0) - s = '0 0 0'; - else - { - makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1))); - s_y = v_forward_x; - s_z = v_forward_y; - } - s = s * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor; - s = w_shotdir + right * s_y + up * s_z; - } - else - { - s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * autocvar_g_balance_crylink_secondary_spread * g_weaponspreadfactor); - } - - W_SetupProjectileVelocityEx(proj, s, v_up, autocvar_g_balance_crylink_secondary_speed, 0, 0, 0, FALSE); - proj.touch = W_Crylink_Touch2; - proj.think = W_Crylink_Fadethink; - if(counter == (shots - 1) / 2) - { - proj.fade_time = time + autocvar_g_balance_crylink_secondary_middle_lifetime; - proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_middle_fadetime; - proj.nextthink = time + autocvar_g_balance_crylink_secondary_middle_lifetime + autocvar_g_balance_crylink_secondary_middle_fadetime; - } - else - { - proj.fade_time = time + autocvar_g_balance_crylink_secondary_line_lifetime; - proj.fade_rate = 1 / autocvar_g_balance_crylink_secondary_line_fadetime; - proj.nextthink = time + autocvar_g_balance_crylink_secondary_line_lifetime + autocvar_g_balance_crylink_secondary_line_fadetime; - } - proj.teleport_time = time + autocvar_g_balance_crylink_secondary_joindelay; - proj.cnt = autocvar_g_balance_crylink_secondary_bounces; - //proj.scale = 1 + 1 * proj.cnt; - - proj.angles = vectoangles (proj.velocity); - - //proj.glow_size = 20; - - proj.flags = FL_PROJECTILE; - proj.missile_flags = MIF_SPLASH; - - CSQCProjectile(proj, TRUE, (proj.cnt ? PROJECTILE_CRYLINK_BOUNCING : PROJECTILE_CRYLINK), TRUE); - - other = proj; MUTATOR_CALLHOOK(EditProjectile); - } - if(autocvar_g_balance_crylink_secondary_joinspread != 0 || autocvar_g_balance_crylink_secondary_jointime != 0) - { - self.crylink_lastgroup = proj; - W_Crylink_CheckLinks(proj); - self.crylink_waitrelease = 2; - } -} - -void spawnfunc_weapon_crylink (void) -{ - weapon_defaultspawnfunc(WEP_CRYLINK); -} - -float w_crylink(float req) -{ - float ammo_amount; - if (req == WR_AIM) - { - if (random() < 0.10) - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_crylink_primary_speed, 0, autocvar_g_balance_crylink_primary_middle_lifetime, FALSE); - else - self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_crylink_secondary_speed, 0, autocvar_g_balance_crylink_secondary_middle_lifetime, FALSE); - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - - if (self.BUTTON_ATCK) - { - if (self.crylink_waitrelease != 1) - if (weapon_prepareattack(0, autocvar_g_balance_crylink_primary_refire)) - { - W_Crylink_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_crylink_primary_animtime, w_ready); - } - } - - if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary) - { - if (self.crylink_waitrelease != 2) - if (weapon_prepareattack(1, autocvar_g_balance_crylink_secondary_refire)) - { - W_Crylink_Attack2(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_crylink_secondary_animtime, w_ready); - } - } - - if ((self.crylink_waitrelease == 1 && !self.BUTTON_ATCK) || (self.crylink_waitrelease == 2 && !self.BUTTON_ATCK2)) - { - if (!self.crylink_lastgroup || time > self.crylink_lastgroup.teleport_time) - { - // fired and released now! - if(self.crylink_lastgroup) - { - vector pos; - entity linkjoineffect; - - if(self.crylink_waitrelease == 1) - { - pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_primary_joinspread * autocvar_g_balance_crylink_primary_speed, autocvar_g_balance_crylink_primary_jointime); - - } - else - { - pos = W_Crylink_LinkJoin(self.crylink_lastgroup, autocvar_g_balance_crylink_secondary_joinspread * autocvar_g_balance_crylink_secondary_speed, autocvar_g_balance_crylink_secondary_jointime); - } - - linkjoineffect = spawn(); - linkjoineffect.think = W_Crylink_LinkJoinEffect_Think; - linkjoineffect.classname = "linkjoineffect"; - linkjoineffect.nextthink = time + w_crylink_linkjoin_time; - linkjoineffect.owner = self; - setorigin(linkjoineffect, pos); - } - self.crylink_waitrelease = 0; - if(!w_crylink(WR_CHECKAMMO1) && !w_crylink(WR_CHECKAMMO2)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - // ran out of ammo! - self.cnt = WEP_CRYLINK; - self.switchweapon = w_getbestweapon(self); - } - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_crylink.md3"); - precache_model ("models/weapons/v_crylink.md3"); - precache_model ("models/weapons/h_crylink.iqm"); - precache_sound ("weapons/crylink_fire.wav"); - precache_sound ("weapons/crylink_fire2.wav"); - precache_sound ("weapons/crylink_linkjoin.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_CRYLINK); - self.current_ammo = ammo_cells; - } - else if (req == WR_CHECKAMMO1) - { - // don't "run out of ammo" and switch weapons while waiting for release - if(self.crylink_lastgroup && self.crylink_waitrelease) - return TRUE; - - ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_primary_ammo; - ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - // don't "run out of ammo" and switch weapons while waiting for release - if(self.crylink_lastgroup && self.crylink_waitrelease) - return TRUE; - - ammo_amount = self.ammo_cells >= autocvar_g_balance_crylink_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_CRYLINK]) >= autocvar_g_balance_crylink_secondary_ammo; - return ammo_amount; - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_crylink_primary_ammo, autocvar_g_balance_crylink_secondary_ammo), autocvar_g_balance_crylink_reload_ammo, autocvar_g_balance_crylink_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_CRYLINK_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - return WEAPON_CRYLINK_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_crylink(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 2; - if(w_deathtype & HITTYPE_SECONDARY) - { - pointparticles(particleeffectnum("crylink_impact"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/crylink_impact2.wav", VOL_BASE, ATTN_NORM); - } - else - { - pointparticles(particleeffectnum("crylink_impactbig"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/crylink_impact.wav", VOL_BASE, ATTN_NORM); - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/crylink_impact2.wav"); - precache_sound("weapons/crylink_impact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_electro.qc b/qcsrc/server/w_electro.qc deleted file mode 100644 index bfd9ebec8..000000000 --- a/qcsrc/server/w_electro.qc +++ /dev/null @@ -1,619 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ ELECTRO, -/* function */ w_electro, -/* ammotype */ IT_CELLS, -/* impulse */ 5, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "electro", -/* shortname */ "electro", -/* fullname */ _("Electro") -); -#else -#ifdef SVQC -.float electro_count; -.float electro_secondarytime; - -void W_Plasma_Explode_Combo (void); - -void W_Plasma_TriggerCombo(vector org, float rad, entity own) -{ - entity e; - e = WarpZone_FindRadius(org, rad, TRUE); - while (e) - { - if (e.classname == "plasma") - { - // change owner to whoever caused the combo explosion - e.realowner = own; - e.takedamage = DAMAGE_NO; - e.classname = "plasma_chain"; - e.think = W_Plasma_Explode_Combo; - e.nextthink = time + vlen(e.WarpZone_findradius_dist) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler - } - e = e.chain; - } -} - -void W_Plasma_Explode (void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - if (self.movetype == MOVETYPE_BOUNCE) - { - RadiusDamage (self, self.realowner, autocvar_g_balance_electro_secondary_damage, autocvar_g_balance_electro_secondary_edgedamage, autocvar_g_balance_electro_secondary_radius, world, world, autocvar_g_balance_electro_secondary_force, self.projectiledeathtype, other); - } - else - { - W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_primary_comboradius, self.realowner); - RadiusDamage (self, self.realowner, autocvar_g_balance_electro_primary_damage, autocvar_g_balance_electro_primary_edgedamage, autocvar_g_balance_electro_primary_radius, world, world, autocvar_g_balance_electro_primary_force, self.projectiledeathtype, other); - } - - remove (self); -} - -void W_Plasma_Explode_Combo (void) -{ - W_Plasma_TriggerCombo(self.origin, autocvar_g_balance_electro_combo_comboradius, self.realowner); - - self.event_damage = func_null; - RadiusDamage (self, self.realowner, autocvar_g_balance_electro_combo_damage, autocvar_g_balance_electro_combo_edgedamage, autocvar_g_balance_electro_combo_radius, world, world, autocvar_g_balance_electro_combo_force, WEP_ELECTRO | HITTYPE_BOUNCE, world); // use THIS type for a combo because primary can't bounce - - remove (self); -} - -void W_Plasma_Touch (void) -{ - //self.velocity = self.velocity * 0.1; - - PROJECTILE_TOUCH; - if (other.takedamage == DAMAGE_AIM) { - W_Plasma_Explode (); - } else { - //UpdateCSQCProjectile(self); - spamsound (self, CH_SHOTS, "weapons/electro_bounce.wav", VOL_BASE, ATTN_NORM); - self.projectiledeathtype |= HITTYPE_BOUNCE; - } -} - -void W_Plasma_TouchExplode (void) -{ - PROJECTILE_TOUCH; - W_Plasma_Explode (); -} - -void W_Plasma_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if(self.health <= 0) - return; - - // note: combos are usually triggered by W_Plasma_TriggerCombo, not damage - float is_combo = (inflictor.classname == "plasma_chain" || inflictor.classname == "plasma_prim"); - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_combo ? 1 : -1))) - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - if (self.health <= 0) - { - self.takedamage = DAMAGE_NO; - self.nextthink = time; - if (is_combo) - { - // change owner to whoever caused the combo explosion - self.realowner = inflictor.realowner; - self.classname = "plasma_chain"; - self.think = W_Plasma_Explode_Combo; - self.nextthink = time + min(autocvar_g_balance_electro_combo_radius, vlen(self.origin - inflictor.origin)) / autocvar_g_balance_electro_combo_speed; // delay combo chains, looks cooler - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ bounding the length, because inflictor may be in a galaxy far far away (warpzones) - } - else - { - self.use = W_Plasma_Explode; - self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately" - } - } -} - -void W_Electro_Attack() -{ - entity proj; - - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_reload_ammo); - - W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', FALSE, 2, "weapons/electro_fire.wav", CH_WEAPON_A, autocvar_g_balance_electro_primary_damage); - - pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - proj = spawn (); - proj.classname = "plasma_prim"; - proj.owner = proj.realowner = self; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = autocvar_g_balance_electro_primary_damage; - proj.use = W_Plasma_Explode; - proj.think = adaptor_think2use_hittype_splash; - proj.nextthink = time + autocvar_g_balance_electro_primary_lifetime; - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_ELECTRO; - setorigin(proj, w_shotorg); - - proj.movetype = MOVETYPE_FLY; - W_SETUPPROJECTILEVELOCITY(proj, g_balance_electro_primary); - proj.angles = vectoangles(proj.velocity); - proj.touch = W_Plasma_TouchExplode; - setsize(proj, '0 0 -3', '0 0 -3'); - proj.flags = FL_PROJECTILE; - proj.missile_flags = MIF_SPLASH; - - CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO_BEAM, TRUE); - - other = proj; MUTATOR_CALLHOOK(EditProjectile); -} - -void W_Electro_Attack2() -{ - entity proj; - - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_electro_secondary_ammo, autocvar_g_balance_electro_reload_ammo); - - W_SetupShot_ProjectileSize (self, '0 0 -4', '0 0 -4', FALSE, 2, "weapons/electro_fire2.wav", CH_WEAPON_A, autocvar_g_balance_electro_secondary_damage); - - w_shotdir = v_forward; // no TrueAim for grenades please - - pointparticles(particleeffectnum("electro_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - proj = spawn (); - proj.classname = "plasma"; - proj.owner = proj.realowner = self; - proj.use = W_Plasma_Explode; - proj.think = adaptor_think2use_hittype_splash; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = autocvar_g_balance_electro_secondary_damage; - proj.nextthink = time + autocvar_g_balance_electro_secondary_lifetime; - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_ELECTRO | HITTYPE_SECONDARY; - setorigin(proj, w_shotorg); - - //proj.glow_size = 50; - //proj.glow_color = 45; - proj.movetype = MOVETYPE_BOUNCE; - W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_electro_secondary); - proj.touch = W_Plasma_Touch; - setsize(proj, '0 0 -4', '0 0 -4'); - proj.takedamage = DAMAGE_YES; - proj.damageforcescale = autocvar_g_balance_electro_secondary_damageforcescale; - proj.health = autocvar_g_balance_electro_secondary_health; - proj.event_damage = W_Plasma_Damage; - proj.flags = FL_PROJECTILE; - proj.damagedbycontents = (autocvar_g_balance_electro_secondary_damagedbycontents); - - proj.bouncefactor = autocvar_g_balance_electro_secondary_bouncefactor; - proj.bouncestop = autocvar_g_balance_electro_secondary_bouncestop; - proj.missile_flags = MIF_SPLASH | MIF_ARC; - -#if 0 - entity p2; - p2 = spawn(); - copyentity(proj, p2); - setmodel(p2, "models/ebomb.mdl"); - setsize(p2, proj.mins, proj.maxs); -#endif - - CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound - - other = proj; MUTATOR_CALLHOOK(EditProjectile); -} - -.vector hook_start, hook_end; -float lgbeam_send(entity to, float sf) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_ELECTRO_BEAM); - sf = sf & 0x7F; - if(sound_allowed(MSG_BROADCAST, self.realowner)) - sf |= 0x80; - WriteByte(MSG_ENTITY, sf); - if(sf & 1) - { - WriteByte(MSG_ENTITY, num_for_edict(self.realowner)); - WriteCoord(MSG_ENTITY, autocvar_g_balance_electro_primary_range); - } - if(sf & 2) - { - WriteCoord(MSG_ENTITY, self.hook_start_x); - WriteCoord(MSG_ENTITY, self.hook_start_y); - WriteCoord(MSG_ENTITY, self.hook_start_z); - } - if(sf & 4) - { - WriteCoord(MSG_ENTITY, self.hook_end_x); - WriteCoord(MSG_ENTITY, self.hook_end_y); - WriteCoord(MSG_ENTITY, self.hook_end_z); - } - return TRUE; -} -.entity lgbeam; -.float prevlgfire; -float lgbeam_checkammo() -{ - if(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO) - return TRUE; - else if(autocvar_g_balance_electro_reload_ammo) - return self.realowner.clip_load > 0; - else - return self.realowner.ammo_cells > 0; -} - -entity lgbeam_owner_ent; -void lgbeam_think() -{ - entity owner_player; - owner_player = self.realowner; - - owner_player.prevlgfire = time; - if (self != owner_player.lgbeam) - { - remove(self); - return; - } - - if (owner_player.weaponentity.state != WS_INUSE || !lgbeam_checkammo() || owner_player.deadflag != DEAD_NO || !owner_player.BUTTON_ATCK || owner_player.freezetag_frozen) - { - if(self == owner_player.lgbeam) - owner_player.lgbeam = world; - remove(self); - return; - } - - self.nextthink = time; - - makevectors(owner_player.v_angle); - - float dt, f; - dt = frametime; - - // if this weapon is reloadable, decrease its load. Else decrease the player's ammo - if not(owner_player.items & IT_UNLIMITED_WEAPON_AMMO) - { - if(autocvar_g_balance_electro_primary_ammo) - { - if(autocvar_g_balance_electro_reload_ammo) - { - dt = min(dt, owner_player.clip_load / autocvar_g_balance_electro_primary_ammo); - owner_player.clip_load = max(0, owner_player.clip_load - autocvar_g_balance_electro_primary_ammo * frametime); - owner_player.(weapon_load[WEP_ELECTRO]) = owner_player.clip_load; - } - else - { - dt = min(dt, owner_player.ammo_cells / autocvar_g_balance_electro_primary_ammo); - owner_player.ammo_cells = max(0, owner_player.ammo_cells - autocvar_g_balance_electro_primary_ammo * frametime); - } - } - } - - W_SetupShot_Range(owner_player, TRUE, 0, "", 0, autocvar_g_balance_electro_primary_damage * dt, autocvar_g_balance_electro_primary_range); - if(!lgbeam_owner_ent) - { - lgbeam_owner_ent = spawn(); - lgbeam_owner_ent.classname = "lgbeam_owner_ent"; - } - WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(owner_player)); - - // apply the damage - if(trace_ent) - { - vector force; - force = w_shotdir * autocvar_g_balance_electro_primary_force + '0 0 1' * autocvar_g_balance_electro_primary_force_up; - - f = ExponentialFalloff(autocvar_g_balance_electro_primary_falloff_mindist, autocvar_g_balance_electro_primary_falloff_maxdist, autocvar_g_balance_electro_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg)); - - if(accuracy_isgooddamage(owner_player, trace_ent)) - accuracy_add(owner_player, WEP_ELECTRO, 0, autocvar_g_balance_electro_primary_damage * dt * f); - Damage (trace_ent, owner_player, owner_player, autocvar_g_balance_electro_primary_damage * dt * f, WEP_ELECTRO, trace_endpos, force * dt); - } - W_Plasma_TriggerCombo(trace_endpos, autocvar_g_balance_electro_primary_comboradius, owner_player); - - // draw effect - if(w_shotorg != self.hook_start) - { - self.SendFlags |= 2; - self.hook_start = w_shotorg; - } - if(w_shotend != self.hook_end) - { - self.SendFlags |= 4; - self.hook_end = w_shotend; - } -} - -// experimental lightning gun -void W_Electro_Attack3 (void) -{ - // only play fire sound if 0.5 sec has passed since player let go the fire button - if(time - self.prevlgfire > 0.5) - sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM); - - entity beam, oldself; - - self.lgbeam = beam = spawn(); - beam.classname = "lgbeam"; - beam.solid = SOLID_NOT; - beam.think = lgbeam_think; - beam.owner = beam.realowner = self; - beam.movetype = MOVETYPE_NONE; - beam.shot_spread = 0; - beam.bot_dodge = TRUE; - beam.bot_dodgerating = autocvar_g_balance_electro_primary_damage; - Net_LinkEntity(beam, FALSE, 0, lgbeam_send); - - oldself = self; - self = beam; - self.think(); - self = oldself; -} - -void ElectroInit() -{ - weapon_action(WEP_ELECTRO, WR_PRECACHE); - electro_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 1); - electro_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 2); - electro_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 3); - electro_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ELECTRO), FALSE, FALSE, 4); -} - -void spawnfunc_weapon_electro (void) -{ - weapon_defaultspawnfunc(WEP_ELECTRO); -} - -void w_electro_checkattack() -{ - if(self.electro_count > 1) - if(self.BUTTON_ATCK2) - if(weapon_prepareattack(1, -1)) - { - W_Electro_Attack2(); - self.electro_count -= 1; - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack); - return; - } - - w_ready(); -} - -.float bot_secondary_electromooth; -.float BUTTON_ATCK_prev; -float w_electro(float req) -{ - float ammo_amount; - if (req == WR_AIM) - { - self.BUTTON_ATCK=FALSE; - self.BUTTON_ATCK2=FALSE; - if(vlen(self.origin-self.enemy.origin) > 1000) - self.bot_secondary_electromooth = 0; - if(self.bot_secondary_electromooth == 0) - { - float shoot; - - if(autocvar_g_balance_electro_primary_speed) - shoot = bot_aim(autocvar_g_balance_electro_primary_speed, 0, autocvar_g_balance_electro_primary_lifetime, FALSE); - else - shoot = bot_aim(1000000, 0, 0.001, FALSE); - - if(shoot) - { - self.BUTTON_ATCK = TRUE; - if(random() < 0.01) self.bot_secondary_electromooth = 1; - } - } - else - { - if(bot_aim(autocvar_g_balance_electro_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_electro_secondary_lifetime, TRUE)) - { - self.BUTTON_ATCK2 = TRUE; - if(random() < 0.03) self.bot_secondary_electromooth = 0; - } - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_electro_reload_ammo) // forced reload - { - ammo_amount = 0; - if(autocvar_g_balance_electro_lightning) - { - if(self.clip_load > 0) - ammo_amount = 1; - } - else if(self.clip_load >= autocvar_g_balance_electro_primary_ammo) - ammo_amount = 1; - if(self.clip_load >= autocvar_g_balance_electro_secondary_ammo) - ammo_amount += 1; - - if(!ammo_amount) - { - weapon_action(self.weapon, WR_RELOAD); - return FALSE; - } - } - if (self.BUTTON_ATCK) - { - if(autocvar_g_balance_electro_lightning) - if(self.BUTTON_ATCK_prev) - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); - - if (weapon_prepareattack(0, (autocvar_g_balance_electro_lightning ? 0 : autocvar_g_balance_electro_primary_refire))) - { - if(autocvar_g_balance_electro_lightning) - { - if ((!self.lgbeam) || wasfreed(self.lgbeam)) - { - W_Electro_Attack3(); - } - if(!self.BUTTON_ATCK_prev) - { - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); - self.BUTTON_ATCK_prev = 1; - } - } - else - { - W_Electro_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); - } - } - } else { - if(autocvar_g_balance_electro_lightning) - { - if (self.BUTTON_ATCK_prev != 0) - { - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_electro_primary_animtime, w_ready); - ATTACK_FINISHED(self) = time + autocvar_g_balance_electro_primary_refire * W_WeaponRateFactor(); - } - self.BUTTON_ATCK_prev = 0; - } - - if (self.BUTTON_ATCK2) - { - if (time >= self.electro_secondarytime) - if (weapon_prepareattack(1, autocvar_g_balance_electro_secondary_refire)) - { - W_Electro_Attack2(); - self.electro_count = autocvar_g_balance_electro_secondary_count; - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_electro_secondary_animtime, w_electro_checkattack); - self.electro_secondarytime = time + autocvar_g_balance_electro_secondary_refire2 * W_WeaponRateFactor(); - } - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_electro.md3"); - precache_model ("models/weapons/v_electro.md3"); - precache_model ("models/weapons/h_electro.iqm"); - precache_sound ("weapons/electro_bounce.wav"); - precache_sound ("weapons/electro_fire.wav"); - precache_sound ("weapons/electro_fire2.wav"); - precache_sound ("weapons/electro_impact.wav"); - precache_sound ("weapons/electro_impact_combo.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - if(autocvar_g_balance_electro_lightning) - { - precache_sound ("weapons/lgbeam_fire.wav"); - } - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_ELECTRO); - self.current_ammo = ammo_cells; - } - else if (req == WR_CHECKAMMO1) - { - if(autocvar_g_balance_electro_lightning) - { - if(!autocvar_g_balance_electro_primary_ammo) - ammo_amount = 1; - else - ammo_amount = self.ammo_cells > 0; - ammo_amount += self.(weapon_load[WEP_ELECTRO]) > 0; - } - else - { - ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_primary_ammo; - ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_primary_ammo; - } - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - if(autocvar_g_balance_electro_combo_safeammocheck) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false. - { - ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo; - ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo + autocvar_g_balance_electro_primary_ammo; - } - else - { - ammo_amount = self.ammo_cells >= autocvar_g_balance_electro_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_ELECTRO]) >= autocvar_g_balance_electro_secondary_ammo; - } - return ammo_amount; - } - else if (req == WR_RESETPLAYER) - { - self.electro_secondarytime = time; - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_electro_primary_ammo, autocvar_g_balance_electro_secondary_ammo), autocvar_g_balance_electro_reload_ammo, autocvar_g_balance_electro_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_ELECTRO_SUICIDE_ORBS; - else - return WEAPON_ELECTRO_SUICIDE_BOLT; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - { - return WEAPON_ELECTRO_MURDER_ORBS; - } - else - { - if(w_deathtype & HITTYPE_BOUNCE) - return WEAPON_ELECTRO_MURDER_COMBO; - else - return WEAPON_ELECTRO_MURDER_BOLT; - } - } - return TRUE; -} -#endif -#ifdef CSQC -float w_electro(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - if(w_deathtype & HITTYPE_SECONDARY) - { - pointparticles(particleeffectnum("electro_ballexplode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM); - } - else - { - if(w_deathtype & HITTYPE_BOUNCE) - { - // this is sent as "primary (w_deathtype & HITTYPE_BOUNCE)" to distinguish it from (w_deathtype & HITTYPE_SECONDARY) bounced balls - pointparticles(particleeffectnum("electro_combo"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/electro_impact_combo.wav", VOL_BASE, ATTN_NORM); - } - else - { - pointparticles(particleeffectnum("electro_impact"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_NORM); - } - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/electro_impact.wav"); - precache_sound("weapons/electro_impact_combo.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_electro.qh b/qcsrc/server/w_electro.qh deleted file mode 100644 index 98c0be13e..000000000 --- a/qcsrc/server/w_electro.qh +++ /dev/null @@ -1,2 +0,0 @@ -void ElectroInit(); -vector electro_shotorigin[4]; diff --git a/qcsrc/server/w_fireball.qc b/qcsrc/server/w_fireball.qc deleted file mode 100644 index 3d84e8e7d..000000000 --- a/qcsrc/server/w_fireball.qc +++ /dev/null @@ -1,434 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ FIREBALL, -/* function */ w_fireball, -/* ammotype */ 0, -/* impulse */ 9, -/* flags */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "fireball", -/* shortname */ "fireball", -/* fullname */ _("Fireball") -); -#else -#ifdef SVQC -.float bot_primary_fireballmooth; // whatever a mooth is -.vector fireball_impactvec; -.float fireball_primarytime; - -void W_Fireball_Explode (void) -{ - entity e; - float dist; - float points; - vector dir; - float d; - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - // 1. dist damage - d = (self.realowner.health + self.realowner.armorvalue); - RadiusDamage (self, self.realowner, autocvar_g_balance_fireball_primary_damage, autocvar_g_balance_fireball_primary_edgedamage, autocvar_g_balance_fireball_primary_radius, world, world, autocvar_g_balance_fireball_primary_force, self.projectiledeathtype, other); - if(self.realowner.health + self.realowner.armorvalue >= d) - if(!self.cnt) - { - modeleffect_spawn("models/sphere/sphere.md3", 0, 0, self.origin, '0 0 0', '0 0 0', '0 0 0', 0, autocvar_g_balance_fireball_primary_bfgradius, 0.2, 0.05, 0.25); - - // 2. bfg effect - // NOTE: this cannot be made warpzone aware by design. So, better intentionally ignore warpzones here. - for(e = findradius(self.origin, autocvar_g_balance_fireball_primary_bfgradius); e; e = e.chain) - if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self)) - { - // can we see fireball? - traceline(e.origin + e.view_ofs, self.origin, MOVE_NORMAL, e); - if(/* trace_startsolid || */ trace_fraction != 1) // startsolid should be never happening anyway - continue; - // can we see player who shot fireball? - traceline(e.origin + e.view_ofs, self.realowner.origin + self.realowner.view_ofs, MOVE_NORMAL, e); - if(trace_ent != self.realowner) - if(/* trace_startsolid || */ trace_fraction != 1) - continue; - dist = vlen(self.origin - e.origin - e.view_ofs); - points = (1 - sqrt(dist / autocvar_g_balance_fireball_primary_bfgradius)); - if(points <= 0) - continue; - dir = normalize(e.origin + e.view_ofs - self.origin); - - if(accuracy_isgooddamage(self.realowner, e)) - accuracy_add(self.realowner, WEP_FIREBALL, 0, autocvar_g_balance_fireball_primary_bfgdamage * points); - - Damage(e, self, self.realowner, autocvar_g_balance_fireball_primary_bfgdamage * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, autocvar_g_balance_fireball_primary_bfgforce * dir); - pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1); - } - } - - remove (self); -} - -void W_Fireball_TouchExplode (void) -{ - PROJECTILE_TOUCH; - W_Fireball_Explode (); -} - -void W_Fireball_LaserPlay(float dt, float dist, float damage, float edgedamage, float burntime) -{ - entity e; - float d; - vector p; - - if(damage <= 0) - return; - - RandomSelection_Init(); - for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain) - if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || IsDifferentTeam(e, self)) - { - p = e.origin; - p_x += e.mins_x + random() * (e.maxs_x - e.mins_x); - p_y += e.mins_y + random() * (e.maxs_y - e.mins_y); - p_z += e.mins_z + random() * (e.maxs_z - e.mins_z); - d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p); - if(d < dist) - { - e.fireball_impactvec = p; - RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e)); - } - } - if(RandomSelection_chosen_ent) - { - d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec); - d = damage + (edgedamage - damage) * (d / dist); - Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE); - //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec); - pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); - } -} - -void W_Fireball_Think() -{ - if(time > self.pushltime) - { - self.cnt = 1; - self.projectiledeathtype |= HITTYPE_SPLASH; - W_Fireball_Explode(); - return; - } - - W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_primary_laserradius, autocvar_g_balance_fireball_primary_laserdamage, autocvar_g_balance_fireball_primary_laseredgedamage, autocvar_g_balance_fireball_primary_laserburntime); - - self.nextthink = time + 0.1; -} - -void W_Fireball_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if(self.health <= 0) - return; - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - if (self.health <= 0) - { - self.cnt = 1; - W_PrepareExplosionByDamage(attacker, W_Fireball_Explode); - } -} - -void W_Fireball_Attack1() -{ - entity proj; - - W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 2, "weapons/fireball_fire2.wav", CH_WEAPON_A, autocvar_g_balance_fireball_primary_damage + autocvar_g_balance_fireball_primary_bfgdamage); - - pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - proj = spawn (); - proj.classname = "plasma_prim"; - proj.owner = proj.realowner = self; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = autocvar_g_balance_fireball_primary_damage; - proj.pushltime = time + autocvar_g_balance_fireball_primary_lifetime; - proj.use = W_Fireball_Explode; - proj.think = W_Fireball_Think; - proj.nextthink = time; - proj.health = autocvar_g_balance_fireball_primary_health; - proj.team = self.team; - proj.event_damage = W_Fireball_Damage; - proj.takedamage = DAMAGE_YES; - proj.damageforcescale = autocvar_g_balance_fireball_primary_damageforcescale; - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_FIREBALL; - setorigin(proj, w_shotorg); - - proj.movetype = MOVETYPE_FLY; - W_SETUPPROJECTILEVELOCITY(proj, g_balance_fireball_primary); - proj.angles = vectoangles(proj.velocity); - proj.touch = W_Fireball_TouchExplode; - setsize(proj, '-16 -16 -16', '16 16 16'); - proj.flags = FL_PROJECTILE; - proj.missile_flags = MIF_SPLASH | MIF_PROXY; - - CSQCProjectile(proj, TRUE, PROJECTILE_FIREBALL, TRUE); - - other = proj; MUTATOR_CALLHOOK(EditProjectile); -} - -void W_Fireball_AttackEffect(float i, vector f_diff) -{ - W_SetupShot_ProjectileSize (self, '-16 -16 -16', '16 16 16', FALSE, 0, "", 0, 0); - w_shotorg += f_diff_x * v_up + f_diff_y * v_right; - pointparticles(particleeffectnum("fireball_preattack_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); -} - -void W_Fireball_Attack1_Frame4() -{ - W_Fireball_Attack1(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, w_ready); -} - -void W_Fireball_Attack1_Frame3() -{ - W_Fireball_AttackEffect(0, '+1.25 +3.75 0'); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame4); -} - -void W_Fireball_Attack1_Frame2() -{ - W_Fireball_AttackEffect(0, '-1.25 +3.75 0'); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame3); -} - -void W_Fireball_Attack1_Frame1() -{ - W_Fireball_AttackEffect(1, '+1.25 -3.75 0'); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame2); -} - -void W_Fireball_Attack1_Frame0() -{ - W_Fireball_AttackEffect(0, '-1.25 -3.75 0'); - sound (self, CH_WEAPON_SINGLE, "weapons/fireball_prefire2.wav", VOL_BASE, ATTN_NORM); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_fireball_primary_animtime, W_Fireball_Attack1_Frame1); -} - -void W_Firemine_Think() -{ - if(time > self.pushltime) - { - remove(self); - return; - } - - // make it "hot" once it leaves its owner - if(self.owner) - { - if(vlen(self.origin - self.owner.origin - self.owner.view_ofs) > autocvar_g_balance_fireball_secondary_laserradius) - { - self.cnt += 1; - if(self.cnt == 3) - self.owner = world; - } - else - self.cnt = 0; - } - - W_Fireball_LaserPlay(0.1, autocvar_g_balance_fireball_secondary_laserradius, autocvar_g_balance_fireball_secondary_laserdamage, autocvar_g_balance_fireball_secondary_laseredgedamage, autocvar_g_balance_fireball_secondary_laserburntime); - - self.nextthink = time + 0.1; -} - -void W_Firemine_Touch (void) -{ - PROJECTILE_TOUCH; - if (other.takedamage == DAMAGE_AIM) - if(Fire_AddDamage(other, self.realowner, autocvar_g_balance_fireball_secondary_damage, autocvar_g_balance_fireball_secondary_damagetime, self.projectiledeathtype) >= 0) - { - remove(self); - return; - } - self.projectiledeathtype |= HITTYPE_BOUNCE; -} - -void W_Fireball_Attack2() -{ - entity proj; - vector f_diff; - float c; - - c = mod(self.bulletcounter, 4); - switch(c) - { - case 0: - f_diff = '-1.25 -3.75 0'; - break; - case 1: - f_diff = '+1.25 -3.75 0'; - break; - case 2: - f_diff = '-1.25 +3.75 0'; - break; - case 3: - default: - f_diff = '+1.25 +3.75 0'; - break; - } - W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 2, "weapons/fireball_fire.wav", CH_WEAPON_A, autocvar_g_balance_fireball_secondary_damage); - traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self); - w_shotorg = trace_endpos; - - pointparticles(particleeffectnum("fireball_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - proj = spawn (); - proj.owner = proj.realowner = self; - proj.classname = "grenade"; - proj.bot_dodge = TRUE; - proj.bot_dodgerating = autocvar_g_balance_fireball_secondary_damage; - proj.movetype = MOVETYPE_BOUNCE; - proj.projectiledeathtype = WEP_FIREBALL | HITTYPE_SECONDARY; - proj.touch = W_Firemine_Touch; - PROJECTILE_MAKETRIGGER(proj); - setsize(proj, '-4 -4 -4', '4 4 4'); - setorigin(proj, w_shotorg); - proj.think = W_Firemine_Think; - proj.nextthink = time; - proj.damageforcescale = autocvar_g_balance_fireball_secondary_damageforcescale; - proj.pushltime = time + autocvar_g_balance_fireball_secondary_lifetime; - W_SETUPPROJECTILEVELOCITY_UP(proj, g_balance_fireball_secondary); - - proj.angles = vectoangles(proj.velocity); - proj.flags = FL_PROJECTILE; - proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; - - CSQCProjectile(proj, TRUE, PROJECTILE_FIREMINE, TRUE); - - other = proj; MUTATOR_CALLHOOK(EditProjectile); -} - -void spawnfunc_weapon_fireball (void) -{ - weapon_defaultspawnfunc(WEP_FIREBALL); -} - -float w_fireball(float req) -{ - //float ammo_amount; - if (req == WR_AIM) - { - self.BUTTON_ATCK = FALSE; - self.BUTTON_ATCK2 = FALSE; - if (self.bot_primary_fireballmooth == 0) - { - if(bot_aim(autocvar_g_balance_fireball_primary_speed, 0, autocvar_g_balance_fireball_primary_lifetime, FALSE)) - { - self.BUTTON_ATCK = TRUE; - if(random() < 0.02) self.bot_primary_fireballmooth = 0; - } - } - else - { - if(bot_aim(autocvar_g_balance_fireball_secondary_speed, autocvar_g_balance_fireball_secondary_speed_up, autocvar_g_balance_fireball_secondary_lifetime, TRUE)) - { - self.BUTTON_ATCK2 = TRUE; - if(random() < 0.01) self.bot_primary_fireballmooth = 1; - } - } - } - else if (req == WR_THINK) - { - if (self.BUTTON_ATCK) - { - if (time >= self.fireball_primarytime) - if (weapon_prepareattack(0, autocvar_g_balance_fireball_primary_refire)) - { - W_Fireball_Attack1_Frame0(); - self.fireball_primarytime = time + autocvar_g_balance_fireball_primary_refire2 * W_WeaponRateFactor(); - } - } - else if (self.BUTTON_ATCK2) - { - if (weapon_prepareattack(1, autocvar_g_balance_fireball_secondary_refire)) - { - W_Fireball_Attack2(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_fireball_secondary_animtime, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_fireball.md3"); - precache_model ("models/weapons/v_fireball.md3"); - precache_model ("models/weapons/h_fireball.iqm"); - precache_model ("models/sphere/sphere.md3"); - precache_sound ("weapons/fireball_fire.wav"); - precache_sound ("weapons/fireball_fire2.wav"); - precache_sound ("weapons/fireball_prefire2.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_FIREBALL); - self.current_ammo = ammo_none; - } - else if (req == WR_CHECKAMMO1) - { - return 1; - } - else if (req == WR_CHECKAMMO2) - { - return 1; - } - else if (req == WR_RESETPLAYER) - { - self.fireball_primarytime = time; - } - else if (req == WR_SUICIDEMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_FIREBALL_SUICIDE_FIREMINE; - else - return WEAPON_FIREBALL_SUICIDE_BLAST; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - { - return WEAPON_FIREBALL_MURDER_FIREMINE; - } - else - { - return WEAPON_FIREBALL_MURDER_BLAST; - } - } - return TRUE; -} -#endif -#ifdef CSQC -float w_fireball(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - if(w_deathtype & HITTYPE_SECONDARY) - { - // firemine goes out silently - } - else - { - org2 = w_org + w_backoff * 16; - pointparticles(particleeffectnum("fireball_explode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/fireball_impact2.wav", VOL_BASE, ATTN_NORM * 0.25); // long range boom - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/fireball_impact2.wav"); - } - - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_grenadelauncher.qc b/qcsrc/server/w_grenadelauncher.qc deleted file mode 100644 index 8b8a1a062..000000000 --- a/qcsrc/server/w_grenadelauncher.qc +++ /dev/null @@ -1,414 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ GRENADE_LAUNCHER, -/* function */ w_glauncher, -/* ammotype */ IT_ROCKETS, -/* impulse */ 4, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "gl", -/* shortname */ "grenadelauncher", -/* fullname */ _("Mortar") -); -#else -#ifdef SVQC -.float gl_detonate_later; -.float gl_bouncecnt; - -void W_Grenade_Explode (void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - if(self.movetype == MOVETYPE_NONE) - self.velocity = self.oldvelocity; - - RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_primary_damage, autocvar_g_balance_grenadelauncher_primary_edgedamage, autocvar_g_balance_grenadelauncher_primary_radius, world, world, autocvar_g_balance_grenadelauncher_primary_force, self.projectiledeathtype, other); - - remove (self); -} - -void W_Grenade_Explode2 (void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - if(self.movetype == MOVETYPE_NONE) - self.velocity = self.oldvelocity; - - RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_secondary_damage, autocvar_g_balance_grenadelauncher_secondary_edgedamage, autocvar_g_balance_grenadelauncher_secondary_radius, world, world, autocvar_g_balance_grenadelauncher_secondary_force, self.projectiledeathtype, other); - - remove (self); -} - -void W_Grenade_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - - if (self.health <= 0) - W_PrepareExplosionByDamage(attacker, self.use); -} - -void W_Grenade_Think1 (void) -{ - self.nextthink = time; - if (time > self.cnt) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_Grenade_Explode (); - return; - } - if(self.gl_detonate_later && self.gl_bouncecnt >= autocvar_g_balance_grenadelauncher_primary_remote_minbouncecnt) - W_Grenade_Explode(); -} - -void W_Grenade_Touch1 (void) -{ - PROJECTILE_TOUCH; - if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_primary_type == 0) // always explode when hitting a player, or if normal mortar projectile - { - self.use (); - } - else if (autocvar_g_balance_grenadelauncher_primary_type == 1) // bounce - { - float r; - r = random() * 6; - if(r < 1) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); - else if(r < 2) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); - else if(r < 3) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); - else if(r < 4) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); - else if(r < 5) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); - else - spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); - self.projectiledeathtype |= HITTYPE_BOUNCE; - self.gl_bouncecnt += 1; - } - else if(autocvar_g_balance_grenadelauncher_primary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick - { - spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); - - // let it stick whereever it is - self.oldvelocity = self.velocity; - self.velocity = '0 0 0'; - self.movetype = MOVETYPE_NONE; // also disables gravity - self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO - UpdateCSQCProjectile(self); - - // do not respond to any more touches - self.solid = SOLID_NOT; - - self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_primary_lifetime_stick); - } -} - -void W_Grenade_Touch2 (void) -{ - PROJECTILE_TOUCH; - if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_secondary_type == 0) // always explode when hitting a player, or if normal mortar projectile - { - self.use (); - } - else if (autocvar_g_balance_grenadelauncher_secondary_type == 1) // bounce - { - float r; - r = random() * 6; - if(r < 1) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); - else if(r < 2) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); - else if(r < 3) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); - else if(r < 4) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); - else if(r < 5) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); - else - spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); - self.projectiledeathtype |= HITTYPE_BOUNCE; - self.gl_bouncecnt += 1; - - if (autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce && self.gl_bouncecnt == 1) - self.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce; - - } - else if(autocvar_g_balance_grenadelauncher_secondary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick - { - spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); - - // let it stick whereever it is - self.oldvelocity = self.velocity; - self.velocity = '0 0 0'; - self.movetype = MOVETYPE_NONE; // also disables gravity - self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO - UpdateCSQCProjectile(self); - - // do not respond to any more touches - self.solid = SOLID_NOT; - - self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_secondary_lifetime_stick); - } -} - -void W_Grenade_Attack (void) -{ - entity gren; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_primary_damage); - w_shotdir = v_forward; // no TrueAim for grenades please - - pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - gren = spawn (); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = TRUE; - gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_primary_damage; - gren.movetype = MOVETYPE_BOUNCE; - gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; - gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_GRENADE_LAUNCHER; - setorigin(gren, w_shotorg); - setsize(gren, '-3 -3 -3', '3 3 3'); - - gren.cnt = time + autocvar_g_balance_grenadelauncher_primary_lifetime; - gren.nextthink = time; - gren.think = W_Grenade_Think1; - gren.use = W_Grenade_Explode; - gren.touch = W_Grenade_Touch1; - - gren.takedamage = DAMAGE_YES; - gren.health = autocvar_g_balance_grenadelauncher_primary_health; - gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale; - gren.event_damage = W_Grenade_Damage; - gren.damagedbycontents = TRUE; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary); - - gren.angles = vectoangles (gren.velocity); - gren.flags = FL_PROJECTILE; - - if(autocvar_g_balance_grenadelauncher_primary_type == 0 || autocvar_g_balance_grenadelauncher_primary_type == 2) - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); - else - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); - - other = gren; MUTATOR_CALLHOOK(EditProjectile); -} - -void W_Grenade_Attack2 (void) -{ - entity gren; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_secondary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_secondary_damage); - w_shotdir = v_forward; // no TrueAim for grenades please - - pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - gren = spawn (); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = TRUE; - gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_secondary_damage; - gren.movetype = MOVETYPE_BOUNCE; - gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; - gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY; - setorigin(gren, w_shotorg); - setsize(gren, '-3 -3 -3', '3 3 3'); - - gren.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime; - gren.think = adaptor_think2use_hittype_splash; - gren.use = W_Grenade_Explode2; - gren.touch = W_Grenade_Touch2; - - gren.takedamage = DAMAGE_YES; - gren.health = autocvar_g_balance_grenadelauncher_secondary_health; - gren.damageforcescale = autocvar_g_balance_grenadelauncher_secondary_damageforcescale; - gren.event_damage = W_Grenade_Damage; - gren.damagedbycontents = TRUE; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_secondary); - - gren.angles = vectoangles (gren.velocity); - gren.flags = FL_PROJECTILE; - - if(autocvar_g_balance_grenadelauncher_secondary_type == 0 || autocvar_g_balance_grenadelauncher_secondary_type == 2) - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); - else - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); - - other = gren; MUTATOR_CALLHOOK(EditProjectile); -} - -void spawnfunc_weapon_grenadelauncher (void) -{ - weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER); -} - -.float bot_secondary_grenademooth; -float w_glauncher(float req) -{ - entity nade; - float nadefound; - float ammo_amount; - - if (req == WR_AIM) - { - self.BUTTON_ATCK = FALSE; - self.BUTTON_ATCK2 = FALSE; - if (self.bot_secondary_grenademooth == 0) - { - if(bot_aim(autocvar_g_balance_grenadelauncher_primary_speed, autocvar_g_balance_grenadelauncher_primary_speed_up, autocvar_g_balance_grenadelauncher_primary_lifetime, TRUE)) - { - self.BUTTON_ATCK = TRUE; - if(random() < 0.01) self.bot_secondary_grenademooth = 1; - } - } - else - { - if(bot_aim(autocvar_g_balance_grenadelauncher_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_grenadelauncher_secondary_lifetime, TRUE)) - { - self.BUTTON_ATCK2 = TRUE; - if(random() < 0.02) self.bot_secondary_grenademooth = 0; - } - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_grenadelauncher_reload_ammo && self.clip_load < min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if (self.BUTTON_ATCK) - { - if (weapon_prepareattack(0, autocvar_g_balance_grenadelauncher_primary_refire)) - { - W_Grenade_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_grenadelauncher_primary_animtime, w_ready); - } - } - else if (self.BUTTON_ATCK2) - { - if (cvar("g_balance_grenadelauncher_secondary_remote_detonateprimary")) - { - nadefound = 0; - for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self) - { - if(!nade.gl_detonate_later) - { - nade.gl_detonate_later = TRUE; - nadefound = 1; - } - } - if(nadefound) - sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); - } - else if (weapon_prepareattack(1, autocvar_g_balance_grenadelauncher_secondary_refire)) - { - W_Grenade_Attack2(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_grenadelauncher_secondary_animtime, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_gl.md3"); - precache_model ("models/weapons/v_gl.md3"); - precache_model ("models/weapons/h_gl.iqm"); - precache_sound ("weapons/grenade_bounce1.wav"); - precache_sound ("weapons/grenade_bounce2.wav"); - precache_sound ("weapons/grenade_bounce3.wav"); - precache_sound ("weapons/grenade_bounce4.wav"); - precache_sound ("weapons/grenade_bounce5.wav"); - precache_sound ("weapons/grenade_bounce6.wav"); - precache_sound ("weapons/grenade_stick.wav"); - precache_sound ("weapons/grenade_fire.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_GRENADE_LAUNCHER); - self.current_ammo = ammo_rockets; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_primary_ammo; - ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_secondary_ammo; - return ammo_amount; - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo), autocvar_g_balance_grenadelauncher_reload_ammo, autocvar_g_balance_grenadelauncher_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_MORTAR_SUICIDE_BOUNCE; - else - return WEAPON_MORTAR_SUICIDE_EXPLODE; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_MORTAR_MURDER_BOUNCE; - else - return WEAPON_MORTAR_MURDER_EXPLODE; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_glauncher(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/grenade_impact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_hagar.qc b/qcsrc/server/w_hagar.qc deleted file mode 100644 index 01a716949..000000000 --- a/qcsrc/server/w_hagar.qc +++ /dev/null @@ -1,489 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ HAGAR, -/* function */ w_hagar, -/* ammotype */ IT_ROCKETS, -/* impulse */ 8, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "hagar", -/* shortname */ "hagar", -/* fullname */ _("Hagar") -); -#else -#ifdef SVQC -// NO bounce protection, as bounces are limited! - -void W_Hagar_Explode (void) -{ - self.event_damage = func_null; - RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_primary_damage, autocvar_g_balance_hagar_primary_edgedamage, autocvar_g_balance_hagar_primary_radius, world, world, autocvar_g_balance_hagar_primary_force, self.projectiledeathtype, other); - - remove (self); -} - -void W_Hagar_Explode2 (void) -{ - self.event_damage = func_null; - RadiusDamage (self, self.realowner, autocvar_g_balance_hagar_secondary_damage, autocvar_g_balance_hagar_secondary_edgedamage, autocvar_g_balance_hagar_secondary_radius, world, world, autocvar_g_balance_hagar_secondary_force, self.projectiledeathtype, other); - - remove (self); -} - -void W_Hagar_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - - float is_linkexplode = ( ((inflictor.owner != world) ? (inflictor.owner == self.owner) : TRUE) - && (inflictor.projectiledeathtype & HITTYPE_SECONDARY) - && (self.projectiledeathtype & HITTYPE_SECONDARY)); - - if(is_linkexplode) - is_linkexplode = (is_linkexplode && autocvar_g_balance_hagar_secondary_load_linkexplode); - else - is_linkexplode = -1; // not secondary load, so continue as normal without exception. - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, is_linkexplode)) - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - self.angles = vectoangles(self.velocity); - - if (self.health <= 0) - W_PrepareExplosionByDamage(attacker, self.think); -} - -void W_Hagar_Touch (void) -{ - PROJECTILE_TOUCH; - self.use (); -} - -void W_Hagar_Touch2 (void) -{ - PROJECTILE_TOUCH; - - if(self.cnt > 0 || other.takedamage == DAMAGE_AIM) { - self.use(); - } else { - self.cnt++; - pointparticles(particleeffectnum("hagar_bounce"), self.origin, self.velocity, 1); - self.angles = vectoangles (self.velocity); - self.owner = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - } -} - -void W_Hagar_Attack (void) -{ - entity missile; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_reload_ammo); - - W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_primary_damage); - - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = spawn (); - missile.owner = missile.realowner = self; - missile.classname = "missile"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_hagar_primary_damage; - - missile.takedamage = DAMAGE_YES; - missile.health = autocvar_g_balance_hagar_primary_health; - missile.damageforcescale = autocvar_g_balance_hagar_primary_damageforcescale; - missile.event_damage = W_Hagar_Damage; - missile.damagedbycontents = TRUE; - - missile.touch = W_Hagar_Touch; - missile.use = W_Hagar_Explode; - missile.think = adaptor_think2use_hittype_splash; - missile.nextthink = time + autocvar_g_balance_hagar_primary_lifetime; - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_HAGAR; - setorigin (missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - missile.movetype = MOVETYPE_FLY; - W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_primary); - - missile.angles = vectoangles (missile.velocity); - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE); - - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -void W_Hagar_Attack2 (void) -{ - entity missile; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo); - - W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage); - - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = spawn (); - missile.owner = missile.realowner = self; - missile.classname = "missile"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage; - - missile.takedamage = DAMAGE_YES; - missile.health = autocvar_g_balance_hagar_secondary_health; - missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale; - missile.event_damage = W_Hagar_Damage; - missile.damagedbycontents = TRUE; - - missile.touch = W_Hagar_Touch2; - missile.cnt = 0; - missile.use = W_Hagar_Explode2; - missile.think = adaptor_think2use_hittype_splash; - missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand; - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY; - setorigin (missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - missile.movetype = MOVETYPE_BOUNCEMISSILE; - W_SETUPPROJECTILEVELOCITY(missile, g_balance_hagar_secondary); - - missile.angles = vectoangles (missile.velocity); - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR_BOUNCING, TRUE); - - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -.float hagar_loadstep, hagar_loadblock, hagar_loadbeep, hagar_warning; -void W_Hagar_Attack2_Load_Release (void) -{ - // time to release the rockets we've loaded - - entity missile; - float counter, shots, spread_pershot; - vector s; - vector forward, right, up; - - if(!self.hagar_load) - return; - - weapon_prepareattack_do(1, autocvar_g_balance_hagar_secondary_refire); - - W_SetupShot (self, FALSE, 2, "weapons/hagar_fire.wav", CH_WEAPON_A, autocvar_g_balance_hagar_secondary_damage); - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - forward = v_forward; - right = v_right; - up = v_up; - - shots = self.hagar_load; - missile = world; - for(counter = 0; counter < shots; ++counter) - { - missile = spawn (); - missile.owner = missile.realowner = self; - missile.classname = "missile"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_hagar_secondary_damage; - - missile.takedamage = DAMAGE_YES; - missile.health = autocvar_g_balance_hagar_secondary_health; - missile.damageforcescale = autocvar_g_balance_hagar_secondary_damageforcescale; - missile.event_damage = W_Hagar_Damage; - missile.damagedbycontents = TRUE; - - missile.touch = W_Hagar_Touch; // not bouncy - missile.use = W_Hagar_Explode2; - missile.think = adaptor_think2use_hittype_splash; - missile.nextthink = time + autocvar_g_balance_hagar_secondary_lifetime_min + random() * autocvar_g_balance_hagar_secondary_lifetime_rand; - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_HAGAR | HITTYPE_SECONDARY; - setorigin (missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - missile.movetype = MOVETYPE_FLY; - missile.missile_flags = MIF_SPLASH; - - // per-shot spread calculation: the more shots there are, the less spread is applied (based on the bias cvar) - spread_pershot = ((shots - 1) / (autocvar_g_balance_hagar_secondary_load_max - 1)); - spread_pershot = (1 - (spread_pershot * autocvar_g_balance_hagar_secondary_load_spread_bias)); - spread_pershot = (autocvar_g_balance_hagar_secondary_spread * spread_pershot * g_weaponspreadfactor); - - // pattern spread calculation - s = '0 0 0'; - if (counter == 0) - s = '0 0 0'; - else - { - makevectors('0 360 0' * (0.75 + (counter - 0.5) / (shots - 1))); - s_y = v_forward_x; - s_z = v_forward_y; - } - s = s * autocvar_g_balance_hagar_secondary_load_spread * g_weaponspreadfactor; - - W_SetupProjectileVelocityEx(missile, w_shotdir + right * s_y + up * s_z, v_up, autocvar_g_balance_hagar_secondary_speed, 0, 0, spread_pershot, FALSE); - - missile.angles = vectoangles (missile.velocity); - missile.flags = FL_PROJECTILE; - - CSQCProjectile(missile, TRUE, PROJECTILE_HAGAR, TRUE); - - other = missile; MUTATOR_CALLHOOK(EditProjectile); - } - - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_load_animtime, w_ready); - self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_refire * W_WeaponRateFactor(); - self.hagar_load = 0; -} - -void W_Hagar_Attack2_Load (void) -{ - // loadable hagar secondary attack, must always run each frame - - if(time < game_starttime) - return; - - float loaded, enough_ammo; - loaded = self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max; - - // this is different than WR_CHECKAMMO when it comes to reloading - if(autocvar_g_balance_hagar_reload_ammo) - enough_ammo = self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo; - else - enough_ammo = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo; - - if(self.BUTTON_ATCK2) - { - if(self.BUTTON_ATCK && autocvar_g_balance_hagar_secondary_load_abort) - { - if(self.hagar_load) - { - // if we pressed primary fire while loading, unload all rockets and abort - self.weaponentity.state = WS_READY; - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo - self.hagar_load = 0; - sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); - - // pause until we can load rockets again, once we re-press the alt fire button - self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor(); - - // require letting go of the alt fire button before we can load again - self.hagar_loadblock = TRUE; - } - } - else - { - // check if we can attempt to load another rocket - if(!loaded && enough_ammo) - { - if(!self.hagar_loadblock && self.hagar_loadstep < time) - { - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo, autocvar_g_balance_hagar_reload_ammo); - self.weaponentity.state = WS_INUSE; - self.hagar_load += 1; - sound(self, CH_WEAPON_B, "weapons/hagar_load.wav", VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most - - if (self.hagar_load >= autocvar_g_balance_hagar_secondary_load_max) - self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_hold * W_WeaponRateFactor(); - else - self.hagar_loadstep = time + autocvar_g_balance_hagar_secondary_load_speed * W_WeaponRateFactor(); - } - } - else if(!self.hagar_loadbeep && self.hagar_load) // prevents the beep from playing each frame - { - // if this is the last rocket we can load, play a beep sound to notify the player - sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); - self.hagar_loadbeep = TRUE; - } - } - } - else if(self.hagar_loadblock) - { - // the alt fire button has been released, so re-enable loading if blocked - self.hagar_loadblock = FALSE; - } - - if(self.hagar_load) - { - // play warning sound if we're about to release - if((loaded || !enough_ammo) && self.hagar_loadstep - 0.5 < time && autocvar_g_balance_hagar_secondary_load_hold >= 0) - { - if(!self.hagar_warning && self.hagar_load) // prevents the beep from playing each frame - { - // we're about to automatically release after holding time, play a beep sound to notify the player - sound(self, CH_WEAPON_A, "weapons/hagar_beep.wav", VOL_BASE, ATTN_NORM); - self.hagar_warning = TRUE; - } - } - - // release if player let go of button or if they've held it in too long - if(!self.BUTTON_ATCK2 || ((loaded || !enough_ammo) && self.hagar_loadstep < time && autocvar_g_balance_hagar_secondary_load_hold >= 0)) - { - self.weaponentity.state = WS_READY; - W_Hagar_Attack2_Load_Release(); - } - } - else - { - self.hagar_loadbeep = FALSE; - self.hagar_warning = FALSE; - } - - // we aren't checking ammo during an attack, so we must do it here - if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2)) - { - // note: this doesn't force the switch - W_SwitchToOtherWeapon(self); - return; - } -} - -void spawnfunc_weapon_hagar (void) -{ - weapon_defaultspawnfunc(WEP_HAGAR); -} - -float w_hagar(float req) -{ - float ammo_amount; - if (req == WR_AIM) - if (random()>0.15) - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE); - else - { - // not using secondary_speed since these are only 15% and should cause some ricochets without re-aiming - self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_hagar_primary_speed, 0, autocvar_g_balance_hagar_primary_lifetime, FALSE); - } - else if (req == WR_THINK) - { - float loadable_secondary; - loadable_secondary = (autocvar_g_balance_hagar_secondary_load && autocvar_g_balance_hagar_secondary); - - if (loadable_secondary) - W_Hagar_Attack2_Load(); // must always run each frame - if(autocvar_g_balance_hagar_reload_ammo && self.clip_load < min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if (self.BUTTON_ATCK && !self.hagar_load && !self.hagar_loadblock) // not while secondary is loaded or awaiting reset - { - if (weapon_prepareattack(0, autocvar_g_balance_hagar_primary_refire)) - { - W_Hagar_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hagar_primary_refire, w_ready); - } - } - else if (self.BUTTON_ATCK2 && !loadable_secondary && autocvar_g_balance_hagar_secondary) - { - if (weapon_prepareattack(1, autocvar_g_balance_hagar_secondary_refire)) - { - W_Hagar_Attack2(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hagar_secondary_refire, w_ready); - } - } - } - else if (req == WR_GONETHINK) - { - // we lost the weapon and want to prepare switching away - if(self.hagar_load) - { - self.weaponentity.state = WS_READY; - W_Hagar_Attack2_Load_Release(); - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_hagar.md3"); - precache_model ("models/weapons/v_hagar.md3"); - precache_model ("models/weapons/h_hagar.iqm"); - precache_sound ("weapons/hagar_fire.wav"); - precache_sound ("weapons/hagar_load.wav"); - precache_sound ("weapons/hagar_beep.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_HAGAR); - self.current_ammo = ammo_rockets; - self.hagar_loadblock = FALSE; - - if(self.hagar_load) - { - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_hagar_secondary_ammo * self.hagar_load * -1, autocvar_g_balance_hagar_reload_ammo); // give back ammo if necessary - self.hagar_load = 0; - } - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_primary_ammo; - ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_hagar_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_HAGAR]) >= autocvar_g_balance_hagar_secondary_ammo; - return ammo_amount; - } - else if (req == WR_RESETPLAYER) - { - self.hagar_load = 0; - } - else if (req == WR_PLAYERDEATH) - { - // if we have any rockets loaded when we die, release them - if(self.hagar_load && autocvar_g_balance_hagar_secondary_load_releasedeath) - W_Hagar_Attack2_Load_Release(); - } - else if (req == WR_RELOAD) - { - if not(self.hagar_load) // require releasing loaded rockets first - W_Reload(min(autocvar_g_balance_hagar_primary_ammo, autocvar_g_balance_hagar_secondary_ammo), autocvar_g_balance_hagar_reload_ammo, autocvar_g_balance_hagar_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_HAGAR_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_HAGAR_MURDER_BURST; - else - return WEAPON_HAGAR_MURDER_SPRAY; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_hagar(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1); - if(!w_issilent) - { - if (w_random<0.15) - sound(self, CH_SHOTS, "weapons/hagexp1.wav", VOL_BASE, ATTN_NORM); - else if (w_random<0.7) - sound(self, CH_SHOTS, "weapons/hagexp2.wav", VOL_BASE, ATTN_NORM); - else - sound(self, CH_SHOTS, "weapons/hagexp3.wav", VOL_BASE, ATTN_NORM); - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/hagexp1.wav"); - precache_sound("weapons/hagexp2.wav"); - precache_sound("weapons/hagexp3.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_hlac.qc b/qcsrc/server/w_hlac.qc deleted file mode 100644 index cc7f05355..000000000 --- a/qcsrc/server/w_hlac.qc +++ /dev/null @@ -1,261 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ HLAC, -/* function */ w_hlac, -/* ammotype */ IT_CELLS, -/* impulse */ 6, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "hlac", -/* shortname */ "hlac", -/* fullname */ _("Heavy Laser Assault Cannon") -); -#else -#ifdef SVQC - -void W_HLAC_Touch (void) -{ - PROJECTILE_TOUCH; - - self.event_damage = func_null; - - if(self.projectiledeathtype & HITTYPE_SECONDARY) - RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_secondary_damage, autocvar_g_balance_hlac_secondary_edgedamage, autocvar_g_balance_hlac_secondary_radius, world, world, autocvar_g_balance_hlac_secondary_force, self.projectiledeathtype, other); - else - RadiusDamage (self, self.realowner, autocvar_g_balance_hlac_primary_damage, autocvar_g_balance_hlac_primary_edgedamage, autocvar_g_balance_hlac_primary_radius, world, world, autocvar_g_balance_hlac_primary_force, self.projectiledeathtype, other); - - remove (self); -} - -void W_HLAC_Attack (void) -{ - entity missile; - float spread; - - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_reload_ammo); - - spread = autocvar_g_balance_hlac_primary_spread_min + (autocvar_g_balance_hlac_primary_spread_add * self.misc_bulletcounter); - spread = min(spread,autocvar_g_balance_hlac_primary_spread_max); - if(self.crouch) - spread = spread * autocvar_g_balance_hlac_primary_spread_crouchmod; - - W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_primary_damage); - pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - missile = spawn (); - missile.owner = missile.realowner = self; - missile.classname = "hlacbolt"; - missile.bot_dodge = TRUE; - - missile.bot_dodgerating = autocvar_g_balance_hlac_primary_damage; - - missile.movetype = MOVETYPE_FLY; - PROJECTILE_MAKETRIGGER(missile); - - setorigin (missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_primary_speed, spread); - //missile.angles = vectoangles (missile.velocity); // csqc - - missile.touch = W_HLAC_Touch; - missile.think = SUB_Remove; - - missile.nextthink = time + autocvar_g_balance_hlac_primary_lifetime; - - missile.flags = FL_PROJECTILE; - missile.projectiledeathtype = WEP_HLAC; - - CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE); - - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -void W_HLAC_Attack2f (void) -{ - entity missile; - float spread; - - spread = autocvar_g_balance_hlac_secondary_spread; - - - if(self.crouch) - spread = spread * autocvar_g_balance_hlac_secondary_spread_crouchmod; - - W_SetupShot (self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_A, autocvar_g_balance_hlac_secondary_damage); - pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = spawn (); - missile.owner = missile.realowner = self; - missile.classname = "hlacbolt"; - missile.bot_dodge = TRUE; - - missile.bot_dodgerating = autocvar_g_balance_hlac_secondary_damage; - - missile.movetype = MOVETYPE_FLY; - PROJECTILE_MAKETRIGGER(missile); - - setorigin (missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - W_SetupProjectileVelocity(missile, autocvar_g_balance_hlac_secondary_speed, spread); - //missile.angles = vectoangles (missile.velocity); // csqc - - missile.touch = W_HLAC_Touch; - missile.think = SUB_Remove; - - missile.nextthink = time + autocvar_g_balance_hlac_secondary_lifetime; - - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - missile.projectiledeathtype = WEP_HLAC | HITTYPE_SECONDARY; - - CSQCProjectile(missile, TRUE, PROJECTILE_HLAC, TRUE); - - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -void W_HLAC_Attack2 (void) -{ - float i; - - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hlac_secondary_ammo, autocvar_g_balance_hlac_reload_ammo); - - for(i=autocvar_g_balance_hlac_secondary_shots;i>0;--i) - W_HLAC_Attack2f(); - - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } -} - -// weapon frames -void HLAC_fire1_02() -{ - if(self.weapon != self.switchweapon) // abort immediately if switching - { - w_ready(); - return; - } - - if (self.BUTTON_ATCK) - { - if (!weapon_action(self.weapon, WR_CHECKAMMO1)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - ATTACK_FINISHED(self) = time + autocvar_g_balance_hlac_primary_refire * W_WeaponRateFactor(); - W_HLAC_Attack(); - self.misc_bulletcounter = self.misc_bulletcounter + 1; - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02); - } - else - { - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_animtime, w_ready); - } -} - -void spawnfunc_weapon_hlac (void) -{ - weapon_defaultspawnfunc(WEP_HLAC); -} - -float w_hlac(float req) -{ - float ammo_amount; - if (req == WR_AIM) - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_hlac_primary_speed, 0, autocvar_g_balance_hlac_primary_lifetime, FALSE); - else if (req == WR_THINK) - { - if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if (self.BUTTON_ATCK) - { - if (weapon_prepareattack(0, autocvar_g_balance_hlac_primary_refire)) - { - self.misc_bulletcounter = 0; - W_HLAC_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hlac_primary_refire, HLAC_fire1_02); - } - } - - else if (self.BUTTON_ATCK2 && autocvar_g_balance_hlac_secondary) - { - if (weapon_prepareattack(1, autocvar_g_balance_hlac_secondary_refire)) - { - W_HLAC_Attack2(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hlac_secondary_animtime, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_hlac.md3"); - precache_model ("models/weapons/v_hlac.md3"); - precache_model ("models/weapons/h_hlac.iqm"); - precache_sound ("weapons/lasergun_fire.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_HLAC); - self.current_ammo = ammo_cells; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_primary_ammo; - ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - ammo_amount = self.ammo_cells >= autocvar_g_balance_hlac_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_HLAC]) >= autocvar_g_balance_hlac_secondary_ammo; - return ammo_amount; - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_hlac_primary_ammo, autocvar_g_balance_hlac_secondary_ammo), autocvar_g_balance_hlac_reload_ammo, autocvar_g_balance_hlac_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_HLAC_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - return WEAPON_HLAC_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_hlac(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum("laser_impact"), org2, w_backoff * 1000, 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/laserimpact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_hook.qc b/qcsrc/server/w_hook.qc deleted file mode 100644 index 7f03744f9..000000000 --- a/qcsrc/server/w_hook.qc +++ /dev/null @@ -1,307 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ HOOK, -/* function */ w_hook, -/* ammotype */ IT_CELLS|IT_FUEL, -/* impulse */ 0, -/* flags */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ 0, -/* model */ "hookgun", -/* shortname */ "hook", -/* fullname */ _("Grappling Hook") -); -#else -#ifdef SVQC -.float dmg; -.float dmg_edge; -.float dmg_radius; -.float dmg_force; -.float dmg_power; -.float dmg_duration; -.float dmg_last; -.float hook_refire; -.float hook_time_hooked; -.float hook_time_fueldecrease; - -void W_Hook_ExplodeThink (void) -{ - float dt, dmg_remaining_next, f; - - dt = time - self.teleport_time; - dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power); - - f = self.dmg_last - dmg_remaining_next; - self.dmg_last = dmg_remaining_next; - - RadiusDamage (self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world); - self.projectiledeathtype |= HITTYPE_BOUNCE; - //RadiusDamage (self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world); - - if(dt < self.dmg_duration) - self.nextthink = time + 0.05; // soon - else - remove(self); -} - -void W_Hook_Explode2 (void) -{ - self.event_damage = func_null; - self.touch = func_null; - self.effects |= EF_NODRAW; - - self.think = W_Hook_ExplodeThink; - self.nextthink = time; - self.dmg = autocvar_g_balance_hook_secondary_damage; - self.dmg_edge = autocvar_g_balance_hook_secondary_edgedamage; - self.dmg_radius = autocvar_g_balance_hook_secondary_radius; - self.dmg_force = autocvar_g_balance_hook_secondary_force; - self.dmg_power = autocvar_g_balance_hook_secondary_power; - self.dmg_duration = autocvar_g_balance_hook_secondary_duration; - self.teleport_time = time; - self.dmg_last = 1; - self.movetype = MOVETYPE_NONE; -} - -void W_Hook_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - - if (self.health <= 0) - W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2); -} - -void W_Hook_Touch2 (void) -{ - PROJECTILE_TOUCH; - self.use(); -} - -void W_Hook_Attack2() -{ - entity gren; - - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_hook_secondary_ammo, FALSE); - W_SetupShot (self, FALSE, 4, "weapons/hookbomb_fire.wav", CH_WEAPON_A, autocvar_g_balance_hook_secondary_damage); - - gren = spawn (); - gren.owner = gren.realowner = self; - gren.classname = "hookbomb"; - gren.bot_dodge = TRUE; - gren.bot_dodgerating = autocvar_g_balance_hook_secondary_damage; - gren.movetype = MOVETYPE_TOSS; - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_HOOK | HITTYPE_SECONDARY; - setorigin(gren, w_shotorg); - setsize(gren, '0 0 0', '0 0 0'); - - gren.nextthink = time + autocvar_g_balance_hook_secondary_lifetime; - gren.think = adaptor_think2use_hittype_splash; - gren.use = W_Hook_Explode2; - gren.touch = W_Hook_Touch2; - - gren.takedamage = DAMAGE_YES; - gren.health = autocvar_g_balance_hook_secondary_health; - gren.damageforcescale = autocvar_g_balance_hook_secondary_damageforcescale; - gren.event_damage = W_Hook_Damage; - gren.damagedbycontents = TRUE; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - - gren.velocity = '0 0 1' * autocvar_g_balance_hook_secondary_speed; - if(autocvar_g_projectiles_newton_style) - gren.velocity = gren.velocity + self.velocity; - - gren.gravity = autocvar_g_balance_hook_secondary_gravity; - //W_SetupProjectileVelocity(gren); // just falling down! - - gren.angles = '0 0 0'; - gren.flags = FL_PROJECTILE; - - CSQCProjectile(gren, TRUE, PROJECTILE_HOOKBOMB, TRUE); - - other = gren; MUTATOR_CALLHOOK(EditProjectile); -} - -void spawnfunc_weapon_hook (void) -{ - if(g_grappling_hook) // offhand hook - { - startitem_failed = TRUE; - remove(self); - return; - } - weapon_defaultspawnfunc(WEP_HOOK); -} - -float w_hook(float req) -{ - float hooked_time_max, hooked_fuel; - - if (req == WR_AIM) - { - // ... sorry ... - } - else if (req == WR_THINK) - { - if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) - { - if(!self.hook) - if not(self.hook_state & HOOK_WAITING_FOR_RELEASE) - if not(self.hook_state & HOOK_FIRING) - if (time > self.hook_refire) - if (weapon_prepareattack(0, -1)) - { - W_DecreaseAmmo(ammo_fuel, autocvar_g_balance_hook_primary_fuel, FALSE); - self.hook_state |= HOOK_FIRING; - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_hook_primary_animtime, w_ready); - } - } - - if (self.BUTTON_ATCK2) - { - if (weapon_prepareattack(1, autocvar_g_balance_hook_secondary_refire)) - { - W_Hook_Attack2(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_hook_secondary_animtime, w_ready); - } - } - - if(self.hook) - { - // if hooked, no bombs, and increase the timer - self.hook_refire = max(self.hook_refire, time + autocvar_g_balance_hook_primary_refire * W_WeaponRateFactor()); - - // hook also inhibits health regeneration, but only for 1 second - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen); - } - - if(self.hook && self.hook.state == 1) - { - hooked_time_max = autocvar_g_balance_hook_primary_hooked_time_max; - if (hooked_time_max > 0) - { - if ( time > self.hook_time_hooked + hooked_time_max ) - self.hook_state |= HOOK_REMOVING; - } - - hooked_fuel = autocvar_g_balance_hook_primary_hooked_fuel; - if (hooked_fuel > 0) - { - if ( time > self.hook_time_fueldecrease ) - { - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - if ( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel ) - { - W_DecreaseAmmo(ammo_fuel, (time - self.hook_time_fueldecrease) * hooked_fuel, FALSE); - self.hook_time_fueldecrease = time; - // decrease next frame again - } - else - { - self.ammo_fuel = 0; - self.hook_state |= HOOK_REMOVING; - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - } - } - } - } - } - else - { - self.hook_time_hooked = time; - self.hook_time_fueldecrease = time + autocvar_g_balance_hook_primary_hooked_time_free; - } - - if (self.BUTTON_CROUCH) - { - self.hook_state &~= HOOK_PULLING; - if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) - self.hook_state &~= HOOK_RELEASING; - else - self.hook_state |= HOOK_RELEASING; - } - else - { - self.hook_state |= HOOK_PULLING; - self.hook_state &~= HOOK_RELEASING; - - if (self.BUTTON_ATCK || (!(self.items & IT_JETPACK) && self.BUTTON_HOOK)) - { - // already fired - if(self.hook) - self.hook_state |= HOOK_WAITING_FOR_RELEASE; - } - else - { - self.hook_state |= HOOK_REMOVING; - self.hook_state &~= HOOK_WAITING_FOR_RELEASE; - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_hookgun.md3"); - precache_model ("models/weapons/v_hookgun.md3"); - precache_model ("models/weapons/h_hookgun.iqm"); - precache_sound ("weapons/hook_impact.wav"); // done by g_hook.qc - precache_sound ("weapons/hook_fire.wav"); - precache_sound ("weapons/hookbomb_fire.wav"); - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_HOOK); - self.current_ammo = ammo_fuel; - self.hook_state &~= HOOK_WAITING_FOR_RELEASE; - } - else if (req == WR_CHECKAMMO1) - { - if(self.hook) - return self.ammo_fuel > 0; - else - return self.ammo_fuel >= autocvar_g_balance_hook_primary_fuel; - } - else if (req == WR_CHECKAMMO2) - { - return self.ammo_cells >= autocvar_g_balance_hook_secondary_ammo; - } - else if (req == WR_RESETPLAYER) - { - self.hook_refire = time; - } - else if (req == WR_SUICIDEMESSAGE) - { - return FALSE; - } - else if (req == WR_KILLMESSAGE) - { - return WEAPON_HOOK_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_hook(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum("hookbomb_explode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/hookbomb_impact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/hookbomb_impact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_laser.qc b/qcsrc/server/w_laser.qc deleted file mode 100644 index c331c5b52..000000000 --- a/qcsrc/server/w_laser.qc +++ /dev/null @@ -1,576 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ LASER, -/* function */ W_Laser, -/* ammotype */ 0, -/* impulse */ 1, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ 0, -/* model */ "laser", -/* shortname */ "laser", -/* fullname */ _("Blaster") -); -#else -#ifdef SVQC -void(float imp) W_SwitchWeapon; -void() W_LastWeapon; -.float swing_prev; -.entity swing_alreadyhit; - -void SendCSQCShockwaveParticle(vector endpos) -{ - //endpos = WarpZone_UnTransformOrigin(transform, endpos); - - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE); - WriteCoord(MSG_BROADCAST, w_shotorg_x); - WriteCoord(MSG_BROADCAST, w_shotorg_y); - WriteCoord(MSG_BROADCAST, w_shotorg_z); - WriteCoord(MSG_BROADCAST, endpos_x); - WriteCoord(MSG_BROADCAST, endpos_y); - WriteCoord(MSG_BROADCAST, endpos_z); - WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_max, 255)); - WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_min, 255)); - WriteByte(MSG_BROADCAST, num_for_edict(self)); -} - -void W_Laser_Touch() -{ - PROJECTILE_TOUCH; - - self.event_damage = func_null; - - if(self.dmg) - RadiusDamage(self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other); - else - RadiusDamage(self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other); - - remove(self); -} - -void W_Laser_Think() -{ - self.movetype = MOVETYPE_FLY; - self.think = SUB_Remove; - - if(self.dmg) - self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime; - else - self.nextthink = time + autocvar_g_balance_laser_primary_lifetime; - - CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE); -} - - -float W_Laser_Shockwave_CheckSpread(vector targetorg, vector nearest_on_line, vector sw_shotorg, vector attack_endpos) -{ - float spreadlimit; - float distance_of_attack = vlen(sw_shotorg - attack_endpos); - float distance_from_line = vlen(targetorg - nearest_on_line); - - spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1); - spreadlimit = (autocvar_g_balance_laser_shockwave_spread_min * (1 - spreadlimit) + autocvar_g_balance_laser_shockwave_spread_max * spreadlimit); - - if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90)) - return bound(0, (distance_from_line / spreadlimit), 1); - else - return FALSE; -} - -float W_Laser_Shockwave_IsVisible(entity head, vector nearest_on_line, vector sw_shotorg, vector attack_endpos) -{ - vector nearest_to_attacker = head.WarpZone_findradius_nearest; - vector center = (head.origin + (head.mins + head.maxs) * 0.5); - vector corner; - float i; - - // STEP ONE: Check if the nearest point is clear - if(W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos)) - { - WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self); - if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage - } - - // STEP TWO: Check if shotorg to center point is clear - if(W_Laser_Shockwave_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos)) - { - WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self); - if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage - } - - // STEP THREE: Check each corner to see if they are clear - for(i=1; i<=8; ++i) - { - corner = get_corner_position(head, i); - if(W_Laser_Shockwave_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos)) - { - WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self); - if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage - } - } - - return FALSE; -} - -#define PLAYER_CENTER(ent) (ent.origin + ((ent.classname == "player") ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5))) - -entity shockwave_hit[32]; -float shockwave_hit_damage[32]; -vector shockwave_hit_force[32]; - -float W_Laser_Shockwave_CheckHit(float queue, entity head, vector final_force, float final_damage) -{ - if not(head) { return FALSE; } - float i; - - ++queue; - - for(i = 1; i <= queue; ++i) - { - if(shockwave_hit[i] == head) - { - if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; } - if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; } - return FALSE; - } - } - - shockwave_hit[queue] = head; - shockwave_hit_force[queue] = final_force; - shockwave_hit_damage[queue] = final_damage; - return TRUE; -} - -void W_Laser_Shockwave() -{ - // declarations - float multiplier, multiplier_from_accuracy, multiplier_from_distance; - float final_damage; //, final_spread; - vector final_force, center, vel; - entity head, next; - - float i, queue = 0; - - // set up the shot direction - W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_shockwave_damage); - vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_shockwave_distance)); - WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self); - vector attack_hitpos = trace_endpos; - float distance_to_end = vlen(w_shotorg - attack_endpos); - float distance_to_hit = vlen(w_shotorg - attack_hitpos); - //entity transform = WarpZone_trace_transform; - - // do the firing effect now - SendCSQCShockwaveParticle(attack_endpos); - Damage_DamageInfo(attack_hitpos, autocvar_g_balance_laser_shockwave_splash_damage, autocvar_g_balance_laser_shockwave_splash_edgedamage, autocvar_g_balance_laser_shockwave_splash_radius, w_shotdir * autocvar_g_balance_laser_shockwave_splash_force, WEP_LASER, 0, self); - - // splash damage/jumping trace - head = WarpZone_FindRadius(attack_hitpos, max(autocvar_g_balance_laser_shockwave_splash_radius, autocvar_g_balance_laser_shockwave_jump_radius), FALSE); - while(head) - { - next = head.chain; - - if(head.takedamage) - { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) - center = PLAYER_CENTER(head); - - float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); - - if((head == self) && (distance_to_head <= autocvar_g_balance_laser_shockwave_jump_radius)) - { - multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_jump_radius)) : 0)); - multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); - multiplier = max(autocvar_g_balance_laser_shockwave_jump_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_jump_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_jump_multiplier_distance))); - - final_force = ((normalize(center - attack_hitpos) * autocvar_g_balance_laser_shockwave_jump_force) * multiplier); - vel = head.velocity; vel_z = 0; - vel = normalize(vel) * bound(0, vlen(vel) / autocvar_sv_maxspeed, 1) * autocvar_g_balance_laser_shockwave_jump_force_velocitybias; - final_force = (vlen(final_force) * normalize(normalize(final_force) + vel)); - final_force_z *= autocvar_g_balance_laser_shockwave_jump_force_zscale; - final_damage = (autocvar_g_balance_laser_shockwave_jump_damage * multiplier + autocvar_g_balance_laser_shockwave_jump_edgedamage * (1 - multiplier)); - - Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); - //print("SELF HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); - } - else if (distance_to_head <= autocvar_g_balance_laser_shockwave_splash_radius) - { - multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_splash_radius)) : 0)); - multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); - multiplier = max(autocvar_g_balance_laser_shockwave_splash_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_splash_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_splash_multiplier_distance))); - - final_force = normalize(center - (attack_hitpos - (w_shotdir * autocvar_g_balance_laser_shockwave_splash_force_forwardbias))); - //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); - final_force = ((final_force * autocvar_g_balance_laser_shockwave_splash_force) * multiplier); - final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; - final_damage = (autocvar_g_balance_laser_shockwave_splash_damage * multiplier + autocvar_g_balance_laser_shockwave_splash_edgedamage * (1 - multiplier)); - - if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } - //print("SPLASH HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); - } - } - head = next; - } - - // cone damage trace - head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_shockwave_distance, FALSE); - while(head) - { - next = head.chain; - - if((head != self) && head.takedamage) - { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) - center = PLAYER_CENTER(head); - - // find the closest point on the enemy to the center of the attack - float ang; // angle between shotdir and h - float h; // hypotenuse, which is the distance between attacker to head - float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin - - h = vlen(center - self.origin); - ang = acos(dotproduct(normalize(center - self.origin), w_shotdir)); - a = h * cos(ang); - - vector nearest_on_line = (w_shotorg + a * w_shotdir); - vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); - float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this - - if((distance_to_target <= autocvar_g_balance_laser_shockwave_distance) - && (W_Laser_Shockwave_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) - { - multiplier_from_accuracy = (1 - W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_endpos)); - multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_target / distance_to_end)) : 0)); - multiplier = max(autocvar_g_balance_laser_shockwave_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_multiplier_distance))); - - final_force = normalize(center - (nearest_on_line - (w_shotdir * autocvar_g_balance_laser_shockwave_force_forwardbias))); - //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); - final_force = ((final_force * autocvar_g_balance_laser_shockwave_force) * multiplier); - final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; - final_damage = (autocvar_g_balance_laser_shockwave_damage * multiplier + autocvar_g_balance_laser_shockwave_edgedamage * (1 - multiplier)); - - if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } - //print("CONE HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); - } - } - head = next; - } - - for(i = 1; i <= queue; ++i) - { - head = shockwave_hit[i]; - final_force = shockwave_hit_force[i]; - final_damage = shockwave_hit_damage[i]; - - Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); - print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n"); - - shockwave_hit[i] = world; - shockwave_hit_force[i] = '0 0 0'; - shockwave_hit_damage[i] = 0; - } - //print("queue was ", ftos(queue), ".\n\n"); -} - -void W_Laser_Melee_Think() -{ - // declarations - float i, f, swing, swing_factor, swing_damage, meleetime, is_player; - entity target_victim; - vector targpos; - - if(!self.cnt) // set start time of melee - { - self.cnt = time; - W_PlayStrengthSound(self.realowner); - } - - makevectors(self.realowner.v_angle); // update values for v_* vectors - - // calculate swing percentage based on time - meleetime = autocvar_g_balance_laser_melee_time * W_WeaponRateFactor(); - swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); - f = ((1 - swing) * autocvar_g_balance_laser_melee_traces); - - // check to see if we can still continue, otherwise give up now - if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_laser_melee_no_doubleslap) - { - remove(self); - return; - } - - // if okay, perform the traces needed for this frame - for(i=self.swing_prev; i < f; ++i) - { - swing_factor = ((1 - (i / autocvar_g_balance_laser_melee_traces)) * 2 - 1); - - targpos = (self.realowner.origin + self.realowner.view_ofs - + (v_forward * autocvar_g_balance_laser_melee_range) - + (v_up * swing_factor * autocvar_g_balance_laser_melee_swing_up) - + (v_right * swing_factor * autocvar_g_balance_laser_melee_swing_side)); - - WarpZone_traceline_antilag(self.realowner, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, ANTILAG_LATENCY(self.realowner)); - - // draw lightning beams for debugging - te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); - te_customflash(targpos, 40, 2, '1 1 1'); - - is_player = (trace_ent.classname == "player" || trace_ent.classname == "body"); - - if((trace_fraction < 1) // if trace is good, apply the damage and remove self - && (trace_ent.takedamage == DAMAGE_AIM) - && (trace_ent != self.swing_alreadyhit) - && (is_player || autocvar_g_balance_laser_melee_nonplayerdamage)) - { - target_victim = trace_ent; // so it persists through other calls - - if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. - swing_damage = (autocvar_g_balance_laser_melee_damage * min(1, swing_factor + 1)); - else - swing_damage = (autocvar_g_balance_laser_melee_nonplayerdamage * min(1, swing_factor + 1)); - - //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n")); - - Damage(target_victim, self.realowner, self.realowner, - swing_damage, WEP_LASER | HITTYPE_SECONDARY, - self.realowner.origin + self.realowner.view_ofs, - v_forward * autocvar_g_balance_laser_melee_force); - - if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LASER, 0, swing_damage); } - - if(autocvar_g_balance_laser_melee_multihit) // allow multiple hits with one swing, but not against the same player twice. - { - self.swing_alreadyhit = target_victim; - continue; // move along to next trace - } - else - { - remove(self); - return; - } - } - } - - if(time >= self.cnt + meleetime) - { - // melee is finished - remove(self); - return; - } - else - { - // set up next frame - self.swing_prev = i; - self.nextthink = time; - } -} - -void W_Laser_Melee() -{ - sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_melee_animtime, w_ready); - - entity meleetemp; - meleetemp = spawn(); - meleetemp.owner = meleetemp.realowner = self; - meleetemp.think = W_Laser_Melee_Think; - meleetemp.nextthink = time + autocvar_g_balance_laser_melee_delay * W_WeaponRateFactor(); - W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_laser_melee_damage, autocvar_g_balance_laser_melee_range); -} - -void W_Laser_Attack(float issecondary) -{ - entity missile; - vector s_forward; - float a; - - a = autocvar_g_balance_laser_primary_shotangle; - s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD); - - //if(nodamage) - // W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0); - /*else*/if(issecondary == 1) - W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage); - else - W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage); - pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = spawn(); - missile.owner = missile.realowner = self; - missile.classname = "laserbolt"; - missile.dmg = 0; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage; - - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_LASER; - - setorigin(missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary); - missile.angles = vectoangles(missile.velocity); - //missile.glow_color = 250; // 244, 250 - //missile.glow_size = 120; - missile.touch = W_Laser_Touch; - - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - missile.think = W_Laser_Think; - missile.nextthink = time + autocvar_g_balance_laser_primary_delay; - - other = missile; MUTATOR_CALLHOOK(EditProjectile); - - if(time >= missile.nextthink) - { - entity oldself; - oldself = self; - self = missile; - self.think(); - self = oldself; - } -} - -void spawnfunc_weapon_laser(void) -{ - weapon_defaultspawnfunc(WEP_LASER); -} - -float W_Laser(float request) -{ - switch(request) - { - case WR_AIM: - { - if((autocvar_g_balance_laser_secondary == 2) && (vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_laser_melee_range)) - self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); - else - self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE); - return TRUE; - } - - case WR_THINK: - { - if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire)) - { - W_DecreaseAmmo(ammo_none, 1, TRUE); - - if not(autocvar_g_balance_laser_primary) - W_Laser_Shockwave(); - else - W_Laser_Attack(FALSE); - - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready); - } - } - else if(self.BUTTON_ATCK2) - { - switch(autocvar_g_balance_laser_secondary) - { - case 0: // switch to last used weapon - { - if(self.switchweapon == WEP_LASER) // don't do this if already switching - W_LastWeapon(); - - break; - } - - case 1: // normal projectile secondary - { - if(weapon_prepareattack(1, autocvar_g_balance_laser_secondary_refire)) - { - W_DecreaseAmmo(ammo_none, 1, TRUE); - W_Laser_Attack(TRUE); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready); - } - - break; - } - - case 2: // melee attack secondary - { - if(!self.crouch) // we are not currently crouching; this fixes an exploit where your melee anim is not visible, and besides wouldn't make much sense - if(weapon_prepareattack(1, autocvar_g_balance_laser_melee_refire)) - { - // attempt forcing playback of the anim by switching to another anim (that we never play) here... - W_Laser_Melee(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_melee_animtime, w_ready); - } - } - } - } - return TRUE; - } - - case WR_PRECACHE: - { - precache_model("models/weapons/g_laser.md3"); - precache_model("models/weapons/v_laser.md3"); - precache_model("models/weapons/h_laser.iqm"); - precache_sound("weapons/lasergun_fire.wav"); - return TRUE; - } - - case WR_SETUP: - { - weapon_setup(WEP_LASER); - self.current_ammo = ammo_none; - return TRUE; - } - - case WR_CHECKAMMO1: - case WR_CHECKAMMO2: - { - return TRUE; // laser has infinite ammo - } - - case WR_RELOAD: - { - W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav"); - return TRUE; - } - - case WR_SUICIDEMESSAGE: - { - return WEAPON_LASER_SUICIDE; - } - - case WR_KILLMESSAGE: - { - return WEAPON_LASER_MURDER; - } - } - - return TRUE; -} -#endif -#ifdef CSQC -float W_Laser(float request) -{ - switch(request) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1); - if(!w_issilent) { sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); } - return TRUE; - } - - case WR_PRECACHE: - { - precache_sound("weapons/laserimpact.wav"); - return TRUE; - } - } - - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_lightning.qc b/qcsrc/server/w_lightning.qc deleted file mode 100644 index e4b7230d6..000000000 --- a/qcsrc/server/w_lightning.qc +++ /dev/null @@ -1,296 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ LIGHTNING, -/* function */ w_lightning, -/* ammotype */ IT_CELLS, -/* impulse */ 5, -/* flags */ WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "lightning", -/* shortname */ "lightning", -/* fullname */ _("Lightning") -); -#else -#ifdef SVQC - -// Declarations ========================= -.vector hook_start, hook_end; // used for beam -.entity lightning_beam; // used for beam -.float BUTTON_ATCK_prev; // for better animation control -.float lg_fire_prev; // for better animation control - -// Lightning functions ========================= -float W_Lightning_Beam_Send(entity to, float sf) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_LIGHTNING_BEAM); - sf = sf & 0x7F; - if(sound_allowed(MSG_BROADCAST, self.owner)) - sf |= 0x80; - WriteByte(MSG_ENTITY, sf); - if(sf & 1) - { - WriteByte(MSG_ENTITY, num_for_edict(self.owner)); - WriteCoord(MSG_ENTITY, autocvar_g_balance_lightning_primary_range); - } - if(sf & 2) - { - WriteCoord(MSG_ENTITY, self.hook_start_x); - WriteCoord(MSG_ENTITY, self.hook_start_y); - WriteCoord(MSG_ENTITY, self.hook_start_z); - } - if(sf & 4) - { - WriteCoord(MSG_ENTITY, self.hook_end_x); - WriteCoord(MSG_ENTITY, self.hook_end_y); - WriteCoord(MSG_ENTITY, self.hook_end_z); - } - return TRUE; -} - -void W_Lightning_Beam_Think() -{ - self.owner.lg_fire_prev = time; - if (self != self.owner.lightning_beam) - { - remove(self); - return; - } - if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen) - { - if(self == self.owner.lightning_beam) - self.owner.lightning_beam = world; - remove(self); - return; - } - - self.nextthink = time; - - makevectors(self.owner.v_angle); - - float dt, f; - dt = frametime; - if not(self.owner.items & IT_UNLIMITED_WEAPON_AMMO) - { - if(autocvar_g_balance_lightning_primary_ammo) - { - dt = min(dt, self.owner.ammo_cells / autocvar_g_balance_lightning_primary_ammo); - self.owner.ammo_cells = max(0, self.owner.ammo_cells - autocvar_g_balance_lightning_primary_ammo * frametime); - } - } - - W_SetupShot_Range(self.owner, TRUE, 0, "", 0, autocvar_g_balance_lightning_primary_damage * dt, autocvar_g_balance_lightning_primary_range); - WarpZone_traceline_antilag(self.owner, w_shotorg, w_shotend, MOVE_NORMAL, self.owner, ANTILAG_LATENCY(self.owner)); - - // apply the damage - if(trace_ent) - { - vector force; - force = w_shotdir * autocvar_g_balance_lightning_primary_force; - - f = ExponentialFalloff(autocvar_g_balance_lightning_primary_falloff_mindist, autocvar_g_balance_lightning_primary_falloff_maxdist, autocvar_g_balance_lightning_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg)); - - if(accuracy_isgooddamage(self.owner, trace_ent)) - accuracy_add(self.owner, WEP_LIGHTNING, 0, autocvar_g_balance_lightning_primary_damage * dt * f); - Damage (trace_ent, self.owner, self.owner, autocvar_g_balance_lightning_primary_damage * dt * f, WEP_LIGHTNING, trace_endpos, force * dt); - } - - // draw effect - if(w_shotorg != self.hook_start) - { - self.SendFlags |= 2; - self.hook_start = w_shotorg; - } - if(w_shotend != self.hook_end) - { - self.SendFlags |= 4; - self.hook_end = w_shotend; - } -} - -// Attack functions ========================= -void W_Lightning_Attack1 (void) -{ - // only play fire sound if 0.5 sec has passed since player let go the fire button - if(time - self.lg_fire_prev > 0.5) - sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM); - - entity beam, oldself; - - self.lightning_beam = beam = spawn(); - beam.classname = "W_Lightning_Beam"; - beam.solid = SOLID_NOT; - beam.think = W_Lightning_Beam_Think; - beam.owner = self; - beam.movetype = MOVETYPE_NONE; - beam.shot_spread = 1; - beam.bot_dodge = TRUE; - beam.bot_dodgerating = autocvar_g_balance_lightning_primary_damage; - Net_LinkEntity(beam, FALSE, 0, W_Lightning_Beam_Send); - - oldself = self; - self = beam; - self.think(); - self = oldself; -} - -float w_lightning(float req) -{ - if (req == WR_AIM) - { - self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); - /* - self.BUTTON_ATCK=FALSE; - self.BUTTON_ATCK2=FALSE; - if(vlen(self.origin-self.enemy.origin) > 1000) - self.bot_aim_whichfiretype = 0; - if(self.bot_aim_whichfiretype == 0) - { - float shoot; - - if(autocvar_g_balance_lightning_primary_speed) - shoot = bot_aim(autocvar_g_balance_lightning_primary_speed, 0, autocvar_g_balance_lightning_primary_lifetime, FALSE); - else - shoot = bot_aim(1000000, 0, 0.001, FALSE); - - if(shoot) - { - self.BUTTON_ATCK = TRUE; - if(random() < 0.01) self.bot_aim_whichfiretype = 1; - } - } - else // todo - { - //if(bot_aim(autocvar_g_balance_lightning_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_lightning_secondary_lifetime, TRUE)) - //{ - // self.BUTTON_ATCK2 = TRUE; - // if(random() < 0.03) self.bot_aim_whichfiretype = 0; - //} - } - */ - } - else if (req == WR_THINK) - { - if (self.BUTTON_ATCK) - { - if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this! - /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y) - weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_lightning_primary_animtime, w_ready); - else*/ - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); - - if (weapon_prepareattack(0, 0)) - { - if ((!self.lightning_beam) || wasfreed(self.lightning_beam)) - W_Lightning_Attack1(); - - if(!self.BUTTON_ATCK_prev) - { - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); - self.BUTTON_ATCK_prev = 1; - } - } - } - else // todo - { - if (self.BUTTON_ATCK_prev != 0) - { - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); - ATTACK_FINISHED(self) = time + autocvar_g_balance_lightning_primary_refire * W_WeaponRateFactor(); - } - self.BUTTON_ATCK_prev = 0; - } - - //if (self.BUTTON_ATCK2) - //if (weapon_prepareattack(1, autocvar_g_balance_lightning_secondary_refire)) - //{ - // W_Lightning_Attack2(); - // self.lightning_count = autocvar_g_balance_lightning_secondary_count; - // weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_lightning_secondary_animtime, w_lightning_checkattack); - // self.lightning_secondarytime = time + autocvar_g_balance_lightning_secondary_refire2 * W_WeaponRateFactor(); - //} - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_lightning.md3"); - precache_model ("models/weapons/v_lightning.md3"); - precache_model ("models/weapons/h_lightning.iqm"); - //precache_sound ("weapons/lightning_bounce.wav"); - precache_sound ("weapons/lightning_fire.wav"); - precache_sound ("weapons/lightning_fire2.wav"); - precache_sound ("weapons/lightning_impact.wav"); - //precache_sound ("weapons/lightning_impact_combo.wav"); - //precache_sound ("weapons/W_Lightning_Beam_fire.wav"); - } - else if (req == WR_SETUP) - weapon_setup(WEP_LIGHTNING); - else if (req == WR_CHECKAMMO1) - { - return !autocvar_g_balance_lightning_primary_ammo || (self.ammo_cells > 0); - } - else if (req == WR_CHECKAMMO2) - return self.ammo_cells >= autocvar_g_balance_lightning_secondary_ammo; - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - { - return WEAPON_ELECTRO_MURDER_ORBS; - } - else - { - if(w_deathtype & HITTYPE_BOUNCE) - return WEAPON_ELECTRO_MURDER_COMBO; - else - return WEAPON_ELECTRO_MURDER_BOLT; - } - } - else if (req == WR_RESETPLAYER) - { - //self.lightning_secondarytime = time; - } - return TRUE; -}; - -void LightningInit() -{ - weapon_action(WEP_LIGHTNING, WR_PRECACHE); - lightning_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 1); - lightning_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 2); - lightning_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 3); - lightning_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 4); -} - -void spawnfunc_weapon_lightning (void) // should this really be here? -{ - weapon_defaultspawnfunc(WEP_LIGHTNING); -} -#endif -#ifdef CSQC -float w_lightning(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - - if(w_deathtype & HITTYPE_SECONDARY) - { - pointparticles(particleeffectnum("lightning_ballexplode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); - } - else - { - pointparticles(particleeffectnum("lightning_impact"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/lightning_impact.wav"); - precache_sound("weapons/lightning_impact_combo.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_lightning.qh b/qcsrc/server/w_lightning.qh deleted file mode 100644 index 57d6ceb0d..000000000 --- a/qcsrc/server/w_lightning.qh +++ /dev/null @@ -1,2 +0,0 @@ -void LightningInit(); -vector lightning_shotorigin[4]; diff --git a/qcsrc/server/w_minelayer.qc b/qcsrc/server/w_minelayer.qc deleted file mode 100644 index b27d069a3..000000000 --- a/qcsrc/server/w_minelayer.qc +++ /dev/null @@ -1,563 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ MINE_LAYER, -/* function */ w_minelayer, -/* ammotype */ IT_ROCKETS, -/* impulse */ 4, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* model */ "minelayer", -/* shortname */ "minelayer", -/* fullname */ _("Mine Layer") -); -#else -#ifdef SVQC -void W_Mine_Think (void); -.float minelayer_detonate, mine_explodeanyway; -.float mine_time; -.vector mine_orientation; - -void spawnfunc_weapon_minelayer (void) -{ - weapon_defaultspawnfunc(WEP_MINE_LAYER); -} - -void W_Mine_Stick (entity to) -{ - spamsound (self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM); - - // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile - - entity newmine; - newmine = spawn(); - newmine.classname = self.classname; - - newmine.bot_dodge = self.bot_dodge; - newmine.bot_dodgerating = self.bot_dodgerating; - - newmine.owner = self.owner; - newmine.realowner = self.realowner; - setsize(newmine, '-4 -4 -4', '4 4 4'); - setorigin(newmine, self.origin); - setmodel(newmine, "models/mine.md3"); - newmine.angles = vectoangles(-trace_plane_normal); // face against the surface - - newmine.mine_orientation = -trace_plane_normal; - - newmine.takedamage = self.takedamage; - newmine.damageforcescale = self.damageforcescale; - newmine.health = self.health; - newmine.event_damage = self.event_damage; - newmine.spawnshieldtime = self.spawnshieldtime; - newmine.damagedbycontents = TRUE; - - newmine.movetype = MOVETYPE_NONE; // lock the mine in place - newmine.projectiledeathtype = self.projectiledeathtype; - - newmine.mine_time = self.mine_time; - - newmine.touch = func_null; - newmine.think = W_Mine_Think; - newmine.nextthink = time; - newmine.cnt = self.cnt; - newmine.flags = self.flags; - - remove(self); - self = newmine; - - if(to) - SetMovetypeFollow(self, to); -} - -void W_Mine_Explode () -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_damage, autocvar_g_balance_minelayer_edgedamage, autocvar_g_balance_minelayer_radius, world, world, autocvar_g_balance_minelayer_force, self.projectiledeathtype, other); - - if (self.realowner.weapon == WEP_MINE_LAYER) - { - entity oldself; - oldself = self; - self = self.realowner; - if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1)) - { - self.cnt = WEP_MINE_LAYER; - ATTACK_FINISHED(self) = time; - self.switchweapon = w_getbestweapon(self); - } - self = oldself; - } - self.realowner.minelayer_mines -= 1; - remove (self); -} - -void W_Mine_DoRemoteExplode () -{ - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) - self.velocity = self.mine_orientation; // particle fx and decals need .velocity - - RadiusDamage (self, self.realowner, autocvar_g_balance_minelayer_remote_damage, autocvar_g_balance_minelayer_remote_edgedamage, autocvar_g_balance_minelayer_remote_radius, world, world, autocvar_g_balance_minelayer_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world); - - if (self.realowner.weapon == WEP_MINE_LAYER) - { - entity oldself; - oldself = self; - self = self.realowner; - if (!weapon_action(WEP_MINE_LAYER, WR_CHECKAMMO1)) - { - self.cnt = WEP_MINE_LAYER; - ATTACK_FINISHED(self) = time; - self.switchweapon = w_getbestweapon(self); - } - self = oldself; - } - self.realowner.minelayer_mines -= 1; - remove (self); -} - -void W_Mine_RemoteExplode () -{ - if(self.realowner.deadflag == DEAD_NO) - if((self.spawnshieldtime >= 0) - ? (time >= self.spawnshieldtime) // timer - : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_minelayer_remote_radius) // safety device - ) - { - W_Mine_DoRemoteExplode(); - } -} - -void W_Mine_ProximityExplode () -{ - // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance - if(autocvar_g_balance_minelayer_protection && self.mine_explodeanyway == 0) - { - entity head; - head = findradius(self.origin, autocvar_g_balance_minelayer_radius); - while(head) - { - if(head == self.realowner || !IsDifferentTeam(head, self.realowner)) - return; - head = head.chain; - } - } - - self.mine_time = 0; - W_Mine_Explode(); -} - -float W_Mine_Count(entity e) -{ - float minecount = 0; - entity mine; - for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e) - minecount += 1; - - return minecount; -} - -void W_Mine_Think (void) -{ - entity head; - - self.nextthink = time; - - if(self.movetype == MOVETYPE_FOLLOW) - { - if(LostMovetypeFollow(self)) - { - UnsetMovetypeFollow(self); - self.movetype = MOVETYPE_NONE; - } - } - - // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this - // TODO: replace this mine_trigger.wav sound with a real countdown - if ((time > self.cnt) && (!self.mine_time)) - { - if(autocvar_g_balance_minelayer_lifetime_countdown > 0) - spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); - self.mine_time = time + autocvar_g_balance_minelayer_lifetime_countdown; - self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near. - } - - // a player's mines shall explode if he disconnects or dies - // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams? - if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_Mine_Explode(); - return; - } - - // set the mine for detonation when a foe gets close enough - head = findradius(self.origin, autocvar_g_balance_minelayer_proximityradius); - while(head) - { - if(IS_PLAYER(head) && head.deadflag == DEAD_NO) - if(head != self.realowner && IsDifferentTeam(head, self.realowner)) // don't trigger for team mates - if(!self.mine_time) - { - spamsound (self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); - self.mine_time = time + autocvar_g_balance_minelayer_time; - } - head = head.chain; - } - - // explode if it's time to - if(self.mine_time && time >= self.mine_time) - { - W_Mine_ProximityExplode(); - return; - } - - // remote detonation - if (self.realowner.weapon == WEP_MINE_LAYER) - if (self.realowner.deadflag == DEAD_NO) - if (self.minelayer_detonate) - W_Mine_RemoteExplode(); -} - -void W_Mine_Touch (void) -{ - if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) - return; // we're already a stuck mine, why do we get called? TODO does this even happen? - - if(WarpZone_Projectile_Touch()) - { - if(wasfreed(self)) - self.realowner.minelayer_mines -= 1; - return; - } - - if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO) - { - // hit a player - // don't stick - } - else - { - W_Mine_Stick(other); - } -} - -void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - - float is_from_enemy = (inflictor.realowner != self.realowner); - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1))) - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - self.angles = vectoangles(self.velocity); - - if (self.health <= 0) - W_PrepareExplosionByDamage(attacker, W_Mine_Explode); -} - -void W_Mine_Attack (void) -{ - entity mine; - entity flash; - - // scan how many mines we placed, and return if we reached our limit - if(autocvar_g_balance_minelayer_limit) - { - if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit) - { - // the refire delay keeps this message from being spammed - sprint(self, strcat("minelayer: You cannot place more than ^2", ftos(autocvar_g_balance_minelayer_limit), " ^7mines at a time\n") ); - play2(self, "weapons/unavailable.wav"); - return; - } - } - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, autocvar_g_balance_minelayer_damage); - pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - mine = WarpZone_RefSys_SpawnSameRefSys(self); - mine.owner = mine.realowner = self; - if(autocvar_g_balance_minelayer_detonatedelay >= 0) - mine.spawnshieldtime = time + autocvar_g_balance_minelayer_detonatedelay; - else - mine.spawnshieldtime = -1; - mine.classname = "mine"; - mine.bot_dodge = TRUE; - mine.bot_dodgerating = autocvar_g_balance_minelayer_damage * 2; // * 2 because it can detonate inflight which makes it even more dangerous - - mine.takedamage = DAMAGE_YES; - mine.damageforcescale = autocvar_g_balance_minelayer_damageforcescale; - mine.health = autocvar_g_balance_minelayer_health; - mine.event_damage = W_Mine_Damage; - mine.damagedbycontents = TRUE; - - mine.movetype = MOVETYPE_TOSS; - PROJECTILE_MAKETRIGGER(mine); - mine.projectiledeathtype = WEP_MINE_LAYER; - setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot - - setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point - W_SetupProjectileVelocity(mine, autocvar_g_balance_minelayer_speed, 0); - mine.angles = vectoangles (mine.velocity); - - mine.touch = W_Mine_Touch; - mine.think = W_Mine_Think; - mine.nextthink = time; - mine.cnt = time + (autocvar_g_balance_minelayer_lifetime - autocvar_g_balance_minelayer_lifetime_countdown); - mine.flags = FL_PROJECTILE; - mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY; - - CSQCProjectile(mine, TRUE, PROJECTILE_MINE, TRUE); - - // muzzle flash for 1st person view - flash = spawn (); - setmodel (flash, "models/flash.md3"); // precision set below - SUB_SetFade (flash, time, 0.1); - flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - W_AttachToShotorg(flash, '5 0 0'); - - // common properties - - other = mine; MUTATOR_CALLHOOK(EditProjectile); - - self.minelayer_mines = W_Mine_Count(self); -} - -float W_PlacedMines(float detonate) -{ - entity mine; - float minfound = 0; - - for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self) - { - if(detonate) - { - if(!mine.minelayer_detonate) - { - mine.minelayer_detonate = TRUE; - minfound = 1; - } - } - else - minfound = 1; - } - return minfound; -} - -float w_minelayer(float req) -{ - entity mine; - float ammo_amount; - - if (req == WR_AIM) - { - // aim and decide to fire if appropriate - if(self.minelayer_mines >= autocvar_g_balance_minelayer_limit) - self.BUTTON_ATCK = FALSE; - else - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_minelayer_speed, 0, autocvar_g_balance_minelayer_lifetime, FALSE); - if(skill >= 2) // skill 0 and 1 bots won't detonate mines! - { - // decide whether to detonate mines - entity targetlist, targ; - float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; - float selfdamage, teamdamage, enemydamage; - edgedamage = autocvar_g_balance_minelayer_edgedamage; - coredamage = autocvar_g_balance_minelayer_damage; - edgeradius = autocvar_g_balance_minelayer_radius; - recipricoledgeradius = 1 / edgeradius; - selfdamage = 0; - teamdamage = 0; - enemydamage = 0; - targetlist = findchainfloat(bot_attack, TRUE); - mine = find(world, classname, "mine"); - while (mine) - { - if (mine.realowner != self) - { - mine = find(mine, classname, "mine"); - continue; - } - targ = targetlist; - while (targ) - { - d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin); - d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); - // count potential damage according to type of target - if (targ == self) - selfdamage = selfdamage + d; - else if (targ.team == self.team && teamplay) - teamdamage = teamdamage + d; - else if (bot_shouldattack(targ)) - enemydamage = enemydamage + d; - targ = targ.chain; - } - mine = find(mine, classname, "mine"); - } - float desirabledamage; - desirabledamage = enemydamage; - if (time > self.invincible_finished && time > self.spawnshieldtime) - desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; - if (teamplay && self.team) - desirabledamage = desirabledamage - teamdamage; - - mine = find(world, classname, "mine"); - while (mine) - { - if (mine.realowner != self) - { - mine = find(mine, classname, "mine"); - continue; - } - makevectors(mine.v_angle); - targ = targetlist; - if (skill > 9) // normal players only do this for the target they are tracking - { - targ = targetlist; - while (targ) - { - if ( - (v_forward * normalize(mine.origin - targ.origin)< 0.1) - && desirabledamage > 0.1*coredamage - )self.BUTTON_ATCK2 = TRUE; - targ = targ.chain; - } - }else{ - float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); - //As the distance gets larger, a correct detonation gets near imposible - //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player - if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1) - if(IS_PLAYER(self.enemy)) - if(desirabledamage >= 0.1*coredamage) - if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) - self.BUTTON_ATCK2 = TRUE; - // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); - } - - mine = find(mine, classname, "mine"); - } - // if we would be doing at X percent of the core damage, detonate it - // but don't fire a new shot at the same time! - if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events - self.BUTTON_ATCK2 = TRUE; - if ((skill > 6.5) && (selfdamage > self.health)) - self.BUTTON_ATCK2 = FALSE; - //if(self.BUTTON_ATCK2 == TRUE) - // dprint(ftos(desirabledamage),"\n"); - if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < autocvar_g_balance_minelayer_ammo) // forced reload - { - // not if we're holding the minelayer without enough ammo, but can detonate existing mines - if not (W_PlacedMines(FALSE) && self.ammo_rockets < autocvar_g_balance_minelayer_ammo) - weapon_action(self.weapon, WR_RELOAD); - } - else if (self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, autocvar_g_balance_minelayer_refire)) - { - W_Mine_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minelayer_animtime, w_ready); - } - } - - if (self.BUTTON_ATCK2) - { - if(W_PlacedMines(TRUE)) - sound (self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM); - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/flash.md3"); - precache_model ("models/mine.md3"); - precache_model ("models/weapons/g_minelayer.md3"); - precache_model ("models/weapons/v_minelayer.md3"); - precache_model ("models/weapons/h_minelayer.iqm"); - precache_sound ("weapons/mine_det.wav"); - precache_sound ("weapons/mine_fire.wav"); - precache_sound ("weapons/mine_stick.wav"); - precache_sound ("weapons/mine_trigger.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_MINE_LAYER); - self.current_ammo = ammo_rockets; - } - else if (req == WR_CHECKAMMO1) - { - // don't switch while placing a mine - if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_minelayer_ammo; - ammo_amount += self.(weapon_load[WEP_MINE_LAYER]) >= autocvar_g_balance_minelayer_ammo; - return ammo_amount; - } - } - else if (req == WR_CHECKAMMO2) - { - if (W_PlacedMines(FALSE)) - return TRUE; - else - return FALSE; - } - else if (req == WR_RESETPLAYER) - { - self.minelayer_mines = 0; - } - else if (req == WR_RELOAD) - { - W_Reload(autocvar_g_balance_minelayer_ammo, autocvar_g_balance_minelayer_reload_ammo, autocvar_g_balance_minelayer_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_MINELAYER_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - return WEAPON_MINELAYER_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_minelayer(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/mine_exp.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_minstanex.qc b/qcsrc/server/w_minstanex.qc deleted file mode 100644 index 2abb66875..000000000 --- a/qcsrc/server/w_minstanex.qc +++ /dev/null @@ -1,213 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ MINSTANEX, -/* function */ w_minstanex, -/* ammotype */ IT_CELLS, -/* impulse */ 7, -/* flags */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* model */ "minstanex", -/* shortname */ "minstanex", -/* fullname */ _("MinstaNex") -); -#else -#ifdef SVQC -.float minstanex_lasthit; -.float jump_interval; - -void W_MinstaNex_Attack (void) -{ - float flying; - flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last - - W_SetupShot (self, TRUE, 0, "weapons/minstanexfire.wav", CH_WEAPON_A, 10000); - - yoda = 0; - damage_goodhits = 0; - FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, 10000, 800, 0, 0, 0, 0, WEP_MINSTANEX); - - if(yoda && flying) - Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); - if(damage_goodhits && self.minstanex_lasthit) - { - Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE); - damage_goodhits = 0; // only every second time - } - - self.minstanex_lasthit = damage_goodhits; - - pointparticles(particleeffectnum("nex_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - // teamcolor / hit beam effect - vector v; - v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); - switch(self.team) - { - case NUM_TEAM_1: // Red - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3RED"), w_shotorg, v); - break; - case NUM_TEAM_2: // Blue - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3BLUE"), w_shotorg, v); - break; - case NUM_TEAM_3: // Yellow - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3YELLOW"), w_shotorg, v); - break; - case NUM_TEAM_4: // Pink - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3PINK"), w_shotorg, v); - break; - default: - if(damage_goodhits) - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3_HIT"), w_shotorg, v); - else - WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), w_shotorg, v); - break; - } - - W_DecreaseAmmo(ammo_cells, ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo), autocvar_g_balance_minstanex_reload_ammo); -} - -void spawnfunc_weapon_minstanex (void); // defined in t_items.qc - -float w_minstanex(float req) -{ - float ammo_amount; - float minstanex_ammo; - - // now multiple WR_s use this - minstanex_ammo = ((g_minstagib) ? 1 : autocvar_g_balance_minstanex_ammo); - - if (req == WR_AIM) - { - if(self.ammo_cells > 0) - self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE); - else - self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE); - } - else if (req == WR_THINK) - { - // if the laser uses load, we also consider its ammo for reloading - if(autocvar_g_balance_minstanex_reload_ammo && autocvar_g_balance_minstanex_laser_ammo && self.clip_load < min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if(autocvar_g_balance_minstanex_reload_ammo && self.clip_load < minstanex_ammo) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if (self.BUTTON_ATCK) - { - if (weapon_prepareattack(0, autocvar_g_balance_minstanex_refire)) - { - W_MinstaNex_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_minstanex_animtime, w_ready); - } - } - else if (self.BUTTON_ATCK2) - { - if (self.jump_interval <= time) - if (weapon_prepareattack(1, -1)) - { - // handle refire manually, so that primary and secondary can be fired without conflictions (important for minstagib) - self.jump_interval = time + autocvar_g_balance_minstanex_laser_refire * W_WeaponRateFactor(); - - // decrease ammo for the laser? - if(autocvar_g_balance_minstanex_laser_ammo) - W_DecreaseAmmo(ammo_cells, autocvar_g_balance_minstanex_laser_ammo, autocvar_g_balance_minstanex_reload_ammo); - - // ugly minstagib hack to reuse the fire mode of the laser - float w; - w = self.weapon; - self.weapon = WEP_LASER; - W_Laser_Shockwave(); - self.weapon = w; - - // now do normal refire - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_minstanex_laser_animtime, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/nexflash.md3"); - precache_model ("models/weapons/g_minstanex.md3"); - precache_model ("models/weapons/v_minstanex.md3"); - precache_model ("models/weapons/h_minstanex.iqm"); - precache_sound ("weapons/minstanexfire.wav"); - precache_sound ("weapons/nexwhoosh1.wav"); - precache_sound ("weapons/nexwhoosh2.wav"); - precache_sound ("weapons/nexwhoosh3.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - W_Laser(WR_PRECACHE); - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_MINSTANEX); - self.current_ammo = ammo_cells; - self.minstanex_lasthit = 0; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_cells >= minstanex_ammo; - ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= minstanex_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - if(!autocvar_g_balance_minstanex_laser_ammo) - return TRUE; - ammo_amount = self.ammo_cells >= autocvar_g_balance_minstanex_laser_ammo; - ammo_amount += self.(weapon_load[WEP_MINSTANEX]) >= autocvar_g_balance_minstanex_laser_ammo; - return ammo_amount; - } - else if (req == WR_RESETPLAYER) - { - self.minstanex_lasthit = 0; - } - else if (req == WR_RELOAD) - { - float used_ammo; - if(autocvar_g_balance_minstanex_laser_ammo) - used_ammo = min(minstanex_ammo, autocvar_g_balance_minstanex_laser_ammo); - else - used_ammo = minstanex_ammo; - - W_Reload(used_ammo, autocvar_g_balance_minstanex_reload_ammo, autocvar_g_balance_minstanex_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_THINKING_WITH_PORTALS; - } - else if (req == WR_KILLMESSAGE) - { - return WEAPON_MINSTANEX_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_minstanex(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/neximpact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_nex.qc b/qcsrc/server/w_nex.qc deleted file mode 100644 index dc3c30ff6..000000000 --- a/qcsrc/server/w_nex.qc +++ /dev/null @@ -1,275 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ NEX, -/* function */ w_nex, -/* ammotype */ IT_CELLS, -/* impulse */ 7, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* model */ "nex", -/* shortname */ "nex", -/* fullname */ _("Nex") -); -#else -#ifdef SVQC - -void SendCSQCNexBeamParticle(float charge) { - vector v; - v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_NEXGUNBEAMPARTICLE); - WriteCoord(MSG_BROADCAST, w_shotorg_x); - WriteCoord(MSG_BROADCAST, w_shotorg_y); - WriteCoord(MSG_BROADCAST, w_shotorg_z); - WriteCoord(MSG_BROADCAST, v_x); - WriteCoord(MSG_BROADCAST, v_y); - WriteCoord(MSG_BROADCAST, v_z); - WriteByte(MSG_BROADCAST, bound(0, 255 * charge, 255)); -} - -void W_Nex_Attack (float issecondary) -{ - float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge; - if(issecondary) - { - mydmg = autocvar_g_balance_nex_secondary_damage; - myforce = autocvar_g_balance_nex_secondary_force; - mymindist = autocvar_g_balance_nex_secondary_damagefalloff_mindist; - mymaxdist = autocvar_g_balance_nex_secondary_damagefalloff_maxdist; - myhalflife = autocvar_g_balance_nex_secondary_damagefalloff_halflife; - myforcehalflife = autocvar_g_balance_nex_secondary_damagefalloff_forcehalflife; - myammo = autocvar_g_balance_nex_secondary_ammo; - } - else - { - mydmg = autocvar_g_balance_nex_primary_damage; - myforce = autocvar_g_balance_nex_primary_force; - mymindist = autocvar_g_balance_nex_primary_damagefalloff_mindist; - mymaxdist = autocvar_g_balance_nex_primary_damagefalloff_maxdist; - myhalflife = autocvar_g_balance_nex_primary_damagefalloff_halflife; - myforcehalflife = autocvar_g_balance_nex_primary_damagefalloff_forcehalflife; - myammo = autocvar_g_balance_nex_primary_ammo; - } - - float flying; - flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last - - if(autocvar_g_balance_nex_charge) - { - charge = autocvar_g_balance_nex_charge_mindmg / mydmg + (1 - autocvar_g_balance_nex_charge_mindmg / mydmg) * self.nex_charge; - self.nex_charge *= autocvar_g_balance_nex_charge_shot_multiplier; // do this AFTER setting mydmg/myforce - // O RLY? -- divVerent - // YA RLY -- FruitieX - } - else - charge = 1; - mydmg *= charge; - myforce *= charge; - - W_SetupShot (self, TRUE, 5, "weapons/nexfire.wav", CH_WEAPON_A, mydmg); - if(charge > autocvar_g_balance_nex_charge_animlimit && autocvar_g_balance_nex_charge_animlimit) // if the Nex is overcharged, we play an extra sound - { - sound (self, CH_WEAPON_B, "weapons/nexcharge.wav", VOL_BASE * (charge - 0.5 * autocvar_g_balance_nex_charge_animlimit) / (1 - 0.5 * autocvar_g_balance_nex_charge_animlimit), ATTN_NORM); - } - - yoda = 0; - FireRailgunBullet (w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_NEX); - - if(yoda && flying) - Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); - - //beam and muzzle flash done on client - SendCSQCNexBeamParticle(charge); - - W_DecreaseAmmo(ammo_cells, myammo, autocvar_g_balance_nex_reload_ammo); -} - -void spawnfunc_weapon_nex (void); // defined in t_items.qc - -.float nex_chargepool_pauseregen_finished; -float w_nex(float req) -{ - float dt; - float ammo_amount; - if (req == WR_AIM) - { - if(bot_aim(1000000, 0, 1, FALSE)) - self.BUTTON_ATCK = TRUE; - else - { - if(autocvar_g_balance_nex_charge) - self.BUTTON_ATCK2 = TRUE; - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_nex_charge && self.nex_charge < autocvar_g_balance_nex_charge_limit) - self.nex_charge = min(1, self.nex_charge + autocvar_g_balance_nex_charge_rate * frametime / W_TICSPERFRAME); - - if(autocvar_g_balance_nex_secondary_chargepool) - if(self.nex_chargepool_ammo < 1) - { - if(self.nex_chargepool_pauseregen_finished < time) - self.nex_chargepool_ammo = min(1, self.nex_chargepool_ammo + autocvar_g_balance_nex_secondary_chargepool_regen * frametime / W_TICSPERFRAME); - self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_nex_secondary_chargepool_pause_health_regen); - } - - if(autocvar_g_balance_nex_reload_ammo && self.clip_load < min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else - { - if (self.BUTTON_ATCK) - { - if (weapon_prepareattack(0, autocvar_g_balance_nex_primary_refire)) - { - W_Nex_Attack(0); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_primary_animtime, w_ready); - } - } - if ((autocvar_g_balance_nex_secondary_charge && !autocvar_g_balance_nex_secondary) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2) - { - if(autocvar_g_balance_nex_secondary_charge) - { - self.nex_charge_rottime = time + autocvar_g_balance_nex_charge_rot_pause; - dt = frametime / W_TICSPERFRAME; - - if(self.nex_charge < 1) - { - if(autocvar_g_balance_nex_secondary_chargepool) - { - if(autocvar_g_balance_nex_secondary_ammo) - { - // always deplete if secondary is held - self.nex_chargepool_ammo = max(0, self.nex_chargepool_ammo - autocvar_g_balance_nex_secondary_ammo * dt); - - dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate); - self.nex_chargepool_pauseregen_finished = time + autocvar_g_balance_nex_secondary_chargepool_pause_regen; - dt = min(dt, self.nex_chargepool_ammo); - dt = max(0, dt); - - self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate; - } - } - - else if(autocvar_g_balance_nex_secondary_ammo) - { - if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed - { - dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate); - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - // if this weapon is reloadable, decrease its load. Else decrease the player's ammo - if(autocvar_g_balance_nex_reload_ammo) - { - dt = min(dt, (self.clip_load - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo); - dt = max(0, dt); - if(dt > 0) - { - self.clip_load = max(autocvar_g_balance_nex_secondary_ammo, self.clip_load - autocvar_g_balance_nex_secondary_ammo * dt); - } - self.(weapon_load[WEP_NEX]) = self.clip_load; - } - else - { - dt = min(dt, (self.ammo_cells - autocvar_g_balance_nex_primary_ammo) / autocvar_g_balance_nex_secondary_ammo); - dt = max(0, dt); - if(dt > 0) - { - self.ammo_cells = max(autocvar_g_balance_nex_secondary_ammo, self.ammo_cells - autocvar_g_balance_nex_secondary_ammo * dt); - } - } - } - self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate; - } - } - - else - { - dt = min(dt, (1 - self.nex_charge) / autocvar_g_balance_nex_secondary_charge_rate); - self.nex_charge += dt * autocvar_g_balance_nex_secondary_charge_rate; - } - } - } - else if(autocvar_g_balance_nex_secondary) - { - if (weapon_prepareattack(0, autocvar_g_balance_nex_secondary_refire)) - { - W_Nex_Attack(1); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_nex_secondary_animtime, w_ready); - } - } - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/nexflash.md3"); - precache_model ("models/weapons/g_nex.md3"); - precache_model ("models/weapons/v_nex.md3"); - precache_model ("models/weapons/h_nex.iqm"); - precache_sound ("weapons/nexfire.wav"); - precache_sound ("weapons/nexcharge.wav"); - precache_sound ("weapons/nexwhoosh1.wav"); - precache_sound ("weapons/nexwhoosh2.wav"); - precache_sound ("weapons/nexwhoosh3.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_NEX); - self.current_ammo = ammo_cells; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_primary_ammo; - ammo_amount += (autocvar_g_balance_nex_reload_ammo && self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_primary_ammo); - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - if(autocvar_g_balance_nex_secondary) - { - // don't allow charging if we don't have enough ammo - ammo_amount = self.ammo_cells >= autocvar_g_balance_nex_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_NEX]) >= autocvar_g_balance_nex_secondary_ammo; - return ammo_amount; - } - else - { - return FALSE; // zoom is not a fire mode - } - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_nex_primary_ammo, autocvar_g_balance_nex_secondary_ammo), autocvar_g_balance_nex_reload_ammo, autocvar_g_balance_nex_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_THINKING_WITH_PORTALS; - } - else if (req == WR_KILLMESSAGE) - { - return WEAPON_NEX_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_nex(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum("nex_impact"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/neximpact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/neximpact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_porto.qc b/qcsrc/server/w_porto.qc deleted file mode 100644 index fad480d01..000000000 --- a/qcsrc/server/w_porto.qc +++ /dev/null @@ -1,391 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ PORTO, -/* function */ w_porto, -/* ammotype */ 0, -/* impulse */ 0, -/* flags */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON, -/* rating */ 0, -/* model */ "porto" , -/* shortname */ "porto", -/* fullname */ _("Port-O-Launch") -); -#else -#ifdef SVQC -.entity porto_current; -.vector porto_v_angle; // holds "held" view angles -.float porto_v_angle_held; -.vector right_vector; - -void W_Porto_Success (void) -{ - if(self.realowner == world) - { - objerror("Cannot succeed successfully: no owner\n"); - return; - } - - self.realowner.porto_current = world; - remove(self); -} - -string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo); -void W_Porto_Fail (float failhard) -{ - if(self.realowner == world) - { - objerror("Cannot fail successfully: no owner\n"); - return; - } - - // no portals here! - if(self.cnt < 0) - { - Portal_ClearWithID(self.realowner, self.portal_id); - } - - self.realowner.porto_current = world; - - if(self.cnt < 0 && !failhard && self.realowner.playerid == self.playerid && self.realowner.deadflag == DEAD_NO && !WEPSET_CONTAINS_EW(self.realowner, WEP_PORTO)) - { - setsize (self, '-16 -16 0', '16 16 32'); - setorigin(self, self.origin + trace_plane_normal); - if(move_out_of_solid(self)) - { - self.flags = FL_ITEM; - self.velocity = trigger_push_calculatevelocity(self.origin, self.realowner, 128); - tracetoss(self, self); - if(vlen(trace_endpos - self.realowner.origin) < 128) - { - W_ThrowNewWeapon(self.realowner, WEP_PORTO, 0, self.origin, self.velocity); - centerprint(self.realowner, "^1Portal deployment failed.\n\n^2Catch it to try again!"); - } - } - } - remove(self); -} - -void W_Porto_Remove (entity p) -{ - if(p.porto_current.realowner == p && p.porto_current.classname == "porto") - { - entity oldself; - oldself = self; - self = p.porto_current; - W_Porto_Fail(1); - self = oldself; - } -} - -void W_Porto_Think (void) -{ - trace_plane_normal = '0 0 0'; - if(self.realowner.playerid != self.playerid) - remove(self); - else - W_Porto_Fail(0); -} - -void W_Porto_Touch (void) -{ - vector norm; - - // do not use PROJECTILE_TOUCH here - // FIXME but DO handle warpzones! - - if(other.classname == "portal") - return; // handled by the portal - - norm = trace_plane_normal; - if(trace_ent.iscreature) - { - traceline(trace_ent.origin, trace_ent.origin + '0 0 2' * PL_MIN_z, MOVE_WORLDONLY, self); - if(trace_fraction >= 1) - return; - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) - return; - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) - return; - } - - if(self.realowner.playerid != self.playerid) - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); - remove(self); - } - else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) - { - spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTN_NORM); - // just reflect - self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * trace_plane_normal); - self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * trace_plane_normal)); - } - else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); - W_Porto_Fail(0); - if(self.cnt < 0) - Portal_ClearAll_PortalsOnly(self.realowner); - } - else if(self.cnt == 0) - { - // in-portal only - if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) - { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); - trace_plane_normal = norm; - centerprint(self.realowner, "^1In^7-portal created."); - W_Porto_Success(); - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); - trace_plane_normal = norm; - W_Porto_Fail(0); - } - } - else if(self.cnt == 1) - { - // out-portal only - if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) - { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); - trace_plane_normal = norm; - centerprint(self.realowner, "^4Out^7-portal created."); - W_Porto_Success(); - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); - trace_plane_normal = norm; - W_Porto_Fail(0); - } - } - else if(self.effects & EF_RED) - { - self.effects += EF_BLUE - EF_RED; - if(Portal_SpawnInPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) - { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); - trace_plane_normal = norm; - centerprint(self.realowner, "^1In^7-portal created."); - self.right_vector = self.right_vector - 2 * trace_plane_normal * (self.right_vector * norm); - self.angles = vectoangles(self.velocity - 2 * trace_plane_normal * (self.velocity * norm)); - CSQCProjectile(self, TRUE, PROJECTILE_PORTO_BLUE, TRUE); // change type - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); - trace_plane_normal = norm; - Portal_ClearAll_PortalsOnly(self.realowner); - W_Porto_Fail(0); - } - } - else - { - if(self.realowner.portal_in.portal_id == self.portal_id) - { - if(Portal_SpawnOutPortalAtTrace(self.realowner, self.right_vector, self.portal_id)) - { - sound(self, CH_SHOTS, "porto/create.wav", VOL_BASE, ATTN_NORM); - trace_plane_normal = norm; - centerprint(self.realowner, "^4Out^7-portal created."); - W_Porto_Success(); - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); - Portal_ClearAll_PortalsOnly(self.realowner); - W_Porto_Fail(0); - } - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTN_NORM); - Portal_ClearAll_PortalsOnly(self.realowner); - W_Porto_Fail(0); - } - } -} - -void W_Porto_Attack (float type) -{ - entity gren; - - W_SetupShot (self, FALSE, 4, "porto/fire.wav", CH_WEAPON_A, 0); - // always shoot from the eye - w_shotdir = v_forward; - w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward; - - //pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - gren = spawn (); - gren.cnt = type; - gren.owner = gren.realowner = self; - gren.playerid = self.playerid; - gren.classname = "porto"; - gren.bot_dodge = TRUE; - gren.bot_dodgerating = 200; - gren.movetype = MOVETYPE_BOUNCEMISSILE; - PROJECTILE_MAKETRIGGER(gren); - gren.effects = EF_RED; - gren.scale = 4; - setorigin(gren, w_shotorg); - setsize(gren, '0 0 0', '0 0 0'); - - if(type > 0) - gren.nextthink = time + autocvar_g_balance_porto_secondary_lifetime; - else - gren.nextthink = time + autocvar_g_balance_porto_primary_lifetime; - gren.think = W_Porto_Think; - gren.touch = W_Porto_Touch; - - if(type > 0) - { - if(self.items & IT_STRENGTH) - W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed * autocvar_g_balance_powerup_strength_force, 0); - else - W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_secondary_speed, 0); - } - else - { - if(self.items & IT_STRENGTH) - W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed * autocvar_g_balance_powerup_strength_force, 0); - else - W_SetupProjectileVelocity(gren, autocvar_g_balance_porto_primary_speed, 0); - } - - gren.angles = vectoangles (gren.velocity); - gren.flags = FL_PROJECTILE; - - gren.portal_id = time; - self.porto_current = gren; - gren.playerid = self.playerid; - fixedmakevectors(fixedvectoangles(gren.velocity)); - gren.right_vector = v_right; - - gren.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP; - - if(type > 0) - CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_BLUE, TRUE); - else - CSQCProjectile(gren, TRUE, PROJECTILE_PORTO_RED, TRUE); - - other = gren; MUTATOR_CALLHOOK(EditProjectile); -} - -void spawnfunc_weapon_porto (void) -{ - weapon_defaultspawnfunc(WEP_PORTO); -} - -float w_nexball_weapon(float req); -float w_porto(float req) -{ - //vector v_angle_save; - - if (g_nexball) { return w_nexball_weapon(req); } - if (req == WR_AIM) - { - self.BUTTON_ATCK = FALSE; - self.BUTTON_ATCK2 = FALSE; - if(!autocvar_g_balance_porto_secondary) - if(bot_aim(autocvar_g_balance_porto_primary_speed, 0, autocvar_g_balance_grenadelauncher_primary_lifetime, FALSE)) - self.BUTTON_ATCK = TRUE; - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_porto_secondary) - { - if (self.BUTTON_ATCK) - if (!self.porto_current) - if (!self.porto_forbidden) - if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire)) - { - W_Porto_Attack(0); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready); - } - - if (self.BUTTON_ATCK2) - if (!self.porto_current) - if (!self.porto_forbidden) - if (weapon_prepareattack(1, autocvar_g_balance_porto_secondary_refire)) - { - W_Porto_Attack(1); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_porto_secondary_animtime, w_ready); - } - } - else - { - if(self.porto_v_angle_held) - { - if(!self.BUTTON_ATCK2) - { - self.porto_v_angle_held = 0; - - ClientData_Touch(self); - } - } - else - { - if(self.BUTTON_ATCK2) - { - self.porto_v_angle = self.v_angle; - self.porto_v_angle_held = 1; - - ClientData_Touch(self); - } - } - if(self.porto_v_angle_held) - makevectors(self.porto_v_angle); // override the previously set angles - - if (self.BUTTON_ATCK) - if (!self.porto_current) - if (!self.porto_forbidden) - if (weapon_prepareattack(0, autocvar_g_balance_porto_primary_refire)) - { - W_Porto_Attack(-1); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_porto_primary_animtime, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_porto.md3"); - precache_model ("models/weapons/v_porto.md3"); - precache_model ("models/weapons/h_porto.iqm"); - precache_model ("models/portal.md3"); - precache_sound ("porto/bounce.wav"); - precache_sound ("porto/create.wav"); - precache_sound ("porto/expire.wav"); - precache_sound ("porto/explode.wav"); - precache_sound ("porto/fire.wav"); - precache_sound ("porto/unsupported.wav"); - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_PORTO); - self.current_ammo = ammo_none; - } - else if (req == WR_RESETPLAYER) - { - self.porto_current = world; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_porto(float req) -{ - if(req == WR_IMPACTEFFECT) - { - print("Since when does Porto send DamageInfo?\n"); - } - else if(req == WR_PRECACHE) - { - // nothing to do - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_rifle.qc b/qcsrc/server/w_rifle.qc deleted file mode 100644 index 8ed4491d7..000000000 --- a/qcsrc/server/w_rifle.qc +++ /dev/null @@ -1,265 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ RIFLE, -/* function */ w_rifle, -/* ammotype */ IT_NAILS, -/* impulse */ 7, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "campingrifle", -/* shortname */ "rifle", -/* fullname */ _("Rifle") -); -#else -#ifdef SVQC - -.float rifle_accumulator; - -void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSpeed, float pLifetime, float pAmmo, float deathtype, float pBulletConstant, float pTracer, float pShots, string pSound) -{ - float i; - - W_DecreaseAmmo(ammo_nails, pAmmo, autocvar_g_balance_rifle_reload_ammo); - - W_SetupShot (self, autocvar_g_antilag_bullets && pSpeed >= autocvar_g_antilag_bullets, 2, pSound, CH_WEAPON_A, pDamage * pShots); - - pointparticles(particleeffectnum("rifle_muzzleflash"), w_shotorg, w_shotdir * 2000, 1); - - if(self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) // if zoomed, shoot from the eye - { - w_shotdir = v_forward; - w_shotorg = self.origin + self.view_ofs + ((w_shotorg - self.origin - self.view_ofs) * v_forward) * v_forward; - } - - for(i = 0; i < pShots; ++i) - fireBallisticBullet(w_shotorg, w_shotdir, pSpread, pSpeed, pLifetime, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE), 1, pBulletConstant); - endFireBallisticBullet(); - - if (autocvar_g_casings >= 2) - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); -} - -void W_Rifle_Attack() -{ - W_Rifle_FireBullet(autocvar_g_balance_rifle_primary_spread, autocvar_g_balance_rifle_primary_damage, autocvar_g_balance_rifle_primary_force, autocvar_g_balance_rifle_primary_speed, autocvar_g_balance_rifle_primary_lifetime, autocvar_g_balance_rifle_primary_ammo, WEP_RIFLE, autocvar_g_balance_rifle_primary_bulletconstant, autocvar_g_balance_rifle_primary_tracer, autocvar_g_balance_rifle_primary_shots, "weapons/campingrifle_fire.wav"); -} - -void W_Rifle_Attack2() -{ - W_Rifle_FireBullet(autocvar_g_balance_rifle_secondary_spread, autocvar_g_balance_rifle_secondary_damage, autocvar_g_balance_rifle_secondary_force, autocvar_g_balance_rifle_secondary_speed, autocvar_g_balance_rifle_secondary_lifetime, autocvar_g_balance_rifle_secondary_ammo, WEP_RIFLE | HITTYPE_SECONDARY, autocvar_g_balance_rifle_secondary_bulletconstant, autocvar_g_balance_rifle_secondary_tracer, autocvar_g_balance_rifle_secondary_shots, "weapons/campingrifle_fire2.wav"); -} - -void spawnfunc_weapon_rifle (void) -{ - weapon_defaultspawnfunc(WEP_RIFLE); -} - -// compatibility alias -void spawnfunc_weapon_campingrifle (void) -{ - spawnfunc_weapon_rifle(); -} -void spawnfunc_weapon_sniperrifle (void) -{ - spawnfunc_weapon_rifle(); -} - -.void(void) rifle_bullethail_attackfunc; -.float rifle_bullethail_frame; -.float rifle_bullethail_animtime; -.float rifle_bullethail_refire; -void W_Rifle_BulletHail_Continue() -{ - float r, sw, af; - - sw = self.switchweapon; // make it not detect weapon changes as reason to abort firing - af = ATTACK_FINISHED(self); - self.switchweapon = self.weapon; - ATTACK_FINISHED(self) = time; - print(ftos(self.ammo_nails), "\n"); - r = weapon_prepareattack(self.rifle_bullethail_frame == WFRAME_FIRE2, self.rifle_bullethail_refire); - if(self.switchweapon == self.weapon) - self.switchweapon = sw; - if(r) - { - self.rifle_bullethail_attackfunc(); - weapon_thinkf(self.rifle_bullethail_frame, self.rifle_bullethail_animtime, W_Rifle_BulletHail_Continue); - print("thinkf set\n"); - } - else - { - ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time - print("out of ammo... ", ftos(self.weaponentity.state), "\n"); - } -} - -void W_Rifle_BulletHail(float mode, void(void) AttackFunc, float fr, float animtime, float refire) -{ - // if we get here, we have at least one bullet to fire - AttackFunc(); - if(mode) - { - // continue hail - self.rifle_bullethail_attackfunc = AttackFunc; - self.rifle_bullethail_frame = fr; - self.rifle_bullethail_animtime = animtime; - self.rifle_bullethail_refire = refire; - weapon_thinkf(fr, animtime, W_Rifle_BulletHail_Continue); - } - else - { - // just one shot - weapon_thinkf(fr, animtime, w_ready); - } -} - -.float bot_secondary_riflemooth; -float w_rifle(float req) -{ - float ammo_amount; - - if (req == WR_AIM) - { - self.BUTTON_ATCK=FALSE; - self.BUTTON_ATCK2=FALSE; - if(vlen(self.origin-self.enemy.origin) > 1000) - self.bot_secondary_riflemooth = 0; - if(self.bot_secondary_riflemooth == 0) - { - if(bot_aim(autocvar_g_balance_rifle_primary_speed, 0, autocvar_g_balance_rifle_primary_lifetime, FALSE)) - { - self.BUTTON_ATCK = TRUE; - if(random() < 0.01) self.bot_secondary_riflemooth = 1; - } - } - else - { - if(bot_aim(autocvar_g_balance_rifle_secondary_speed, 0, autocvar_g_balance_rifle_secondary_lifetime, FALSE)) - { - self.BUTTON_ATCK2 = TRUE; - if(random() < 0.03) self.bot_secondary_riflemooth = 0; - } - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else - { - self.rifle_accumulator = bound(time - autocvar_g_balance_rifle_bursttime, self.rifle_accumulator, time); - if (self.BUTTON_ATCK) - if (weapon_prepareattack_check(0, autocvar_g_balance_rifle_primary_refire)) - if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_primary_burstcost) - { - weapon_prepareattack_do(0, autocvar_g_balance_rifle_primary_refire); - W_Rifle_BulletHail(autocvar_g_balance_rifle_primary_bullethail, W_Rifle_Attack, WFRAME_FIRE1, autocvar_g_balance_rifle_primary_animtime, autocvar_g_balance_rifle_primary_refire); - self.rifle_accumulator += autocvar_g_balance_rifle_primary_burstcost; - } - if (self.BUTTON_ATCK2) - { - if (autocvar_g_balance_rifle_secondary) - { - if(autocvar_g_balance_rifle_secondary_reload) - weapon_action(self.weapon, WR_RELOAD); - else - { - if (weapon_prepareattack_check(1, autocvar_g_balance_rifle_secondary_refire)) - if (time >= self.rifle_accumulator + autocvar_g_balance_rifle_secondary_burstcost) - { - weapon_prepareattack_do(1, autocvar_g_balance_rifle_secondary_refire); - W_Rifle_BulletHail(autocvar_g_balance_rifle_secondary_bullethail, W_Rifle_Attack2, WFRAME_FIRE2, autocvar_g_balance_rifle_secondary_animtime, autocvar_g_balance_rifle_primary_refire); - self.rifle_accumulator += autocvar_g_balance_rifle_secondary_burstcost; - } - } - } - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_campingrifle.md3"); - precache_model ("models/weapons/v_campingrifle.md3"); - precache_model ("models/weapons/h_campingrifle.iqm"); - precache_sound ("weapons/campingrifle_fire.wav"); - precache_sound ("weapons/campingrifle_fire2.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_RIFLE); - self.current_ammo = ammo_nails; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_primary_ammo; - ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - ammo_amount = self.ammo_nails >= autocvar_g_balance_rifle_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_RIFLE]) >= autocvar_g_balance_rifle_secondary_ammo; - return ammo_amount; - } - else if (req == WR_RESETPLAYER) - { - self.rifle_accumulator = time - autocvar_g_balance_rifle_bursttime; - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_rifle_primary_ammo, autocvar_g_balance_rifle_secondary_ammo), autocvar_g_balance_rifle_reload_ammo, autocvar_g_balance_rifle_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_THINKING_WITH_PORTALS; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - { - if(w_deathtype & HITTYPE_BOUNCE) - return WEAPON_RIFLE_MURDER_HAIL_PIERCING; - else - return WEAPON_RIFLE_MURDER_HAIL; - } - else - { - if(w_deathtype & HITTYPE_BOUNCE) - return WEAPON_RIFLE_MURDER_PIERCING; - else - return WEAPON_RIFLE_MURDER; - } - } - return TRUE; -} -#endif -#ifdef CSQC -float w_rifle(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1); - if(!w_issilent) - { - if(w_random < 0.2) - sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.4) - sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.5) - sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/ric1.wav"); - precache_sound("weapons/ric2.wav"); - precache_sound("weapons/ric3.wav"); - } - - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_rocketlauncher.qc b/qcsrc/server/w_rocketlauncher.qc deleted file mode 100644 index 504167cda..000000000 --- a/qcsrc/server/w_rocketlauncher.qc +++ /dev/null @@ -1,491 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ ROCKET_LAUNCHER, -/* function */ w_rlauncher, -/* ammotype */ IT_ROCKETS, -/* impulse */ 9, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* model */ "rl", -/* shortname */ "rocketlauncher", -/* fullname */ _("Rocket Launcher") -); -#else -#ifdef SVQC -.float rl_release; -.float rl_detonate_later; - -void W_Rocket_Unregister() -{ - if(self.realowner && self.realowner.lastrocket == self) - { - self.realowner.lastrocket = world; - // self.realowner.rl_release = 1; - } -} - -void W_Rocket_Explode () -{ - W_Rocket_Unregister(); - - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other); - - if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) - { - if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) - { - self.realowner.cnt = WEP_ROCKET_LAUNCHER; - ATTACK_FINISHED(self.realowner) = time; - self.realowner.switchweapon = w_getbestweapon(self.realowner); - } - } - remove (self); -} - -void W_Rocket_DoRemoteExplode () -{ - W_Rocket_Unregister(); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world); - - if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) - { - if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) - { - self.realowner.cnt = WEP_ROCKET_LAUNCHER; - ATTACK_FINISHED(self.realowner) = time; - self.realowner.switchweapon = w_getbestweapon(self.realowner); - } - } - remove (self); -} - -void W_Rocket_RemoteExplode() -{ - if(self.realowner.deadflag == DEAD_NO) - if(self.realowner.lastrocket) - { - if((self.spawnshieldtime >= 0) - ? (time >= self.spawnshieldtime) // timer - : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device - ) - { - W_Rocket_DoRemoteExplode(); - } - } -} - -vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos) -{ - if(thisdir * goaldir > maxturn_cos) - return goaldir; - if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite - return thisdir; // refuse to guide (better than letting a numerical error happen) - float f, m2; - vector v; - // solve: - // g = normalize(thisdir + goaldir * X) - // thisdir * g = maxturn - // - // gg = thisdir + goaldir * X - // (thisdir * gg)^2 = maxturn^2 * (gg * gg) - // - // (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir) - f = thisdir * goaldir; - // (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f) - // 0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1) - m2 = maxturn_cos * maxturn_cos; - v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1); - return normalize(thisdir + goaldir * v_y); // the larger solution! -} -// assume thisdir == -goaldir: -// f == -1 -// v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1) -// (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0 -// x^2 - 2 * x + 1 = 0 -// (x - 1)^2 = 0 -// x = 1 -// normalize(thisdir + goaldir) -// normalize(0) - -void W_Rocket_Think (void) -{ - vector desireddir, olddir, newdir, desiredorigin, goal; -#if 0 - float cosminang, cosmaxang, cosang; -#endif - float velspeed, f; - self.nextthink = time; - if (time > self.cnt) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_Rocket_Explode (); - return; - } - - // accelerate - makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0'); - velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward); - if (velspeed > 0) - self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed); - - // laser guided, or remote detonation - if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) - { - if(self == self.realowner.lastrocket) - if not(self.realowner.rl_release) - if not(self.BUTTON_ATCK2) - if(autocvar_g_balance_rocketlauncher_guiderate) - if(time > self.pushltime) - if(self.realowner.deadflag == DEAD_NO) - { - f = autocvar_g_balance_rocketlauncher_guideratedelay; - if(f) - f = bound(0, (time - self.pushltime) / f, 1); - else - f = 1; - - velspeed = vlen(self.velocity); - - makevectors(self.realowner.v_angle); - desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward); - desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs); - olddir = normalize(self.velocity); - - // now it gets tricky... we want to move like some curve to approximate the target direction - // but we are limiting the rate at which we can turn! - goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir; - newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD)); - - self.velocity = newdir * velspeed; - self.angles = vectoangles(self.velocity); - - if(!self.count) - { - pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1); - // TODO add a better sound here - sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM); - self.count = 1; - } - } - - if(self.rl_detonate_later) - W_Rocket_RemoteExplode(); - } - - if(self.csqcprojectile_clientanimate == 0) - UpdateCSQCProjectile(self); -} - -void W_Rocket_Touch (void) -{ - if(WarpZone_Projectile_Touch()) - { - if(wasfreed(self)) - W_Rocket_Unregister(); - return; - } - W_Rocket_Unregister(); - W_Rocket_Explode (); -} - -void W_Rocket_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions - return; // g_projectiles_damage says to halt - - self.health = self.health - damage; - self.angles = vectoangles(self.velocity); - - if (self.health <= 0) - W_PrepareExplosionByDamage(attacker, W_Rocket_Explode); -} - -void W_Rocket_Attack (void) -{ - entity missile; - entity flash; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_balance_rocketlauncher_damage); - pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = WarpZone_RefSys_SpawnSameRefSys(self); - missile.owner = missile.realowner = self; - self.lastrocket = missile; - if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0) - missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay; - else - missile.spawnshieldtime = -1; - missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay; - missile.classname = "rocket"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous - - missile.takedamage = DAMAGE_YES; - missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale; - missile.health = autocvar_g_balance_rocketlauncher_health; - missile.event_damage = W_Rocket_Damage; - missile.damagedbycontents = TRUE; - - missile.movetype = MOVETYPE_FLY; - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_ROCKET_LAUNCHER; - setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot - - setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point - W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0); - missile.angles = vectoangles (missile.velocity); - - missile.touch = W_Rocket_Touch; - missile.think = W_Rocket_Think; - missile.nextthink = time; - missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime; - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound - - // muzzle flash for 1st person view - flash = spawn (); - setmodel (flash, "models/flash.md3"); // precision set below - SUB_SetFade (flash, time, 0.1); - flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - W_AttachToShotorg(flash, '5 0 0'); - - // common properties - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc - -float w_rlauncher(float req) -{ - entity rock; - float rockfound; - float ammo_amount; - - if (req == WR_AIM) - { - // aim and decide to fire if appropriate - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE); - if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! - { - // decide whether to detonate rockets - entity missile, targetlist, targ; - float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; - float selfdamage, teamdamage, enemydamage; - edgedamage = autocvar_g_balance_rocketlauncher_edgedamage; - coredamage = autocvar_g_balance_rocketlauncher_damage; - edgeradius = autocvar_g_balance_rocketlauncher_radius; - recipricoledgeradius = 1 / edgeradius; - selfdamage = 0; - teamdamage = 0; - enemydamage = 0; - targetlist = findchainfloat(bot_attack, TRUE); - missile = find(world, classname, "rocket"); - while (missile) - { - if (missile.realowner != self) - { - missile = find(missile, classname, "rocket"); - continue; - } - targ = targetlist; - while (targ) - { - d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin); - d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); - // count potential damage according to type of target - if (targ == self) - selfdamage = selfdamage + d; - else if (targ.team == self.team && teamplay) - teamdamage = teamdamage + d; - else if (bot_shouldattack(targ)) - enemydamage = enemydamage + d; - targ = targ.chain; - } - missile = find(missile, classname, "rocket"); - } - float desirabledamage; - desirabledamage = enemydamage; - if (time > self.invincible_finished && time > self.spawnshieldtime) - desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; - if (teamplay && self.team) - desirabledamage = desirabledamage - teamdamage; - - missile = find(world, classname, "rocket"); - while (missile) - { - if (missile.realowner != self) - { - missile = find(missile, classname, "rocket"); - continue; - } - makevectors(missile.v_angle); - targ = targetlist; - if (skill > 9) // normal players only do this for the target they are tracking - { - targ = targetlist; - while (targ) - { - if ( - (v_forward * normalize(missile.origin - targ.origin)< 0.1) - && desirabledamage > 0.1*coredamage - )self.BUTTON_ATCK2 = TRUE; - targ = targ.chain; - } - }else{ - float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); - //As the distance gets larger, a correct detonation gets near imposible - //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player - if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1) - if(IS_PLAYER(self.enemy)) - if(desirabledamage >= 0.1*coredamage) - if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) - self.BUTTON_ATCK2 = TRUE; - // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); - } - - missile = find(missile, classname, "rocket"); - } - // if we would be doing at X percent of the core damage, detonate it - // but don't fire a new shot at the same time! - if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events - self.BUTTON_ATCK2 = TRUE; - if ((skill > 6.5) && (selfdamage > self.health)) - self.BUTTON_ATCK2 = FALSE; - //if(self.BUTTON_ATCK2 == TRUE) - // dprint(ftos(desirabledamage),"\n"); - if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else - { - if (self.BUTTON_ATCK) - { - if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop) - if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire)) - { - W_Rocket_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready); - self.rl_release = 0; - } - } - else - self.rl_release = 1; - - if (self.BUTTON_ATCK2) - { - rockfound = 0; - for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self) - { - if(!rock.rl_detonate_later) - { - rock.rl_detonate_later = TRUE; - rockfound = 1; - } - } - if(rockfound) - sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/flash.md3"); - precache_model ("models/weapons/g_rl.md3"); - precache_model ("models/weapons/v_rl.md3"); - precache_model ("models/weapons/h_rl.iqm"); - precache_sound ("weapons/rocket_det.wav"); - precache_sound ("weapons/rocket_fire.wav"); - precache_sound ("weapons/rocket_mode.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_ROCKET_LAUNCHER); - self.current_ammo = ammo_rockets; - self.rl_release = 1; - } - else if (req == WR_CHECKAMMO1) - { - // don't switch while guiding a missile - if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER) - { - ammo_amount = FALSE; - if(autocvar_g_balance_rocketlauncher_reload_ammo) - { - if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo && self.(weapon_load[WEP_ROCKET_LAUNCHER]) < autocvar_g_balance_rocketlauncher_ammo) - ammo_amount = TRUE; - } - else if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) - ammo_amount = TRUE; - return !ammo_amount; - } - } - else if (req == WR_CHECKAMMO2) - return FALSE; - else if (req == WR_RESETPLAYER) - { - self.rl_release = 0; - } - else if (req == WR_RELOAD) - { - W_Reload(autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo, autocvar_g_balance_rocketlauncher_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_ROCKETLAUNCHER_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) - return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH; - else - return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_rlauncher(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/rocket_impact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_seeker.qc b/qcsrc/server/w_seeker.qc deleted file mode 100644 index 1683a37e2..000000000 --- a/qcsrc/server/w_seeker.qc +++ /dev/null @@ -1,713 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ SEEKER, -/* function */ w_seeker, -/* ammotype */ IT_ROCKETS, -/* impulse */ 8, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "seeker", -/* shortname */ "seeker", -/* fullname */ _("T.A.G. Seeker") -); -#else -#ifdef SVQC -//.float proxytime; = autoswitch -//.float tl; = wait -.entity tag_target, wps_tag_tracker; -.float tag_time; - -// ============================ -// Begin: Missile functions, these are general functions to be manipulated by other code -// ============================ -void Seeker_Missile_Explode () -{ - self.event_damage = func_null; - RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_missile_damage, autocvar_g_balance_seeker_missile_edgedamage, autocvar_g_balance_seeker_missile_radius, world, world, autocvar_g_balance_seeker_missile_force, self.projectiledeathtype, other); - - - remove (self); -} - -void Seeker_Missile_Touch() -{ - PROJECTILE_TOUCH; - - Seeker_Missile_Explode(); -} - -void Seeker_Missile_Think() -{ - entity e; - vector desireddir, olddir, newdir, eorg; - float turnrate; - float dist; - float spd; - - if (time > self.cnt) - { - self.projectiledeathtype |= HITTYPE_SPLASH; - Seeker_Missile_Explode(); - } - - spd = vlen(self.velocity); - spd = bound( - spd - autocvar_g_balance_seeker_missile_decel * frametime, - autocvar_g_balance_seeker_missile_speed_max, - spd + autocvar_g_balance_seeker_missile_accel * frametime - ); - - if (self.enemy != world) - if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO) - self.enemy = world; - - if (self.enemy != world) - { - e = self.enemy; - eorg = 0.5 * (e.absmin + e.absmax); - turnrate = autocvar_g_balance_seeker_missile_turnrate; // how fast to turn - desireddir = normalize(eorg - self.origin); - olddir = normalize(self.velocity); // get my current direction - dist = vlen(eorg - self.origin); - - // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P ) - if (autocvar_g_balance_seeker_missile_smart && (dist > autocvar_g_balance_seeker_missile_smart_mindist)) - { - // Is it a better idea (shorter distance) to trace to the target itself? - if ( vlen(self.origin + olddir * self.wait) < dist) - traceline(self.origin, self.origin + olddir * self.wait, FALSE, self); - else - traceline(self.origin, eorg, FALSE, self); - - // Setup adaptive tracelength - self.wait = bound(autocvar_g_balance_seeker_missile_smart_trace_min, vlen(self.origin - trace_endpos), self.wait = autocvar_g_balance_seeker_missile_smart_trace_max); - - // Calc how important it is that we turn and add this to the desierd (enemy) dir. - desireddir = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5); - } - - newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy - self.velocity = newdir * spd; // make me fly in the new direction at my flight speed - } - else - dist = 0; - - // Proxy - if (autocvar_g_balance_seeker_missile_proxy) - { - if ( dist <= autocvar_g_balance_seeker_missile_proxy_maxrange) - { - if (self.autoswitch == 0) - { - self.autoswitch = time + autocvar_g_balance_seeker_missile_proxy_delay; - } - else - { - if (self.autoswitch <= time) - { - Seeker_Missile_Explode(); - self.autoswitch = 0; - } - } - } - else - { - if (self.autoswitch != 0) - self.autoswitch = 0; - } - } - /////////////// - - if (self.enemy.deadflag != DEAD_NO) - { - self.enemy = world; - self.cnt = time + 1 + (random() * 4); - self.nextthink = self.cnt; - return; - } - - //self.angles = vectoangles(self.velocity); // turn model in the new flight direction - self.nextthink = time;// + 0.05; // csqc projectiles - UpdateCSQCProjectile(self); -} - - - -void Seeker_Missile_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - - if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions - return; // g_projectiles_damage says to halt - - if (self.realowner == attacker) - self.health = self.health - (damage * 0.25); - else - self.health = self.health - damage; - - if (self.health <= 0) - W_PrepareExplosionByDamage(attacker, Seeker_Missile_Explode); -} - -/* -void Seeker_Missile_Animate() -{ - self.frame = self.frame +1; - self.nextthink = time + 0.05; - - if (self.enemy != world) - if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO) - self.enemy = world; - - if(self.frame == 5) - { - self.think = Seeker_Missile_Think; - self.nextthink = time;// + cvar("g_balance_seeker_missile_activate_delay"); // cant dealy with csqc projectiles - - if (autocvar_g_balance_seeker_missile_proxy) - self.movetype = MOVETYPE_BOUNCEMISSILE; - else - self.movetype = MOVETYPE_FLYMISSILE; - } - - UpdateCSQCProjectile(self); -} -*/ - -void Seeker_Fire_Missile(vector f_diff, entity m_target) -{ - entity missile; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_reload_ammo); - - makevectors(self.v_angle); - W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/seeker_fire.wav", CH_WEAPON_A, 0); - w_shotorg += f_diff; - pointparticles(particleeffectnum("seeker_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - //self.detornator = FALSE; - - missile = spawn(); - missile.owner = missile.realowner = self; - missile.classname = "seeker_missile"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_seeker_missile_damage; - - missile.think = Seeker_Missile_Think; - missile.touch = Seeker_Missile_Touch; - missile.event_damage = Seeker_Missile_Damage; - missile.nextthink = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay"); - missile.cnt = time + autocvar_g_balance_seeker_missile_lifetime; - missile.enemy = m_target; - missile.solid = SOLID_BBOX; - missile.scale = 2; - missile.takedamage = DAMAGE_YES; - missile.health = autocvar_g_balance_seeker_missile_health; - missile.damageforcescale = autocvar_g_balance_seeker_missile_damageforcescale; - missile.damagedbycontents = TRUE; - //missile.think = Seeker_Missile_Animate; // csqc projectiles. - - if (missile.enemy != world) - missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY; - else - missile.projectiledeathtype = WEP_SEEKER; - - - setorigin (missile, w_shotorg); - setsize (missile, '-4 -4 -4', '4 4 4'); - missile.movetype = MOVETYPE_FLYMISSILE; - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH | MIF_GUIDED_TAG; - - W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_missile); - - missile.angles = vectoangles (missile.velocity); - - CSQCProjectile(missile, FALSE, PROJECTILE_SEEKER, TRUE); - - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -// ============================ -// Begin: FLAC, close range attack meant for defeating rockets which are coming at you. -// ============================ -void Seeker_Flac_Explode () -{ - self.event_damage = func_null; - - RadiusDamage (self, self.realowner, autocvar_g_balance_seeker_flac_damage, autocvar_g_balance_seeker_flac_edgedamage, autocvar_g_balance_seeker_flac_radius, world, world, autocvar_g_balance_seeker_flac_force, self.projectiledeathtype, other); - - remove (self); -} - -void Seeker_Flac_Touch() -{ - PROJECTILE_TOUCH; - - Seeker_Flac_Explode(); -} - -void Seeker_Fire_Flac() -{ - entity missile; - vector f_diff; - float c; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_flac_ammo, autocvar_g_balance_seeker_reload_ammo); - - c = mod(self.bulletcounter, 4); - switch(c) - { - case 0: - f_diff = '-1.25 -3.75 0'; - break; - case 1: - f_diff = '+1.25 -3.75 0'; - break; - case 2: - f_diff = '-1.25 +3.75 0'; - break; - case 3: - default: - f_diff = '+1.25 +3.75 0'; - break; - } - W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/flac_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_flac_damage); - w_shotorg += f_diff; - - pointparticles(particleeffectnum("hagar_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = spawn (); - missile.owner = missile.realowner = self; - missile.classname = "missile"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_seeker_flac_damage; - missile.touch = Seeker_Flac_Explode; - missile.use = Seeker_Flac_Explode; - missile.think = adaptor_think2use_hittype_splash; - missile.nextthink = time + autocvar_g_balance_seeker_flac_lifetime + autocvar_g_balance_seeker_flac_lifetime_rand; - missile.solid = SOLID_BBOX; - missile.movetype = MOVETYPE_FLY; - missile.projectiledeathtype = WEP_SEEKER; - missile.projectiledeathtype = WEP_SEEKER | HITTYPE_SECONDARY; - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - // csqc projectiles - //missile.angles = vectoangles (missile.velocity); - //missile.scale = 0.4; // BUG: the model is too big - - setorigin (missile, w_shotorg); - setsize (missile, '-2 -2 -2', '2 2 2'); - - W_SETUPPROJECTILEVELOCITY_UP(missile, g_balance_seeker_flac); - CSQCProjectile(missile, TRUE, PROJECTILE_FLAC, TRUE); - - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -// ============================ -// Begin: Tag and rocket controllers -// ============================ -entity Seeker_Tagged_Info(entity isowner, entity istarget) -{ - entity tag; - for(tag = world; (tag = find(tag, classname, "tag_tracker")); ) - if ((tag.realowner == isowner) && (tag.tag_target == istarget)) - return tag; - - return world; -} - -void Seeker_Attack() -{ - entity tracker, closest_target; - - closest_target = world; - for(tracker = world; (tracker = find(tracker, classname, "tag_tracker")); ) if (tracker.realowner == self) - { - if (closest_target) - { - if (vlen(self.origin - tracker.tag_target.origin) < vlen(self.origin - closest_target.origin)) - closest_target = tracker.tag_target; - } - else - closest_target = tracker.tag_target; - } - - traceline(self.origin + self.view_ofs, closest_target.origin, MOVE_NOMONSTERS, self); - if ((!closest_target) || ((trace_fraction < 1) && (trace_ent != closest_target))) - closest_target = world; - - Seeker_Fire_Missile('0 0 0', closest_target); -} - -void Seeker_Vollycontroller_Think() // TODO: Merge this with Seeker_Attack -{ - float c; - entity oldself,oldenemy; - self.cnt = self.cnt - 1; - - if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.ammo_rockets < autocvar_g_balance_seeker_missile_ammo) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER)) - { - remove(self); - return; - } - - self.nextthink = time + autocvar_g_balance_seeker_missile_delay * W_WeaponRateFactor(); - - oldself = self; - self = self.realowner; - - oldenemy = self.enemy; - self.enemy = oldself.enemy; - - c = mod(self.cnt, 4); - switch(c) - { - case 0: - Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy); - break; - case 1: - Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy); - break; - case 2: - Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy); - break; - case 3: - default: - Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy); - break; - } - - self.enemy = oldenemy; - self = oldself; -} - -void Seeker_Tracker_Think() -{ - // commit suicide if: You die OR target dies OR you switch away from the seeker OR commit suicide if lifetime is up - if ((self.realowner.deadflag != DEAD_NO) || (self.tag_target.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER) - || (time > self.tag_time + autocvar_g_balance_seeker_tag_tracker_lifetime)) - { - if (self) - { - WaypointSprite_Kill(self.tag_target.wps_tag_tracker); - remove(self); - } - return; - } - - // Update the think method information - self.nextthink = time; -} - -// ============================ -// Begin: Tag projectile -// ============================ -void Seeker_Tag_Explode () -{ - //if(other==self.realowner) - // return; - Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE, other.species, self); - - remove (self); -} - -void Seeker_Tag_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -{ - if (self.health <= 0) - return; - self.health = self.health - damage; - if (self.health <= 0) - Seeker_Tag_Explode(); -} - -void Seeker_Tag_Touch() -{ - vector dir; - vector org2; - entity e; - - PROJECTILE_TOUCH; - - dir = normalize (self.realowner.origin - self.origin); - org2 = findbetterlocation (self.origin, 8); - - te_knightspike(org2); - - self.event_damage = func_null; - Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER | HITTYPE_BOUNCE | HITTYPE_SECONDARY, other.species, self); - - if (other.takedamage == DAMAGE_AIM && other.deadflag == DEAD_NO) - { - // check to see if this person is already tagged by me - entity tag = Seeker_Tagged_Info(self.realowner, other); - - if (tag != world) - { - if (other.wps_tag_tracker && (autocvar_g_balance_seeker_type == 1)) // don't attach another waypointsprite without killing the old one first - WaypointSprite_Kill(other.wps_tag_tracker); - - tag.tag_time = time; - } - else - { - //sprint(self.realowner, strcat("You just tagged ^2", other.netname, "^7 with a tracking device!\n")); - e = spawn(); - e.cnt = autocvar_g_balance_seeker_missile_count; - e.classname = "tag_tracker"; - e.owner = self.owner; - e.realowner = self.realowner; - - if (autocvar_g_balance_seeker_type == 1) - { - e.tag_target = other; - e.tag_time = time; - e.think = Seeker_Tracker_Think; - } - else - { - e.enemy = other; - e.think = Seeker_Vollycontroller_Think; - } - - e.nextthink = time; - } - - if (autocvar_g_balance_seeker_type == 1) - { - WaypointSprite_Spawn("tagged-target", autocvar_g_balance_seeker_tag_tracker_lifetime, 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, TRUE, RADARICON_TAGGED, '0.5 1 0'); - WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT); - } - } - - remove(self); - return; -} - -void Seeker_Fire_Tag() -{ - entity missile; - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_seeker_tag_ammo, autocvar_g_balance_seeker_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-2 -2 -2', '2 2 2', FALSE, 2, "weapons/tag_fire.wav", CH_WEAPON_A, autocvar_g_balance_seeker_missile_damage * autocvar_g_balance_seeker_missile_count); - - missile = spawn(); - missile.owner = missile.realowner = self; - missile.classname = "seeker_tag"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = 50; - missile.touch = Seeker_Tag_Touch; - missile.think = SUB_Remove; - missile.nextthink = time + autocvar_g_balance_seeker_tag_lifetime; - missile.movetype = MOVETYPE_FLY; - missile.solid = SOLID_BBOX; - - missile.takedamage = DAMAGE_YES; - missile.event_damage = Seeker_Tag_Damage; - missile.health = autocvar_g_balance_seeker_tag_health; - missile.damageforcescale = autocvar_g_balance_seeker_tag_damageforcescale; - - setorigin (missile, w_shotorg); - setsize (missile, '-2 -2 -2', '2 2 2'); - - missile.flags = FL_PROJECTILE; - //missile.missile_flags = MIF_..?; - - missile.movetype = MOVETYPE_FLY; - W_SETUPPROJECTILEVELOCITY(missile, g_balance_seeker_tag); - missile.angles = vectoangles (missile.velocity); - - CSQCProjectile(missile, TRUE, PROJECTILE_TAG, FALSE); // has sound - - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -// ============================ -// Begin: Genereal weapon functions -// ============================ -void spawnfunc_weapon_seeker (void) -{ - weapon_defaultspawnfunc(WEP_SEEKER); -} - -float w_seeker(float req) -{ - float ammo_amount; - - if (req == WR_AIM) - { - if (autocvar_g_balance_seeker_type == 1) - if (Seeker_Tagged_Info(self, self.enemy) != world) - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_missile_speed_max, 0, autocvar_g_balance_seeker_missile_lifetime, FALSE); - else - self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE); - else - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_seeker_tag_speed, 0, autocvar_g_balance_seeker_tag_lifetime, FALSE); - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - - else if (self.BUTTON_ATCK) - { - if (autocvar_g_balance_seeker_type == 1) - { - if (weapon_prepareattack(0, autocvar_g_balance_seeker_missile_refire)) - { - Seeker_Attack(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_missile_animtime, w_ready); - } - } - else - { - if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire)) - { - Seeker_Fire_Tag(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready); - } - } - } - - else if (self.BUTTON_ATCK2) - { - if (autocvar_g_balance_seeker_type == 1) - { - if (weapon_prepareattack(0, autocvar_g_balance_seeker_tag_refire)) - { - Seeker_Fire_Tag(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_tag_animtime, w_ready); - } - } - else - { - if (weapon_prepareattack(0, autocvar_g_balance_seeker_flac_refire)) - { - Seeker_Fire_Flac(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_seeker_flac_animtime, w_ready); - } - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_seeker.md3"); - precache_model ("models/weapons/v_seeker.md3"); - precache_model ("models/weapons/h_seeker.iqm"); - precache_sound ("weapons/tag_fire.wav"); - precache_sound ("weapons/flac_fire.wav"); - precache_sound ("weapons/seeker_fire.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_SEEKER); - self.current_ammo = ammo_rockets; - } - else if (req == WR_CHECKAMMO1) - { - if (autocvar_g_balance_seeker_type == 1) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_missile_ammo; - ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_missile_ammo; - } - else - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo; - ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo; - } - - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - if (autocvar_g_balance_seeker_type == 1) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_tag_ammo; - ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_tag_ammo; - } - else - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_seeker_flac_ammo; - ammo_amount += self.(weapon_load[WEP_SEEKER]) >= autocvar_g_balance_seeker_flac_ammo; - } - - return ammo_amount; - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_seeker_missile_ammo, autocvar_g_balance_seeker_tag_ammo), autocvar_g_balance_seeker_reload_ammo, autocvar_g_balance_seeker_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_SEEKER_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_SEEKER_MURDER_TAG; - else - return WEAPON_SEEKER_MURDER_SPRAY; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_seeker(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - if(w_deathtype & HITTYPE_BOUNCE) - { - if(w_deathtype & HITTYPE_SECONDARY) - { - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/tag_impact.wav", 1, ATTN_NORM); - } - else - { - pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1); - if(!w_issilent) - { - if (w_random<0.15) - sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTN_NORM); - else if (w_random<0.7) - sound(self, CH_SHOTS, "weapons/tagexp2.wav", 1, ATTN_NORM); - else - sound(self, CH_SHOTS, "weapons/tagexp3.wav", 1, ATTN_NORM); - } - } - } - else - { - pointparticles(particleeffectnum("hagar_explode"), org2, '0 0 0', 1); - if(!w_issilent) - { - if (w_random<0.15) - sound(self, CH_SHOTS, "weapons/seekerexp1.wav", 1, ATTN_NORM); - else if (w_random<0.7) - sound(self, CH_SHOTS, "weapons/seekerexp2.wav", 1, ATTN_NORM); - else - sound(self, CH_SHOTS, "weapons/seekerexp3.wav", 1, ATTN_NORM); - } - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/seekerexp1.wav"); - precache_sound("weapons/seekerexp2.wav"); - precache_sound("weapons/seekerexp3.wav"); - precache_sound("weapons/tagexp1.wav"); - precache_sound("weapons/tagexp2.wav"); - precache_sound("weapons/tagexp3.wav"); - precache_sound("weapons/tag_impact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_shotgun.qc b/qcsrc/server/w_shotgun.qc deleted file mode 100644 index 6c6658d93..000000000 --- a/qcsrc/server/w_shotgun.qc +++ /dev/null @@ -1,294 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ SHOTGUN, -/* function */ w_shotgun, -/* ammotype */ IT_SHELLS, -/* impulse */ 2, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_LOW, -/* model */ "shotgun", -/* shortname */ "shotgun", -/* fullname */ _("Shotgun") -); -#else -#ifdef SVQC - -void W_Shotgun_Attack (void) -{ - float sc; - float ammoamount; - float bullets; - float d; - float f; - float spread; - float bulletspeed; - float bulletconstant; - entity flash; - - ammoamount = autocvar_g_balance_shotgun_primary_ammo; - bullets = autocvar_g_balance_shotgun_primary_bullets; - d = autocvar_g_balance_shotgun_primary_damage; - f = autocvar_g_balance_shotgun_primary_force; - spread = autocvar_g_balance_shotgun_primary_spread; - bulletspeed = autocvar_g_balance_shotgun_primary_speed; - bulletconstant = autocvar_g_balance_shotgun_primary_bulletconstant; - - W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo); - - W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets); - for (sc = 0;sc < bullets;sc = sc + 1) - fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant); - endFireBallisticBullet(); - - pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo); - - // casing code - if (autocvar_g_casings >= 1) - for (sc = 0;sc < ammoamount;sc = sc + 1) - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self); - - // muzzle flash for 1st person view - flash = spawn(); - setmodel(flash, "models/uziflash.md3"); // precision set below - flash.think = SUB_Remove; - flash.nextthink = time + 0.06; - flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - W_AttachToShotorg(flash, '5 0 0'); -} - -.float swing_prev; -.entity swing_alreadyhit; -void shotgun_meleethink (void) -{ - // declarations - float i, f, swing, swing_factor, swing_damage, meleetime, is_player; - entity target_victim; - vector targpos; - - if(!self.cnt) // set start time of melee - { - self.cnt = time; - W_PlayStrengthSound(self.realowner); - } - - makevectors(self.realowner.v_angle); // update values for v_* vectors - - // calculate swing percentage based on time - meleetime = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor(); - swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); - f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces); - - // check to see if we can still continue, otherwise give up now - if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_melee_no_doubleslap) - { - remove(self); - return; - } - - // if okay, perform the traces needed for this frame - for(i=self.swing_prev; i < f; ++i) - { - swing_factor = ((1 - (i / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1); - - targpos = (self.realowner.origin + self.realowner.view_ofs - + (v_forward * autocvar_g_balance_shotgun_secondary_melee_range) - + (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up) - + (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_side)); - - WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner)); - - // draw lightning beams for debugging - //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); - //te_customflash(targpos, 40, 2, '1 1 1'); - - is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body"); - - if((trace_fraction < 1) // if trace is good, apply the damage and remove self - && (trace_ent.takedamage == DAMAGE_AIM) - && (trace_ent != self.swing_alreadyhit) - && (is_player || autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage)) - { - target_victim = trace_ent; // so it persists through other calls - - if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. - swing_damage = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1)); - else - swing_damage = (autocvar_g_balance_shotgun_secondary_melee_nonplayerdamage * min(1, swing_factor + 1)); - - //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n")); - - Damage(target_victim, self.realowner, self.realowner, - swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY, - self.realowner.origin + self.realowner.view_ofs, - v_forward * autocvar_g_balance_shotgun_secondary_force); - - if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); } - - // draw large red flash for debugging - //te_customflash(targpos, 200, 2, '15 0 0'); - - if(autocvar_g_balance_shotgun_secondary_melee_multihit) // allow multiple hits with one swing, but not against the same player twice. - { - self.swing_alreadyhit = target_victim; - continue; // move along to next trace - } - else - { - remove(self); - return; - } - } - } - - if(time >= self.cnt + meleetime) - { - // melee is finished - remove(self); - return; - } - else - { - // set up next frame - self.swing_prev = i; - self.nextthink = time; - } -} - -void W_Shotgun_Attack2 (void) -{ - sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_shotgun_secondary_animtime, w_ready); - - entity meleetemp; - meleetemp = spawn(); - meleetemp.realowner = self; - meleetemp.think = shotgun_meleethink; - meleetemp.nextthink = time + autocvar_g_balance_shotgun_secondary_melee_delay * W_WeaponRateFactor(); - W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_shotgun_secondary_damage, autocvar_g_balance_shotgun_secondary_melee_range); -} - -void spawnfunc_weapon_shotgun(); // defined in t_items.qc - -.float shotgun_primarytime; - -float w_shotgun(float req) -{ - float ammo_amount; - if (req == WR_AIM) - if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range) - self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); - else - { - if(autocvar_g_antilag_bullets) - self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); - else - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE); - } - - else if (req == WR_THINK) - { - if(autocvar_g_balance_shotgun_reload_ammo && self.clip_load < autocvar_g_balance_shotgun_primary_ammo) // forced reload - { - // don't force reload an empty shotgun if its melee attack is active - if not(autocvar_g_balance_shotgun_secondary && self.ammo_shells < autocvar_g_balance_shotgun_primary_ammo) - weapon_action(self.weapon, WR_RELOAD); - } - else - { - if (self.BUTTON_ATCK) - { - if (time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary - { - if(weapon_prepareattack(0, autocvar_g_balance_shotgun_primary_animtime)) - { - W_Shotgun_Attack(); - self.shotgun_primarytime = time + autocvar_g_balance_shotgun_primary_refire * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_shotgun_primary_animtime, w_ready); - } - } - } - } - if (self.clip_load >= 0) // we are not currently reloading - if (!self.crouch) // no crouchmelee please - if (self.BUTTON_ATCK2 && autocvar_g_balance_shotgun_secondary) - if (weapon_prepareattack(1, autocvar_g_balance_shotgun_secondary_refire)) - { - // attempt forcing playback of the anim by switching to another anim (that we never play) here... - weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2); - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/uziflash.md3"); - precache_model ("models/weapons/g_shotgun.md3"); - precache_model ("models/weapons/v_shotgun.md3"); - precache_model ("models/weapons/h_shotgun.iqm"); - precache_sound ("misc/itempickup.wav"); - precache_sound ("weapons/shotgun_fire.wav"); - precache_sound ("weapons/shotgun_melee.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_SHOTGUN); - self.current_ammo = ammo_shells; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_shells >= autocvar_g_balance_shotgun_primary_ammo; - ammo_amount += self.(weapon_load[WEP_SHOTGUN]) >= autocvar_g_balance_shotgun_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - // melee attack is always available - return TRUE; - } - else if (req == WR_RELOAD) - { - W_Reload(autocvar_g_balance_shotgun_primary_ammo, autocvar_g_balance_shotgun_reload_ammo, autocvar_g_balance_shotgun_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_THINKING_WITH_PORTALS; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_SHOTGUN_MURDER_SLAP; - else - return WEAPON_SHOTGUN_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -.float prevric; -float w_shotgun(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum("shotgun_impact"), org2, w_backoff * 1000, 1); - if(!w_issilent && time - self.prevric > 0.25) - { - if(w_random < 0.0165) - sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.033) - sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.05) - sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); - self.prevric = time; - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/ric1.wav"); - precache_sound("weapons/ric2.wav"); - precache_sound("weapons/ric3.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_tuba.qc b/qcsrc/server/w_tuba.qc deleted file mode 100644 index 48b696dd0..000000000 --- a/qcsrc/server/w_tuba.qc +++ /dev/null @@ -1,465 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ TUBA, -/* function */ w_tuba, -/* ammotype */ 0, -/* impulse */ 1, -/* flags */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "tuba", -/* shortname */ "tuba", -/* fullname */ _("@!#%'n Tuba") -); -#else -#ifdef SVQC -//#define TUBA_NOTE(n) strcat("weapons/tuba_note", ftos(n), ".wav") -.entity tuba_note; -.float tuba_smoketime; -.float tuba_instrument; - -#define MAX_TUBANOTES 32 -.float tuba_lastnotes_last; -.float tuba_lastnotes_cnt; // over -.vector tuba_lastnotes[MAX_TUBANOTES]; - -float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo) -{ - float i, j, mmin, mmax, nolength; - float n = tokenize_console(melody); - if(n > pl.tuba_lastnotes_cnt) - return FALSE; - float pitchshift = 0; - - if(instrument >= 0) - if(pl.tuba_instrument != instrument) - return FALSE; - - // verify notes... - nolength = FALSE; - for(i = 0; i < n; ++i) - { - vector v = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]); - float ai = stof(argv(n - i - 1)); - float np = floor(ai); - if(ai == np) - nolength = TRUE; - // n counts the last played notes BACKWARDS - // _x is start - // _y is end - // _z is note pitch - if(ignorepitch && i == 0) - { - pitchshift = np - v_z; - } - else - { - if(v_z + pitchshift != np) - return FALSE; - } - } - - // now we know the right NOTES were played - if(!nolength) - { - // verify rhythm... - float ti = 0; - if(maxtempo > 0) - mmin = 240 / maxtempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec - else - mmin = 0; - if(mintempo > 0) - mmax = 240 / mintempo; // 60 = "0.25 means 1 sec", at 120 0.5 means 1 sec, at 240 1 means 1 sec - else - mmax = 240; // you won't try THAT hard... (tempo 1) - //print(sprintf("initial tempo rules: %f %f\n", mmin, mmax)); - - for(i = 0; i < n; ++i) - { - vector vi = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - i + MAX_TUBANOTES, MAX_TUBANOTES)]); - float ai = stof(argv(n - i - 1)); - ti -= 1 / (ai - floor(ai)); - float tj = ti; - for(j = i+1; j < n; ++j) - { - vector vj = pl.(tuba_lastnotes[mod(pl.tuba_lastnotes_last - j + MAX_TUBANOTES, MAX_TUBANOTES)]); - float aj = stof(argv(n - j - 1)); - tj -= (aj - floor(aj)); - - // note i should be at m*ti+b - // note j should be at m*tj+b - // so: - // we have a LINE l, so that - // vi_x <= l(ti) <= vi_y - // vj_x <= l(tj) <= vj_y - // what is m? - - // vi_x <= vi_y <= vj_x <= vj_y - // ti <= tj - //print(sprintf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti)); - //print(sprintf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj)); - //print(sprintf("m1 = %f\n", (vi_x - vj_y) / (ti - tj))); - //print(sprintf("m2 = %f\n", (vi_y - vj_x) / (ti - tj))); - mmin = max(mmin, (vi_x - vj_y) / (ti - tj)); // lower bound - mmax = min(mmax, (vi_y - vj_x) / (ti - tj)); // upper bound - } - } - - if(mmin > mmax) // rhythm fail - return FALSE; - } - - pl.tuba_lastnotes_cnt = 0; - - return TRUE; -} - -void W_Tuba_NoteOff() -{ - // we have a note: - // on: self.spawnshieldtime - // off: time - // note: self.cnt - if(self.owner.tuba_note == self) - { - self.owner.tuba_lastnotes_last = mod(self.owner.tuba_lastnotes_last + 1, MAX_TUBANOTES); - self.owner.(tuba_lastnotes[self.owner.tuba_lastnotes_last]) = eX * self.spawnshieldtime + eY * time + eZ * self.cnt; - self.owner.tuba_note = world; - self.owner.tuba_lastnotes_cnt = bound(0, self.owner.tuba_lastnotes_cnt + 1, MAX_TUBANOTES); - - string s; - s = trigger_magicear_processmessage_forallears(self.owner, 0, world, string_null); - if(s != "") - { - // simulate a server message - switch(self.tuba_instrument) - { - default: - case 0: // Tuba - bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Tuba: ^7", s, "\n")); - break; - case 1: - bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Accordeon: ^7", s, "\n")); - break; - case 2: - bprint(strcat("\{1}\{13}* ^3", self.owner.netname, "^3 played on the @!#%'n Klein Bottle: ^7", s, "\n")); - break; - } - } - } - remove(self); -} - -float Tuba_GetNote(entity pl, float hittype) -{ - float note; - float movestate; - movestate = 5; - if(pl.movement_x < 0) movestate -= 3; - if(pl.movement_x > 0) movestate += 3; - if(pl.movement_y < 0) movestate -= 1; - if(pl.movement_y > 0) movestate += 1; -#ifdef GMQCC - note = 0; -#endif - switch(movestate) - { - // layout: originally I wanted - // eb e e#=f - // B c d - // Gb G G# - // but then you only use forward and right key. So to make things more - // interesting, I swapped B with e#. Har har har... - // eb e B - // f=e# c d - // Gb G G# - case 1: note = -6; break; // Gb - case 2: note = -5; break; // G - case 3: note = -4; break; // G# - case 4: note = +5; break; // e# - default: - case 5: note = 0; break; // c - case 6: note = +2; break; // d - case 7: note = +3; break; // eb - case 8: note = +4; break; // e - case 9: note = -1; break; // B - } - if(pl.BUTTON_CROUCH) - note -= 12; - if(pl.BUTTON_JUMP) - note += 12; - if(hittype & HITTYPE_SECONDARY) - note += 7; - - // we support two kinds of tubas, those tuned in Eb and those tuned in C - // kind of tuba currently is player slot number, or team number if in - // teamplay - // that way, holes in the range of notes are "plugged" - if(teamplay) - { - if(pl.team == NUM_TEAM_2 || pl.team == NUM_TEAM_4) - note += 3; - } - else - { - if(pl.clientcolors & 1) - note += 3; - } - - // total range of notes: - // 0 - // *** ** **** - // *** ** **** - // *** ** **** - // *** ** **** - // *** ********************* **** - // -18.........................+12 - // *** ********************* **** - // -18............................+15 - // with jump: ... +24 - // ... +27 - return note; -} - -float W_Tuba_NoteSendEntity(entity to, float sf) -{ - float f; - - msg_entity = to; - if(!sound_allowed(MSG_ONE, self.realowner)) - return FALSE; - - WriteByte(MSG_ENTITY, ENT_CLIENT_TUBANOTE); - WriteByte(MSG_ENTITY, sf); - if(sf & 1) - { - WriteChar(MSG_ENTITY, self.cnt); - f = 0; - if(self.realowner != to) - f |= 1; - f |= 2 * self.tuba_instrument; - WriteByte(MSG_ENTITY, f); - } - if(sf & 2) - { - WriteCoord(MSG_ENTITY, self.origin_x); - WriteCoord(MSG_ENTITY, self.origin_y); - WriteCoord(MSG_ENTITY, self.origin_z); - } - return TRUE; -} - -void W_Tuba_NoteThink() -{ - float dist_mult; - float vol0, vol1; - vector dir0, dir1; - vector v; - entity e; - if(time > self.teleport_time) - { - W_Tuba_NoteOff(); - return; - } - self.nextthink = time; - dist_mult = autocvar_g_balance_tuba_attenuation / autocvar_snd_soundradius; - FOR_EACH_REALCLIENT(e) - if(e != self.realowner) - { - v = self.origin - (e.origin + e.view_ofs); - vol0 = max(0, 1 - vlen(v) * dist_mult); - dir0 = normalize(v); - v = self.realowner.origin - (e.origin + e.view_ofs); - vol1 = max(0, 1 - vlen(v) * dist_mult); - dir1 = normalize(v); - if(fabs(vol0 - vol1) > 0.005) // 0.5 percent change in volume - { - setorigin(self, self.realowner.origin); - self.SendFlags |= 2; - break; - } - if(dir0 * dir1 < 0.9994) // 2 degrees change in angle - { - setorigin(self, self.realowner.origin); - self.SendFlags |= 2; - break; - } - } -} - -void W_Tuba_NoteOn(float hittype) -{ - vector o; - float n; - - W_SetupShot(self, FALSE, 2, "", 0, autocvar_g_balance_tuba_damage); - - n = Tuba_GetNote(self, hittype); - - hittype = 0; - if(self.tuba_instrument & 1) - hittype |= HITTYPE_SECONDARY; - if(self.tuba_instrument & 2) - hittype |= HITTYPE_BOUNCE; - - if(self.tuba_note) - { - if(self.tuba_note.cnt != n || self.tuba_note.tuba_instrument != self.tuba_instrument) - { - entity oldself = self; - self = self.tuba_note; - W_Tuba_NoteOff(); - self = oldself; - } - } - - if not(self.tuba_note) - { - self.tuba_note = spawn(); - self.tuba_note.owner = self.tuba_note.realowner = self; - self.tuba_note.cnt = n; - self.tuba_note.tuba_instrument = self.tuba_instrument; - self.tuba_note.think = W_Tuba_NoteThink; - self.tuba_note.nextthink = time; - self.tuba_note.spawnshieldtime = time; - Net_LinkEntity(self.tuba_note, FALSE, 0, W_Tuba_NoteSendEntity); - } - - self.tuba_note.teleport_time = time + autocvar_g_balance_tuba_refire * 2 * W_WeaponRateFactor(); // so it can get prolonged safely - - //sound(self, c, TUBA_NOTE(n), bound(0, VOL_BASE * cvar("g_balance_tuba_volume"), 1), autocvar_g_balance_tuba_attenuation); - RadiusDamage(self, self, autocvar_g_balance_tuba_damage, autocvar_g_balance_tuba_edgedamage, autocvar_g_balance_tuba_radius, world, world, autocvar_g_balance_tuba_force, hittype | WEP_TUBA, world); - - o = gettaginfo(self.exteriorweaponentity, 0); - if(time > self.tuba_smoketime) - { - pointparticles(particleeffectnum("smoke_ring"), o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1); - self.tuba_smoketime = time + 0.25; - } -} - -void spawnfunc_weapon_tuba (void) -{ - weapon_defaultspawnfunc(WEP_TUBA); -} - -float w_tuba(float req) -{ - if (req == WR_AIM) - { - // bots cannot play the Tuba well yet - // I think they should start with the recorder first - if(vlen(self.origin - self.enemy.origin) < autocvar_g_balance_tuba_radius) - { - if(random() > 0.5) - self.BUTTON_ATCK = 1; - else - self.BUTTON_ATCK2 = 1; - } - } - else if (req == WR_THINK) - { - if (self.BUTTON_ATCK) - if (weapon_prepareattack(0, autocvar_g_balance_tuba_refire)) - { - W_Tuba_NoteOn(0); - //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready); - weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready); - } - if (self.BUTTON_ATCK2) - if (weapon_prepareattack(1, autocvar_g_balance_tuba_refire)) - { - W_Tuba_NoteOn(HITTYPE_SECONDARY); - //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready); - weapon_thinkf(WFRAME_IDLE, autocvar_g_balance_tuba_animtime, w_ready); - } - if(self.tuba_note) - { - if(!self.BUTTON_ATCK && !self.BUTTON_ATCK2) - { - entity oldself = self; - self = self.tuba_note; - W_Tuba_NoteOff(); - self = oldself; - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_tuba.md3"); - precache_model ("models/weapons/v_tuba.md3"); - precache_model ("models/weapons/h_tuba.iqm"); - precache_model ("models/weapons/v_akordeon.md3"); - precache_model ("models/weapons/h_akordeon.iqm"); - precache_model ("models/weapons/v_kleinbottle.md3"); - precache_model ("models/weapons/h_kleinbottle.iqm"); - - //float i; - //for(i = -18; i <= +27; ++i) - // precache_sound(TUBA_NOTE(i)); - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_TUBA); - self.current_ammo = ammo_none; - self.tuba_instrument = 0; - } - else if (req == WR_RELOAD) - { - // switch to alternate instruments :) - if(self.weaponentity.state == WS_READY) - { - switch(self.tuba_instrument) - { - case 0: - self.tuba_instrument = 1; - self.weaponname = "akordeon"; - break; - case 1: - self.tuba_instrument = 2; - self.weaponname = "kleinbottle"; - break; - case 2: - self.tuba_instrument = 0; - self.weaponname = "tuba"; - break; - } - W_SetupShot(self, FALSE, 0, "", 0, 0); - pointparticles(particleeffectnum("teleport"), w_shotorg, '0 0 0', 1); - self.weaponentity.state = WS_INUSE; - weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready); - } - } - else if (req == WR_CHECKAMMO1) - return TRUE; // TODO use fuel? - else if (req == WR_CHECKAMMO2) - return TRUE; // TODO use fuel? - else if (req == WR_SUICIDEMESSAGE) - { - if(w_deathtype & HITTYPE_BOUNCE) - return WEAPON_KLEINBOTTLE_SUICIDE; - else if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_ACCORDEON_SUICIDE; - else - return WEAPON_TUBA_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_BOUNCE) - return WEAPON_KLEINBOTTLE_MURDER; - else if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_ACCORDEON_MURDER; - else - return WEAPON_TUBA_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_tuba(float req) -{ - // nothing to do here; particles of tuba are handled differently - - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/w_uzi.qc b/qcsrc/server/w_uzi.qc deleted file mode 100644 index 923ed9504..000000000 --- a/qcsrc/server/w_uzi.qc +++ /dev/null @@ -1,341 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ UZI, -/* function */ w_uzi, -/* ammotype */ IT_NAILS, -/* impulse */ 3, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "uzi", -/* shortname */ "uzi", -/* fullname */ _("Machine Gun") -); -#else -#ifdef SVQC - -// leilei's fancy muzzleflash stuff -void UZI_Flash_Go() -{ - self.frame = self.frame + 2; - self.scale = self.scale * 0.5; - self.alpha = self.alpha - 0.25; - self.nextthink = time + 0.05; - - if (self.alpha <= 0) - { - self.think = SUB_Remove; - self.nextthink = time; - self.realowner.muzzle_flash = world; - return; - } - -} - -void UziFlash() -{ - if (self.muzzle_flash == world) - self.muzzle_flash = spawn(); - - // muzzle flash for 1st person view - setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below - - self.muzzle_flash.scale = 0.75; - self.muzzle_flash.think = UZI_Flash_Go; - self.muzzle_flash.nextthink = time + 0.02; - self.muzzle_flash.frame = 2; - self.muzzle_flash.alpha = 0.75; - self.muzzle_flash.angles_z = random() * 180; - self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - self.muzzle_flash.owner = self.muzzle_flash.realowner = self; -} - -void W_UZI_Attack (float deathtype) -{ - W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? autocvar_g_balance_uzi_first_damage : autocvar_g_balance_uzi_sustained_damage)); - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - // this attack_finished just enforces a cooldown at the end of a burst - ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); - - if (self.misc_bulletcounter == 1) - fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_first_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_first_damage, autocvar_g_balance_uzi_first_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); - else - fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_sustained_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); - endFireBallisticBullet(); - - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - UziFlash(); - W_AttachToShotorg(self.muzzle_flash, '5 0 0'); - - // casing code - if (autocvar_g_casings >= 2) - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); - - if (self.misc_bulletcounter == 1) - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_first_ammo, autocvar_g_balance_uzi_reload_ammo); - else - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); -} - -// weapon frames -void uzi_fire1_02() -{ - if(self.weapon != self.switchweapon) // abort immediately if switching - { - w_ready(); - return; - } - if (self.BUTTON_ATCK) - { - if (!weapon_action(self.weapon, WR_CHECKAMMO2)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - self.misc_bulletcounter = self.misc_bulletcounter + 1; - W_UZI_Attack(WEP_UZI); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); - } - else - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, w_ready); -} - - -void uzi_mode1_fire_auto() -{ - float uzi_spread; - - if (!self.BUTTON_ATCK) - { - w_ready(); - return; - } - - if (!weapon_action(self.weapon, WR_CHECKAMMO1)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); - - W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - uzi_spread = bound(autocvar_g_balance_uzi_spread_min, autocvar_g_balance_uzi_spread_min + (autocvar_g_balance_uzi_spread_add * self.misc_bulletcounter), autocvar_g_balance_uzi_spread_max); - fireBallisticBullet(w_shotorg, w_shotdir, uzi_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); - endFireBallisticBullet(); - - self.misc_bulletcounter = self.misc_bulletcounter + 1; - - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - UziFlash(); - W_AttachToShotorg(self.muzzle_flash, '5 0 0'); - - if (autocvar_g_casings >= 2) // casing code - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); - - ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_mode1_fire_auto); -} - -void uzi_mode1_fire_burst() -{ - W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_burst_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); - endFireBallisticBullet(); - - - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - UziFlash(); - W_AttachToShotorg(self.muzzle_flash, '5 0 0'); - - if (autocvar_g_casings >= 2) // casing code - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); - - self.misc_bulletcounter = self.misc_bulletcounter + 1; - if (self.misc_bulletcounter == 0) - { - ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_burst_refire2 * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_animtime, w_ready); - } - else - { - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_refire, uzi_mode1_fire_burst); - } - -} - -void spawnfunc_weapon_machinegun(); // defined in t_items.qc - -float w_uzi(float req) -{ - float ammo_amount; - if (req == WR_AIM) - if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200) - self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); - else - { - self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_uzi_reload_ammo && self.clip_load < min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if(autocvar_g_balance_uzi_mode == 1) - { - if (self.BUTTON_ATCK) - if (weapon_prepareattack(0, 0)) - { - self.misc_bulletcounter = 0; - uzi_mode1_fire_auto(); - } - - if(self.BUTTON_ATCK2) - if(weapon_prepareattack(1, 0)) - { - if (!weapon_action(self.weapon, WR_CHECKAMMO2)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return FALSE; - } - - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_burst_ammo, autocvar_g_balance_uzi_reload_ammo); - - self.misc_bulletcounter = autocvar_g_balance_uzi_burst * -1; - uzi_mode1_fire_burst(); - } - } - else - { - - if (self.BUTTON_ATCK) - if (weapon_prepareattack(0, 0)) - { - self.misc_bulletcounter = 1; - W_UZI_Attack(WEP_UZI); // sets attack_finished - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); - } - - if (self.BUTTON_ATCK2 && autocvar_g_balance_uzi_first) - if (weapon_prepareattack(1, 0)) - { - self.misc_bulletcounter = 1; - W_UZI_Attack(WEP_UZI | HITTYPE_SECONDARY); // sets attack_finished - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_first_refire, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/uziflash.md3"); - precache_model ("models/weapons/g_uzi.md3"); - precache_model ("models/weapons/v_uzi.md3"); - precache_model ("models/weapons/h_uzi.iqm"); - precache_sound ("weapons/uzi_fire.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_UZI); - self.current_ammo = ammo_nails; - } - else if (req == WR_CHECKAMMO1) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_sustained_ammo; - else - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; - - if(autocvar_g_balance_uzi_reload_ammo) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_sustained_ammo; - else - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; - } - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_burst_ammo; - else - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; - - if(autocvar_g_balance_uzi_reload_ammo) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_burst_ammo; - else - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; - } - return ammo_amount; - } - else if (req == WR_RELOAD) - { - W_Reload(min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo), autocvar_g_balance_uzi_reload_ammo, autocvar_g_balance_uzi_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_THINKING_WITH_PORTALS; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_UZI_MURDER_SNIPE; - else - return WEAPON_UZI_MURDER_SPRAY; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_uzi(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1); - if(!w_issilent) - if(w_random < 0.05) - sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.1) - sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.2) - sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/ric1.wav"); - precache_sound("weapons/ric2.wav"); - precache_sound("weapons/ric3.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/server/weapons/cl_weapons.qc b/qcsrc/server/weapons/cl_weapons.qc new file mode 100644 index 000000000..34f17a613 --- /dev/null +++ b/qcsrc/server/weapons/cl_weapons.qc @@ -0,0 +1,537 @@ +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); +} diff --git a/qcsrc/server/weapons/cl_weaponsystem.qc b/qcsrc/server/weapons/cl_weaponsystem.qc new file mode 100644 index 000000000..23dfaedce --- /dev/null +++ b/qcsrc/server/weapons/cl_weaponsystem.qc @@ -0,0 +1,1210 @@ +/* +=========================================================================== + + 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; +} diff --git a/qcsrc/server/weapons/w_common.qc b/qcsrc/server/weapons/w_common.qc new file mode 100644 index 000000000..486673d4f --- /dev/null +++ b/qcsrc/server/weapons/w_common.qc @@ -0,0 +1,630 @@ + +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; +}