From: TimePath Date: Sun, 30 Aug 2015 05:55:52 +0000 (+1000) Subject: Move weapons into a folder like items, monsters, turrets, and vehicles X-Git-Tag: xonotic-v0.8.2~1980 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=a81365864859ecccf601a8e50768322b6012e90c Move weapons into a folder like items, monsters, turrets, and vehicles --- diff --git a/qcsrc/common/weapons/all.inc b/qcsrc/common/weapons/all.inc index 4f4cd2b3d8..fc89a0cd20 100644 --- a/qcsrc/common/weapons/all.inc +++ b/qcsrc/common/weapons/all.inc @@ -3,27 +3,27 @@ // IF YOU DISREGARD THIS NOTICE, I'LL KILL YOU WITH THE @!#%'N TUBA // core weapons -#include "w_blaster.qc" -#include "w_shotgun.qc" -#include "w_machinegun.qc" -#include "w_mortar.qc" -#include "w_minelayer.qc" -#include "w_electro.qc" -#include "w_crylink.qc" -#include "w_vortex.qc" -#include "w_hagar.qc" -#include "w_devastator.qc" +#include "weapon/blaster.qc" +#include "weapon/shotgun.qc" +#include "weapon/machinegun.qc" +#include "weapon/mortar.qc" +#include "weapon/minelayer.qc" +#include "weapon/electro.qc" +#include "weapon/crylink.qc" +#include "weapon/vortex.qc" +#include "weapon/hagar.qc" +#include "weapon/devastator.qc" // other weapons -#include "w_porto.qc" -#include "w_vaporizer.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" -#include "w_shockwave.qc" -#include "w_arc.qc" -#include "w_hmg.qc" -#include "w_rpc.qc" +#include "weapon/porto.qc" +#include "weapon/vaporizer.qc" +#include "weapon/hook.qc" +#include "weapon/hlac.qc" +#include "weapon/tuba.qc" +#include "weapon/rifle.qc" +#include "weapon/fireball.qc" +#include "weapon/seeker.qc" +#include "weapon/shockwave.qc" +#include "weapon/arc.qc" +#include "weapon/hmg.qc" +#include "weapon/rpc.qc" diff --git a/qcsrc/common/weapons/w_arc.qc b/qcsrc/common/weapons/w_arc.qc deleted file mode 100644 index c101a8c8ab..0000000000 --- a/qcsrc/common/weapons/w_arc.qc +++ /dev/null @@ -1,1546 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ ARC, -/* function */ W_Arc, -/* ammotype */ ammo_cells, -/* impulse */ 3, -/* flags */ WEP_FLAG_NORMAL, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* color */ '1 1 1', -/* modelname */ "arc", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairhlac 0.7", -/* wepimg */ "weaponarc", -/* refname */ "arc", -/* wepname */ _("Arc") -); - -#define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc) -#define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, beam_ammo) \ - w_cvar(id, sn, NONE, beam_animtime) \ - w_cvar(id, sn, NONE, beam_botaimspeed) \ - w_cvar(id, sn, NONE, beam_botaimlifetime) \ - w_cvar(id, sn, NONE, beam_damage) \ - w_cvar(id, sn, NONE, beam_degreespersegment) \ - w_cvar(id, sn, NONE, beam_distancepersegment) \ - w_cvar(id, sn, NONE, beam_falloff_halflifedist) \ - w_cvar(id, sn, NONE, beam_falloff_maxdist) \ - w_cvar(id, sn, NONE, beam_falloff_mindist) \ - w_cvar(id, sn, NONE, beam_force) \ - w_cvar(id, sn, NONE, beam_healing_amax) \ - w_cvar(id, sn, NONE, beam_healing_aps) \ - w_cvar(id, sn, NONE, beam_healing_hmax) \ - w_cvar(id, sn, NONE, beam_healing_hps) \ - w_cvar(id, sn, NONE, beam_maxangle) \ - w_cvar(id, sn, NONE, beam_nonplayerdamage) \ - w_cvar(id, sn, NONE, beam_range) \ - w_cvar(id, sn, NONE, beam_refire) \ - w_cvar(id, sn, NONE, beam_returnspeed) \ - w_cvar(id, sn, NONE, beam_tightness) \ - w_cvar(id, sn, NONE, burst_ammo) \ - w_cvar(id, sn, NONE, burst_damage) \ - w_cvar(id, sn, NONE, burst_healing_aps) \ - w_cvar(id, sn, NONE, burst_healing_hps) \ - w_cvar(id, sn, NONE, overheat_max)/* maximum heat before jamming */ \ - w_cvar(id, sn, NONE, overheat_min)/* minimum heat to wait for cooldown */ \ - w_cvar(id, sn, NONE, beam_heat) /* heat increase per second (primary) */ \ - w_cvar(id, sn, NONE, burst_heat) /* heat increase per second (secondary) */ \ - w_cvar(id, sn, NONE, cooldown) /* heat decrease per second when resting */ \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifndef MENUQC -const float ARC_MAX_SEGMENTS = 20; -vector arc_shotorigin[4]; -.vector beam_start; -.vector beam_dir; -.vector beam_wantdir; -.int beam_type; - -const int ARC_BT_MISS = 0x00; -const int ARC_BT_WALL = 0x01; -const int ARC_BT_HEAL = 0x02; -const int ARC_BT_HIT = 0x03; -const int ARC_BT_BURST_MISS = 0x10; -const int ARC_BT_BURST_WALL = 0x11; -const int ARC_BT_BURST_HEAL = 0x12; -const int ARC_BT_BURST_HIT = 0x13; -const int ARC_BT_BURSTMASK = 0x10; - -const int ARC_SF_SETTINGS = 1; -const int ARC_SF_START = 2; -const int ARC_SF_WANTDIR = 4; -const int ARC_SF_BEAMDIR = 8; -const int ARC_SF_BEAMTYPE = 16; -const int ARC_SF_LOCALMASK = 14; -#endif -#ifdef SVQC -ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.entity arc_beam; -.float arc_BUTTON_ATCK_prev; // for better animation control -.float beam_prev; -.float beam_initialized; -.float beam_bursting; -.float beam_teleporttime; -.float beam_heat; // (beam) amount of heat produced -.float arc_overheat; // (dropped arc/player) time during which it's too hot -.float arc_cooldown; // (dropped arc/player) cooling speed -.float arc_heat_percent; // (player) arc heat in [0,1] (stat) -.float arc_smoke_sound; -#endif -#ifdef CSQC -void Ent_ReadArcBeam(float isnew); - -.vector beam_color; -.float beam_alpha; -.float beam_thickness; -.float beam_traileffect; -.float beam_hiteffect; -.float beam_hitlight[4]; // 0: radius, 123: rgb -.float beam_muzzleeffect; -.float beam_muzzlelight[4]; // 0: radius, 123: rgb -.string beam_image; - -.entity beam_muzzleentity; - -.float beam_degreespersegment; -.float beam_distancepersegment; -.float beam_usevieworigin; -.float beam_initialized; -.float beam_maxangle; -.float beam_range; -.float beam_returnspeed; -.float beam_tightness; -.vector beam_shotorigin; - -entity Draw_ArcBeam_callback_entity; -float Draw_ArcBeam_callback_last_thickness; -vector Draw_ArcBeam_callback_last_top; // NOTE: in same coordinate system as player. -vector Draw_ArcBeam_callback_last_bottom; // NOTE: in same coordinate system as player. -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC.m_id); } - -float W_Arc_Beam_Send(entity to, int sf) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM); - - // Truncate information when this beam is displayed to the owner client - // - The owner client has no use for beam start position or directions, - // it always figures this information out for itself with csqc code. - // - Spectating the owner also truncates this information. - float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to))); - if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; } - - WriteByte(MSG_ENTITY, sf); - - if(sf & ARC_SF_SETTINGS) // settings information - { - WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment)); - WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment)); - WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle)); - WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range)); - WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed)); - WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10); - - WriteByte(MSG_ENTITY, drawlocal); - } - if(sf & ARC_SF_START) // starting location - { - WriteCoord(MSG_ENTITY, self.beam_start.x); - WriteCoord(MSG_ENTITY, self.beam_start.y); - WriteCoord(MSG_ENTITY, self.beam_start.z); - } - if(sf & ARC_SF_WANTDIR) // want/aim direction - { - WriteCoord(MSG_ENTITY, self.beam_wantdir.x); - WriteCoord(MSG_ENTITY, self.beam_wantdir.y); - WriteCoord(MSG_ENTITY, self.beam_wantdir.z); - } - if(sf & ARC_SF_BEAMDIR) // beam direction - { - WriteCoord(MSG_ENTITY, self.beam_dir.x); - WriteCoord(MSG_ENTITY, self.beam_dir.y); - WriteCoord(MSG_ENTITY, self.beam_dir.z); - } - if(sf & ARC_SF_BEAMTYPE) // beam type - { - WriteByte(MSG_ENTITY, self.beam_type); - } - - return true; -} - -void Reset_ArcBeam(entity player, vector forward) -{ - if (!player.arc_beam) { - return; - } - player.arc_beam.beam_dir = forward; - player.arc_beam.beam_teleporttime = time; -} - -float Arc_GetHeat_Percent(entity player) -{ - if ( WEP_CVAR(arc, overheat_max) <= 0 || WEP_CVAR(arc, overheat_max) <= 0 ) - { - player.arc_overheat = 0; - return 0; - } - - if ( player.arc_beam ) - return player.arc_beam.beam_heat/WEP_CVAR(arc, overheat_max); - - if ( player.arc_overheat > time ) - { - return (player.arc_overheat-time) / WEP_CVAR(arc, overheat_max) - * player.arc_cooldown; - } - - return 0; -} -void Arc_Player_SetHeat(entity player) -{ - player.arc_heat_percent = Arc_GetHeat_Percent(player); - //dprint("Heat: ",ftos(player.arc_heat_percent*100),"%\n"); -} - -void W_Arc_Beam_Think(void) -{ - if(self != self.owner.arc_beam) - { - remove(self); - return; - } - - - float burst = 0; - if( self.owner.BUTTON_ATCK2 || self.beam_bursting) - { - if(!self.beam_bursting) - self.beam_bursting = true; - burst = ARC_BT_BURSTMASK; - } - - if( - !IS_PLAYER(self.owner) - || - (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) - || - self.owner.deadflag != DEAD_NO - || - (!self.owner.BUTTON_ATCK && !burst ) - || - self.owner.frozen - || - self.owner.vehicle - || - (WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max)) - ) - { - if ( WEP_CVAR(arc, cooldown) > 0 ) - { - float cooldown_speed = 0; - if ( self.beam_heat > WEP_CVAR(arc, overheat_min) && WEP_CVAR(arc, cooldown) > 0 ) - { - cooldown_speed = WEP_CVAR(arc, cooldown); - } - else if ( !burst ) - { - cooldown_speed = self.beam_heat / WEP_CVAR(arc, beam_refire); - } - - if ( cooldown_speed ) - { - self.owner.arc_overheat = time + self.beam_heat / cooldown_speed; - self.owner.arc_cooldown = cooldown_speed; - } - - if ( WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max) ) - { - Send_Effect_("arc_overheat", - self.beam_start, self.beam_wantdir, 1 ); - sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM); - } - } - - if(self == self.owner.arc_beam) { self.owner.arc_beam = world; } - entity oldself = self; - self = self.owner; - if(!WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO2)) - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - // note: this doesn't force the switch - W_SwitchToOtherWeapon(self); - } - self = oldself; - remove(self); - return; - } - - // decrease ammo - float coefficient = frametime; - if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) - { - float rootammo; - if(burst) - { rootammo = WEP_CVAR(arc, burst_ammo); } - else - { rootammo = WEP_CVAR(arc, beam_ammo); } - - if(rootammo) - { - coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo); - self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime)); - } - } - float heat_speed = burst ? WEP_CVAR(arc, burst_heat) : WEP_CVAR(arc, beam_heat); - self.beam_heat = min( WEP_CVAR(arc, overheat_max), self.beam_heat + heat_speed*frametime ); - - makevectors(self.owner.v_angle); - - W_SetupShot_Range( - self.owner, - true, - 0, - "", - 0, - WEP_CVAR(arc, beam_damage) * coefficient, - WEP_CVAR(arc, beam_range) - ); - - // After teleport, "lock" the beam until the teleport is confirmed. - if (time < self.beam_teleporttime + ANTILAG_LATENCY(self.owner)) { - w_shotdir = self.beam_dir; - } - - // network information: shot origin and want/aim direction - if(self.beam_start != w_shotorg) - { - self.SendFlags |= ARC_SF_START; - self.beam_start = w_shotorg; - } - if(self.beam_wantdir != w_shotdir) - { - self.SendFlags |= ARC_SF_WANTDIR; - self.beam_wantdir = w_shotdir; - } - - if(!self.beam_initialized) - { - self.beam_dir = w_shotdir; - self.beam_initialized = true; - } - - // WEAPONTODO: Detect player velocity so that the beam curves when moving too - // idea: blend together self.beam_dir with the inverted direction the player is moving in - // might have to make some special accomodation so that it only uses view_right and view_up - - // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling - - float segments; - if(self.beam_dir != w_shotdir) - { - // calculate how much we're going to move the end of the beam to the want position - // WEAPONTODO (server and client): - // blendfactor never actually becomes 0 in this situation, which is a problem - // regarding precision... this means that self.beam_dir and w_shotdir approach - // eachother, however they never actually become the same value with this method. - // Perhaps we should do some form of rounding/snapping? - float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG; - if(angle && (angle > WEP_CVAR(arc, beam_maxangle))) - { - // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor - float blendfactor = bound( - 0, - (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), - min(WEP_CVAR(arc, beam_maxangle) / angle, 1) - ); - self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); - } - else - { - // the radius is not too far yet, no worries :D - float blendfactor = bound( - 0, - (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), - 1 - ); - self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); - } - - // network information: beam direction - self.SendFlags |= ARC_SF_BEAMDIR; - - // calculate how many segments are needed - float max_allowed_segments; - - if(WEP_CVAR(arc, beam_distancepersegment)) - { - max_allowed_segments = min( - ARC_MAX_SEGMENTS, - 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment))) - ); - } - else { max_allowed_segments = ARC_MAX_SEGMENTS; } - - if(WEP_CVAR(arc, beam_degreespersegment)) - { - segments = bound( - 1, - ( - min( - angle, - WEP_CVAR(arc, beam_maxangle) - ) - / - WEP_CVAR(arc, beam_degreespersegment) - ), - max_allowed_segments - ); - } - else { segments = 1; } - } - else { segments = 1; } - - vector beam_endpos = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range))); - vector beam_controlpoint = w_shotorg + w_shotdir * (WEP_CVAR(arc, beam_range) * (1 - WEP_CVAR(arc, beam_tightness))); - - float i; - float new_beam_type = 0; - vector last_origin = w_shotorg; - for(i = 1; i <= segments; ++i) - { - // WEAPONTODO (client): - // In order to do nice fading and pointing on the starting segment, we must always - // have that drawn as a separate triangle... However, that is difficult to do when - // keeping in mind the above problems and also optimizing the amount of segments - // drawn on screen at any given time. (Automatic beam quality scaling, essentially) - - vector new_origin = bezier_quadratic_getpoint( - w_shotorg, - beam_controlpoint, - beam_endpos, - i / segments); - vector new_dir = normalize(new_origin - last_origin); - - WarpZone_traceline_antilag( - self.owner, - last_origin, - new_origin, - MOVE_NORMAL, - self.owner, - ANTILAG_LATENCY(self.owner) - ); - - // Do all the transforms for warpzones right now, as we already - // "are" in the post-trace system (if we hit a player, that's - // always BEHIND the last passed wz). - last_origin = trace_endpos; - w_shotorg = WarpZone_TransformOrigin(WarpZone_trace_transform, w_shotorg); - beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); - beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); - new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir); - - float is_player = ( - IS_PLAYER(trace_ent) - || - trace_ent.classname == "body" - || - IS_MONSTER(trace_ent) - ); - - if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage))) - { - // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) - // NO. trace_endpos should be just fine. If not, - // that's an engine bug that needs proper debugging. - vector hitorigin = trace_endpos; - - float falloff = ExponentialFalloff( - WEP_CVAR(arc, beam_falloff_mindist), - WEP_CVAR(arc, beam_falloff_maxdist), - WEP_CVAR(arc, beam_falloff_halflifedist), - vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg) - ); - - if(is_player && SAME_TEAM(self.owner, trace_ent)) - { - float roothealth, rootarmor; - if(burst) - { - roothealth = WEP_CVAR(arc, burst_healing_hps); - rootarmor = WEP_CVAR(arc, burst_healing_aps); - } - else - { - roothealth = WEP_CVAR(arc, beam_healing_hps); - rootarmor = WEP_CVAR(arc, beam_healing_aps); - } - - if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth) - { - trace_ent.health = min( - trace_ent.health + (roothealth * coefficient), - WEP_CVAR(arc, beam_healing_hmax) - ); - } - if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor) - { - trace_ent.armorvalue = min( - trace_ent.armorvalue + (rootarmor * coefficient), - WEP_CVAR(arc, beam_healing_amax) - ); - } - - // stop rot, set visual effect - if(roothealth || rootarmor) - { - trace_ent.pauserothealth_finished = max( - trace_ent.pauserothealth_finished, - time + autocvar_g_balance_pause_health_rot - ); - trace_ent.pauserotarmor_finished = max( - trace_ent.pauserotarmor_finished, - time + autocvar_g_balance_pause_armor_rot - ); - new_beam_type = ARC_BT_HEAL; - } - } - else - { - float rootdamage; - if(is_player) - { - if(burst) - { rootdamage = WEP_CVAR(arc, burst_damage); } - else - { rootdamage = WEP_CVAR(arc, beam_damage); } - } - else - { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); } - - if(accuracy_isgooddamage(self.owner, trace_ent)) - { - accuracy_add( - self.owner, - WEP_ARC.m_id, - 0, - rootdamage * coefficient * falloff - ); - } - - Damage( - trace_ent, - self.owner, - self.owner, - rootdamage * coefficient * falloff, - WEP_ARC.m_id, - hitorigin, - WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff - ); - - new_beam_type = ARC_BT_HIT; - } - break; - } - else if(trace_fraction != 1) - { - // we collided with geometry - new_beam_type = ARC_BT_WALL; - break; - } - } - - // te_explosion(trace_endpos); - - // if we're bursting, use burst visual effects - new_beam_type |= burst; - - // network information: beam type - if(new_beam_type != self.beam_type) - { - self.SendFlags |= ARC_SF_BEAMTYPE; - self.beam_type = new_beam_type; - } - - self.owner.beam_prev = time; - self.nextthink = time; -} - -void W_Arc_Beam(float burst) -{ - - // only play fire sound if 1 sec has passed since player let go the fire button - if(time - self.beam_prev > 1) - sound(self, CH_WEAPON_A, W_Sound("arc_fire"), VOL_BASE, ATTN_NORM); - - entity beam = self.arc_beam = spawn(); - beam.classname = "W_Arc_Beam"; - beam.solid = SOLID_NOT; - beam.think = W_Arc_Beam_Think; - beam.owner = self; - beam.movetype = MOVETYPE_NONE; - beam.bot_dodge = true; - beam.bot_dodgerating = WEP_CVAR(arc, beam_damage); - beam.beam_bursting = burst; - Net_LinkEntity(beam, false, 0, W_Arc_Beam_Send); - - entity oldself = self; - self = beam; - self.think(); - self = oldself; -} - -void Arc_Smoke() -{ - makevectors(self.v_angle); - W_SetupShot_Range(self,true,0,"",0,0,0); - - vector smoke_origin = w_shotorg + self.velocity*frametime; - if ( self.arc_overheat > time ) - { - if ( random() < self.arc_heat_percent ) - Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 ); - if ( self.BUTTON_ATCK || self.BUTTON_ATCK2 ) - { - Send_Effect_("arc_overheat_fire", smoke_origin, w_shotdir, 1 ); - if ( !self.arc_smoke_sound ) - { - self.arc_smoke_sound = 1; - sound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop_overheat"), VOL_BASE, ATTN_NORM); - } - } - } - else if ( self.arc_beam && WEP_CVAR(arc, overheat_max) > 0 && - self.arc_beam.beam_heat > WEP_CVAR(arc, overheat_min) ) - { - if ( random() < (self.arc_beam.beam_heat-WEP_CVAR(arc, overheat_min)) / - ( WEP_CVAR(arc, overheat_max)-WEP_CVAR(arc, overheat_min) ) ) - Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 ); - } - - if ( self.arc_smoke_sound && ( self.arc_overheat <= time || - !( self.BUTTON_ATCK || self.BUTTON_ATCK2 ) ) || self.switchweapon != WEP_ARC.m_id ) - { - self.arc_smoke_sound = 0; - sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); - } -} - -bool W_Arc(int req) -{ - switch(req) - { - case WR_AIM: - { - if(WEP_CVAR(arc, beam_botaimspeed)) - { - self.BUTTON_ATCK = bot_aim( - WEP_CVAR(arc, beam_botaimspeed), - 0, - WEP_CVAR(arc, beam_botaimlifetime), - false - ); - } - else - { - self.BUTTON_ATCK = bot_aim( - 1000000, - 0, - 0.001, - false - ); - } - return true; - } - case WR_THINK: - { - Arc_Player_SetHeat(self); - Arc_Smoke(); - - if ( self.arc_overheat <= time ) - if(self.BUTTON_ATCK || self.BUTTON_ATCK2 || self.arc_beam.beam_bursting ) - { - - if(self.arc_BUTTON_ATCK_prev) - { - #if 0 - if(self.animstate_startframe == self.anim_shoot.x && self.animstate_numframes == self.anim_shoot.y) - weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready); - else - #endif - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); - } - - if((!self.arc_beam) || wasfreed(self.arc_beam)) - { - if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0)) - { - W_Arc_Beam(!!self.BUTTON_ATCK2); - - if(!self.arc_BUTTON_ATCK_prev) - { - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); - self.arc_BUTTON_ATCK_prev = 1; - } - } - } - - return true; - } - - if(self.arc_BUTTON_ATCK_prev != 0) - { - sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); - ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(); - } - self.arc_BUTTON_ATCK_prev = 0; - - #if 0 - if(self.BUTTON_ATCK2) - if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire)) - { - W_Arc_Attack2(); - self.arc_count = autocvar_g_balance_arc_secondary_count; - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack); - self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor(); - } - #endif - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_arc.md3")); - precache_model(W_Model("v_arc.md3")); - precache_model(W_Model("h_arc.iqm")); - precache_sound(W_Sound("arc_fire")); - precache_sound(W_Sound("arc_loop")); - precache_sound(W_Sound("arc_stop")); - precache_sound(W_Sound("arc_loop_overheat")); - if(!arc_shotorigin[0]) - { - arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 1); - arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 2); - arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 3); - arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 4); - } - ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0)); - } - case WR_CHECKAMMO2: - { - return WEP_CVAR(arc, overheat_max) > 0 && - ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0)); - } - case WR_CONFIG: - { - ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_KILLMESSAGE: - { - return WEAPON_ARC_MURDER; - } - case WR_DROP: - { - weapon_dropevent_item.arc_overheat = self.arc_overheat; - weapon_dropevent_item.arc_cooldown = self.arc_cooldown; - self.arc_overheat = 0; - self.arc_cooldown = 0; - return true; - } - case WR_PICKUP: - { - if ( !client_hasweapon(self, WEP_ARC.m_id, false, false) && - weapon_dropevent_item.arc_overheat > time ) - { - self.arc_overheat = weapon_dropevent_item.arc_overheat; - self.arc_cooldown = weapon_dropevent_item.arc_cooldown; - } - return true; - } - } - return false; -} -#endif -#ifdef CSQC -void Draw_ArcBeam_callback(vector start, vector hit, vector end) -{ - entity beam = Draw_ArcBeam_callback_entity; - vector transformed_view_org; - transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin); - - // Thickdir shall be perpendicular to the beam and to the view-to-beam direction (WEAPONTODO: WHY) - // WEAPONTODO: Wouldn't it be better to be perpendicular to the beam and to the view FORWARD direction? - vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start)); - - vector hitorigin; - - // draw segment - #if 0 - if(trace_fraction != 1) - { - // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) - hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction); - hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin); - } - else - { - hitorigin = hit; - } - #else - hitorigin = hit; - #endif - - // decide upon thickness - float thickness = beam.beam_thickness; - - // draw primary beam render - vector top = hitorigin + (thickdir * thickness); - vector bottom = hitorigin - (thickdir * thickness); - - vector last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); - vector last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); - - R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE - R_PolygonVertex( - top, - '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)), - beam.beam_color, - beam.beam_alpha - ); - R_PolygonVertex( - last_top, - '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), - beam.beam_color, - beam.beam_alpha - ); - R_PolygonVertex( - last_bottom, - '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), - beam.beam_color, - beam.beam_alpha - ); - R_PolygonVertex( - bottom, - '0 0.5 0' * (1 - (thickness / beam.beam_thickness)), - beam.beam_color, - beam.beam_alpha - ); - R_EndPolygon(); - - // draw trailing particles - // NOTES: - // - Don't use spammy particle counts here, use a FEW small particles around the beam - // - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves. - if(beam.beam_traileffect) - { - trailparticles(beam, beam.beam_traileffect, start, hitorigin); - } - - // set up for the next - Draw_ArcBeam_callback_last_thickness = thickness; - Draw_ArcBeam_callback_last_top = WarpZone_UnTransformOrigin(WarpZone_trace_transform, top); - Draw_ArcBeam_callback_last_bottom = WarpZone_UnTransformOrigin(WarpZone_trace_transform, bottom); -} - -void Reset_ArcBeam(void) -{ - entity e; - for (e = world; (e = findfloat(e, beam_usevieworigin, 1)); ) { - e.beam_initialized = false; - } - for (e = world; (e = findfloat(e, beam_usevieworigin, 2)); ) { - e.beam_initialized = false; - } -} - -void Draw_ArcBeam(void) -{ - float dt = time - self.move_time; - self.move_time = time; - if(dt <= 0) { return; } - - if(!self.beam_usevieworigin) - { - InterpolateOrigin_Do(); - } - - // origin = beam starting origin - // v_angle = wanted/aim direction - // angles = current direction of beam - - vector start_pos; - vector wantdir; //= view_forward; - vector beamdir; //= self.beam_dir; - - float segments; - if(self.beam_usevieworigin) - { - // WEAPONTODO: - // Currently we have to replicate nearly the same method of figuring - // out the shotdir that the server does... Ideally in the future we - // should be able to acquire this from a generalized function built - // into a weapon system for client code. - - // find where we are aiming - makevectors(warpzone_save_view_angles); - vector forward = v_forward; - vector right = v_right; - vector up = v_up; - - // decide upon start position - if(self.beam_usevieworigin == 2) - { start_pos = warpzone_save_view_origin; } - else - { start_pos = self.origin; } - - // trace forward with an estimation - WarpZone_TraceLine( - start_pos, - start_pos + forward * self.beam_range, - MOVE_NOMONSTERS, - self - ); - - // untransform in case our trace went through a warpzone - vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); - - // un-adjust trueaim if shotend is too close - if(vlen(end_pos - start_pos) < g_trueaim_minrange) - end_pos = start_pos + (forward * g_trueaim_minrange); - - // move shot origin to the actual gun muzzle origin - vector origin_offset = - right * -self.beam_shotorigin.y - + up * self.beam_shotorigin.z; - - start_pos = start_pos + origin_offset; - - // Move it also forward, but only as far as possible without hitting anything. Don't poke into walls! - traceline(start_pos, start_pos + forward * self.beam_shotorigin.x, MOVE_NORMAL, self); - start_pos = trace_endpos; - - // calculate the aim direction now - wantdir = normalize(end_pos - start_pos); - - if(!self.beam_initialized) - { - self.beam_dir = wantdir; - self.beam_initialized = true; - } - - if(self.beam_dir != wantdir) - { - // calculate how much we're going to move the end of the beam to the want position - // WEAPONTODO (server and client): - // blendfactor never actually becomes 0 in this situation, which is a problem - // regarding precision... this means that self.beam_dir and w_shotdir approach - // eachother, however they never actually become the same value with this method. - // Perhaps we should do some form of rounding/snapping? - float angle = vlen(wantdir - self.beam_dir) * RAD2DEG; - if(angle && (angle > self.beam_maxangle)) - { - // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor - float blendfactor = bound( - 0, - (1 - (self.beam_returnspeed * frametime)), - min(self.beam_maxangle / angle, 1) - ); - self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); - } - else - { - // the radius is not too far yet, no worries :D - float blendfactor = bound( - 0, - (1 - (self.beam_returnspeed * frametime)), - 1 - ); - self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); - } - - // calculate how many segments are needed - float max_allowed_segments; - - if(self.beam_distancepersegment) - { - max_allowed_segments = min( - ARC_MAX_SEGMENTS, - 1 + (vlen(wantdir / self.beam_distancepersegment)) - ); - } - else { max_allowed_segments = ARC_MAX_SEGMENTS; } - - if(self.beam_degreespersegment) - { - segments = bound( - 1, - ( - min( - angle, - self.beam_maxangle - ) - / - self.beam_degreespersegment - ), - max_allowed_segments - ); - } - else { segments = 1; } - } - else { segments = 1; } - - // set the beam direction which the rest of the code will refer to - beamdir = self.beam_dir; - - // finally, set self.angles to the proper direction so that muzzle attachment points in proper direction - self.angles = fixedvectoangles2(forward, up); // TODO(Samual): is this == warpzone_save_view_angles? - } - else - { - // set the values from the provided info from the networked entity - start_pos = self.origin; - wantdir = self.v_angle; - beamdir = self.angles; - - if(beamdir != wantdir) - { - float angle = vlen(wantdir - beamdir) * RAD2DEG; - - // calculate how many segments are needed - float max_allowed_segments; - - if(self.beam_distancepersegment) - { - max_allowed_segments = min( - ARC_MAX_SEGMENTS, - 1 + (vlen(wantdir / self.beam_distancepersegment)) - ); - } - else { max_allowed_segments = ARC_MAX_SEGMENTS; } - - if(self.beam_degreespersegment) - { - segments = bound( - 1, - ( - min( - angle, - self.beam_maxangle - ) - / - self.beam_degreespersegment - ), - max_allowed_segments - ); - } - else { segments = 1; } - } - else { segments = 1; } - } - - setorigin(self, start_pos); - self.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead? - - vector beam_endpos = (start_pos + (beamdir * self.beam_range)); - vector beam_controlpoint = start_pos + wantdir * (self.beam_range * (1 - self.beam_tightness)); - - Draw_ArcBeam_callback_entity = self; - Draw_ArcBeam_callback_last_thickness = 0; - Draw_ArcBeam_callback_last_top = start_pos; - Draw_ArcBeam_callback_last_bottom = start_pos; - - vector last_origin = start_pos; - vector original_start_pos = start_pos; - - float i; - for(i = 1; i <= segments; ++i) - { - // WEAPONTODO (client): - // In order to do nice fading and pointing on the starting segment, we must always - // have that drawn as a separate triangle... However, that is difficult to do when - // keeping in mind the above problems and also optimizing the amount of segments - // drawn on screen at any given time. (Automatic beam quality scaling, essentially) - - vector new_origin = bezier_quadratic_getpoint( - start_pos, - beam_controlpoint, - beam_endpos, - i / segments); - - WarpZone_TraceBox_ThroughZone( - last_origin, - '0 0 0', - '0 0 0', - new_origin, - MOVE_NORMAL, - world, - world, - Draw_ArcBeam_callback - ); - - // Do all the transforms for warpzones right now, as we already "are" in the post-trace - // system (if we hit a player, that's always BEHIND the last passed wz). - last_origin = trace_endpos; - start_pos = WarpZone_TransformOrigin(WarpZone_trace_transform, start_pos); - beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); - beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); - beamdir = WarpZone_TransformVelocity(WarpZone_trace_transform, beamdir); - Draw_ArcBeam_callback_last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); - Draw_ArcBeam_callback_last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); - - if(trace_fraction < 1) { break; } - } - - // visual effects for startpoint and endpoint - if(self.beam_hiteffect) - { - // FIXME we really should do this on the server so it actually - // matches gameplay. What this client side stuff is doing is no - // more than guesswork. - if((trace_ent || trace_fraction < 1) && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)) - pointparticles( - self.beam_hiteffect, - last_origin, - beamdir * -1, - frametime * 2 - ); - } - if(self.beam_hitlight[0]) - { - adddynamiclight( - last_origin, - self.beam_hitlight[0], - vec3( - self.beam_hitlight[1], - self.beam_hitlight[2], - self.beam_hitlight[3] - ) - ); - } - if(self.beam_muzzleeffect) - { - pointparticles( - self.beam_muzzleeffect, - original_start_pos + wantdir * 20, - wantdir * 1000, - frametime * 0.1 - ); - } - if(self.beam_muzzlelight[0]) - { - adddynamiclight( - original_start_pos + wantdir * 20, - self.beam_muzzlelight[0], - vec3( - self.beam_muzzlelight[1], - self.beam_muzzlelight[2], - self.beam_muzzlelight[3] - ) - ); - } - - // cleanup - Draw_ArcBeam_callback_entity = world; - Draw_ArcBeam_callback_last_thickness = 0; - Draw_ArcBeam_callback_last_top = '0 0 0'; - Draw_ArcBeam_callback_last_bottom = '0 0 0'; -} - -void Remove_ArcBeam(void) -{ - remove(self.beam_muzzleentity); - sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); -} - -void Ent_ReadArcBeam(float isnew) -{ - int sf = ReadByte(); - entity flash; - - if(isnew) - { - // calculate shot origin offset from gun alignment - int gunalign = autocvar_cl_gunalign; - if(gunalign != 1 && gunalign != 2 && gunalign != 4) - gunalign = 3; // default value - --gunalign; - - self.beam_shotorigin = arc_shotorigin[gunalign]; - - // set other main attributes of the beam - self.draw = Draw_ArcBeam; - self.entremove = Remove_ArcBeam; - self.move_time = time; - loopsound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop"), VOL_BASE, ATTEN_NORM); - - flash = spawn(); - flash.owner = self; - flash.effects = EF_ADDITIVE | EF_FULLBRIGHT; - flash.drawmask = MASK_NORMAL; - flash.solid = SOLID_NOT; - flash.avelocity_z = 5000; - setattachment(flash, self, ""); - setorigin(flash, '0 0 0'); - - self.beam_muzzleentity = flash; - } - else - { - flash = self.beam_muzzleentity; - } - - if(sf & ARC_SF_SETTINGS) // settings information - { - self.beam_degreespersegment = ReadShort(); - self.beam_distancepersegment = ReadShort(); - self.beam_maxangle = ReadShort(); - self.beam_range = ReadCoord(); - self.beam_returnspeed = ReadShort(); - self.beam_tightness = (ReadByte() / 10); - - if(ReadByte()) - { - if(autocvar_chase_active) - { self.beam_usevieworigin = 1; } - else // use view origin - { self.beam_usevieworigin = 2; } - } - else - { - self.beam_usevieworigin = 0; - } - } - - if(!self.beam_usevieworigin) - { - // self.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work? - self.iflags = IFLAG_ORIGIN; - - InterpolateOrigin_Undo(); - } - - if(sf & ARC_SF_START) // starting location - { - self.origin_x = ReadCoord(); - self.origin_y = ReadCoord(); - self.origin_z = ReadCoord(); - } - else if(self.beam_usevieworigin) // infer the location from player location - { - if(self.beam_usevieworigin == 2) - { - // use view origin - self.origin = view_origin; - } - else - { - // use player origin so that third person display still works - self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT)); - } - } - - setorigin(self, self.origin); - - if(sf & ARC_SF_WANTDIR) // want/aim direction - { - self.v_angle_x = ReadCoord(); - self.v_angle_y = ReadCoord(); - self.v_angle_z = ReadCoord(); - } - - if(sf & ARC_SF_BEAMDIR) // beam direction - { - self.angles_x = ReadCoord(); - self.angles_y = ReadCoord(); - self.angles_z = ReadCoord(); - } - - if(sf & ARC_SF_BEAMTYPE) // beam type - { - self.beam_type = ReadByte(); - switch(self.beam_type) - { - case ARC_BT_MISS: - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 8; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 8; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; // particleeffectnum(EFFECT_GRENADE_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - case ARC_BT_HEAL: - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 8; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT); - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - case ARC_BT_HIT: - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 8; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); - self.beam_hitlight[0] = 20; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 0; - self.beam_hitlight[3] = 0; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 50; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 0; - self.beam_muzzlelight[3] = 0; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - case ARC_BT_BURST_MISS: - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 14; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - case ARC_BT_BURST_WALL: - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 14; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - case ARC_BT_BURST_HEAL: - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 14; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT2); - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - case ARC_BT_BURST_HIT: - { - self.beam_color = '1 1 1'; - self.beam_alpha = 0.5; - self.beam_thickness = 14; - self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); - self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - - // shouldn't be possible, but lets make it colorful if it does :D - default: - { - self.beam_color = randomvec(); - self.beam_alpha = 1; - self.beam_thickness = 8; - self.beam_traileffect = false; - self.beam_hiteffect = false; - self.beam_hitlight[0] = 0; - self.beam_hitlight[1] = 1; - self.beam_hitlight[2] = 1; - self.beam_hitlight[3] = 1; - self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); - self.beam_muzzlelight[0] = 0; - self.beam_muzzlelight[1] = 1; - self.beam_muzzlelight[2] = 1; - self.beam_muzzlelight[3] = 1; - self.beam_image = "particles/lgbeam"; - if(self.beam_muzzleeffect >= 0) - { - setmodel(flash, "models/flash.md3"); - flash.alpha = self.beam_alpha; - flash.colormod = self.beam_color; - flash.scale = 0.5; - } - break; - } - } - } - - if(!self.beam_usevieworigin) - { - InterpolateOrigin_Note(); - } -} - -bool W_Arc(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - // todo - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("arc_loop")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_blaster.qc b/qcsrc/common/weapons/w_blaster.qc deleted file mode 100644 index 6234f4a3e1..0000000000 --- a/qcsrc/common/weapons/w_blaster.qc +++ /dev/null @@ -1,297 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ BLASTER, -/* function */ W_Blaster, -/* ammotype */ ammo_none, -/* impulse */ 1, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ 0, -/* color */ '1 0.5 0.5', -/* modelname */ "laser", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairlaser 0.5", -/* wepimg */ "weaponlaser", -/* refname */ "blaster", -/* wepname */ _("Blaster") -); - -#define BLASTER_SETTINGS(w_cvar,w_prop) BLASTER_SETTINGS_LIST(w_cvar, w_prop, BLASTER, blaster) -#define BLASTER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, delay) \ - w_cvar(id, sn, BOTH, edgedamage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, force_zscale) \ - w_cvar(id, sn, BOTH, lifetime) \ - w_cvar(id, sn, BOTH, radius) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, shotangle) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, BOTH, spread) \ - w_cvar(id, sn, NONE, secondary) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -BLASTER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float blaster_damage; -.float blaster_edgedamage; -.float blaster_radius; -.float blaster_force; -.float blaster_lifetime; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_blaster(void) { weapon_defaultspawnfunc(WEP_BLASTER.m_id); } -void spawnfunc_weapon_laser(void) { spawnfunc_weapon_blaster(); } - -void W_Blaster_Touch(void) -{ - PROJECTILE_TOUCH; - - self.event_damage = func_null; - - RadiusDamage( - self, - self.realowner, - self.blaster_damage, - self.blaster_edgedamage, - self.blaster_radius, - world, - world, - self.blaster_force, - self.projectiledeathtype, - other - ); - - remove(self); -} - -void W_Blaster_Think(void) -{ - self.movetype = MOVETYPE_FLY; - self.think = SUB_Remove; - self.nextthink = time + self.blaster_lifetime; - CSQCProjectile(self, true, PROJECTILE_BLASTER, true); -} - -void W_Blaster_Attack( - float atk_deathtype, - float atk_shotangle, - float atk_damage, - float atk_edgedamage, - float atk_radius, - float atk_force, - float atk_speed, - float atk_spread, - float atk_delay, - float atk_lifetime) -{ - vector s_forward = v_forward * cos(atk_shotangle * DEG2RAD) + v_up * sin(atk_shotangle * DEG2RAD); - - W_SetupShot_Dir(self, s_forward, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, atk_damage); - Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - entity missile = spawn(); - missile.owner = missile.realowner = self; - missile.classname = "blasterbolt"; - missile.bot_dodge = true; - missile.bot_dodgerating = atk_damage; - PROJECTILE_MAKETRIGGER(missile); - - missile.blaster_damage = atk_damage; - missile.blaster_edgedamage = atk_edgedamage; - missile.blaster_radius = atk_radius; - missile.blaster_force = atk_force; - missile.blaster_lifetime = atk_lifetime; - - setorigin(missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - W_SetupProjVelocity_Explicit( - missile, - w_shotdir, - v_up, - atk_speed, - 0, - 0, - atk_spread, - false - ); - - missile.angles = vectoangles(missile.velocity); - - //missile.glow_color = 250; // 244, 250 - //missile.glow_size = 120; - - missile.touch = W_Blaster_Touch; - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - missile.projectiledeathtype = atk_deathtype; - missile.think = W_Blaster_Think; - missile.nextthink = time + atk_delay; - - MUTATOR_CALLHOOK(EditProjectile, self, missile); - - if(time >= missile.nextthink) - { - entity oldself; - oldself = self; - self = missile; - self.think(); - self = oldself; - } -} -bool W_Blaster(int request) -{ - switch(request) - { - case WR_AIM: - { - if(WEP_CVAR(blaster, secondary)) - { - if((random() * (WEP_CVAR_PRI(blaster, damage) + WEP_CVAR_SEC(blaster, damage))) > WEP_CVAR_PRI(blaster, damage)) - { self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(blaster, speed), 0, WEP_CVAR_SEC(blaster, lifetime), false); } - else - { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); } - } - else - { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); } - - return true; - } - - case WR_THINK: - { - if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR_PRI(blaster, refire))) - { - W_Blaster_Attack( - WEP_BLASTER.m_id, - WEP_CVAR_PRI(blaster, shotangle), - WEP_CVAR_PRI(blaster, damage), - WEP_CVAR_PRI(blaster, edgedamage), - WEP_CVAR_PRI(blaster, radius), - WEP_CVAR_PRI(blaster, force), - WEP_CVAR_PRI(blaster, speed), - WEP_CVAR_PRI(blaster, spread), - WEP_CVAR_PRI(blaster, delay), - WEP_CVAR_PRI(blaster, lifetime) - ); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(blaster, animtime), w_ready); - } - } - else if(self.BUTTON_ATCK2) - { - switch(WEP_CVAR(blaster, secondary)) - { - case 0: // switch to last used weapon - { - if(self.switchweapon == WEP_BLASTER.m_id) // don't do this if already switching - W_LastWeapon(); - break; - } - - case 1: // normal projectile secondary - { - if(weapon_prepareattack(1, WEP_CVAR_SEC(blaster, refire))) - { - W_Blaster_Attack( - WEP_BLASTER.m_id | HITTYPE_SECONDARY, - WEP_CVAR_SEC(blaster, shotangle), - WEP_CVAR_SEC(blaster, damage), - WEP_CVAR_SEC(blaster, edgedamage), - WEP_CVAR_SEC(blaster, radius), - WEP_CVAR_SEC(blaster, force), - WEP_CVAR_SEC(blaster, speed), - WEP_CVAR_SEC(blaster, spread), - WEP_CVAR_SEC(blaster, delay), - WEP_CVAR_SEC(blaster, lifetime) - ); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(blaster, animtime), w_ready); - } - - break; - } - } - } - return true; - } - - case WR_INIT: - { - precache_model(W_Model("g_laser.md3")); - precache_model(W_Model("v_laser.md3")); - precache_model(W_Model("h_laser.iqm")); - precache_sound(W_Sound("lasergun_fire")); - BLASTER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - - case WR_SETUP: - { - self.ammo_field = ammo_none; - return true; - } - - case WR_CHECKAMMO1: - case WR_CHECKAMMO2: - { - return true; // laser has infinite ammo - } - - case WR_CONFIG: - { - BLASTER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - - case WR_SUICIDEMESSAGE: - { - return WEAPON_BLASTER_SUICIDE; - } - - case WR_KILLMESSAGE: - { - return WEAPON_BLASTER_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Blaster(int request) -{ - switch(request) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); - if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); } - return true; - } - - case WR_INIT: - { - precache_sound(W_Sound("laserimpact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_crylink.qc b/qcsrc/common/weapons/w_crylink.qc deleted file mode 100644 index 08deaf78c4..0000000000 --- a/qcsrc/common/weapons/w_crylink.qc +++ /dev/null @@ -1,731 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ CRYLINK, -/* function */ W_Crylink, -/* ammotype */ ammo_cells, -/* impulse */ 6, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '1 0.5 1', -/* modelname */ "crylink", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshaircrylink 0.5", -/* wepimg */ "weaponcrylink", -/* refname */ "crylink", -/* wepname */ _("Crylink") -); - -#define CRYLINK_SETTINGS(w_cvar,w_prop) CRYLINK_SETTINGS_LIST(w_cvar, w_prop, CRYLINK, crylink) -#define CRYLINK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, ammo) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, edgedamage) \ - w_cvar(id, sn, BOTH, radius) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, spread) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, BOTH, shots) \ - w_cvar(id, sn, BOTH, bounces) \ - w_cvar(id, sn, BOTH, bouncedamagefactor) \ - w_cvar(id, sn, BOTH, middle_lifetime) \ - w_cvar(id, sn, BOTH, middle_fadetime) \ - w_cvar(id, sn, BOTH, other_lifetime) \ - w_cvar(id, sn, BOTH, other_fadetime) \ - w_cvar(id, sn, BOTH, linkexplode) \ - w_cvar(id, sn, BOTH, joindelay) \ - w_cvar(id, sn, BOTH, joinspread) \ - w_cvar(id, sn, BOTH, joinexplode) \ - w_cvar(id, sn, BOTH, joinexplode_damage) \ - w_cvar(id, sn, BOTH, joinexplode_edgedamage) \ - w_cvar(id, sn, BOTH, joinexplode_radius) \ - w_cvar(id, sn, BOTH, joinexplode_force) \ - w_cvar(id, sn, SEC, spreadtype) \ - w_cvar(id, sn, NONE, secondary) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -CRYLINK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float gravity; -.float crylink_waitrelease; -.entity crylink_lastgroup; - -.entity queuenext; -.entity queueprev; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_crylink(void) { weapon_defaultspawnfunc(WEP_CRYLINK.m_id); } - -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; - - float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY); - - RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, 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: joining speed (calculate this as join spread * initial speed) -float w_crylink_linkjoin_time; -vector W_Crylink_LinkJoin(entity e, float jspeed) -{ - 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) - { - 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 - { - 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(void) -{ - // 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) - { - float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY); - - if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode)) - { - n /= WEP_CVAR_BOTH(crylink, isprimary, shots); - RadiusDamage( - e, - e.realowner, - WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n, - WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n, - WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n, - e.realowner, - world, - WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n, - e.projectiledeathtype, - other - ); - Send_Effect(EFFECT_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(SAME_TEAM(head, projectile.realowner)) - ++hit_friendly; - else - ++hit_enemy; - } - - 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; - float isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY); - 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 = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor); - if(a) - f *= a; - - float totaldamage = RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * f, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * f, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * f, self.projectiledeathtype, other); - - if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(self, WEP_CVAR_BOTH(crylink, isprimary, 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(WEP_CVAR_PRI(crylink, ammo)); - - maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots); - maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces); - if(WEP_CVAR_PRI(crylink, joinexplode)) - maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage); - - W_SetupShot(self, false, 2, W_Sound("crylink_fire"), CH_WEAPON_A, maxdmg); - forward = v_forward; - right = v_right; - up = v_up; - - shots = WEP_CVAR_PRI(crylink, shots); - Send_Effect(EFFECT_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 = WEP_CVAR_PRI(crylink, 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.m_id; - //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 * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor; - W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false); - proj.touch = W_Crylink_Touch; - - proj.think = W_Crylink_Fadethink; - if(counter == 0) - { - proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime); - proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime); - proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime); - } - else - { - proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime); - proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime); - proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime); - } - proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay); - proj.cnt = WEP_CVAR_PRI(crylink, 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); - - MUTATOR_CALLHOOK(EditProjectile, self, proj); - } - if(WEP_CVAR_PRI(crylink, joinspread) != 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(WEP_CVAR_SEC(crylink, ammo)); - - maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots); - maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces); - if(WEP_CVAR_SEC(crylink, joinexplode)) - maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage); - - W_SetupShot(self, false, 2, W_Sound("crylink_fire2"), CH_WEAPON_A, maxdmg); - forward = v_forward; - right = v_right; - up = v_up; - - shots = WEP_CVAR_SEC(crylink, shots); - Send_Effect(EFFECT_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 = WEP_CVAR_SEC(crylink, 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.m_id | HITTYPE_SECONDARY; - //proj.gravity = 0.001; - - setorigin(proj, w_shotorg); - setsize(proj, '0 0 0', '0 0 0'); - - if(WEP_CVAR_SEC(crylink, 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 * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor; - s = w_shotdir + right * s.y + up * s.z; - } - else - { - s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor); - } - - W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false); - proj.touch = W_Crylink_Touch; - proj.think = W_Crylink_Fadethink; - if(counter == (shots - 1) / 2) - { - proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime); - proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime); - proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime); - } - else - { - proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime); - proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime); - proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime); - } - proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay); - proj.cnt = WEP_CVAR_SEC(crylink, 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); - - MUTATOR_CALLHOOK(EditProjectile, self, proj); - } - if(WEP_CVAR_SEC(crylink, joinspread) != 0) - { - self.crylink_lastgroup = proj; - W_Crylink_CheckLinks(proj); - self.crylink_waitrelease = 2; - } -} - -bool W_Crylink(int req) -{ - float ammo_amount; - switch(req) - { - case WR_AIM: - { - if(random() < 0.10) - self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false); - else - self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false); - - return true; - } - case WR_THINK: - { - if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - - if(self.BUTTON_ATCK) - { - if(self.crylink_waitrelease != 1) - if(weapon_prepareattack(0, WEP_CVAR_PRI(crylink, refire))) - { - W_Crylink_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready); - } - } - - if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary) - { - if(self.crylink_waitrelease != 2) - if(weapon_prepareattack(1, WEP_CVAR_SEC(crylink, refire))) - { - W_Crylink_Attack2(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(crylink, 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; - float isprimary = (self.crylink_waitrelease == 1); - - pos = W_Crylink_LinkJoin(self.crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed)); - - 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(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - // ran out of ammo! - self.cnt = WEP_CRYLINK.m_id; - self.switchweapon = w_getbestweapon(self); - } - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_crylink.md3")); - precache_model(W_Model("v_crylink.md3")); - precache_model(W_Model("h_crylink.iqm")); - precache_sound(W_Sound("crylink_fire")); - precache_sound(W_Sound("crylink_fire2")); - precache_sound(W_Sound("crylink_linkjoin")); - CRYLINK_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case 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.WEP_AMMO(CRYLINK) >= WEP_CVAR_PRI(crylink, ammo); - ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo); - return ammo_amount; - } - case 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.WEP_AMMO(CRYLINK) >= WEP_CVAR_SEC(crylink, ammo); - ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo); - return ammo_amount; - } - case WR_CONFIG: - { - CRYLINK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_CRYLINK_SUICIDE; - } - case WR_KILLMESSAGE: - { - return WEAPON_CRYLINK_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Crylink(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 2; - if(w_deathtype & HITTYPE_SECONDARY) - { - pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT2), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("crylink_impact2"), VOL_BASE, ATTN_NORM); - } - else - { - pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("crylink_impact"), VOL_BASE, ATTN_NORM); - } - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("crylink_impact2")); - precache_sound(W_Sound("crylink_impact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_devastator.qc b/qcsrc/common/weapons/w_devastator.qc deleted file mode 100644 index 9a235bf35d..0000000000 --- a/qcsrc/common/weapons/w_devastator.qc +++ /dev/null @@ -1,687 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ DEVASTATOR, -/* function */ W_Devastator, -/* ammotype */ ammo_rockets, -/* impulse */ 9, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* color */ '1 1 0', -/* modelname */ "rl", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairrocketlauncher 0.7", -/* wepimg */ "weaponrocketlauncher", -/* refname */ "devastator", -/* wepname */ _("Devastator") -); - -#define DEVASTATOR_SETTINGS(w_cvar,w_prop) DEVASTATOR_SETTINGS_LIST(w_cvar, w_prop, DEVASTATOR, devastator) -#define DEVASTATOR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, ammo) \ - w_cvar(id, sn, NONE, animtime) \ - w_cvar(id, sn, NONE, damage) \ - w_cvar(id, sn, NONE, damageforcescale) \ - w_cvar(id, sn, NONE, detonatedelay) \ - w_cvar(id, sn, NONE, edgedamage) \ - w_cvar(id, sn, NONE, force) \ - w_cvar(id, sn, NONE, guidedelay) \ - w_cvar(id, sn, NONE, guidegoal) \ - w_cvar(id, sn, NONE, guiderate) \ - w_cvar(id, sn, NONE, guideratedelay) \ - w_cvar(id, sn, NONE, guidestop) \ - w_cvar(id, sn, NONE, health) \ - w_cvar(id, sn, NONE, lifetime) \ - w_cvar(id, sn, NONE, radius) \ - w_cvar(id, sn, NONE, refire) \ - w_cvar(id, sn, NONE, remote_damage) \ - w_cvar(id, sn, NONE, remote_edgedamage) \ - w_cvar(id, sn, NONE, remote_force) \ - w_cvar(id, sn, NONE, remote_jump_damage) \ - w_cvar(id, sn, NONE, remote_jump_radius) \ - w_cvar(id, sn, NONE, remote_jump_velocity_z_add) \ - w_cvar(id, sn, NONE, remote_jump_velocity_z_max) \ - w_cvar(id, sn, NONE, remote_jump_velocity_z_min) \ - w_cvar(id, sn, NONE, remote_radius) \ - w_cvar(id, sn, NONE, speed) \ - w_cvar(id, sn, NONE, speedaccel) \ - w_cvar(id, sn, NONE, speedstart) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -DEVASTATOR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float rl_release; -.float rl_detonate_later; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_devastator(void) { weapon_defaultspawnfunc(WEP_DEVASTATOR.m_id); } -void spawnfunc_weapon_rocketlauncher(void) { spawnfunc_weapon_devastator(); } - -void W_Devastator_Unregister(void) -{ - if(self.realowner && self.realowner.lastrocket == self) - { - self.realowner.lastrocket = world; - // self.realowner.rl_release = 1; - } -} - -void W_Devastator_Explode(void) -{ - W_Devastator_Unregister(); - - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(DIFF_TEAM(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, - WEP_CVAR(devastator, damage), - WEP_CVAR(devastator, edgedamage), - WEP_CVAR(devastator, radius), - world, - world, - WEP_CVAR(devastator, force), - self.projectiledeathtype, - other - ); - - if(self.realowner.weapon == WEP_DEVASTATOR.m_id) - { - if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo)) - if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)) - { - self.realowner.cnt = WEP_DEVASTATOR.m_id; - ATTACK_FINISHED(self.realowner) = time; - self.realowner.switchweapon = w_getbestweapon(self.realowner); - } - } - remove(self); -} - -void W_Devastator_DoRemoteExplode(void) -{ - W_Devastator_Unregister(); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - float handled_as_rocketjump = false; - - entity head = WarpZone_FindRadius( - self.origin, - WEP_CVAR(devastator, remote_jump_radius), - false - ); - - while(head) - { - if(head.takedamage && (head == self.realowner)) - { - float distance_to_head = vlen(self.origin - head.WarpZone_findradius_nearest); - if(distance_to_head <= WEP_CVAR(devastator, remote_jump_radius)) - { - // we handled this as a rocketjump :) - handled_as_rocketjump = true; - - // modify velocity - head.velocity_x *= 0.9; - head.velocity_y *= 0.9; - head.velocity_z = bound( - WEP_CVAR(devastator, remote_jump_velocity_z_min), - head.velocity.z + WEP_CVAR(devastator, remote_jump_velocity_z_add), - WEP_CVAR(devastator, remote_jump_velocity_z_max) - ); - - // now do the damage - RadiusDamage( - self, - head, - WEP_CVAR(devastator, remote_jump_damage), - WEP_CVAR(devastator, remote_jump_damage), - WEP_CVAR(devastator, remote_jump_radius), - world, - head, - 0, - self.projectiledeathtype | HITTYPE_BOUNCE, - world - ); - break; - } - } - head = head.chain; - } - - RadiusDamage( - self, - self.realowner, - WEP_CVAR(devastator, remote_damage), - WEP_CVAR(devastator, remote_edgedamage), - WEP_CVAR(devastator, remote_radius), - (handled_as_rocketjump ? head : world), - world, - WEP_CVAR(devastator, remote_force), - self.projectiledeathtype | HITTYPE_BOUNCE, - world - ); - - if(self.realowner.weapon == WEP_DEVASTATOR.m_id) - { - if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo)) - if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)) - { - self.realowner.cnt = WEP_DEVASTATOR.m_id; - ATTACK_FINISHED(self.realowner) = time; - self.realowner.switchweapon = w_getbestweapon(self.realowner); - } - } - remove(self); -} - -void W_Devastator_RemoteExplode(void) -{ - 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) > WEP_CVAR(devastator, remote_radius)) // safety device - ) - { - W_Devastator_DoRemoteExplode(); - } - } -} - -vector W_Devastator_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_Devastator_Think(void) -{ - vector desireddir, olddir, newdir, desiredorigin, goal; - float velspeed, f; - self.nextthink = time; - if(time > self.cnt) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_Devastator_Explode(); - return; - } - - // accelerate - makevectors(self.angles.x * '-1 0 0' + self.angles.y * '0 1 0'); - velspeed = WEP_CVAR(devastator, speed) * W_WeaponSpeedFactor() - (self.velocity * v_forward); - if(velspeed > 0) - self.velocity = self.velocity + v_forward * min(WEP_CVAR(devastator, speedaccel) * W_WeaponSpeedFactor() * frametime, velspeed); - - // laser guided, or remote detonation - if(self.realowner.weapon == WEP_DEVASTATOR.m_id) - { - if(self == self.realowner.lastrocket) - if(!self.realowner.rl_release) - if(!self.BUTTON_ATCK2) - if(WEP_CVAR(devastator, guiderate)) - if(time > self.pushltime) - if(self.realowner.deadflag == DEAD_NO) - { - f = WEP_CVAR(devastator, 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 + WEP_CVAR(devastator, guidegoal)) * desireddir; - newdir = W_Devastator_SteerTo(olddir, normalize(goal - self.origin), cos(WEP_CVAR(devastator, guiderate) * f * frametime * DEG2RAD)); - - self.velocity = newdir * velspeed; - self.angles = vectoangles(self.velocity); - - if(!self.count) - { - Send_Effect(EFFECT_ROCKET_GUIDE, self.origin, self.velocity, 1); - // TODO add a better sound here - sound(self.realowner, CH_WEAPON_B, W_Sound("rocket_mode"), VOL_BASE, ATTN_NORM); - self.count = 1; - } - } - - if(self.rl_detonate_later) - W_Devastator_RemoteExplode(); - } - - if(self.csqcprojectile_clientanimate == 0) - UpdateCSQCProjectile(self); -} - -void W_Devastator_Touch(void) -{ - if(WarpZone_Projectile_Touch()) - { - if(wasfreed(self)) - W_Devastator_Unregister(); - return; - } - W_Devastator_Unregister(); - W_Devastator_Explode(); -} - -void W_Devastator_Damage(entity inflictor, entity attacker, float damage, int 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_Devastator_Explode); -} - -void W_Devastator_Attack(void) -{ - entity missile; - entity flash; - - W_DecreaseAmmo(WEP_CVAR(devastator, ammo)); - - W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 5, W_Sound("rocket_fire"), CH_WEAPON_A, WEP_CVAR(devastator, damage)); - Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - missile = WarpZone_RefSys_SpawnSameRefSys(self); - missile.owner = missile.realowner = self; - self.lastrocket = missile; - if(WEP_CVAR(devastator, detonatedelay) >= 0) - missile.spawnshieldtime = time + WEP_CVAR(devastator, detonatedelay); - else - missile.spawnshieldtime = -1; - missile.pushltime = time + WEP_CVAR(devastator, guidedelay); - missile.classname = "rocket"; - missile.bot_dodge = true; - missile.bot_dodgerating = WEP_CVAR(devastator, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous - - missile.takedamage = DAMAGE_YES; - missile.damageforcescale = WEP_CVAR(devastator, damageforcescale); - missile.health = WEP_CVAR(devastator, health); - missile.event_damage = W_Devastator_Damage; - missile.damagedbycontents = true; - - missile.movetype = MOVETYPE_FLY; - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_DEVASTATOR.m_id; - 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_SetupProjVelocity_Basic(missile, WEP_CVAR(devastator, speedstart), 0); - missile.angles = vectoangles(missile.velocity); - - missile.touch = W_Devastator_Touch; - missile.think = W_Devastator_Think; - missile.nextthink = time; - missile.cnt = time + WEP_CVAR(devastator, lifetime); - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - CSQCProjectile(missile, WEP_CVAR(devastator, guiderate) == 0 && WEP_CVAR(devastator, 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 - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -bool W_Devastator(int req) -{ - entity rock; - float rockfound; - float ammo_amount; - switch(req) - { - #if 0 - case WR_AIM: - { - // aim and decide to fire if appropriate - self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), false); - if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! - { - // decide whether to detonate rockets - entity missile, targetlist, targ; - targetlist = findchainfloat(bot_attack, true); - for(missile = world; (missile = find(missile, classname, "rocket")); ) if(missile.realowner == self) - { - targ = targetlist; - while(targ) - { - if(targ != missile.realowner && vlen(targ.origin - missile.origin) < WEP_CVAR(devastator, radius)) - { - self.BUTTON_ATCK2 = true; - break; - } - targ = targ.chain; - } - } - - if(self.BUTTON_ATCK2) self.BUTTON_ATCK = false; - } - - return true; - } - #else - case WR_AIM: - { - // aim and decide to fire if appropriate - self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, 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 = WEP_CVAR(devastator, edgedamage); - coredamage = WEP_CVAR(devastator, damage); - edgeradius = WEP_CVAR(devastator, 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; - } - - return true; - } - #endif - case WR_THINK: - { - if(WEP_CVAR(devastator, reload_ammo) && self.clip_load < WEP_CVAR(devastator, ammo)) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else - { - if(self.BUTTON_ATCK) - { - if(self.rl_release || WEP_CVAR(devastator, guidestop)) - if(weapon_prepareattack(0, WEP_CVAR(devastator, refire))) - { - W_Devastator_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready); - self.rl_release = 0; - } - } - else - self.rl_release = 1; - - if(self.BUTTON_ATCK2) - if(self.switchweapon == WEP_DEVASTATOR.m_id) - { - 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, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM); - } - } - - return true; - } - case WR_INIT: - { - //if(autocvar_sv_precacheweapons) - //{ - precache_model("models/flash.md3"); - precache_model(W_Model("g_rl.md3")); - precache_model(W_Model("v_rl.md3")); - precache_model(W_Model("h_rl.iqm")); - precache_sound(W_Sound("rocket_det")); - precache_sound(W_Sound("rocket_fire")); - precache_sound(W_Sound("rocket_mode")); - //} - DEVASTATOR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.rl_release = 1; - return true; - } - case WR_CHECKAMMO1: - { - #if 0 - // don't switch while guiding a missile - if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_DEVASTATOR.m_id) - { - ammo_amount = false; - if(WEP_CVAR(devastator, reload_ammo)) - { - if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo)) - ammo_amount = true; - } - else if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo)) - ammo_amount = true; - return !ammo_amount; - } - #endif - #if 0 - if(self.rl_release == 0) - { - LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo)); - return true; - } - else - { - ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo); - ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo); - LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE")); - return ammo_amount; - } - #else - ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo); - ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo); - return ammo_amount; - #endif - } - case WR_CHECKAMMO2: - { - return false; - } - case WR_CONFIG: - { - DEVASTATOR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.rl_release = 0; - return true; - } - case WR_RELOAD: - { - W_Reload(WEP_CVAR(devastator, ammo), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_DEVASTATOR_SUICIDE; - } - case WR_KILLMESSAGE: - { - if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) - return WEAPON_DEVASTATOR_MURDER_SPLASH; - else - return WEAPON_DEVASTATOR_MURDER_DIRECT; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Devastator(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("rocket_impact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_electro.qc b/qcsrc/common/weapons/w_electro.qc deleted file mode 100644 index 0f60fd29d3..0000000000 --- a/qcsrc/common/weapons/w_electro.qc +++ /dev/null @@ -1,621 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ ELECTRO, -/* function */ W_Electro, -/* ammotype */ ammo_cells, -/* impulse */ 5, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '0 0.5 1', -/* modelname */ "electro", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairelectro 0.6", -/* wepimg */ "weaponelectro", -/* refname */ "electro", -/* wepname */ _("Electro") -); - -#define ELECTRO_SETTINGS(w_cvar,w_prop) ELECTRO_SETTINGS_LIST(w_cvar, w_prop, ELECTRO, electro) -#define ELECTRO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, ammo) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, edgedamage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, radius) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, BOTH, spread) \ - w_cvar(id, sn, BOTH, lifetime) \ - w_cvar(id, sn, PRI, comboradius) \ - w_cvar(id, sn, PRI, midaircombo_explode) \ - w_cvar(id, sn, PRI, midaircombo_interval) \ - w_cvar(id, sn, PRI, midaircombo_radius) \ - w_cvar(id, sn, SEC, bouncefactor) \ - w_cvar(id, sn, SEC, bouncestop) \ - w_cvar(id, sn, SEC, count) \ - w_cvar(id, sn, SEC, damageforcescale) \ - w_cvar(id, sn, SEC, damagedbycontents) \ - w_cvar(id, sn, SEC, health) \ - w_cvar(id, sn, SEC, refire2) \ - w_cvar(id, sn, SEC, speed_up) \ - w_cvar(id, sn, SEC, speed_z) \ - w_cvar(id, sn, SEC, touchexplode) \ - w_cvar(id, sn, NONE, combo_comboradius) \ - w_cvar(id, sn, NONE, combo_comboradius_thruwall) \ - w_cvar(id, sn, NONE, combo_damage) \ - w_cvar(id, sn, NONE, combo_edgedamage) \ - w_cvar(id, sn, NONE, combo_force) \ - w_cvar(id, sn, NONE, combo_radius) \ - w_cvar(id, sn, NONE, combo_speed) \ - w_cvar(id, sn, NONE, combo_safeammocheck) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -ELECTRO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float electro_count; -.float electro_secondarytime; -void W_Electro_ExplodeCombo(void); -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_electro(void) { weapon_defaultspawnfunc(WEP_ELECTRO.m_id); } - -void W_Electro_TriggerCombo(vector org, float rad, entity own) -{ - entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(electro, combo_comboradius_thruwall)); - while(e) - { - if(e.classname == "electro_orb") - { - // do we allow thruwall triggering? - if(WEP_CVAR(electro, combo_comboradius_thruwall)) - { - // if distance is greater than thruwall distance, check to make sure it's not through a wall - if(vlen(e.WarpZone_findradius_dist) > WEP_CVAR(electro, combo_comboradius_thruwall)) - { - WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e); - if(trace_fraction != 1) - { - // trigger is through a wall and outside of thruwall range, abort - e = e.chain; - continue; - } - } - } - - // change owner to whoever caused the combo explosion - e.realowner = own; - e.takedamage = DAMAGE_NO; - e.classname = "electro_orb_chain"; - - // now set the next one to trigger as well - e.think = W_Electro_ExplodeCombo; - - // delay combo chains, looks cooler - e.nextthink = - ( - time - + - (WEP_CVAR(electro, combo_speed) ? - (vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed)) - : - 0 - ) - ); - } - e = e.chain; - } -} - -void W_Electro_ExplodeCombo(void) -{ - W_Electro_TriggerCombo(self.origin, WEP_CVAR(electro, combo_comboradius), self.realowner); - - self.event_damage = func_null; - - RadiusDamage( - self, - self.realowner, - WEP_CVAR(electro, combo_damage), - WEP_CVAR(electro, combo_edgedamage), - WEP_CVAR(electro, combo_radius), - world, - world, - WEP_CVAR(electro, combo_force), - WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce - world - ); - - remove(self); -} - -void W_Electro_Explode(void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(DIFF_TEAM(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, - WEP_CVAR_SEC(electro, damage), - WEP_CVAR_SEC(electro, edgedamage), - WEP_CVAR_SEC(electro, radius), - world, - world, - WEP_CVAR_SEC(electro, force), - self.projectiledeathtype, - other - ); - } - else - { - W_Electro_TriggerCombo(self.origin, WEP_CVAR_PRI(electro, comboradius), self.realowner); - RadiusDamage( - self, - self.realowner, - WEP_CVAR_PRI(electro, damage), - WEP_CVAR_PRI(electro, edgedamage), - WEP_CVAR_PRI(electro, radius), - world, - world, - WEP_CVAR_PRI(electro, force), - self.projectiledeathtype, - other - ); - } - - remove(self); -} - -void W_Electro_TouchExplode(void) -{ - PROJECTILE_TOUCH; - W_Electro_Explode(); -} - -void W_Electro_Bolt_Think(void) -{ - if(time >= self.ltime) - { - self.use(); - return; - } - - if(WEP_CVAR_PRI(electro, midaircombo_radius)) - { - float found = 0; - entity e = WarpZone_FindRadius(self.origin, WEP_CVAR_PRI(electro, midaircombo_radius), true); - - // loop through nearby orbs and trigger them - while(e) - { - if(e.classname == "electro_orb") - { - // change owner to whoever caused the combo explosion - e.realowner = self.realowner; - e.takedamage = DAMAGE_NO; - e.classname = "electro_orb_chain"; - - // now set the next one to trigger as well - e.think = W_Electro_ExplodeCombo; - - // delay combo chains, looks cooler - e.nextthink = - ( - time - + - (WEP_CVAR(electro, combo_speed) ? - (vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed)) - : - 0 - ) - ); - - ++found; - } - e = e.chain; - } - - // if we triggered an orb, should we explode? if not, lets try again next time - if(found && WEP_CVAR_PRI(electro, midaircombo_explode)) - { self.use(); } - else - { self.nextthink = min(time + WEP_CVAR_PRI(electro, midaircombo_interval), self.ltime); } - } - else { self.nextthink = self.ltime; } -} - -void W_Electro_Attack_Bolt(void) -{ - entity proj; - - W_DecreaseAmmo(WEP_CVAR_PRI(electro, ammo)); - - W_SetupShot_ProjectileSize( - self, - '0 0 -3', - '0 0 -3', - false, - 2, - W_Sound("electro_fire"), - CH_WEAPON_A, - WEP_CVAR_PRI(electro, damage) - ); - - Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - proj = spawn(); - proj.classname = "electro_bolt"; - proj.owner = proj.realowner = self; - proj.bot_dodge = true; - proj.bot_dodgerating = WEP_CVAR_PRI(electro, damage); - proj.use = W_Electro_Explode; - proj.think = W_Electro_Bolt_Think; - proj.nextthink = time; - proj.ltime = time + WEP_CVAR_PRI(electro, lifetime); - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_ELECTRO.m_id; - setorigin(proj, w_shotorg); - - proj.movetype = MOVETYPE_FLY; - W_SetupProjVelocity_PRI(proj, electro); - proj.angles = vectoangles(proj.velocity); - proj.touch = W_Electro_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); - - MUTATOR_CALLHOOK(EditProjectile, self, proj); -} - -void W_Electro_Orb_Touch(void) -{ - PROJECTILE_TOUCH; - if(other.takedamage == DAMAGE_AIM) - { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(); } } - else - { - //UpdateCSQCProjectile(self); - spamsound(self, CH_SHOTS, W_Sound("electro_bounce"), VOL_BASE, ATTEN_NORM); - self.projectiledeathtype |= HITTYPE_BOUNCE; - } -} - -void W_Electro_Orb_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - if(self.health <= 0) - return; - - // note: combos are usually triggered by W_Electro_TriggerCombo, not damage - float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt"); - - 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 = "electro_orb_chain"; - self.think = W_Electro_ExplodeCombo; - self.nextthink = time + - ( - // bound the length, inflictor may be in a galaxy far far away (warpzones) - min( - WEP_CVAR(electro, combo_radius), - vlen(self.origin - inflictor.origin) - ) - / - // delay combo chains, looks cooler - WEP_CVAR(electro, combo_speed) - ); - } - else - { - self.use = W_Electro_Explode; - self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately" - } - } -} - -void W_Electro_Attack_Orb(void) -{ - W_DecreaseAmmo(WEP_CVAR_SEC(electro, ammo)); - - W_SetupShot_ProjectileSize( - self, - '0 0 -4', - '0 0 -4', - false, - 2, - W_Sound("electro_fire2"), - CH_WEAPON_A, - WEP_CVAR_SEC(electro, damage) - ); - - w_shotdir = v_forward; // no TrueAim for grenades please - - Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - entity proj = spawn(); - proj.classname = "electro_orb"; - proj.owner = proj.realowner = self; - proj.use = W_Electro_Explode; - proj.think = adaptor_think2use_hittype_splash; - proj.bot_dodge = true; - proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage); - proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime); - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_SECONDARY; - setorigin(proj, w_shotorg); - - //proj.glow_size = 50; - //proj.glow_color = 45; - proj.movetype = MOVETYPE_BOUNCE; - W_SetupProjVelocity_UP_SEC(proj, electro); - proj.touch = W_Electro_Orb_Touch; - setsize(proj, '0 0 -4', '0 0 -4'); - proj.takedamage = DAMAGE_YES; - proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale); - proj.health = WEP_CVAR_SEC(electro, health); - proj.event_damage = W_Electro_Orb_Damage; - proj.flags = FL_PROJECTILE; - proj.damagedbycontents = (WEP_CVAR_SEC(electro, damagedbycontents)); - - proj.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor); - proj.bouncestop = WEP_CVAR_SEC(electro, 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 - - MUTATOR_CALLHOOK(EditProjectile, self, proj); -} - -void W_Electro_CheckAttack(void) -{ - if(self.electro_count > 1) - if(self.BUTTON_ATCK2) - if(weapon_prepareattack(1, -1)) - { - W_Electro_Attack_Orb(); - self.electro_count -= 1; - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack); - return; - } - // WEAPONTODO: when the player releases the button, cut down the length of refire2? - w_ready(); -} - -.float bot_secondary_electromooth; -bool W_Electro(int req) -{ - float ammo_amount; - switch(req) - { - case WR_AIM: - { - self.BUTTON_ATCK = 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(WEP_CVAR_PRI(electro, speed)) - shoot = bot_aim(WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, 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(WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true)) - { - self.BUTTON_ATCK2 = true; - if(random() < 0.03) self.bot_secondary_electromooth = 0; - } - } - - return true; - } - case WR_THINK: - { - if(autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO - { - ammo_amount = 0; - if(self.clip_load >= WEP_CVAR_PRI(electro, ammo)) - ammo_amount = 1; - if(self.clip_load >= WEP_CVAR_SEC(electro, ammo)) - ammo_amount += 1; - - if(!ammo_amount) - { - WEP_ACTION(self.weapon, WR_RELOAD); - return false; - } - - return true; - } - - if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR_PRI(electro, refire))) - { - W_Electro_Attack_Bolt(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready); - } - } - else if(self.BUTTON_ATCK2) - { - if(time >= self.electro_secondarytime) - if(weapon_prepareattack(1, WEP_CVAR_SEC(electro, refire))) - { - W_Electro_Attack_Orb(); - self.electro_count = WEP_CVAR_SEC(electro, count); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack); - self.electro_secondarytime = time + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor(); - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_electro.md3")); - precache_model(W_Model("v_electro.md3")); - precache_model(W_Model("h_electro.iqm")); - precache_sound(W_Sound("electro_bounce")); - precache_sound(W_Sound("electro_fire")); - precache_sound(W_Sound("electro_fire2")); - precache_sound(W_Sound("electro_impact")); - precache_sound(W_Sound("electro_impact_combo")); - ELECTRO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_PRI(electro, ammo); - ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_PRI(electro, ammo); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false. - { - ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo); - ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo); - } - else - { - ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo); - ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo); - } - return ammo_amount; - } - case WR_CONFIG: - { - ELECTRO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.electro_secondarytime = time; - return true; - } - case WR_RELOAD: - { - W_Reload(min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_ELECTRO_SUICIDE_ORBS; - else - return WEAPON_ELECTRO_SUICIDE_BOLT; - } - case 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 false; -} -#endif -#ifdef CSQC -bool W_Electro(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - if(w_deathtype & HITTYPE_SECONDARY) - { - pointparticles(particleeffectnum(EFFECT_ELECTRO_BALLEXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_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(EFFECT_ELECTRO_COMBO), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("electro_impact_combo"), VOL_BASE, ATTEN_NORM); - } - else - { - pointparticles(particleeffectnum(EFFECT_ELECTRO_IMPACT), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_NORM); - } - } - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("electro_impact")); - precache_sound(W_Sound("electro_impact_combo")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_fireball.qc b/qcsrc/common/weapons/w_fireball.qc deleted file mode 100644 index 4f8f3787c3..0000000000 --- a/qcsrc/common/weapons/w_fireball.qc +++ /dev/null @@ -1,486 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ FIREBALL, -/* function */ W_Fireball, -/* ammotype */ ammo_none, -/* impulse */ 9, -/* flags */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '1 0.5 0', -/* modelname */ "fireball", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairfireball", -/* wepimg */ "weaponfireball", -/* refname */ "fireball", -/* wepname */ _("Fireball") -); - -#define FIREBALL_SETTINGS(w_cvar,w_prop) FIREBALL_SETTINGS_LIST(w_cvar, w_prop, FIREBALL, fireball) -#define FIREBALL_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, damageforcescale) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, BOTH, spread) \ - w_cvar(id, sn, BOTH, lifetime) \ - w_cvar(id, sn, BOTH, laserburntime) \ - w_cvar(id, sn, BOTH, laserdamage) \ - w_cvar(id, sn, BOTH, laseredgedamage) \ - w_cvar(id, sn, BOTH, laserradius) \ - w_cvar(id, sn, PRI, edgedamage) \ - w_cvar(id, sn, PRI, force) \ - w_cvar(id, sn, PRI, radius) \ - w_cvar(id, sn, PRI, health) \ - w_cvar(id, sn, PRI, refire2) \ - w_cvar(id, sn, PRI, bfgdamage) \ - w_cvar(id, sn, PRI, bfgforce) \ - w_cvar(id, sn, PRI, bfgradius) \ - w_cvar(id, sn, SEC, damagetime) \ - w_cvar(id, sn, SEC, speed_up) \ - w_cvar(id, sn, SEC, speed_z) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -FIREBALL_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float bot_primary_fireballmooth; // whatever a mooth is -.vector fireball_impactvec; -.float fireball_primarytime; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_fireball(void) { weapon_defaultspawnfunc(WEP_FIREBALL.m_id); } - -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, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), world, world, WEP_CVAR_PRI(fireball, 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, WEP_CVAR_PRI(fireball, 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, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain) - if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(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 / WEP_CVAR_PRI(fireball, 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.m_id, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points); - - Damage(e, self, self.realowner, WEP_CVAR_PRI(fireball, bfgdamage) * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, WEP_CVAR_PRI(fireball, bfgforce) * dir); - Send_Effect(EFFECT_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 || DIFF_TEAM(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(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec); - Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); - } -} - -void W_Fireball_Think(void) -{ - if(time > self.pushltime) - { - self.cnt = 1; - self.projectiledeathtype |= HITTYPE_SPLASH; - W_Fireball_Explode(); - return; - } - - W_Fireball_LaserPlay(0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime)); - - self.nextthink = time + 0.1; -} - -void W_Fireball_Damage(entity inflictor, entity attacker, float damage, int 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(void) -{ - entity proj; - - W_SetupShot_ProjectileSize(self, '-16 -16 -16', '16 16 16', false, 2, W_Sound("fireball_fire2"), CH_WEAPON_A, WEP_CVAR_PRI(fireball, damage) + WEP_CVAR_PRI(fireball, bfgdamage)); - - Send_Effect(EFFECT_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 = WEP_CVAR_PRI(fireball, damage); - proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime); - proj.use = W_Fireball_Explode; - proj.think = W_Fireball_Think; - proj.nextthink = time; - proj.health = WEP_CVAR_PRI(fireball, health); - proj.team = self.team; - proj.event_damage = W_Fireball_Damage; - proj.takedamage = DAMAGE_YES; - proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale); - PROJECTILE_MAKETRIGGER(proj); - proj.projectiledeathtype = WEP_FIREBALL.m_id; - setorigin(proj, w_shotorg); - - proj.movetype = MOVETYPE_FLY; - W_SetupProjVelocity_PRI(proj, fireball); - 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); - - MUTATOR_CALLHOOK(EditProjectile, self, proj); -} - -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; - Send_Effect(EFFECT_FIREBALL_PRE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); -} - -void W_Fireball_Attack1_Frame4(void) -{ - W_Fireball_Attack1(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready); -} - -void W_Fireball_Attack1_Frame3(void) -{ - W_Fireball_AttackEffect(0, '+1.25 +3.75 0'); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4); -} - -void W_Fireball_Attack1_Frame2(void) -{ - W_Fireball_AttackEffect(0, '-1.25 +3.75 0'); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3); -} - -void W_Fireball_Attack1_Frame1(void) -{ - W_Fireball_AttackEffect(1, '+1.25 -3.75 0'); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2); -} - -void W_Fireball_Attack1_Frame0(void) -{ - W_Fireball_AttackEffect(0, '-1.25 -3.75 0'); - sound(self, CH_WEAPON_SINGLE, W_Sound("fireball_prefire2"), VOL_BASE, ATTEN_NORM); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1); -} - -void W_Fireball_Firemine_Think(void) -{ - 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) > WEP_CVAR_SEC(fireball, laserradius)) - { - self.cnt += 1; - if(self.cnt == 3) - self.owner = world; - } - else - self.cnt = 0; - } - - W_Fireball_LaserPlay(0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime)); - - self.nextthink = time + 0.1; -} - -void W_Fireball_Firemine_Touch(void) -{ - PROJECTILE_TOUCH; - if(other.takedamage == DAMAGE_AIM) - if(Fire_AddDamage(other, self.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), self.projectiledeathtype) >= 0) - { - remove(self); - return; - } - self.projectiledeathtype |= HITTYPE_BOUNCE; -} - -void W_Fireball_Attack2(void) -{ - entity proj; - vector f_diff; - float c; - - c = 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, W_Sound("fireball_fire"), CH_WEAPON_A, WEP_CVAR_SEC(fireball, damage)); - traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self); - w_shotorg = trace_endpos; - - Send_Effect(EFFECT_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 = WEP_CVAR_SEC(fireball, damage); - proj.movetype = MOVETYPE_BOUNCE; - proj.projectiledeathtype = WEP_FIREBALL.m_id | HITTYPE_SECONDARY; - proj.touch = W_Fireball_Firemine_Touch; - PROJECTILE_MAKETRIGGER(proj); - setsize(proj, '-4 -4 -4', '4 4 4'); - setorigin(proj, w_shotorg); - proj.think = W_Fireball_Firemine_Think; - proj.nextthink = time; - proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale); - proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime); - W_SetupProjVelocity_UP_SEC(proj, fireball); - - proj.angles = vectoangles(proj.velocity); - proj.flags = FL_PROJECTILE; - proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; - - CSQCProjectile(proj, true, PROJECTILE_FIREMINE, true); - - MUTATOR_CALLHOOK(EditProjectile, self, proj); -} - -bool W_Fireball(int req) -{ - switch(req) - { - case WR_AIM: - { - self.BUTTON_ATCK = false; - self.BUTTON_ATCK2 = false; - if(self.bot_primary_fireballmooth == 0) - { - if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false)) - { - self.BUTTON_ATCK = true; - if(random() < 0.02) self.bot_primary_fireballmooth = 0; - } - } - else - { - if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true)) - { - self.BUTTON_ATCK2 = true; - if(random() < 0.01) self.bot_primary_fireballmooth = 1; - } - } - - return true; - } - case WR_THINK: - { - if(self.BUTTON_ATCK) - { - if(time >= self.fireball_primarytime) - if(weapon_prepareattack(0, WEP_CVAR_PRI(fireball, refire))) - { - W_Fireball_Attack1_Frame0(); - self.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor(); - } - } - else if(self.BUTTON_ATCK2) - { - if(weapon_prepareattack(1, WEP_CVAR_SEC(fireball, refire))) - { - W_Fireball_Attack2(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready); - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_fireball.md3")); - precache_model(W_Model("v_fireball.md3")); - precache_model(W_Model("h_fireball.iqm")); - precache_model("models/sphere/sphere.md3"); - precache_sound(W_Sound("fireball_fire")); - precache_sound(W_Sound("fireball_fire2")); - precache_sound(W_Sound("fireball_prefire2")); - FIREBALL_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.ammo_field = ammo_none; - return true; - } - case WR_CHECKAMMO1: - case WR_CHECKAMMO2: - { - return true; // fireball has infinite ammo - } - case WR_CONFIG: - { - FIREBALL_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.fireball_primarytime = time; - return true; - } - case WR_SUICIDEMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_FIREBALL_SUICIDE_FIREMINE; - else - return WEAPON_FIREBALL_SUICIDE_BLAST; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_FIREBALL_MURDER_FIREMINE; - else - return WEAPON_FIREBALL_MURDER_BLAST; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Fireball(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - if(w_deathtype & HITTYPE_SECONDARY) - { - // firemine goes out silently - } - else - { - org2 = w_org + w_backoff * 16; - pointparticles(particleeffectnum(EFFECT_FIREBALL_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("fireball_impact2"), VOL_BASE, ATTEN_NORM * 0.25); // long range boom - } - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("fireball_impact2")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_hagar.qc b/qcsrc/common/weapons/w_hagar.qc deleted file mode 100644 index 4f1b905847..0000000000 --- a/qcsrc/common/weapons/w_hagar.qc +++ /dev/null @@ -1,565 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ HAGAR, -/* function */ W_Hagar, -/* ammotype */ ammo_rockets, -/* impulse */ 8, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '1 1 0.5', -/* modelname */ "hagar", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairhagar 0.8", -/* wepimg */ "weaponhagar", -/* refname */ "hagar", -/* wepname */ _("Hagar") -); - -#define HAGAR_SETTINGS(w_cvar,w_prop) HAGAR_SETTINGS_LIST(w_cvar, w_prop, HAGAR, hagar) -#define HAGAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, ammo) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, edgedamage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, radius) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, BOTH, spread) \ - w_cvar(id, sn, BOTH, damageforcescale) \ - w_cvar(id, sn, BOTH, health) \ - w_cvar(id, sn, PRI, lifetime) \ - w_cvar(id, sn, SEC, load) \ - w_cvar(id, sn, SEC, load_max) \ - w_cvar(id, sn, SEC, load_abort) \ - w_cvar(id, sn, SEC, load_animtime) \ - w_cvar(id, sn, SEC, load_hold) \ - w_cvar(id, sn, SEC, load_speed) \ - w_cvar(id, sn, SEC, load_releasedeath) \ - w_cvar(id, sn, SEC, load_spread) \ - w_cvar(id, sn, SEC, load_spread_bias) \ - w_cvar(id, sn, SEC, load_linkexplode) \ - w_cvar(id, sn, SEC, lifetime_min) \ - w_cvar(id, sn, SEC, lifetime_rand) \ - w_cvar(id, sn, NONE, secondary) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -HAGAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_hagar(void) { weapon_defaultspawnfunc(WEP_HAGAR.m_id); } - -// NO bounce protection, as bounces are limited! - -void W_Hagar_Explode(void) -{ - self.event_damage = func_null; - RadiusDamage(self, self.realowner, WEP_CVAR_PRI(hagar, damage), WEP_CVAR_PRI(hagar, edgedamage), WEP_CVAR_PRI(hagar, radius), world, world, WEP_CVAR_PRI(hagar, force), self.projectiledeathtype, other); - - remove(self); -} - -void W_Hagar_Explode2(void) -{ - self.event_damage = func_null; - RadiusDamage(self, self.realowner, WEP_CVAR_SEC(hagar, damage), WEP_CVAR_SEC(hagar, edgedamage), WEP_CVAR_SEC(hagar, radius), world, world, WEP_CVAR_SEC(hagar, force), self.projectiledeathtype, other); - - remove(self); -} - -void W_Hagar_Damage(entity inflictor, entity attacker, float damage, int 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 && WEP_CVAR_SEC(hagar, 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++; - Send_Effect(EFFECT_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(WEP_CVAR_PRI(hagar, ammo)); - - W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage)); - - Send_Effect(EFFECT_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 = WEP_CVAR_PRI(hagar, damage); - - missile.takedamage = DAMAGE_YES; - missile.health = WEP_CVAR_PRI(hagar, health); - missile.damageforcescale = WEP_CVAR_PRI(hagar, 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 + WEP_CVAR_PRI(hagar, lifetime); - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_HAGAR.m_id; - setorigin(missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - missile.movetype = MOVETYPE_FLY; - W_SetupProjVelocity_PRI(missile, hagar); - - missile.angles = vectoangles(missile.velocity); - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - CSQCProjectile(missile, true, PROJECTILE_HAGAR, true); - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -void W_Hagar_Attack2(void) -{ - entity missile; - - W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo)); - - W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); - - Send_Effect(EFFECT_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 = WEP_CVAR_SEC(hagar, damage); - - missile.takedamage = DAMAGE_YES; - missile.health = WEP_CVAR_SEC(hagar, health); - missile.damageforcescale = WEP_CVAR_SEC(hagar, 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 + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand); - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY; - setorigin(missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - missile.movetype = MOVETYPE_BOUNCEMISSILE; - W_SetupProjVelocity_SEC(missile, hagar); - - missile.angles = vectoangles(missile.velocity); - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - CSQCProjectile(missile, true, PROJECTILE_HAGAR_BOUNCING, true); - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -.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, WEP_CVAR_SEC(hagar, refire)); - - W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); - Send_Effect(EFFECT_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 = WEP_CVAR_SEC(hagar, damage); - - missile.takedamage = DAMAGE_YES; - missile.health = WEP_CVAR_SEC(hagar, health); - missile.damageforcescale = WEP_CVAR_SEC(hagar, 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 + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand); - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_HAGAR.m_id | 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) / (WEP_CVAR_SEC(hagar, load_max) - 1)); - spread_pershot = (1 - (spread_pershot * WEP_CVAR_SEC(hagar, load_spread_bias))); - spread_pershot = (WEP_CVAR_SEC(hagar, 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 * WEP_CVAR_SEC(hagar, load_spread) * g_weaponspreadfactor; - - W_SetupProjVelocity_Explicit(missile, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_SEC(hagar, speed), 0, 0, spread_pershot, false); - - missile.angles = vectoangles(missile.velocity); - missile.flags = FL_PROJECTILE; - - CSQCProjectile(missile, true, PROJECTILE_HAGAR, true); - - MUTATOR_CALLHOOK(EditProjectile, self, missile); - } - - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, load_animtime), w_ready); - self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, 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; - - bool loaded = self.hagar_load >= WEP_CVAR_SEC(hagar, load_max); - - // this is different than WR_CHECKAMMO when it comes to reloading - bool enough_ammo; - if(self.items & IT_UNLIMITED_WEAPON_AMMO) - enough_ammo = true; - else if(autocvar_g_balance_hagar_reload_ammo) - enough_ammo = self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo); - else - enough_ammo = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo); - - bool stopped = loaded || !enough_ammo; - - if(self.BUTTON_ATCK2) - { - if(self.BUTTON_ATCK && WEP_CVAR_SEC(hagar, 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(WEP_CVAR_SEC(hagar, ammo) * self.hagar_load * -1); // give back ammo - self.hagar_load = 0; - sound(self, CH_WEAPON_A, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM); - - // pause until we can load rockets again, once we re-press the alt fire button - self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, 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(!stopped) - { - if(!self.hagar_loadblock && self.hagar_loadstep < time) - { - W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo)); - self.weaponentity.state = WS_INUSE; - self.hagar_load += 1; - sound(self, CH_WEAPON_B, W_Sound("hagar_load"), VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most - - if(self.hagar_load >= WEP_CVAR_SEC(hagar, load_max)) - stopped = true; - else - self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_speed) * W_WeaponRateFactor(); - } - } - if(stopped && !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, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM); - self.hagar_loadbeep = true; - self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_hold) * W_WeaponRateFactor(); - } - } - } - 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(stopped && self.hagar_loadstep - 0.5 < time && WEP_CVAR_SEC(hagar, load_hold) >= 0) - { - if(!self.hagar_warning) // 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, W_Sound("hagar_beep"), 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 || (stopped && self.hagar_loadstep < time && WEP_CVAR_SEC(hagar, 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(!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))) - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - // note: this doesn't force the switch - W_SwitchToOtherWeapon(self); - return; - } - } -} - -bool W_Hagar(int req) -{ - float ammo_amount; - switch(req) - { - case WR_AIM: - { - if(random()>0.15) - self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, 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(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false); - - return true; - } - case WR_THINK: - { - float loadable_secondary; - loadable_secondary = (WEP_CVAR_SEC(hagar, load) && WEP_CVAR(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(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo))) // forced reload - WEP_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, WEP_CVAR_PRI(hagar, refire))) - { - W_Hagar_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hagar, refire), w_ready); - } - } - else if(self.BUTTON_ATCK2 && !loadable_secondary && WEP_CVAR(hagar, secondary)) - { - if(weapon_prepareattack(1, WEP_CVAR_SEC(hagar, refire))) - { - W_Hagar_Attack2(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, refire), w_ready); - } - } - return true; - } - case 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(); - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_hagar.md3")); - precache_model(W_Model("v_hagar.md3")); - precache_model(W_Model("h_hagar.iqm")); - precache_sound(W_Sound("hagar_fire")); - precache_sound(W_Sound("hagar_load")); - precache_sound(W_Sound("hagar_beep")); - HAGAR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.hagar_loadblock = false; - - if(self.hagar_load) - { - W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo) * self.hagar_load * -1); // give back ammo if necessary - self.hagar_load = 0; - } - - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_PRI(hagar, ammo); - ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_PRI(hagar, ammo); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo); - ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo); - return ammo_amount; - } - case WR_CONFIG: - { - HAGAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.hagar_load = 0; - return true; - } - case WR_PLAYERDEATH: - { - // if we have any rockets loaded when we die, release them - if(self.hagar_load && WEP_CVAR_SEC(hagar, load_releasedeath)) - W_Hagar_Attack2_Load_Release(); - - return true; - } - case WR_RELOAD: - { - if(!self.hagar_load) // require releasing loaded rockets first - W_Reload(min(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo)), W_Sound("reload")); - - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_HAGAR_SUICIDE; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_HAGAR_MURDER_BURST; - else - return WEAPON_HAGAR_MURDER_SPRAY; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Hagar(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - { - if(w_random<0.15) - sound(self, CH_SHOTS, W_Sound("hagexp1"), VOL_BASE, ATTN_NORM); - else if(w_random<0.7) - sound(self, CH_SHOTS, W_Sound("hagexp2"), VOL_BASE, ATTN_NORM); - else - sound(self, CH_SHOTS, W_Sound("hagexp3"), VOL_BASE, ATTN_NORM); - } - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("hagexp1")); - precache_sound(W_Sound("hagexp2")); - precache_sound(W_Sound("hagexp3")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_hlac.qc b/qcsrc/common/weapons/w_hlac.qc deleted file mode 100644 index 49685296a8..0000000000 --- a/qcsrc/common/weapons/w_hlac.qc +++ /dev/null @@ -1,314 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ HLAC, -/* function */ W_HLAC, -/* ammotype */ ammo_cells, -/* impulse */ 6, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '0 1 0', -/* modelname */ "hlac", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairhlac 0.6", -/* wepimg */ "weaponhlac", -/* refname */ "hlac", -/* wepname */ _("Heavy Laser Assault Cannon") -); - -#define HLAC_SETTINGS(w_cvar,w_prop) HLAC_SETTINGS_LIST(w_cvar, w_prop, HLAC, hlac) -#define HLAC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, ammo) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, edgedamage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, lifetime) \ - w_cvar(id, sn, BOTH, radius) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, BOTH, spread_crouchmod) \ - w_cvar(id, sn, PRI, spread_add) \ - w_cvar(id, sn, PRI, spread_max) \ - w_cvar(id, sn, PRI, spread_min) \ - w_cvar(id, sn, NONE, secondary) \ - w_cvar(id, sn, SEC, shots) \ - w_cvar(id, sn, SEC, spread) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -HLAC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_hlac(void) { weapon_defaultspawnfunc(WEP_HLAC.m_id); } - -void W_HLAC_Touch(void) -{ - float isprimary; - - PROJECTILE_TOUCH; - - self.event_damage = func_null; - - isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY); - - RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(hlac, isprimary, damage), WEP_CVAR_BOTH(hlac, isprimary, edgedamage), WEP_CVAR_BOTH(hlac, isprimary, radius), world, world, WEP_CVAR_BOTH(hlac, isprimary, force), self.projectiledeathtype, other); - - remove(self); -} - -void W_HLAC_Attack(void) -{ - entity missile; - float spread; - - W_DecreaseAmmo(WEP_CVAR_PRI(hlac, ammo)); - - spread = WEP_CVAR_PRI(hlac, spread_min) + (WEP_CVAR_PRI(hlac, spread_add) * self.misc_bulletcounter); - spread = min(spread,WEP_CVAR_PRI(hlac, spread_max)); - if(self.crouch) - spread = spread * WEP_CVAR_PRI(hlac, spread_crouchmod); - - W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage)); - Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - if(!autocvar_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 = WEP_CVAR_PRI(hlac, damage); - - missile.movetype = MOVETYPE_FLY; - PROJECTILE_MAKETRIGGER(missile); - - setorigin(missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - W_SetupProjVelocity_Basic(missile, WEP_CVAR_PRI(hlac, speed), spread); - //missile.angles = vectoangles(missile.velocity); // csqc - - missile.touch = W_HLAC_Touch; - missile.think = SUB_Remove; - - missile.nextthink = time + WEP_CVAR_PRI(hlac, lifetime); - - missile.flags = FL_PROJECTILE; - missile.projectiledeathtype = WEP_HLAC.m_id; - - CSQCProjectile(missile, true, PROJECTILE_HLAC, true); - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -void W_HLAC_Attack2(void) -{ - entity missile; - float spread; - - spread = WEP_CVAR_SEC(hlac, spread); - - - if(self.crouch) - spread = spread * WEP_CVAR_SEC(hlac, spread_crouchmod); - - W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage)); - Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - missile = spawn(); - missile.owner = missile.realowner = self; - missile.classname = "hlacbolt"; - missile.bot_dodge = true; - - missile.bot_dodgerating = WEP_CVAR_SEC(hlac, damage); - - missile.movetype = MOVETYPE_FLY; - PROJECTILE_MAKETRIGGER(missile); - - setorigin(missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - W_SetupProjVelocity_Basic(missile, WEP_CVAR_SEC(hlac, speed), spread); - //missile.angles = vectoangles(missile.velocity); // csqc - - missile.touch = W_HLAC_Touch; - missile.think = SUB_Remove; - - missile.nextthink = time + WEP_CVAR_SEC(hlac, lifetime); - - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - missile.projectiledeathtype = WEP_HLAC.m_id | HITTYPE_SECONDARY; - - CSQCProjectile(missile, true, PROJECTILE_HLAC, true); - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -// weapon frames -void W_HLAC_Attack_Frame(void) -{ - if(self.weapon != self.switchweapon) // abort immediately if switching - { - w_ready(); - return; - } - - if(self.BUTTON_ATCK) - { - if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1)) - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - ATTACK_FINISHED(self) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor(); - W_HLAC_Attack(); - self.misc_bulletcounter = self.misc_bulletcounter + 1; - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame); - } - else - { - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, animtime), w_ready); - } -} - -void W_HLAC_Attack2_Frame(void) -{ - float i; - - W_DecreaseAmmo(WEP_CVAR_SEC(hlac, ammo)); - - for(i=WEP_CVAR_SEC(hlac, shots);i>0;--i) - W_HLAC_Attack2(); - - if(!autocvar_g_norecoil) - { - self.punchangle_x = random() - 0.5; - self.punchangle_y = random() - 0.5; - } -} - -bool W_HLAC(int req) -{ - float ammo_amount; - switch(req) - { - case WR_AIM: - { - self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hlac, speed), 0, WEP_CVAR_PRI(hlac, lifetime), false); - return true; - } - case WR_THINK: - { - if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR_PRI(hlac, refire))) - { - self.misc_bulletcounter = 0; - W_HLAC_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame); - } - } - - else if(self.BUTTON_ATCK2 && WEP_CVAR(hlac, secondary)) - { - if(weapon_prepareattack(1, WEP_CVAR_SEC(hlac, refire))) - { - W_HLAC_Attack2_Frame(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hlac, animtime), w_ready); - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_hlac.md3")); - precache_model(W_Model("v_hlac.md3")); - precache_model(W_Model("h_hlac.iqm")); - precache_sound(W_Sound("lasergun_fire")); - HLAC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_PRI(hlac, ammo); - ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_PRI(hlac, ammo); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_SEC(hlac, ammo); - ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_SEC(hlac, ammo); - return ammo_amount; - } - case WR_CONFIG: - { - HLAC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo)), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_HLAC_SUICIDE; - } - case WR_KILLMESSAGE: - { - return WEAPON_HLAC_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_HLAC(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("laserimpact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_hmg.qc b/qcsrc/common/weapons/w_hmg.qc deleted file mode 100644 index 52b60e83bb..0000000000 --- a/qcsrc/common/weapons/w_hmg.qc +++ /dev/null @@ -1,208 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ HMG, -/* function */ W_HeavyMachineGun, -/* ammotype */ ammo_nails, -/* impulse */ 3, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* color */ '0.5 0.5 0', -/* modelname */ "ok_hmg", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairuzi 0.6", -/* wepimg */ "weaponhmg", -/* refname */ "hmg", -/* wepname */ _("Heavy Machine Gun") -); - -#define HMG_SETTINGS(w_cvar,w_prop) HMG_SETTINGS_LIST(w_cvar, w_prop, HMG, hmg) -#define HMG_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, spread_min) \ - w_cvar(id, sn, NONE, spread_max) \ - w_cvar(id, sn, NONE, spread_add) \ - w_cvar(id, sn, NONE, solidpenetration) \ - w_cvar(id, sn, NONE, damage) \ - w_cvar(id, sn, NONE, force) \ - w_cvar(id, sn, NONE, refire) \ - w_cvar(id, sn, NONE, ammo) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -HMG_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC - -void spawnfunc_weapon_hmg() { weapon_defaultspawnfunc(WEP_HMG.m_id); } - -void W_HeavyMachineGun_Attack_Auto() -{ - if (!self.BUTTON_ATCK) - { - w_ready(); - return; - } - - if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1)) - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - W_DecreaseAmmo(WEP_CVAR(hmg, ammo)); - - W_SetupShot (self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(hmg, damage)); - - if(!autocvar_g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - float hmg_spread = bound(WEP_CVAR(hmg, spread_min), WEP_CVAR(hmg, spread_min) + (WEP_CVAR(hmg, spread_add) * self.misc_bulletcounter), WEP_CVAR(hmg, spread_max)); - fireBullet(w_shotorg, w_shotdir, hmg_spread, WEP_CVAR(hmg, solidpenetration), WEP_CVAR(hmg, damage), WEP_CVAR(hmg, force), WEP_HMG.m_id, 0); - - self.misc_bulletcounter = self.misc_bulletcounter + 1; - - Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - W_MachineGun_MuzzleFlash(); - 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 + WEP_CVAR(hmg, refire) * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(hmg, refire), W_HeavyMachineGun_Attack_Auto); -} - -bool W_HeavyMachineGun(int req) -{ - float ammo_amount; - switch(req) - { - case 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); - - return true; - } - case WR_THINK: - { - if(WEP_CVAR(hmg, reload_ammo) && self.clip_load < WEP_CVAR(hmg, ammo)) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else - { - if (self.BUTTON_ATCK) - if (weapon_prepareattack(0, 0)) - { - self.misc_bulletcounter = 0; - W_HeavyMachineGun_Attack_Auto(); - } - } - - return true; - } - case WR_INIT: - { - precache_model ("models/uziflash.md3"); - precache_model(W_Model("g_ok_hmg.md3")); - precache_model(W_Model("v_ok_hmg.md3")); - precache_model(W_Model("h_ok_hmg.iqm")); - precache_sound (W_Sound("uzi_fire")); - HMG_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo); - - if(autocvar_g_balance_hmg_reload_ammo) - ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo); - - return ammo_amount; - } - case WR_CHECKAMMO2: - { - ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo); - - if(autocvar_g_balance_hmg_reload_ammo) - ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo); - - return ammo_amount; - } - case WR_CONFIG: - { - HMG_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(WEP_CVAR(hmg, ammo), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_THINKING_WITH_PORTALS; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_HMG_MURDER_SNIPE; - else - return WEAPON_HMG_MURDER_SPRAY; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_HeavyMachineGun(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1); - if(!w_issilent) - if(w_random < 0.05) - sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM); - else if(w_random < 0.1) - sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM); - else if(w_random < 0.2) - sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("ric1")); - precache_sound(W_Sound("ric2")); - precache_sound(W_Sound("ric3")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_hook.qc b/qcsrc/common/weapons/w_hook.qc deleted file mode 100644 index 6c5519e60b..0000000000 --- a/qcsrc/common/weapons/w_hook.qc +++ /dev/null @@ -1,368 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ HOOK, -/* function */ W_Hook, -/* ammotype */ ammo_fuel, -/* impulse */ 0, -/* flags */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ 0, -/* color */ '0 0.5 0', -/* modelname */ "hookgun", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairhook 0.5", -/* wepimg */ "weaponhook", -/* refname */ "hook", -/* wepname */ _("Grappling Hook") -); - -#define HOOK_SETTINGS(w_cvar,w_prop) HOOK_SETTINGS_LIST(w_cvar, w_prop, HOOK, hook) -#define HOOK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, PRI, ammo) \ - w_cvar(id, sn, PRI, hooked_ammo) \ - w_cvar(id, sn, PRI, hooked_time_free) \ - w_cvar(id, sn, PRI, hooked_time_max) \ - w_cvar(id, sn, SEC, damage) \ - w_cvar(id, sn, SEC, duration) \ - w_cvar(id, sn, SEC, edgedamage) \ - w_cvar(id, sn, SEC, force) \ - w_cvar(id, sn, SEC, gravity) \ - w_cvar(id, sn, SEC, lifetime) \ - w_cvar(id, sn, SEC, power) \ - w_cvar(id, sn, SEC, radius) \ - w_cvar(id, sn, SEC, speed) \ - w_cvar(id, sn, SEC, health) \ - w_cvar(id, sn, SEC, damageforcescale) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -HOOK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) - -.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; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC - -void spawnfunc_weapon_hook(void) -{ - if(g_grappling_hook) // offhand hook - { - startitem_failed = true; - remove(self); - return; - } - weapon_defaultspawnfunc(WEP_HOOK.m_id); -} - -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 = WEP_CVAR_SEC(hook, damage); - self.dmg_edge = WEP_CVAR_SEC(hook, edgedamage); - self.dmg_radius = WEP_CVAR_SEC(hook, radius); - self.dmg_force = WEP_CVAR_SEC(hook, force); - self.dmg_power = WEP_CVAR_SEC(hook, power); - self.dmg_duration = WEP_CVAR_SEC(hook, duration); - self.teleport_time = time; - self.dmg_last = 1; - self.movetype = MOVETYPE_NONE; -} - -void W_Hook_Damage(entity inflictor, entity attacker, float damage, int 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(void) -{ - entity gren; - - //W_DecreaseAmmo(WEP_CVAR_SEC(hook, ammo)); // WEAPONTODO: Figure out how to handle ammo with hook secondary (gravitybomb) - W_SetupShot(self, false, 4, W_Sound("hookbomb_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hook, damage)); - - gren = spawn(); - gren.owner = gren.realowner = self; - gren.classname = "hookbomb"; - gren.bot_dodge = true; - gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage); - gren.movetype = MOVETYPE_TOSS; - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_HOOK.m_id | HITTYPE_SECONDARY; - setorigin(gren, w_shotorg); - setsize(gren, '0 0 0', '0 0 0'); - - gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime); - gren.think = adaptor_think2use_hittype_splash; - gren.use = W_Hook_Explode2; - gren.touch = W_Hook_Touch2; - - gren.takedamage = DAMAGE_YES; - gren.health = WEP_CVAR_SEC(hook, health); - gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale); - gren.event_damage = W_Hook_Damage; - gren.damagedbycontents = true; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - - gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed); - if(autocvar_g_projectiles_newton_style) - gren.velocity = gren.velocity + self.velocity; - - gren.gravity = WEP_CVAR_SEC(hook, gravity); - //W_SetupProjVelocity_Basic(gren); // just falling down! - - gren.angles = '0 0 0'; - gren.flags = FL_PROJECTILE; - - CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true); - - MUTATOR_CALLHOOK(EditProjectile, self, gren); -} - -bool W_Hook(int req) -{ - float hooked_time_max, hooked_fuel; - - switch(req) - { - case WR_AIM: - { - // no bot AI for hook (yet?) - return true; - } - case WR_THINK: - { - if(self.BUTTON_ATCK || self.BUTTON_HOOK) - { - if(!self.hook) - if(!(self.hook_state & HOOK_WAITING_FOR_RELEASE)) - if(!(self.hook_state & HOOK_FIRING)) - if(time > self.hook_refire) - if(weapon_prepareattack(0, -1)) - { - W_DecreaseAmmo(WEP_CVAR_PRI(hook, ammo)); - self.hook_state |= HOOK_FIRING; - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready); - } - } - - if(self.BUTTON_ATCK2) - { - if(weapon_prepareattack(1, WEP_CVAR_SEC(hook, refire))) - { - W_Hook_Attack2(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready); - } - } - - if(self.hook) - { - // if hooked, no bombs, and increase the timer - self.hook_refire = max(self.hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor()); - - // hook also inhibits health regeneration, but only for 1 second - if(!(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 = WEP_CVAR_PRI(hook, hooked_time_max); - if(hooked_time_max > 0) - { - if( time > self.hook_time_hooked + hooked_time_max ) - self.hook_state |= HOOK_REMOVING; - } - - hooked_fuel = WEP_CVAR_PRI(hook, hooked_ammo); - if(hooked_fuel > 0) - { - if( time > self.hook_time_fueldecrease ) - { - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - if( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel ) - { - W_DecreaseAmmo((time - self.hook_time_fueldecrease) * hooked_fuel); - 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 + WEP_CVAR_PRI(hook, hooked_time_free); - } - - if(self.BUTTON_CROUCH) - { - self.hook_state &= ~HOOK_PULLING; - if(self.BUTTON_ATCK || 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.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; - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_hookgun.md3")); - precache_model(W_Model("v_hookgun.md3")); - precache_model(W_Model("h_hookgun.iqm")); - precache_sound(W_Sound("hook_impact")); // done by g_hook.qc - precache_sound(W_Sound("hook_fire")); - precache_sound(W_Sound("hookbomb_fire")); - HOOK_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.hook_state &= ~HOOK_WAITING_FOR_RELEASE; - return true; - } - case WR_CHECKAMMO1: - { - if(self.hook) - return self.ammo_fuel > 0; - else - return self.ammo_fuel >= WEP_CVAR_PRI(hook, ammo); - } - case WR_CHECKAMMO2: - { - // infinite ammo for now - return true; // self.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above - } - case WR_CONFIG: - { - HOOK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.hook_refire = time; - return true; - } - case WR_SUICIDEMESSAGE: - { - return false; - } - case WR_KILLMESSAGE: - { - return WEAPON_HOOK_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Hook(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum(EFFECT_HOOK_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("hookbomb_impact"), VOL_BASE, ATTN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("hookbomb_impact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_machinegun.qc b/qcsrc/common/weapons/w_machinegun.qc deleted file mode 100644 index da1eb33a60..0000000000 --- a/qcsrc/common/weapons/w_machinegun.qc +++ /dev/null @@ -1,408 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ MACHINEGUN, -/* function */ W_MachineGun, -/* ammotype */ ammo_nails, -/* impulse */ 3, -/* flags */ WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '1 1 0', -/* modelname */ "uzi", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairuzi 0.6", -/* wepimg */ "weaponuzi", -/* refname */ "machinegun", -/* wepname */ _("Machine Gun") -); - -#define MACHINEGUN_SETTINGS(w_cvar,w_prop) MACHINEGUN_SETTINGS_LIST(w_cvar, w_prop, MACHINEGUN, machinegun) -#define MACHINEGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, spread_min) \ - w_cvar(id, sn, NONE, spread_max) \ - w_cvar(id, sn, NONE, spread_add) \ - w_cvar(id, sn, NONE, mode) \ - w_cvar(id, sn, NONE, first) \ - w_cvar(id, sn, NONE, first_damage) \ - w_cvar(id, sn, NONE, first_force) \ - w_cvar(id, sn, NONE, first_refire) \ - w_cvar(id, sn, NONE, first_spread) \ - w_cvar(id, sn, NONE, first_ammo) \ - w_cvar(id, sn, NONE, solidpenetration) \ - w_cvar(id, sn, NONE, sustained_damage) \ - w_cvar(id, sn, NONE, sustained_force) \ - w_cvar(id, sn, NONE, sustained_refire) \ - w_cvar(id, sn, NONE, sustained_spread) \ - w_cvar(id, sn, NONE, sustained_ammo) \ - w_cvar(id, sn, NONE, burst) \ - w_cvar(id, sn, NONE, burst_refire) \ - w_cvar(id, sn, NONE, burst_refire2) \ - w_cvar(id, sn, NONE, burst_animtime) \ - w_cvar(id, sn, NONE, burst_speed) \ - w_cvar(id, sn, NONE, burst_ammo) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -MACHINEGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC - -void spawnfunc_weapon_machinegun(void) -{ - if(autocvar_sv_q3acompat_machineshotgunswap) - if(self.classname != "droppedweapon") - { - weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id); - return; - } - weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id); -} -void spawnfunc_weapon_uzi(void) { spawnfunc_weapon_machinegun(); } - -void W_MachineGun_MuzzleFlash_Think(void) -{ - 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 W_MachineGun_MuzzleFlash(void) -{ - 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 = W_MachineGun_MuzzleFlash_Think; - 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_MachineGun_Attack(int deathtype) -{ - W_SetupShot(self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? WEP_CVAR(machinegun, first_damage) : WEP_CVAR(machinegun, sustained_damage))); - if(!autocvar_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 + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(); - - if(self.misc_bulletcounter == 1) - fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, 0); - else - fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, 0); - - Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - W_MachineGun_MuzzleFlash(); - 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(WEP_CVAR(machinegun, first_ammo)); - else - W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo)); -} - -// weapon frames -void W_MachineGun_Attack_Frame(void) -{ - if(self.weapon != self.switchweapon) // abort immediately if switching - { - w_ready(); - return; - } - if(self.BUTTON_ATCK) - { - if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - self.misc_bulletcounter = self.misc_bulletcounter + 1; - W_MachineGun_Attack(WEP_MACHINEGUN.m_id); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame); - } - else - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), w_ready); -} - - -void W_MachineGun_Attack_Auto(void) -{ - float machinegun_spread; - - if(!self.BUTTON_ATCK) - { - w_ready(); - return; - } - - if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1)) - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo)); - - W_SetupShot(self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); - if(!autocvar_g_norecoil) - { - self.punchangle_x = random() - 0.5; - self.punchangle_y = random() - 0.5; - } - - machinegun_spread = bound(WEP_CVAR(machinegun, spread_min), WEP_CVAR(machinegun, spread_min) + (WEP_CVAR(machinegun, spread_add) * self.misc_bulletcounter), WEP_CVAR(machinegun, spread_max)); - fireBullet(w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0); - - self.misc_bulletcounter = self.misc_bulletcounter + 1; - - Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - W_MachineGun_MuzzleFlash(); - 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 + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Auto); -} - -void W_MachineGun_Attack_Burst(void) -{ - W_SetupShot(self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); - if(!autocvar_g_norecoil) - { - self.punchangle_x = random() - 0.5; - self.punchangle_y = random() - 0.5; - } - - fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0); - - Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - W_MachineGun_MuzzleFlash(); - 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 + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_animtime), w_ready); - } - else - { - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_refire), W_MachineGun_Attack_Burst); - } - -} - -bool W_MachineGun(int req) -{ - float ammo_amount; - switch(req) - { - case 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); - - return true; - } - case WR_THINK: - { - if(WEP_CVAR(machinegun, reload_ammo) && self.clip_load < min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else if(WEP_CVAR(machinegun, mode) == 1) - { - if(self.BUTTON_ATCK) - if(weapon_prepareattack(0, 0)) - { - self.misc_bulletcounter = 0; - W_MachineGun_Attack_Auto(); - } - - if(self.BUTTON_ATCK2) - if(weapon_prepareattack(1, 0)) - { - if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return false; - } - - W_DecreaseAmmo(WEP_CVAR(machinegun, burst_ammo)); - - self.misc_bulletcounter = WEP_CVAR(machinegun, burst) * -1; - W_MachineGun_Attack_Burst(); - } - } - else - { - - if(self.BUTTON_ATCK) - if(weapon_prepareattack(0, 0)) - { - self.misc_bulletcounter = 1; - W_MachineGun_Attack(WEP_MACHINEGUN.m_id); // sets attack_finished - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame); - } - - if(self.BUTTON_ATCK2 && WEP_CVAR(machinegun, first)) - if(weapon_prepareattack(1, 0)) - { - self.misc_bulletcounter = 1; - W_MachineGun_Attack(WEP_MACHINEGUN.m_id | HITTYPE_SECONDARY); // sets attack_finished - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, first_refire), w_ready); - } - } - - return true; - } - case WR_INIT: - { - precache_model("models/uziflash.md3"); - precache_model(W_Model("g_uzi.md3")); - precache_model(W_Model("v_uzi.md3")); - precache_model(W_Model("h_uzi.iqm")); - precache_sound(W_Sound("uzi_fire")); - MACHINEGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - if(WEP_CVAR(machinegun, mode) == 1) - ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, sustained_ammo); - else - ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo); - - if(WEP_CVAR(machinegun, reload_ammo)) - { - if(WEP_CVAR(machinegun, mode) == 1) - ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, sustained_ammo); - else - ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo); - } - return ammo_amount; - } - case WR_CHECKAMMO2: - { - if(WEP_CVAR(machinegun, mode) == 1) - ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, burst_ammo); - else - ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo); - - if(WEP_CVAR(machinegun, reload_ammo)) - { - if(WEP_CVAR(machinegun, mode) == 1) - ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, burst_ammo); - else - ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo); - } - return ammo_amount; - } - case WR_CONFIG: - { - MACHINEGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo)), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_THINKING_WITH_PORTALS; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_MACHINEGUN_MURDER_SNIPE; - else - return WEAPON_MACHINEGUN_MURDER_SPRAY; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_MachineGun(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1); - if(!w_issilent) - if(w_random < 0.05) - sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM); - else if(w_random < 0.1) - sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM); - else if(w_random < 0.2) - sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("ric1")); - precache_sound(W_Sound("ric2")); - precache_sound(W_Sound("ric3")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_minelayer.qc b/qcsrc/common/weapons/w_minelayer.qc deleted file mode 100644 index 8048957bce..0000000000 --- a/qcsrc/common/weapons/w_minelayer.qc +++ /dev/null @@ -1,623 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ MINE_LAYER, -/* function */ W_MineLayer, -/* ammotype */ ammo_rockets, -/* impulse */ 4, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* color */ '0.75 1 0', -/* modelname */ "minelayer", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairminelayer 0.9", -/* wepimg */ "weaponminelayer", -/* refname */ "minelayer", -/* wepname */ _("Mine Layer") -); - -#define MINELAYER_SETTINGS(w_cvar,w_prop) MINELAYER_SETTINGS_LIST(w_cvar, w_prop, MINE_LAYER, minelayer) -#define MINELAYER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, ammo) \ - w_cvar(id, sn, NONE, animtime) \ - w_cvar(id, sn, NONE, damage) \ - w_cvar(id, sn, NONE, damageforcescale) \ - w_cvar(id, sn, NONE, detonatedelay) \ - w_cvar(id, sn, NONE, edgedamage) \ - w_cvar(id, sn, NONE, force) \ - w_cvar(id, sn, NONE, health) \ - w_cvar(id, sn, NONE, lifetime) \ - w_cvar(id, sn, NONE, lifetime_countdown) \ - w_cvar(id, sn, NONE, limit) \ - w_cvar(id, sn, NONE, protection) \ - w_cvar(id, sn, NONE, proximityradius) \ - w_cvar(id, sn, NONE, radius) \ - w_cvar(id, sn, NONE, refire) \ - w_cvar(id, sn, NONE, remote_damage) \ - w_cvar(id, sn, NONE, remote_edgedamage) \ - w_cvar(id, sn, NONE, remote_force) \ - w_cvar(id, sn, NONE, remote_radius) \ - w_cvar(id, sn, NONE, speed) \ - w_cvar(id, sn, NONE, time) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -MINELAYER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -void W_MineLayer_Think(void); -.float minelayer_detonate, mine_explodeanyway; -.float mine_time; -.vector mine_orientation; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER.m_id); } - -void W_MineLayer_Stick(entity to) -{ - spamsound(self, CH_SHOTS, W_Sound("mine_stick"), 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_MineLayer_Think; - newmine.nextthink = time; - newmine.cnt = self.cnt; - newmine.flags = self.flags; - - remove(self); - self = newmine; - - if(to) - SetMovetypeFollow(self, to); -} - -void W_MineLayer_Explode(void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(DIFF_TEAM(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, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), world, world, WEP_CVAR(minelayer, force), self.projectiledeathtype, other); - - if(self.realowner.weapon == WEP_MINE_LAYER.m_id) - { - entity oldself; - oldself = self; - self = self.realowner; - if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1)) - { - self.cnt = WEP_MINE_LAYER.m_id; - ATTACK_FINISHED(self) = time; - self.switchweapon = w_getbestweapon(self); - } - self = oldself; - } - self.realowner.minelayer_mines -= 1; - remove(self); -} - -void W_MineLayer_DoRemoteExplode(void) -{ - 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, WEP_CVAR(minelayer, remote_damage), WEP_CVAR(minelayer, remote_edgedamage), WEP_CVAR(minelayer, remote_radius), world, world, WEP_CVAR(minelayer, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world); - - if(self.realowner.weapon == WEP_MINE_LAYER.m_id) - { - entity oldself; - oldself = self; - self = self.realowner; - if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1)) - { - self.cnt = WEP_MINE_LAYER.m_id; - ATTACK_FINISHED(self) = time; - self.switchweapon = w_getbestweapon(self); - } - self = oldself; - } - self.realowner.minelayer_mines -= 1; - remove(self); -} - -void W_MineLayer_RemoteExplode(void) -{ - if(self.realowner.deadflag == DEAD_NO) - if((self.spawnshieldtime >= 0) - ? (time >= self.spawnshieldtime) // timer - : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(minelayer, remote_radius)) // safety device - ) - { - W_MineLayer_DoRemoteExplode(); - } -} - -void W_MineLayer_ProximityExplode(void) -{ - // 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(WEP_CVAR(minelayer, protection) && self.mine_explodeanyway == 0) - { - entity head; - head = findradius(self.origin, WEP_CVAR(minelayer, radius)); - while(head) - { - if(head == self.realowner || SAME_TEAM(head, self.realowner)) - return; - head = head.chain; - } - } - - self.mine_time = 0; - W_MineLayer_Explode(); -} - -int W_MineLayer_Count(entity e) -{ - int minecount = 0; - entity mine; - for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e) - minecount += 1; - - return minecount; -} - -void W_MineLayer_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) && (self.cnt > 0)) - { - if(WEP_CVAR(minelayer, lifetime_countdown) > 0) - spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM); - self.mine_time = time + WEP_CVAR(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 || self.realowner.frozen) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_MineLayer_Explode(); - return; - } - - // set the mine for detonation when a foe gets close enough - head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius)); - while(head) - { - if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen) - if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates - if(!self.mine_time) - { - spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM); - self.mine_time = time + WEP_CVAR(minelayer, time); - } - head = head.chain; - } - - // explode if it's time to - if(self.mine_time && time >= self.mine_time) - { - W_MineLayer_ProximityExplode(); - return; - } - - // remote detonation - if(self.realowner.weapon == WEP_MINE_LAYER.m_id) - if(self.realowner.deadflag == DEAD_NO) - if(self.minelayer_detonate) - W_MineLayer_RemoteExplode(); -} - -void W_MineLayer_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_MineLayer_Stick(other); - } -} - -void W_MineLayer_Damage(entity inflictor, entity attacker, float damage, int 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_MineLayer_Explode); -} - -void W_MineLayer_Attack(void) -{ - entity mine; - entity flash; - - // scan how many mines we placed, and return if we reached our limit - if(WEP_CVAR(minelayer, limit)) - { - if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) - { - // the refire delay keeps this message from being spammed - Send_Notification(NOTIF_ONE, self, MSG_MULTI, WEAPON_MINELAYER_LIMIT, WEP_CVAR(minelayer, limit)); - play2(self, W_Sound("unavailable")); - return; - } - } - - W_DecreaseAmmo(WEP_CVAR(minelayer, ammo)); - - W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', false, 5, W_Sound("mine_fire"), CH_WEAPON_A, WEP_CVAR(minelayer, damage)); - Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - mine = WarpZone_RefSys_SpawnSameRefSys(self); - mine.owner = mine.realowner = self; - if(WEP_CVAR(minelayer, detonatedelay) >= 0) - mine.spawnshieldtime = time + WEP_CVAR(minelayer, detonatedelay); - else - mine.spawnshieldtime = -1; - mine.classname = "mine"; - mine.bot_dodge = true; - mine.bot_dodgerating = WEP_CVAR(minelayer, damage) * 2; // * 2 because it can detonate inflight which makes it even more dangerous - - mine.takedamage = DAMAGE_YES; - mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale); - mine.health = WEP_CVAR(minelayer, health); - mine.event_damage = W_MineLayer_Damage; - mine.damagedbycontents = true; - - mine.movetype = MOVETYPE_TOSS; - PROJECTILE_MAKETRIGGER(mine); - mine.projectiledeathtype = WEP_MINE_LAYER.m_id; - 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_SetupProjVelocity_Basic(mine, WEP_CVAR(minelayer, speed), 0); - mine.angles = vectoangles(mine.velocity); - - mine.touch = W_MineLayer_Touch; - mine.think = W_MineLayer_Think; - mine.nextthink = time; - mine.cnt = (WEP_CVAR(minelayer, lifetime) - WEP_CVAR(minelayer, lifetime_countdown)); - mine.flags = FL_PROJECTILE; - mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY; - - if(mine.cnt > 0) { mine.cnt += time; } - - 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 - - MUTATOR_CALLHOOK(EditProjectile, self, mine); - - self.minelayer_mines = W_MineLayer_Count(self); -} - -float W_MineLayer_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; -} - -bool W_MineLayer(int req) -{ - entity mine; - float ammo_amount; - switch(req) - { - case WR_AIM: - { - // aim and decide to fire if appropriate - if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) - self.BUTTON_ATCK = false; - else - self.BUTTON_ATCK = bot_aim(WEP_CVAR(minelayer, speed), 0, WEP_CVAR(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 = WEP_CVAR(minelayer, edgedamage); - coredamage = WEP_CVAR(minelayer, damage); - edgeradius = WEP_CVAR(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; - } - - return true; - } - case WR_THINK: - { - if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < WEP_CVAR(minelayer, ammo)) // forced reload - { - // not if we're holding the minelayer without enough ammo, but can detonate existing mines - if(!(W_MineLayer_PlacedMines(false) && self.WEP_AMMO(MINE_LAYER) < WEP_CVAR(minelayer, ammo))) - WEP_ACTION(self.weapon, WR_RELOAD); - } - else if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire))) - { - W_MineLayer_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready); - } - } - - if(self.BUTTON_ATCK2) - { - if(W_MineLayer_PlacedMines(true)) - sound(self, CH_WEAPON_B, W_Sound("mine_det"), VOL_BASE, ATTN_NORM); - } - - return true; - } - case WR_INIT: - { - precache_model("models/flash.md3"); - precache_model("models/mine.md3"); - precache_model(W_Model("g_minelayer.md3")); - precache_model(W_Model("v_minelayer.md3")); - precache_model(W_Model("h_minelayer.iqm")); - precache_sound(W_Sound("mine_det")); - precache_sound(W_Sound("mine_fire")); - precache_sound(W_Sound("mine_stick")); - precache_sound(W_Sound("mine_trigger")); - MINELAYER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - // don't switch while placing a mine - if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER.m_id) - { - ammo_amount = self.WEP_AMMO(MINE_LAYER) >= WEP_CVAR(minelayer, ammo); - ammo_amount += self.(weapon_load[WEP_MINE_LAYER.m_id]) >= WEP_CVAR(minelayer, ammo); - return ammo_amount; - } - return true; - } - case WR_CHECKAMMO2: - { - if(W_MineLayer_PlacedMines(false)) - return true; - else - return false; - } - case WR_CONFIG: - { - MINELAYER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.minelayer_mines = 0; - return true; - } - case WR_RELOAD: - { - W_Reload(WEP_CVAR(minelayer, ammo), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_MINELAYER_SUICIDE; - } - case WR_KILLMESSAGE: - { - return WEAPON_MINELAYER_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_MineLayer(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("mine_exp"), VOL_BASE, ATTN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("mine_exp")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_mortar.qc b/qcsrc/common/weapons/w_mortar.qc deleted file mode 100644 index 850a6c342f..0000000000 --- a/qcsrc/common/weapons/w_mortar.qc +++ /dev/null @@ -1,491 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ MORTAR, -/* function */ W_Mortar, -/* ammotype */ ammo_rockets, -/* impulse */ 4, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '1 0 0', -/* modelname */ "gl", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairgrenadelauncher 0.7", -/* wepimg */ "weapongrenadelauncher", -/* refname */ "mortar", -/* wepname */ _("Mortar") -); - -#define MORTAR_SETTINGS(w_cvar,w_prop) MORTAR_SETTINGS_LIST(w_cvar, w_prop, MORTAR, mortar) -#define MORTAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, ammo) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, NONE, bouncefactor) \ - w_cvar(id, sn, NONE, bouncestop) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, damageforcescale) \ - w_cvar(id, sn, BOTH, edgedamage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, health) \ - w_cvar(id, sn, BOTH, lifetime) \ - w_cvar(id, sn, SEC, lifetime_bounce) \ - w_cvar(id, sn, BOTH, lifetime_stick) \ - w_cvar(id, sn, BOTH, radius) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, SEC, remote_detonateprimary) \ - w_cvar(id, sn, PRI, remote_minbouncecnt) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, BOTH, speed_up) \ - w_cvar(id, sn, BOTH, speed_z) \ - w_cvar(id, sn, BOTH, spread) \ - w_cvar(id, sn, BOTH, type) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -MORTAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float gl_detonate_later; -.float gl_bouncecnt; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC - -void spawnfunc_weapon_mortar(void) { weapon_defaultspawnfunc(WEP_MORTAR.m_id); } -void spawnfunc_weapon_grenadelauncher(void) { spawnfunc_weapon_mortar(); } - -void W_Mortar_Grenade_Explode(void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(DIFF_TEAM(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, WEP_CVAR_PRI(mortar, damage), WEP_CVAR_PRI(mortar, edgedamage), WEP_CVAR_PRI(mortar, radius), world, world, WEP_CVAR_PRI(mortar, force), self.projectiledeathtype, other); - - remove(self); -} - -void W_Mortar_Grenade_Explode2(void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(DIFF_TEAM(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, WEP_CVAR_SEC(mortar, damage), WEP_CVAR_SEC(mortar, edgedamage), WEP_CVAR_SEC(mortar, radius), world, world, WEP_CVAR_SEC(mortar, force), self.projectiledeathtype, other); - - remove(self); -} - - -void W_Mortar_Grenade_Damage(entity inflictor, entity attacker, float damage, int 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_Mortar_Grenade_Think1(void) -{ - self.nextthink = time; - if(time > self.cnt) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_Mortar_Grenade_Explode(); - return; - } - if(self.gl_detonate_later && self.gl_bouncecnt >= WEP_CVAR_PRI(mortar, remote_minbouncecnt)) - W_Mortar_Grenade_Explode(); -} - -void W_Mortar_Grenade_Touch1(void) -{ - PROJECTILE_TOUCH; - if(other.takedamage == DAMAGE_AIM || WEP_CVAR_PRI(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile - { - self.use(); - } - else if(WEP_CVAR_PRI(mortar, type) == 1) // bounce - { - float r; - r = random() * 6; - if(r < 1) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM); - else if(r < 2) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM); - else if(r < 3) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM); - else if(r < 4) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM); - else if(r < 5) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM); - else - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce6"), VOL_BASE, ATTN_NORM); - Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1); - self.projectiledeathtype |= HITTYPE_BOUNCE; - self.gl_bouncecnt += 1; - } - else if(WEP_CVAR_PRI(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick - { - spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), 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 + WEP_CVAR_PRI(mortar, lifetime_stick)); - } -} - -void W_Mortar_Grenade_Touch2(void) -{ - PROJECTILE_TOUCH; - if(other.takedamage == DAMAGE_AIM || WEP_CVAR_SEC(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile - { - self.use(); - } - else if(WEP_CVAR_SEC(mortar, type) == 1) // bounce - { - float r; - r = random() * 6; - if(r < 1) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM); - else if(r < 2) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM); - else if(r < 3) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM); - else if(r < 4) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM); - else if(r < 5) - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM); - else - spamsound(self, CH_SHOTS, W_Sound("grenade_bounce6"), VOL_BASE, ATTN_NORM); - Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1); - self.projectiledeathtype |= HITTYPE_BOUNCE; - self.gl_bouncecnt += 1; - - if(WEP_CVAR_SEC(mortar, lifetime_bounce) && self.gl_bouncecnt == 1) - self.nextthink = time + WEP_CVAR_SEC(mortar, lifetime_bounce); - - } - else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick - { - spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), 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 + WEP_CVAR_SEC(mortar, lifetime_stick)); - } -} - -void W_Mortar_Attack(void) -{ - entity gren; - - W_DecreaseAmmo(WEP_CVAR_PRI(mortar, ammo)); - - W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 4, W_Sound("grenade_fire"), CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage)); - w_shotdir = v_forward; // no TrueAim for grenades please - - Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - gren = spawn(); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = true; - gren.bot_dodgerating = WEP_CVAR_PRI(mortar, damage); - gren.movetype = MOVETYPE_BOUNCE; - gren.bouncefactor = WEP_CVAR(mortar, bouncefactor); - gren.bouncestop = WEP_CVAR(mortar, bouncestop); - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_MORTAR.m_id; - setorigin(gren, w_shotorg); - setsize(gren, '-3 -3 -3', '3 3 3'); - - gren.cnt = time + WEP_CVAR_PRI(mortar, lifetime); - gren.nextthink = time; - gren.think = W_Mortar_Grenade_Think1; - gren.use = W_Mortar_Grenade_Explode; - gren.touch = W_Mortar_Grenade_Touch1; - - gren.takedamage = DAMAGE_YES; - gren.health = WEP_CVAR_PRI(mortar, health); - gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale); - gren.event_damage = W_Mortar_Grenade_Damage; - gren.damagedbycontents = true; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SetupProjVelocity_UP_PRI(gren, mortar); - - gren.angles = vectoangles(gren.velocity); - gren.flags = FL_PROJECTILE; - - if(WEP_CVAR_PRI(mortar, type) == 0 || WEP_CVAR_PRI(mortar, type) == 2) - CSQCProjectile(gren, true, PROJECTILE_GRENADE, true); - else - CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true); - - MUTATOR_CALLHOOK(EditProjectile, self, gren); -} - -void W_Mortar_Attack2(void) -{ - entity gren; - - W_DecreaseAmmo(WEP_CVAR_SEC(mortar, ammo)); - - W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 4, W_Sound("grenade_fire"), CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage)); - w_shotdir = v_forward; // no TrueAim for grenades please - - Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - - gren = spawn(); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = true; - gren.bot_dodgerating = WEP_CVAR_SEC(mortar, damage); - gren.movetype = MOVETYPE_BOUNCE; - gren.bouncefactor = WEP_CVAR(mortar, bouncefactor); - gren.bouncestop = WEP_CVAR(mortar, bouncestop); - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_MORTAR.m_id | HITTYPE_SECONDARY; - setorigin(gren, w_shotorg); - setsize(gren, '-3 -3 -3', '3 3 3'); - - gren.nextthink = time + WEP_CVAR_SEC(mortar, lifetime); - gren.think = adaptor_think2use_hittype_splash; - gren.use = W_Mortar_Grenade_Explode2; - gren.touch = W_Mortar_Grenade_Touch2; - - gren.takedamage = DAMAGE_YES; - gren.health = WEP_CVAR_SEC(mortar, health); - gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale); - gren.event_damage = W_Mortar_Grenade_Damage; - gren.damagedbycontents = true; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SetupProjVelocity_UP_SEC(gren, mortar); - - gren.angles = vectoangles(gren.velocity); - gren.flags = FL_PROJECTILE; - - if(WEP_CVAR_SEC(mortar, type) == 0 || WEP_CVAR_SEC(mortar, type) == 2) - CSQCProjectile(gren, true, PROJECTILE_GRENADE, true); - else - CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true); - - MUTATOR_CALLHOOK(EditProjectile, self, gren); -} - -.float bot_secondary_grenademooth; -bool W_Mortar(int req) -{ - entity nade; - float nadefound; - float ammo_amount; - switch(req) - { - case WR_AIM: - { - self.BUTTON_ATCK = false; - self.BUTTON_ATCK2 = false; - if(self.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH - { - if(bot_aim(WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), true)) - { - self.BUTTON_ATCK = true; - if(random() < 0.01) self.bot_secondary_grenademooth = 1; - } - } - else - { - if(bot_aim(WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), true)) - { - self.BUTTON_ATCK2 = true; - if(random() < 0.02) self.bot_secondary_grenademooth = 0; - } - } - - return true; - } - /*case WR_CALCINFO: - { - wepinfo_pri_refire = max3(sys_frametime, WEP_CVAR_PRI(mortar, refire), WEP_CVAR_PRI(mortar, animtime)); - wepinfo_pri_dps = (WEP_CVAR_PRI(mortar, damage) * (1 / wepinfo_pri_refire)); - wepinfo_pri_speed = (1 / max(1, (10000 / max(1, WEP_CVAR_PRI(mortar, speed))))); - - // for the range calculation, closer to 1 is better - wepinfo_pri_range_max = 2000 * wepinfo_pri_speed; - wepinfo_pri_range = wepinfo_pri_speed * WEP_CVAR_PRI(mortar, - - wepinfo_sec_refire = max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime)); - wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / wepinfo_sec_refire)); - - wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime)))); - wepinfo_ter_dps = 0; - */ - case WR_THINK: - { - if(autocvar_g_balance_mortar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR_PRI(mortar, refire))) - { - W_Mortar_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(mortar, animtime), w_ready); - } - } - else if(self.BUTTON_ATCK2) - { - if(WEP_CVAR_SEC(mortar, 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, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM); - } - else if(weapon_prepareattack(1, WEP_CVAR_SEC(mortar, refire))) - { - W_Mortar_Attack2(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(mortar, animtime), w_ready); - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_gl.md3")); - precache_model(W_Model("v_gl.md3")); - precache_model(W_Model("h_gl.iqm")); - precache_sound(W_Sound("grenade_bounce1")); - precache_sound(W_Sound("grenade_bounce2")); - precache_sound(W_Sound("grenade_bounce3")); - precache_sound(W_Sound("grenade_bounce4")); - precache_sound(W_Sound("grenade_bounce5")); - precache_sound(W_Sound("grenade_bounce6")); - precache_sound(W_Sound("grenade_stick")); - precache_sound(W_Sound("grenade_fire")); - MORTAR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_PRI(mortar, ammo); - ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_SEC(mortar, ammo); - ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo); - return ammo_amount; - } - case WR_CONFIG: - { - MORTAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), W_Sound("reload")); // WEAPONTODO - return true; - } - case WR_SUICIDEMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_MORTAR_SUICIDE_BOUNCE; - else - return WEAPON_MORTAR_SUICIDE_EXPLODE; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_MORTAR_MURDER_BOUNCE; - else - return WEAPON_MORTAR_MURDER_EXPLODE; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Mortar(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum(EFFECT_GRENADE_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("grenade_impact"), VOL_BASE, ATTN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("grenade_impact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_porto.qc b/qcsrc/common/weapons/w_porto.qc deleted file mode 100644 index 2d08c0fcfd..0000000000 --- a/qcsrc/common/weapons/w_porto.qc +++ /dev/null @@ -1,431 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ PORTO, -/* function */ W_Porto, -/* ammotype */ ammo_none, -/* impulse */ 0, -/* flags */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON, -/* rating */ 0, -/* color */ '0.5 0.5 0.5', -/* modelname */ "porto", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairporto 0.6", -/* wepimg */ "weaponporto", -/* refname */ "porto", -/* wepname */ _("Port-O-Launch") -); - -#define PORTO_SETTINGS(w_cvar,w_prop) PORTO_SETTINGS_LIST(w_cvar, w_prop, PORTO, porto) -#define PORTO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, lifetime) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, speed) \ - w_cvar(id, sn, NONE, secondary) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -PORTO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.entity porto_current; -.vector porto_v_angle; // holds "held" view angles -.float porto_v_angle_held; -.vector right_vector; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -#include "../triggers/trigger/jumppads.qh" - -void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO.m_id); } - -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 && !(self.realowner.weapons & WEPSET_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.m_id, 0, self.origin, self.velocity); - Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_FAILED); - } - } - } - 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, ATTEN_NORM); - remove(self); - } - else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) - { - spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTEN_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, ATTEN_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, ATTEN_NORM); - trace_plane_normal = norm; - Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN); - W_Porto_Success(); - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_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, ATTEN_NORM); - trace_plane_normal = norm; - Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT); - W_Porto_Success(); - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_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, ATTEN_NORM); - trace_plane_normal = norm; - Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN); - 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, ATTEN_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, ATTEN_NORM); - trace_plane_normal = norm; - Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT); - W_Porto_Success(); - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); - Portal_ClearAll_PortalsOnly(self.realowner); - W_Porto_Fail(0); - } - } - else - { - sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_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; - - //Send_Effect(EFFECT_GRENADE_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'); - - gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime); - gren.think = W_Porto_Think; - gren.touch = W_Porto_Touch; - - if(self.items & ITEM_Strength.m_itemid) - W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed) * autocvar_g_balance_powerup_strength_force, 0); - else - W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), 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); - - MUTATOR_CALLHOOK(EditProjectile, self, gren); -} - -bool w_nexball_weapon(int req); // WEAPONTODO -bool W_Porto(int req) -{ - //vector v_angle_save; - - if(g_nexball) { return w_nexball_weapon(req); } - - switch(req) - { - case WR_AIM: - { - self.BUTTON_ATCK = false; - self.BUTTON_ATCK2 = false; - if(!WEP_CVAR(porto, secondary)) - if(bot_aim(WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false)) - self.BUTTON_ATCK = true; - - return true; - } - case WR_CONFIG: - { - PORTO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_THINK: - { - if(WEP_CVAR(porto, secondary)) - { - if(self.BUTTON_ATCK) - if(!self.porto_current) - if(!self.porto_forbidden) - if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire))) - { - W_Porto_Attack(0); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); - } - - if(self.BUTTON_ATCK2) - if(!self.porto_current) - if(!self.porto_forbidden) - if(weapon_prepareattack(1, WEP_CVAR_SEC(porto, refire))) - { - W_Porto_Attack(1); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(porto, 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, WEP_CVAR_PRI(porto, refire))) - { - W_Porto_Attack(-1); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); - } - } - - return true; - } - case WR_CHECKAMMO1: - case WR_CHECKAMMO2: - { - // always allow infinite ammo - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_porto.md3")); - precache_model(W_Model("v_porto.md3")); - precache_model(W_Model("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"); - PORTO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.ammo_field = ammo_none; - return true; - } - case WR_RESETPLAYER: - { - self.porto_current = world; - return true; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Porto(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - LOG_INFO("Since when does Porto send DamageInfo?\n"); - return true; - } - case WR_INIT: - { - // nothing to do - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_rifle.qc b/qcsrc/common/weapons/w_rifle.qc deleted file mode 100644 index ef3babd872..0000000000 --- a/qcsrc/common/weapons/w_rifle.qc +++ /dev/null @@ -1,317 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ RIFLE, -/* function */ W_Rifle, -/* ammotype */ ammo_nails, -/* impulse */ 7, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '0.5 1 0', -/* modelname */ "campingrifle", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairrifle 0.6", -/* wepimg */ "weaponrifle", -/* refname */ "rifle", -/* wepname */ _("Rifle") -); - -#define RIFLE_SETTINGS(w_cvar,w_prop) RIFLE_SETTINGS_LIST(w_cvar, w_prop, RIFLE, rifle) -#define RIFLE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, ammo) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, bullethail) \ - w_cvar(id, sn, BOTH, burstcost) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, BOTH, shots) \ - w_cvar(id, sn, BOTH, solidpenetration) \ - w_cvar(id, sn, BOTH, spread) \ - w_cvar(id, sn, BOTH, tracer) \ - w_cvar(id, sn, NONE, bursttime) \ - w_cvar(id, sn, NONE, secondary) \ - w_cvar(id, sn, SEC, reload) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -RIFLE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float rifle_accumulator; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_rifle(void) { weapon_defaultspawnfunc(WEP_RIFLE.m_id); } -void spawnfunc_weapon_campingrifle(void) { spawnfunc_weapon_rifle(); } -void spawnfunc_weapon_sniperrifle(void) { spawnfunc_weapon_rifle(); } - -void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSolidPenetration, float pAmmo, int deathtype, float pTracer, float pShots, string pSound) -{ - float i; - - W_DecreaseAmmo(pAmmo); - - W_SetupShot(self, true, 2, pSound, CH_WEAPON_A, pDamage * pShots); - - Send_Effect(EFFECT_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) - fireBullet(w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE)); - - 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(void) -{ - W_Rifle_FireBullet(WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), WEP_RIFLE.m_id, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), W_Sound("campingrifle_fire")); -} - -void W_Rifle_Attack2(void) -{ - W_Rifle_FireBullet(WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), WEP_RIFLE.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), W_Sound("campingrifle_fire2")); -} - -.void(void) rifle_bullethail_attackfunc; -.float rifle_bullethail_frame; -.float rifle_bullethail_animtime; -.float rifle_bullethail_refire; -void W_Rifle_BulletHail_Continue(void) -{ - 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; - LOG_INFO(ftos(self.WEP_AMMO(RIFLE)), "\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); - LOG_INFO("thinkf set\n"); - } - else - { - ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time - LOG_INFO("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; -bool W_Rifle(int req) -{ - float ammo_amount; - - switch(req) - { - case 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(1000000, 0, 0.001, false)) - { - self.BUTTON_ATCK = true; - if(random() < 0.01) self.bot_secondary_riflemooth = 1; - } - } - else - { - if(bot_aim(1000000, 0, 0.001, false)) - { - self.BUTTON_ATCK2 = true; - if(random() < 0.03) self.bot_secondary_riflemooth = 0; - } - } - - return true; - } - case WR_THINK: - { - if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else - { - self.rifle_accumulator = bound(time - WEP_CVAR(rifle, bursttime), self.rifle_accumulator, time); - if(self.BUTTON_ATCK) - if(weapon_prepareattack_check(0, WEP_CVAR_PRI(rifle, refire))) - if(time >= self.rifle_accumulator + WEP_CVAR_PRI(rifle, burstcost)) - { - weapon_prepareattack_do(0, WEP_CVAR_PRI(rifle, refire)); - W_Rifle_BulletHail(WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire)); - self.rifle_accumulator += WEP_CVAR_PRI(rifle, burstcost); - } - if(self.BUTTON_ATCK2) - { - if(WEP_CVAR(rifle, secondary)) - { - if(WEP_CVAR_SEC(rifle, reload)) - WEP_ACTION(self.weapon, WR_RELOAD); - else - { - if(weapon_prepareattack_check(1, WEP_CVAR_SEC(rifle, refire))) - if(time >= self.rifle_accumulator + WEP_CVAR_SEC(rifle, burstcost)) - { - weapon_prepareattack_do(1, WEP_CVAR_SEC(rifle, refire)); - W_Rifle_BulletHail(WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire)); - self.rifle_accumulator += WEP_CVAR_SEC(rifle, burstcost); - } - } - } - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_sniperrifle.md3")); - precache_model(W_Model("v_sniperrifle.md3")); - precache_model(W_Model("h_sniperrifle.iqm")); - precache_sound(W_Sound("campingrifle_fire")); - precache_sound(W_Sound("campingrifle_fire2")); - RIFLE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_PRI(rifle, ammo); - ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_PRI(rifle, ammo); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_SEC(rifle, ammo); - ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_SEC(rifle, ammo); - return ammo_amount; - } - case WR_CONFIG: - { - RIFLE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.rifle_accumulator = time - WEP_CVAR(rifle, bursttime); - return true; - } - case WR_RELOAD: - { - W_Reload(min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo)), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_THINKING_WITH_PORTALS; - } - case 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 false; -} -#endif -#ifdef CSQC -bool W_Rifle(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum(EFFECT_RIFLE_IMPACT), org2, w_backoff * 1000, 1); - if(!w_issilent) - { - if(w_random < 0.2) - sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM); - else if(w_random < 0.4) - sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM); - else if(w_random < 0.5) - sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM); - } - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("ric1")); - precache_sound(W_Sound("ric2")); - precache_sound(W_Sound("ric3")); - if(autocvar_cl_reticle && autocvar_cl_reticle_weapon) - { - precache_pic("gfx/reticle_nex"); - } - return true; - } - case WR_ZOOMRETICLE: - { - if(button_zoom || zoomscript_caught) - { - reticle_image = "gfx/reticle_nex"; - return true; - } - else - { - // no weapon specific image for this weapon - return false; - } - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_rpc.qc b/qcsrc/common/weapons/w_rpc.qc deleted file mode 100644 index 8a34490e28..0000000000 --- a/qcsrc/common/weapons/w_rpc.qc +++ /dev/null @@ -1,265 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ RPC, -/* function */ W_RocketPropelledChainsaw, -/* ammotype */ ammo_rockets, -/* impulse */ 7, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* color */ '0.5 0.5 0', -/* modelname */ "ok_rl", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairrocketlauncher 0.7", -/* wepimg */ "weaponrpc", -/* refname */ "rpc", -/* wepname */ _("Rocket Propelled Chainsaw") -); - -#define RPC_SETTINGS(w_cvar,w_prop) RPC_SETTINGS_LIST(w_cvar, w_prop, RPC, rpc) -#define RPC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, ammo) \ - w_cvar(id, sn, NONE, animtime) \ - w_cvar(id, sn, NONE, damage) \ - w_cvar(id, sn, NONE, damage2) \ - w_cvar(id, sn, NONE, damageforcescale) \ - w_cvar(id, sn, NONE, edgedamage) \ - w_cvar(id, sn, NONE, force) \ - w_cvar(id, sn, NONE, health) \ - w_cvar(id, sn, NONE, lifetime) \ - w_cvar(id, sn, NONE, radius) \ - w_cvar(id, sn, NONE, refire) \ - w_cvar(id, sn, NONE, speed) \ - w_cvar(id, sn, NONE, speedaccel) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -RPC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_rpc() { weapon_defaultspawnfunc(WEP_RPC.m_id); } - -void W_RocketPropelledChainsaw_Explode() -{ - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - RadiusDamage (self, self.realowner, WEP_CVAR(rpc, damage), WEP_CVAR(rpc, edgedamage), WEP_CVAR(rpc, radius), world, world, WEP_CVAR(rpc, force), self.projectiledeathtype, other); - - remove (self); -} - -void W_RocketPropelledChainsaw_Touch (void) -{ - if(WarpZone_Projectile_Touch()) - if(wasfreed(self)) - return; - - W_RocketPropelledChainsaw_Explode(); -} - -void W_RocketPropelledChainsaw_Damage (entity inflictor, entity attacker, float damage, int 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, W_RocketPropelledChainsaw_Explode); -} - -void W_RocketPropelledChainsaw_Think() -{ - if(self.cnt <= time) - { - remove(self); - return; - } - - self.cnt = vlen(self.velocity); - self.wait = self.cnt * sys_frametime; - self.pos1 = normalize(self.velocity); - - tracebox(self.origin, self.mins, self.maxs, self.origin + self.pos1 * (2 * self.wait), MOVE_NORMAL, self); - if(IS_PLAYER(trace_ent)) - Damage (trace_ent, self, self.realowner, WEP_CVAR(rpc, damage2), self.projectiledeathtype, self.origin, normalize(self.origin - other.origin) * WEP_CVAR(rpc, force)); - - self.velocity = self.pos1 * (self.cnt + (WEP_CVAR(rpc, speedaccel) * sys_frametime)); - - UpdateCSQCProjectile(self); - self.nextthink = time; -} - -void W_RocketPropelledChainsaw_Attack (void) -{ - entity missile = spawn(); //WarpZone_RefSys_SpawnSameRefSys(self); - entity flash = spawn (); - - W_DecreaseAmmo(WEP_CVAR(rpc, ammo)); - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', false, 5, W_Sound("rocket_fire"), CH_WEAPON_A, WEP_CVAR(rpc, damage)); - Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); - PROJECTILE_MAKETRIGGER(missile); - - missile.owner = missile.realowner = self; - missile.bot_dodge = true; - missile.bot_dodgerating = WEP_CVAR(rpc, damage) * 2; - - missile.takedamage = DAMAGE_YES; - missile.damageforcescale = WEP_CVAR(rpc, damageforcescale); - missile.health = WEP_CVAR(rpc, health); - missile.event_damage = W_RocketPropelledChainsaw_Damage; - missile.damagedbycontents = true; - missile.movetype = MOVETYPE_FLY; - - missile.projectiledeathtype = WEP_RPC.m_id; - 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_SetupProjVelocity_Basic(missile, WEP_CVAR(rpc, speed), 0); - - missile.touch = W_RocketPropelledChainsaw_Touch; - - missile.think = W_RocketPropelledChainsaw_Think; - missile.cnt = time + WEP_CVAR(rpc, lifetime); - missile.nextthink = time; - missile.flags = FL_PROJECTILE; - - CSQCProjectile(missile, true, PROJECTILE_RPC, false); - - 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'); - missile.pos1 = missile.velocity; - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -bool W_RocketPropelledChainsaw(int req) -{ - float ammo_amount = false; - switch(req) - { - case WR_AIM: - { - self.BUTTON_ATCK = bot_aim(WEP_CVAR(rpc, speed), 0, WEP_CVAR(rpc, lifetime), false); - return true; - } - case WR_THINK: - { - if(WEP_CVAR(rpc, reload_ammo) && self.clip_load < WEP_CVAR(rpc, ammo)) - WEP_ACTION(self.weapon, WR_RELOAD); - else - { - if (self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR(rpc, refire))) - { - W_RocketPropelledChainsaw_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(rpc, animtime), w_ready); - } - } - - if (self.BUTTON_ATCK2) - { - // to-do - } - } - - return true; - } - case WR_INIT: - { - precache_model ("models/flash.md3"); - precache_model(W_Model("g_ok_rl.md3")); - precache_model(W_Model("v_ok_rl.md3")); - precache_model(W_Model("h_ok_rl.iqm")); - precache_sound (W_Sound("rocket_fire")); - RPC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(RPC) >= WEP_CVAR(rpc, ammo); - ammo_amount += self.(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR(rpc, ammo); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - return false; - } - case WR_CONFIG: - { - RPC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(WEP_CVAR(rpc, ammo), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) - return WEAPON_RPC_SUICIDE_SPLASH; - else - return WEAPON_RPC_SUICIDE_DIRECT; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_BLASTER_MURDER; - else if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) - return WEAPON_RPC_MURDER_SPLASH; - else - return WEAPON_RPC_MURDER_DIRECT; - } - } - - return false; -} -#endif - -#ifdef CSQC -bool W_RocketPropelledChainsaw(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("rocket_impact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_seeker.qc b/qcsrc/common/weapons/w_seeker.qc deleted file mode 100644 index 69c4974de6..0000000000 --- a/qcsrc/common/weapons/w_seeker.qc +++ /dev/null @@ -1,793 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ SEEKER, -/* function */ W_Seeker, -/* ammotype */ ammo_rockets, -/* impulse */ 8, -/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '0.5 1 0', -/* modelname */ "seeker", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairseeker 0.8", -/* wepimg */ "weaponseeker", -/* refname */ "seeker", -/* wepname */ _("T.A.G. Seeker") -); - -#define SEEKER_SETTINGS(w_cvar,w_prop) SEEKER_SETTINGS_LIST(w_cvar, w_prop, SEEKER, seeker) -#define SEEKER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, type) \ - w_cvar(id, sn, NONE, flac_ammo) \ - w_cvar(id, sn, NONE, flac_animtime) \ - w_cvar(id, sn, NONE, flac_damage) \ - w_cvar(id, sn, NONE, flac_edgedamage) \ - w_cvar(id, sn, NONE, flac_force) \ - w_cvar(id, sn, NONE, flac_lifetime) \ - w_cvar(id, sn, NONE, flac_lifetime_rand) \ - w_cvar(id, sn, NONE, flac_radius) \ - w_cvar(id, sn, NONE, flac_refire) \ - w_cvar(id, sn, NONE, flac_speed) \ - w_cvar(id, sn, NONE, flac_speed_up) \ - w_cvar(id, sn, NONE, flac_speed_z) \ - w_cvar(id, sn, NONE, flac_spread) \ - w_cvar(id, sn, NONE, missile_accel) \ - w_cvar(id, sn, NONE, missile_ammo) \ - w_cvar(id, sn, NONE, missile_animtime) \ - w_cvar(id, sn, NONE, missile_count) \ - w_cvar(id, sn, NONE, missile_damage) \ - w_cvar(id, sn, NONE, missile_damageforcescale) \ - w_cvar(id, sn, NONE, missile_decel) \ - w_cvar(id, sn, NONE, missile_delay) \ - w_cvar(id, sn, NONE, missile_edgedamage) \ - w_cvar(id, sn, NONE, missile_force) \ - w_cvar(id, sn, NONE, missile_health) \ - w_cvar(id, sn, NONE, missile_lifetime) \ - w_cvar(id, sn, NONE, missile_proxy) \ - w_cvar(id, sn, NONE, missile_proxy_delay) \ - w_cvar(id, sn, NONE, missile_proxy_maxrange) \ - w_cvar(id, sn, NONE, missile_radius) \ - w_cvar(id, sn, NONE, missile_refire) \ - w_cvar(id, sn, NONE, missile_smart) \ - w_cvar(id, sn, NONE, missile_smart_mindist) \ - w_cvar(id, sn, NONE, missile_smart_trace_max) \ - w_cvar(id, sn, NONE, missile_smart_trace_min) \ - w_cvar(id, sn, NONE, missile_speed) \ - w_cvar(id, sn, NONE, missile_speed_max) \ - w_cvar(id, sn, NONE, missile_speed_up) \ - w_cvar(id, sn, NONE, missile_speed_z) \ - w_cvar(id, sn, NONE, missile_spread) \ - w_cvar(id, sn, NONE, missile_turnrate) \ - w_cvar(id, sn, NONE, tag_ammo) \ - w_cvar(id, sn, NONE, tag_animtime) \ - w_cvar(id, sn, NONE, tag_damageforcescale) \ - w_cvar(id, sn, NONE, tag_health) \ - w_cvar(id, sn, NONE, tag_lifetime) \ - w_cvar(id, sn, NONE, tag_refire) \ - w_cvar(id, sn, NONE, tag_speed) \ - w_cvar(id, sn, NONE, tag_spread) \ - w_cvar(id, sn, NONE, tag_tracker_lifetime) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -SEEKER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.entity tag_target, wps_tag_tracker; -.float tag_time; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_seeker(void) { weapon_defaultspawnfunc(WEP_SEEKER.m_id); } - -// ============================ -// Begin: Missile functions, these are general functions to be manipulated by other code -// ============================ -void W_Seeker_Missile_Explode(void) -{ - self.event_damage = func_null; - RadiusDamage(self, self.realowner, WEP_CVAR(seeker, missile_damage), WEP_CVAR(seeker, missile_edgedamage), WEP_CVAR(seeker, missile_radius), world, world, WEP_CVAR(seeker, missile_force), self.projectiledeathtype, other); - - remove(self); -} - -void W_Seeker_Missile_Touch(void) -{ - PROJECTILE_TOUCH; - - W_Seeker_Missile_Explode(); -} - -void W_Seeker_Missile_Think(void) -{ - entity e; - vector desireddir, olddir, newdir, eorg; - float turnrate; - float dist; - float spd; - - if(time > self.cnt) - { - self.projectiledeathtype |= HITTYPE_SPLASH; - W_Seeker_Missile_Explode(); - } - - spd = vlen(self.velocity); - spd = bound( - spd - WEP_CVAR(seeker, missile_decel) * frametime, - WEP_CVAR(seeker, missile_speed_max), - spd + WEP_CVAR(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 = WEP_CVAR(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(WEP_CVAR(seeker, missile_smart) && (dist > WEP_CVAR(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(WEP_CVAR(seeker, missile_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = WEP_CVAR(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(WEP_CVAR(seeker, missile_proxy)) - { - if(dist <= WEP_CVAR(seeker, missile_proxy_maxrange)) - { - if(self.autoswitch == 0) - { - self.autoswitch = time + WEP_CVAR(seeker, missile_proxy_delay); - } - else - { - if(self.autoswitch <= time) - { - W_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 W_Seeker_Missile_Damage(entity inflictor, entity attacker, float damage, int 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, W_Seeker_Missile_Explode); -} - -/* -void W_Seeker_Missile_Animate(void) -{ - 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 = W_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 W_Seeker_Fire_Missile(vector f_diff, entity m_target) -{ - entity missile; - - W_DecreaseAmmo(WEP_CVAR(seeker, missile_ammo)); - - makevectors(self.v_angle); - W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("seeker_fire"), CH_WEAPON_A, 0); - w_shotorg += f_diff; - Send_Effect(EFFECT_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 = WEP_CVAR(seeker, missile_damage); - - missile.think = W_Seeker_Missile_Think; - missile.touch = W_Seeker_Missile_Touch; - missile.event_damage = W_Seeker_Missile_Damage; - missile.nextthink = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay"); - missile.cnt = time + WEP_CVAR(seeker, missile_lifetime); - missile.enemy = m_target; - missile.solid = SOLID_BBOX; - missile.scale = 2; - missile.takedamage = DAMAGE_YES; - missile.health = WEP_CVAR(seeker, missile_health); - missile.damageforcescale = WEP_CVAR(seeker, missile_damageforcescale); - missile.damagedbycontents = true; - //missile.think = W_Seeker_Missile_Animate; // csqc projectiles. - - if(missile.enemy != world) - missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY; - else - missile.projectiledeathtype = WEP_SEEKER.m_id; - - - 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_SetupProjVelocity_UP_PRE(missile, seeker, missile_); - - missile.angles = vectoangles(missile.velocity); - - CSQCProjectile(missile, false, PROJECTILE_SEEKER, true); - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -// ============================ -// Begin: FLAC, close range attack meant for defeating rockets which are coming at you. -// ============================ -void W_Seeker_Flac_Explode(void) -{ - self.event_damage = func_null; - - RadiusDamage(self, self.realowner, WEP_CVAR(seeker, flac_damage), WEP_CVAR(seeker, flac_edgedamage), WEP_CVAR(seeker, flac_radius), world, world, WEP_CVAR(seeker, flac_force), self.projectiledeathtype, other); - - remove(self); -} - -void W_Seeker_Flac_Touch(void) -{ - PROJECTILE_TOUCH; - - W_Seeker_Flac_Explode(); -} - -void W_Seeker_Fire_Flac(void) -{ - entity missile; - vector f_diff; - float c; - - W_DecreaseAmmo(WEP_CVAR(seeker, flac_ammo)); - - c = 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, W_Sound("flac_fire"), CH_WEAPON_A, WEP_CVAR(seeker, flac_damage)); - w_shotorg += f_diff; - - Send_Effect(EFFECT_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 = WEP_CVAR(seeker, flac_damage); - missile.touch = W_Seeker_Flac_Explode; - missile.use = W_Seeker_Flac_Explode; - missile.think = adaptor_think2use_hittype_splash; - missile.nextthink = time + WEP_CVAR(seeker, flac_lifetime) + WEP_CVAR(seeker, flac_lifetime_rand); - missile.solid = SOLID_BBOX; - missile.movetype = MOVETYPE_FLY; - missile.projectiledeathtype = WEP_SEEKER.m_id; - missile.projectiledeathtype = WEP_SEEKER.m_id | 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_SetupProjVelocity_UP_PRE(missile, seeker, flac_); - CSQCProjectile(missile, true, PROJECTILE_FLAC, true); - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -// ============================ -// Begin: Tag and rocket controllers -// ============================ -entity W_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 W_Seeker_Attack(void) -{ - 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; - - W_Seeker_Fire_Missile('0 0 0', closest_target); -} - -void W_Seeker_Vollycontroller_Think(void) // TODO: Merge this with W_Seeker_Attack -{ - float c; - entity oldself,oldenemy; - self.cnt = self.cnt - 1; - - if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.WEP_AMMO(SEEKER) < WEP_CVAR(seeker, missile_ammo)) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER.m_id)) - { - remove(self); - return; - } - - self.nextthink = time + WEP_CVAR(seeker, missile_delay) * W_WeaponRateFactor(); - - oldself = self; - self = self.realowner; - - oldenemy = self.enemy; - self.enemy = oldself.enemy; - - c = self.cnt % 4; - switch(c) - { - case 0: - W_Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy); - break; - case 1: - W_Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy); - break; - case 2: - W_Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy); - break; - case 3: - default: - W_Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy); - break; - } - - self.enemy = oldenemy; - self = oldself; -} - -void W_Seeker_Tracker_Think(void) -{ - // 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.m_id) - || (time > self.tag_time + WEP_CVAR(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 W_Seeker_Tag_Explode(void) -{ - //if(other==self.realowner) - // return; - Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER.m_id | HITTYPE_BOUNCE, other.species, self); - - remove(self); -} - -void W_Seeker_Tag_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - if(self.health <= 0) - return; - self.health = self.health - damage; - if(self.health <= 0) - W_Seeker_Tag_Explode(); -} - -void W_Seeker_Tag_Touch(void) -{ - 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.m_id | 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 = W_Seeker_Tagged_Info(self.realowner, other); - - if(tag != world) - { - if(other.wps_tag_tracker && (WEP_CVAR(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 = WEP_CVAR(seeker, missile_count); - e.classname = "tag_tracker"; - e.owner = self.owner; - e.realowner = self.realowner; - - if(WEP_CVAR(seeker, type) == 1) - { - e.tag_target = other; - e.tag_time = time; - e.think = W_Seeker_Tracker_Think; - } - else - { - e.enemy = other; - e.think = W_Seeker_Vollycontroller_Think; - } - - e.nextthink = time; - } - - if(WEP_CVAR(seeker, type) == 1) - { - WaypointSprite_Spawn(WP_Seeker, WEP_CVAR(seeker, tag_tracker_lifetime), 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, true, RADARICON_TAGGED); - WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT); - } - } - - remove(self); - return; -} - -void W_Seeker_Fire_Tag(void) -{ - entity missile; - W_DecreaseAmmo(WEP_CVAR(seeker, tag_ammo)); - - W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("tag_fire"), CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count)); - - missile = spawn(); - missile.owner = missile.realowner = self; - missile.classname = "seeker_tag"; - missile.bot_dodge = true; - missile.bot_dodgerating = 50; - missile.touch = W_Seeker_Tag_Touch; - missile.think = SUB_Remove; - missile.nextthink = time + WEP_CVAR(seeker, tag_lifetime); - missile.movetype = MOVETYPE_FLY; - missile.solid = SOLID_BBOX; - - missile.takedamage = DAMAGE_YES; - missile.event_damage = W_Seeker_Tag_Damage; - missile.health = WEP_CVAR(seeker, tag_health); - missile.damageforcescale = WEP_CVAR(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_SetupProjVelocity_PRE(missile, seeker, tag_); - missile.angles = vectoangles(missile.velocity); - - CSQCProjectile(missile, true, PROJECTILE_TAG, false); // has sound - - MUTATOR_CALLHOOK(EditProjectile, self, missile); -} - -// ============================ -// Begin: Genereal weapon functions -// ============================ - -bool W_Seeker(int req) -{ - float ammo_amount; - - switch(req) - { - case WR_AIM: - { - if(WEP_CVAR(seeker, type) == 1) - if(W_Seeker_Tagged_Info(self, self.enemy) != world) - self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, missile_speed_max), 0, WEP_CVAR(seeker, missile_lifetime), false); - else - self.BUTTON_ATCK2 = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false); - else - self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false); - return true; - } - case WR_THINK: - { - if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - - else if(self.BUTTON_ATCK) - { - if(WEP_CVAR(seeker, type) == 1) - { - if(weapon_prepareattack(0, WEP_CVAR(seeker, missile_refire))) - { - W_Seeker_Attack(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, missile_animtime), w_ready); - } - } - else - { - if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire))) - { - W_Seeker_Fire_Tag(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready); - } - } - } - - else if(self.BUTTON_ATCK2) - { - if(WEP_CVAR(seeker, type) == 1) - { - if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire))) - { - W_Seeker_Fire_Tag(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready); - } - } - else - { - if(weapon_prepareattack(0, WEP_CVAR(seeker, flac_refire))) - { - W_Seeker_Fire_Flac(); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, flac_animtime), w_ready); - } - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_seeker.md3")); - precache_model(W_Model("v_seeker.md3")); - precache_model(W_Model("h_seeker.iqm")); - precache_sound(W_Sound("tag_fire")); - precache_sound(W_Sound("flac_fire")); - precache_sound(W_Sound("seeker_fire")); - SEEKER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - { - if(WEP_CVAR(seeker, type) == 1) - { - ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, missile_ammo); - ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, missile_ammo); - } - else - { - ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo); - ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo); - } - return ammo_amount; - } - case WR_CHECKAMMO2: - { - if(WEP_CVAR(seeker, type) == 1) - { - ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo); - ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo); - } - else - { - ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, flac_ammo); - ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, flac_ammo); - } - return ammo_amount; - } - case WR_CONFIG: - { - SEEKER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo)), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_SEEKER_SUICIDE; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_SEEKER_MURDER_TAG; - else - return WEAPON_SEEKER_MURDER_SPRAY; - } - } - return false; -} -#endif -#ifdef CSQC -bool W_Seeker(int req) -{ - switch(req) - { - case 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, W_Sound("tag_impact"), 1, ATTEN_NORM); - } - else - { - pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - { - if(w_random<0.15) - sound(self, CH_SHOTS, W_Sound("tagexp1"), 1, ATTEN_NORM); - else if(w_random<0.7) - sound(self, CH_SHOTS, W_Sound("tagexp2"), 1, ATTEN_NORM); - else - sound(self, CH_SHOTS, W_Sound("tagexp3"), 1, ATTEN_NORM); - } - } - } - else - { - pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1); - if(!w_issilent) - { - if(w_random<0.15) - sound(self, CH_SHOTS, W_Sound("seekerexp1"), 1, ATTEN_NORM); - else if(w_random<0.7) - sound(self, CH_SHOTS, W_Sound("seekerexp2"), 1, ATTEN_NORM); - else - sound(self, CH_SHOTS, W_Sound("seekerexp3"), 1, ATTEN_NORM); - } - } - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("seekerexp1")); - precache_sound(W_Sound("seekerexp2")); - precache_sound(W_Sound("seekerexp3")); - precache_sound(W_Sound("tagexp1")); - precache_sound(W_Sound("tagexp2")); - precache_sound(W_Sound("tagexp3")); - precache_sound(W_Sound("tag_impact")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_shockwave.qc b/qcsrc/common/weapons/w_shockwave.qc deleted file mode 100644 index 3287ea3cb3..0000000000 --- a/qcsrc/common/weapons/w_shockwave.qc +++ /dev/null @@ -1,896 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ SHOCKWAVE, -/* function */ W_Shockwave, -/* ammotype */ ammo_none, -/* impulse */ 2, -/* flags */ WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN | WEP_FLAG_CANCLIMB | WEP_FLAG_MUTATORBLOCKED, -/* rating */ BOT_PICKUP_RATING_LOW, -/* color */ '0.5 0.25 0', -/* modelname */ "shotgun", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairshotgun 0.7", -/* wepimg */ "weaponshotgun", -/* refname */ "shockwave", -/* wepname */ _("Shockwave") -); - -#define SHOCKWAVE_SETTINGS(w_cvar,w_prop) SHOCKWAVE_SETTINGS_LIST(w_cvar, w_prop, SHOCKWAVE, shockwave) -#define SHOCKWAVE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, blast_animtime) \ - w_cvar(id, sn, NONE, blast_damage) \ - w_cvar(id, sn, NONE, blast_distance) \ - w_cvar(id, sn, NONE, blast_edgedamage) \ - w_cvar(id, sn, NONE, blast_force) \ - w_cvar(id, sn, NONE, blast_force_forwardbias) \ - w_cvar(id, sn, NONE, blast_force_zscale) \ - w_cvar(id, sn, NONE, blast_jump_damage) \ - w_cvar(id, sn, NONE, blast_jump_edgedamage) \ - w_cvar(id, sn, NONE, blast_jump_force) \ - w_cvar(id, sn, NONE, blast_jump_force_velocitybias) \ - w_cvar(id, sn, NONE, blast_jump_force_zscale) \ - w_cvar(id, sn, NONE, blast_jump_multiplier_accuracy) \ - w_cvar(id, sn, NONE, blast_jump_multiplier_distance) \ - w_cvar(id, sn, NONE, blast_jump_multiplier_min) \ - w_cvar(id, sn, NONE, blast_jump_radius) \ - w_cvar(id, sn, NONE, blast_multiplier_accuracy) \ - w_cvar(id, sn, NONE, blast_multiplier_distance) \ - w_cvar(id, sn, NONE, blast_multiplier_min) \ - w_cvar(id, sn, NONE, blast_refire) \ - w_cvar(id, sn, NONE, blast_splash_damage) \ - w_cvar(id, sn, NONE, blast_splash_edgedamage) \ - w_cvar(id, sn, NONE, blast_splash_force) \ - w_cvar(id, sn, NONE, blast_splash_force_forwardbias) \ - w_cvar(id, sn, NONE, blast_splash_multiplier_accuracy) \ - w_cvar(id, sn, NONE, blast_splash_multiplier_distance) \ - w_cvar(id, sn, NONE, blast_splash_multiplier_min) \ - w_cvar(id, sn, NONE, blast_splash_radius) \ - w_cvar(id, sn, NONE, blast_spread_max) \ - w_cvar(id, sn, NONE, blast_spread_min) \ - w_cvar(id, sn, NONE, melee_animtime) \ - w_cvar(id, sn, NONE, melee_damage) \ - w_cvar(id, sn, NONE, melee_delay) \ - w_cvar(id, sn, NONE, melee_force) \ - w_cvar(id, sn, NONE, melee_multihit) \ - w_cvar(id, sn, NONE, melee_no_doubleslap) \ - w_cvar(id, sn, NONE, melee_nonplayerdamage) \ - w_cvar(id, sn, NONE, melee_range) \ - w_cvar(id, sn, NONE, melee_refire) \ - w_cvar(id, sn, NONE, melee_swing_side) \ - w_cvar(id, sn, NONE, melee_swing_up) \ - w_cvar(id, sn, NONE, melee_time) \ - w_cvar(id, sn, NONE, melee_traces) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -SHOCKWAVE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -#endif -#ifdef CSQC -void Net_ReadShockwaveParticle(void); -.vector sw_shotorg; -.vector sw_shotdir; -.float sw_distance; -.float sw_spread_max; -.float sw_spread_min; -.float sw_time; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_shockwave(void) -{ - //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO - if(autocvar_sv_q3acompat_machineshotgunswap) - if(self.classname != "droppedweapon") - { - weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id); - return; - } - weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id); -} - -const float MAX_SHOCKWAVE_HITS = 10; -//#define DEBUG_SHOCKWAVE - -.float swing_prev; -.entity swing_alreadyhit; -.float shockwave_blasttime; -entity shockwave_hit[MAX_SHOCKWAVE_HITS]; -float shockwave_hit_damage[MAX_SHOCKWAVE_HITS]; -vector shockwave_hit_force[MAX_SHOCKWAVE_HITS]; - -// MELEE ATTACK MODE -void W_Shockwave_Melee_Think(void) -{ - // declarations - float i, f, swing, swing_factor, swing_damage, meleetime, is_player; - entity target_victim; - vector targpos; - - // check to see if we can still continue, otherwise give up now - if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR(shockwave, melee_no_doubleslap)) - { - remove(self); - return; - } - - // set start time of melee - if(!self.cnt) - { - self.cnt = time; - W_PlayStrengthSound(self.realowner); - } - - // update values for v_* vectors - makevectors(self.realowner.v_angle); - - // calculate swing percentage based on time - meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor(); - swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); - f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces)); - - // perform the traces needed for this frame - for(i=self.swing_prev; i < f; ++i) - { - swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1); - - targpos = (self.realowner.origin + self.realowner.view_ofs - + (v_forward * WEP_CVAR(shockwave, melee_range)) - + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up)) - + (v_right * swing_factor * WEP_CVAR(shockwave, 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 -#ifdef DEBUG_SHOCKWAVE - 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'); -#endif - - is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || IS_MONSTER(trace_ent)); - - if((trace_fraction < 1) // if trace is good, apply the damage and remove self if necessary - && (trace_ent.takedamage == DAMAGE_AIM) - && (trace_ent != self.swing_alreadyhit) - && (is_player || WEP_CVAR(shockwave, 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 = (WEP_CVAR(shockwave, melee_damage) * min(1, swing_factor + 1)); - else - swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage) * min(1, swing_factor + 1)); - - // trigger damage with this calculated info - Damage( - target_victim, - self.realowner, - self.realowner, - swing_damage, - (WEP_SHOCKWAVE.m_id | HITTYPE_SECONDARY), - (self.realowner.origin + self.realowner.view_ofs), - (v_forward * WEP_CVAR(shockwave, melee_force)) - ); - - // handle accuracy - if(accuracy_isgooddamage(self.realowner, target_victim)) - { accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, swing_damage); } - - #ifdef DEBUG_SHOCKWAVE - LOG_INFO(sprintf( - "MELEE: %s hitting %s with %f damage (factor: %f) at %f time.\n", - self.realowner.netname, - target_victim.netname, - swing_damage, - swing_factor, - time - )); - #endif - - // allow multiple hits with one swing, but not against the same player twice - if(WEP_CVAR(shockwave, melee_multihit)) - { - 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_Shockwave_Melee(void) -{ - sound(self, CH_WEAPON_A, W_Sound("shotgun_melee"), VOL_BASE, ATTN_NORM); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(shockwave, melee_animtime), w_ready); - - entity meleetemp; - meleetemp = spawn(); - meleetemp.owner = meleetemp.realowner = self; - meleetemp.think = W_Shockwave_Melee_Think; - meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor(); - W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range)); -} - -// SHOCKWAVE ATTACK MODE -float W_Shockwave_Attack_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 = - ( - (WEP_CVAR(shockwave, blast_spread_min) * (1 - spreadlimit)) - + - (WEP_CVAR(shockwave, blast_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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; -} - -float W_Shockwave_Attack_CheckHit( - float queue, - entity head, - vector final_force, - float final_damage) -{ - if(!head) { return false; } - float i; - - for(i = 0; 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_Shockwave_Send(void) -{ - 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, w_shotdir.x); - WriteCoord(MSG_BROADCAST, w_shotdir.y); - WriteCoord(MSG_BROADCAST, w_shotdir.z); - WriteShort(MSG_BROADCAST, WEP_CVAR(shockwave, blast_distance)); - WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_max), 255)); - WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_min), 255)); - WriteByte(MSG_BROADCAST, num_for_edict(self)); -} - -void W_Shockwave_Attack(void) -{ - // declarations - float multiplier, multiplier_from_accuracy, multiplier_from_distance; - float final_damage; - vector final_force, center, vel; - entity head; - - float i, queue = 0; - - // set up the shot direction - W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage)); - vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_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 - W_Shockwave_Send(); - Damage_DamageInfo( - attack_hitpos, - WEP_CVAR(shockwave, blast_splash_damage), - WEP_CVAR(shockwave, blast_splash_edgedamage), - WEP_CVAR(shockwave, blast_splash_radius), - w_shotdir * WEP_CVAR(shockwave, blast_splash_force), - WEP_SHOCKWAVE.m_id, - 0, - self - ); - - // splash damage/jumping trace - head = WarpZone_FindRadius( - attack_hitpos, - max( - WEP_CVAR(shockwave, blast_splash_radius), - WEP_CVAR(shockwave, blast_jump_radius) - ), - false - ); - - while(head) - { - if(head.takedamage) - { - float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); - - if((head == self) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius))) - { - // ======================== - // BLAST JUMP CALCULATION - // ======================== - - // calculate importance of distance and accuracy for this attack - multiplier_from_accuracy = (1 - - (distance_to_head ? - min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius))) - : - 0 - ) - ); - multiplier_from_distance = (1 - - (distance_to_hit ? - min(1, (distance_to_hit / distance_to_end)) - : - 0 - ) - ); - multiplier = - max( - WEP_CVAR(shockwave, blast_jump_multiplier_min), - ( - (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy)) - + - (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance)) - ) - ); - - // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage - final_damage = - ( - (WEP_CVAR(shockwave, blast_jump_damage) * multiplier) - + - (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier)) - ); - - // figure out the direction of force - vel = normalize(combine_to_vector(head.velocity.x, head.velocity.y, 0)); - vel *= - ( - bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1) - * - WEP_CVAR(shockwave, blast_jump_force_velocitybias) - ); - final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel); - - // now multiply the direction by force units - final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier); - final_force.z *= WEP_CVAR(shockwave, blast_jump_force_zscale); - - // trigger damage with this calculated info - Damage( - head, - self, - self, - final_damage, - WEP_SHOCKWAVE.m_id, - head.origin, - final_force - ); - - #ifdef DEBUG_SHOCKWAVE - LOG_INFO(sprintf( - "SELF HIT: multiplier = %f, damage = %f, force = %f... " - "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", - multiplier, - final_damage, - vlen(final_force), - multiplier_from_accuracy, - multiplier_from_distance - )); - #endif - } - else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius)) - { - // ========================== - // BLAST SPLASH CALCULATION - // ========================== - - // calculate importance of distance and accuracy for this attack - multiplier_from_accuracy = (1 - - (distance_to_head ? - min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius))) - : - 0 - ) - ); - multiplier_from_distance = (1 - - (distance_to_hit ? - min(1, (distance_to_hit / distance_to_end)) - : - 0 - ) - ); - multiplier = - max( - WEP_CVAR(shockwave, blast_splash_multiplier_min), - ( - (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy)) - + - (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance)) - ) - ); - - // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage - final_damage = - ( - (WEP_CVAR(shockwave, blast_splash_damage) * multiplier) - + - (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier)) - ); - - // figure out the direction of force - final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias)); - final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force)); - //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); - - // now multiply the direction by force units - final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier); - final_force.z *= WEP_CVAR(shockwave, blast_force_zscale); - - // queue damage with this calculated info - if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); } - - #ifdef DEBUG_SHOCKWAVE - LOG_INFO(sprintf( - "SPLASH HIT: multiplier = %f, damage = %f, force = %f... " - "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", - multiplier, - final_damage, - vlen(final_force), - multiplier_from_accuracy, - multiplier_from_distance - )); - #endif - } - } - head = head.chain; - } - - // cone damage trace - head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), false); - while(head) - { - if((head != self) && head.takedamage) - { - // ======================== - // BLAST CONE CALCULATION - // ======================== - - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) - center = CENTER_OR_VIEWOFS(head); - - // find the closest point on the enemy to the center of the attack - 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); - a = h * (normalize(center - self.origin) * w_shotdir); - // WEAPONTODO: replace with simpler method - - vector nearest_on_line = (w_shotorg + a * w_shotdir); - vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); - - if((vlen(head.WarpZone_findradius_dist) <= WEP_CVAR(shockwave, blast_distance)) - && (W_Shockwave_Attack_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) - { - // calculate importance of distance and accuracy for this attack - multiplier_from_accuracy = (1 - - W_Shockwave_Attack_CheckSpread( - nearest_to_attacker, - nearest_on_line, - w_shotorg, - attack_endpos - ) - ); - multiplier_from_distance = (1 - - (distance_to_hit ? - min(1, (vlen(head.WarpZone_findradius_dist) / distance_to_end)) - : - 0 - ) - ); - multiplier = - max( - WEP_CVAR(shockwave, blast_multiplier_min), - ( - (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy)) - + - (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance)) - ) - ); - - // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage - final_damage = - ( - (WEP_CVAR(shockwave, blast_damage) * multiplier) - + - (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier)) - ); - - // figure out the direction of force - final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias)); - final_force = normalize(center - (nearest_on_line - final_force)); - //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); - - // now multiply the direction by force units - final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier); - final_force.z *= WEP_CVAR(shockwave, blast_force_zscale); - - // queue damage with this calculated info - if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); } - - #ifdef DEBUG_SHOCKWAVE - LOG_INFO(sprintf( - "BLAST HIT: multiplier = %f, damage = %f, force = %f... " - "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", - multiplier, - final_damage, - vlen(final_force), - multiplier_from_accuracy, - multiplier_from_distance - )); - #endif - } - } - head = head.chain; - } - - for(i = 1; i <= queue; ++i) - { - head = shockwave_hit[i-1]; - final_force = shockwave_hit_force[i-1]; - final_damage = shockwave_hit_damage[i-1]; - - Damage( - head, - self, - self, - final_damage, - WEP_SHOCKWAVE.m_id, - head.origin, - final_force - ); - - if(accuracy_isgooddamage(self.realowner, head)) - { - LOG_INFO("wtf\n"); - accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, final_damage); - } - - #ifdef DEBUG_SHOCKWAVE - LOG_INFO(sprintf( - "SHOCKWAVE by %s: damage = %f, force = %f.\n", - self.netname, - final_damage, - vlen(final_force) - )); - #endif - - shockwave_hit[i-1] = world; - shockwave_hit_force[i-1] = '0 0 0'; - shockwave_hit_damage[i-1] = 0; - } -} - -bool W_Shockwave(int req) -{ - switch(req) - { - case WR_AIM: - { - if(vlen(self.origin - self.enemy.origin) <= WEP_CVAR(shockwave, melee_range)) - { self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false); } - else - { self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false); } - - return true; - } - case WR_THINK: - { - if(self.BUTTON_ATCK) - { - if(time >= self.shockwave_blasttime) // handle refire separately so the secondary can be fired straight after a primary - { - if(weapon_prepareattack(0, WEP_CVAR(shockwave, blast_animtime))) - { - W_Shockwave_Attack(); - self.shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready); - } - } - } - else if(self.BUTTON_ATCK2) - { - //if(self.clip_load >= 0) // we are not currently reloading - if(!self.crouch) // no crouchmelee please - if(weapon_prepareattack(1, WEP_CVAR(shockwave, melee_refire))) - { - // attempt forcing playback of the anim by switching to another anim (that we never play) here... - weapon_thinkf(WFRAME_FIRE1, 0, W_Shockwave_Melee); - } - } - - return true; - } - case WR_INIT: - { - precache_model("models/uziflash.md3"); - precache_model(W_Model("g_shotgun.md3")); - precache_model(W_Model("v_shotgun.md3")); - precache_model(W_Model("h_shotgun.iqm")); - precache_sound("misc/itempickup.wav"); - precache_sound(W_Sound("lasergun_fire")); - precache_sound(W_Sound("shotgun_melee")); - SHOCKWAVE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_CHECKAMMO1: - case WR_CHECKAMMO2: - { - // shockwave has infinite ammo - return true; - } - case WR_CONFIG: - { - SHOCKWAVE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_THINKING_WITH_PORTALS; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_SHOCKWAVE_MURDER_SLAP; - else - return WEAPON_SHOCKWAVE_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -// WEAPONTODO: add client side settings for these -const float SW_MAXALPHA = 0.5; -const float SW_FADETIME = 0.4; -const float SW_DISTTOMIN = 200; -void Draw_Shockwave() -{ - // fading/removal control - float a = bound(0, (SW_MAXALPHA - ((time - self.sw_time) / SW_FADETIME)), SW_MAXALPHA); - if(a < ALPHA_MIN_VISIBLE) { remove(self); } - - // WEAPONTODO: save this only once when creating the entity - vector sw_color = getcsqcplayercolor(self.sv_entnum); // GetTeamRGB(GetPlayerColor(self.sv_entnum)); - - // WEAPONTODO: trace to find what we actually hit - vector endpos = (self.sw_shotorg + (self.sw_shotdir * self.sw_distance)); - - vectorvectors(self.sw_shotdir); - vector right = v_right; // save this for when we do makevectors later - vector up = v_up; // save this for when we do makevectors later - - // WEAPONTODO: combine and simplify these calculations - vector min_end = ((self.sw_shotorg + (self.sw_shotdir * SW_DISTTOMIN)) + (up * self.sw_spread_min)); - vector max_end = (endpos + (up * self.sw_spread_max)); - float spread_to_min = vlen(normalize(min_end - self.sw_shotorg) - self.sw_shotdir); - float spread_to_max = vlen(normalize(max_end - min_end) - self.sw_shotdir); - - vector first_min_end = '0 0 0', prev_min_end = '0 0 0', new_min_end = '0 0 0'; - vector first_max_end = '0 0 0', prev_max_end = '0 0 0', new_max_end = '0 0 0'; - float new_max_dist, new_min_dist; - - vector deviation, angle = '0 0 0'; - float counter, divisions = 20; - for(counter = 0; counter < divisions; ++counter) - { - // perfect circle effect lines - makevectors('0 360 0' * (0.75 + (counter - 0.5) / divisions)); - angle.y = v_forward.x; - angle.z = v_forward.y; - - // first do the spread_to_min effect - deviation = angle * spread_to_min; - deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z))); - new_min_dist = SW_DISTTOMIN; - new_min_end = (self.sw_shotorg + (deviation * new_min_dist)); - //te_lightning2(world, new_min_end, self.sw_shotorg); - - // then calculate spread_to_max effect - deviation = angle * spread_to_max; - deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z))); - new_max_dist = vlen(new_min_end - endpos); - new_max_end = (new_min_end + (deviation * new_max_dist)); - //te_lightning2(world, new_end, prev_min_end); - - - if(counter == 0) - { - first_min_end = new_min_end; - first_max_end = new_max_end; - } - - if(counter >= 1) - { - // draw from shot origin to min spread radius - R_BeginPolygon("", DRAWFLAG_NORMAL); - R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(new_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a); - R_EndPolygon(); - - // draw from min spread radius to max spread radius - R_BeginPolygon("", DRAWFLAG_NORMAL); - R_PolygonVertex(new_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a); - R_PolygonVertex(new_max_end, '0 0 0', sw_color, a); - R_EndPolygon(); - } - - prev_min_end = new_min_end; - prev_max_end = new_max_end; - - // last division only - if((counter + 1) == divisions) - { - // draw from shot origin to min spread radius - R_BeginPolygon("", DRAWFLAG_NORMAL); - R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(first_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a); - R_EndPolygon(); - - // draw from min spread radius to max spread radius - R_BeginPolygon("", DRAWFLAG_NORMAL); - R_PolygonVertex(first_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); - R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a); - R_PolygonVertex(first_max_end, '0 0 0', sw_color, a); - R_EndPolygon(); - } - } -} - -void Net_ReadShockwaveParticle(void) -{ - entity shockwave; - shockwave = spawn(); - shockwave.draw = Draw_Shockwave; - - shockwave.sw_shotorg_x = ReadCoord(); shockwave.sw_shotorg_y = ReadCoord(); shockwave.sw_shotorg_z = ReadCoord(); - shockwave.sw_shotdir_x = ReadCoord(); shockwave.sw_shotdir_y = ReadCoord(); shockwave.sw_shotdir_z = ReadCoord(); - - shockwave.sw_distance = ReadShort(); - shockwave.sw_spread_max = ReadByte(); - shockwave.sw_spread_min = ReadByte(); - - shockwave.sv_entnum = ReadByte(); - - shockwave.sw_time = time; -} - -bool W_Shockwave(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - // handled by Net_ReadShockwaveParticle - //vector org2; - //org2 = w_org + w_backoff * 2; - //pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); - return false; - } - case WR_INIT: - { - //precache_sound(W_Sound("ric1")); - //precache_sound(W_Sound("ric2")); - //precache_sound(W_Sound("ric3")); - return false; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_shotgun.qc b/qcsrc/common/weapons/w_shotgun.qc deleted file mode 100644 index 9024bbfa1f..0000000000 --- a/qcsrc/common/weapons/w_shotgun.qc +++ /dev/null @@ -1,392 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ SHOTGUN, -/* function */ W_Shotgun, -/* ammotype */ ammo_shells, -/* impulse */ 2, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_LOW, -/* color */ '0.5 0.25 0', -/* modelname */ "shotgun", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairshotgun 0.65", -/* wepimg */ "weaponshotgun", -/* refname */ "shotgun", -/* wepname */ _("Shotgun") -); - -#define SHOTGUN_SETTINGS(w_cvar,w_prop) SHOTGUN_SETTINGS_LIST(w_cvar, w_prop, SHOTGUN, shotgun) -#define SHOTGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, PRI, ammo) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, PRI, bullets) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, PRI, solidpenetration) \ - w_cvar(id, sn, PRI, spread) \ - w_cvar(id, sn, NONE, secondary) \ - w_cvar(id, sn, SEC, melee_time) \ - w_cvar(id, sn, SEC, melee_no_doubleslap) \ - w_cvar(id, sn, SEC, melee_traces) \ - w_cvar(id, sn, SEC, melee_swing_up) \ - w_cvar(id, sn, SEC, melee_swing_side) \ - w_cvar(id, sn, SEC, melee_nonplayerdamage) \ - w_cvar(id, sn, SEC, melee_multihit) \ - w_cvar(id, sn, SEC, melee_delay) \ - w_cvar(id, sn, SEC, melee_range) \ - w_cvar(id, sn, SEC, alt_animtime) \ - w_cvar(id, sn, SEC, alt_refire) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -SHOTGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_shotgun(void) { weapon_defaultspawnfunc(WEP_SHOTGUN.m_id); } - -void W_Shotgun_Attack(float isprimary) -{ - float sc; - entity flash; - - W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo)); - - W_SetupShot(self, true, 5, W_Sound("shotgun_fire"), ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets)); - for(sc = 0;sc < WEP_CVAR_PRI(shotgun, bullets);sc = sc + 1) - fireBullet(w_shotorg, w_shotdir, WEP_CVAR_PRI(shotgun, spread), WEP_CVAR_PRI(shotgun, solidpenetration), WEP_CVAR_PRI(shotgun, damage), WEP_CVAR_PRI(shotgun, force), WEP_SHOTGUN.m_id, 0); - - Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo)); - - // casing code - if(autocvar_g_casings >= 1) - for(sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);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 W_Shotgun_Melee_Think(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 = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor(); - swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); - f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces)); - - // check to see if we can still continue, otherwise give up now - if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_SEC(shotgun, 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 / WEP_CVAR_SEC(shotgun, melee_traces))) * 2 - 1); - - targpos = (self.realowner.origin + self.realowner.view_ofs - + (v_forward * WEP_CVAR_SEC(shotgun, melee_range)) - + (v_up * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_up)) - + (v_right * swing_factor * WEP_CVAR_SEC(shotgun, 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" || IS_MONSTER(trace_ent)); - - 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 || WEP_CVAR_SEC(shotgun, 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 = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1)); - else - swing_damage = (WEP_CVAR_SEC(shotgun, 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.m_id | HITTYPE_SECONDARY, - self.realowner.origin + self.realowner.view_ofs, - v_forward * WEP_CVAR_SEC(shotgun, force)); - - if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN.m_id, 0, swing_damage); } - - // draw large red flash for debugging - //te_customflash(targpos, 200, 2, '15 0 0'); - - if(WEP_CVAR_SEC(shotgun, 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, W_Sound("shotgun_melee"), VOL_BASE, ATTEN_NORM); - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready); - - entity meleetemp; - meleetemp = spawn(); - meleetemp.realowner = self; - meleetemp.think = W_Shotgun_Melee_Think; - meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor(); - W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range)); -} - -// alternate secondary weapon frames -void W_Shotgun_Attack3_Frame2() -{ - if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) - if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - sound(self, CH_WEAPON_SINGLE, "misc/null.wav", VOL_BASE, ATTN_NORM); // kill previous sound - W_Shotgun_Attack(true); // actually is secondary, but we trick the last shot into playing full reload sound - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready); -} -void W_Shotgun_Attack3_Frame1() -{ - if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) - if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - W_Shotgun_Attack(false); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2); -} - -.float shotgun_primarytime; - -float W_Shotgun(float req) -{ - float ammo_amount; - switch(req) - { - case WR_AIM: - { - if(vlen(self.origin-self.enemy.origin) <= WEP_CVAR_SEC(shotgun, melee_range)) - self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false); - else - self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false); - - return true; - } - case WR_THINK: - { - if(WEP_CVAR(shotgun, reload_ammo) && self.clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload - { - // don't force reload an empty shotgun if its melee attack is active - if(WEP_CVAR(shotgun, secondary) < 2) - WEP_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, WEP_CVAR_PRI(shotgun, animtime))) - { - W_Shotgun_Attack(true); - self.shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready); - } - } - } - else if(self.BUTTON_ATCK2 && WEP_CVAR(shotgun, secondary) == 2) - { - if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary - { - if(weapon_prepareattack(0, WEP_CVAR_SEC(shotgun, alt_animtime))) - { - W_Shotgun_Attack(false); - self.shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1); - } - } - } - } - if(self.clip_load >= 0) // we are not currently reloading - if(!self.crouch) // no crouchmelee please - if(WEP_CVAR(shotgun, secondary) == 1) - if((self.BUTTON_ATCK && self.WEP_AMMO(SHOTGUN) <= 0 && !(self.items & IT_UNLIMITED_WEAPON_AMMO)) || self.BUTTON_ATCK2) - if(weapon_prepareattack(1, WEP_CVAR_SEC(shotgun, 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); - } - - return true; - } - case WR_INIT: - { - precache_model("models/uziflash.md3"); - precache_model(W_Model("g_shotgun.md3")); - precache_model(W_Model("v_shotgun.md3")); - precache_model(W_Model("h_shotgun.iqm")); - precache_sound("misc/itempickup.wav"); - precache_sound(W_Sound("shotgun_fire")); - precache_sound(W_Sound("shotgun_melee")); - SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.ammo_field = ammo_none; - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo); - ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - if(IS_BOT_CLIENT(self)) - if(vlen(self.origin-self.enemy.origin) > WEP_CVAR_SEC(shotgun, melee_range)) - return false; // bots cannot use secondary out of range (fixes constant melee when out of ammo) - switch(WEP_CVAR(shotgun, secondary)) - { - case 1: return true; // melee does not use ammo - case 2: // secondary triple shot - { - ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo); - ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo); - return ammo_amount; - } - default: return false; // secondary unavailable - } - } - case WR_CONFIG: - { - SHOTGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RELOAD: - { - W_Reload(WEP_CVAR_PRI(shotgun, ammo), W_Sound("reload")); // WEAPONTODO - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_THINKING_WITH_PORTALS; - } - case WR_KILLMESSAGE: - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_SHOTGUN_MURDER_SLAP; - else - return WEAPON_SHOTGUN_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -.float prevric; -float W_Shotgun(float req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum(EFFECT_SHOTGUN_IMPACT), org2, w_backoff * 1000, 1); - if(!w_issilent && time - self.prevric > 0.25) - { - if(w_random < 0.0165) - sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM); - else if(w_random < 0.033) - sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM); - else if(w_random < 0.05) - sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM); - self.prevric = time; - } - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("ric1")); - precache_sound(W_Sound("ric2")); - precache_sound(W_Sound("ric3")); - return true; - } - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_tuba.qc b/qcsrc/common/weapons/w_tuba.qc deleted file mode 100644 index 686c89ec3a..0000000000 --- a/qcsrc/common/weapons/w_tuba.qc +++ /dev/null @@ -1,514 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ TUBA, -/* function */ W_Tuba, -/* ammotype */ ammo_none, -/* impulse */ 1, -/* flags */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* color */ '0 1 0', -/* modelname */ "tuba", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairtuba", -/* wepimg */ "weapontuba", -/* refname */ "tuba", -/* xgettext:no-c-format */ -/* wepname */ _("@!#%'n Tuba") -); - -#define TUBA_SETTINGS(w_cvar,w_prop) TUBA_SETTINGS_LIST(w_cvar, w_prop, TUBA, tuba) -#define TUBA_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, NONE, animtime) \ - w_cvar(id, sn, NONE, attenuation) \ - w_cvar(id, sn, NONE, damage) \ - w_cvar(id, sn, NONE, edgedamage) \ - w_cvar(id, sn, NONE, fadetime) \ - w_cvar(id, sn, NONE, force) \ - w_cvar(id, sn, NONE, pitchstep) \ - w_cvar(id, sn, NONE, radius) \ - w_cvar(id, sn, NONE, refire) \ - w_cvar(id, sn, NONE, volume) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -TUBA_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -float W_Tuba_MarkClientOnlyFieldsAsUsed() { - // These variables are only used by client/tuba.qc. TODO: move client/tuba.qc code here. - return WEP_CVAR(tuba, fadetime) + WEP_CVAR(tuba, pitchstep) + WEP_CVAR(tuba, volume); -} - -.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]; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_tuba(void) { weapon_defaultspawnfunc(WEP_TUBA.m_id); } - -bool W_Tuba_HasPlayed(entity pl, string melody, int instrument, bool 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[(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) - //printf("initial tempo rules: %f %f\n", mmin, mmax); - - for(i = 0; i < n; ++i) - { - vector vi = pl.(tuba_lastnotes[(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[(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 - //printf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti); - //printf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj); - //printf("m1 = %f\n", (vi_x - vj_y) / (ti - tj)); - //printf("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(void) -{ - // we have a note: - // on: self.spawnshieldtime - // off: time - // note: self.cnt - if(self.owner.tuba_note == self) - { - self.owner.tuba_lastnotes_last = (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); -} - -int W_Tuba_GetNote(entity pl, int hittype) -{ - float movestate = 5; - if (pl.movement.x < 0) movestate -= 3; - else if (pl.movement.x > 0) movestate += 3; - if (pl.movement.y < 0) movestate -= 1; - else if (pl.movement.y > 0) movestate += 1; - - int note = 0; - 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; -} - -bool W_Tuba_NoteSendEntity(entity to, int sf) -{ - int 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(void) -{ - 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 = WEP_CVAR(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, WEP_CVAR(tuba, damage)); - - n = W_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(!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 + WEP_CVAR(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, WEP_CVAR(tuba, damage), WEP_CVAR(tuba, edgedamage), WEP_CVAR(tuba, radius), world, world, WEP_CVAR(tuba, force), hittype | WEP_TUBA.m_id, world); - - o = gettaginfo(self.exteriorweaponentity, 0); - if(time > self.tuba_smoketime) - { - Send_Effect(EFFECT_SMOKE_RING, o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1); - self.tuba_smoketime = time + 0.25; - } -} - -bool W_Tuba(int req) -{ - switch(req) - { - case 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) < WEP_CVAR(tuba, radius)) - { - if(random() > 0.5) - self.BUTTON_ATCK = 1; - else - self.BUTTON_ATCK2 = 1; - } - - return true; - } - case WR_THINK: - { - if(self.BUTTON_ATCK) - if(weapon_prepareattack(0, WEP_CVAR(tuba, refire))) - { - W_Tuba_NoteOn(0); - //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready); - weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready); - } - if(self.BUTTON_ATCK2) - if(weapon_prepareattack(1, WEP_CVAR(tuba, refire))) - { - W_Tuba_NoteOn(HITTYPE_SECONDARY); - //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready); - weapon_thinkf(WFRAME_IDLE, WEP_CVAR(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; - } - } - - return true; - } - case WR_INIT: - { - precache_model(W_Model("g_tuba.md3")); - precache_model(W_Model("v_tuba.md3")); - precache_model(W_Model("h_tuba.iqm")); - precache_model(W_Model("v_akordeon.md3")); - precache_model(W_Model("h_akordeon.iqm")); - precache_model(W_Model("v_kleinbottle.md3")); - precache_model(W_Model("h_kleinbottle.iqm")); - TUBA_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.ammo_field = ammo_none; - self.tuba_instrument = 0; - return true; - } - case 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); - Send_Effect(EFFECT_TELEPORT, w_shotorg, '0 0 0', 1); - self.weaponentity.state = WS_INUSE; - weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready); - } - - return true; - } - case WR_CHECKAMMO1: - case WR_CHECKAMMO2: - { - return true; // tuba has infinite ammo - } - case WR_CONFIG: - { - TUBA_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case 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; - } - case 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 false; -} -#endif -#ifdef CSQC -bool W_Tuba(int req) -{ - // nothing to do here; particles of tuba are handled differently - // WEAPONTODO - - switch(req) - { - case WR_ZOOMRETICLE: - { - // no weapon specific image for this weapon - return false; - } - } - - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_vaporizer.qc b/qcsrc/common/weapons/w_vaporizer.qc deleted file mode 100644 index f61960594a..0000000000 --- a/qcsrc/common/weapons/w_vaporizer.qc +++ /dev/null @@ -1,306 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ VAPORIZER, -/* function */ W_Vaporizer, -/* ammotype */ ammo_cells, -/* impulse */ 7, -/* flags */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* color */ '0.5 1 1', -/* modelname */ "minstanex", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairminstanex 0.6", -/* wepimg */ "weaponminstanex", -/* refname */ "vaporizer", -/* wepname */ _("Vaporizer") -); - -#define VAPORIZER_SETTINGS(w_cvar,w_prop) VAPORIZER_SETTINGS_LIST(w_cvar, w_prop, VAPORIZER, vaporizer) -#define VAPORIZER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, PRI, ammo) \ - w_cvar(id, sn, PRI, animtime) \ - w_cvar(id, sn, PRI, refire) \ - w_cvar(id, sn, SEC, ammo) \ - w_cvar(id, sn, SEC, animtime) \ - w_cvar(id, sn, SEC, damage) \ - w_cvar(id, sn, SEC, delay) \ - w_cvar(id, sn, SEC, edgedamage) \ - w_cvar(id, sn, SEC, force) \ - w_cvar(id, sn, SEC, lifetime) \ - w_cvar(id, sn, SEC, radius) \ - w_cvar(id, sn, SEC, refire) \ - w_cvar(id, sn, SEC, shotangle) \ - w_cvar(id, sn, SEC, speed) \ - w_cvar(id, sn, SEC, spread) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -VAPORIZER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) -.float vaporizer_lasthit; -.float jump_interval; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_vaporizer(void) { weapon_defaultspawnfunc(WEP_VAPORIZER.m_id); } -void spawnfunc_weapon_minstanex(void) { spawnfunc_weapon_vaporizer(); } - -void W_Vaporizer_Attack(void) -{ - float flying; - flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last - - W_SetupShot(self, true, 0, "", CH_WEAPON_A, 10000); - // handle sound separately so we can change the volume - // added bonus: no longer plays the strength sound (strength gives no bonus to instakill anyway) - sound (self, CH_WEAPON_A, W_Sound("minstanexfire"), VOL_BASE * 0.8, ATTEN_NORM); - - yoda = 0; - damage_goodhits = 0; - FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, 10000, 800, 0, 0, 0, 0, WEP_VAPORIZER.m_id); - - if(yoda && flying) - Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); - if(damage_goodhits && self.vaporizer_lasthit) - { - Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE); - damage_goodhits = 0; // only every second time - } - - self.vaporizer_lasthit = damage_goodhits; - - Send_Effect(EFFECT_VORTEX_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) - Send_Effect(EFFECT_VAPORIZER_RED_HIT, w_shotorg, v, 1); - else - Send_Effect(EFFECT_VAPORIZER_RED, w_shotorg, v, 1); - break; - case NUM_TEAM_2: // Blue - if(damage_goodhits) - Send_Effect(EFFECT_VAPORIZER_BLUE_HIT, w_shotorg, v, 1); - else - Send_Effect(EFFECT_VAPORIZER_BLUE, w_shotorg, v, 1); - break; - case NUM_TEAM_3: // Yellow - if(damage_goodhits) - Send_Effect(EFFECT_VAPORIZER_YELLOW_HIT, w_shotorg, v, 1); - else - Send_Effect(EFFECT_VAPORIZER_YELLOW, w_shotorg, v, 1); - break; - case NUM_TEAM_4: // Pink - if(damage_goodhits) - Send_Effect(EFFECT_VAPORIZER_PINK_HIT, w_shotorg, v, 1); - else - Send_Effect(EFFECT_VAPORIZER_PINK, w_shotorg, v, 1); - break; - default: - if(damage_goodhits) - Send_Effect_("TE_TEI_G3_HIT", w_shotorg, v, 1); - else - Send_Effect_("TE_TEI_G3", w_shotorg, v, 1); - break; - } - - W_DecreaseAmmo(((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo))); -} - -float W_Vaporizer(float req) -{ - float ammo_amount; - float vaporizer_ammo; - - // now multiple WR_s use this - vaporizer_ammo = ((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo)); - - switch(req) - { - case WR_AIM: - { - if(self.WEP_AMMO(VAPORIZER) > 0) - self.BUTTON_ATCK = bot_aim(1000000, 0, 1, false); - else - self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(vaporizer, speed), 0, WEP_CVAR_SEC(vaporizer, lifetime), false); // WEAPONTODO: replace with proper vaporizer cvars - - return true; - } - case WR_THINK: - { - // if the laser uses load, we also consider its ammo for reloading - if(WEP_CVAR(vaporizer, reload_ammo) && WEP_CVAR_SEC(vaporizer, ammo) && self.clip_load < min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else if(WEP_CVAR(vaporizer, reload_ammo) && self.clip_load < vaporizer_ammo) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire))) - { - W_Vaporizer_Attack(); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, 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 instagib) - self.jump_interval = time + WEP_CVAR_SEC(vaporizer, refire) * W_WeaponRateFactor(); - - // decrease ammo for the laser? - if(WEP_CVAR_SEC(vaporizer, ammo)) - W_DecreaseAmmo(WEP_CVAR_SEC(vaporizer, ammo)); - - // ugly instagib hack to reuse the fire mode of the laser - int oldwep = self.weapon; // we can't avoid this hack - self.weapon = WEP_BLASTER.m_id; - W_Blaster_Attack( - WEP_BLASTER.m_id | HITTYPE_SECONDARY, - WEP_CVAR_SEC(vaporizer, shotangle), - WEP_CVAR_SEC(vaporizer, damage), - WEP_CVAR_SEC(vaporizer, edgedamage), - WEP_CVAR_SEC(vaporizer, radius), - WEP_CVAR_SEC(vaporizer, force), - WEP_CVAR_SEC(vaporizer, speed), - WEP_CVAR_SEC(vaporizer, spread), - WEP_CVAR_SEC(vaporizer, delay), - WEP_CVAR_SEC(vaporizer, lifetime) - ); - self.weapon = oldwep; - - // now do normal refire - weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(vaporizer, animtime), w_ready); - } - } - - return true; - } - case WR_INIT: - { - precache_model("models/nexflash.md3"); - precache_model(W_Model("g_minstanex.md3")); - precache_model(W_Model("v_minstanex.md3")); - precache_model(W_Model("h_minstanex.iqm")); - precache_sound(W_Sound("minstanexfire")); - precache_sound(W_Sound("nexwhoosh1")); - precache_sound(W_Sound("nexwhoosh2")); - precache_sound(W_Sound("nexwhoosh3")); - //W_Blaster(WR_INIT); // Samual: Is this really the proper thing to do? Didn't we already run this previously? - VAPORIZER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.ammo_field = WEP_AMMO(VAPORIZER); - self.vaporizer_lasthit = 0; - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(VAPORIZER) >= vaporizer_ammo; - ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= vaporizer_ammo; - return ammo_amount; - } - case WR_CHECKAMMO2: - { - if(!WEP_CVAR_SEC(vaporizer, ammo)) - return true; - ammo_amount = self.WEP_AMMO(VAPORIZER) >= WEP_CVAR_SEC(vaporizer, ammo); - ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo); - return ammo_amount; - } - case WR_CONFIG: - { - VAPORIZER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.vaporizer_lasthit = 0; - return true; - } - case WR_RELOAD: - { - float used_ammo; - if(WEP_CVAR_SEC(vaporizer, ammo)) - used_ammo = min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo)); - else - used_ammo = vaporizer_ammo; - - W_Reload(used_ammo, W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_THINKING_WITH_PORTALS; - } - case WR_KILLMESSAGE: - { - return WEAPON_VAPORIZER_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -float W_Vaporizer(float req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - if(w_deathtype & HITTYPE_SECONDARY) - { - pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); - if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); } - } - else - { - pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1); - if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM); } - } - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("laserimpact")); - precache_sound(W_Sound("neximpact")); - if(autocvar_cl_reticle && autocvar_cl_reticle_weapon) - { - precache_pic("gfx/reticle_nex"); - } - return true; - } - case WR_ZOOMRETICLE: - { - if(button_zoom || zoomscript_caught) - { - reticle_image = "gfx/reticle_nex"; - return true; - } - else - { - // no weapon specific image for this weapon - return false; - } - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/w_vortex.qc b/qcsrc/common/weapons/w_vortex.qc deleted file mode 100644 index 3aa17e526d..0000000000 --- a/qcsrc/common/weapons/w_vortex.qc +++ /dev/null @@ -1,362 +0,0 @@ -#ifndef IMPLEMENTATION -REGISTER_WEAPON( -/* WEP_##id */ VORTEX, -/* function */ W_Vortex, -/* ammotype */ ammo_cells, -/* impulse */ 7, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* color */ '0.5 1 1', -/* modelname */ "nex", -/* simplemdl */ "foobar", -/* crosshair */ "gfx/crosshairnex 0.65", -/* wepimg */ "weaponnex", -/* refname */ "vortex", -/* wepname */ _("Vortex") -); - -#define VORTEX_SETTINGS(w_cvar,w_prop) VORTEX_SETTINGS_LIST(w_cvar, w_prop, VORTEX, vortex) -#define VORTEX_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ - w_cvar(id, sn, BOTH, ammo) \ - w_cvar(id, sn, BOTH, animtime) \ - w_cvar(id, sn, BOTH, damage) \ - w_cvar(id, sn, BOTH, force) \ - w_cvar(id, sn, BOTH, damagefalloff_mindist) \ - w_cvar(id, sn, BOTH, damagefalloff_maxdist) \ - w_cvar(id, sn, BOTH, damagefalloff_halflife) \ - w_cvar(id, sn, BOTH, damagefalloff_forcehalflife) \ - w_cvar(id, sn, BOTH, refire) \ - w_cvar(id, sn, NONE, charge) \ - w_cvar(id, sn, NONE, charge_mindmg) \ - w_cvar(id, sn, NONE, charge_shot_multiplier) \ - w_cvar(id, sn, NONE, charge_animlimit) \ - w_cvar(id, sn, NONE, charge_limit) \ - w_cvar(id, sn, NONE, charge_rate) \ - w_cvar(id, sn, NONE, charge_rot_rate) \ - w_cvar(id, sn, NONE, charge_rot_pause) \ - w_cvar(id, sn, NONE, charge_start) \ - w_cvar(id, sn, NONE, charge_minspeed) \ - w_cvar(id, sn, NONE, charge_maxspeed) \ - w_cvar(id, sn, NONE, charge_velocity_rate) \ - w_cvar(id, sn, NONE, secondary) \ - w_cvar(id, sn, SEC, chargepool) \ - w_cvar(id, sn, SEC, chargepool_regen) \ - w_cvar(id, sn, SEC, chargepool_pause_regen) \ - w_prop(id, sn, float, reloading_ammo, reload_ammo) \ - w_prop(id, sn, float, reloading_time, reload_time) \ - w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ - w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ - w_prop(id, sn, string, weaponreplace, weaponreplace) \ - w_prop(id, sn, float, weaponstart, weaponstart) \ - w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ - w_prop(id, sn, float, weaponthrowable, weaponthrowable) - -#ifdef SVQC -VORTEX_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) - -.float vortex_lasthit; -#endif -#endif -#ifdef IMPLEMENTATION -#ifdef SVQC -void spawnfunc_weapon_vortex(void) { weapon_defaultspawnfunc(WEP_VORTEX.m_id); } -void spawnfunc_weapon_nex(void) { spawnfunc_weapon_vortex(); } - -void SendCSQCVortexBeamParticle(float charge) { - vector v; - v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_VORTEXBEAMPARTICLE); - 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_Vortex_Attack(float issecondary) -{ - float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge; - - mydmg = WEP_CVAR_BOTH(vortex, !issecondary, damage); - myforce = WEP_CVAR_BOTH(vortex, !issecondary, force); - mymindist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_mindist); - mymaxdist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_maxdist); - myhalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_halflife); - myforcehalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_forcehalflife); - myammo = WEP_CVAR_BOTH(vortex, !issecondary, ammo); - - float flying; - flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last - - if(WEP_CVAR(vortex, charge)) - { - charge = WEP_CVAR(vortex, charge_mindmg) / mydmg + (1 - WEP_CVAR(vortex, charge_mindmg) / mydmg) * self.vortex_charge; - self.vortex_charge *= WEP_CVAR(vortex, 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, W_Sound("nexfire"), CH_WEAPON_A, mydmg); - if(charge > WEP_CVAR(vortex, charge_animlimit) && WEP_CVAR(vortex, charge_animlimit)) // if the Vortex is overcharged, we play an extra sound - { - sound(self, CH_WEAPON_B, W_Sound("nexcharge"), VOL_BASE * (charge - 0.5 * WEP_CVAR(vortex, charge_animlimit)) / (1 - 0.5 * WEP_CVAR(vortex, charge_animlimit)), ATTN_NORM); - } - - yoda = 0; - damage_goodhits = 0; - FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_VORTEX.m_id); - - if(yoda && flying) - Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); - if(damage_goodhits && self.vortex_lasthit) - { - Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE); - damage_goodhits = 0; // only every second time - } - - self.vortex_lasthit = damage_goodhits; - - //beam and muzzle flash done on client - SendCSQCVortexBeamParticle(charge); - - W_DecreaseAmmo(myammo); -} - -void spawnfunc_weapon_vortex(void); // defined in t_items.qc - -.float vortex_chargepool_pauseregen_finished; -bool W_Vortex(int req) -{ - float dt; - float ammo_amount; - switch(req) - { - case WR_AIM: - { - if(bot_aim(1000000, 0, 1, false)) - self.BUTTON_ATCK = true; - else - { - if(WEP_CVAR(vortex, charge)) - self.BUTTON_ATCK2 = true; - } - return true; - } - case WR_THINK: - { - if(WEP_CVAR(vortex, charge) && self.vortex_charge < WEP_CVAR(vortex, charge_limit)) - self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_rate) * frametime / W_TICSPERFRAME); - - if(WEP_CVAR_SEC(vortex, chargepool)) - if(self.vortex_chargepool_ammo < 1) - { - if(self.vortex_chargepool_pauseregen_finished < time) - self.vortex_chargepool_ammo = min(1, self.vortex_chargepool_ammo + WEP_CVAR_SEC(vortex, chargepool_regen) * frametime / W_TICSPERFRAME); - self.pauseregen_finished = max(self.pauseregen_finished, time + WEP_CVAR_SEC(vortex, chargepool_pause_regen)); - } - - if(autocvar_g_balance_vortex_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo))) // forced reload - WEP_ACTION(self.weapon, WR_RELOAD); - else - { - if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, WEP_CVAR_PRI(vortex, refire))) - { - W_Vortex_Attack(0); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vortex, animtime), w_ready); - } - } - if((WEP_CVAR(vortex, charge) && !WEP_CVAR(vortex, secondary)) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2) - { - if(WEP_CVAR(vortex, charge)) - { - self.vortex_charge_rottime = time + WEP_CVAR(vortex, charge_rot_pause); - dt = frametime / W_TICSPERFRAME; - - if(self.vortex_charge < 1) - { - if(WEP_CVAR_SEC(vortex, chargepool)) - { - if(WEP_CVAR_SEC(vortex, ammo)) - { - // always deplete if secondary is held - self.vortex_chargepool_ammo = max(0, self.vortex_chargepool_ammo - WEP_CVAR_SEC(vortex, ammo) * dt); - - dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate)); - self.vortex_chargepool_pauseregen_finished = time + WEP_CVAR_SEC(vortex, chargepool_pause_regen); - dt = min(dt, self.vortex_chargepool_ammo); - dt = max(0, dt); - - self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate); - } - } - - else if(WEP_CVAR_SEC(vortex, ammo)) - { - if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed - { - dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate)); - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - { - // if this weapon is reloadable, decrease its load. Else decrease the player's ammo - if(autocvar_g_balance_vortex_reload_ammo) - { - dt = min(dt, (self.clip_load - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo)); - dt = max(0, dt); - if(dt > 0) - { - self.clip_load = max(WEP_CVAR_SEC(vortex, ammo), self.clip_load - WEP_CVAR_SEC(vortex, ammo) * dt); - } - self.(weapon_load[WEP_VORTEX.m_id]) = self.clip_load; - } - else - { - dt = min(dt, (self.WEP_AMMO(VORTEX) - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo)); - dt = max(0, dt); - if(dt > 0) - { - self.WEP_AMMO(VORTEX) = max(WEP_CVAR_SEC(vortex, ammo), self.WEP_AMMO(VORTEX) - WEP_CVAR_SEC(vortex, ammo) * dt); - } - } - } - self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate); - } - } - - else - { - dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate)); - self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate); - } - } - } - else if(WEP_CVAR(vortex, secondary)) - { - if(weapon_prepareattack(0, WEP_CVAR_SEC(vortex, refire))) - { - W_Vortex_Attack(1); - weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(vortex, animtime), w_ready); - } - } - } - } - - return true; - } - case WR_INIT: - { - precache_model("models/nexflash.md3"); - precache_model(W_Model("g_nex.md3")); - precache_model(W_Model("v_nex.md3")); - precache_model(W_Model("h_nex.iqm")); - precache_sound(W_Sound("nexfire")); - precache_sound(W_Sound("nexcharge")); - precache_sound(W_Sound("nexwhoosh1")); - precache_sound(W_Sound("nexwhoosh2")); - precache_sound(W_Sound("nexwhoosh3")); - VORTEX_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); - return true; - } - case WR_SETUP: - { - self.vortex_lasthit = 0; - return true; - } - case WR_CHECKAMMO1: - { - ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_PRI(vortex, ammo); - ammo_amount += (autocvar_g_balance_vortex_reload_ammo && self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_PRI(vortex, ammo)); - return ammo_amount; - } - case WR_CHECKAMMO2: - { - if(WEP_CVAR(vortex, secondary)) - { - // don't allow charging if we don't have enough ammo - ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_SEC(vortex, ammo); - ammo_amount += self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_SEC(vortex, ammo); - return ammo_amount; - } - else - { - return false; // zoom is not a fire mode - } - } - case WR_CONFIG: - { - VORTEX_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); - return true; - } - case WR_RESETPLAYER: - { - self.vortex_lasthit = 0; - return true; - } - case WR_RELOAD: - { - W_Reload(min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo)), W_Sound("reload")); - return true; - } - case WR_SUICIDEMESSAGE: - { - return WEAPON_THINKING_WITH_PORTALS; - } - case WR_KILLMESSAGE: - { - return WEAPON_VORTEX_MURDER; - } - } - return false; -} -#endif -#ifdef CSQC -float autocvar_g_balance_vortex_secondary = 0; // WEAPONTODO -bool W_Vortex(int req) -{ - switch(req) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM); - - return true; - } - case WR_INIT: - { - precache_sound(W_Sound("neximpact")); - if(autocvar_cl_reticle && autocvar_cl_reticle_weapon) - { - precache_pic("gfx/reticle_nex"); - } - return true; - } - case WR_ZOOMRETICLE: - { - if(button_zoom || zoomscript_caught || (!WEP_CVAR(vortex, secondary) && button_attack2)) - { - reticle_image = "gfx/reticle_nex"; - return true; - } - else - { - // no weapon specific image for this weapon - return false; - } - } - } - return false; -} -#endif -#endif diff --git a/qcsrc/common/weapons/weapon/arc.qc b/qcsrc/common/weapons/weapon/arc.qc new file mode 100644 index 0000000000..c101a8c8ab --- /dev/null +++ b/qcsrc/common/weapons/weapon/arc.qc @@ -0,0 +1,1546 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ ARC, +/* function */ W_Arc, +/* ammotype */ ammo_cells, +/* impulse */ 3, +/* flags */ WEP_FLAG_NORMAL, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '1 1 1', +/* modelname */ "arc", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairhlac 0.7", +/* wepimg */ "weaponarc", +/* refname */ "arc", +/* wepname */ _("Arc") +); + +#define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc) +#define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, beam_ammo) \ + w_cvar(id, sn, NONE, beam_animtime) \ + w_cvar(id, sn, NONE, beam_botaimspeed) \ + w_cvar(id, sn, NONE, beam_botaimlifetime) \ + w_cvar(id, sn, NONE, beam_damage) \ + w_cvar(id, sn, NONE, beam_degreespersegment) \ + w_cvar(id, sn, NONE, beam_distancepersegment) \ + w_cvar(id, sn, NONE, beam_falloff_halflifedist) \ + w_cvar(id, sn, NONE, beam_falloff_maxdist) \ + w_cvar(id, sn, NONE, beam_falloff_mindist) \ + w_cvar(id, sn, NONE, beam_force) \ + w_cvar(id, sn, NONE, beam_healing_amax) \ + w_cvar(id, sn, NONE, beam_healing_aps) \ + w_cvar(id, sn, NONE, beam_healing_hmax) \ + w_cvar(id, sn, NONE, beam_healing_hps) \ + w_cvar(id, sn, NONE, beam_maxangle) \ + w_cvar(id, sn, NONE, beam_nonplayerdamage) \ + w_cvar(id, sn, NONE, beam_range) \ + w_cvar(id, sn, NONE, beam_refire) \ + w_cvar(id, sn, NONE, beam_returnspeed) \ + w_cvar(id, sn, NONE, beam_tightness) \ + w_cvar(id, sn, NONE, burst_ammo) \ + w_cvar(id, sn, NONE, burst_damage) \ + w_cvar(id, sn, NONE, burst_healing_aps) \ + w_cvar(id, sn, NONE, burst_healing_hps) \ + w_cvar(id, sn, NONE, overheat_max)/* maximum heat before jamming */ \ + w_cvar(id, sn, NONE, overheat_min)/* minimum heat to wait for cooldown */ \ + w_cvar(id, sn, NONE, beam_heat) /* heat increase per second (primary) */ \ + w_cvar(id, sn, NONE, burst_heat) /* heat increase per second (secondary) */ \ + w_cvar(id, sn, NONE, cooldown) /* heat decrease per second when resting */ \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifndef MENUQC +const float ARC_MAX_SEGMENTS = 20; +vector arc_shotorigin[4]; +.vector beam_start; +.vector beam_dir; +.vector beam_wantdir; +.int beam_type; + +const int ARC_BT_MISS = 0x00; +const int ARC_BT_WALL = 0x01; +const int ARC_BT_HEAL = 0x02; +const int ARC_BT_HIT = 0x03; +const int ARC_BT_BURST_MISS = 0x10; +const int ARC_BT_BURST_WALL = 0x11; +const int ARC_BT_BURST_HEAL = 0x12; +const int ARC_BT_BURST_HIT = 0x13; +const int ARC_BT_BURSTMASK = 0x10; + +const int ARC_SF_SETTINGS = 1; +const int ARC_SF_START = 2; +const int ARC_SF_WANTDIR = 4; +const int ARC_SF_BEAMDIR = 8; +const int ARC_SF_BEAMTYPE = 16; +const int ARC_SF_LOCALMASK = 14; +#endif +#ifdef SVQC +ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.entity arc_beam; +.float arc_BUTTON_ATCK_prev; // for better animation control +.float beam_prev; +.float beam_initialized; +.float beam_bursting; +.float beam_teleporttime; +.float beam_heat; // (beam) amount of heat produced +.float arc_overheat; // (dropped arc/player) time during which it's too hot +.float arc_cooldown; // (dropped arc/player) cooling speed +.float arc_heat_percent; // (player) arc heat in [0,1] (stat) +.float arc_smoke_sound; +#endif +#ifdef CSQC +void Ent_ReadArcBeam(float isnew); + +.vector beam_color; +.float beam_alpha; +.float beam_thickness; +.float beam_traileffect; +.float beam_hiteffect; +.float beam_hitlight[4]; // 0: radius, 123: rgb +.float beam_muzzleeffect; +.float beam_muzzlelight[4]; // 0: radius, 123: rgb +.string beam_image; + +.entity beam_muzzleentity; + +.float beam_degreespersegment; +.float beam_distancepersegment; +.float beam_usevieworigin; +.float beam_initialized; +.float beam_maxangle; +.float beam_range; +.float beam_returnspeed; +.float beam_tightness; +.vector beam_shotorigin; + +entity Draw_ArcBeam_callback_entity; +float Draw_ArcBeam_callback_last_thickness; +vector Draw_ArcBeam_callback_last_top; // NOTE: in same coordinate system as player. +vector Draw_ArcBeam_callback_last_bottom; // NOTE: in same coordinate system as player. +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC.m_id); } + +float W_Arc_Beam_Send(entity to, int sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM); + + // Truncate information when this beam is displayed to the owner client + // - The owner client has no use for beam start position or directions, + // it always figures this information out for itself with csqc code. + // - Spectating the owner also truncates this information. + float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to))); + if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; } + + WriteByte(MSG_ENTITY, sf); + + if(sf & ARC_SF_SETTINGS) // settings information + { + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment)); + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment)); + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle)); + WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range)); + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed)); + WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10); + + WriteByte(MSG_ENTITY, drawlocal); + } + if(sf & ARC_SF_START) // starting location + { + WriteCoord(MSG_ENTITY, self.beam_start.x); + WriteCoord(MSG_ENTITY, self.beam_start.y); + WriteCoord(MSG_ENTITY, self.beam_start.z); + } + if(sf & ARC_SF_WANTDIR) // want/aim direction + { + WriteCoord(MSG_ENTITY, self.beam_wantdir.x); + WriteCoord(MSG_ENTITY, self.beam_wantdir.y); + WriteCoord(MSG_ENTITY, self.beam_wantdir.z); + } + if(sf & ARC_SF_BEAMDIR) // beam direction + { + WriteCoord(MSG_ENTITY, self.beam_dir.x); + WriteCoord(MSG_ENTITY, self.beam_dir.y); + WriteCoord(MSG_ENTITY, self.beam_dir.z); + } + if(sf & ARC_SF_BEAMTYPE) // beam type + { + WriteByte(MSG_ENTITY, self.beam_type); + } + + return true; +} + +void Reset_ArcBeam(entity player, vector forward) +{ + if (!player.arc_beam) { + return; + } + player.arc_beam.beam_dir = forward; + player.arc_beam.beam_teleporttime = time; +} + +float Arc_GetHeat_Percent(entity player) +{ + if ( WEP_CVAR(arc, overheat_max) <= 0 || WEP_CVAR(arc, overheat_max) <= 0 ) + { + player.arc_overheat = 0; + return 0; + } + + if ( player.arc_beam ) + return player.arc_beam.beam_heat/WEP_CVAR(arc, overheat_max); + + if ( player.arc_overheat > time ) + { + return (player.arc_overheat-time) / WEP_CVAR(arc, overheat_max) + * player.arc_cooldown; + } + + return 0; +} +void Arc_Player_SetHeat(entity player) +{ + player.arc_heat_percent = Arc_GetHeat_Percent(player); + //dprint("Heat: ",ftos(player.arc_heat_percent*100),"%\n"); +} + +void W_Arc_Beam_Think(void) +{ + if(self != self.owner.arc_beam) + { + remove(self); + return; + } + + + float burst = 0; + if( self.owner.BUTTON_ATCK2 || self.beam_bursting) + { + if(!self.beam_bursting) + self.beam_bursting = true; + burst = ARC_BT_BURSTMASK; + } + + if( + !IS_PLAYER(self.owner) + || + (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) + || + self.owner.deadflag != DEAD_NO + || + (!self.owner.BUTTON_ATCK && !burst ) + || + self.owner.frozen + || + self.owner.vehicle + || + (WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max)) + ) + { + if ( WEP_CVAR(arc, cooldown) > 0 ) + { + float cooldown_speed = 0; + if ( self.beam_heat > WEP_CVAR(arc, overheat_min) && WEP_CVAR(arc, cooldown) > 0 ) + { + cooldown_speed = WEP_CVAR(arc, cooldown); + } + else if ( !burst ) + { + cooldown_speed = self.beam_heat / WEP_CVAR(arc, beam_refire); + } + + if ( cooldown_speed ) + { + self.owner.arc_overheat = time + self.beam_heat / cooldown_speed; + self.owner.arc_cooldown = cooldown_speed; + } + + if ( WEP_CVAR(arc, overheat_max) > 0 && self.beam_heat >= WEP_CVAR(arc, overheat_max) ) + { + Send_Effect_("arc_overheat", + self.beam_start, self.beam_wantdir, 1 ); + sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM); + } + } + + if(self == self.owner.arc_beam) { self.owner.arc_beam = world; } + entity oldself = self; + self = self.owner; + if(!WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC.m_id, WR_CHECKAMMO2)) + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + // note: this doesn't force the switch + W_SwitchToOtherWeapon(self); + } + self = oldself; + remove(self); + return; + } + + // decrease ammo + float coefficient = frametime; + if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) + { + float rootammo; + if(burst) + { rootammo = WEP_CVAR(arc, burst_ammo); } + else + { rootammo = WEP_CVAR(arc, beam_ammo); } + + if(rootammo) + { + coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo); + self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime)); + } + } + float heat_speed = burst ? WEP_CVAR(arc, burst_heat) : WEP_CVAR(arc, beam_heat); + self.beam_heat = min( WEP_CVAR(arc, overheat_max), self.beam_heat + heat_speed*frametime ); + + makevectors(self.owner.v_angle); + + W_SetupShot_Range( + self.owner, + true, + 0, + "", + 0, + WEP_CVAR(arc, beam_damage) * coefficient, + WEP_CVAR(arc, beam_range) + ); + + // After teleport, "lock" the beam until the teleport is confirmed. + if (time < self.beam_teleporttime + ANTILAG_LATENCY(self.owner)) { + w_shotdir = self.beam_dir; + } + + // network information: shot origin and want/aim direction + if(self.beam_start != w_shotorg) + { + self.SendFlags |= ARC_SF_START; + self.beam_start = w_shotorg; + } + if(self.beam_wantdir != w_shotdir) + { + self.SendFlags |= ARC_SF_WANTDIR; + self.beam_wantdir = w_shotdir; + } + + if(!self.beam_initialized) + { + self.beam_dir = w_shotdir; + self.beam_initialized = true; + } + + // WEAPONTODO: Detect player velocity so that the beam curves when moving too + // idea: blend together self.beam_dir with the inverted direction the player is moving in + // might have to make some special accomodation so that it only uses view_right and view_up + + // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling + + float segments; + if(self.beam_dir != w_shotdir) + { + // calculate how much we're going to move the end of the beam to the want position + // WEAPONTODO (server and client): + // blendfactor never actually becomes 0 in this situation, which is a problem + // regarding precision... this means that self.beam_dir and w_shotdir approach + // eachother, however they never actually become the same value with this method. + // Perhaps we should do some form of rounding/snapping? + float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG; + if(angle && (angle > WEP_CVAR(arc, beam_maxangle))) + { + // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor + float blendfactor = bound( + 0, + (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), + min(WEP_CVAR(arc, beam_maxangle) / angle, 1) + ); + self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + else + { + // the radius is not too far yet, no worries :D + float blendfactor = bound( + 0, + (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), + 1 + ); + self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + + // network information: beam direction + self.SendFlags |= ARC_SF_BEAMDIR; + + // calculate how many segments are needed + float max_allowed_segments; + + if(WEP_CVAR(arc, beam_distancepersegment)) + { + max_allowed_segments = min( + ARC_MAX_SEGMENTS, + 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment))) + ); + } + else { max_allowed_segments = ARC_MAX_SEGMENTS; } + + if(WEP_CVAR(arc, beam_degreespersegment)) + { + segments = bound( + 1, + ( + min( + angle, + WEP_CVAR(arc, beam_maxangle) + ) + / + WEP_CVAR(arc, beam_degreespersegment) + ), + max_allowed_segments + ); + } + else { segments = 1; } + } + else { segments = 1; } + + vector beam_endpos = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range))); + vector beam_controlpoint = w_shotorg + w_shotdir * (WEP_CVAR(arc, beam_range) * (1 - WEP_CVAR(arc, beam_tightness))); + + float i; + float new_beam_type = 0; + vector last_origin = w_shotorg; + for(i = 1; i <= segments; ++i) + { + // WEAPONTODO (client): + // In order to do nice fading and pointing on the starting segment, we must always + // have that drawn as a separate triangle... However, that is difficult to do when + // keeping in mind the above problems and also optimizing the amount of segments + // drawn on screen at any given time. (Automatic beam quality scaling, essentially) + + vector new_origin = bezier_quadratic_getpoint( + w_shotorg, + beam_controlpoint, + beam_endpos, + i / segments); + vector new_dir = normalize(new_origin - last_origin); + + WarpZone_traceline_antilag( + self.owner, + last_origin, + new_origin, + MOVE_NORMAL, + self.owner, + ANTILAG_LATENCY(self.owner) + ); + + // Do all the transforms for warpzones right now, as we already + // "are" in the post-trace system (if we hit a player, that's + // always BEHIND the last passed wz). + last_origin = trace_endpos; + w_shotorg = WarpZone_TransformOrigin(WarpZone_trace_transform, w_shotorg); + beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); + beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); + new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir); + + float is_player = ( + IS_PLAYER(trace_ent) + || + trace_ent.classname == "body" + || + IS_MONSTER(trace_ent) + ); + + if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage))) + { + // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) + // NO. trace_endpos should be just fine. If not, + // that's an engine bug that needs proper debugging. + vector hitorigin = trace_endpos; + + float falloff = ExponentialFalloff( + WEP_CVAR(arc, beam_falloff_mindist), + WEP_CVAR(arc, beam_falloff_maxdist), + WEP_CVAR(arc, beam_falloff_halflifedist), + vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg) + ); + + if(is_player && SAME_TEAM(self.owner, trace_ent)) + { + float roothealth, rootarmor; + if(burst) + { + roothealth = WEP_CVAR(arc, burst_healing_hps); + rootarmor = WEP_CVAR(arc, burst_healing_aps); + } + else + { + roothealth = WEP_CVAR(arc, beam_healing_hps); + rootarmor = WEP_CVAR(arc, beam_healing_aps); + } + + if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth) + { + trace_ent.health = min( + trace_ent.health + (roothealth * coefficient), + WEP_CVAR(arc, beam_healing_hmax) + ); + } + if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor) + { + trace_ent.armorvalue = min( + trace_ent.armorvalue + (rootarmor * coefficient), + WEP_CVAR(arc, beam_healing_amax) + ); + } + + // stop rot, set visual effect + if(roothealth || rootarmor) + { + trace_ent.pauserothealth_finished = max( + trace_ent.pauserothealth_finished, + time + autocvar_g_balance_pause_health_rot + ); + trace_ent.pauserotarmor_finished = max( + trace_ent.pauserotarmor_finished, + time + autocvar_g_balance_pause_armor_rot + ); + new_beam_type = ARC_BT_HEAL; + } + } + else + { + float rootdamage; + if(is_player) + { + if(burst) + { rootdamage = WEP_CVAR(arc, burst_damage); } + else + { rootdamage = WEP_CVAR(arc, beam_damage); } + } + else + { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); } + + if(accuracy_isgooddamage(self.owner, trace_ent)) + { + accuracy_add( + self.owner, + WEP_ARC.m_id, + 0, + rootdamage * coefficient * falloff + ); + } + + Damage( + trace_ent, + self.owner, + self.owner, + rootdamage * coefficient * falloff, + WEP_ARC.m_id, + hitorigin, + WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff + ); + + new_beam_type = ARC_BT_HIT; + } + break; + } + else if(trace_fraction != 1) + { + // we collided with geometry + new_beam_type = ARC_BT_WALL; + break; + } + } + + // te_explosion(trace_endpos); + + // if we're bursting, use burst visual effects + new_beam_type |= burst; + + // network information: beam type + if(new_beam_type != self.beam_type) + { + self.SendFlags |= ARC_SF_BEAMTYPE; + self.beam_type = new_beam_type; + } + + self.owner.beam_prev = time; + self.nextthink = time; +} + +void W_Arc_Beam(float burst) +{ + + // only play fire sound if 1 sec has passed since player let go the fire button + if(time - self.beam_prev > 1) + sound(self, CH_WEAPON_A, W_Sound("arc_fire"), VOL_BASE, ATTN_NORM); + + entity beam = self.arc_beam = spawn(); + beam.classname = "W_Arc_Beam"; + beam.solid = SOLID_NOT; + beam.think = W_Arc_Beam_Think; + beam.owner = self; + beam.movetype = MOVETYPE_NONE; + beam.bot_dodge = true; + beam.bot_dodgerating = WEP_CVAR(arc, beam_damage); + beam.beam_bursting = burst; + Net_LinkEntity(beam, false, 0, W_Arc_Beam_Send); + + entity oldself = self; + self = beam; + self.think(); + self = oldself; +} + +void Arc_Smoke() +{ + makevectors(self.v_angle); + W_SetupShot_Range(self,true,0,"",0,0,0); + + vector smoke_origin = w_shotorg + self.velocity*frametime; + if ( self.arc_overheat > time ) + { + if ( random() < self.arc_heat_percent ) + Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 ); + if ( self.BUTTON_ATCK || self.BUTTON_ATCK2 ) + { + Send_Effect_("arc_overheat_fire", smoke_origin, w_shotdir, 1 ); + if ( !self.arc_smoke_sound ) + { + self.arc_smoke_sound = 1; + sound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop_overheat"), VOL_BASE, ATTN_NORM); + } + } + } + else if ( self.arc_beam && WEP_CVAR(arc, overheat_max) > 0 && + self.arc_beam.beam_heat > WEP_CVAR(arc, overheat_min) ) + { + if ( random() < (self.arc_beam.beam_heat-WEP_CVAR(arc, overheat_min)) / + ( WEP_CVAR(arc, overheat_max)-WEP_CVAR(arc, overheat_min) ) ) + Send_Effect_("arc_smoke", smoke_origin, '0 0 0', 1 ); + } + + if ( self.arc_smoke_sound && ( self.arc_overheat <= time || + !( self.BUTTON_ATCK || self.BUTTON_ATCK2 ) ) || self.switchweapon != WEP_ARC.m_id ) + { + self.arc_smoke_sound = 0; + sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); + } +} + +bool W_Arc(int req) +{ + switch(req) + { + case WR_AIM: + { + if(WEP_CVAR(arc, beam_botaimspeed)) + { + self.BUTTON_ATCK = bot_aim( + WEP_CVAR(arc, beam_botaimspeed), + 0, + WEP_CVAR(arc, beam_botaimlifetime), + false + ); + } + else + { + self.BUTTON_ATCK = bot_aim( + 1000000, + 0, + 0.001, + false + ); + } + return true; + } + case WR_THINK: + { + Arc_Player_SetHeat(self); + Arc_Smoke(); + + if ( self.arc_overheat <= time ) + if(self.BUTTON_ATCK || self.BUTTON_ATCK2 || self.arc_beam.beam_bursting ) + { + + if(self.arc_BUTTON_ATCK_prev) + { + #if 0 + if(self.animstate_startframe == self.anim_shoot.x && self.animstate_numframes == self.anim_shoot.y) + weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready); + else + #endif + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); + } + + if((!self.arc_beam) || wasfreed(self.arc_beam)) + { + if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0)) + { + W_Arc_Beam(!!self.BUTTON_ATCK2); + + if(!self.arc_BUTTON_ATCK_prev) + { + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); + self.arc_BUTTON_ATCK_prev = 1; + } + } + } + + return true; + } + + if(self.arc_BUTTON_ATCK_prev != 0) + { + sound(self, CH_WEAPON_A, W_Sound("arc_stop"), VOL_BASE, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); + ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(); + } + self.arc_BUTTON_ATCK_prev = 0; + + #if 0 + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire)) + { + W_Arc_Attack2(); + self.arc_count = autocvar_g_balance_arc_secondary_count; + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack); + self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor(); + } + #endif + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_arc.md3")); + precache_model(W_Model("v_arc.md3")); + precache_model(W_Model("h_arc.iqm")); + precache_sound(W_Sound("arc_fire")); + precache_sound(W_Sound("arc_loop")); + precache_sound(W_Sound("arc_stop")); + precache_sound(W_Sound("arc_loop_overheat")); + if(!arc_shotorigin[0]) + { + arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 1); + arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 2); + arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 3); + arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC.m_id), false, false, 4); + } + ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0)); + } + case WR_CHECKAMMO2: + { + return WEP_CVAR(arc, overheat_max) > 0 && + ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0)); + } + case WR_CONFIG: + { + ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_KILLMESSAGE: + { + return WEAPON_ARC_MURDER; + } + case WR_DROP: + { + weapon_dropevent_item.arc_overheat = self.arc_overheat; + weapon_dropevent_item.arc_cooldown = self.arc_cooldown; + self.arc_overheat = 0; + self.arc_cooldown = 0; + return true; + } + case WR_PICKUP: + { + if ( !client_hasweapon(self, WEP_ARC.m_id, false, false) && + weapon_dropevent_item.arc_overheat > time ) + { + self.arc_overheat = weapon_dropevent_item.arc_overheat; + self.arc_cooldown = weapon_dropevent_item.arc_cooldown; + } + return true; + } + } + return false; +} +#endif +#ifdef CSQC +void Draw_ArcBeam_callback(vector start, vector hit, vector end) +{ + entity beam = Draw_ArcBeam_callback_entity; + vector transformed_view_org; + transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin); + + // Thickdir shall be perpendicular to the beam and to the view-to-beam direction (WEAPONTODO: WHY) + // WEAPONTODO: Wouldn't it be better to be perpendicular to the beam and to the view FORWARD direction? + vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start)); + + vector hitorigin; + + // draw segment + #if 0 + if(trace_fraction != 1) + { + // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) + hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction); + hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin); + } + else + { + hitorigin = hit; + } + #else + hitorigin = hit; + #endif + + // decide upon thickness + float thickness = beam.beam_thickness; + + // draw primary beam render + vector top = hitorigin + (thickdir * thickness); + vector bottom = hitorigin - (thickdir * thickness); + + vector last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); + vector last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); + + R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE + R_PolygonVertex( + top, + '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_PolygonVertex( + last_top, + '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_PolygonVertex( + last_bottom, + '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_PolygonVertex( + bottom, + '0 0.5 0' * (1 - (thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_EndPolygon(); + + // draw trailing particles + // NOTES: + // - Don't use spammy particle counts here, use a FEW small particles around the beam + // - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves. + if(beam.beam_traileffect) + { + trailparticles(beam, beam.beam_traileffect, start, hitorigin); + } + + // set up for the next + Draw_ArcBeam_callback_last_thickness = thickness; + Draw_ArcBeam_callback_last_top = WarpZone_UnTransformOrigin(WarpZone_trace_transform, top); + Draw_ArcBeam_callback_last_bottom = WarpZone_UnTransformOrigin(WarpZone_trace_transform, bottom); +} + +void Reset_ArcBeam(void) +{ + entity e; + for (e = world; (e = findfloat(e, beam_usevieworigin, 1)); ) { + e.beam_initialized = false; + } + for (e = world; (e = findfloat(e, beam_usevieworigin, 2)); ) { + e.beam_initialized = false; + } +} + +void Draw_ArcBeam(void) +{ + float dt = time - self.move_time; + self.move_time = time; + if(dt <= 0) { return; } + + if(!self.beam_usevieworigin) + { + InterpolateOrigin_Do(); + } + + // origin = beam starting origin + // v_angle = wanted/aim direction + // angles = current direction of beam + + vector start_pos; + vector wantdir; //= view_forward; + vector beamdir; //= self.beam_dir; + + float segments; + if(self.beam_usevieworigin) + { + // WEAPONTODO: + // Currently we have to replicate nearly the same method of figuring + // out the shotdir that the server does... Ideally in the future we + // should be able to acquire this from a generalized function built + // into a weapon system for client code. + + // find where we are aiming + makevectors(warpzone_save_view_angles); + vector forward = v_forward; + vector right = v_right; + vector up = v_up; + + // decide upon start position + if(self.beam_usevieworigin == 2) + { start_pos = warpzone_save_view_origin; } + else + { start_pos = self.origin; } + + // trace forward with an estimation + WarpZone_TraceLine( + start_pos, + start_pos + forward * self.beam_range, + MOVE_NOMONSTERS, + self + ); + + // untransform in case our trace went through a warpzone + vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + + // un-adjust trueaim if shotend is too close + if(vlen(end_pos - start_pos) < g_trueaim_minrange) + end_pos = start_pos + (forward * g_trueaim_minrange); + + // move shot origin to the actual gun muzzle origin + vector origin_offset = + right * -self.beam_shotorigin.y + + up * self.beam_shotorigin.z; + + start_pos = start_pos + origin_offset; + + // Move it also forward, but only as far as possible without hitting anything. Don't poke into walls! + traceline(start_pos, start_pos + forward * self.beam_shotorigin.x, MOVE_NORMAL, self); + start_pos = trace_endpos; + + // calculate the aim direction now + wantdir = normalize(end_pos - start_pos); + + if(!self.beam_initialized) + { + self.beam_dir = wantdir; + self.beam_initialized = true; + } + + if(self.beam_dir != wantdir) + { + // calculate how much we're going to move the end of the beam to the want position + // WEAPONTODO (server and client): + // blendfactor never actually becomes 0 in this situation, which is a problem + // regarding precision... this means that self.beam_dir and w_shotdir approach + // eachother, however they never actually become the same value with this method. + // Perhaps we should do some form of rounding/snapping? + float angle = vlen(wantdir - self.beam_dir) * RAD2DEG; + if(angle && (angle > self.beam_maxangle)) + { + // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor + float blendfactor = bound( + 0, + (1 - (self.beam_returnspeed * frametime)), + min(self.beam_maxangle / angle, 1) + ); + self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + else + { + // the radius is not too far yet, no worries :D + float blendfactor = bound( + 0, + (1 - (self.beam_returnspeed * frametime)), + 1 + ); + self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + + // calculate how many segments are needed + float max_allowed_segments; + + if(self.beam_distancepersegment) + { + max_allowed_segments = min( + ARC_MAX_SEGMENTS, + 1 + (vlen(wantdir / self.beam_distancepersegment)) + ); + } + else { max_allowed_segments = ARC_MAX_SEGMENTS; } + + if(self.beam_degreespersegment) + { + segments = bound( + 1, + ( + min( + angle, + self.beam_maxangle + ) + / + self.beam_degreespersegment + ), + max_allowed_segments + ); + } + else { segments = 1; } + } + else { segments = 1; } + + // set the beam direction which the rest of the code will refer to + beamdir = self.beam_dir; + + // finally, set self.angles to the proper direction so that muzzle attachment points in proper direction + self.angles = fixedvectoangles2(forward, up); // TODO(Samual): is this == warpzone_save_view_angles? + } + else + { + // set the values from the provided info from the networked entity + start_pos = self.origin; + wantdir = self.v_angle; + beamdir = self.angles; + + if(beamdir != wantdir) + { + float angle = vlen(wantdir - beamdir) * RAD2DEG; + + // calculate how many segments are needed + float max_allowed_segments; + + if(self.beam_distancepersegment) + { + max_allowed_segments = min( + ARC_MAX_SEGMENTS, + 1 + (vlen(wantdir / self.beam_distancepersegment)) + ); + } + else { max_allowed_segments = ARC_MAX_SEGMENTS; } + + if(self.beam_degreespersegment) + { + segments = bound( + 1, + ( + min( + angle, + self.beam_maxangle + ) + / + self.beam_degreespersegment + ), + max_allowed_segments + ); + } + else { segments = 1; } + } + else { segments = 1; } + } + + setorigin(self, start_pos); + self.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead? + + vector beam_endpos = (start_pos + (beamdir * self.beam_range)); + vector beam_controlpoint = start_pos + wantdir * (self.beam_range * (1 - self.beam_tightness)); + + Draw_ArcBeam_callback_entity = self; + Draw_ArcBeam_callback_last_thickness = 0; + Draw_ArcBeam_callback_last_top = start_pos; + Draw_ArcBeam_callback_last_bottom = start_pos; + + vector last_origin = start_pos; + vector original_start_pos = start_pos; + + float i; + for(i = 1; i <= segments; ++i) + { + // WEAPONTODO (client): + // In order to do nice fading and pointing on the starting segment, we must always + // have that drawn as a separate triangle... However, that is difficult to do when + // keeping in mind the above problems and also optimizing the amount of segments + // drawn on screen at any given time. (Automatic beam quality scaling, essentially) + + vector new_origin = bezier_quadratic_getpoint( + start_pos, + beam_controlpoint, + beam_endpos, + i / segments); + + WarpZone_TraceBox_ThroughZone( + last_origin, + '0 0 0', + '0 0 0', + new_origin, + MOVE_NORMAL, + world, + world, + Draw_ArcBeam_callback + ); + + // Do all the transforms for warpzones right now, as we already "are" in the post-trace + // system (if we hit a player, that's always BEHIND the last passed wz). + last_origin = trace_endpos; + start_pos = WarpZone_TransformOrigin(WarpZone_trace_transform, start_pos); + beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); + beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); + beamdir = WarpZone_TransformVelocity(WarpZone_trace_transform, beamdir); + Draw_ArcBeam_callback_last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); + Draw_ArcBeam_callback_last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); + + if(trace_fraction < 1) { break; } + } + + // visual effects for startpoint and endpoint + if(self.beam_hiteffect) + { + // FIXME we really should do this on the server so it actually + // matches gameplay. What this client side stuff is doing is no + // more than guesswork. + if((trace_ent || trace_fraction < 1) && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)) + pointparticles( + self.beam_hiteffect, + last_origin, + beamdir * -1, + frametime * 2 + ); + } + if(self.beam_hitlight[0]) + { + adddynamiclight( + last_origin, + self.beam_hitlight[0], + vec3( + self.beam_hitlight[1], + self.beam_hitlight[2], + self.beam_hitlight[3] + ) + ); + } + if(self.beam_muzzleeffect) + { + pointparticles( + self.beam_muzzleeffect, + original_start_pos + wantdir * 20, + wantdir * 1000, + frametime * 0.1 + ); + } + if(self.beam_muzzlelight[0]) + { + adddynamiclight( + original_start_pos + wantdir * 20, + self.beam_muzzlelight[0], + vec3( + self.beam_muzzlelight[1], + self.beam_muzzlelight[2], + self.beam_muzzlelight[3] + ) + ); + } + + // cleanup + Draw_ArcBeam_callback_entity = world; + Draw_ArcBeam_callback_last_thickness = 0; + Draw_ArcBeam_callback_last_top = '0 0 0'; + Draw_ArcBeam_callback_last_bottom = '0 0 0'; +} + +void Remove_ArcBeam(void) +{ + remove(self.beam_muzzleentity); + sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); +} + +void Ent_ReadArcBeam(float isnew) +{ + int sf = ReadByte(); + entity flash; + + if(isnew) + { + // calculate shot origin offset from gun alignment + int gunalign = autocvar_cl_gunalign; + if(gunalign != 1 && gunalign != 2 && gunalign != 4) + gunalign = 3; // default value + --gunalign; + + self.beam_shotorigin = arc_shotorigin[gunalign]; + + // set other main attributes of the beam + self.draw = Draw_ArcBeam; + self.entremove = Remove_ArcBeam; + self.move_time = time; + loopsound(self, CH_SHOTS_SINGLE, W_Sound("arc_loop"), VOL_BASE, ATTEN_NORM); + + flash = spawn(); + flash.owner = self; + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT; + flash.drawmask = MASK_NORMAL; + flash.solid = SOLID_NOT; + flash.avelocity_z = 5000; + setattachment(flash, self, ""); + setorigin(flash, '0 0 0'); + + self.beam_muzzleentity = flash; + } + else + { + flash = self.beam_muzzleentity; + } + + if(sf & ARC_SF_SETTINGS) // settings information + { + self.beam_degreespersegment = ReadShort(); + self.beam_distancepersegment = ReadShort(); + self.beam_maxangle = ReadShort(); + self.beam_range = ReadCoord(); + self.beam_returnspeed = ReadShort(); + self.beam_tightness = (ReadByte() / 10); + + if(ReadByte()) + { + if(autocvar_chase_active) + { self.beam_usevieworigin = 1; } + else // use view origin + { self.beam_usevieworigin = 2; } + } + else + { + self.beam_usevieworigin = 0; + } + } + + if(!self.beam_usevieworigin) + { + // self.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work? + self.iflags = IFLAG_ORIGIN; + + InterpolateOrigin_Undo(); + } + + if(sf & ARC_SF_START) // starting location + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + } + else if(self.beam_usevieworigin) // infer the location from player location + { + if(self.beam_usevieworigin == 2) + { + // use view origin + self.origin = view_origin; + } + else + { + // use player origin so that third person display still works + self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT)); + } + } + + setorigin(self, self.origin); + + if(sf & ARC_SF_WANTDIR) // want/aim direction + { + self.v_angle_x = ReadCoord(); + self.v_angle_y = ReadCoord(); + self.v_angle_z = ReadCoord(); + } + + if(sf & ARC_SF_BEAMDIR) // beam direction + { + self.angles_x = ReadCoord(); + self.angles_y = ReadCoord(); + self.angles_z = ReadCoord(); + } + + if(sf & ARC_SF_BEAMTYPE) // beam type + { + self.beam_type = ReadByte(); + switch(self.beam_type) + { + case ARC_BT_MISS: + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; // particleeffectnum(EFFECT_GRENADE_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_HEAL: + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_HIT: + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); + self.beam_hitlight[0] = 20; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 0; + self.beam_hitlight[3] = 0; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 50; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 0; + self.beam_muzzlelight[3] = 0; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_BURST_MISS: + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_BURST_WALL: + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_BURST_HEAL: + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_BEAM_HEAL_IMPACT2); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_BURST_HIT: + { + self.beam_color = '1 1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = particleeffectnum(EFFECT_ARC_BEAM); + self.beam_hiteffect = particleeffectnum(EFFECT_ARC_LIGHTNING); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + + // shouldn't be possible, but lets make it colorful if it does :D + default: + { + self.beam_color = randomvec(); + self.beam_alpha = 1; + self.beam_thickness = 8; + self.beam_traileffect = false; + self.beam_hiteffect = false; + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum(EFFECT_VORTEX_MUZZLEFLASH); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + } + } + + if(!self.beam_usevieworigin) + { + InterpolateOrigin_Note(); + } +} + +bool W_Arc(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + // todo + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("arc_loop")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/blaster.qc b/qcsrc/common/weapons/weapon/blaster.qc new file mode 100644 index 0000000000..6234f4a3e1 --- /dev/null +++ b/qcsrc/common/weapons/weapon/blaster.qc @@ -0,0 +1,297 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ BLASTER, +/* function */ W_Blaster, +/* ammotype */ ammo_none, +/* impulse */ 1, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ 0, +/* color */ '1 0.5 0.5', +/* modelname */ "laser", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairlaser 0.5", +/* wepimg */ "weaponlaser", +/* refname */ "blaster", +/* wepname */ _("Blaster") +); + +#define BLASTER_SETTINGS(w_cvar,w_prop) BLASTER_SETTINGS_LIST(w_cvar, w_prop, BLASTER, blaster) +#define BLASTER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, delay) \ + w_cvar(id, sn, BOTH, edgedamage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, force_zscale) \ + w_cvar(id, sn, BOTH, lifetime) \ + w_cvar(id, sn, BOTH, radius) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, shotangle) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, BOTH, spread) \ + w_cvar(id, sn, NONE, secondary) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +BLASTER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float blaster_damage; +.float blaster_edgedamage; +.float blaster_radius; +.float blaster_force; +.float blaster_lifetime; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_blaster(void) { weapon_defaultspawnfunc(WEP_BLASTER.m_id); } +void spawnfunc_weapon_laser(void) { spawnfunc_weapon_blaster(); } + +void W_Blaster_Touch(void) +{ + PROJECTILE_TOUCH; + + self.event_damage = func_null; + + RadiusDamage( + self, + self.realowner, + self.blaster_damage, + self.blaster_edgedamage, + self.blaster_radius, + world, + world, + self.blaster_force, + self.projectiledeathtype, + other + ); + + remove(self); +} + +void W_Blaster_Think(void) +{ + self.movetype = MOVETYPE_FLY; + self.think = SUB_Remove; + self.nextthink = time + self.blaster_lifetime; + CSQCProjectile(self, true, PROJECTILE_BLASTER, true); +} + +void W_Blaster_Attack( + float atk_deathtype, + float atk_shotangle, + float atk_damage, + float atk_edgedamage, + float atk_radius, + float atk_force, + float atk_speed, + float atk_spread, + float atk_delay, + float atk_lifetime) +{ + vector s_forward = v_forward * cos(atk_shotangle * DEG2RAD) + v_up * sin(atk_shotangle * DEG2RAD); + + W_SetupShot_Dir(self, s_forward, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, atk_damage); + Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + entity missile = spawn(); + missile.owner = missile.realowner = self; + missile.classname = "blasterbolt"; + missile.bot_dodge = true; + missile.bot_dodgerating = atk_damage; + PROJECTILE_MAKETRIGGER(missile); + + missile.blaster_damage = atk_damage; + missile.blaster_edgedamage = atk_edgedamage; + missile.blaster_radius = atk_radius; + missile.blaster_force = atk_force; + missile.blaster_lifetime = atk_lifetime; + + setorigin(missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + W_SetupProjVelocity_Explicit( + missile, + w_shotdir, + v_up, + atk_speed, + 0, + 0, + atk_spread, + false + ); + + missile.angles = vectoangles(missile.velocity); + + //missile.glow_color = 250; // 244, 250 + //missile.glow_size = 120; + + missile.touch = W_Blaster_Touch; + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + missile.projectiledeathtype = atk_deathtype; + missile.think = W_Blaster_Think; + missile.nextthink = time + atk_delay; + + MUTATOR_CALLHOOK(EditProjectile, self, missile); + + if(time >= missile.nextthink) + { + entity oldself; + oldself = self; + self = missile; + self.think(); + self = oldself; + } +} +bool W_Blaster(int request) +{ + switch(request) + { + case WR_AIM: + { + if(WEP_CVAR(blaster, secondary)) + { + if((random() * (WEP_CVAR_PRI(blaster, damage) + WEP_CVAR_SEC(blaster, damage))) > WEP_CVAR_PRI(blaster, damage)) + { self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(blaster, speed), 0, WEP_CVAR_SEC(blaster, lifetime), false); } + else + { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); } + } + else + { self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(blaster, speed), 0, WEP_CVAR_PRI(blaster, lifetime), false); } + + return true; + } + + case WR_THINK: + { + if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(blaster, refire))) + { + W_Blaster_Attack( + WEP_BLASTER.m_id, + WEP_CVAR_PRI(blaster, shotangle), + WEP_CVAR_PRI(blaster, damage), + WEP_CVAR_PRI(blaster, edgedamage), + WEP_CVAR_PRI(blaster, radius), + WEP_CVAR_PRI(blaster, force), + WEP_CVAR_PRI(blaster, speed), + WEP_CVAR_PRI(blaster, spread), + WEP_CVAR_PRI(blaster, delay), + WEP_CVAR_PRI(blaster, lifetime) + ); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(blaster, animtime), w_ready); + } + } + else if(self.BUTTON_ATCK2) + { + switch(WEP_CVAR(blaster, secondary)) + { + case 0: // switch to last used weapon + { + if(self.switchweapon == WEP_BLASTER.m_id) // don't do this if already switching + W_LastWeapon(); + break; + } + + case 1: // normal projectile secondary + { + if(weapon_prepareattack(1, WEP_CVAR_SEC(blaster, refire))) + { + W_Blaster_Attack( + WEP_BLASTER.m_id | HITTYPE_SECONDARY, + WEP_CVAR_SEC(blaster, shotangle), + WEP_CVAR_SEC(blaster, damage), + WEP_CVAR_SEC(blaster, edgedamage), + WEP_CVAR_SEC(blaster, radius), + WEP_CVAR_SEC(blaster, force), + WEP_CVAR_SEC(blaster, speed), + WEP_CVAR_SEC(blaster, spread), + WEP_CVAR_SEC(blaster, delay), + WEP_CVAR_SEC(blaster, lifetime) + ); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(blaster, animtime), w_ready); + } + + break; + } + } + } + return true; + } + + case WR_INIT: + { + precache_model(W_Model("g_laser.md3")); + precache_model(W_Model("v_laser.md3")); + precache_model(W_Model("h_laser.iqm")); + precache_sound(W_Sound("lasergun_fire")); + BLASTER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + + case WR_SETUP: + { + self.ammo_field = ammo_none; + return true; + } + + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + return true; // laser has infinite ammo + } + + case WR_CONFIG: + { + BLASTER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + + case WR_SUICIDEMESSAGE: + { + return WEAPON_BLASTER_SUICIDE; + } + + case WR_KILLMESSAGE: + { + return WEAPON_BLASTER_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Blaster(int request) +{ + switch(request) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); + if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); } + return true; + } + + case WR_INIT: + { + precache_sound(W_Sound("laserimpact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/crylink.qc b/qcsrc/common/weapons/weapon/crylink.qc new file mode 100644 index 0000000000..08deaf78c4 --- /dev/null +++ b/qcsrc/common/weapons/weapon/crylink.qc @@ -0,0 +1,731 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ CRYLINK, +/* function */ W_Crylink, +/* ammotype */ ammo_cells, +/* impulse */ 6, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '1 0.5 1', +/* modelname */ "crylink", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshaircrylink 0.5", +/* wepimg */ "weaponcrylink", +/* refname */ "crylink", +/* wepname */ _("Crylink") +); + +#define CRYLINK_SETTINGS(w_cvar,w_prop) CRYLINK_SETTINGS_LIST(w_cvar, w_prop, CRYLINK, crylink) +#define CRYLINK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, edgedamage) \ + w_cvar(id, sn, BOTH, radius) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, spread) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, BOTH, shots) \ + w_cvar(id, sn, BOTH, bounces) \ + w_cvar(id, sn, BOTH, bouncedamagefactor) \ + w_cvar(id, sn, BOTH, middle_lifetime) \ + w_cvar(id, sn, BOTH, middle_fadetime) \ + w_cvar(id, sn, BOTH, other_lifetime) \ + w_cvar(id, sn, BOTH, other_fadetime) \ + w_cvar(id, sn, BOTH, linkexplode) \ + w_cvar(id, sn, BOTH, joindelay) \ + w_cvar(id, sn, BOTH, joinspread) \ + w_cvar(id, sn, BOTH, joinexplode) \ + w_cvar(id, sn, BOTH, joinexplode_damage) \ + w_cvar(id, sn, BOTH, joinexplode_edgedamage) \ + w_cvar(id, sn, BOTH, joinexplode_radius) \ + w_cvar(id, sn, BOTH, joinexplode_force) \ + w_cvar(id, sn, SEC, spreadtype) \ + w_cvar(id, sn, NONE, secondary) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +CRYLINK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float gravity; +.float crylink_waitrelease; +.entity crylink_lastgroup; + +.entity queuenext; +.entity queueprev; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_crylink(void) { weapon_defaultspawnfunc(WEP_CRYLINK.m_id); } + +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; + + float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY); + + RadiusDamage(e, e.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * a, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * a, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, 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: joining speed (calculate this as join spread * initial speed) +float w_crylink_linkjoin_time; +vector W_Crylink_LinkJoin(entity e, float jspeed) +{ + 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) + { + 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 + { + 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(void) +{ + // 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) + { + float isprimary = !(e.projectiledeathtype & HITTYPE_SECONDARY); + + if(WEP_CVAR_BOTH(crylink, isprimary, joinexplode)) + { + n /= WEP_CVAR_BOTH(crylink, isprimary, shots); + RadiusDamage( + e, + e.realowner, + WEP_CVAR_BOTH(crylink, isprimary, joinexplode_damage) * n, + WEP_CVAR_BOTH(crylink, isprimary, joinexplode_edgedamage) * n, + WEP_CVAR_BOTH(crylink, isprimary, joinexplode_radius) * n, + e.realowner, + world, + WEP_CVAR_BOTH(crylink, isprimary, joinexplode_force) * n, + e.projectiledeathtype, + other + ); + Send_Effect(EFFECT_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(SAME_TEAM(head, projectile.realowner)) + ++hit_friendly; + else + ++hit_enemy; + } + + 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; + float isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY); + 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 = WEP_CVAR_BOTH(crylink, isprimary, bouncedamagefactor); + if(a) + f *= a; + + float totaldamage = RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(crylink, isprimary, damage) * f, WEP_CVAR_BOTH(crylink, isprimary, edgedamage) * f, WEP_CVAR_BOTH(crylink, isprimary, radius), world, world, WEP_CVAR_BOTH(crylink, isprimary, force) * f, self.projectiledeathtype, other); + + if(totaldamage && ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 2) || ((WEP_CVAR_BOTH(crylink, isprimary, linkexplode) == 1) && !W_Crylink_Touch_WouldHitFriendly(self, WEP_CVAR_BOTH(crylink, isprimary, 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(WEP_CVAR_PRI(crylink, ammo)); + + maxdmg = WEP_CVAR_PRI(crylink, damage) * WEP_CVAR_PRI(crylink, shots); + maxdmg *= 1 + WEP_CVAR_PRI(crylink, bouncedamagefactor) * WEP_CVAR_PRI(crylink, bounces); + if(WEP_CVAR_PRI(crylink, joinexplode)) + maxdmg += WEP_CVAR_PRI(crylink, joinexplode_damage); + + W_SetupShot(self, false, 2, W_Sound("crylink_fire"), CH_WEAPON_A, maxdmg); + forward = v_forward; + right = v_right; + up = v_up; + + shots = WEP_CVAR_PRI(crylink, shots); + Send_Effect(EFFECT_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 = WEP_CVAR_PRI(crylink, 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.m_id; + //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 * WEP_CVAR_PRI(crylink, spread) * g_weaponspreadfactor; + W_SetupProjVelocity_Explicit(proj, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_PRI(crylink, speed), 0, 0, 0, false); + proj.touch = W_Crylink_Touch; + + proj.think = W_Crylink_Fadethink; + if(counter == 0) + { + proj.fade_time = time + WEP_CVAR_PRI(crylink, middle_lifetime); + proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, middle_fadetime); + proj.nextthink = time + WEP_CVAR_PRI(crylink, middle_lifetime) + WEP_CVAR_PRI(crylink, middle_fadetime); + } + else + { + proj.fade_time = time + WEP_CVAR_PRI(crylink, other_lifetime); + proj.fade_rate = 1 / WEP_CVAR_PRI(crylink, other_fadetime); + proj.nextthink = time + WEP_CVAR_PRI(crylink, other_lifetime) + WEP_CVAR_PRI(crylink, other_fadetime); + } + proj.teleport_time = time + WEP_CVAR_PRI(crylink, joindelay); + proj.cnt = WEP_CVAR_PRI(crylink, 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); + + MUTATOR_CALLHOOK(EditProjectile, self, proj); + } + if(WEP_CVAR_PRI(crylink, joinspread) != 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(WEP_CVAR_SEC(crylink, ammo)); + + maxdmg = WEP_CVAR_SEC(crylink, damage) * WEP_CVAR_SEC(crylink, shots); + maxdmg *= 1 + WEP_CVAR_SEC(crylink, bouncedamagefactor) * WEP_CVAR_SEC(crylink, bounces); + if(WEP_CVAR_SEC(crylink, joinexplode)) + maxdmg += WEP_CVAR_SEC(crylink, joinexplode_damage); + + W_SetupShot(self, false, 2, W_Sound("crylink_fire2"), CH_WEAPON_A, maxdmg); + forward = v_forward; + right = v_right; + up = v_up; + + shots = WEP_CVAR_SEC(crylink, shots); + Send_Effect(EFFECT_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 = WEP_CVAR_SEC(crylink, 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.m_id | HITTYPE_SECONDARY; + //proj.gravity = 0.001; + + setorigin(proj, w_shotorg); + setsize(proj, '0 0 0', '0 0 0'); + + if(WEP_CVAR_SEC(crylink, 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 * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor; + s = w_shotdir + right * s.y + up * s.z; + } + else + { + s = (w_shotdir + (((counter + 0.5) / shots) * 2 - 1) * v_right * WEP_CVAR_SEC(crylink, spread) * g_weaponspreadfactor); + } + + W_SetupProjVelocity_Explicit(proj, s, v_up, WEP_CVAR_SEC(crylink, speed), 0, 0, 0, false); + proj.touch = W_Crylink_Touch; + proj.think = W_Crylink_Fadethink; + if(counter == (shots - 1) / 2) + { + proj.fade_time = time + WEP_CVAR_SEC(crylink, middle_lifetime); + proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, middle_fadetime); + proj.nextthink = time + WEP_CVAR_SEC(crylink, middle_lifetime) + WEP_CVAR_SEC(crylink, middle_fadetime); + } + else + { + proj.fade_time = time + WEP_CVAR_SEC(crylink, other_lifetime); + proj.fade_rate = 1 / WEP_CVAR_SEC(crylink, other_fadetime); + proj.nextthink = time + WEP_CVAR_SEC(crylink, other_lifetime) + WEP_CVAR_SEC(crylink, other_fadetime); + } + proj.teleport_time = time + WEP_CVAR_SEC(crylink, joindelay); + proj.cnt = WEP_CVAR_SEC(crylink, 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); + + MUTATOR_CALLHOOK(EditProjectile, self, proj); + } + if(WEP_CVAR_SEC(crylink, joinspread) != 0) + { + self.crylink_lastgroup = proj; + W_Crylink_CheckLinks(proj); + self.crylink_waitrelease = 2; + } +} + +bool W_Crylink(int req) +{ + float ammo_amount; + switch(req) + { + case WR_AIM: + { + if(random() < 0.10) + self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(crylink, speed), 0, WEP_CVAR_PRI(crylink, middle_lifetime), false); + else + self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(crylink, speed), 0, WEP_CVAR_SEC(crylink, middle_lifetime), false); + + return true; + } + case WR_THINK: + { + if(autocvar_g_balance_crylink_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + + if(self.BUTTON_ATCK) + { + if(self.crylink_waitrelease != 1) + if(weapon_prepareattack(0, WEP_CVAR_PRI(crylink, refire))) + { + W_Crylink_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(crylink, animtime), w_ready); + } + } + + if(self.BUTTON_ATCK2 && autocvar_g_balance_crylink_secondary) + { + if(self.crylink_waitrelease != 2) + if(weapon_prepareattack(1, WEP_CVAR_SEC(crylink, refire))) + { + W_Crylink_Attack2(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(crylink, 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; + float isprimary = (self.crylink_waitrelease == 1); + + pos = W_Crylink_LinkJoin(self.crylink_lastgroup, WEP_CVAR_BOTH(crylink, isprimary, joinspread) * WEP_CVAR_BOTH(crylink, isprimary, speed)); + + 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(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + // ran out of ammo! + self.cnt = WEP_CRYLINK.m_id; + self.switchweapon = w_getbestweapon(self); + } + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_crylink.md3")); + precache_model(W_Model("v_crylink.md3")); + precache_model(W_Model("h_crylink.iqm")); + precache_sound(W_Sound("crylink_fire")); + precache_sound(W_Sound("crylink_fire2")); + precache_sound(W_Sound("crylink_linkjoin")); + CRYLINK_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case 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.WEP_AMMO(CRYLINK) >= WEP_CVAR_PRI(crylink, ammo); + ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_PRI(crylink, ammo); + return ammo_amount; + } + case 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.WEP_AMMO(CRYLINK) >= WEP_CVAR_SEC(crylink, ammo); + ammo_amount += self.(weapon_load[WEP_CRYLINK.m_id]) >= WEP_CVAR_SEC(crylink, ammo); + return ammo_amount; + } + case WR_CONFIG: + { + CRYLINK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(min(WEP_CVAR_PRI(crylink, ammo), WEP_CVAR_SEC(crylink, ammo)), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_CRYLINK_SUICIDE; + } + case WR_KILLMESSAGE: + { + return WEAPON_CRYLINK_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Crylink(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + if(w_deathtype & HITTYPE_SECONDARY) + { + pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT2), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("crylink_impact2"), VOL_BASE, ATTN_NORM); + } + else + { + pointparticles(particleeffectnum(EFFECT_CRYLINK_IMPACT), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("crylink_impact"), VOL_BASE, ATTN_NORM); + } + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("crylink_impact2")); + precache_sound(W_Sound("crylink_impact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/devastator.qc b/qcsrc/common/weapons/weapon/devastator.qc new file mode 100644 index 0000000000..9a235bf35d --- /dev/null +++ b/qcsrc/common/weapons/weapon/devastator.qc @@ -0,0 +1,687 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ DEVASTATOR, +/* function */ W_Devastator, +/* ammotype */ ammo_rockets, +/* impulse */ 9, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '1 1 0', +/* modelname */ "rl", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairrocketlauncher 0.7", +/* wepimg */ "weaponrocketlauncher", +/* refname */ "devastator", +/* wepname */ _("Devastator") +); + +#define DEVASTATOR_SETTINGS(w_cvar,w_prop) DEVASTATOR_SETTINGS_LIST(w_cvar, w_prop, DEVASTATOR, devastator) +#define DEVASTATOR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, ammo) \ + w_cvar(id, sn, NONE, animtime) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, damageforcescale) \ + w_cvar(id, sn, NONE, detonatedelay) \ + w_cvar(id, sn, NONE, edgedamage) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, guidedelay) \ + w_cvar(id, sn, NONE, guidegoal) \ + w_cvar(id, sn, NONE, guiderate) \ + w_cvar(id, sn, NONE, guideratedelay) \ + w_cvar(id, sn, NONE, guidestop) \ + w_cvar(id, sn, NONE, health) \ + w_cvar(id, sn, NONE, lifetime) \ + w_cvar(id, sn, NONE, radius) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, remote_damage) \ + w_cvar(id, sn, NONE, remote_edgedamage) \ + w_cvar(id, sn, NONE, remote_force) \ + w_cvar(id, sn, NONE, remote_jump_damage) \ + w_cvar(id, sn, NONE, remote_jump_radius) \ + w_cvar(id, sn, NONE, remote_jump_velocity_z_add) \ + w_cvar(id, sn, NONE, remote_jump_velocity_z_max) \ + w_cvar(id, sn, NONE, remote_jump_velocity_z_min) \ + w_cvar(id, sn, NONE, remote_radius) \ + w_cvar(id, sn, NONE, speed) \ + w_cvar(id, sn, NONE, speedaccel) \ + w_cvar(id, sn, NONE, speedstart) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +DEVASTATOR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float rl_release; +.float rl_detonate_later; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_devastator(void) { weapon_defaultspawnfunc(WEP_DEVASTATOR.m_id); } +void spawnfunc_weapon_rocketlauncher(void) { spawnfunc_weapon_devastator(); } + +void W_Devastator_Unregister(void) +{ + if(self.realowner && self.realowner.lastrocket == self) + { + self.realowner.lastrocket = world; + // self.realowner.rl_release = 1; + } +} + +void W_Devastator_Explode(void) +{ + W_Devastator_Unregister(); + + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(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, + WEP_CVAR(devastator, damage), + WEP_CVAR(devastator, edgedamage), + WEP_CVAR(devastator, radius), + world, + world, + WEP_CVAR(devastator, force), + self.projectiledeathtype, + other + ); + + if(self.realowner.weapon == WEP_DEVASTATOR.m_id) + { + if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo)) + if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)) + { + self.realowner.cnt = WEP_DEVASTATOR.m_id; + ATTACK_FINISHED(self.realowner) = time; + self.realowner.switchweapon = w_getbestweapon(self.realowner); + } + } + remove(self); +} + +void W_Devastator_DoRemoteExplode(void) +{ + W_Devastator_Unregister(); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + float handled_as_rocketjump = false; + + entity head = WarpZone_FindRadius( + self.origin, + WEP_CVAR(devastator, remote_jump_radius), + false + ); + + while(head) + { + if(head.takedamage && (head == self.realowner)) + { + float distance_to_head = vlen(self.origin - head.WarpZone_findradius_nearest); + if(distance_to_head <= WEP_CVAR(devastator, remote_jump_radius)) + { + // we handled this as a rocketjump :) + handled_as_rocketjump = true; + + // modify velocity + head.velocity_x *= 0.9; + head.velocity_y *= 0.9; + head.velocity_z = bound( + WEP_CVAR(devastator, remote_jump_velocity_z_min), + head.velocity.z + WEP_CVAR(devastator, remote_jump_velocity_z_add), + WEP_CVAR(devastator, remote_jump_velocity_z_max) + ); + + // now do the damage + RadiusDamage( + self, + head, + WEP_CVAR(devastator, remote_jump_damage), + WEP_CVAR(devastator, remote_jump_damage), + WEP_CVAR(devastator, remote_jump_radius), + world, + head, + 0, + self.projectiledeathtype | HITTYPE_BOUNCE, + world + ); + break; + } + } + head = head.chain; + } + + RadiusDamage( + self, + self.realowner, + WEP_CVAR(devastator, remote_damage), + WEP_CVAR(devastator, remote_edgedamage), + WEP_CVAR(devastator, remote_radius), + (handled_as_rocketjump ? head : world), + world, + WEP_CVAR(devastator, remote_force), + self.projectiledeathtype | HITTYPE_BOUNCE, + world + ); + + if(self.realowner.weapon == WEP_DEVASTATOR.m_id) + { + if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo)) + if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO)) + { + self.realowner.cnt = WEP_DEVASTATOR.m_id; + ATTACK_FINISHED(self.realowner) = time; + self.realowner.switchweapon = w_getbestweapon(self.realowner); + } + } + remove(self); +} + +void W_Devastator_RemoteExplode(void) +{ + 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) > WEP_CVAR(devastator, remote_radius)) // safety device + ) + { + W_Devastator_DoRemoteExplode(); + } + } +} + +vector W_Devastator_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_Devastator_Think(void) +{ + vector desireddir, olddir, newdir, desiredorigin, goal; + float velspeed, f; + self.nextthink = time; + if(time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Devastator_Explode(); + return; + } + + // accelerate + makevectors(self.angles.x * '-1 0 0' + self.angles.y * '0 1 0'); + velspeed = WEP_CVAR(devastator, speed) * W_WeaponSpeedFactor() - (self.velocity * v_forward); + if(velspeed > 0) + self.velocity = self.velocity + v_forward * min(WEP_CVAR(devastator, speedaccel) * W_WeaponSpeedFactor() * frametime, velspeed); + + // laser guided, or remote detonation + if(self.realowner.weapon == WEP_DEVASTATOR.m_id) + { + if(self == self.realowner.lastrocket) + if(!self.realowner.rl_release) + if(!self.BUTTON_ATCK2) + if(WEP_CVAR(devastator, guiderate)) + if(time > self.pushltime) + if(self.realowner.deadflag == DEAD_NO) + { + f = WEP_CVAR(devastator, 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 + WEP_CVAR(devastator, guidegoal)) * desireddir; + newdir = W_Devastator_SteerTo(olddir, normalize(goal - self.origin), cos(WEP_CVAR(devastator, guiderate) * f * frametime * DEG2RAD)); + + self.velocity = newdir * velspeed; + self.angles = vectoangles(self.velocity); + + if(!self.count) + { + Send_Effect(EFFECT_ROCKET_GUIDE, self.origin, self.velocity, 1); + // TODO add a better sound here + sound(self.realowner, CH_WEAPON_B, W_Sound("rocket_mode"), VOL_BASE, ATTN_NORM); + self.count = 1; + } + } + + if(self.rl_detonate_later) + W_Devastator_RemoteExplode(); + } + + if(self.csqcprojectile_clientanimate == 0) + UpdateCSQCProjectile(self); +} + +void W_Devastator_Touch(void) +{ + if(WarpZone_Projectile_Touch()) + { + if(wasfreed(self)) + W_Devastator_Unregister(); + return; + } + W_Devastator_Unregister(); + W_Devastator_Explode(); +} + +void W_Devastator_Damage(entity inflictor, entity attacker, float damage, int 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_Devastator_Explode); +} + +void W_Devastator_Attack(void) +{ + entity missile; + entity flash; + + W_DecreaseAmmo(WEP_CVAR(devastator, ammo)); + + W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 5, W_Sound("rocket_fire"), CH_WEAPON_A, WEP_CVAR(devastator, damage)); + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + missile = WarpZone_RefSys_SpawnSameRefSys(self); + missile.owner = missile.realowner = self; + self.lastrocket = missile; + if(WEP_CVAR(devastator, detonatedelay) >= 0) + missile.spawnshieldtime = time + WEP_CVAR(devastator, detonatedelay); + else + missile.spawnshieldtime = -1; + missile.pushltime = time + WEP_CVAR(devastator, guidedelay); + missile.classname = "rocket"; + missile.bot_dodge = true; + missile.bot_dodgerating = WEP_CVAR(devastator, damage) * 2; // * 2 because it can be detonated inflight which makes it even more dangerous + + missile.takedamage = DAMAGE_YES; + missile.damageforcescale = WEP_CVAR(devastator, damageforcescale); + missile.health = WEP_CVAR(devastator, health); + missile.event_damage = W_Devastator_Damage; + missile.damagedbycontents = true; + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_DEVASTATOR.m_id; + 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_SetupProjVelocity_Basic(missile, WEP_CVAR(devastator, speedstart), 0); + missile.angles = vectoangles(missile.velocity); + + missile.touch = W_Devastator_Touch; + missile.think = W_Devastator_Think; + missile.nextthink = time; + missile.cnt = time + WEP_CVAR(devastator, lifetime); + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, WEP_CVAR(devastator, guiderate) == 0 && WEP_CVAR(devastator, 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 + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +bool W_Devastator(int req) +{ + entity rock; + float rockfound; + float ammo_amount; + switch(req) + { + #if 0 + case WR_AIM: + { + // aim and decide to fire if appropriate + self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), false); + if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! + { + // decide whether to detonate rockets + entity missile, targetlist, targ; + targetlist = findchainfloat(bot_attack, true); + for(missile = world; (missile = find(missile, classname, "rocket")); ) if(missile.realowner == self) + { + targ = targetlist; + while(targ) + { + if(targ != missile.realowner && vlen(targ.origin - missile.origin) < WEP_CVAR(devastator, radius)) + { + self.BUTTON_ATCK2 = true; + break; + } + targ = targ.chain; + } + } + + if(self.BUTTON_ATCK2) self.BUTTON_ATCK = false; + } + + return true; + } + #else + case WR_AIM: + { + // aim and decide to fire if appropriate + self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, 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 = WEP_CVAR(devastator, edgedamage); + coredamage = WEP_CVAR(devastator, damage); + edgeradius = WEP_CVAR(devastator, 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; + } + + return true; + } + #endif + case WR_THINK: + { + if(WEP_CVAR(devastator, reload_ammo) && self.clip_load < WEP_CVAR(devastator, ammo)) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else + { + if(self.BUTTON_ATCK) + { + if(self.rl_release || WEP_CVAR(devastator, guidestop)) + if(weapon_prepareattack(0, WEP_CVAR(devastator, refire))) + { + W_Devastator_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready); + self.rl_release = 0; + } + } + else + self.rl_release = 1; + + if(self.BUTTON_ATCK2) + if(self.switchweapon == WEP_DEVASTATOR.m_id) + { + 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, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM); + } + } + + return true; + } + case WR_INIT: + { + //if(autocvar_sv_precacheweapons) + //{ + precache_model("models/flash.md3"); + precache_model(W_Model("g_rl.md3")); + precache_model(W_Model("v_rl.md3")); + precache_model(W_Model("h_rl.iqm")); + precache_sound(W_Sound("rocket_det")); + precache_sound(W_Sound("rocket_fire")); + precache_sound(W_Sound("rocket_mode")); + //} + DEVASTATOR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.rl_release = 1; + return true; + } + case WR_CHECKAMMO1: + { + #if 0 + // don't switch while guiding a missile + if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_DEVASTATOR.m_id) + { + ammo_amount = false; + if(WEP_CVAR(devastator, reload_ammo)) + { + if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo)) + ammo_amount = true; + } + else if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo)) + ammo_amount = true; + return !ammo_amount; + } + #endif + #if 0 + if(self.rl_release == 0) + { + LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo)); + return true; + } + else + { + ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo); + ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo); + LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE")); + return ammo_amount; + } + #else + ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo); + ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo); + return ammo_amount; + #endif + } + case WR_CHECKAMMO2: + { + return false; + } + case WR_CONFIG: + { + DEVASTATOR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.rl_release = 0; + return true; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR(devastator, ammo), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_DEVASTATOR_SUICIDE; + } + case WR_KILLMESSAGE: + { + if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) + return WEAPON_DEVASTATOR_MURDER_SPLASH; + else + return WEAPON_DEVASTATOR_MURDER_DIRECT; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Devastator(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("rocket_impact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/electro.qc b/qcsrc/common/weapons/weapon/electro.qc new file mode 100644 index 0000000000..0f60fd29d3 --- /dev/null +++ b/qcsrc/common/weapons/weapon/electro.qc @@ -0,0 +1,621 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ ELECTRO, +/* function */ W_Electro, +/* ammotype */ ammo_cells, +/* impulse */ 5, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '0 0.5 1', +/* modelname */ "electro", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairelectro 0.6", +/* wepimg */ "weaponelectro", +/* refname */ "electro", +/* wepname */ _("Electro") +); + +#define ELECTRO_SETTINGS(w_cvar,w_prop) ELECTRO_SETTINGS_LIST(w_cvar, w_prop, ELECTRO, electro) +#define ELECTRO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, edgedamage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, radius) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, BOTH, spread) \ + w_cvar(id, sn, BOTH, lifetime) \ + w_cvar(id, sn, PRI, comboradius) \ + w_cvar(id, sn, PRI, midaircombo_explode) \ + w_cvar(id, sn, PRI, midaircombo_interval) \ + w_cvar(id, sn, PRI, midaircombo_radius) \ + w_cvar(id, sn, SEC, bouncefactor) \ + w_cvar(id, sn, SEC, bouncestop) \ + w_cvar(id, sn, SEC, count) \ + w_cvar(id, sn, SEC, damageforcescale) \ + w_cvar(id, sn, SEC, damagedbycontents) \ + w_cvar(id, sn, SEC, health) \ + w_cvar(id, sn, SEC, refire2) \ + w_cvar(id, sn, SEC, speed_up) \ + w_cvar(id, sn, SEC, speed_z) \ + w_cvar(id, sn, SEC, touchexplode) \ + w_cvar(id, sn, NONE, combo_comboradius) \ + w_cvar(id, sn, NONE, combo_comboradius_thruwall) \ + w_cvar(id, sn, NONE, combo_damage) \ + w_cvar(id, sn, NONE, combo_edgedamage) \ + w_cvar(id, sn, NONE, combo_force) \ + w_cvar(id, sn, NONE, combo_radius) \ + w_cvar(id, sn, NONE, combo_speed) \ + w_cvar(id, sn, NONE, combo_safeammocheck) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +ELECTRO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float electro_count; +.float electro_secondarytime; +void W_Electro_ExplodeCombo(void); +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_electro(void) { weapon_defaultspawnfunc(WEP_ELECTRO.m_id); } + +void W_Electro_TriggerCombo(vector org, float rad, entity own) +{ + entity e = WarpZone_FindRadius(org, rad, !WEP_CVAR(electro, combo_comboradius_thruwall)); + while(e) + { + if(e.classname == "electro_orb") + { + // do we allow thruwall triggering? + if(WEP_CVAR(electro, combo_comboradius_thruwall)) + { + // if distance is greater than thruwall distance, check to make sure it's not through a wall + if(vlen(e.WarpZone_findradius_dist) > WEP_CVAR(electro, combo_comboradius_thruwall)) + { + WarpZone_TraceLine(org, e.origin, MOVE_NOMONSTERS, e); + if(trace_fraction != 1) + { + // trigger is through a wall and outside of thruwall range, abort + e = e.chain; + continue; + } + } + } + + // change owner to whoever caused the combo explosion + e.realowner = own; + e.takedamage = DAMAGE_NO; + e.classname = "electro_orb_chain"; + + // now set the next one to trigger as well + e.think = W_Electro_ExplodeCombo; + + // delay combo chains, looks cooler + e.nextthink = + ( + time + + + (WEP_CVAR(electro, combo_speed) ? + (vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed)) + : + 0 + ) + ); + } + e = e.chain; + } +} + +void W_Electro_ExplodeCombo(void) +{ + W_Electro_TriggerCombo(self.origin, WEP_CVAR(electro, combo_comboradius), self.realowner); + + self.event_damage = func_null; + + RadiusDamage( + self, + self.realowner, + WEP_CVAR(electro, combo_damage), + WEP_CVAR(electro, combo_edgedamage), + WEP_CVAR(electro, combo_radius), + world, + world, + WEP_CVAR(electro, combo_force), + WEP_ELECTRO.m_id | HITTYPE_BOUNCE, // use THIS type for a combo because primary can't bounce + world + ); + + remove(self); +} + +void W_Electro_Explode(void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(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, + WEP_CVAR_SEC(electro, damage), + WEP_CVAR_SEC(electro, edgedamage), + WEP_CVAR_SEC(electro, radius), + world, + world, + WEP_CVAR_SEC(electro, force), + self.projectiledeathtype, + other + ); + } + else + { + W_Electro_TriggerCombo(self.origin, WEP_CVAR_PRI(electro, comboradius), self.realowner); + RadiusDamage( + self, + self.realowner, + WEP_CVAR_PRI(electro, damage), + WEP_CVAR_PRI(electro, edgedamage), + WEP_CVAR_PRI(electro, radius), + world, + world, + WEP_CVAR_PRI(electro, force), + self.projectiledeathtype, + other + ); + } + + remove(self); +} + +void W_Electro_TouchExplode(void) +{ + PROJECTILE_TOUCH; + W_Electro_Explode(); +} + +void W_Electro_Bolt_Think(void) +{ + if(time >= self.ltime) + { + self.use(); + return; + } + + if(WEP_CVAR_PRI(electro, midaircombo_radius)) + { + float found = 0; + entity e = WarpZone_FindRadius(self.origin, WEP_CVAR_PRI(electro, midaircombo_radius), true); + + // loop through nearby orbs and trigger them + while(e) + { + if(e.classname == "electro_orb") + { + // change owner to whoever caused the combo explosion + e.realowner = self.realowner; + e.takedamage = DAMAGE_NO; + e.classname = "electro_orb_chain"; + + // now set the next one to trigger as well + e.think = W_Electro_ExplodeCombo; + + // delay combo chains, looks cooler + e.nextthink = + ( + time + + + (WEP_CVAR(electro, combo_speed) ? + (vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed)) + : + 0 + ) + ); + + ++found; + } + e = e.chain; + } + + // if we triggered an orb, should we explode? if not, lets try again next time + if(found && WEP_CVAR_PRI(electro, midaircombo_explode)) + { self.use(); } + else + { self.nextthink = min(time + WEP_CVAR_PRI(electro, midaircombo_interval), self.ltime); } + } + else { self.nextthink = self.ltime; } +} + +void W_Electro_Attack_Bolt(void) +{ + entity proj; + + W_DecreaseAmmo(WEP_CVAR_PRI(electro, ammo)); + + W_SetupShot_ProjectileSize( + self, + '0 0 -3', + '0 0 -3', + false, + 2, + W_Sound("electro_fire"), + CH_WEAPON_A, + WEP_CVAR_PRI(electro, damage) + ); + + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + proj = spawn(); + proj.classname = "electro_bolt"; + proj.owner = proj.realowner = self; + proj.bot_dodge = true; + proj.bot_dodgerating = WEP_CVAR_PRI(electro, damage); + proj.use = W_Electro_Explode; + proj.think = W_Electro_Bolt_Think; + proj.nextthink = time; + proj.ltime = time + WEP_CVAR_PRI(electro, lifetime); + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO.m_id; + setorigin(proj, w_shotorg); + + proj.movetype = MOVETYPE_FLY; + W_SetupProjVelocity_PRI(proj, electro); + proj.angles = vectoangles(proj.velocity); + proj.touch = W_Electro_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); + + MUTATOR_CALLHOOK(EditProjectile, self, proj); +} + +void W_Electro_Orb_Touch(void) +{ + PROJECTILE_TOUCH; + if(other.takedamage == DAMAGE_AIM) + { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(); } } + else + { + //UpdateCSQCProjectile(self); + spamsound(self, CH_SHOTS, W_Sound("electro_bounce"), VOL_BASE, ATTEN_NORM); + self.projectiledeathtype |= HITTYPE_BOUNCE; + } +} + +void W_Electro_Orb_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{ + if(self.health <= 0) + return; + + // note: combos are usually triggered by W_Electro_TriggerCombo, not damage + float is_combo = (inflictor.classname == "electro_orb_chain" || inflictor.classname == "electro_bolt"); + + 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 = "electro_orb_chain"; + self.think = W_Electro_ExplodeCombo; + self.nextthink = time + + ( + // bound the length, inflictor may be in a galaxy far far away (warpzones) + min( + WEP_CVAR(electro, combo_radius), + vlen(self.origin - inflictor.origin) + ) + / + // delay combo chains, looks cooler + WEP_CVAR(electro, combo_speed) + ); + } + else + { + self.use = W_Electro_Explode; + self.think = adaptor_think2use; // not _hittype_splash, as this runs "immediately" + } + } +} + +void W_Electro_Attack_Orb(void) +{ + W_DecreaseAmmo(WEP_CVAR_SEC(electro, ammo)); + + W_SetupShot_ProjectileSize( + self, + '0 0 -4', + '0 0 -4', + false, + 2, + W_Sound("electro_fire2"), + CH_WEAPON_A, + WEP_CVAR_SEC(electro, damage) + ); + + w_shotdir = v_forward; // no TrueAim for grenades please + + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + entity proj = spawn(); + proj.classname = "electro_orb"; + proj.owner = proj.realowner = self; + proj.use = W_Electro_Explode; + proj.think = adaptor_think2use_hittype_splash; + proj.bot_dodge = true; + proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage); + proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime); + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO.m_id | HITTYPE_SECONDARY; + setorigin(proj, w_shotorg); + + //proj.glow_size = 50; + //proj.glow_color = 45; + proj.movetype = MOVETYPE_BOUNCE; + W_SetupProjVelocity_UP_SEC(proj, electro); + proj.touch = W_Electro_Orb_Touch; + setsize(proj, '0 0 -4', '0 0 -4'); + proj.takedamage = DAMAGE_YES; + proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale); + proj.health = WEP_CVAR_SEC(electro, health); + proj.event_damage = W_Electro_Orb_Damage; + proj.flags = FL_PROJECTILE; + proj.damagedbycontents = (WEP_CVAR_SEC(electro, damagedbycontents)); + + proj.bouncefactor = WEP_CVAR_SEC(electro, bouncefactor); + proj.bouncestop = WEP_CVAR_SEC(electro, 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 + + MUTATOR_CALLHOOK(EditProjectile, self, proj); +} + +void W_Electro_CheckAttack(void) +{ + if(self.electro_count > 1) + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, -1)) + { + W_Electro_Attack_Orb(); + self.electro_count -= 1; + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack); + return; + } + // WEAPONTODO: when the player releases the button, cut down the length of refire2? + w_ready(); +} + +.float bot_secondary_electromooth; +bool W_Electro(int req) +{ + float ammo_amount; + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = 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(WEP_CVAR_PRI(electro, speed)) + shoot = bot_aim(WEP_CVAR_PRI(electro, speed), 0, WEP_CVAR_PRI(electro, 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(WEP_CVAR_SEC(electro, speed), WEP_CVAR_SEC(electro, speed_up), WEP_CVAR_SEC(electro, lifetime), true)) + { + self.BUTTON_ATCK2 = true; + if(random() < 0.03) self.bot_secondary_electromooth = 0; + } + } + + return true; + } + case WR_THINK: + { + if(autocvar_g_balance_electro_reload_ammo) // forced reload // WEAPONTODO + { + ammo_amount = 0; + if(self.clip_load >= WEP_CVAR_PRI(electro, ammo)) + ammo_amount = 1; + if(self.clip_load >= WEP_CVAR_SEC(electro, ammo)) + ammo_amount += 1; + + if(!ammo_amount) + { + WEP_ACTION(self.weapon, WR_RELOAD); + return false; + } + + return true; + } + + if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(electro, refire))) + { + W_Electro_Attack_Bolt(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready); + } + } + else if(self.BUTTON_ATCK2) + { + if(time >= self.electro_secondarytime) + if(weapon_prepareattack(1, WEP_CVAR_SEC(electro, refire))) + { + W_Electro_Attack_Orb(); + self.electro_count = WEP_CVAR_SEC(electro, count); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack); + self.electro_secondarytime = time + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor(); + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_electro.md3")); + precache_model(W_Model("v_electro.md3")); + precache_model(W_Model("h_electro.iqm")); + precache_sound(W_Sound("electro_bounce")); + precache_sound(W_Sound("electro_fire")); + precache_sound(W_Sound("electro_fire2")); + precache_sound(W_Sound("electro_impact")); + precache_sound(W_Sound("electro_impact_combo")); + ELECTRO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_PRI(electro, ammo); + ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_PRI(electro, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false. + { + ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo); + ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo); + } + else + { + ammo_amount = self.WEP_AMMO(ELECTRO) >= WEP_CVAR_SEC(electro, ammo); + ammo_amount += self.(weapon_load[WEP_ELECTRO.m_id]) >= WEP_CVAR_SEC(electro, ammo); + } + return ammo_amount; + } + case WR_CONFIG: + { + ELECTRO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.electro_secondarytime = time; + return true; + } + case WR_RELOAD: + { + W_Reload(min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_ELECTRO_SUICIDE_ORBS; + else + return WEAPON_ELECTRO_SUICIDE_BOLT; + } + case 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 false; +} +#endif +#ifdef CSQC +bool W_Electro(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + if(w_deathtype & HITTYPE_SECONDARY) + { + pointparticles(particleeffectnum(EFFECT_ELECTRO_BALLEXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_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(EFFECT_ELECTRO_COMBO), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("electro_impact_combo"), VOL_BASE, ATTEN_NORM); + } + else + { + pointparticles(particleeffectnum(EFFECT_ELECTRO_IMPACT), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("electro_impact"), VOL_BASE, ATTEN_NORM); + } + } + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("electro_impact")); + precache_sound(W_Sound("electro_impact_combo")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/fireball.qc b/qcsrc/common/weapons/weapon/fireball.qc new file mode 100644 index 0000000000..4f8f3787c3 --- /dev/null +++ b/qcsrc/common/weapons/weapon/fireball.qc @@ -0,0 +1,486 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ FIREBALL, +/* function */ W_Fireball, +/* ammotype */ ammo_none, +/* impulse */ 9, +/* flags */ WEP_FLAG_SUPERWEAPON | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '1 0.5 0', +/* modelname */ "fireball", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairfireball", +/* wepimg */ "weaponfireball", +/* refname */ "fireball", +/* wepname */ _("Fireball") +); + +#define FIREBALL_SETTINGS(w_cvar,w_prop) FIREBALL_SETTINGS_LIST(w_cvar, w_prop, FIREBALL, fireball) +#define FIREBALL_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, damageforcescale) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, BOTH, spread) \ + w_cvar(id, sn, BOTH, lifetime) \ + w_cvar(id, sn, BOTH, laserburntime) \ + w_cvar(id, sn, BOTH, laserdamage) \ + w_cvar(id, sn, BOTH, laseredgedamage) \ + w_cvar(id, sn, BOTH, laserradius) \ + w_cvar(id, sn, PRI, edgedamage) \ + w_cvar(id, sn, PRI, force) \ + w_cvar(id, sn, PRI, radius) \ + w_cvar(id, sn, PRI, health) \ + w_cvar(id, sn, PRI, refire2) \ + w_cvar(id, sn, PRI, bfgdamage) \ + w_cvar(id, sn, PRI, bfgforce) \ + w_cvar(id, sn, PRI, bfgradius) \ + w_cvar(id, sn, SEC, damagetime) \ + w_cvar(id, sn, SEC, speed_up) \ + w_cvar(id, sn, SEC, speed_z) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +FIREBALL_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float bot_primary_fireballmooth; // whatever a mooth is +.vector fireball_impactvec; +.float fireball_primarytime; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_fireball(void) { weapon_defaultspawnfunc(WEP_FIREBALL.m_id); } + +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, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), world, world, WEP_CVAR_PRI(fireball, 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, WEP_CVAR_PRI(fireball, 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, WEP_CVAR_PRI(fireball, bfgradius)); e; e = e.chain) + if(e != self.realowner) if(e.takedamage == DAMAGE_AIM) if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(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 / WEP_CVAR_PRI(fireball, 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.m_id, 0, WEP_CVAR_PRI(fireball, bfgdamage) * points); + + Damage(e, self, self.realowner, WEP_CVAR_PRI(fireball, bfgdamage) * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, WEP_CVAR_PRI(fireball, bfgforce) * dir); + Send_Effect(EFFECT_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 || DIFF_TEAM(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(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec); + Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); + } +} + +void W_Fireball_Think(void) +{ + if(time > self.pushltime) + { + self.cnt = 1; + self.projectiledeathtype |= HITTYPE_SPLASH; + W_Fireball_Explode(); + return; + } + + W_Fireball_LaserPlay(0.1, WEP_CVAR_PRI(fireball, laserradius), WEP_CVAR_PRI(fireball, laserdamage), WEP_CVAR_PRI(fireball, laseredgedamage), WEP_CVAR_PRI(fireball, laserburntime)); + + self.nextthink = time + 0.1; +} + +void W_Fireball_Damage(entity inflictor, entity attacker, float damage, int 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(void) +{ + entity proj; + + W_SetupShot_ProjectileSize(self, '-16 -16 -16', '16 16 16', false, 2, W_Sound("fireball_fire2"), CH_WEAPON_A, WEP_CVAR_PRI(fireball, damage) + WEP_CVAR_PRI(fireball, bfgdamage)); + + Send_Effect(EFFECT_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 = WEP_CVAR_PRI(fireball, damage); + proj.pushltime = time + WEP_CVAR_PRI(fireball, lifetime); + proj.use = W_Fireball_Explode; + proj.think = W_Fireball_Think; + proj.nextthink = time; + proj.health = WEP_CVAR_PRI(fireball, health); + proj.team = self.team; + proj.event_damage = W_Fireball_Damage; + proj.takedamage = DAMAGE_YES; + proj.damageforcescale = WEP_CVAR_PRI(fireball, damageforcescale); + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_FIREBALL.m_id; + setorigin(proj, w_shotorg); + + proj.movetype = MOVETYPE_FLY; + W_SetupProjVelocity_PRI(proj, fireball); + 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); + + MUTATOR_CALLHOOK(EditProjectile, self, proj); +} + +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; + Send_Effect(EFFECT_FIREBALL_PRE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); +} + +void W_Fireball_Attack1_Frame4(void) +{ + W_Fireball_Attack1(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), w_ready); +} + +void W_Fireball_Attack1_Frame3(void) +{ + W_Fireball_AttackEffect(0, '+1.25 +3.75 0'); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame4); +} + +void W_Fireball_Attack1_Frame2(void) +{ + W_Fireball_AttackEffect(0, '-1.25 +3.75 0'); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame3); +} + +void W_Fireball_Attack1_Frame1(void) +{ + W_Fireball_AttackEffect(1, '+1.25 -3.75 0'); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame2); +} + +void W_Fireball_Attack1_Frame0(void) +{ + W_Fireball_AttackEffect(0, '-1.25 -3.75 0'); + sound(self, CH_WEAPON_SINGLE, W_Sound("fireball_prefire2"), VOL_BASE, ATTEN_NORM); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(fireball, animtime), W_Fireball_Attack1_Frame1); +} + +void W_Fireball_Firemine_Think(void) +{ + 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) > WEP_CVAR_SEC(fireball, laserradius)) + { + self.cnt += 1; + if(self.cnt == 3) + self.owner = world; + } + else + self.cnt = 0; + } + + W_Fireball_LaserPlay(0.1, WEP_CVAR_SEC(fireball, laserradius), WEP_CVAR_SEC(fireball, laserdamage), WEP_CVAR_SEC(fireball, laseredgedamage), WEP_CVAR_SEC(fireball, laserburntime)); + + self.nextthink = time + 0.1; +} + +void W_Fireball_Firemine_Touch(void) +{ + PROJECTILE_TOUCH; + if(other.takedamage == DAMAGE_AIM) + if(Fire_AddDamage(other, self.realowner, WEP_CVAR_SEC(fireball, damage), WEP_CVAR_SEC(fireball, damagetime), self.projectiledeathtype) >= 0) + { + remove(self); + return; + } + self.projectiledeathtype |= HITTYPE_BOUNCE; +} + +void W_Fireball_Attack2(void) +{ + entity proj; + vector f_diff; + float c; + + c = 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, W_Sound("fireball_fire"), CH_WEAPON_A, WEP_CVAR_SEC(fireball, damage)); + traceline(w_shotorg, w_shotorg + f_diff_x * v_up + f_diff_y * v_right, MOVE_NORMAL, self); + w_shotorg = trace_endpos; + + Send_Effect(EFFECT_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 = WEP_CVAR_SEC(fireball, damage); + proj.movetype = MOVETYPE_BOUNCE; + proj.projectiledeathtype = WEP_FIREBALL.m_id | HITTYPE_SECONDARY; + proj.touch = W_Fireball_Firemine_Touch; + PROJECTILE_MAKETRIGGER(proj); + setsize(proj, '-4 -4 -4', '4 4 4'); + setorigin(proj, w_shotorg); + proj.think = W_Fireball_Firemine_Think; + proj.nextthink = time; + proj.damageforcescale = WEP_CVAR_SEC(fireball, damageforcescale); + proj.pushltime = time + WEP_CVAR_SEC(fireball, lifetime); + W_SetupProjVelocity_UP_SEC(proj, fireball); + + proj.angles = vectoangles(proj.velocity); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; + + CSQCProjectile(proj, true, PROJECTILE_FIREMINE, true); + + MUTATOR_CALLHOOK(EditProjectile, self, proj); +} + +bool W_Fireball(int req) +{ + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = false; + self.BUTTON_ATCK2 = false; + if(self.bot_primary_fireballmooth == 0) + { + if(bot_aim(WEP_CVAR_PRI(fireball, speed), 0, WEP_CVAR_PRI(fireball, lifetime), false)) + { + self.BUTTON_ATCK = true; + if(random() < 0.02) self.bot_primary_fireballmooth = 0; + } + } + else + { + if(bot_aim(WEP_CVAR_SEC(fireball, speed), WEP_CVAR_SEC(fireball, speed_up), WEP_CVAR_SEC(fireball, lifetime), true)) + { + self.BUTTON_ATCK2 = true; + if(random() < 0.01) self.bot_primary_fireballmooth = 1; + } + } + + return true; + } + case WR_THINK: + { + if(self.BUTTON_ATCK) + { + if(time >= self.fireball_primarytime) + if(weapon_prepareattack(0, WEP_CVAR_PRI(fireball, refire))) + { + W_Fireball_Attack1_Frame0(); + self.fireball_primarytime = time + WEP_CVAR_PRI(fireball, refire2) * W_WeaponRateFactor(); + } + } + else if(self.BUTTON_ATCK2) + { + if(weapon_prepareattack(1, WEP_CVAR_SEC(fireball, refire))) + { + W_Fireball_Attack2(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(fireball, animtime), w_ready); + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_fireball.md3")); + precache_model(W_Model("v_fireball.md3")); + precache_model(W_Model("h_fireball.iqm")); + precache_model("models/sphere/sphere.md3"); + precache_sound(W_Sound("fireball_fire")); + precache_sound(W_Sound("fireball_fire2")); + precache_sound(W_Sound("fireball_prefire2")); + FIREBALL_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.ammo_field = ammo_none; + return true; + } + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + return true; // fireball has infinite ammo + } + case WR_CONFIG: + { + FIREBALL_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.fireball_primarytime = time; + return true; + } + case WR_SUICIDEMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_FIREBALL_SUICIDE_FIREMINE; + else + return WEAPON_FIREBALL_SUICIDE_BLAST; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_FIREBALL_MURDER_FIREMINE; + else + return WEAPON_FIREBALL_MURDER_BLAST; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Fireball(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + if(w_deathtype & HITTYPE_SECONDARY) + { + // firemine goes out silently + } + else + { + org2 = w_org + w_backoff * 16; + pointparticles(particleeffectnum(EFFECT_FIREBALL_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("fireball_impact2"), VOL_BASE, ATTEN_NORM * 0.25); // long range boom + } + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("fireball_impact2")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/hagar.qc b/qcsrc/common/weapons/weapon/hagar.qc new file mode 100644 index 0000000000..4f1b905847 --- /dev/null +++ b/qcsrc/common/weapons/weapon/hagar.qc @@ -0,0 +1,565 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ HAGAR, +/* function */ W_Hagar, +/* ammotype */ ammo_rockets, +/* impulse */ 8, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '1 1 0.5', +/* modelname */ "hagar", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairhagar 0.8", +/* wepimg */ "weaponhagar", +/* refname */ "hagar", +/* wepname */ _("Hagar") +); + +#define HAGAR_SETTINGS(w_cvar,w_prop) HAGAR_SETTINGS_LIST(w_cvar, w_prop, HAGAR, hagar) +#define HAGAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, ammo) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, edgedamage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, radius) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, BOTH, spread) \ + w_cvar(id, sn, BOTH, damageforcescale) \ + w_cvar(id, sn, BOTH, health) \ + w_cvar(id, sn, PRI, lifetime) \ + w_cvar(id, sn, SEC, load) \ + w_cvar(id, sn, SEC, load_max) \ + w_cvar(id, sn, SEC, load_abort) \ + w_cvar(id, sn, SEC, load_animtime) \ + w_cvar(id, sn, SEC, load_hold) \ + w_cvar(id, sn, SEC, load_speed) \ + w_cvar(id, sn, SEC, load_releasedeath) \ + w_cvar(id, sn, SEC, load_spread) \ + w_cvar(id, sn, SEC, load_spread_bias) \ + w_cvar(id, sn, SEC, load_linkexplode) \ + w_cvar(id, sn, SEC, lifetime_min) \ + w_cvar(id, sn, SEC, lifetime_rand) \ + w_cvar(id, sn, NONE, secondary) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +HAGAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_hagar(void) { weapon_defaultspawnfunc(WEP_HAGAR.m_id); } + +// NO bounce protection, as bounces are limited! + +void W_Hagar_Explode(void) +{ + self.event_damage = func_null; + RadiusDamage(self, self.realowner, WEP_CVAR_PRI(hagar, damage), WEP_CVAR_PRI(hagar, edgedamage), WEP_CVAR_PRI(hagar, radius), world, world, WEP_CVAR_PRI(hagar, force), self.projectiledeathtype, other); + + remove(self); +} + +void W_Hagar_Explode2(void) +{ + self.event_damage = func_null; + RadiusDamage(self, self.realowner, WEP_CVAR_SEC(hagar, damage), WEP_CVAR_SEC(hagar, edgedamage), WEP_CVAR_SEC(hagar, radius), world, world, WEP_CVAR_SEC(hagar, force), self.projectiledeathtype, other); + + remove(self); +} + +void W_Hagar_Damage(entity inflictor, entity attacker, float damage, int 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 && WEP_CVAR_SEC(hagar, 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++; + Send_Effect(EFFECT_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(WEP_CVAR_PRI(hagar, ammo)); + + W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hagar, damage)); + + Send_Effect(EFFECT_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 = WEP_CVAR_PRI(hagar, damage); + + missile.takedamage = DAMAGE_YES; + missile.health = WEP_CVAR_PRI(hagar, health); + missile.damageforcescale = WEP_CVAR_PRI(hagar, 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 + WEP_CVAR_PRI(hagar, lifetime); + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_HAGAR.m_id; + setorigin(missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + missile.movetype = MOVETYPE_FLY; + W_SetupProjVelocity_PRI(missile, hagar); + + missile.angles = vectoangles(missile.velocity); + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, true, PROJECTILE_HAGAR, true); + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +void W_Hagar_Attack2(void) +{ + entity missile; + + W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo)); + + W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); + + Send_Effect(EFFECT_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 = WEP_CVAR_SEC(hagar, damage); + + missile.takedamage = DAMAGE_YES; + missile.health = WEP_CVAR_SEC(hagar, health); + missile.damageforcescale = WEP_CVAR_SEC(hagar, 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 + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand); + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_HAGAR.m_id | HITTYPE_SECONDARY; + setorigin(missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + missile.movetype = MOVETYPE_BOUNCEMISSILE; + W_SetupProjVelocity_SEC(missile, hagar); + + missile.angles = vectoangles(missile.velocity); + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, true, PROJECTILE_HAGAR_BOUNCING, true); + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +.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, WEP_CVAR_SEC(hagar, refire)); + + W_SetupShot(self, false, 2, W_Sound("hagar_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hagar, damage)); + Send_Effect(EFFECT_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 = WEP_CVAR_SEC(hagar, damage); + + missile.takedamage = DAMAGE_YES; + missile.health = WEP_CVAR_SEC(hagar, health); + missile.damageforcescale = WEP_CVAR_SEC(hagar, 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 + WEP_CVAR_SEC(hagar, lifetime_min) + random() * WEP_CVAR_SEC(hagar, lifetime_rand); + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_HAGAR.m_id | 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) / (WEP_CVAR_SEC(hagar, load_max) - 1)); + spread_pershot = (1 - (spread_pershot * WEP_CVAR_SEC(hagar, load_spread_bias))); + spread_pershot = (WEP_CVAR_SEC(hagar, 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 * WEP_CVAR_SEC(hagar, load_spread) * g_weaponspreadfactor; + + W_SetupProjVelocity_Explicit(missile, w_shotdir + right * s.y + up * s.z, v_up, WEP_CVAR_SEC(hagar, speed), 0, 0, spread_pershot, false); + + missile.angles = vectoangles(missile.velocity); + missile.flags = FL_PROJECTILE; + + CSQCProjectile(missile, true, PROJECTILE_HAGAR, true); + + MUTATOR_CALLHOOK(EditProjectile, self, missile); + } + + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, load_animtime), w_ready); + self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, 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; + + bool loaded = self.hagar_load >= WEP_CVAR_SEC(hagar, load_max); + + // this is different than WR_CHECKAMMO when it comes to reloading + bool enough_ammo; + if(self.items & IT_UNLIMITED_WEAPON_AMMO) + enough_ammo = true; + else if(autocvar_g_balance_hagar_reload_ammo) + enough_ammo = self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo); + else + enough_ammo = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo); + + bool stopped = loaded || !enough_ammo; + + if(self.BUTTON_ATCK2) + { + if(self.BUTTON_ATCK && WEP_CVAR_SEC(hagar, 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(WEP_CVAR_SEC(hagar, ammo) * self.hagar_load * -1); // give back ammo + self.hagar_load = 0; + sound(self, CH_WEAPON_A, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM); + + // pause until we can load rockets again, once we re-press the alt fire button + self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, 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(!stopped) + { + if(!self.hagar_loadblock && self.hagar_loadstep < time) + { + W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo)); + self.weaponentity.state = WS_INUSE; + self.hagar_load += 1; + sound(self, CH_WEAPON_B, W_Sound("hagar_load"), VOL_BASE * 0.8, ATTN_NORM); // sound is too loud according to most + + if(self.hagar_load >= WEP_CVAR_SEC(hagar, load_max)) + stopped = true; + else + self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_speed) * W_WeaponRateFactor(); + } + } + if(stopped && !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, W_Sound("hagar_beep"), VOL_BASE, ATTN_NORM); + self.hagar_loadbeep = true; + self.hagar_loadstep = time + WEP_CVAR_SEC(hagar, load_hold) * W_WeaponRateFactor(); + } + } + } + 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(stopped && self.hagar_loadstep - 0.5 < time && WEP_CVAR_SEC(hagar, load_hold) >= 0) + { + if(!self.hagar_warning) // 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, W_Sound("hagar_beep"), 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 || (stopped && self.hagar_loadstep < time && WEP_CVAR_SEC(hagar, 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(!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))) + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + // note: this doesn't force the switch + W_SwitchToOtherWeapon(self); + return; + } + } +} + +bool W_Hagar(int req) +{ + float ammo_amount; + switch(req) + { + case WR_AIM: + { + if(random()>0.15) + self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, 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(WEP_CVAR_PRI(hagar, speed), 0, WEP_CVAR_PRI(hagar, lifetime), false); + + return true; + } + case WR_THINK: + { + float loadable_secondary; + loadable_secondary = (WEP_CVAR_SEC(hagar, load) && WEP_CVAR(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(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo))) // forced reload + WEP_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, WEP_CVAR_PRI(hagar, refire))) + { + W_Hagar_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hagar, refire), w_ready); + } + } + else if(self.BUTTON_ATCK2 && !loadable_secondary && WEP_CVAR(hagar, secondary)) + { + if(weapon_prepareattack(1, WEP_CVAR_SEC(hagar, refire))) + { + W_Hagar_Attack2(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hagar, refire), w_ready); + } + } + return true; + } + case 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(); + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_hagar.md3")); + precache_model(W_Model("v_hagar.md3")); + precache_model(W_Model("h_hagar.iqm")); + precache_sound(W_Sound("hagar_fire")); + precache_sound(W_Sound("hagar_load")); + precache_sound(W_Sound("hagar_beep")); + HAGAR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.hagar_loadblock = false; + + if(self.hagar_load) + { + W_DecreaseAmmo(WEP_CVAR_SEC(hagar, ammo) * self.hagar_load * -1); // give back ammo if necessary + self.hagar_load = 0; + } + + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_PRI(hagar, ammo); + ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_PRI(hagar, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + ammo_amount = self.WEP_AMMO(HAGAR) >= WEP_CVAR_SEC(hagar, ammo); + ammo_amount += self.(weapon_load[WEP_HAGAR.m_id]) >= WEP_CVAR_SEC(hagar, ammo); + return ammo_amount; + } + case WR_CONFIG: + { + HAGAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.hagar_load = 0; + return true; + } + case WR_PLAYERDEATH: + { + // if we have any rockets loaded when we die, release them + if(self.hagar_load && WEP_CVAR_SEC(hagar, load_releasedeath)) + W_Hagar_Attack2_Load_Release(); + + return true; + } + case WR_RELOAD: + { + if(!self.hagar_load) // require releasing loaded rockets first + W_Reload(min(WEP_CVAR_PRI(hagar, ammo), WEP_CVAR_SEC(hagar, ammo)), W_Sound("reload")); + + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_HAGAR_SUICIDE; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_HAGAR_MURDER_BURST; + else + return WEAPON_HAGAR_MURDER_SPRAY; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Hagar(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + { + if(w_random<0.15) + sound(self, CH_SHOTS, W_Sound("hagexp1"), VOL_BASE, ATTN_NORM); + else if(w_random<0.7) + sound(self, CH_SHOTS, W_Sound("hagexp2"), VOL_BASE, ATTN_NORM); + else + sound(self, CH_SHOTS, W_Sound("hagexp3"), VOL_BASE, ATTN_NORM); + } + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("hagexp1")); + precache_sound(W_Sound("hagexp2")); + precache_sound(W_Sound("hagexp3")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/hlac.qc b/qcsrc/common/weapons/weapon/hlac.qc new file mode 100644 index 0000000000..49685296a8 --- /dev/null +++ b/qcsrc/common/weapons/weapon/hlac.qc @@ -0,0 +1,314 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ HLAC, +/* function */ W_HLAC, +/* ammotype */ ammo_cells, +/* impulse */ 6, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '0 1 0', +/* modelname */ "hlac", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairhlac 0.6", +/* wepimg */ "weaponhlac", +/* refname */ "hlac", +/* wepname */ _("Heavy Laser Assault Cannon") +); + +#define HLAC_SETTINGS(w_cvar,w_prop) HLAC_SETTINGS_LIST(w_cvar, w_prop, HLAC, hlac) +#define HLAC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, edgedamage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, lifetime) \ + w_cvar(id, sn, BOTH, radius) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, BOTH, spread_crouchmod) \ + w_cvar(id, sn, PRI, spread_add) \ + w_cvar(id, sn, PRI, spread_max) \ + w_cvar(id, sn, PRI, spread_min) \ + w_cvar(id, sn, NONE, secondary) \ + w_cvar(id, sn, SEC, shots) \ + w_cvar(id, sn, SEC, spread) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +HLAC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_hlac(void) { weapon_defaultspawnfunc(WEP_HLAC.m_id); } + +void W_HLAC_Touch(void) +{ + float isprimary; + + PROJECTILE_TOUCH; + + self.event_damage = func_null; + + isprimary = !(self.projectiledeathtype & HITTYPE_SECONDARY); + + RadiusDamage(self, self.realowner, WEP_CVAR_BOTH(hlac, isprimary, damage), WEP_CVAR_BOTH(hlac, isprimary, edgedamage), WEP_CVAR_BOTH(hlac, isprimary, radius), world, world, WEP_CVAR_BOTH(hlac, isprimary, force), self.projectiledeathtype, other); + + remove(self); +} + +void W_HLAC_Attack(void) +{ + entity missile; + float spread; + + W_DecreaseAmmo(WEP_CVAR_PRI(hlac, ammo)); + + spread = WEP_CVAR_PRI(hlac, spread_min) + (WEP_CVAR_PRI(hlac, spread_add) * self.misc_bulletcounter); + spread = min(spread,WEP_CVAR_PRI(hlac, spread_max)); + if(self.crouch) + spread = spread * WEP_CVAR_PRI(hlac, spread_crouchmod); + + W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_PRI(hlac, damage)); + Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + if(!autocvar_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 = WEP_CVAR_PRI(hlac, damage); + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + + setorigin(missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + W_SetupProjVelocity_Basic(missile, WEP_CVAR_PRI(hlac, speed), spread); + //missile.angles = vectoangles(missile.velocity); // csqc + + missile.touch = W_HLAC_Touch; + missile.think = SUB_Remove; + + missile.nextthink = time + WEP_CVAR_PRI(hlac, lifetime); + + missile.flags = FL_PROJECTILE; + missile.projectiledeathtype = WEP_HLAC.m_id; + + CSQCProjectile(missile, true, PROJECTILE_HLAC, true); + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +void W_HLAC_Attack2(void) +{ + entity missile; + float spread; + + spread = WEP_CVAR_SEC(hlac, spread); + + + if(self.crouch) + spread = spread * WEP_CVAR_SEC(hlac, spread_crouchmod); + + W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hlac, damage)); + Send_Effect(EFFECT_BLASTER_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + missile = spawn(); + missile.owner = missile.realowner = self; + missile.classname = "hlacbolt"; + missile.bot_dodge = true; + + missile.bot_dodgerating = WEP_CVAR_SEC(hlac, damage); + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + + setorigin(missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + W_SetupProjVelocity_Basic(missile, WEP_CVAR_SEC(hlac, speed), spread); + //missile.angles = vectoangles(missile.velocity); // csqc + + missile.touch = W_HLAC_Touch; + missile.think = SUB_Remove; + + missile.nextthink = time + WEP_CVAR_SEC(hlac, lifetime); + + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + missile.projectiledeathtype = WEP_HLAC.m_id | HITTYPE_SECONDARY; + + CSQCProjectile(missile, true, PROJECTILE_HLAC, true); + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +// weapon frames +void W_HLAC_Attack_Frame(void) +{ + if(self.weapon != self.switchweapon) // abort immediately if switching + { + w_ready(); + return; + } + + if(self.BUTTON_ATCK) + { + if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1)) + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + ATTACK_FINISHED(self) = time + WEP_CVAR_PRI(hlac, refire) * W_WeaponRateFactor(); + W_HLAC_Attack(); + self.misc_bulletcounter = self.misc_bulletcounter + 1; + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame); + } + else + { + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, animtime), w_ready); + } +} + +void W_HLAC_Attack2_Frame(void) +{ + float i; + + W_DecreaseAmmo(WEP_CVAR_SEC(hlac, ammo)); + + for(i=WEP_CVAR_SEC(hlac, shots);i>0;--i) + W_HLAC_Attack2(); + + if(!autocvar_g_norecoil) + { + self.punchangle_x = random() - 0.5; + self.punchangle_y = random() - 0.5; + } +} + +bool W_HLAC(int req) +{ + float ammo_amount; + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = bot_aim(WEP_CVAR_PRI(hlac, speed), 0, WEP_CVAR_PRI(hlac, lifetime), false); + return true; + } + case WR_THINK: + { + if(autocvar_g_balance_hlac_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(hlac, refire))) + { + self.misc_bulletcounter = 0; + W_HLAC_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hlac, refire), W_HLAC_Attack_Frame); + } + } + + else if(self.BUTTON_ATCK2 && WEP_CVAR(hlac, secondary)) + { + if(weapon_prepareattack(1, WEP_CVAR_SEC(hlac, refire))) + { + W_HLAC_Attack2_Frame(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hlac, animtime), w_ready); + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_hlac.md3")); + precache_model(W_Model("v_hlac.md3")); + precache_model(W_Model("h_hlac.iqm")); + precache_sound(W_Sound("lasergun_fire")); + HLAC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_PRI(hlac, ammo); + ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_PRI(hlac, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + ammo_amount = self.WEP_AMMO(HLAC) >= WEP_CVAR_SEC(hlac, ammo); + ammo_amount += self.(weapon_load[WEP_HLAC.m_id]) >= WEP_CVAR_SEC(hlac, ammo); + return ammo_amount; + } + case WR_CONFIG: + { + HLAC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(min(WEP_CVAR_PRI(hlac, ammo), WEP_CVAR_SEC(hlac, ammo)), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_HLAC_SUICIDE; + } + case WR_KILLMESSAGE: + { + return WEAPON_HLAC_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_HLAC(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("laserimpact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/hmg.qc b/qcsrc/common/weapons/weapon/hmg.qc new file mode 100644 index 0000000000..52b60e83bb --- /dev/null +++ b/qcsrc/common/weapons/weapon/hmg.qc @@ -0,0 +1,208 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ HMG, +/* function */ W_HeavyMachineGun, +/* ammotype */ ammo_nails, +/* impulse */ 3, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_SUPERWEAPON, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '0.5 0.5 0', +/* modelname */ "ok_hmg", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairuzi 0.6", +/* wepimg */ "weaponhmg", +/* refname */ "hmg", +/* wepname */ _("Heavy Machine Gun") +); + +#define HMG_SETTINGS(w_cvar,w_prop) HMG_SETTINGS_LIST(w_cvar, w_prop, HMG, hmg) +#define HMG_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, spread_min) \ + w_cvar(id, sn, NONE, spread_max) \ + w_cvar(id, sn, NONE, spread_add) \ + w_cvar(id, sn, NONE, solidpenetration) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, ammo) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +HMG_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC + +void spawnfunc_weapon_hmg() { weapon_defaultspawnfunc(WEP_HMG.m_id); } + +void W_HeavyMachineGun_Attack_Auto() +{ + if (!self.BUTTON_ATCK) + { + w_ready(); + return; + } + + if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1)) + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + W_DecreaseAmmo(WEP_CVAR(hmg, ammo)); + + W_SetupShot (self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(hmg, damage)); + + if(!autocvar_g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + float hmg_spread = bound(WEP_CVAR(hmg, spread_min), WEP_CVAR(hmg, spread_min) + (WEP_CVAR(hmg, spread_add) * self.misc_bulletcounter), WEP_CVAR(hmg, spread_max)); + fireBullet(w_shotorg, w_shotdir, hmg_spread, WEP_CVAR(hmg, solidpenetration), WEP_CVAR(hmg, damage), WEP_CVAR(hmg, force), WEP_HMG.m_id, 0); + + self.misc_bulletcounter = self.misc_bulletcounter + 1; + + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + W_MachineGun_MuzzleFlash(); + 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 + WEP_CVAR(hmg, refire) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(hmg, refire), W_HeavyMachineGun_Attack_Auto); +} + +bool W_HeavyMachineGun(int req) +{ + float ammo_amount; + switch(req) + { + case 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); + + return true; + } + case WR_THINK: + { + if(WEP_CVAR(hmg, reload_ammo) && self.clip_load < WEP_CVAR(hmg, ammo)) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else + { + if (self.BUTTON_ATCK) + if (weapon_prepareattack(0, 0)) + { + self.misc_bulletcounter = 0; + W_HeavyMachineGun_Attack_Auto(); + } + } + + return true; + } + case WR_INIT: + { + precache_model ("models/uziflash.md3"); + precache_model(W_Model("g_ok_hmg.md3")); + precache_model(W_Model("v_ok_hmg.md3")); + precache_model(W_Model("h_ok_hmg.iqm")); + precache_sound (W_Sound("uzi_fire")); + HMG_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo); + + if(autocvar_g_balance_hmg_reload_ammo) + ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo); + + return ammo_amount; + } + case WR_CHECKAMMO2: + { + ammo_amount = self.ammo_nails >= WEP_CVAR(hmg, ammo); + + if(autocvar_g_balance_hmg_reload_ammo) + ammo_amount += self.(weapon_load[WEP_HMG.m_id]) >= WEP_CVAR(hmg, ammo); + + return ammo_amount; + } + case WR_CONFIG: + { + HMG_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR(hmg, ammo), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_HMG_MURDER_SNIPE; + else + return WEAPON_HMG_MURDER_SPRAY; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_HeavyMachineGun(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1); + if(!w_issilent) + if(w_random < 0.05) + sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM); + else if(w_random < 0.1) + sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM); + else if(w_random < 0.2) + sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("ric1")); + precache_sound(W_Sound("ric2")); + precache_sound(W_Sound("ric3")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/hook.qc b/qcsrc/common/weapons/weapon/hook.qc new file mode 100644 index 0000000000..6c5519e60b --- /dev/null +++ b/qcsrc/common/weapons/weapon/hook.qc @@ -0,0 +1,368 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ HOOK, +/* function */ W_Hook, +/* ammotype */ ammo_fuel, +/* impulse */ 0, +/* flags */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ 0, +/* color */ '0 0.5 0', +/* modelname */ "hookgun", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairhook 0.5", +/* wepimg */ "weaponhook", +/* refname */ "hook", +/* wepname */ _("Grappling Hook") +); + +#define HOOK_SETTINGS(w_cvar,w_prop) HOOK_SETTINGS_LIST(w_cvar, w_prop, HOOK, hook) +#define HOOK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, PRI, ammo) \ + w_cvar(id, sn, PRI, hooked_ammo) \ + w_cvar(id, sn, PRI, hooked_time_free) \ + w_cvar(id, sn, PRI, hooked_time_max) \ + w_cvar(id, sn, SEC, damage) \ + w_cvar(id, sn, SEC, duration) \ + w_cvar(id, sn, SEC, edgedamage) \ + w_cvar(id, sn, SEC, force) \ + w_cvar(id, sn, SEC, gravity) \ + w_cvar(id, sn, SEC, lifetime) \ + w_cvar(id, sn, SEC, power) \ + w_cvar(id, sn, SEC, radius) \ + w_cvar(id, sn, SEC, speed) \ + w_cvar(id, sn, SEC, health) \ + w_cvar(id, sn, SEC, damageforcescale) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +HOOK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) + +.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; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC + +void spawnfunc_weapon_hook(void) +{ + if(g_grappling_hook) // offhand hook + { + startitem_failed = true; + remove(self); + return; + } + weapon_defaultspawnfunc(WEP_HOOK.m_id); +} + +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 = WEP_CVAR_SEC(hook, damage); + self.dmg_edge = WEP_CVAR_SEC(hook, edgedamage); + self.dmg_radius = WEP_CVAR_SEC(hook, radius); + self.dmg_force = WEP_CVAR_SEC(hook, force); + self.dmg_power = WEP_CVAR_SEC(hook, power); + self.dmg_duration = WEP_CVAR_SEC(hook, duration); + self.teleport_time = time; + self.dmg_last = 1; + self.movetype = MOVETYPE_NONE; +} + +void W_Hook_Damage(entity inflictor, entity attacker, float damage, int 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(void) +{ + entity gren; + + //W_DecreaseAmmo(WEP_CVAR_SEC(hook, ammo)); // WEAPONTODO: Figure out how to handle ammo with hook secondary (gravitybomb) + W_SetupShot(self, false, 4, W_Sound("hookbomb_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hook, damage)); + + gren = spawn(); + gren.owner = gren.realowner = self; + gren.classname = "hookbomb"; + gren.bot_dodge = true; + gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage); + gren.movetype = MOVETYPE_TOSS; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_HOOK.m_id | HITTYPE_SECONDARY; + setorigin(gren, w_shotorg); + setsize(gren, '0 0 0', '0 0 0'); + + gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime); + gren.think = adaptor_think2use_hittype_splash; + gren.use = W_Hook_Explode2; + gren.touch = W_Hook_Touch2; + + gren.takedamage = DAMAGE_YES; + gren.health = WEP_CVAR_SEC(hook, health); + gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale); + gren.event_damage = W_Hook_Damage; + gren.damagedbycontents = true; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + + gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed); + if(autocvar_g_projectiles_newton_style) + gren.velocity = gren.velocity + self.velocity; + + gren.gravity = WEP_CVAR_SEC(hook, gravity); + //W_SetupProjVelocity_Basic(gren); // just falling down! + + gren.angles = '0 0 0'; + gren.flags = FL_PROJECTILE; + + CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true); + + MUTATOR_CALLHOOK(EditProjectile, self, gren); +} + +bool W_Hook(int req) +{ + float hooked_time_max, hooked_fuel; + + switch(req) + { + case WR_AIM: + { + // no bot AI for hook (yet?) + return true; + } + case WR_THINK: + { + if(self.BUTTON_ATCK || self.BUTTON_HOOK) + { + if(!self.hook) + if(!(self.hook_state & HOOK_WAITING_FOR_RELEASE)) + if(!(self.hook_state & HOOK_FIRING)) + if(time > self.hook_refire) + if(weapon_prepareattack(0, -1)) + { + W_DecreaseAmmo(WEP_CVAR_PRI(hook, ammo)); + self.hook_state |= HOOK_FIRING; + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready); + } + } + + if(self.BUTTON_ATCK2) + { + if(weapon_prepareattack(1, WEP_CVAR_SEC(hook, refire))) + { + W_Hook_Attack2(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready); + } + } + + if(self.hook) + { + // if hooked, no bombs, and increase the timer + self.hook_refire = max(self.hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor()); + + // hook also inhibits health regeneration, but only for 1 second + if(!(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 = WEP_CVAR_PRI(hook, hooked_time_max); + if(hooked_time_max > 0) + { + if( time > self.hook_time_hooked + hooked_time_max ) + self.hook_state |= HOOK_REMOVING; + } + + hooked_fuel = WEP_CVAR_PRI(hook, hooked_ammo); + if(hooked_fuel > 0) + { + if( time > self.hook_time_fueldecrease ) + { + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + if( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel ) + { + W_DecreaseAmmo((time - self.hook_time_fueldecrease) * hooked_fuel); + 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 + WEP_CVAR_PRI(hook, hooked_time_free); + } + + if(self.BUTTON_CROUCH) + { + self.hook_state &= ~HOOK_PULLING; + if(self.BUTTON_ATCK || 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.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; + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_hookgun.md3")); + precache_model(W_Model("v_hookgun.md3")); + precache_model(W_Model("h_hookgun.iqm")); + precache_sound(W_Sound("hook_impact")); // done by g_hook.qc + precache_sound(W_Sound("hook_fire")); + precache_sound(W_Sound("hookbomb_fire")); + HOOK_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.hook_state &= ~HOOK_WAITING_FOR_RELEASE; + return true; + } + case WR_CHECKAMMO1: + { + if(self.hook) + return self.ammo_fuel > 0; + else + return self.ammo_fuel >= WEP_CVAR_PRI(hook, ammo); + } + case WR_CHECKAMMO2: + { + // infinite ammo for now + return true; // self.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above + } + case WR_CONFIG: + { + HOOK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.hook_refire = time; + return true; + } + case WR_SUICIDEMESSAGE: + { + return false; + } + case WR_KILLMESSAGE: + { + return WEAPON_HOOK_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Hook(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum(EFFECT_HOOK_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("hookbomb_impact"), VOL_BASE, ATTN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("hookbomb_impact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/machinegun.qc b/qcsrc/common/weapons/weapon/machinegun.qc new file mode 100644 index 0000000000..da1eb33a60 --- /dev/null +++ b/qcsrc/common/weapons/weapon/machinegun.qc @@ -0,0 +1,408 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ MACHINEGUN, +/* function */ W_MachineGun, +/* ammotype */ ammo_nails, +/* impulse */ 3, +/* flags */ WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '1 1 0', +/* modelname */ "uzi", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairuzi 0.6", +/* wepimg */ "weaponuzi", +/* refname */ "machinegun", +/* wepname */ _("Machine Gun") +); + +#define MACHINEGUN_SETTINGS(w_cvar,w_prop) MACHINEGUN_SETTINGS_LIST(w_cvar, w_prop, MACHINEGUN, machinegun) +#define MACHINEGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, spread_min) \ + w_cvar(id, sn, NONE, spread_max) \ + w_cvar(id, sn, NONE, spread_add) \ + w_cvar(id, sn, NONE, mode) \ + w_cvar(id, sn, NONE, first) \ + w_cvar(id, sn, NONE, first_damage) \ + w_cvar(id, sn, NONE, first_force) \ + w_cvar(id, sn, NONE, first_refire) \ + w_cvar(id, sn, NONE, first_spread) \ + w_cvar(id, sn, NONE, first_ammo) \ + w_cvar(id, sn, NONE, solidpenetration) \ + w_cvar(id, sn, NONE, sustained_damage) \ + w_cvar(id, sn, NONE, sustained_force) \ + w_cvar(id, sn, NONE, sustained_refire) \ + w_cvar(id, sn, NONE, sustained_spread) \ + w_cvar(id, sn, NONE, sustained_ammo) \ + w_cvar(id, sn, NONE, burst) \ + w_cvar(id, sn, NONE, burst_refire) \ + w_cvar(id, sn, NONE, burst_refire2) \ + w_cvar(id, sn, NONE, burst_animtime) \ + w_cvar(id, sn, NONE, burst_speed) \ + w_cvar(id, sn, NONE, burst_ammo) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +MACHINEGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC + +void spawnfunc_weapon_machinegun(void) +{ + if(autocvar_sv_q3acompat_machineshotgunswap) + if(self.classname != "droppedweapon") + { + weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id); + return; + } + weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id); +} +void spawnfunc_weapon_uzi(void) { spawnfunc_weapon_machinegun(); } + +void W_MachineGun_MuzzleFlash_Think(void) +{ + 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 W_MachineGun_MuzzleFlash(void) +{ + 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 = W_MachineGun_MuzzleFlash_Think; + 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_MachineGun_Attack(int deathtype) +{ + W_SetupShot(self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? WEP_CVAR(machinegun, first_damage) : WEP_CVAR(machinegun, sustained_damage))); + if(!autocvar_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 + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(); + + if(self.misc_bulletcounter == 1) + fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, first_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, first_damage), WEP_CVAR(machinegun, first_force), deathtype, 0); + else + fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, sustained_spread), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), deathtype, 0); + + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + W_MachineGun_MuzzleFlash(); + 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(WEP_CVAR(machinegun, first_ammo)); + else + W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo)); +} + +// weapon frames +void W_MachineGun_Attack_Frame(void) +{ + if(self.weapon != self.switchweapon) // abort immediately if switching + { + w_ready(); + return; + } + if(self.BUTTON_ATCK) + { + if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + self.misc_bulletcounter = self.misc_bulletcounter + 1; + W_MachineGun_Attack(WEP_MACHINEGUN.m_id); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame); + } + else + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), w_ready); +} + + +void W_MachineGun_Attack_Auto(void) +{ + float machinegun_spread; + + if(!self.BUTTON_ATCK) + { + w_ready(); + return; + } + + if(!WEP_ACTION(self.weapon, WR_CHECKAMMO1)) + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + W_DecreaseAmmo(WEP_CVAR(machinegun, sustained_ammo)); + + W_SetupShot(self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); + if(!autocvar_g_norecoil) + { + self.punchangle_x = random() - 0.5; + self.punchangle_y = random() - 0.5; + } + + machinegun_spread = bound(WEP_CVAR(machinegun, spread_min), WEP_CVAR(machinegun, spread_min) + (WEP_CVAR(machinegun, spread_add) * self.misc_bulletcounter), WEP_CVAR(machinegun, spread_max)); + fireBullet(w_shotorg, w_shotdir, machinegun_spread, WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0); + + self.misc_bulletcounter = self.misc_bulletcounter + 1; + + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + W_MachineGun_MuzzleFlash(); + 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 + WEP_CVAR(machinegun, first_refire) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Auto); +} + +void W_MachineGun_Attack_Burst(void) +{ + W_SetupShot(self, true, 0, W_Sound("uzi_fire"), CH_WEAPON_A, WEP_CVAR(machinegun, sustained_damage)); + if(!autocvar_g_norecoil) + { + self.punchangle_x = random() - 0.5; + self.punchangle_y = random() - 0.5; + } + + fireBullet(w_shotorg, w_shotdir, WEP_CVAR(machinegun, burst_speed), WEP_CVAR(machinegun, solidpenetration), WEP_CVAR(machinegun, sustained_damage), WEP_CVAR(machinegun, sustained_force), WEP_MACHINEGUN.m_id, 0); + + Send_Effect(EFFECT_MACHINEGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + W_MachineGun_MuzzleFlash(); + 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 + WEP_CVAR(machinegun, burst_refire2) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_animtime), w_ready); + } + else + { + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, burst_refire), W_MachineGun_Attack_Burst); + } + +} + +bool W_MachineGun(int req) +{ + float ammo_amount; + switch(req) + { + case 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); + + return true; + } + case WR_THINK: + { + if(WEP_CVAR(machinegun, reload_ammo) && self.clip_load < min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else if(WEP_CVAR(machinegun, mode) == 1) + { + if(self.BUTTON_ATCK) + if(weapon_prepareattack(0, 0)) + { + self.misc_bulletcounter = 0; + W_MachineGun_Attack_Auto(); + } + + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, 0)) + { + if(!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return false; + } + + W_DecreaseAmmo(WEP_CVAR(machinegun, burst_ammo)); + + self.misc_bulletcounter = WEP_CVAR(machinegun, burst) * -1; + W_MachineGun_Attack_Burst(); + } + } + else + { + + if(self.BUTTON_ATCK) + if(weapon_prepareattack(0, 0)) + { + self.misc_bulletcounter = 1; + W_MachineGun_Attack(WEP_MACHINEGUN.m_id); // sets attack_finished + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(machinegun, sustained_refire), W_MachineGun_Attack_Frame); + } + + if(self.BUTTON_ATCK2 && WEP_CVAR(machinegun, first)) + if(weapon_prepareattack(1, 0)) + { + self.misc_bulletcounter = 1; + W_MachineGun_Attack(WEP_MACHINEGUN.m_id | HITTYPE_SECONDARY); // sets attack_finished + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(machinegun, first_refire), w_ready); + } + } + + return true; + } + case WR_INIT: + { + precache_model("models/uziflash.md3"); + precache_model(W_Model("g_uzi.md3")); + precache_model(W_Model("v_uzi.md3")); + precache_model(W_Model("h_uzi.iqm")); + precache_sound(W_Sound("uzi_fire")); + MACHINEGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + if(WEP_CVAR(machinegun, mode) == 1) + ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, sustained_ammo); + else + ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo); + + if(WEP_CVAR(machinegun, reload_ammo)) + { + if(WEP_CVAR(machinegun, mode) == 1) + ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, sustained_ammo); + else + ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo); + } + return ammo_amount; + } + case WR_CHECKAMMO2: + { + if(WEP_CVAR(machinegun, mode) == 1) + ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, burst_ammo); + else + ammo_amount = self.WEP_AMMO(MACHINEGUN) >= WEP_CVAR(machinegun, first_ammo); + + if(WEP_CVAR(machinegun, reload_ammo)) + { + if(WEP_CVAR(machinegun, mode) == 1) + ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, burst_ammo); + else + ammo_amount += self.(weapon_load[WEP_MACHINEGUN.m_id]) >= WEP_CVAR(machinegun, first_ammo); + } + return ammo_amount; + } + case WR_CONFIG: + { + MACHINEGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(min(max(WEP_CVAR(machinegun, sustained_ammo), WEP_CVAR(machinegun, first_ammo)), WEP_CVAR(machinegun, burst_ammo)), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_MACHINEGUN_MURDER_SNIPE; + else + return WEAPON_MACHINEGUN_MURDER_SPRAY; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_MachineGun(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum(EFFECT_MACHINEGUN_IMPACT), org2, w_backoff * 1000, 1); + if(!w_issilent) + if(w_random < 0.05) + sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM); + else if(w_random < 0.1) + sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM); + else if(w_random < 0.2) + sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("ric1")); + precache_sound(W_Sound("ric2")); + precache_sound(W_Sound("ric3")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/minelayer.qc b/qcsrc/common/weapons/weapon/minelayer.qc new file mode 100644 index 0000000000..8048957bce --- /dev/null +++ b/qcsrc/common/weapons/weapon/minelayer.qc @@ -0,0 +1,623 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ MINE_LAYER, +/* function */ W_MineLayer, +/* ammotype */ ammo_rockets, +/* impulse */ 4, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '0.75 1 0', +/* modelname */ "minelayer", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairminelayer 0.9", +/* wepimg */ "weaponminelayer", +/* refname */ "minelayer", +/* wepname */ _("Mine Layer") +); + +#define MINELAYER_SETTINGS(w_cvar,w_prop) MINELAYER_SETTINGS_LIST(w_cvar, w_prop, MINE_LAYER, minelayer) +#define MINELAYER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, ammo) \ + w_cvar(id, sn, NONE, animtime) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, damageforcescale) \ + w_cvar(id, sn, NONE, detonatedelay) \ + w_cvar(id, sn, NONE, edgedamage) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, health) \ + w_cvar(id, sn, NONE, lifetime) \ + w_cvar(id, sn, NONE, lifetime_countdown) \ + w_cvar(id, sn, NONE, limit) \ + w_cvar(id, sn, NONE, protection) \ + w_cvar(id, sn, NONE, proximityradius) \ + w_cvar(id, sn, NONE, radius) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, remote_damage) \ + w_cvar(id, sn, NONE, remote_edgedamage) \ + w_cvar(id, sn, NONE, remote_force) \ + w_cvar(id, sn, NONE, remote_radius) \ + w_cvar(id, sn, NONE, speed) \ + w_cvar(id, sn, NONE, time) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +MINELAYER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +void W_MineLayer_Think(void); +.float minelayer_detonate, mine_explodeanyway; +.float mine_time; +.vector mine_orientation; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER.m_id); } + +void W_MineLayer_Stick(entity to) +{ + spamsound(self, CH_SHOTS, W_Sound("mine_stick"), 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_MineLayer_Think; + newmine.nextthink = time; + newmine.cnt = self.cnt; + newmine.flags = self.flags; + + remove(self); + self = newmine; + + if(to) + SetMovetypeFollow(self, to); +} + +void W_MineLayer_Explode(void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(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, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), world, world, WEP_CVAR(minelayer, force), self.projectiledeathtype, other); + + if(self.realowner.weapon == WEP_MINE_LAYER.m_id) + { + entity oldself; + oldself = self; + self = self.realowner; + if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER.m_id; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove(self); +} + +void W_MineLayer_DoRemoteExplode(void) +{ + 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, WEP_CVAR(minelayer, remote_damage), WEP_CVAR(minelayer, remote_edgedamage), WEP_CVAR(minelayer, remote_radius), world, world, WEP_CVAR(minelayer, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world); + + if(self.realowner.weapon == WEP_MINE_LAYER.m_id) + { + entity oldself; + oldself = self; + self = self.realowner; + if(!WEP_ACTION(WEP_MINE_LAYER.m_id, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER.m_id; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove(self); +} + +void W_MineLayer_RemoteExplode(void) +{ + if(self.realowner.deadflag == DEAD_NO) + if((self.spawnshieldtime >= 0) + ? (time >= self.spawnshieldtime) // timer + : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(minelayer, remote_radius)) // safety device + ) + { + W_MineLayer_DoRemoteExplode(); + } +} + +void W_MineLayer_ProximityExplode(void) +{ + // 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(WEP_CVAR(minelayer, protection) && self.mine_explodeanyway == 0) + { + entity head; + head = findradius(self.origin, WEP_CVAR(minelayer, radius)); + while(head) + { + if(head == self.realowner || SAME_TEAM(head, self.realowner)) + return; + head = head.chain; + } + } + + self.mine_time = 0; + W_MineLayer_Explode(); +} + +int W_MineLayer_Count(entity e) +{ + int minecount = 0; + entity mine; + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e) + minecount += 1; + + return minecount; +} + +void W_MineLayer_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) && (self.cnt > 0)) + { + if(WEP_CVAR(minelayer, lifetime_countdown) > 0) + spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM); + self.mine_time = time + WEP_CVAR(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 || self.realowner.frozen) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_MineLayer_Explode(); + return; + } + + // set the mine for detonation when a foe gets close enough + head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius)); + while(head) + { + if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen) + if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates + if(!self.mine_time) + { + spamsound(self, CH_SHOTS, W_Sound("mine_trigger"), VOL_BASE, ATTN_NORM); + self.mine_time = time + WEP_CVAR(minelayer, time); + } + head = head.chain; + } + + // explode if it's time to + if(self.mine_time && time >= self.mine_time) + { + W_MineLayer_ProximityExplode(); + return; + } + + // remote detonation + if(self.realowner.weapon == WEP_MINE_LAYER.m_id) + if(self.realowner.deadflag == DEAD_NO) + if(self.minelayer_detonate) + W_MineLayer_RemoteExplode(); +} + +void W_MineLayer_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_MineLayer_Stick(other); + } +} + +void W_MineLayer_Damage(entity inflictor, entity attacker, float damage, int 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_MineLayer_Explode); +} + +void W_MineLayer_Attack(void) +{ + entity mine; + entity flash; + + // scan how many mines we placed, and return if we reached our limit + if(WEP_CVAR(minelayer, limit)) + { + if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) + { + // the refire delay keeps this message from being spammed + Send_Notification(NOTIF_ONE, self, MSG_MULTI, WEAPON_MINELAYER_LIMIT, WEP_CVAR(minelayer, limit)); + play2(self, W_Sound("unavailable")); + return; + } + } + + W_DecreaseAmmo(WEP_CVAR(minelayer, ammo)); + + W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', false, 5, W_Sound("mine_fire"), CH_WEAPON_A, WEP_CVAR(minelayer, damage)); + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + mine = WarpZone_RefSys_SpawnSameRefSys(self); + mine.owner = mine.realowner = self; + if(WEP_CVAR(minelayer, detonatedelay) >= 0) + mine.spawnshieldtime = time + WEP_CVAR(minelayer, detonatedelay); + else + mine.spawnshieldtime = -1; + mine.classname = "mine"; + mine.bot_dodge = true; + mine.bot_dodgerating = WEP_CVAR(minelayer, damage) * 2; // * 2 because it can detonate inflight which makes it even more dangerous + + mine.takedamage = DAMAGE_YES; + mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale); + mine.health = WEP_CVAR(minelayer, health); + mine.event_damage = W_MineLayer_Damage; + mine.damagedbycontents = true; + + mine.movetype = MOVETYPE_TOSS; + PROJECTILE_MAKETRIGGER(mine); + mine.projectiledeathtype = WEP_MINE_LAYER.m_id; + 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_SetupProjVelocity_Basic(mine, WEP_CVAR(minelayer, speed), 0); + mine.angles = vectoangles(mine.velocity); + + mine.touch = W_MineLayer_Touch; + mine.think = W_MineLayer_Think; + mine.nextthink = time; + mine.cnt = (WEP_CVAR(minelayer, lifetime) - WEP_CVAR(minelayer, lifetime_countdown)); + mine.flags = FL_PROJECTILE; + mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY; + + if(mine.cnt > 0) { mine.cnt += time; } + + 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 + + MUTATOR_CALLHOOK(EditProjectile, self, mine); + + self.minelayer_mines = W_MineLayer_Count(self); +} + +float W_MineLayer_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; +} + +bool W_MineLayer(int req) +{ + entity mine; + float ammo_amount; + switch(req) + { + case WR_AIM: + { + // aim and decide to fire if appropriate + if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) + self.BUTTON_ATCK = false; + else + self.BUTTON_ATCK = bot_aim(WEP_CVAR(minelayer, speed), 0, WEP_CVAR(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 = WEP_CVAR(minelayer, edgedamage); + coredamage = WEP_CVAR(minelayer, damage); + edgeradius = WEP_CVAR(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; + } + + return true; + } + case WR_THINK: + { + if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < WEP_CVAR(minelayer, ammo)) // forced reload + { + // not if we're holding the minelayer without enough ammo, but can detonate existing mines + if(!(W_MineLayer_PlacedMines(false) && self.WEP_AMMO(MINE_LAYER) < WEP_CVAR(minelayer, ammo))) + WEP_ACTION(self.weapon, WR_RELOAD); + } + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire))) + { + W_MineLayer_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready); + } + } + + if(self.BUTTON_ATCK2) + { + if(W_MineLayer_PlacedMines(true)) + sound(self, CH_WEAPON_B, W_Sound("mine_det"), VOL_BASE, ATTN_NORM); + } + + return true; + } + case WR_INIT: + { + precache_model("models/flash.md3"); + precache_model("models/mine.md3"); + precache_model(W_Model("g_minelayer.md3")); + precache_model(W_Model("v_minelayer.md3")); + precache_model(W_Model("h_minelayer.iqm")); + precache_sound(W_Sound("mine_det")); + precache_sound(W_Sound("mine_fire")); + precache_sound(W_Sound("mine_stick")); + precache_sound(W_Sound("mine_trigger")); + MINELAYER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + // don't switch while placing a mine + if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER.m_id) + { + ammo_amount = self.WEP_AMMO(MINE_LAYER) >= WEP_CVAR(minelayer, ammo); + ammo_amount += self.(weapon_load[WEP_MINE_LAYER.m_id]) >= WEP_CVAR(minelayer, ammo); + return ammo_amount; + } + return true; + } + case WR_CHECKAMMO2: + { + if(W_MineLayer_PlacedMines(false)) + return true; + else + return false; + } + case WR_CONFIG: + { + MINELAYER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.minelayer_mines = 0; + return true; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR(minelayer, ammo), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_MINELAYER_SUICIDE; + } + case WR_KILLMESSAGE: + { + return WEAPON_MINELAYER_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_MineLayer(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("mine_exp"), VOL_BASE, ATTN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("mine_exp")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/mortar.qc b/qcsrc/common/weapons/weapon/mortar.qc new file mode 100644 index 0000000000..850a6c342f --- /dev/null +++ b/qcsrc/common/weapons/weapon/mortar.qc @@ -0,0 +1,491 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ MORTAR, +/* function */ W_Mortar, +/* ammotype */ ammo_rockets, +/* impulse */ 4, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '1 0 0', +/* modelname */ "gl", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairgrenadelauncher 0.7", +/* wepimg */ "weapongrenadelauncher", +/* refname */ "mortar", +/* wepname */ _("Mortar") +); + +#define MORTAR_SETTINGS(w_cvar,w_prop) MORTAR_SETTINGS_LIST(w_cvar, w_prop, MORTAR, mortar) +#define MORTAR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, NONE, bouncefactor) \ + w_cvar(id, sn, NONE, bouncestop) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, damageforcescale) \ + w_cvar(id, sn, BOTH, edgedamage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, health) \ + w_cvar(id, sn, BOTH, lifetime) \ + w_cvar(id, sn, SEC, lifetime_bounce) \ + w_cvar(id, sn, BOTH, lifetime_stick) \ + w_cvar(id, sn, BOTH, radius) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, SEC, remote_detonateprimary) \ + w_cvar(id, sn, PRI, remote_minbouncecnt) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, BOTH, speed_up) \ + w_cvar(id, sn, BOTH, speed_z) \ + w_cvar(id, sn, BOTH, spread) \ + w_cvar(id, sn, BOTH, type) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +MORTAR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float gl_detonate_later; +.float gl_bouncecnt; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC + +void spawnfunc_weapon_mortar(void) { weapon_defaultspawnfunc(WEP_MORTAR.m_id); } +void spawnfunc_weapon_grenadelauncher(void) { spawnfunc_weapon_mortar(); } + +void W_Mortar_Grenade_Explode(void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(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, WEP_CVAR_PRI(mortar, damage), WEP_CVAR_PRI(mortar, edgedamage), WEP_CVAR_PRI(mortar, radius), world, world, WEP_CVAR_PRI(mortar, force), self.projectiledeathtype, other); + + remove(self); +} + +void W_Mortar_Grenade_Explode2(void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(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, WEP_CVAR_SEC(mortar, damage), WEP_CVAR_SEC(mortar, edgedamage), WEP_CVAR_SEC(mortar, radius), world, world, WEP_CVAR_SEC(mortar, force), self.projectiledeathtype, other); + + remove(self); +} + + +void W_Mortar_Grenade_Damage(entity inflictor, entity attacker, float damage, int 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_Mortar_Grenade_Think1(void) +{ + self.nextthink = time; + if(time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Mortar_Grenade_Explode(); + return; + } + if(self.gl_detonate_later && self.gl_bouncecnt >= WEP_CVAR_PRI(mortar, remote_minbouncecnt)) + W_Mortar_Grenade_Explode(); +} + +void W_Mortar_Grenade_Touch1(void) +{ + PROJECTILE_TOUCH; + if(other.takedamage == DAMAGE_AIM || WEP_CVAR_PRI(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile + { + self.use(); + } + else if(WEP_CVAR_PRI(mortar, type) == 1) // bounce + { + float r; + r = random() * 6; + if(r < 1) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM); + else if(r < 2) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM); + else if(r < 3) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM); + else if(r < 4) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM); + else if(r < 5) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM); + else + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce6"), VOL_BASE, ATTN_NORM); + Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1); + self.projectiledeathtype |= HITTYPE_BOUNCE; + self.gl_bouncecnt += 1; + } + else if(WEP_CVAR_PRI(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick + { + spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), 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 + WEP_CVAR_PRI(mortar, lifetime_stick)); + } +} + +void W_Mortar_Grenade_Touch2(void) +{ + PROJECTILE_TOUCH; + if(other.takedamage == DAMAGE_AIM || WEP_CVAR_SEC(mortar, type) == 0) // always explode when hitting a player, or if normal mortar projectile + { + self.use(); + } + else if(WEP_CVAR_SEC(mortar, type) == 1) // bounce + { + float r; + r = random() * 6; + if(r < 1) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce1"), VOL_BASE, ATTN_NORM); + else if(r < 2) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce2"), VOL_BASE, ATTN_NORM); + else if(r < 3) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce3"), VOL_BASE, ATTN_NORM); + else if(r < 4) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce4"), VOL_BASE, ATTN_NORM); + else if(r < 5) + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce5"), VOL_BASE, ATTN_NORM); + else + spamsound(self, CH_SHOTS, W_Sound("grenade_bounce6"), VOL_BASE, ATTN_NORM); + Send_Effect(EFFECT_HAGAR_BOUNCE, self.origin, self.velocity, 1); + self.projectiledeathtype |= HITTYPE_BOUNCE; + self.gl_bouncecnt += 1; + + if(WEP_CVAR_SEC(mortar, lifetime_bounce) && self.gl_bouncecnt == 1) + self.nextthink = time + WEP_CVAR_SEC(mortar, lifetime_bounce); + + } + else if(WEP_CVAR_SEC(mortar, type) == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick + { + spamsound(self, CH_SHOTS, W_Sound("grenade_stick"), 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 + WEP_CVAR_SEC(mortar, lifetime_stick)); + } +} + +void W_Mortar_Attack(void) +{ + entity gren; + + W_DecreaseAmmo(WEP_CVAR_PRI(mortar, ammo)); + + W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 4, W_Sound("grenade_fire"), CH_WEAPON_A, WEP_CVAR_PRI(mortar, damage)); + w_shotdir = v_forward; // no TrueAim for grenades please + + Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + gren = spawn(); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = true; + gren.bot_dodgerating = WEP_CVAR_PRI(mortar, damage); + gren.movetype = MOVETYPE_BOUNCE; + gren.bouncefactor = WEP_CVAR(mortar, bouncefactor); + gren.bouncestop = WEP_CVAR(mortar, bouncestop); + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_MORTAR.m_id; + setorigin(gren, w_shotorg); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.cnt = time + WEP_CVAR_PRI(mortar, lifetime); + gren.nextthink = time; + gren.think = W_Mortar_Grenade_Think1; + gren.use = W_Mortar_Grenade_Explode; + gren.touch = W_Mortar_Grenade_Touch1; + + gren.takedamage = DAMAGE_YES; + gren.health = WEP_CVAR_PRI(mortar, health); + gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale); + gren.event_damage = W_Mortar_Grenade_Damage; + gren.damagedbycontents = true; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SetupProjVelocity_UP_PRI(gren, mortar); + + gren.angles = vectoangles(gren.velocity); + gren.flags = FL_PROJECTILE; + + if(WEP_CVAR_PRI(mortar, type) == 0 || WEP_CVAR_PRI(mortar, type) == 2) + CSQCProjectile(gren, true, PROJECTILE_GRENADE, true); + else + CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true); + + MUTATOR_CALLHOOK(EditProjectile, self, gren); +} + +void W_Mortar_Attack2(void) +{ + entity gren; + + W_DecreaseAmmo(WEP_CVAR_SEC(mortar, ammo)); + + W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 4, W_Sound("grenade_fire"), CH_WEAPON_A, WEP_CVAR_SEC(mortar, damage)); + w_shotdir = v_forward; // no TrueAim for grenades please + + Send_Effect(EFFECT_GRENADE_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + gren = spawn(); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = true; + gren.bot_dodgerating = WEP_CVAR_SEC(mortar, damage); + gren.movetype = MOVETYPE_BOUNCE; + gren.bouncefactor = WEP_CVAR(mortar, bouncefactor); + gren.bouncestop = WEP_CVAR(mortar, bouncestop); + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_MORTAR.m_id | HITTYPE_SECONDARY; + setorigin(gren, w_shotorg); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.nextthink = time + WEP_CVAR_SEC(mortar, lifetime); + gren.think = adaptor_think2use_hittype_splash; + gren.use = W_Mortar_Grenade_Explode2; + gren.touch = W_Mortar_Grenade_Touch2; + + gren.takedamage = DAMAGE_YES; + gren.health = WEP_CVAR_SEC(mortar, health); + gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale); + gren.event_damage = W_Mortar_Grenade_Damage; + gren.damagedbycontents = true; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SetupProjVelocity_UP_SEC(gren, mortar); + + gren.angles = vectoangles(gren.velocity); + gren.flags = FL_PROJECTILE; + + if(WEP_CVAR_SEC(mortar, type) == 0 || WEP_CVAR_SEC(mortar, type) == 2) + CSQCProjectile(gren, true, PROJECTILE_GRENADE, true); + else + CSQCProjectile(gren, true, PROJECTILE_GRENADE_BOUNCING, true); + + MUTATOR_CALLHOOK(EditProjectile, self, gren); +} + +.float bot_secondary_grenademooth; +bool W_Mortar(int req) +{ + entity nade; + float nadefound; + float ammo_amount; + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = false; + self.BUTTON_ATCK2 = false; + if(self.bot_secondary_grenademooth == 0) // WEAPONTODO: merge this into using WEP_CVAR_BOTH + { + if(bot_aim(WEP_CVAR_PRI(mortar, speed), WEP_CVAR_PRI(mortar, speed_up), WEP_CVAR_PRI(mortar, lifetime), true)) + { + self.BUTTON_ATCK = true; + if(random() < 0.01) self.bot_secondary_grenademooth = 1; + } + } + else + { + if(bot_aim(WEP_CVAR_SEC(mortar, speed), WEP_CVAR_SEC(mortar, speed_up), WEP_CVAR_SEC(mortar, lifetime), true)) + { + self.BUTTON_ATCK2 = true; + if(random() < 0.02) self.bot_secondary_grenademooth = 0; + } + } + + return true; + } + /*case WR_CALCINFO: + { + wepinfo_pri_refire = max3(sys_frametime, WEP_CVAR_PRI(mortar, refire), WEP_CVAR_PRI(mortar, animtime)); + wepinfo_pri_dps = (WEP_CVAR_PRI(mortar, damage) * (1 / wepinfo_pri_refire)); + wepinfo_pri_speed = (1 / max(1, (10000 / max(1, WEP_CVAR_PRI(mortar, speed))))); + + // for the range calculation, closer to 1 is better + wepinfo_pri_range_max = 2000 * wepinfo_pri_speed; + wepinfo_pri_range = wepinfo_pri_speed * WEP_CVAR_PRI(mortar, + + wepinfo_sec_refire = max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime)); + wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / wepinfo_sec_refire)); + + wepinfo_sec_dps = (WEP_CVAR_SEC(mortar, damage) * (1 / max3(sys_frametime, WEP_CVAR_SEC(mortar, refire), WEP_CVAR_SEC(mortar, animtime)))); + wepinfo_ter_dps = 0; + */ + case WR_THINK: + { + if(autocvar_g_balance_mortar_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(mortar, refire))) + { + W_Mortar_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(mortar, animtime), w_ready); + } + } + else if(self.BUTTON_ATCK2) + { + if(WEP_CVAR_SEC(mortar, 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, W_Sound("rocket_det"), VOL_BASE, ATTN_NORM); + } + else if(weapon_prepareattack(1, WEP_CVAR_SEC(mortar, refire))) + { + W_Mortar_Attack2(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(mortar, animtime), w_ready); + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_gl.md3")); + precache_model(W_Model("v_gl.md3")); + precache_model(W_Model("h_gl.iqm")); + precache_sound(W_Sound("grenade_bounce1")); + precache_sound(W_Sound("grenade_bounce2")); + precache_sound(W_Sound("grenade_bounce3")); + precache_sound(W_Sound("grenade_bounce4")); + precache_sound(W_Sound("grenade_bounce5")); + precache_sound(W_Sound("grenade_bounce6")); + precache_sound(W_Sound("grenade_stick")); + precache_sound(W_Sound("grenade_fire")); + MORTAR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_PRI(mortar, ammo); + ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_PRI(mortar, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + ammo_amount = self.WEP_AMMO(MORTAR) >= WEP_CVAR_SEC(mortar, ammo); + ammo_amount += self.(weapon_load[WEP_MORTAR.m_id]) >= WEP_CVAR_SEC(mortar, ammo); + return ammo_amount; + } + case WR_CONFIG: + { + MORTAR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(min(WEP_CVAR_PRI(mortar, ammo), WEP_CVAR_SEC(mortar, ammo)), W_Sound("reload")); // WEAPONTODO + return true; + } + case WR_SUICIDEMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_MORTAR_SUICIDE_BOUNCE; + else + return WEAPON_MORTAR_SUICIDE_EXPLODE; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_MORTAR_MURDER_BOUNCE; + else + return WEAPON_MORTAR_MURDER_EXPLODE; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Mortar(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum(EFFECT_GRENADE_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("grenade_impact"), VOL_BASE, ATTN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("grenade_impact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/porto.qc b/qcsrc/common/weapons/weapon/porto.qc new file mode 100644 index 0000000000..2d08c0fcfd --- /dev/null +++ b/qcsrc/common/weapons/weapon/porto.qc @@ -0,0 +1,431 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ PORTO, +/* function */ W_Porto, +/* ammotype */ ammo_none, +/* impulse */ 0, +/* flags */ WEP_TYPE_OTHER | WEP_FLAG_SUPERWEAPON, +/* rating */ 0, +/* color */ '0.5 0.5 0.5', +/* modelname */ "porto", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairporto 0.6", +/* wepimg */ "weaponporto", +/* refname */ "porto", +/* wepname */ _("Port-O-Launch") +); + +#define PORTO_SETTINGS(w_cvar,w_prop) PORTO_SETTINGS_LIST(w_cvar, w_prop, PORTO, porto) +#define PORTO_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, lifetime) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, speed) \ + w_cvar(id, sn, NONE, secondary) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +PORTO_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.entity porto_current; +.vector porto_v_angle; // holds "held" view angles +.float porto_v_angle_held; +.vector right_vector; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +#include "../triggers/trigger/jumppads.qh" + +void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO.m_id); } + +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 && !(self.realowner.weapons & WEPSET_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.m_id, 0, self.origin, self.velocity); + Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_FAILED); + } + } + } + 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, ATTEN_NORM); + remove(self); + } + else if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + { + spamsound(self, CH_SHOTS, "porto/bounce.wav", VOL_BASE, ATTEN_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, ATTEN_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, ATTEN_NORM); + trace_plane_normal = norm; + Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_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, ATTEN_NORM); + trace_plane_normal = norm; + Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_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, ATTEN_NORM); + trace_plane_normal = norm; + Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_IN); + 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, ATTEN_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, ATTEN_NORM); + trace_plane_normal = norm; + Send_Notification(NOTIF_ONE, self.realowner, MSG_CENTER, CENTER_PORTO_CREATED_OUT); + W_Porto_Success(); + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_NORM); + Portal_ClearAll_PortalsOnly(self.realowner); + W_Porto_Fail(0); + } + } + else + { + sound(self, CH_SHOTS, "porto/unsupported.wav", VOL_BASE, ATTEN_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; + + //Send_Effect(EFFECT_GRENADE_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'); + + gren.nextthink = time + WEP_CVAR_BOTH(porto, (type <= 0), lifetime); + gren.think = W_Porto_Think; + gren.touch = W_Porto_Touch; + + if(self.items & ITEM_Strength.m_itemid) + W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), speed) * autocvar_g_balance_powerup_strength_force, 0); + else + W_SetupProjVelocity_Basic(gren, WEP_CVAR_BOTH(porto, (type <= 0), 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); + + MUTATOR_CALLHOOK(EditProjectile, self, gren); +} + +bool w_nexball_weapon(int req); // WEAPONTODO +bool W_Porto(int req) +{ + //vector v_angle_save; + + if(g_nexball) { return w_nexball_weapon(req); } + + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = false; + self.BUTTON_ATCK2 = false; + if(!WEP_CVAR(porto, secondary)) + if(bot_aim(WEP_CVAR_PRI(porto, speed), 0, WEP_CVAR_PRI(porto, lifetime), false)) + self.BUTTON_ATCK = true; + + return true; + } + case WR_CONFIG: + { + PORTO_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_THINK: + { + if(WEP_CVAR(porto, secondary)) + { + if(self.BUTTON_ATCK) + if(!self.porto_current) + if(!self.porto_forbidden) + if(weapon_prepareattack(0, WEP_CVAR_PRI(porto, refire))) + { + W_Porto_Attack(0); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); + } + + if(self.BUTTON_ATCK2) + if(!self.porto_current) + if(!self.porto_forbidden) + if(weapon_prepareattack(1, WEP_CVAR_SEC(porto, refire))) + { + W_Porto_Attack(1); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(porto, 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, WEP_CVAR_PRI(porto, refire))) + { + W_Porto_Attack(-1); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(porto, animtime), w_ready); + } + } + + return true; + } + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + // always allow infinite ammo + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_porto.md3")); + precache_model(W_Model("v_porto.md3")); + precache_model(W_Model("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"); + PORTO_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.ammo_field = ammo_none; + return true; + } + case WR_RESETPLAYER: + { + self.porto_current = world; + return true; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Porto(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + LOG_INFO("Since when does Porto send DamageInfo?\n"); + return true; + } + case WR_INIT: + { + // nothing to do + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/rifle.qc b/qcsrc/common/weapons/weapon/rifle.qc new file mode 100644 index 0000000000..ef3babd872 --- /dev/null +++ b/qcsrc/common/weapons/weapon/rifle.qc @@ -0,0 +1,317 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ RIFLE, +/* function */ W_Rifle, +/* ammotype */ ammo_nails, +/* impulse */ 7, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '0.5 1 0', +/* modelname */ "campingrifle", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairrifle 0.6", +/* wepimg */ "weaponrifle", +/* refname */ "rifle", +/* wepname */ _("Rifle") +); + +#define RIFLE_SETTINGS(w_cvar,w_prop) RIFLE_SETTINGS_LIST(w_cvar, w_prop, RIFLE, rifle) +#define RIFLE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, bullethail) \ + w_cvar(id, sn, BOTH, burstcost) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, BOTH, shots) \ + w_cvar(id, sn, BOTH, solidpenetration) \ + w_cvar(id, sn, BOTH, spread) \ + w_cvar(id, sn, BOTH, tracer) \ + w_cvar(id, sn, NONE, bursttime) \ + w_cvar(id, sn, NONE, secondary) \ + w_cvar(id, sn, SEC, reload) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +RIFLE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float rifle_accumulator; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_rifle(void) { weapon_defaultspawnfunc(WEP_RIFLE.m_id); } +void spawnfunc_weapon_campingrifle(void) { spawnfunc_weapon_rifle(); } +void spawnfunc_weapon_sniperrifle(void) { spawnfunc_weapon_rifle(); } + +void W_Rifle_FireBullet(float pSpread, float pDamage, float pForce, float pSolidPenetration, float pAmmo, int deathtype, float pTracer, float pShots, string pSound) +{ + float i; + + W_DecreaseAmmo(pAmmo); + + W_SetupShot(self, true, 2, pSound, CH_WEAPON_A, pDamage * pShots); + + Send_Effect(EFFECT_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) + fireBullet(w_shotorg, w_shotdir, pSpread, pSolidPenetration, pDamage, pForce, deathtype, (pTracer ? EF_RED : EF_BLUE)); + + 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(void) +{ + W_Rifle_FireBullet(WEP_CVAR_PRI(rifle, spread), WEP_CVAR_PRI(rifle, damage), WEP_CVAR_PRI(rifle, force), WEP_CVAR_PRI(rifle, solidpenetration), WEP_CVAR_PRI(rifle, ammo), WEP_RIFLE.m_id, WEP_CVAR_PRI(rifle, tracer), WEP_CVAR_PRI(rifle, shots), W_Sound("campingrifle_fire")); +} + +void W_Rifle_Attack2(void) +{ + W_Rifle_FireBullet(WEP_CVAR_SEC(rifle, spread), WEP_CVAR_SEC(rifle, damage), WEP_CVAR_SEC(rifle, force), WEP_CVAR_SEC(rifle, solidpenetration), WEP_CVAR_SEC(rifle, ammo), WEP_RIFLE.m_id | HITTYPE_SECONDARY, WEP_CVAR_SEC(rifle, tracer), WEP_CVAR_SEC(rifle, shots), W_Sound("campingrifle_fire2")); +} + +.void(void) rifle_bullethail_attackfunc; +.float rifle_bullethail_frame; +.float rifle_bullethail_animtime; +.float rifle_bullethail_refire; +void W_Rifle_BulletHail_Continue(void) +{ + 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; + LOG_INFO(ftos(self.WEP_AMMO(RIFLE)), "\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); + LOG_INFO("thinkf set\n"); + } + else + { + ATTACK_FINISHED(self) = af; // reset attack_finished if we didn't fire, so the last shot enforces the refire time + LOG_INFO("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; +bool W_Rifle(int req) +{ + float ammo_amount; + + switch(req) + { + case 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(1000000, 0, 0.001, false)) + { + self.BUTTON_ATCK = true; + if(random() < 0.01) self.bot_secondary_riflemooth = 1; + } + } + else + { + if(bot_aim(1000000, 0, 0.001, false)) + { + self.BUTTON_ATCK2 = true; + if(random() < 0.03) self.bot_secondary_riflemooth = 0; + } + } + + return true; + } + case WR_THINK: + { + if(autocvar_g_balance_rifle_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else + { + self.rifle_accumulator = bound(time - WEP_CVAR(rifle, bursttime), self.rifle_accumulator, time); + if(self.BUTTON_ATCK) + if(weapon_prepareattack_check(0, WEP_CVAR_PRI(rifle, refire))) + if(time >= self.rifle_accumulator + WEP_CVAR_PRI(rifle, burstcost)) + { + weapon_prepareattack_do(0, WEP_CVAR_PRI(rifle, refire)); + W_Rifle_BulletHail(WEP_CVAR_PRI(rifle, bullethail), W_Rifle_Attack, WFRAME_FIRE1, WEP_CVAR_PRI(rifle, animtime), WEP_CVAR_PRI(rifle, refire)); + self.rifle_accumulator += WEP_CVAR_PRI(rifle, burstcost); + } + if(self.BUTTON_ATCK2) + { + if(WEP_CVAR(rifle, secondary)) + { + if(WEP_CVAR_SEC(rifle, reload)) + WEP_ACTION(self.weapon, WR_RELOAD); + else + { + if(weapon_prepareattack_check(1, WEP_CVAR_SEC(rifle, refire))) + if(time >= self.rifle_accumulator + WEP_CVAR_SEC(rifle, burstcost)) + { + weapon_prepareattack_do(1, WEP_CVAR_SEC(rifle, refire)); + W_Rifle_BulletHail(WEP_CVAR_SEC(rifle, bullethail), W_Rifle_Attack2, WFRAME_FIRE2, WEP_CVAR_SEC(rifle, animtime), WEP_CVAR_PRI(rifle, refire)); + self.rifle_accumulator += WEP_CVAR_SEC(rifle, burstcost); + } + } + } + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_sniperrifle.md3")); + precache_model(W_Model("v_sniperrifle.md3")); + precache_model(W_Model("h_sniperrifle.iqm")); + precache_sound(W_Sound("campingrifle_fire")); + precache_sound(W_Sound("campingrifle_fire2")); + RIFLE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_PRI(rifle, ammo); + ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_PRI(rifle, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + ammo_amount = self.WEP_AMMO(RIFLE) >= WEP_CVAR_SEC(rifle, ammo); + ammo_amount += self.(weapon_load[WEP_RIFLE.m_id]) >= WEP_CVAR_SEC(rifle, ammo); + return ammo_amount; + } + case WR_CONFIG: + { + RIFLE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.rifle_accumulator = time - WEP_CVAR(rifle, bursttime); + return true; + } + case WR_RELOAD: + { + W_Reload(min(WEP_CVAR_PRI(rifle, ammo), WEP_CVAR_SEC(rifle, ammo)), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case 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 false; +} +#endif +#ifdef CSQC +bool W_Rifle(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum(EFFECT_RIFLE_IMPACT), org2, w_backoff * 1000, 1); + if(!w_issilent) + { + if(w_random < 0.2) + sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTN_NORM); + else if(w_random < 0.4) + sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTN_NORM); + else if(w_random < 0.5) + sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTN_NORM); + } + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("ric1")); + precache_sound(W_Sound("ric2")); + precache_sound(W_Sound("ric3")); + if(autocvar_cl_reticle && autocvar_cl_reticle_weapon) + { + precache_pic("gfx/reticle_nex"); + } + return true; + } + case WR_ZOOMRETICLE: + { + if(button_zoom || zoomscript_caught) + { + reticle_image = "gfx/reticle_nex"; + return true; + } + else + { + // no weapon specific image for this weapon + return false; + } + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/rpc.qc b/qcsrc/common/weapons/weapon/rpc.qc new file mode 100644 index 0000000000..8a34490e28 --- /dev/null +++ b/qcsrc/common/weapons/weapon/rpc.qc @@ -0,0 +1,265 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ RPC, +/* function */ W_RocketPropelledChainsaw, +/* ammotype */ ammo_rockets, +/* impulse */ 7, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_NORMAL | WEP_FLAG_CANCLIMB | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH | WEP_FLAG_SUPERWEAPON, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '0.5 0.5 0', +/* modelname */ "ok_rl", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairrocketlauncher 0.7", +/* wepimg */ "weaponrpc", +/* refname */ "rpc", +/* wepname */ _("Rocket Propelled Chainsaw") +); + +#define RPC_SETTINGS(w_cvar,w_prop) RPC_SETTINGS_LIST(w_cvar, w_prop, RPC, rpc) +#define RPC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, ammo) \ + w_cvar(id, sn, NONE, animtime) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, damage2) \ + w_cvar(id, sn, NONE, damageforcescale) \ + w_cvar(id, sn, NONE, edgedamage) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, health) \ + w_cvar(id, sn, NONE, lifetime) \ + w_cvar(id, sn, NONE, radius) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, speed) \ + w_cvar(id, sn, NONE, speedaccel) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +RPC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_rpc() { weapon_defaultspawnfunc(WEP_RPC.m_id); } + +void W_RocketPropelledChainsaw_Explode() +{ + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, WEP_CVAR(rpc, damage), WEP_CVAR(rpc, edgedamage), WEP_CVAR(rpc, radius), world, world, WEP_CVAR(rpc, force), self.projectiledeathtype, other); + + remove (self); +} + +void W_RocketPropelledChainsaw_Touch (void) +{ + if(WarpZone_Projectile_Touch()) + if(wasfreed(self)) + return; + + W_RocketPropelledChainsaw_Explode(); +} + +void W_RocketPropelledChainsaw_Damage (entity inflictor, entity attacker, float damage, int 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, W_RocketPropelledChainsaw_Explode); +} + +void W_RocketPropelledChainsaw_Think() +{ + if(self.cnt <= time) + { + remove(self); + return; + } + + self.cnt = vlen(self.velocity); + self.wait = self.cnt * sys_frametime; + self.pos1 = normalize(self.velocity); + + tracebox(self.origin, self.mins, self.maxs, self.origin + self.pos1 * (2 * self.wait), MOVE_NORMAL, self); + if(IS_PLAYER(trace_ent)) + Damage (trace_ent, self, self.realowner, WEP_CVAR(rpc, damage2), self.projectiledeathtype, self.origin, normalize(self.origin - other.origin) * WEP_CVAR(rpc, force)); + + self.velocity = self.pos1 * (self.cnt + (WEP_CVAR(rpc, speedaccel) * sys_frametime)); + + UpdateCSQCProjectile(self); + self.nextthink = time; +} + +void W_RocketPropelledChainsaw_Attack (void) +{ + entity missile = spawn(); //WarpZone_RefSys_SpawnSameRefSys(self); + entity flash = spawn (); + + W_DecreaseAmmo(WEP_CVAR(rpc, ammo)); + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', false, 5, W_Sound("rocket_fire"), CH_WEAPON_A, WEP_CVAR(rpc, damage)); + Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + PROJECTILE_MAKETRIGGER(missile); + + missile.owner = missile.realowner = self; + missile.bot_dodge = true; + missile.bot_dodgerating = WEP_CVAR(rpc, damage) * 2; + + missile.takedamage = DAMAGE_YES; + missile.damageforcescale = WEP_CVAR(rpc, damageforcescale); + missile.health = WEP_CVAR(rpc, health); + missile.event_damage = W_RocketPropelledChainsaw_Damage; + missile.damagedbycontents = true; + missile.movetype = MOVETYPE_FLY; + + missile.projectiledeathtype = WEP_RPC.m_id; + 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_SetupProjVelocity_Basic(missile, WEP_CVAR(rpc, speed), 0); + + missile.touch = W_RocketPropelledChainsaw_Touch; + + missile.think = W_RocketPropelledChainsaw_Think; + missile.cnt = time + WEP_CVAR(rpc, lifetime); + missile.nextthink = time; + missile.flags = FL_PROJECTILE; + + CSQCProjectile(missile, true, PROJECTILE_RPC, false); + + 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'); + missile.pos1 = missile.velocity; + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +bool W_RocketPropelledChainsaw(int req) +{ + float ammo_amount = false; + switch(req) + { + case WR_AIM: + { + self.BUTTON_ATCK = bot_aim(WEP_CVAR(rpc, speed), 0, WEP_CVAR(rpc, lifetime), false); + return true; + } + case WR_THINK: + { + if(WEP_CVAR(rpc, reload_ammo) && self.clip_load < WEP_CVAR(rpc, ammo)) + WEP_ACTION(self.weapon, WR_RELOAD); + else + { + if (self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR(rpc, refire))) + { + W_RocketPropelledChainsaw_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(rpc, animtime), w_ready); + } + } + + if (self.BUTTON_ATCK2) + { + // to-do + } + } + + return true; + } + case WR_INIT: + { + precache_model ("models/flash.md3"); + precache_model(W_Model("g_ok_rl.md3")); + precache_model(W_Model("v_ok_rl.md3")); + precache_model(W_Model("h_ok_rl.iqm")); + precache_sound (W_Sound("rocket_fire")); + RPC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(RPC) >= WEP_CVAR(rpc, ammo); + ammo_amount += self.(weapon_load[WEP_RPC.m_id]) >= WEP_CVAR(rpc, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + return false; + } + case WR_CONFIG: + { + RPC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR(rpc, ammo), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) + return WEAPON_RPC_SUICIDE_SPLASH; + else + return WEAPON_RPC_SUICIDE_DIRECT; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_BLASTER_MURDER; + else if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) + return WEAPON_RPC_MURDER_SPLASH; + else + return WEAPON_RPC_MURDER_DIRECT; + } + } + + return false; +} +#endif + +#ifdef CSQC +bool W_RocketPropelledChainsaw(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("rocket_impact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/seeker.qc b/qcsrc/common/weapons/weapon/seeker.qc new file mode 100644 index 0000000000..69c4974de6 --- /dev/null +++ b/qcsrc/common/weapons/weapon/seeker.qc @@ -0,0 +1,793 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ SEEKER, +/* function */ W_Seeker, +/* ammotype */ ammo_rockets, +/* impulse */ 8, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '0.5 1 0', +/* modelname */ "seeker", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairseeker 0.8", +/* wepimg */ "weaponseeker", +/* refname */ "seeker", +/* wepname */ _("T.A.G. Seeker") +); + +#define SEEKER_SETTINGS(w_cvar,w_prop) SEEKER_SETTINGS_LIST(w_cvar, w_prop, SEEKER, seeker) +#define SEEKER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, type) \ + w_cvar(id, sn, NONE, flac_ammo) \ + w_cvar(id, sn, NONE, flac_animtime) \ + w_cvar(id, sn, NONE, flac_damage) \ + w_cvar(id, sn, NONE, flac_edgedamage) \ + w_cvar(id, sn, NONE, flac_force) \ + w_cvar(id, sn, NONE, flac_lifetime) \ + w_cvar(id, sn, NONE, flac_lifetime_rand) \ + w_cvar(id, sn, NONE, flac_radius) \ + w_cvar(id, sn, NONE, flac_refire) \ + w_cvar(id, sn, NONE, flac_speed) \ + w_cvar(id, sn, NONE, flac_speed_up) \ + w_cvar(id, sn, NONE, flac_speed_z) \ + w_cvar(id, sn, NONE, flac_spread) \ + w_cvar(id, sn, NONE, missile_accel) \ + w_cvar(id, sn, NONE, missile_ammo) \ + w_cvar(id, sn, NONE, missile_animtime) \ + w_cvar(id, sn, NONE, missile_count) \ + w_cvar(id, sn, NONE, missile_damage) \ + w_cvar(id, sn, NONE, missile_damageforcescale) \ + w_cvar(id, sn, NONE, missile_decel) \ + w_cvar(id, sn, NONE, missile_delay) \ + w_cvar(id, sn, NONE, missile_edgedamage) \ + w_cvar(id, sn, NONE, missile_force) \ + w_cvar(id, sn, NONE, missile_health) \ + w_cvar(id, sn, NONE, missile_lifetime) \ + w_cvar(id, sn, NONE, missile_proxy) \ + w_cvar(id, sn, NONE, missile_proxy_delay) \ + w_cvar(id, sn, NONE, missile_proxy_maxrange) \ + w_cvar(id, sn, NONE, missile_radius) \ + w_cvar(id, sn, NONE, missile_refire) \ + w_cvar(id, sn, NONE, missile_smart) \ + w_cvar(id, sn, NONE, missile_smart_mindist) \ + w_cvar(id, sn, NONE, missile_smart_trace_max) \ + w_cvar(id, sn, NONE, missile_smart_trace_min) \ + w_cvar(id, sn, NONE, missile_speed) \ + w_cvar(id, sn, NONE, missile_speed_max) \ + w_cvar(id, sn, NONE, missile_speed_up) \ + w_cvar(id, sn, NONE, missile_speed_z) \ + w_cvar(id, sn, NONE, missile_spread) \ + w_cvar(id, sn, NONE, missile_turnrate) \ + w_cvar(id, sn, NONE, tag_ammo) \ + w_cvar(id, sn, NONE, tag_animtime) \ + w_cvar(id, sn, NONE, tag_damageforcescale) \ + w_cvar(id, sn, NONE, tag_health) \ + w_cvar(id, sn, NONE, tag_lifetime) \ + w_cvar(id, sn, NONE, tag_refire) \ + w_cvar(id, sn, NONE, tag_speed) \ + w_cvar(id, sn, NONE, tag_spread) \ + w_cvar(id, sn, NONE, tag_tracker_lifetime) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +SEEKER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.entity tag_target, wps_tag_tracker; +.float tag_time; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_seeker(void) { weapon_defaultspawnfunc(WEP_SEEKER.m_id); } + +// ============================ +// Begin: Missile functions, these are general functions to be manipulated by other code +// ============================ +void W_Seeker_Missile_Explode(void) +{ + self.event_damage = func_null; + RadiusDamage(self, self.realowner, WEP_CVAR(seeker, missile_damage), WEP_CVAR(seeker, missile_edgedamage), WEP_CVAR(seeker, missile_radius), world, world, WEP_CVAR(seeker, missile_force), self.projectiledeathtype, other); + + remove(self); +} + +void W_Seeker_Missile_Touch(void) +{ + PROJECTILE_TOUCH; + + W_Seeker_Missile_Explode(); +} + +void W_Seeker_Missile_Think(void) +{ + entity e; + vector desireddir, olddir, newdir, eorg; + float turnrate; + float dist; + float spd; + + if(time > self.cnt) + { + self.projectiledeathtype |= HITTYPE_SPLASH; + W_Seeker_Missile_Explode(); + } + + spd = vlen(self.velocity); + spd = bound( + spd - WEP_CVAR(seeker, missile_decel) * frametime, + WEP_CVAR(seeker, missile_speed_max), + spd + WEP_CVAR(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 = WEP_CVAR(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(WEP_CVAR(seeker, missile_smart) && (dist > WEP_CVAR(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(WEP_CVAR(seeker, missile_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = WEP_CVAR(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(WEP_CVAR(seeker, missile_proxy)) + { + if(dist <= WEP_CVAR(seeker, missile_proxy_maxrange)) + { + if(self.autoswitch == 0) + { + self.autoswitch = time + WEP_CVAR(seeker, missile_proxy_delay); + } + else + { + if(self.autoswitch <= time) + { + W_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 W_Seeker_Missile_Damage(entity inflictor, entity attacker, float damage, int 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, W_Seeker_Missile_Explode); +} + +/* +void W_Seeker_Missile_Animate(void) +{ + 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 = W_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 W_Seeker_Fire_Missile(vector f_diff, entity m_target) +{ + entity missile; + + W_DecreaseAmmo(WEP_CVAR(seeker, missile_ammo)); + + makevectors(self.v_angle); + W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("seeker_fire"), CH_WEAPON_A, 0); + w_shotorg += f_diff; + Send_Effect(EFFECT_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 = WEP_CVAR(seeker, missile_damage); + + missile.think = W_Seeker_Missile_Think; + missile.touch = W_Seeker_Missile_Touch; + missile.event_damage = W_Seeker_Missile_Damage; + missile.nextthink = time;// + 0.2;// + cvar("g_balance_seeker_missile_activate_delay"); + missile.cnt = time + WEP_CVAR(seeker, missile_lifetime); + missile.enemy = m_target; + missile.solid = SOLID_BBOX; + missile.scale = 2; + missile.takedamage = DAMAGE_YES; + missile.health = WEP_CVAR(seeker, missile_health); + missile.damageforcescale = WEP_CVAR(seeker, missile_damageforcescale); + missile.damagedbycontents = true; + //missile.think = W_Seeker_Missile_Animate; // csqc projectiles. + + if(missile.enemy != world) + missile.projectiledeathtype = WEP_SEEKER.m_id | HITTYPE_SECONDARY; + else + missile.projectiledeathtype = WEP_SEEKER.m_id; + + + 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_SetupProjVelocity_UP_PRE(missile, seeker, missile_); + + missile.angles = vectoangles(missile.velocity); + + CSQCProjectile(missile, false, PROJECTILE_SEEKER, true); + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +// ============================ +// Begin: FLAC, close range attack meant for defeating rockets which are coming at you. +// ============================ +void W_Seeker_Flac_Explode(void) +{ + self.event_damage = func_null; + + RadiusDamage(self, self.realowner, WEP_CVAR(seeker, flac_damage), WEP_CVAR(seeker, flac_edgedamage), WEP_CVAR(seeker, flac_radius), world, world, WEP_CVAR(seeker, flac_force), self.projectiledeathtype, other); + + remove(self); +} + +void W_Seeker_Flac_Touch(void) +{ + PROJECTILE_TOUCH; + + W_Seeker_Flac_Explode(); +} + +void W_Seeker_Fire_Flac(void) +{ + entity missile; + vector f_diff; + float c; + + W_DecreaseAmmo(WEP_CVAR(seeker, flac_ammo)); + + c = 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, W_Sound("flac_fire"), CH_WEAPON_A, WEP_CVAR(seeker, flac_damage)); + w_shotorg += f_diff; + + Send_Effect(EFFECT_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 = WEP_CVAR(seeker, flac_damage); + missile.touch = W_Seeker_Flac_Explode; + missile.use = W_Seeker_Flac_Explode; + missile.think = adaptor_think2use_hittype_splash; + missile.nextthink = time + WEP_CVAR(seeker, flac_lifetime) + WEP_CVAR(seeker, flac_lifetime_rand); + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLY; + missile.projectiledeathtype = WEP_SEEKER.m_id; + missile.projectiledeathtype = WEP_SEEKER.m_id | 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_SetupProjVelocity_UP_PRE(missile, seeker, flac_); + CSQCProjectile(missile, true, PROJECTILE_FLAC, true); + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +// ============================ +// Begin: Tag and rocket controllers +// ============================ +entity W_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 W_Seeker_Attack(void) +{ + 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; + + W_Seeker_Fire_Missile('0 0 0', closest_target); +} + +void W_Seeker_Vollycontroller_Think(void) // TODO: Merge this with W_Seeker_Attack +{ + float c; + entity oldself,oldenemy; + self.cnt = self.cnt - 1; + + if((!(self.realowner.items & IT_UNLIMITED_AMMO) && self.realowner.WEP_AMMO(SEEKER) < WEP_CVAR(seeker, missile_ammo)) || (self.cnt <= -1) || (self.realowner.deadflag != DEAD_NO) || (self.realowner.switchweapon != WEP_SEEKER.m_id)) + { + remove(self); + return; + } + + self.nextthink = time + WEP_CVAR(seeker, missile_delay) * W_WeaponRateFactor(); + + oldself = self; + self = self.realowner; + + oldenemy = self.enemy; + self.enemy = oldself.enemy; + + c = self.cnt % 4; + switch(c) + { + case 0: + W_Seeker_Fire_Missile('-1.25 -3.75 0', self.enemy); + break; + case 1: + W_Seeker_Fire_Missile('+1.25 -3.75 0', self.enemy); + break; + case 2: + W_Seeker_Fire_Missile('-1.25 +3.75 0', self.enemy); + break; + case 3: + default: + W_Seeker_Fire_Missile('+1.25 +3.75 0', self.enemy); + break; + } + + self.enemy = oldenemy; + self = oldself; +} + +void W_Seeker_Tracker_Think(void) +{ + // 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.m_id) + || (time > self.tag_time + WEP_CVAR(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 W_Seeker_Tag_Explode(void) +{ + //if(other==self.realowner) + // return; + Damage_DamageInfo(self.origin, 0, 0, 0, self.velocity, WEP_SEEKER.m_id | HITTYPE_BOUNCE, other.species, self); + + remove(self); +} + +void W_Seeker_Tag_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{ + if(self.health <= 0) + return; + self.health = self.health - damage; + if(self.health <= 0) + W_Seeker_Tag_Explode(); +} + +void W_Seeker_Tag_Touch(void) +{ + 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.m_id | 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 = W_Seeker_Tagged_Info(self.realowner, other); + + if(tag != world) + { + if(other.wps_tag_tracker && (WEP_CVAR(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 = WEP_CVAR(seeker, missile_count); + e.classname = "tag_tracker"; + e.owner = self.owner; + e.realowner = self.realowner; + + if(WEP_CVAR(seeker, type) == 1) + { + e.tag_target = other; + e.tag_time = time; + e.think = W_Seeker_Tracker_Think; + } + else + { + e.enemy = other; + e.think = W_Seeker_Vollycontroller_Think; + } + + e.nextthink = time; + } + + if(WEP_CVAR(seeker, type) == 1) + { + WaypointSprite_Spawn(WP_Seeker, WEP_CVAR(seeker, tag_tracker_lifetime), 0, other, '0 0 64', self.realowner, 0, other, wps_tag_tracker, true, RADARICON_TAGGED); + WaypointSprite_UpdateRule(other.wps_tag_tracker, 0, SPRITERULE_DEFAULT); + } + } + + remove(self); + return; +} + +void W_Seeker_Fire_Tag(void) +{ + entity missile; + W_DecreaseAmmo(WEP_CVAR(seeker, tag_ammo)); + + W_SetupShot_ProjectileSize(self, '-2 -2 -2', '2 2 2', false, 2, W_Sound("tag_fire"), CH_WEAPON_A, WEP_CVAR(seeker, missile_damage) * WEP_CVAR(seeker, missile_count)); + + missile = spawn(); + missile.owner = missile.realowner = self; + missile.classname = "seeker_tag"; + missile.bot_dodge = true; + missile.bot_dodgerating = 50; + missile.touch = W_Seeker_Tag_Touch; + missile.think = SUB_Remove; + missile.nextthink = time + WEP_CVAR(seeker, tag_lifetime); + missile.movetype = MOVETYPE_FLY; + missile.solid = SOLID_BBOX; + + missile.takedamage = DAMAGE_YES; + missile.event_damage = W_Seeker_Tag_Damage; + missile.health = WEP_CVAR(seeker, tag_health); + missile.damageforcescale = WEP_CVAR(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_SetupProjVelocity_PRE(missile, seeker, tag_); + missile.angles = vectoangles(missile.velocity); + + CSQCProjectile(missile, true, PROJECTILE_TAG, false); // has sound + + MUTATOR_CALLHOOK(EditProjectile, self, missile); +} + +// ============================ +// Begin: Genereal weapon functions +// ============================ + +bool W_Seeker(int req) +{ + float ammo_amount; + + switch(req) + { + case WR_AIM: + { + if(WEP_CVAR(seeker, type) == 1) + if(W_Seeker_Tagged_Info(self, self.enemy) != world) + self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, missile_speed_max), 0, WEP_CVAR(seeker, missile_lifetime), false); + else + self.BUTTON_ATCK2 = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false); + else + self.BUTTON_ATCK = bot_aim(WEP_CVAR(seeker, tag_speed), 0, WEP_CVAR(seeker, tag_lifetime), false); + return true; + } + case WR_THINK: + { + if(autocvar_g_balance_seeker_reload_ammo && self.clip_load < min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + + else if(self.BUTTON_ATCK) + { + if(WEP_CVAR(seeker, type) == 1) + { + if(weapon_prepareattack(0, WEP_CVAR(seeker, missile_refire))) + { + W_Seeker_Attack(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, missile_animtime), w_ready); + } + } + else + { + if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire))) + { + W_Seeker_Fire_Tag(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready); + } + } + } + + else if(self.BUTTON_ATCK2) + { + if(WEP_CVAR(seeker, type) == 1) + { + if(weapon_prepareattack(0, WEP_CVAR(seeker, tag_refire))) + { + W_Seeker_Fire_Tag(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, tag_animtime), w_ready); + } + } + else + { + if(weapon_prepareattack(0, WEP_CVAR(seeker, flac_refire))) + { + W_Seeker_Fire_Flac(); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(seeker, flac_animtime), w_ready); + } + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_seeker.md3")); + precache_model(W_Model("v_seeker.md3")); + precache_model(W_Model("h_seeker.iqm")); + precache_sound(W_Sound("tag_fire")); + precache_sound(W_Sound("flac_fire")); + precache_sound(W_Sound("seeker_fire")); + SEEKER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + { + if(WEP_CVAR(seeker, type) == 1) + { + ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, missile_ammo); + ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, missile_ammo); + } + else + { + ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo); + ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo); + } + return ammo_amount; + } + case WR_CHECKAMMO2: + { + if(WEP_CVAR(seeker, type) == 1) + { + ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, tag_ammo); + ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, tag_ammo); + } + else + { + ammo_amount = self.WEP_AMMO(SEEKER) >= WEP_CVAR(seeker, flac_ammo); + ammo_amount += self.(weapon_load[WEP_SEEKER.m_id]) >= WEP_CVAR(seeker, flac_ammo); + } + return ammo_amount; + } + case WR_CONFIG: + { + SEEKER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(min(WEP_CVAR(seeker, missile_ammo), WEP_CVAR(seeker, tag_ammo)), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_SEEKER_SUICIDE; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SEEKER_MURDER_TAG; + else + return WEAPON_SEEKER_MURDER_SPRAY; + } + } + return false; +} +#endif +#ifdef CSQC +bool W_Seeker(int req) +{ + switch(req) + { + case 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, W_Sound("tag_impact"), 1, ATTEN_NORM); + } + else + { + pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + { + if(w_random<0.15) + sound(self, CH_SHOTS, W_Sound("tagexp1"), 1, ATTEN_NORM); + else if(w_random<0.7) + sound(self, CH_SHOTS, W_Sound("tagexp2"), 1, ATTEN_NORM); + else + sound(self, CH_SHOTS, W_Sound("tagexp3"), 1, ATTEN_NORM); + } + } + } + else + { + pointparticles(particleeffectnum(EFFECT_HAGAR_EXPLODE), org2, '0 0 0', 1); + if(!w_issilent) + { + if(w_random<0.15) + sound(self, CH_SHOTS, W_Sound("seekerexp1"), 1, ATTEN_NORM); + else if(w_random<0.7) + sound(self, CH_SHOTS, W_Sound("seekerexp2"), 1, ATTEN_NORM); + else + sound(self, CH_SHOTS, W_Sound("seekerexp3"), 1, ATTEN_NORM); + } + } + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("seekerexp1")); + precache_sound(W_Sound("seekerexp2")); + precache_sound(W_Sound("seekerexp3")); + precache_sound(W_Sound("tagexp1")); + precache_sound(W_Sound("tagexp2")); + precache_sound(W_Sound("tagexp3")); + precache_sound(W_Sound("tag_impact")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/shockwave.qc b/qcsrc/common/weapons/weapon/shockwave.qc new file mode 100644 index 0000000000..3287ea3cb3 --- /dev/null +++ b/qcsrc/common/weapons/weapon/shockwave.qc @@ -0,0 +1,896 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ SHOCKWAVE, +/* function */ W_Shockwave, +/* ammotype */ ammo_none, +/* impulse */ 2, +/* flags */ WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN | WEP_FLAG_CANCLIMB | WEP_FLAG_MUTATORBLOCKED, +/* rating */ BOT_PICKUP_RATING_LOW, +/* color */ '0.5 0.25 0', +/* modelname */ "shotgun", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairshotgun 0.7", +/* wepimg */ "weaponshotgun", +/* refname */ "shockwave", +/* wepname */ _("Shockwave") +); + +#define SHOCKWAVE_SETTINGS(w_cvar,w_prop) SHOCKWAVE_SETTINGS_LIST(w_cvar, w_prop, SHOCKWAVE, shockwave) +#define SHOCKWAVE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, blast_animtime) \ + w_cvar(id, sn, NONE, blast_damage) \ + w_cvar(id, sn, NONE, blast_distance) \ + w_cvar(id, sn, NONE, blast_edgedamage) \ + w_cvar(id, sn, NONE, blast_force) \ + w_cvar(id, sn, NONE, blast_force_forwardbias) \ + w_cvar(id, sn, NONE, blast_force_zscale) \ + w_cvar(id, sn, NONE, blast_jump_damage) \ + w_cvar(id, sn, NONE, blast_jump_edgedamage) \ + w_cvar(id, sn, NONE, blast_jump_force) \ + w_cvar(id, sn, NONE, blast_jump_force_velocitybias) \ + w_cvar(id, sn, NONE, blast_jump_force_zscale) \ + w_cvar(id, sn, NONE, blast_jump_multiplier_accuracy) \ + w_cvar(id, sn, NONE, blast_jump_multiplier_distance) \ + w_cvar(id, sn, NONE, blast_jump_multiplier_min) \ + w_cvar(id, sn, NONE, blast_jump_radius) \ + w_cvar(id, sn, NONE, blast_multiplier_accuracy) \ + w_cvar(id, sn, NONE, blast_multiplier_distance) \ + w_cvar(id, sn, NONE, blast_multiplier_min) \ + w_cvar(id, sn, NONE, blast_refire) \ + w_cvar(id, sn, NONE, blast_splash_damage) \ + w_cvar(id, sn, NONE, blast_splash_edgedamage) \ + w_cvar(id, sn, NONE, blast_splash_force) \ + w_cvar(id, sn, NONE, blast_splash_force_forwardbias) \ + w_cvar(id, sn, NONE, blast_splash_multiplier_accuracy) \ + w_cvar(id, sn, NONE, blast_splash_multiplier_distance) \ + w_cvar(id, sn, NONE, blast_splash_multiplier_min) \ + w_cvar(id, sn, NONE, blast_splash_radius) \ + w_cvar(id, sn, NONE, blast_spread_max) \ + w_cvar(id, sn, NONE, blast_spread_min) \ + w_cvar(id, sn, NONE, melee_animtime) \ + w_cvar(id, sn, NONE, melee_damage) \ + w_cvar(id, sn, NONE, melee_delay) \ + w_cvar(id, sn, NONE, melee_force) \ + w_cvar(id, sn, NONE, melee_multihit) \ + w_cvar(id, sn, NONE, melee_no_doubleslap) \ + w_cvar(id, sn, NONE, melee_nonplayerdamage) \ + w_cvar(id, sn, NONE, melee_range) \ + w_cvar(id, sn, NONE, melee_refire) \ + w_cvar(id, sn, NONE, melee_swing_side) \ + w_cvar(id, sn, NONE, melee_swing_up) \ + w_cvar(id, sn, NONE, melee_time) \ + w_cvar(id, sn, NONE, melee_traces) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +SHOCKWAVE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#ifdef CSQC +void Net_ReadShockwaveParticle(void); +.vector sw_shotorg; +.vector sw_shotdir; +.float sw_distance; +.float sw_spread_max; +.float sw_spread_min; +.float sw_time; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_shockwave(void) +{ + //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO + if(autocvar_sv_q3acompat_machineshotgunswap) + if(self.classname != "droppedweapon") + { + weapon_defaultspawnfunc(WEP_MACHINEGUN.m_id); + return; + } + weapon_defaultspawnfunc(WEP_SHOCKWAVE.m_id); +} + +const float MAX_SHOCKWAVE_HITS = 10; +//#define DEBUG_SHOCKWAVE + +.float swing_prev; +.entity swing_alreadyhit; +.float shockwave_blasttime; +entity shockwave_hit[MAX_SHOCKWAVE_HITS]; +float shockwave_hit_damage[MAX_SHOCKWAVE_HITS]; +vector shockwave_hit_force[MAX_SHOCKWAVE_HITS]; + +// MELEE ATTACK MODE +void W_Shockwave_Melee_Think(void) +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player; + entity target_victim; + vector targpos; + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR(shockwave, melee_no_doubleslap)) + { + remove(self); + return; + } + + // set start time of melee + if(!self.cnt) + { + self.cnt = time; + W_PlayStrengthSound(self.realowner); + } + + // update values for v_* vectors + makevectors(self.realowner.v_angle); + + // calculate swing percentage based on time + meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces)); + + // perform the traces needed for this frame + for(i=self.swing_prev; i < f; ++i) + { + swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * WEP_CVAR(shockwave, melee_range)) + + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up)) + + (v_right * swing_factor * WEP_CVAR(shockwave, 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 +#ifdef DEBUG_SHOCKWAVE + 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'); +#endif + + is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || IS_MONSTER(trace_ent)); + + if((trace_fraction < 1) // if trace is good, apply the damage and remove self if necessary + && (trace_ent.takedamage == DAMAGE_AIM) + && (trace_ent != self.swing_alreadyhit) + && (is_player || WEP_CVAR(shockwave, 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 = (WEP_CVAR(shockwave, melee_damage) * min(1, swing_factor + 1)); + else + swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage) * min(1, swing_factor + 1)); + + // trigger damage with this calculated info + Damage( + target_victim, + self.realowner, + self.realowner, + swing_damage, + (WEP_SHOCKWAVE.m_id | HITTYPE_SECONDARY), + (self.realowner.origin + self.realowner.view_ofs), + (v_forward * WEP_CVAR(shockwave, melee_force)) + ); + + // handle accuracy + if(accuracy_isgooddamage(self.realowner, target_victim)) + { accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, swing_damage); } + + #ifdef DEBUG_SHOCKWAVE + LOG_INFO(sprintf( + "MELEE: %s hitting %s with %f damage (factor: %f) at %f time.\n", + self.realowner.netname, + target_victim.netname, + swing_damage, + swing_factor, + time + )); + #endif + + // allow multiple hits with one swing, but not against the same player twice + if(WEP_CVAR(shockwave, melee_multihit)) + { + 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_Shockwave_Melee(void) +{ + sound(self, CH_WEAPON_A, W_Sound("shotgun_melee"), VOL_BASE, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(shockwave, melee_animtime), w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.owner = meleetemp.realowner = self; + meleetemp.think = W_Shockwave_Melee_Think; + meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor(); + W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range)); +} + +// SHOCKWAVE ATTACK MODE +float W_Shockwave_Attack_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 = + ( + (WEP_CVAR(shockwave, blast_spread_min) * (1 - spreadlimit)) + + + (WEP_CVAR(shockwave, blast_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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_Shockwave_Attack_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; +} + +float W_Shockwave_Attack_CheckHit( + float queue, + entity head, + vector final_force, + float final_damage) +{ + if(!head) { return false; } + float i; + + for(i = 0; 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_Shockwave_Send(void) +{ + 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, w_shotdir.x); + WriteCoord(MSG_BROADCAST, w_shotdir.y); + WriteCoord(MSG_BROADCAST, w_shotdir.z); + WriteShort(MSG_BROADCAST, WEP_CVAR(shockwave, blast_distance)); + WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_max), 255)); + WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_min), 255)); + WriteByte(MSG_BROADCAST, num_for_edict(self)); +} + +void W_Shockwave_Attack(void) +{ + // declarations + float multiplier, multiplier_from_accuracy, multiplier_from_distance; + float final_damage; + vector final_force, center, vel; + entity head; + + float i, queue = 0; + + // set up the shot direction + W_SetupShot(self, false, 3, W_Sound("lasergun_fire"), CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage)); + vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_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 + W_Shockwave_Send(); + Damage_DamageInfo( + attack_hitpos, + WEP_CVAR(shockwave, blast_splash_damage), + WEP_CVAR(shockwave, blast_splash_edgedamage), + WEP_CVAR(shockwave, blast_splash_radius), + w_shotdir * WEP_CVAR(shockwave, blast_splash_force), + WEP_SHOCKWAVE.m_id, + 0, + self + ); + + // splash damage/jumping trace + head = WarpZone_FindRadius( + attack_hitpos, + max( + WEP_CVAR(shockwave, blast_splash_radius), + WEP_CVAR(shockwave, blast_jump_radius) + ), + false + ); + + while(head) + { + if(head.takedamage) + { + float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); + + if((head == self) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius))) + { + // ======================== + // BLAST JUMP CALCULATION + // ======================== + + // calculate importance of distance and accuracy for this attack + multiplier_from_accuracy = (1 - + (distance_to_head ? + min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius))) + : + 0 + ) + ); + multiplier_from_distance = (1 - + (distance_to_hit ? + min(1, (distance_to_hit / distance_to_end)) + : + 0 + ) + ); + multiplier = + max( + WEP_CVAR(shockwave, blast_jump_multiplier_min), + ( + (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy)) + + + (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance)) + ) + ); + + // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage + final_damage = + ( + (WEP_CVAR(shockwave, blast_jump_damage) * multiplier) + + + (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier)) + ); + + // figure out the direction of force + vel = normalize(combine_to_vector(head.velocity.x, head.velocity.y, 0)); + vel *= + ( + bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1) + * + WEP_CVAR(shockwave, blast_jump_force_velocitybias) + ); + final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel); + + // now multiply the direction by force units + final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier); + final_force.z *= WEP_CVAR(shockwave, blast_jump_force_zscale); + + // trigger damage with this calculated info + Damage( + head, + self, + self, + final_damage, + WEP_SHOCKWAVE.m_id, + head.origin, + final_force + ); + + #ifdef DEBUG_SHOCKWAVE + LOG_INFO(sprintf( + "SELF HIT: multiplier = %f, damage = %f, force = %f... " + "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", + multiplier, + final_damage, + vlen(final_force), + multiplier_from_accuracy, + multiplier_from_distance + )); + #endif + } + else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius)) + { + // ========================== + // BLAST SPLASH CALCULATION + // ========================== + + // calculate importance of distance and accuracy for this attack + multiplier_from_accuracy = (1 - + (distance_to_head ? + min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius))) + : + 0 + ) + ); + multiplier_from_distance = (1 - + (distance_to_hit ? + min(1, (distance_to_hit / distance_to_end)) + : + 0 + ) + ); + multiplier = + max( + WEP_CVAR(shockwave, blast_splash_multiplier_min), + ( + (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy)) + + + (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance)) + ) + ); + + // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage + final_damage = + ( + (WEP_CVAR(shockwave, blast_splash_damage) * multiplier) + + + (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier)) + ); + + // figure out the direction of force + final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias)); + final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force)); + //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); + + // now multiply the direction by force units + final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier); + final_force.z *= WEP_CVAR(shockwave, blast_force_zscale); + + // queue damage with this calculated info + if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); } + + #ifdef DEBUG_SHOCKWAVE + LOG_INFO(sprintf( + "SPLASH HIT: multiplier = %f, damage = %f, force = %f... " + "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", + multiplier, + final_damage, + vlen(final_force), + multiplier_from_accuracy, + multiplier_from_distance + )); + #endif + } + } + head = head.chain; + } + + // cone damage trace + head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), false); + while(head) + { + if((head != self) && head.takedamage) + { + // ======================== + // BLAST CONE CALCULATION + // ======================== + + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = CENTER_OR_VIEWOFS(head); + + // find the closest point on the enemy to the center of the attack + 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); + a = h * (normalize(center - self.origin) * w_shotdir); + // WEAPONTODO: replace with simpler method + + vector nearest_on_line = (w_shotorg + a * w_shotdir); + vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); + + if((vlen(head.WarpZone_findradius_dist) <= WEP_CVAR(shockwave, blast_distance)) + && (W_Shockwave_Attack_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) + { + // calculate importance of distance and accuracy for this attack + multiplier_from_accuracy = (1 - + W_Shockwave_Attack_CheckSpread( + nearest_to_attacker, + nearest_on_line, + w_shotorg, + attack_endpos + ) + ); + multiplier_from_distance = (1 - + (distance_to_hit ? + min(1, (vlen(head.WarpZone_findradius_dist) / distance_to_end)) + : + 0 + ) + ); + multiplier = + max( + WEP_CVAR(shockwave, blast_multiplier_min), + ( + (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy)) + + + (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance)) + ) + ); + + // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage + final_damage = + ( + (WEP_CVAR(shockwave, blast_damage) * multiplier) + + + (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier)) + ); + + // figure out the direction of force + final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias)); + final_force = normalize(center - (nearest_on_line - final_force)); + //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); + + // now multiply the direction by force units + final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier); + final_force.z *= WEP_CVAR(shockwave, blast_force_zscale); + + // queue damage with this calculated info + if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); } + + #ifdef DEBUG_SHOCKWAVE + LOG_INFO(sprintf( + "BLAST HIT: multiplier = %f, damage = %f, force = %f... " + "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", + multiplier, + final_damage, + vlen(final_force), + multiplier_from_accuracy, + multiplier_from_distance + )); + #endif + } + } + head = head.chain; + } + + for(i = 1; i <= queue; ++i) + { + head = shockwave_hit[i-1]; + final_force = shockwave_hit_force[i-1]; + final_damage = shockwave_hit_damage[i-1]; + + Damage( + head, + self, + self, + final_damage, + WEP_SHOCKWAVE.m_id, + head.origin, + final_force + ); + + if(accuracy_isgooddamage(self.realowner, head)) + { + LOG_INFO("wtf\n"); + accuracy_add(self.realowner, WEP_SHOCKWAVE.m_id, 0, final_damage); + } + + #ifdef DEBUG_SHOCKWAVE + LOG_INFO(sprintf( + "SHOCKWAVE by %s: damage = %f, force = %f.\n", + self.netname, + final_damage, + vlen(final_force) + )); + #endif + + shockwave_hit[i-1] = world; + shockwave_hit_force[i-1] = '0 0 0'; + shockwave_hit_damage[i-1] = 0; + } +} + +bool W_Shockwave(int req) +{ + switch(req) + { + case WR_AIM: + { + if(vlen(self.origin - self.enemy.origin) <= WEP_CVAR(shockwave, melee_range)) + { self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false); } + else + { self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false); } + + return true; + } + case WR_THINK: + { + if(self.BUTTON_ATCK) + { + if(time >= self.shockwave_blasttime) // handle refire separately so the secondary can be fired straight after a primary + { + if(weapon_prepareattack(0, WEP_CVAR(shockwave, blast_animtime))) + { + W_Shockwave_Attack(); + self.shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready); + } + } + } + else if(self.BUTTON_ATCK2) + { + //if(self.clip_load >= 0) // we are not currently reloading + if(!self.crouch) // no crouchmelee please + if(weapon_prepareattack(1, WEP_CVAR(shockwave, melee_refire))) + { + // attempt forcing playback of the anim by switching to another anim (that we never play) here... + weapon_thinkf(WFRAME_FIRE1, 0, W_Shockwave_Melee); + } + } + + return true; + } + case WR_INIT: + { + precache_model("models/uziflash.md3"); + precache_model(W_Model("g_shotgun.md3")); + precache_model(W_Model("v_shotgun.md3")); + precache_model(W_Model("h_shotgun.iqm")); + precache_sound("misc/itempickup.wav"); + precache_sound(W_Sound("lasergun_fire")); + precache_sound(W_Sound("shotgun_melee")); + SHOCKWAVE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + // shockwave has infinite ammo + return true; + } + case WR_CONFIG: + { + SHOCKWAVE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SHOCKWAVE_MURDER_SLAP; + else + return WEAPON_SHOCKWAVE_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +// WEAPONTODO: add client side settings for these +const float SW_MAXALPHA = 0.5; +const float SW_FADETIME = 0.4; +const float SW_DISTTOMIN = 200; +void Draw_Shockwave() +{ + // fading/removal control + float a = bound(0, (SW_MAXALPHA - ((time - self.sw_time) / SW_FADETIME)), SW_MAXALPHA); + if(a < ALPHA_MIN_VISIBLE) { remove(self); } + + // WEAPONTODO: save this only once when creating the entity + vector sw_color = getcsqcplayercolor(self.sv_entnum); // GetTeamRGB(GetPlayerColor(self.sv_entnum)); + + // WEAPONTODO: trace to find what we actually hit + vector endpos = (self.sw_shotorg + (self.sw_shotdir * self.sw_distance)); + + vectorvectors(self.sw_shotdir); + vector right = v_right; // save this for when we do makevectors later + vector up = v_up; // save this for when we do makevectors later + + // WEAPONTODO: combine and simplify these calculations + vector min_end = ((self.sw_shotorg + (self.sw_shotdir * SW_DISTTOMIN)) + (up * self.sw_spread_min)); + vector max_end = (endpos + (up * self.sw_spread_max)); + float spread_to_min = vlen(normalize(min_end - self.sw_shotorg) - self.sw_shotdir); + float spread_to_max = vlen(normalize(max_end - min_end) - self.sw_shotdir); + + vector first_min_end = '0 0 0', prev_min_end = '0 0 0', new_min_end = '0 0 0'; + vector first_max_end = '0 0 0', prev_max_end = '0 0 0', new_max_end = '0 0 0'; + float new_max_dist, new_min_dist; + + vector deviation, angle = '0 0 0'; + float counter, divisions = 20; + for(counter = 0; counter < divisions; ++counter) + { + // perfect circle effect lines + makevectors('0 360 0' * (0.75 + (counter - 0.5) / divisions)); + angle.y = v_forward.x; + angle.z = v_forward.y; + + // first do the spread_to_min effect + deviation = angle * spread_to_min; + deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z))); + new_min_dist = SW_DISTTOMIN; + new_min_end = (self.sw_shotorg + (deviation * new_min_dist)); + //te_lightning2(world, new_min_end, self.sw_shotorg); + + // then calculate spread_to_max effect + deviation = angle * spread_to_max; + deviation = ((self.sw_shotdir + (right * deviation.y) + (up * deviation.z))); + new_max_dist = vlen(new_min_end - endpos); + new_max_end = (new_min_end + (deviation * new_max_dist)); + //te_lightning2(world, new_end, prev_min_end); + + + if(counter == 0) + { + first_min_end = new_min_end; + first_max_end = new_max_end; + } + + if(counter >= 1) + { + // draw from shot origin to min spread radius + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(new_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a); + R_EndPolygon(); + + // draw from min spread radius to max spread radius + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(new_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a); + R_PolygonVertex(new_max_end, '0 0 0', sw_color, a); + R_EndPolygon(); + } + + prev_min_end = new_min_end; + prev_max_end = new_max_end; + + // last division only + if((counter + 1) == divisions) + { + // draw from shot origin to min spread radius + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(first_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(self.sw_shotorg, '0 0 0', sw_color, a); + R_EndPolygon(); + + // draw from min spread radius to max spread radius + R_BeginPolygon("", DRAWFLAG_NORMAL); + R_PolygonVertex(first_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a); + R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a); + R_PolygonVertex(first_max_end, '0 0 0', sw_color, a); + R_EndPolygon(); + } + } +} + +void Net_ReadShockwaveParticle(void) +{ + entity shockwave; + shockwave = spawn(); + shockwave.draw = Draw_Shockwave; + + shockwave.sw_shotorg_x = ReadCoord(); shockwave.sw_shotorg_y = ReadCoord(); shockwave.sw_shotorg_z = ReadCoord(); + shockwave.sw_shotdir_x = ReadCoord(); shockwave.sw_shotdir_y = ReadCoord(); shockwave.sw_shotdir_z = ReadCoord(); + + shockwave.sw_distance = ReadShort(); + shockwave.sw_spread_max = ReadByte(); + shockwave.sw_spread_min = ReadByte(); + + shockwave.sv_entnum = ReadByte(); + + shockwave.sw_time = time; +} + +bool W_Shockwave(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + // handled by Net_ReadShockwaveParticle + //vector org2; + //org2 = w_org + w_backoff * 2; + //pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); + return false; + } + case WR_INIT: + { + //precache_sound(W_Sound("ric1")); + //precache_sound(W_Sound("ric2")); + //precache_sound(W_Sound("ric3")); + return false; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/shotgun.qc b/qcsrc/common/weapons/weapon/shotgun.qc new file mode 100644 index 0000000000..9024bbfa1f --- /dev/null +++ b/qcsrc/common/weapons/weapon/shotgun.qc @@ -0,0 +1,392 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ SHOTGUN, +/* function */ W_Shotgun, +/* ammotype */ ammo_shells, +/* impulse */ 2, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_LOW, +/* color */ '0.5 0.25 0', +/* modelname */ "shotgun", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairshotgun 0.65", +/* wepimg */ "weaponshotgun", +/* refname */ "shotgun", +/* wepname */ _("Shotgun") +); + +#define SHOTGUN_SETTINGS(w_cvar,w_prop) SHOTGUN_SETTINGS_LIST(w_cvar, w_prop, SHOTGUN, shotgun) +#define SHOTGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, PRI, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, PRI, bullets) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, PRI, solidpenetration) \ + w_cvar(id, sn, PRI, spread) \ + w_cvar(id, sn, NONE, secondary) \ + w_cvar(id, sn, SEC, melee_time) \ + w_cvar(id, sn, SEC, melee_no_doubleslap) \ + w_cvar(id, sn, SEC, melee_traces) \ + w_cvar(id, sn, SEC, melee_swing_up) \ + w_cvar(id, sn, SEC, melee_swing_side) \ + w_cvar(id, sn, SEC, melee_nonplayerdamage) \ + w_cvar(id, sn, SEC, melee_multihit) \ + w_cvar(id, sn, SEC, melee_delay) \ + w_cvar(id, sn, SEC, melee_range) \ + w_cvar(id, sn, SEC, alt_animtime) \ + w_cvar(id, sn, SEC, alt_refire) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +SHOTGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_shotgun(void) { weapon_defaultspawnfunc(WEP_SHOTGUN.m_id); } + +void W_Shotgun_Attack(float isprimary) +{ + float sc; + entity flash; + + W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo)); + + W_SetupShot(self, true, 5, W_Sound("shotgun_fire"), ((isprimary) ? CH_WEAPON_A : CH_WEAPON_SINGLE), WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets)); + for(sc = 0;sc < WEP_CVAR_PRI(shotgun, bullets);sc = sc + 1) + fireBullet(w_shotorg, w_shotdir, WEP_CVAR_PRI(shotgun, spread), WEP_CVAR_PRI(shotgun, solidpenetration), WEP_CVAR_PRI(shotgun, damage), WEP_CVAR_PRI(shotgun, force), WEP_SHOTGUN.m_id, 0); + + Send_Effect(EFFECT_SHOTGUN_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo)); + + // casing code + if(autocvar_g_casings >= 1) + for(sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);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 W_Shotgun_Melee_Think(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 = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces)); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_SEC(shotgun, 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 / WEP_CVAR_SEC(shotgun, melee_traces))) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * WEP_CVAR_SEC(shotgun, melee_range)) + + (v_up * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_up)) + + (v_right * swing_factor * WEP_CVAR_SEC(shotgun, 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" || IS_MONSTER(trace_ent)); + + 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 || WEP_CVAR_SEC(shotgun, 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 = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1)); + else + swing_damage = (WEP_CVAR_SEC(shotgun, 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.m_id | HITTYPE_SECONDARY, + self.realowner.origin + self.realowner.view_ofs, + v_forward * WEP_CVAR_SEC(shotgun, force)); + + if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN.m_id, 0, swing_damage); } + + // draw large red flash for debugging + //te_customflash(targpos, 200, 2, '15 0 0'); + + if(WEP_CVAR_SEC(shotgun, 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, W_Sound("shotgun_melee"), VOL_BASE, ATTEN_NORM); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.realowner = self; + meleetemp.think = W_Shotgun_Melee_Think; + meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor(); + W_SetupShot_Range(self, true, 0, "", 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range)); +} + +// alternate secondary weapon frames +void W_Shotgun_Attack3_Frame2() +{ + if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) + if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + sound(self, CH_WEAPON_SINGLE, "misc/null.wav", VOL_BASE, ATTN_NORM); // kill previous sound + W_Shotgun_Attack(true); // actually is secondary, but we trick the last shot into playing full reload sound + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), w_ready); +} +void W_Shotgun_Attack3_Frame1() +{ + if (!WEP_ACTION(self.weapon, WR_CHECKAMMO2)) + if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + W_Shotgun_Attack(false); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame2); +} + +.float shotgun_primarytime; + +float W_Shotgun(float req) +{ + float ammo_amount; + switch(req) + { + case WR_AIM: + { + if(vlen(self.origin-self.enemy.origin) <= WEP_CVAR_SEC(shotgun, melee_range)) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, false); + else + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, false); + + return true; + } + case WR_THINK: + { + if(WEP_CVAR(shotgun, reload_ammo) && self.clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload + { + // don't force reload an empty shotgun if its melee attack is active + if(WEP_CVAR(shotgun, secondary) < 2) + WEP_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, WEP_CVAR_PRI(shotgun, animtime))) + { + W_Shotgun_Attack(true); + self.shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready); + } + } + } + else if(self.BUTTON_ATCK2 && WEP_CVAR(shotgun, secondary) == 2) + { + if(time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary + { + if(weapon_prepareattack(0, WEP_CVAR_SEC(shotgun, alt_animtime))) + { + W_Shotgun_Attack(false); + self.shotgun_primarytime = time + WEP_CVAR_SEC(shotgun, alt_refire) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(shotgun, alt_animtime), W_Shotgun_Attack3_Frame1); + } + } + } + } + if(self.clip_load >= 0) // we are not currently reloading + if(!self.crouch) // no crouchmelee please + if(WEP_CVAR(shotgun, secondary) == 1) + if((self.BUTTON_ATCK && self.WEP_AMMO(SHOTGUN) <= 0 && !(self.items & IT_UNLIMITED_WEAPON_AMMO)) || self.BUTTON_ATCK2) + if(weapon_prepareattack(1, WEP_CVAR_SEC(shotgun, 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); + } + + return true; + } + case WR_INIT: + { + precache_model("models/uziflash.md3"); + precache_model(W_Model("g_shotgun.md3")); + precache_model(W_Model("v_shotgun.md3")); + precache_model(W_Model("h_shotgun.iqm")); + precache_sound("misc/itempickup.wav"); + precache_sound(W_Sound("shotgun_fire")); + precache_sound(W_Sound("shotgun_melee")); + SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.ammo_field = ammo_none; + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo); + ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + if(IS_BOT_CLIENT(self)) + if(vlen(self.origin-self.enemy.origin) > WEP_CVAR_SEC(shotgun, melee_range)) + return false; // bots cannot use secondary out of range (fixes constant melee when out of ammo) + switch(WEP_CVAR(shotgun, secondary)) + { + case 1: return true; // melee does not use ammo + case 2: // secondary triple shot + { + ammo_amount = self.WEP_AMMO(SHOTGUN) >= WEP_CVAR_PRI(shotgun, ammo); + ammo_amount += self.(weapon_load[WEP_SHOTGUN.m_id]) >= WEP_CVAR_PRI(shotgun, ammo); + return ammo_amount; + } + default: return false; // secondary unavailable + } + } + case WR_CONFIG: + { + SHOTGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR_PRI(shotgun, ammo), W_Sound("reload")); // WEAPONTODO + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SHOTGUN_MURDER_SLAP; + else + return WEAPON_SHOTGUN_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +.float prevric; +float W_Shotgun(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum(EFFECT_SHOTGUN_IMPACT), org2, w_backoff * 1000, 1); + if(!w_issilent && time - self.prevric > 0.25) + { + if(w_random < 0.0165) + sound(self, CH_SHOTS, W_Sound("ric1"), VOL_BASE, ATTEN_NORM); + else if(w_random < 0.033) + sound(self, CH_SHOTS, W_Sound("ric2"), VOL_BASE, ATTEN_NORM); + else if(w_random < 0.05) + sound(self, CH_SHOTS, W_Sound("ric3"), VOL_BASE, ATTEN_NORM); + self.prevric = time; + } + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("ric1")); + precache_sound(W_Sound("ric2")); + precache_sound(W_Sound("ric3")); + return true; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/tuba.qc b/qcsrc/common/weapons/weapon/tuba.qc new file mode 100644 index 0000000000..686c89ec3a --- /dev/null +++ b/qcsrc/common/weapons/weapon/tuba.qc @@ -0,0 +1,514 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ TUBA, +/* function */ W_Tuba, +/* ammotype */ ammo_none, +/* impulse */ 1, +/* flags */ WEP_FLAG_HIDDEN | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* color */ '0 1 0', +/* modelname */ "tuba", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairtuba", +/* wepimg */ "weapontuba", +/* refname */ "tuba", +/* xgettext:no-c-format */ +/* wepname */ _("@!#%'n Tuba") +); + +#define TUBA_SETTINGS(w_cvar,w_prop) TUBA_SETTINGS_LIST(w_cvar, w_prop, TUBA, tuba) +#define TUBA_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, animtime) \ + w_cvar(id, sn, NONE, attenuation) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, edgedamage) \ + w_cvar(id, sn, NONE, fadetime) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, pitchstep) \ + w_cvar(id, sn, NONE, radius) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, volume) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +TUBA_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +float W_Tuba_MarkClientOnlyFieldsAsUsed() { + // These variables are only used by client/tuba.qc. TODO: move client/tuba.qc code here. + return WEP_CVAR(tuba, fadetime) + WEP_CVAR(tuba, pitchstep) + WEP_CVAR(tuba, volume); +} + +.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]; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_tuba(void) { weapon_defaultspawnfunc(WEP_TUBA.m_id); } + +bool W_Tuba_HasPlayed(entity pl, string melody, int instrument, bool 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[(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) + //printf("initial tempo rules: %f %f\n", mmin, mmax); + + for(i = 0; i < n; ++i) + { + vector vi = pl.(tuba_lastnotes[(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[(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 + //printf("first note: %f to %f, should be %f\n", vi_x, vi_y, ti); + //printf("second note: %f to %f, should be %f\n", vj_x, vj_y, tj); + //printf("m1 = %f\n", (vi_x - vj_y) / (ti - tj)); + //printf("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(void) +{ + // we have a note: + // on: self.spawnshieldtime + // off: time + // note: self.cnt + if(self.owner.tuba_note == self) + { + self.owner.tuba_lastnotes_last = (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); +} + +int W_Tuba_GetNote(entity pl, int hittype) +{ + float movestate = 5; + if (pl.movement.x < 0) movestate -= 3; + else if (pl.movement.x > 0) movestate += 3; + if (pl.movement.y < 0) movestate -= 1; + else if (pl.movement.y > 0) movestate += 1; + + int note = 0; + 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; +} + +bool W_Tuba_NoteSendEntity(entity to, int sf) +{ + int 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(void) +{ + 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 = WEP_CVAR(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, WEP_CVAR(tuba, damage)); + + n = W_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(!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 + WEP_CVAR(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, WEP_CVAR(tuba, damage), WEP_CVAR(tuba, edgedamage), WEP_CVAR(tuba, radius), world, world, WEP_CVAR(tuba, force), hittype | WEP_TUBA.m_id, world); + + o = gettaginfo(self.exteriorweaponentity, 0); + if(time > self.tuba_smoketime) + { + Send_Effect(EFFECT_SMOKE_RING, o + v_up * 45 + v_right * -6 + v_forward * 8, v_up * 100, 1); + self.tuba_smoketime = time + 0.25; + } +} + +bool W_Tuba(int req) +{ + switch(req) + { + case 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) < WEP_CVAR(tuba, radius)) + { + if(random() > 0.5) + self.BUTTON_ATCK = 1; + else + self.BUTTON_ATCK2 = 1; + } + + return true; + } + case WR_THINK: + { + if(self.BUTTON_ATCK) + if(weapon_prepareattack(0, WEP_CVAR(tuba, refire))) + { + W_Tuba_NoteOn(0); + //weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_tuba_animtime, w_ready); + weapon_thinkf(WFRAME_IDLE, WEP_CVAR(tuba, animtime), w_ready); + } + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, WEP_CVAR(tuba, refire))) + { + W_Tuba_NoteOn(HITTYPE_SECONDARY); + //weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_tuba_animtime, w_ready); + weapon_thinkf(WFRAME_IDLE, WEP_CVAR(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; + } + } + + return true; + } + case WR_INIT: + { + precache_model(W_Model("g_tuba.md3")); + precache_model(W_Model("v_tuba.md3")); + precache_model(W_Model("h_tuba.iqm")); + precache_model(W_Model("v_akordeon.md3")); + precache_model(W_Model("h_akordeon.iqm")); + precache_model(W_Model("v_kleinbottle.md3")); + precache_model(W_Model("h_kleinbottle.iqm")); + TUBA_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.ammo_field = ammo_none; + self.tuba_instrument = 0; + return true; + } + case 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); + Send_Effect(EFFECT_TELEPORT, w_shotorg, '0 0 0', 1); + self.weaponentity.state = WS_INUSE; + weapon_thinkf(WFRAME_RELOAD, 0.5, w_ready); + } + + return true; + } + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + return true; // tuba has infinite ammo + } + case WR_CONFIG: + { + TUBA_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case 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; + } + case 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 false; +} +#endif +#ifdef CSQC +bool W_Tuba(int req) +{ + // nothing to do here; particles of tuba are handled differently + // WEAPONTODO + + switch(req) + { + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return false; + } + } + + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/vaporizer.qc b/qcsrc/common/weapons/weapon/vaporizer.qc new file mode 100644 index 0000000000..f61960594a --- /dev/null +++ b/qcsrc/common/weapons/weapon/vaporizer.qc @@ -0,0 +1,306 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ VAPORIZER, +/* function */ W_Vaporizer, +/* ammotype */ ammo_cells, +/* impulse */ 7, +/* flags */ WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_FLAG_SUPERWEAPON | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '0.5 1 1', +/* modelname */ "minstanex", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairminstanex 0.6", +/* wepimg */ "weaponminstanex", +/* refname */ "vaporizer", +/* wepname */ _("Vaporizer") +); + +#define VAPORIZER_SETTINGS(w_cvar,w_prop) VAPORIZER_SETTINGS_LIST(w_cvar, w_prop, VAPORIZER, vaporizer) +#define VAPORIZER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, PRI, ammo) \ + w_cvar(id, sn, PRI, animtime) \ + w_cvar(id, sn, PRI, refire) \ + w_cvar(id, sn, SEC, ammo) \ + w_cvar(id, sn, SEC, animtime) \ + w_cvar(id, sn, SEC, damage) \ + w_cvar(id, sn, SEC, delay) \ + w_cvar(id, sn, SEC, edgedamage) \ + w_cvar(id, sn, SEC, force) \ + w_cvar(id, sn, SEC, lifetime) \ + w_cvar(id, sn, SEC, radius) \ + w_cvar(id, sn, SEC, refire) \ + w_cvar(id, sn, SEC, shotangle) \ + w_cvar(id, sn, SEC, speed) \ + w_cvar(id, sn, SEC, spread) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +VAPORIZER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.float vaporizer_lasthit; +.float jump_interval; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_vaporizer(void) { weapon_defaultspawnfunc(WEP_VAPORIZER.m_id); } +void spawnfunc_weapon_minstanex(void) { spawnfunc_weapon_vaporizer(); } + +void W_Vaporizer_Attack(void) +{ + float flying; + flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last + + W_SetupShot(self, true, 0, "", CH_WEAPON_A, 10000); + // handle sound separately so we can change the volume + // added bonus: no longer plays the strength sound (strength gives no bonus to instakill anyway) + sound (self, CH_WEAPON_A, W_Sound("minstanexfire"), VOL_BASE * 0.8, ATTEN_NORM); + + yoda = 0; + damage_goodhits = 0; + FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, 10000, 800, 0, 0, 0, 0, WEP_VAPORIZER.m_id); + + if(yoda && flying) + Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); + if(damage_goodhits && self.vaporizer_lasthit) + { + Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE); + damage_goodhits = 0; // only every second time + } + + self.vaporizer_lasthit = damage_goodhits; + + Send_Effect(EFFECT_VORTEX_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) + Send_Effect(EFFECT_VAPORIZER_RED_HIT, w_shotorg, v, 1); + else + Send_Effect(EFFECT_VAPORIZER_RED, w_shotorg, v, 1); + break; + case NUM_TEAM_2: // Blue + if(damage_goodhits) + Send_Effect(EFFECT_VAPORIZER_BLUE_HIT, w_shotorg, v, 1); + else + Send_Effect(EFFECT_VAPORIZER_BLUE, w_shotorg, v, 1); + break; + case NUM_TEAM_3: // Yellow + if(damage_goodhits) + Send_Effect(EFFECT_VAPORIZER_YELLOW_HIT, w_shotorg, v, 1); + else + Send_Effect(EFFECT_VAPORIZER_YELLOW, w_shotorg, v, 1); + break; + case NUM_TEAM_4: // Pink + if(damage_goodhits) + Send_Effect(EFFECT_VAPORIZER_PINK_HIT, w_shotorg, v, 1); + else + Send_Effect(EFFECT_VAPORIZER_PINK, w_shotorg, v, 1); + break; + default: + if(damage_goodhits) + Send_Effect_("TE_TEI_G3_HIT", w_shotorg, v, 1); + else + Send_Effect_("TE_TEI_G3", w_shotorg, v, 1); + break; + } + + W_DecreaseAmmo(((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo))); +} + +float W_Vaporizer(float req) +{ + float ammo_amount; + float vaporizer_ammo; + + // now multiple WR_s use this + vaporizer_ammo = ((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo)); + + switch(req) + { + case WR_AIM: + { + if(self.WEP_AMMO(VAPORIZER) > 0) + self.BUTTON_ATCK = bot_aim(1000000, 0, 1, false); + else + self.BUTTON_ATCK2 = bot_aim(WEP_CVAR_SEC(vaporizer, speed), 0, WEP_CVAR_SEC(vaporizer, lifetime), false); // WEAPONTODO: replace with proper vaporizer cvars + + return true; + } + case WR_THINK: + { + // if the laser uses load, we also consider its ammo for reloading + if(WEP_CVAR(vaporizer, reload_ammo) && WEP_CVAR_SEC(vaporizer, ammo) && self.clip_load < min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else if(WEP_CVAR(vaporizer, reload_ammo) && self.clip_load < vaporizer_ammo) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire))) + { + W_Vaporizer_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, 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 instagib) + self.jump_interval = time + WEP_CVAR_SEC(vaporizer, refire) * W_WeaponRateFactor(); + + // decrease ammo for the laser? + if(WEP_CVAR_SEC(vaporizer, ammo)) + W_DecreaseAmmo(WEP_CVAR_SEC(vaporizer, ammo)); + + // ugly instagib hack to reuse the fire mode of the laser + int oldwep = self.weapon; // we can't avoid this hack + self.weapon = WEP_BLASTER.m_id; + W_Blaster_Attack( + WEP_BLASTER.m_id | HITTYPE_SECONDARY, + WEP_CVAR_SEC(vaporizer, shotangle), + WEP_CVAR_SEC(vaporizer, damage), + WEP_CVAR_SEC(vaporizer, edgedamage), + WEP_CVAR_SEC(vaporizer, radius), + WEP_CVAR_SEC(vaporizer, force), + WEP_CVAR_SEC(vaporizer, speed), + WEP_CVAR_SEC(vaporizer, spread), + WEP_CVAR_SEC(vaporizer, delay), + WEP_CVAR_SEC(vaporizer, lifetime) + ); + self.weapon = oldwep; + + // now do normal refire + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(vaporizer, animtime), w_ready); + } + } + + return true; + } + case WR_INIT: + { + precache_model("models/nexflash.md3"); + precache_model(W_Model("g_minstanex.md3")); + precache_model(W_Model("v_minstanex.md3")); + precache_model(W_Model("h_minstanex.iqm")); + precache_sound(W_Sound("minstanexfire")); + precache_sound(W_Sound("nexwhoosh1")); + precache_sound(W_Sound("nexwhoosh2")); + precache_sound(W_Sound("nexwhoosh3")); + //W_Blaster(WR_INIT); // Samual: Is this really the proper thing to do? Didn't we already run this previously? + VAPORIZER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.ammo_field = WEP_AMMO(VAPORIZER); + self.vaporizer_lasthit = 0; + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(VAPORIZER) >= vaporizer_ammo; + ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= vaporizer_ammo; + return ammo_amount; + } + case WR_CHECKAMMO2: + { + if(!WEP_CVAR_SEC(vaporizer, ammo)) + return true; + ammo_amount = self.WEP_AMMO(VAPORIZER) >= WEP_CVAR_SEC(vaporizer, ammo); + ammo_amount += self.(weapon_load[WEP_VAPORIZER.m_id]) >= WEP_CVAR_SEC(vaporizer, ammo); + return ammo_amount; + } + case WR_CONFIG: + { + VAPORIZER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.vaporizer_lasthit = 0; + return true; + } + case WR_RELOAD: + { + float used_ammo; + if(WEP_CVAR_SEC(vaporizer, ammo)) + used_ammo = min(vaporizer_ammo, WEP_CVAR_SEC(vaporizer, ammo)); + else + used_ammo = vaporizer_ammo; + + W_Reload(used_ammo, W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + return WEAPON_VAPORIZER_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +float W_Vaporizer(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + if(w_deathtype & HITTYPE_SECONDARY) + { + pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); + if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("laserimpact"), VOL_BASE, ATTN_NORM); } + } + else + { + pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1); + if(!w_issilent) { sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM); } + } + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("laserimpact")); + precache_sound(W_Sound("neximpact")); + if(autocvar_cl_reticle && autocvar_cl_reticle_weapon) + { + precache_pic("gfx/reticle_nex"); + } + return true; + } + case WR_ZOOMRETICLE: + { + if(button_zoom || zoomscript_caught) + { + reticle_image = "gfx/reticle_nex"; + return true; + } + else + { + // no weapon specific image for this weapon + return false; + } + } + } + return false; +} +#endif +#endif diff --git a/qcsrc/common/weapons/weapon/vortex.qc b/qcsrc/common/weapons/weapon/vortex.qc new file mode 100644 index 0000000000..3aa17e526d --- /dev/null +++ b/qcsrc/common/weapons/weapon/vortex.qc @@ -0,0 +1,362 @@ +#ifndef IMPLEMENTATION +REGISTER_WEAPON( +/* WEP_##id */ VORTEX, +/* function */ W_Vortex, +/* ammotype */ ammo_cells, +/* impulse */ 7, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '0.5 1 1', +/* modelname */ "nex", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairnex 0.65", +/* wepimg */ "weaponnex", +/* refname */ "vortex", +/* wepname */ _("Vortex") +); + +#define VORTEX_SETTINGS(w_cvar,w_prop) VORTEX_SETTINGS_LIST(w_cvar, w_prop, VORTEX, vortex) +#define VORTEX_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, BOTH, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, BOTH, damagefalloff_mindist) \ + w_cvar(id, sn, BOTH, damagefalloff_maxdist) \ + w_cvar(id, sn, BOTH, damagefalloff_halflife) \ + w_cvar(id, sn, BOTH, damagefalloff_forcehalflife) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, NONE, charge) \ + w_cvar(id, sn, NONE, charge_mindmg) \ + w_cvar(id, sn, NONE, charge_shot_multiplier) \ + w_cvar(id, sn, NONE, charge_animlimit) \ + w_cvar(id, sn, NONE, charge_limit) \ + w_cvar(id, sn, NONE, charge_rate) \ + w_cvar(id, sn, NONE, charge_rot_rate) \ + w_cvar(id, sn, NONE, charge_rot_pause) \ + w_cvar(id, sn, NONE, charge_start) \ + w_cvar(id, sn, NONE, charge_minspeed) \ + w_cvar(id, sn, NONE, charge_maxspeed) \ + w_cvar(id, sn, NONE, charge_velocity_rate) \ + w_cvar(id, sn, NONE, secondary) \ + w_cvar(id, sn, SEC, chargepool) \ + w_cvar(id, sn, SEC, chargepool_regen) \ + w_cvar(id, sn, SEC, chargepool_pause_regen) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +VORTEX_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) + +.float vortex_lasthit; +#endif +#endif +#ifdef IMPLEMENTATION +#ifdef SVQC +void spawnfunc_weapon_vortex(void) { weapon_defaultspawnfunc(WEP_VORTEX.m_id); } +void spawnfunc_weapon_nex(void) { spawnfunc_weapon_vortex(); } + +void SendCSQCVortexBeamParticle(float charge) { + vector v; + v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte(MSG_BROADCAST, TE_CSQC_VORTEXBEAMPARTICLE); + 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_Vortex_Attack(float issecondary) +{ + float mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, myammo, charge; + + mydmg = WEP_CVAR_BOTH(vortex, !issecondary, damage); + myforce = WEP_CVAR_BOTH(vortex, !issecondary, force); + mymindist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_mindist); + mymaxdist = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_maxdist); + myhalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_halflife); + myforcehalflife = WEP_CVAR_BOTH(vortex, !issecondary, damagefalloff_forcehalflife); + myammo = WEP_CVAR_BOTH(vortex, !issecondary, ammo); + + float flying; + flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last + + if(WEP_CVAR(vortex, charge)) + { + charge = WEP_CVAR(vortex, charge_mindmg) / mydmg + (1 - WEP_CVAR(vortex, charge_mindmg) / mydmg) * self.vortex_charge; + self.vortex_charge *= WEP_CVAR(vortex, 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, W_Sound("nexfire"), CH_WEAPON_A, mydmg); + if(charge > WEP_CVAR(vortex, charge_animlimit) && WEP_CVAR(vortex, charge_animlimit)) // if the Vortex is overcharged, we play an extra sound + { + sound(self, CH_WEAPON_B, W_Sound("nexcharge"), VOL_BASE * (charge - 0.5 * WEP_CVAR(vortex, charge_animlimit)) / (1 - 0.5 * WEP_CVAR(vortex, charge_animlimit)), ATTN_NORM); + } + + yoda = 0; + damage_goodhits = 0; + FireRailgunBullet(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, mydmg, myforce, mymindist, mymaxdist, myhalflife, myforcehalflife, WEP_VORTEX.m_id); + + if(yoda && flying) + Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_YODA); + if(damage_goodhits && self.vortex_lasthit) + { + Send_Notification(NOTIF_ONE, self, MSG_ANNCE, ANNCE_ACHIEVEMENT_IMPRESSIVE); + damage_goodhits = 0; // only every second time + } + + self.vortex_lasthit = damage_goodhits; + + //beam and muzzle flash done on client + SendCSQCVortexBeamParticle(charge); + + W_DecreaseAmmo(myammo); +} + +void spawnfunc_weapon_vortex(void); // defined in t_items.qc + +.float vortex_chargepool_pauseregen_finished; +bool W_Vortex(int req) +{ + float dt; + float ammo_amount; + switch(req) + { + case WR_AIM: + { + if(bot_aim(1000000, 0, 1, false)) + self.BUTTON_ATCK = true; + else + { + if(WEP_CVAR(vortex, charge)) + self.BUTTON_ATCK2 = true; + } + return true; + } + case WR_THINK: + { + if(WEP_CVAR(vortex, charge) && self.vortex_charge < WEP_CVAR(vortex, charge_limit)) + self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_rate) * frametime / W_TICSPERFRAME); + + if(WEP_CVAR_SEC(vortex, chargepool)) + if(self.vortex_chargepool_ammo < 1) + { + if(self.vortex_chargepool_pauseregen_finished < time) + self.vortex_chargepool_ammo = min(1, self.vortex_chargepool_ammo + WEP_CVAR_SEC(vortex, chargepool_regen) * frametime / W_TICSPERFRAME); + self.pauseregen_finished = max(self.pauseregen_finished, time + WEP_CVAR_SEC(vortex, chargepool_pause_regen)); + } + + if(autocvar_g_balance_vortex_reload_ammo && self.clip_load < min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo))) // forced reload + WEP_ACTION(self.weapon, WR_RELOAD); + else + { + if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(vortex, refire))) + { + W_Vortex_Attack(0); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vortex, animtime), w_ready); + } + } + if((WEP_CVAR(vortex, charge) && !WEP_CVAR(vortex, secondary)) ? (self.BUTTON_ZOOM | self.BUTTON_ZOOMSCRIPT) : self.BUTTON_ATCK2) + { + if(WEP_CVAR(vortex, charge)) + { + self.vortex_charge_rottime = time + WEP_CVAR(vortex, charge_rot_pause); + dt = frametime / W_TICSPERFRAME; + + if(self.vortex_charge < 1) + { + if(WEP_CVAR_SEC(vortex, chargepool)) + { + if(WEP_CVAR_SEC(vortex, ammo)) + { + // always deplete if secondary is held + self.vortex_chargepool_ammo = max(0, self.vortex_chargepool_ammo - WEP_CVAR_SEC(vortex, ammo) * dt); + + dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate)); + self.vortex_chargepool_pauseregen_finished = time + WEP_CVAR_SEC(vortex, chargepool_pause_regen); + dt = min(dt, self.vortex_chargepool_ammo); + dt = max(0, dt); + + self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate); + } + } + + else if(WEP_CVAR_SEC(vortex, ammo)) + { + if(self.BUTTON_ATCK2) // only eat ammo when the button is pressed + { + dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate)); + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + // if this weapon is reloadable, decrease its load. Else decrease the player's ammo + if(autocvar_g_balance_vortex_reload_ammo) + { + dt = min(dt, (self.clip_load - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo)); + dt = max(0, dt); + if(dt > 0) + { + self.clip_load = max(WEP_CVAR_SEC(vortex, ammo), self.clip_load - WEP_CVAR_SEC(vortex, ammo) * dt); + } + self.(weapon_load[WEP_VORTEX.m_id]) = self.clip_load; + } + else + { + dt = min(dt, (self.WEP_AMMO(VORTEX) - WEP_CVAR_PRI(vortex, ammo)) / WEP_CVAR_SEC(vortex, ammo)); + dt = max(0, dt); + if(dt > 0) + { + self.WEP_AMMO(VORTEX) = max(WEP_CVAR_SEC(vortex, ammo), self.WEP_AMMO(VORTEX) - WEP_CVAR_SEC(vortex, ammo) * dt); + } + } + } + self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate); + } + } + + else + { + dt = min(dt, (1 - self.vortex_charge) / WEP_CVAR(vortex, charge_rate)); + self.vortex_charge += dt * WEP_CVAR(vortex, charge_rate); + } + } + } + else if(WEP_CVAR(vortex, secondary)) + { + if(weapon_prepareattack(0, WEP_CVAR_SEC(vortex, refire))) + { + W_Vortex_Attack(1); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_SEC(vortex, animtime), w_ready); + } + } + } + } + + return true; + } + case WR_INIT: + { + precache_model("models/nexflash.md3"); + precache_model(W_Model("g_nex.md3")); + precache_model(W_Model("v_nex.md3")); + precache_model(W_Model("h_nex.iqm")); + precache_sound(W_Sound("nexfire")); + precache_sound(W_Sound("nexcharge")); + precache_sound(W_Sound("nexwhoosh1")); + precache_sound(W_Sound("nexwhoosh2")); + precache_sound(W_Sound("nexwhoosh3")); + VORTEX_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP); + return true; + } + case WR_SETUP: + { + self.vortex_lasthit = 0; + return true; + } + case WR_CHECKAMMO1: + { + ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_PRI(vortex, ammo); + ammo_amount += (autocvar_g_balance_vortex_reload_ammo && self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_PRI(vortex, ammo)); + return ammo_amount; + } + case WR_CHECKAMMO2: + { + if(WEP_CVAR(vortex, secondary)) + { + // don't allow charging if we don't have enough ammo + ammo_amount = self.WEP_AMMO(VORTEX) >= WEP_CVAR_SEC(vortex, ammo); + ammo_amount += self.(weapon_load[WEP_VORTEX.m_id]) >= WEP_CVAR_SEC(vortex, ammo); + return ammo_amount; + } + else + { + return false; // zoom is not a fire mode + } + } + case WR_CONFIG: + { + VORTEX_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS); + return true; + } + case WR_RESETPLAYER: + { + self.vortex_lasthit = 0; + return true; + } + case WR_RELOAD: + { + W_Reload(min(WEP_CVAR_PRI(vortex, ammo), WEP_CVAR_SEC(vortex, ammo)), W_Sound("reload")); + return true; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + return WEAPON_VORTEX_MURDER; + } + } + return false; +} +#endif +#ifdef CSQC +float autocvar_g_balance_vortex_secondary = 0; // WEAPONTODO +bool W_Vortex(int req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum(EFFECT_VORTEX_IMPACT), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, W_Sound("neximpact"), VOL_BASE, ATTN_NORM); + + return true; + } + case WR_INIT: + { + precache_sound(W_Sound("neximpact")); + if(autocvar_cl_reticle && autocvar_cl_reticle_weapon) + { + precache_pic("gfx/reticle_nex"); + } + return true; + } + case WR_ZOOMRETICLE: + { + if(button_zoom || zoomscript_caught || (!WEP_CVAR(vortex, secondary) && button_attack2)) + { + reticle_image = "gfx/reticle_nex"; + return true; + } + else + { + // no weapon specific image for this weapon + return false; + } + } + } + return false; +} +#endif +#endif