2 REGISTER_WEAPON(LASER, w_laser, 0, 1, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, 0, "laser", "laser", _("Laser"))
5 void(float imp) W_SwitchWeapon;
8 .entity swing_alreadyhit;
10 void SendCSQCShockwaveParticle(float spread)
12 //WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
13 WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
14 WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE);
15 WriteCoord(MSG_BROADCAST, w_shotorg_x);
16 WriteCoord(MSG_BROADCAST, w_shotorg_y);
17 WriteCoord(MSG_BROADCAST, w_shotorg_z);
18 WriteCoord(MSG_BROADCAST, w_shotdir_x);
19 WriteCoord(MSG_BROADCAST, w_shotdir_y);
20 WriteCoord(MSG_BROADCAST, w_shotdir_z);
21 WriteByte(MSG_BROADCAST, bound(0, 255 * spread, 255));
24 void W_Laser_Touch (void)
28 self.event_damage = SUB_Null;
30 RadiusDamage (self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other);
32 RadiusDamage (self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other);
39 self.movetype = MOVETYPE_FLY;
40 self.think = SUB_Remove;
42 self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime;
44 self.nextthink = time + autocvar_g_balance_laser_primary_lifetime;
45 CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE);
49 float W_Laser_Shockwave_CheckSpread(vector targetorg, vector nearest_on_line, vector sw_shotorg, vector attack_hitpos)
52 float distance_of_attack = vlen(sw_shotorg - attack_hitpos);
53 float distance_from_line = vlen(targetorg - nearest_on_line);
55 spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1);
56 spreadlimit = (autocvar_g_balance_laser_primary_spread_min * (1 - spreadlimit) + autocvar_g_balance_laser_primary_spread_max * spreadlimit);
58 if(spreadlimit && (distance_from_line <= spreadlimit))
59 return bound(0, (distance_from_line / spreadlimit), 1);
64 float W_Laser_Shockwave_IsVisible(entity head, vector nearest_on_line, vector sw_shotorg, vector attack_hitpos)
66 vector nearest_to_attacker = head.WarpZone_findradius_nearest;
67 vector center = (head.origin + (head.mins + head.maxs) * 0.5);
71 // STEP ONE: Check if the nearest point is clear
72 if(W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_hitpos))
74 WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_WORLDONLY, self);
75 if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage
78 // STEP TWO: Check if shotorg to center point is clear
79 if(W_Laser_Shockwave_CheckSpread(center, nearest_on_line, sw_shotorg, attack_hitpos))
81 WarpZone_TraceLine(sw_shotorg, center, MOVE_WORLDONLY, self);
82 if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage
85 // STEP THREE: Check each corner to see if they are clear
88 corner = get_corner_position(head, i);
89 if(W_Laser_Shockwave_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_hitpos))
91 WarpZone_TraceLine(sw_shotorg, corner, MOVE_WORLDONLY, self);
92 if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage
99 void W_Laser_Shockwave (void)
102 float multiplier, multiplier_from_accuracy, multiplier_from_distance;
103 float final_damage, final_spread;
104 vector final_force, center;
107 // set up the shot direction
108 vector wanted_shot_direction = (v_forward * cos(autocvar_g_balance_laser_primary_shotangle * DEG2RAD) + v_up * sin(autocvar_g_balance_laser_primary_shotangle * DEG2RAD));
109 W_SetupShot_Dir(self, wanted_shot_direction, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage);
110 vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_primary_radius));
112 // find out what we're pointing at and acquire the warpzone transform
113 WarpZone_TraceLine(w_shotorg, attack_endpos, FALSE, self);
114 entity aim_ent = trace_ent;
115 vector attack_hitpos = trace_endpos;
116 float distance_of_attack = vlen(w_shotorg - attack_hitpos);
118 // do the jump explosion now (also handles the impact effect)
119 RadiusDamageForSource(self, trace_endpos, '0 0 0', self, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_jumpradius, world, self, TRUE, autocvar_g_balance_laser_primary_force, WEP_LASER, world);
121 // also do the firing effect now
122 SendCSQCShockwaveParticle(autocvar_g_balance_laser_primary_spread);
124 // did we hit a player directly?
125 if(aim_ent.takedamage)
127 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
128 center = (aim_ent.origin + ((aim_ent.classname == "player") ? aim_ent.view_ofs : ((aim_ent.mins + aim_ent.maxs) * 0.5)));
130 multiplier_from_accuracy = 1;
131 multiplier_from_distance = (1 - (distance_of_attack ? min(1, (distance_of_attack / autocvar_g_balance_laser_primary_radius)) : 0));
132 multiplier = max(autocvar_g_balance_laser_primary_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_primary_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_primary_multiplier_distance)));
134 final_force = ((normalize(center - attack_hitpos) * autocvar_g_balance_laser_primary_force) * multiplier);
135 final_damage = (autocvar_g_balance_laser_primary_damage * multiplier);
136 Damage(aim_ent, self, self, final_damage, WEP_LASER, aim_ent.origin, final_force);
138 print("multiplier = ", ftos(multiplier), ", multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ", ");
139 print(strcat("direct hit damage = ", ftos(autocvar_g_balance_laser_primary_damage), ", force = ", vtos(final_force), ".\n"));
142 // now figure out if I hit anything else than what my aim directly pointed at...
143 head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_primary_radius, FALSE);
148 if((head != self && head != aim_ent) && (head.takedamage))
150 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
151 center = (head.origin + ((head.classname == "player") ? head.view_ofs : ((head.mins + head.maxs) * 0.5)));
153 // find the closest point on the enemy to the center of the attack
154 float ang; // angle between shotdir and h
155 float h; // hypotenuse, which is the distance between attacker to head
156 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
158 h = vlen(center - self.origin);
159 ang = acos(dotproduct(normalize(center - self.origin), w_shotdir));
162 vector nearest_on_line = (w_shotorg + a * w_shotdir);
163 vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
164 float distance_to_target = vlen(w_shotorg - nearest_to_attacker);
166 if(distance_to_target <= autocvar_g_balance_laser_primary_radius)
168 if(W_Laser_Shockwave_IsVisible(head, nearest_on_line, w_shotorg, attack_hitpos))
170 multiplier_from_accuracy = (1 - W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_hitpos));
171 multiplier_from_distance = (1 - (distance_of_attack ? min(1, (distance_to_target / autocvar_g_balance_laser_primary_radius)) : 0));
172 multiplier = max(autocvar_g_balance_laser_primary_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_primary_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_primary_multiplier_distance)));
174 final_force = ((normalize(center - nearest_on_line) * autocvar_g_balance_laser_primary_force) * multiplier);
175 final_damage = (autocvar_g_balance_laser_primary_damage * multiplier + autocvar_g_balance_laser_primary_edgedamage * (1 - multiplier));
176 Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force);
178 print("multiplier = ", ftos(multiplier), ", multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ", ");
179 print(strcat("edge hit damage = ", ftos(final_damage), ", force = ", vtos(final_force), ".\n"));
181 //pointparticles(particleeffectnum("rocket_guide"), w_shotorg, w_shotdir * 1000, 1);
182 //SendCSQCShockwaveParticle(autocvar_g_balance_laser_primary_spread, trace_endpos);
190 void W_Laser_Melee_Think(void)
193 float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
194 entity target_victim;
197 if(!self.cnt) // set start time of melee
200 W_PlayStrengthSound(self.realowner);
203 makevectors(self.realowner.v_angle); // update values for v_* vectors
205 // calculate swing percentage based on time
206 meleetime = autocvar_g_balance_laser_secondary_melee_time * W_WeaponRateFactor();
207 swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
208 f = ((1 - swing) * autocvar_g_balance_laser_secondary_melee_traces);
210 // check to see if we can still continue, otherwise give up now
211 if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_laser_secondary_melee_no_doubleslap)
217 // if okay, perform the traces needed for this frame
218 for(i=self.swing_prev; i < f; ++i)
220 swing_factor = ((1 - (i / autocvar_g_balance_laser_secondary_melee_traces)) * 2 - 1);
222 targpos = (self.realowner.origin + self.realowner.view_ofs
223 + (v_forward * autocvar_g_balance_laser_secondary_melee_range)
224 + (v_up * swing_factor * autocvar_g_balance_laser_secondary_melee_swing_up)
225 + (v_right * swing_factor * autocvar_g_balance_laser_secondary_melee_swing_side));
227 WarpZone_traceline_antilag(self.realowner, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, ANTILAG_LATENCY(self.realowner));
229 // draw lightning beams for debugging
230 //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5);
231 //te_customflash(targpos, 40, 2, '1 1 1');
233 is_player = (trace_ent.classname == "player" || trace_ent.classname == "body");
235 if((trace_fraction < 1) // if trace is good, apply the damage and remove self
236 && (trace_ent.takedamage == DAMAGE_AIM)
237 && (trace_ent != self.swing_alreadyhit)
238 && (is_player || autocvar_g_balance_laser_secondary_melee_nonplayerdamage))
240 target_victim = trace_ent; // so it persists through other calls
242 if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
243 swing_damage = (autocvar_g_balance_laser_secondary_damage * min(1, swing_factor + 1));
245 swing_damage = (autocvar_g_balance_laser_secondary_melee_nonplayerdamage * min(1, swing_factor + 1));
247 //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
249 Damage(target_victim, self.realowner, self.realowner,
250 swing_damage, WEP_LASER | HITTYPE_SECONDARY,
251 self.realowner.origin + self.realowner.view_ofs,
252 v_forward * autocvar_g_balance_laser_secondary_force);
254 if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LASER, 0, swing_damage); }
256 // draw large red flash for debugging
257 //te_customflash(targpos, 200, 2, '15 0 0');
259 if(autocvar_g_balance_laser_secondary_melee_multihit) // allow multiple hits with one swing, but not against the same player twice.
261 self.swing_alreadyhit = target_victim;
262 continue; // move along to next trace
272 if(time >= self.cnt + meleetime)
282 self.nextthink = time;
286 void W_Laser_Melee(void)
288 sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM);
289 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
293 meleetemp.owner = meleetemp.realowner = self;
294 meleetemp.think = W_Laser_Melee_Think;
295 meleetemp.nextthink = time + autocvar_g_balance_laser_secondary_melee_delay * W_WeaponRateFactor();
296 W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_melee_range);
299 void W_Laser_Attack (float issecondary)
306 if(issecondary == 2) // minstanex shot
307 nodamage = g_minstagib;
311 a = autocvar_g_balance_laser_primary_shotangle;
312 s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD);
315 W_SetupShot_Dir (self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0);
316 else if(issecondary == 1)
317 W_SetupShot_Dir (self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage);
319 W_SetupShot_Dir (self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage);
320 pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
323 missile.owner = missile.realowner = self;
324 missile.classname = "laserbolt";
328 missile.bot_dodge = TRUE;
329 missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage;
332 PROJECTILE_MAKETRIGGER(missile);
333 missile.projectiledeathtype = WEP_LASER;
335 setorigin (missile, w_shotorg);
336 setsize(missile, '0 0 0', '0 0 0');
338 W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary);
339 missile.angles = vectoangles (missile.velocity);
340 //missile.glow_color = 250; // 244, 250
341 //missile.glow_size = 120;
342 missile.touch = W_Laser_Touch;
344 missile.flags = FL_PROJECTILE;
345 missile.missile_flags = MIF_SPLASH;
347 missile.think = W_Laser_Think;
348 missile.nextthink = time + autocvar_g_balance_laser_primary_delay;
350 other = missile; MUTATOR_CALLHOOK(EditProjectile);
352 if(time >= missile.nextthink)
362 void spawnfunc_weapon_laser (void)
364 weapon_defaultspawnfunc(WEP_LASER);
367 float w_laser(float req)
373 if(autocvar_g_balance_laser_secondary)
375 r1 = autocvar_g_balance_laser_primary_damage;
376 r2 = autocvar_g_balance_laser_secondary_damage;
377 if (random() * (r2 + r1) > r1)
378 self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_laser_secondary_speed, 0, autocvar_g_balance_laser_secondary_lifetime, FALSE);
380 self.BUTTON_ATCK = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
383 self.BUTTON_ATCK = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
385 else if (req == WR_THINK)
387 if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload
388 weapon_action(self.weapon, WR_RELOAD);
389 else if (self.BUTTON_ATCK)
391 if (weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire))
393 W_DecreaseAmmo(ammo_none, 1, TRUE);
396 if not(autocvar_g_balance_laser_oldprimary)
399 W_Laser_Attack(FALSE);
401 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready);
404 else if (self.BUTTON_ATCK2)
406 switch(autocvar_g_balance_laser_secondary)
408 case 0: // switch to last used weapon
410 if(self.switchweapon == WEP_LASER) // don't do this if already switching
416 case 1: // normal projectile secondary
418 if(weapon_prepareattack(0, autocvar_g_balance_laser_secondary_refire))
420 W_DecreaseAmmo(ammo_none, 1, TRUE);
421 W_Laser_Attack(TRUE);
422 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
428 case 2: // melee attack secondary
430 if (self.clip_load >= 0) // we are not currently reloading
431 if (!self.crouch) // we are not currently crouching; this fixes an exploit where your melee anim is not visible, and besides wouldn't make much sense
432 if (weapon_prepareattack(1, autocvar_g_balance_laser_secondary_refire))
434 // attempt forcing playback of the anim by switching to another anim (that we never play) here...
435 weapon_thinkf(WFRAME_FIRE1, 0, W_Laser_Melee);
441 else if (req == WR_PRECACHE)
443 precache_model ("models/weapons/g_laser.md3");
444 precache_model ("models/weapons/v_laser.md3");
445 precache_model ("models/weapons/h_laser.iqm");
446 precache_sound ("weapons/lasergun_fire.wav");
447 //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
449 else if (req == WR_SETUP)
451 weapon_setup(WEP_LASER);
452 self.current_ammo = ammo_none;
454 else if (req == WR_CHECKAMMO1)
458 else if (req == WR_CHECKAMMO2)
462 else if (req == WR_RELOAD)
464 W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav");
470 float w_laser(float req)
472 if(req == WR_IMPACTEFFECT)
475 org2 = w_org + w_backoff * 6;
476 pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1);
478 sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM);
480 else if(req == WR_PRECACHE)
482 precache_sound("weapons/laserimpact.wav");
484 else if (req == WR_SUICIDEMESSAGE)
485 w_deathtypestring = _("%s lasered themself to hell");
486 else if (req == WR_KILLMESSAGE)
488 if(w_deathtype & HITTYPE_SECONDARY)
489 w_deathtypestring = _("%s was cut in half by %s's gauntlet"); // unchecked: SPLASH // TODO
491 w_deathtypestring = _("%s was lasered to death by %s"); // unchecked: SPLASH