]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_shockwave.qc
Add weaponstartoverride property to weapons
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / w_shockwave.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id */ SHOCKWAVE,
4 /* function */ W_Shockwave,
5 /* ammotype */ IT_SHELLS,
6 /* impulse  */ 2,
7 /* flags    */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN,
8 /* rating   */ BOT_PICKUP_RATING_LOW,
9 /* model    */ "shotgun",
10 /* netname  */ "shockwave",
11 /* fullname */ _("Shockwave")
12 );
13
14 #define SHOCKWAVE_SETTINGS(w_cvar,w_prop) \
15         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_damage) \
16         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_distance) \
17         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_edgedamage) \
18         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_force) \
19         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_force_forwardbias) \
20         /*w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_force_velocitybias)*/ \
21         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_force_zscale) \
22         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_damage) \
23         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_edgedamage) \
24         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_force) \
25         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_force_velocitybias) \
26         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_force_zscale) \
27         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_multiplier_accuracy) \
28         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_multiplier_distance) \
29         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_multiplier_min) \
30         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_jump_radius) \
31         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_multiplier_accuracy) \
32         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_multiplier_distance) \
33         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_multiplier_min) \
34         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_damage) \
35         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_edgedamage) \
36         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_force) \
37         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_force_forwardbias) \
38         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_multiplier_accuracy) \
39         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_multiplier_distance) \
40         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_multiplier_min) \
41         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_splash_radius) \
42         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_spread_max) \
43         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, blast_spread_min) \
44         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_animtime) \
45         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_damage) \
46         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_delay) \
47         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_force) \
48         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_multihit) \
49         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_no_doubleslap) \
50         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_nonplayerdamage) \
51         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_range) \
52         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_refire) \
53         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_swing_side) \
54         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_swing_up) \
55         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_time) \
56         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, melee_traces) \
57         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_ammo) \
58         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_animtime) \
59         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_bulletconstant) \
60         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_bullets) \
61         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_damage) \
62         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_force) \
63         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_refire) \
64         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_speed) \
65         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, pellets_spread) \
66         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, primary) \
67         w_cvar(WEP_SHOCKWAVE, shockwave, MO_NONE, secondary) \
68         w_prop(WEP_SHOCKWAVE, shockwave, float,  reloading_ammo, reload_ammo) \
69         w_prop(WEP_SHOCKWAVE, shockwave, float,  reloading_time, reload_time) \
70         w_prop(WEP_SHOCKWAVE, shockwave, float,  switchdelay_raise, switchdelay_raise) \
71         w_prop(WEP_SHOCKWAVE, shockwave, float,  switchdelay_drop, switchdelay_drop) \
72         w_prop(WEP_SHOCKWAVE, shockwave, string, weaponreplace, weaponreplace) \
73         w_prop(WEP_SHOCKWAVE, shockwave, float,  weaponstart, weaponstart) \
74         w_prop(WEP_SHOCKWAVE, shockwave, float,  weaponstartoverride, weaponstartoverride)
75
76 #ifdef SVQC
77 SHOCKWAVE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
78 #endif
79 #else
80 #ifdef SVQC
81 void spawnfunc_weapon_shockwave()
82 {
83         //if(autocvar_sv_q3acompat_machineshockwaveswap)
84         if(autocvar_sv_q3acompat_machineshotgunswap)
85         if(self.classname != "droppedweapon")
86         {
87                 weapon_defaultspawnfunc(WEP_UZI);
88                 return;
89         }
90         weapon_defaultspawnfunc(WEP_SHOCKWAVE);
91 }
92
93 .float swing_prev;
94 .entity swing_alreadyhit;
95 .float shockwave_pelletstime;
96 entity shockwave_hit[32];
97 float shockwave_hit_damage[32];
98 vector shockwave_hit_force[32];
99
100 // LEGACY ATTACK MODE: Scattered bullets
101 void W_Shockwave_Pellets(void)
102 {
103         float sc;
104         entity flash;
105
106         W_DecreaseAmmo(ammo_shells, WEP_CVAR(shockwave, pellets_ammo), WEP_CVAR(shockwave, reload_ammo));
107
108         W_SetupShot(self, TRUE, 5, "weapons/shockwave_fire.wav", CH_WEAPON_A, WEP_CVAR(shockwave, pellets_damage) * WEP_CVAR(shockwave, pellets_bullets));
109         
110         for(sc = 0; sc < WEP_CVAR(shockwave, pellets_bullets); ++sc)
111         {
112                 fireBallisticBullet(
113                         w_shotorg,
114                         w_shotdir,
115                         WEP_CVAR(shockwave, pellets_spread),
116                         WEP_CVAR(shockwave, pellets_speed),
117                         5,
118                         WEP_CVAR(shockwave, pellets_damage),
119                         WEP_CVAR(shockwave, pellets_force),
120                         WEP_SHOCKWAVE,
121                         0,
122                         WEP_CVAR(shockwave, pellets_bulletconstant)
123                 );
124         }
125         endFireBallisticBullet();
126
127         pointparticles(particleeffectnum("shockwave_muzzleflash"), w_shotorg, w_shotdir * 1000, WEP_CVAR(shockwave, pellets_ammo));
128
129         // casing code
130         if(autocvar_g_casings >= 1)
131         {
132                 for(sc = 0;sc < WEP_CVAR(shockwave, pellets_ammo); ++sc)
133                 {
134                         SpawnCasing(
135                                 (
136                                         ((random () * 50 + 50) * v_right)
137                                         -
138                                         (v_forward * (random () * 25 + 25))
139                                         -
140                                         ((random () * 5 - 30) * v_up)
141                                 ),
142                                 2,
143                                 vectoangles(v_forward),
144                                 '0 250 0',
145                                 100,
146                                 1,
147                                 self
148                         );
149                 }
150         }
151
152         // muzzle flash for 1st person view
153         flash = spawn();
154         setmodel(flash, "models/uziflash.md3"); // precision set below
155         flash.think = SUB_Remove;
156         flash.nextthink = time + 0.06;
157         flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
158         W_AttachToShotorg(flash, '5 0 0');
159 }
160
161 // MELEE ATTACK MODE
162 void W_Shockwave_Melee_Think()
163 {
164         // declarations
165         float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
166         entity target_victim;
167         vector targpos;
168
169         if(!self.cnt) // set start time of melee
170         {
171                 self.cnt = time; 
172                 W_PlayStrengthSound(self.realowner);
173         }
174
175         makevectors(self.realowner.v_angle); // update values for v_* vectors
176         
177         // calculate swing percentage based on time
178         meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor();
179         swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
180         f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces));
181         
182         // check to see if we can still continue, otherwise give up now
183         if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR(shockwave, melee_no_doubleslap))
184         {
185                 remove(self);
186                 return;
187         }
188         
189         // if okay, perform the traces needed for this frame 
190         for(i=self.swing_prev; i < f; ++i)
191         {
192                 swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1);
193                 
194                 targpos = (self.realowner.origin + self.realowner.view_ofs 
195                         + (v_forward * WEP_CVAR(shockwave, melee_range))
196                         + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up))
197                         + (v_right * swing_factor * WEP_CVAR(shockwave, melee_swing_side)));
198
199                 WarpZone_traceline_antilag(self.realowner, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self.realowner, ANTILAG_LATENCY(self.realowner));
200                 
201                 // draw lightning beams for debugging
202                 te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); 
203                 te_customflash(targpos, 40,  2, '1 1 1');
204                 
205                 is_player = (trace_ent.classname == "player" || trace_ent.classname == "body");
206
207                 if((trace_fraction < 1) // if trace is good, apply the damage and remove self
208                         && (trace_ent.takedamage == DAMAGE_AIM)  
209                         && (trace_ent != self.swing_alreadyhit)
210                         && (is_player || WEP_CVAR(shockwave, melee_nonplayerdamage)))
211                 {
212                         target_victim = trace_ent; // so it persists through other calls
213                         
214                         if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
215                                 swing_damage = (WEP_CVAR(shockwave, melee_damage * min(1, swing_factor + 1)));
216                         else
217                                 swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage * min(1, swing_factor + 1)));
218                         
219                         //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
220                         
221                         Damage(target_victim, self.realowner, self.realowner, 
222                                 swing_damage, WEP_SHOCKWAVE | HITTYPE_SECONDARY, 
223                                 self.realowner.origin + self.realowner.view_ofs, 
224                                 v_forward * WEP_CVAR(shockwave, melee_force));
225                                 
226                         if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOCKWAVE, 0, swing_damage); }
227                         
228                         if(WEP_CVAR(shockwave, melee_multihit)) // allow multiple hits with one swing, but not against the same player twice.
229                         {
230                                 self.swing_alreadyhit = target_victim;
231                                 continue; // move along to next trace
232                         }
233                         else
234                         {
235                                 remove(self);
236                                 return;
237                         }
238                 }
239         }
240         
241         if(time >= self.cnt + meleetime)
242         {
243                 // melee is finished
244                 remove(self);
245                 return;
246         }
247         else
248         {
249                 // set up next frame 
250                 self.swing_prev = i;
251                 self.nextthink = time;
252         }
253 }
254
255 void W_Shockwave_Melee()
256 {
257         sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM);
258         weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(shockwave, melee_animtime), w_ready);
259
260         entity meleetemp;
261         meleetemp = spawn();
262         meleetemp.owner = meleetemp.realowner = self;
263         meleetemp.think = W_Shockwave_Melee_Think;
264         meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor();
265         W_SetupShot_Range(self, TRUE, 0, "", 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range));
266 }
267
268 // SHOCKWAVE ATTACK MODE
269 float W_Shockwave_Attack_CheckSpread(
270         vector targetorg,
271         vector nearest_on_line,
272         vector sw_shotorg,
273         vector attack_endpos)
274 {
275         float spreadlimit;
276         float distance_of_attack = vlen(sw_shotorg - attack_endpos);
277         float distance_from_line = vlen(targetorg - nearest_on_line);
278         
279         spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1);
280         spreadlimit =
281                 (
282                         (WEP_CVAR(shockwave, blast_spread_min) * (1 - spreadlimit))
283                         +
284                         (WEP_CVAR(shockwave, blast_spread_max) * spreadlimit)
285                 );
286
287         if(
288                 (spreadlimit && (distance_from_line <= spreadlimit))
289                 &&
290                 ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90)
291         )
292                 { return bound(0, (distance_from_line / spreadlimit), 1); }
293         else
294                 { return FALSE; }
295 }
296
297 float W_Shockwave_Attack_IsVisible(
298         entity head,
299         vector nearest_on_line,
300         vector sw_shotorg,
301         vector attack_endpos)
302 {
303         vector nearest_to_attacker = head.WarpZone_findradius_nearest;
304         vector center = (head.origin + (head.mins + head.maxs) * 0.5);
305         vector corner;
306         float i;
307
308         // STEP ONE: Check if the nearest point is clear
309         if(W_Shockwave_Attack_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos))
310         {
311                 WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self);
312                 if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage
313         }
314
315         // STEP TWO: Check if shotorg to center point is clear
316         if(W_Shockwave_Attack_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos))
317         {
318                 WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self);
319                 if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage
320         }
321
322         // STEP THREE: Check each corner to see if they are clear
323         for(i=1; i<=8; ++i)
324         {
325                 corner = get_corner_position(head, i);
326                 if(W_Shockwave_Attack_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos))
327                 {
328                         WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self);
329                         if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage
330                 }
331         }
332
333         return FALSE;
334 }
335
336 float W_Shockwave_Attack_CheckHit(
337         float queue,
338         entity head,
339         vector final_force,
340         float final_damage)
341 {
342         if(!head) { return FALSE; }
343         float i;
344
345         ++queue;
346         
347         for(i = 1; i <= queue; ++i)
348         {
349                 if(shockwave_hit[i] == head)
350                 {
351                         if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; }
352                         if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; }
353                         return FALSE;
354                 }
355         }
356
357         shockwave_hit[queue] = head;
358         shockwave_hit_force[queue] = final_force;
359         shockwave_hit_damage[queue] = final_damage;
360         return TRUE;
361 }
362
363 void W_Shockwave_Attack()
364 {
365         // declarations
366         float multiplier, multiplier_from_accuracy, multiplier_from_distance;
367         float final_damage;
368         vector final_force, center, vel;
369         entity head;
370
371         float i, queue = 0;
372         
373         // set up the shot direction
374         W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage));
375         vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_distance)));
376         WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self);
377         vector attack_hitpos = trace_endpos;
378         float distance_to_end = vlen(w_shotorg - attack_endpos);
379         float distance_to_hit = vlen(w_shotorg - attack_hitpos);
380         //entity transform = WarpZone_trace_transform;
381
382         // do the firing effect now
383         //SendCSQCShockwaveParticle(attack_endpos); // WEAPONTODO
384         Damage_DamageInfo(
385                 attack_hitpos,
386                 WEP_CVAR(shockwave, blast_splash_damage),
387                 WEP_CVAR(shockwave, blast_splash_edgedamage),
388                 WEP_CVAR(shockwave, blast_splash_radius),
389                 w_shotdir * WEP_CVAR(shockwave, blast_splash_force),
390                 WEP_SHOCKWAVE,
391                 0,
392                 self
393         );
394
395         // splash damage/jumping trace
396         head = WarpZone_FindRadius(
397                 attack_hitpos,
398                 max(
399                         WEP_CVAR(shockwave, blast_splash_radius),
400                         WEP_CVAR(shockwave, blast_jump_radius)
401                 ),
402                 FALSE
403         );
404         
405         while(head)
406         {
407                 if(head.takedamage)
408                 {
409                         float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest);
410                         
411                         if((head == self) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius)))
412                         {
413                                 // ========================
414                                 //  BLAST JUMP CALCULATION
415                                 // ========================
416                                 
417                                 // calculate importance of distance and accuracy for this attack
418                                 multiplier_from_accuracy = (1 -
419                                         (distance_to_head ?
420                                                 min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius)))
421                                                 :
422                                                 0
423                                         )
424                                 );
425                                 multiplier_from_distance = (1 -
426                                         (distance_to_hit ?
427                                                 min(1, (distance_to_hit / distance_to_end))
428                                                 :
429                                                 0
430                                         )
431                                 );
432                                 multiplier =
433                                         max(
434                                                 WEP_CVAR(shockwave, blast_jump_multiplier_min),
435                                                 (
436                                                         (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy))
437                                                         +
438                                                         (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance))
439                                                 )
440                                         );
441
442                                 // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
443                                 final_damage =
444                                         (
445                                                 (WEP_CVAR(shockwave, blast_jump_damage) * multiplier)
446                                                 +
447                                                 (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier))
448                                         );
449
450                                 // figure out the direction of force
451                                 vel = normalize(combine_to_vector(head.velocity_x, head.velocity_y, 0));
452                                 vel *=
453                                         (
454                                                 bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1)
455                                                 *
456                                                 WEP_CVAR(shockwave, blast_jump_force_velocitybias)
457                                         );
458                                 final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel);
459
460                                 // now multiply the direction by force units
461                                 final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier);
462                                 final_force_z *= WEP_CVAR(shockwave, blast_jump_force_zscale);
463
464                                 // trigger damage with this calculated info
465                                 Damage(
466                                         head,
467                                         self,
468                                         self,
469                                         final_damage,
470                                         WEP_SHOCKWAVE,
471                                         head.origin,
472                                         final_force
473                                 );
474
475                                 #ifdef DEBUG_SHOCKWAVE
476                                 print(sprintf(
477                                         "SELF HIT: multiplier = %f, damage = %f, force = %f... "
478                                         "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
479                                         multiplier,
480                                         final_damage,
481                                         vlen(final_force),
482                                         multiplier_from_accuracy,
483                                         multiplier_from_distance
484                                 ));
485                                 #endif
486                         }
487                         else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius))
488                         {
489                                 // ==========================
490                                 //  BLAST SPLASH CALCULATION
491                                 // ==========================
492                                 
493                                 // calculate importance of distance and accuracy for this attack
494                                 multiplier_from_accuracy = (1 -
495                                         (distance_to_head ?
496                                                 min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius)))
497                                                 :
498                                                 0
499                                         )
500                                 );
501                                 multiplier_from_distance = (1 -
502                                         (distance_to_hit ?
503                                                 min(1, (distance_to_hit / distance_to_end))
504                                                 :
505                                                 0
506                                         )
507                                 );
508                                 multiplier =
509                                         max(
510                                                 WEP_CVAR(shockwave, blast_splash_multiplier_min),
511                                                 (
512                                                         (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy))
513                                                         +
514                                                         (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance))
515                                                 )
516                                         );
517
518                                 // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
519                                 final_damage =
520                                         (
521                                                 (WEP_CVAR(shockwave, blast_splash_damage) * multiplier)
522                                                 +
523                                                 (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier))
524                                         );
525
526                                 // figure out the direction of force
527                                 final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias));
528                                 final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force));
529                                 //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200)));
530
531                                 // now multiply the direction by force units
532                                 final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier);
533                                 final_force_z *= WEP_CVAR(shockwave, blast_force_zscale);
534
535                                 // queue damage with this calculated info
536                                 if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { ++queue; }
537
538                                 #ifdef DEBUG_SHOCKWAVE
539                                 print(sprintf(
540                                         "SPLASH HIT: multiplier = %f, damage = %f, force = %f... "
541                                         "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
542                                         multiplier,
543                                         final_damage,
544                                         vlen(final_force),
545                                         multiplier_from_accuracy,
546                                         multiplier_from_distance
547                                 ));
548                                 #endif
549                         }
550                 }
551                 head = head.chain;
552         }
553
554         // cone damage trace
555         head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), FALSE);
556         while(head)
557         {
558                 if((head != self) && head.takedamage)
559                 {
560                         // ========================
561                         //  BLAST CONE CALCULATION
562                         // ========================
563
564                         // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
565                         center = CENTER_OR_VIEWOFS(head);
566
567                         // find the closest point on the enemy to the center of the attack
568                         float ang; // angle between shotdir and h
569                         float h; // hypotenuse, which is the distance between attacker to head
570                         float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
571                         
572                         h = vlen(center - self.origin);
573                         ang = acos(dotproduct(normalize(center - self.origin), w_shotdir));
574                         a = h * cos(ang);
575                         // WEAPONTODO: replace with simpler method
576
577                         vector nearest_on_line = (w_shotorg + a * w_shotdir);
578                         vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
579                         float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this
580
581                         if((distance_to_target <= WEP_CVAR(shockwave, blast_distance)) 
582                                 && (W_Shockwave_Attack_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos)))
583                         {
584                                 // calculate importance of distance and accuracy for this attack
585                                 multiplier_from_accuracy = (1 -
586                                         W_Shockwave_Attack_CheckSpread(
587                                                 nearest_to_attacker,
588                                                 nearest_on_line,
589                                                 w_shotorg,
590                                                 attack_endpos
591                                         )
592                                 );
593                                 multiplier_from_distance = (1 -
594                                         (distance_to_hit ?
595                                                 min(1, (distance_to_target / distance_to_end))
596                                                 :
597                                                 0
598                                         )
599                                 );
600                                 multiplier =
601                                         max(
602                                                 WEP_CVAR(shockwave, blast_multiplier_min),
603                                                 (
604                                                         (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy))
605                                                         +
606                                                         (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance))
607                                                 )
608                                         );
609
610                                 // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
611                                 final_damage =
612                                         (
613                                                 (WEP_CVAR(shockwave, blast_damage) * multiplier)
614                                                 +
615                                                 (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier))
616                                         );
617
618                                 // figure out the direction of force
619                                 final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias));
620                                 final_force = normalize(center - (nearest_on_line - final_force));
621                                 //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200)));
622
623                                 // now multiply the direction by force units
624                                 final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier);
625                                 final_force_z *= WEP_CVAR(shockwave, blast_force_zscale);
626
627                                 // queue damage with this calculated info
628                                 if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { ++queue; }
629
630                                 #ifdef DEBUG_SHOCKWAVE
631                                 print(sprintf(
632                                         "BLAST HIT: multiplier = %f, damage = %f, force = %f... "
633                                         "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
634                                         multiplier,
635                                         final_damage,
636                                         vlen(final_force),
637                                         multiplier_from_accuracy,
638                                         multiplier_from_distance
639                                 ));
640                                 #endif
641                         }
642                 }
643                 head = head.chain;
644         }
645
646         for(i = 1; i <= queue; ++i)
647         {
648                 head = shockwave_hit[i];
649                 final_force = shockwave_hit_force[i];
650                 final_damage = shockwave_hit_damage[i];
651                 
652                 Damage(
653                         head,
654                         self,
655                         self,
656                         final_damage,
657                         WEP_SHOCKWAVE,
658                         head.origin,
659                         final_force
660                 );
661                 
662                 #ifdef DEBUG_SHOCKWAVE
663                 print(sprintf(
664                         "SHOCKWAVE by %s: damage = %f, force = %f.\n",
665                         self.netname,
666                         final_damage,
667                         vlen(final_force)
668                 ));
669                 #endif
670                 
671                 shockwave_hit[i] = world;
672                 shockwave_hit_force[i] = '0 0 0';
673                 shockwave_hit_damage[i] = 0;
674         }
675 }
676
677 float W_Shockwave(float req)
678 {
679         float ammo_amount;
680         switch(req)
681         {
682                 case WR_AIM:
683                 {
684                         if(vlen(self.origin - self.enemy.origin) <= WEP_CVAR(shockwave, melee_range))
685                                 { self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); }
686                         else
687                                 { self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); }
688                         
689                         return TRUE;
690                 }
691                 case WR_THINK:
692                 {
693                         if(WEP_CVAR(shockwave, reload_ammo) && self.clip_load < WEP_CVAR(shockwave, pellets_ammo)) // forced reload
694                         {
695                                 // don't force reload an empty shockwave if its melee attack is active
696                                 if(!(WEP_CVAR(shockwave, secondary) && self.ammo_shells < WEP_CVAR(shockwave, pellets_ammo)))
697                                         WEP_ACTION(self.weapon, WR_RELOAD);
698                         }
699                         else
700                         {
701                                 if(self.BUTTON_ATCK)
702                                 {
703                                         switch(WEP_CVAR(shockwave, primary))
704                                         {
705                                                 case 1:
706                                                 {
707                                                         if(time >= self.shockwave_pelletstime) // handle refire separately so the secondary can be fired straight after a primary
708                                                         {
709                                                                 if(weapon_prepareattack(0, WEP_CVAR(shockwave, pellets_animtime)))
710                                                                 {
711                                                                         W_Shockwave_Attack();
712                                                                         self.shockwave_pelletstime = time + WEP_CVAR(shockwave, pellets_refire) * W_WeaponRateFactor();
713                                                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, pellets_animtime), w_ready);
714                                                                 }
715                                                         }
716                                                         break;
717                                                 }
718                                                 case 2:
719                                                 {
720                                                         if(time >= self.shockwave_pelletstime) // handle refire separately so the secondary can be fired straight after a primary
721                                                         {
722                                                                 if(weapon_prepareattack(0, WEP_CVAR(shockwave, pellets_animtime)))
723                                                                 {
724                                                                         W_Shockwave_Pellets();
725                                                                         self.shockwave_pelletstime = time + WEP_CVAR(shockwave, pellets_refire) * W_WeaponRateFactor();
726                                                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, pellets_animtime), w_ready);
727                                                                 }
728                                                         }
729                                                         break;
730                                                 }
731                                         }
732                                 }
733                                 
734                                 if(self.clip_load >= 0) // we are not currently reloading
735                                 if(!self.crouch) // no crouchmelee please
736                                 if(self.BUTTON_ATCK2 && WEP_CVAR(shockwave, secondary))
737                                 if(weapon_prepareattack(1, WEP_CVAR(shockwave, melee_refire)))
738                                 {
739                                         // attempt forcing playback of the anim by switching to another anim (that we never play) here...
740                                         weapon_thinkf(WFRAME_FIRE1, 0, W_Shockwave_Melee);
741                                 }
742                         }
743                         
744                         return TRUE;
745                 }
746                 case WR_INIT:
747                 {
748                         precache_model("models/uziflash.md3");
749                         precache_model("models/weapons/g_shockwave.md3");
750                         precache_model("models/weapons/v_shockwave.md3");
751                         precache_model("models/weapons/h_shockwave.iqm");
752                         precache_sound("misc/itempickup.wav");
753                         precache_sound("weapons/shockwave_fire.wav");
754                         precache_sound("weapons/shockwave_melee.wav");
755                         SHOCKWAVE_SETTINGS(WEP_SKIPCVAR, WEP_SET_PROP)
756                         return TRUE;
757                 }
758                 case WR_SETUP:
759                 {
760                         self.current_ammo = ammo_shells;
761                         return TRUE;
762                 }
763                 case WR_CHECKAMMO1:
764                 {
765                         ammo_amount = self.ammo_shells >= WEP_CVAR(shockwave, pellets_ammo);
766                         ammo_amount += self.(weapon_load[WEP_SHOCKWAVE]) >= WEP_CVAR(shockwave, pellets_ammo);
767                         return ammo_amount;
768                 }
769                 case WR_CHECKAMMO2:
770                 {
771                         // melee attack is always available
772                         return TRUE;
773                 }
774                 case WR_CONFIG:
775                 {
776                         SHOCKWAVE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
777                         return TRUE;
778                 }
779                 case WR_RELOAD:
780                 {
781                         W_Reload(WEP_CVAR(shockwave, pellets_ammo), "weapons/reload.wav");
782                         return TRUE;
783                 }
784                 case WR_SUICIDEMESSAGE:
785                 {
786                         return WEAPON_THINKING_WITH_PORTALS;
787                 }
788                 case WR_KILLMESSAGE:
789                 {
790                         if(w_deathtype & HITTYPE_SECONDARY)
791                                 return WEAPON_SHOCKWAVE_MURDER_SLAP;
792                         else
793                                 return WEAPON_SHOCKWAVE_MURDER;
794                 }
795         }
796         return TRUE;
797 }
798 #endif
799 #ifdef CSQC
800 float W_Shockwave(float req)
801 {
802         switch(req)
803         {
804                 case WR_IMPACTEFFECT:
805                 {
806                         vector org2;
807                         org2 = w_org + w_backoff * 2;
808                         pointparticles(particleeffectnum("shockwave_impact"), org2, w_backoff * 1000, 1);
809                         return TRUE;
810                 }
811                 case WR_INIT:
812                 {
813                         //precache_sound("weapons/ric1.wav");
814                         //precache_sound("weapons/ric2.wav");
815                         //precache_sound("weapons/ric3.wav");
816                         return FALSE;
817                 }
818         }
819         return TRUE;
820 }
821 #endif
822 #endif