// 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"
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
+++ /dev/null
-#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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