Xonotic
shockwave.qc
Go to the documentation of this file.
1 #include "shockwave.qh"
2 
3 REGISTER_NET_TEMP(TE_CSQC_SHOCKWAVEPARTICLE)
4 
5 #ifdef SVQC
6 
7 const float MAX_SHOCKWAVE_HITS = 10;
8 //#define DEBUG_SHOCKWAVE
9 
10 .float swing_prev;
11 .entity swing_alreadyhit;
12 .float shockwave_blasttime;
13 entity shockwave_hit[MAX_SHOCKWAVE_HITS];
14 float shockwave_hit_damage[MAX_SHOCKWAVE_HITS];
15 vector shockwave_hit_force[MAX_SHOCKWAVE_HITS];
16 
17 // MELEE ATTACK MODE
18 void W_Shockwave_Melee_Think(entity this)
19 {
20  // declarations
21  float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
22  entity target_victim;
23  vector targpos;
24 
25  // check to see if we can still continue, otherwise give up now
26  if(IS_DEAD(this.realowner) && WEP_CVAR(shockwave, melee_no_doubleslap))
27  {
28  delete(this);
29  return;
30  }
31 
32  // set start time of melee
33  if(!this.cnt)
34  {
35  this.cnt = time;
37  }
38 
39  // update values for v_* vectors
40  makevectors(this.realowner.v_angle);
41 
42  // calculate swing percentage based on time
43  meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor(this.realowner);
44  swing = bound(0, (this.cnt + meleetime - time) / meleetime, 10);
45  f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces));
46 
47  // perform the traces needed for this frame
48  for(i=this.swing_prev; i < f; ++i)
49  {
50  swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1);
51 
52  targpos = (this.realowner.origin + this.realowner.view_ofs
53  + (v_forward * WEP_CVAR(shockwave, melee_range))
54  + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up))
55  + (v_right * swing_factor * WEP_CVAR(shockwave, melee_swing_side)));
56 
58  this.realowner,
59  (this.realowner.origin + this.realowner.view_ofs),
60  targpos,
61  false,
62  this.realowner,
64  );
65 
66  // draw lightning beams for debugging
67 #ifdef DEBUG_SHOCKWAVE
68  te_lightning2(NULL, targpos, this.realowner.origin + this.realowner.view_ofs + v_forward * 5 - v_up * 5);
69  te_customflash(targpos, 40, 2, '1 1 1');
70 #endif
71 
72  is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || IS_MONSTER(trace_ent));
73 
74  if((trace_fraction < 1) // if trace is good, apply the damage and remove this if necessary
75  && (trace_ent.takedamage == DAMAGE_AIM)
76  && (trace_ent != this.swing_alreadyhit)
77  && (is_player || WEP_CVAR(shockwave, melee_nonplayerdamage)))
78  {
79  target_victim = trace_ent; // so it persists through other calls
80 
81  if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught
82  swing_damage = (WEP_CVAR(shockwave, melee_damage) * min(1, swing_factor + 1));
83  else
84  swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage) * min(1, swing_factor + 1));
85 
86  // trigger damage with this calculated info
87  Damage(
88  target_victim,
89  this.realowner,
90  this.realowner,
91  swing_damage,
92  (WEP_SHOCKWAVE.m_id | HITTYPE_SECONDARY),
93  this.weaponentity_fld,
94  (this.realowner.origin + this.realowner.view_ofs),
95  (v_forward * WEP_CVAR(shockwave, melee_force))
96  );
97 
98  // handle accuracy
99  if(accuracy_isgooddamage(this.realowner, target_victim))
100  { accuracy_add(this.realowner, WEP_SHOCKWAVE, 0, swing_damage); }
101 
102  #ifdef DEBUG_SHOCKWAVE
103  LOG_INFOF(
104  "MELEE: %s hitting %s with %f damage (factor: %f) at %f time.",
105  this.realowner.netname,
106  target_victim.netname,
107  swing_damage,
108  swing_factor,
109  time
110  );
111  #endif
112 
113  // allow multiple hits with one swing, but not against the same player twice
114  if(WEP_CVAR(shockwave, melee_multihit))
115  {
116  this.swing_alreadyhit = target_victim;
117  continue; // move along to next trace
118  }
119  else
120  {
121  delete(this);
122  return;
123  }
124  }
125  }
126 
127  if(time >= this.cnt + meleetime)
128  {
129  // melee is finished
130  delete(this);
131  return;
132  }
133  else
134  {
135  // set up next frame
136  this.swing_prev = i;
137  this.nextthink = time;
138  }
139 }
140 
141 void W_Shockwave_Melee(Weapon thiswep, entity actor, .entity weaponentity, int fire)
142 {
143  sound(actor, CH_WEAPON_A, SND_SHOTGUN_MELEE, VOL_BASE, ATTN_NORM);
144  weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR(shockwave, melee_animtime), w_ready);
145 
146  entity meleetemp = new_pure(meleetemp);
147  meleetemp.owner = meleetemp.realowner = actor;
148  setthink(meleetemp, W_Shockwave_Melee_Think);
149  meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor(actor);
150  meleetemp.weaponentity_fld = weaponentity;
151  W_SetupShot_Range(actor, weaponentity, true, 0, SND_Null, 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range), thiswep.m_id | HITTYPE_SECONDARY);
152 }
153 
154 // SHOCKWAVE ATTACK MODE
155 float W_Shockwave_Attack_CheckSpread(
156  vector targetorg,
157  vector nearest_on_line,
158  vector sw_shotorg,
159  vector attack_endpos)
160 {
161  float spreadlimit;
162  float distance_of_attack = vlen(sw_shotorg - attack_endpos);
163  float distance_from_line = vlen(targetorg - nearest_on_line);
164 
165  spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1);
166  spreadlimit =
167  (
168  (WEP_CVAR(shockwave, blast_spread_min) * (1 - spreadlimit))
169  +
170  (WEP_CVAR(shockwave, blast_spread_max) * spreadlimit)
171  );
172 
173  if(
174  (spreadlimit && (distance_from_line <= spreadlimit))
175  &&
176  ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90)
177  )
178  { return bound(0, (distance_from_line / spreadlimit), 1); }
179  else
180  { return false; }
181 }
182 
183 float W_Shockwave_Attack_IsVisible(
184  entity actor,
185  entity head,
186  vector nearest_on_line,
187  vector sw_shotorg,
188  vector attack_endpos)
189 {
190  vector nearest_to_attacker = head.WarpZone_findradius_nearest;
191  vector center = (head.origin + (head.mins + head.maxs) * 0.5);
192  vector corner;
193  float i;
194 
195  // STEP ONE: Check if the nearest point is clear
196  if(W_Shockwave_Attack_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos))
197  {
198  WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, actor);
199  if(trace_fraction == 1) { return true; } // yes, the nearest point is clear and we can allow the damage
200  }
201 
202  // STEP TWO: Check if shotorg to center point is clear
203  if(W_Shockwave_Attack_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos))
204  {
205  WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, actor);
206  if(trace_fraction == 1) { return true; } // yes, the center point is clear and we can allow the damage
207  }
208 
209  // STEP THREE: Check each corner to see if they are clear
210  for(i=1; i<=8; ++i)
211  {
212  corner = get_corner_position(head, i);
213  if(W_Shockwave_Attack_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos))
214  {
215  WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, actor);
216  if(trace_fraction == 1) { return true; } // yes, this corner is clear and we can allow the damage
217  }
218  }
219 
220  return false;
221 }
222 
223 float W_Shockwave_Attack_CheckHit(
224  float queue,
225  entity head,
226  vector final_force,
227  float final_damage)
228 {
229  if(!head) { return false; }
230  float i;
231 
232  for(i = 0; i <= queue; ++i)
233  {
234  if(shockwave_hit[i] == head)
235  {
236  if(vlen2(final_force) > vlen2(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; }
237  if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; }
238  return false;
239  }
240  }
241 
242  shockwave_hit[queue] = head;
243  shockwave_hit_force[queue] = final_force;
244  shockwave_hit_damage[queue] = final_damage;
245  return true;
246 }
247 
248 void W_Shockwave_Send(entity actor)
249 {
250  WriteHeader(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE);
251  WriteVector(MSG_BROADCAST, w_shotorg);
252  WriteVector(MSG_BROADCAST, w_shotdir);
253  WriteShort(MSG_BROADCAST, WEP_CVAR(shockwave, blast_distance));
254  WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_max), 255));
255  WriteByte(MSG_BROADCAST, bound(0, WEP_CVAR(shockwave, blast_spread_min), 255));
256  WriteByte(MSG_BROADCAST, etof(actor));
257 }
258 
259 void W_Shockwave_Attack(Weapon thiswep, entity actor, .entity weaponentity)
260 {
261  // declarations
262  float multiplier, multiplier_from_accuracy, multiplier_from_distance;
263  float final_damage;
264  vector final_force, center, vel;
265  entity head;
266 
267  float i, queue = 0;
268 
269  // set up the shot direction
270  W_SetupShot(actor, weaponentity, true, 3, SND_LASERGUN_FIRE, CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage), thiswep.m_id);
271  vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_distance)));
272  WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, actor);
273  vector attack_hitpos = trace_endpos;
274  float distance_to_end = vlen(w_shotorg - attack_endpos);
275  float distance_to_hit = vlen(w_shotorg - attack_hitpos);
276  //entity transform = WarpZone_trace_transform;
277 
278  // do the firing effect now
279  W_Shockwave_Send(actor);
281  attack_hitpos,
282  WEP_CVAR(shockwave, blast_splash_damage),
283  WEP_CVAR(shockwave, blast_splash_edgedamage),
284  WEP_CVAR(shockwave, blast_splash_radius),
285  w_shotdir * WEP_CVAR(shockwave, blast_splash_force),
286  thiswep.m_id,
287  0,
288  actor
289  );
290 
291  // splash damage/jumping trace
292  head = WarpZone_FindRadius(
293  attack_hitpos,
294  max(
295  WEP_CVAR(shockwave, blast_splash_radius),
296  WEP_CVAR(shockwave, blast_jump_radius)
297  ),
298  false
299  );
300 
301  float lag = ((IS_REAL_CLIENT(actor)) ? ANTILAG_LATENCY(actor) : 0);
302  bool noantilag = ((IS_CLIENT(actor)) ? CS_CVAR(actor).cvar_cl_noantilag : false);
303  if(lag < 0.001)
304  lag = 0;
305  if(autocvar_g_antilag == 0 || noantilag)
306  lag = 0; // only do hitscan, but no antilag
307  if(lag)
308  antilag_takeback_all(actor, lag);
309 
310  while(head)
311  {
312  if(head.takedamage)
313  {
314  float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest);
315 
316  if((head == actor) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius)))
317  {
318  // ========================
319  // BLAST JUMP CALCULATION
320  // ========================
321 
322  // calculate importance of distance and accuracy for this attack
323  multiplier_from_accuracy = (1 -
324  (distance_to_head ?
325  min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius)))
326  :
327  0
328  )
329  );
330  multiplier_from_distance = (1 -
331  (distance_to_hit ?
332  min(1, (distance_to_hit / distance_to_end))
333  :
334  0
335  )
336  );
337  multiplier =
338  max(
339  WEP_CVAR(shockwave, blast_jump_multiplier_min),
340  (
341  (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy))
342  +
343  (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance))
344  )
345  );
346 
347  // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
348  final_damage =
349  (
350  (WEP_CVAR(shockwave, blast_jump_damage) * multiplier)
351  +
352  (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier))
353  );
354 
355  // figure out the direction of force
356  vel = normalize(vec2(head.velocity));
357  vel *=
358  (
359  bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1)
360  *
361  WEP_CVAR(shockwave, blast_jump_force_velocitybias)
362  );
363  final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel);
364 
365  // now multiply the direction by force units
366  final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier);
367  final_force.z *= WEP_CVAR(shockwave, blast_jump_force_zscale);
368 
369  // trigger damage with this calculated info
370  Damage(
371  head,
372  actor,
373  actor,
374  final_damage,
375  thiswep.m_id,
376  weaponentity,
377  head.origin,
378  final_force
379  );
380 
381  #ifdef DEBUG_SHOCKWAVE
382  LOG_INFOF(
383  "SELF HIT: multiplier = %f, damage = %f, force = %f... "
384  "multiplier_from_accuracy = %f, multiplier_from_distance = %f.",
385  multiplier,
386  final_damage,
387  vlen(final_force),
388  multiplier_from_accuracy,
389  multiplier_from_distance
390  );
391  #endif
392  }
393  else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius))
394  {
395  // ==========================
396  // BLAST SPLASH CALCULATION
397  // ==========================
398 
399  // calculate importance of distance and accuracy for this attack
400  multiplier_from_accuracy = (1 -
401  (distance_to_head ?
402  min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius)))
403  :
404  0
405  )
406  );
407  multiplier_from_distance = (1 -
408  (distance_to_hit ?
409  min(1, (distance_to_hit / distance_to_end))
410  :
411  0
412  )
413  );
414  multiplier =
415  max(
416  WEP_CVAR(shockwave, blast_splash_multiplier_min),
417  (
418  (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy))
419  +
420  (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance))
421  )
422  );
423 
424  // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
425  final_damage =
426  (
427  (WEP_CVAR(shockwave, blast_splash_damage) * multiplier)
428  +
429  (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier))
430  );
431 
432  // figure out the direction of force
433  final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias));
434  final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force));
435  //te_lightning2(NULL, attack_hitpos, (attack_hitpos + (final_force * 200)));
436 
437  // now multiply the direction by force units
438  final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier);
439  final_force.z *= WEP_CVAR(shockwave, blast_force_zscale);
440 
441  // queue damage with this calculated info
442  if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
443 
444  #ifdef DEBUG_SHOCKWAVE
445  LOG_INFOF(
446  "SPLASH HIT: multiplier = %f, damage = %f, force = %f... "
447  "multiplier_from_accuracy = %f, multiplier_from_distance = %f.",
448  multiplier,
449  final_damage,
450  vlen(final_force),
451  multiplier_from_accuracy,
452  multiplier_from_distance
453  );
454  #endif
455  }
456  }
457  head = head.chain;
458  }
459 
460  // cone damage trace
461  head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), false);
462  while(head)
463  {
464  if((head != actor) && head.takedamage)
465  {
466  // ========================
467  // BLAST CONE CALCULATION
468  // ========================
469 
470  // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
471  center = CENTER_OR_VIEWOFS(head);
472 
473  // find the closest point on the enemy to the center of the attack
474  float h; // hypotenuse, which is the distance between attacker to head
475  float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
476 
477  h = vlen(center - actor.origin);
478  a = h * (normalize(center - actor.origin) * w_shotdir);
479  // WEAPONTODO: replace with simpler method
480 
481  vector nearest_on_line = (w_shotorg + a * w_shotdir);
482  vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
483 
484  if((vdist(head.WarpZone_findradius_dist, <=, WEP_CVAR(shockwave, blast_distance)))
485  && (W_Shockwave_Attack_IsVisible(actor, head, nearest_on_line, w_shotorg, attack_endpos)))
486  {
487  // calculate importance of distance and accuracy for this attack
488  multiplier_from_accuracy = (1 -
489  W_Shockwave_Attack_CheckSpread(
490  nearest_to_attacker,
491  nearest_on_line,
492  w_shotorg,
493  attack_endpos
494  )
495  );
496  multiplier_from_distance = (1 -
497  (distance_to_hit ?
498  min(1, (vlen(head.WarpZone_findradius_dist) / distance_to_end))
499  :
500  0
501  )
502  );
503  multiplier =
504  max(
505  WEP_CVAR(shockwave, blast_multiplier_min),
506  (
507  (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy))
508  +
509  (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance))
510  )
511  );
512 
513  // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
514  final_damage =
515  (
516  (WEP_CVAR(shockwave, blast_damage) * multiplier)
517  +
518  (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier))
519  );
520 
521  // figure out the direction of force
522  final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias));
523  final_force = normalize(center - (nearest_on_line - final_force));
524  //te_lightning2(NULL, nearest_on_line, (attack_hitpos + (final_force * 200)));
525 
526  // now multiply the direction by force units
527  final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier);
528  final_force.z *= WEP_CVAR(shockwave, blast_force_zscale);
529 
530  // queue damage with this calculated info
531  if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
532 
533  #ifdef DEBUG_SHOCKWAVE
534  LOG_INFOF(
535  "BLAST HIT: multiplier = %f, damage = %f, force = %f... "
536  "multiplier_from_accuracy = %f, multiplier_from_distance = %f.",
537  multiplier,
538  final_damage,
539  vlen(final_force),
540  multiplier_from_accuracy,
541  multiplier_from_distance
542  );
543  #endif
544  }
545  }
546  head = head.chain;
547  }
548 
549  for(i = 1; i <= queue; ++i)
550  {
551  head = shockwave_hit[i-1];
552  final_force = shockwave_hit_force[i-1];
553  final_damage = shockwave_hit_damage[i-1];
554 
555  Damage(
556  head,
557  actor,
558  actor,
559  final_damage,
560  thiswep.m_id,
561  weaponentity,
562  head.origin,
563  final_force
564  );
565 
566  if(accuracy_isgooddamage(actor, head))
567  accuracy_add(actor, thiswep, 0, final_damage);
568 
569  #ifdef DEBUG_SHOCKWAVE
570  LOG_INFOF(
571  "SHOCKWAVE by %s: damage = %f, force = %f.",
572  actor.netname,
573  final_damage,
574  vlen(final_force)
575  );
576  #endif
577 
578  shockwave_hit[i-1] = NULL;
579  shockwave_hit_force[i-1] = '0 0 0';
580  shockwave_hit_damage[i-1] = 0;
581  }
582 
583  if(lag)
584  antilag_restore_all(actor);
585 }
586 
587 METHOD(Shockwave, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
588 {
589  if(vdist(actor.origin - actor.enemy.origin, <=, WEP_CVAR(shockwave, melee_range)))
590  { PHYS_INPUT_BUTTON_ATCK2(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); }
591  else
592  { PHYS_INPUT_BUTTON_ATCK(actor) = bot_aim(actor, weaponentity, 1000000, 0, 0.001, false); }
593 }
594 METHOD(Shockwave, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
595 {
596  if(fire & 1)
597  {
598  if(time >= actor.(weaponentity).shockwave_blasttime) // handle refire separately so the secondary can be fired straight after a primary
599  {
600  if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(shockwave, blast_animtime)))
601  {
602  W_Shockwave_Attack(thiswep, actor, weaponentity);
603  actor.(weaponentity).shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor(actor);
604  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready);
605  }
606  }
607  }
608  else if(fire & 2)
609  {
610  //if(actor.clip_load >= 0) // we are not currently reloading
611  if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR(shockwave, melee_refire)))
612  {
613  // attempt forcing playback of the anim by switching to another anim (that we never play) here...
614  weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, 0, W_Shockwave_Melee);
615  }
616  }
617 }
618 METHOD(Shockwave, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity))
619 {
620  return true; // infinite ammo
621 }
622 METHOD(Shockwave, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponentity))
623 {
624  // shockwave has infinite ammo
625  return true;
626 }
627 METHOD(Shockwave, wr_suicidemessage, Notification(entity thiswep))
628 {
629  return WEAPON_THINKING_WITH_PORTALS;
630 }
631 METHOD(Shockwave, wr_killmessage, Notification(entity thiswep))
632 {
634  return WEAPON_SHOCKWAVE_MURDER_SLAP;
635  else
636  return WEAPON_SHOCKWAVE_MURDER;
637 }
638 
639 #endif
640 #ifdef CSQC
641 // WEAPONTODO: add client side settings for these
642 const float SW_MAXALPHA = 0.5;
643 const float SW_FADETIME = 0.4;
644 const float SW_DISTTOMIN = 200;
645 void Draw_Shockwave(entity this)
646 {
647  // fading/removal control
648  float a = bound(0, (SW_MAXALPHA - ((time - this.sw_time) / SW_FADETIME)), SW_MAXALPHA);
649  if(a < ALPHA_MIN_VISIBLE) { delete(this); }
650 
651  // WEAPONTODO: save this only once when creating the entity
652  vector sw_color = entcs_GetColor(this.sv_entnum - 1); // Team_ColorRGB(entcs_GetTeam(this.sv_entnum));
653 
654  // WEAPONTODO: trace to find what we actually hit
655  vector endpos = (this.sw_shotorg + (this.sw_shotdir * this.sw_distance));
656 
657  vector _forward, right, up;
658  VECTOR_VECTORS(this.sw_shotdir, _forward, right, up);
659 
660  // WEAPONTODO: combine and simplify these calculations
661  vector min_end = ((this.sw_shotorg + (this.sw_shotdir * SW_DISTTOMIN)) + (up * this.sw_spread_min));
662  vector max_end = (endpos + (up * this.sw_spread_max));
663  float spread_to_min = vlen(normalize(min_end - this.sw_shotorg) - this.sw_shotdir);
664  float spread_to_max = vlen(normalize(max_end - min_end) - this.sw_shotdir);
665 
666  vector first_min_end = '0 0 0', prev_min_end = '0 0 0', new_min_end = '0 0 0';
667  vector first_max_end = '0 0 0', prev_max_end = '0 0 0', new_max_end = '0 0 0';
668  float new_max_dist, new_min_dist;
669 
670  vector deviation, angle = '0 0 0';
671  float counter, divisions = 20;
672  for(counter = 0; counter < divisions; ++counter)
673  {
674  // perfect circle effect lines
675  makevectors('0 360 0' * (0.75 + (counter - 0.5) / divisions));
676  angle.y = v_forward.x;
677  angle.z = v_forward.y;
678 
679  // first do the spread_to_min effect
680  deviation = angle * spread_to_min;
681  deviation = ((this.sw_shotdir + (right * deviation.y) + (up * deviation.z)));
682  new_min_dist = SW_DISTTOMIN;
683  new_min_end = (this.sw_shotorg + (deviation * new_min_dist));
684  //te_lightning2(NULL, new_min_end, this.sw_shotorg);
685 
686  // then calculate spread_to_max effect
687  deviation = angle * spread_to_max;
688  deviation = ((this.sw_shotdir + (right * deviation.y) + (up * deviation.z)));
689  new_max_dist = vlen(new_min_end - endpos);
690  new_max_end = (new_min_end + (deviation * new_max_dist));
691  //te_lightning2(NULL, new_end, prev_min_end);
692 
693 
694  if(counter == 0)
695  {
696  first_min_end = new_min_end;
697  first_max_end = new_max_end;
698  }
699 
700  if(counter >= 1)
701  {
702  // draw from shot origin to min spread radius
703  R_BeginPolygon("", DRAWFLAG_NORMAL, false);
704  R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
705  R_PolygonVertex(new_min_end, '0 0 0', sw_color, a);
706  R_PolygonVertex(this.sw_shotorg, '0 0 0', sw_color, a);
707  R_EndPolygon();
708 
709  // draw from min spread radius to max spread radius
710  R_BeginPolygon("", DRAWFLAG_NORMAL, false);
711  R_PolygonVertex(new_min_end, '0 0 0', sw_color, a);
712  R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
713  R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a);
714  R_PolygonVertex(new_max_end, '0 0 0', sw_color, a);
715  R_EndPolygon();
716  }
717 
718  prev_min_end = new_min_end;
719  prev_max_end = new_max_end;
720 
721  // last division only
722  if((counter + 1) == divisions)
723  {
724  // draw from shot origin to min spread radius
725  R_BeginPolygon("", DRAWFLAG_NORMAL, false);
726  R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
727  R_PolygonVertex(first_min_end, '0 0 0', sw_color, a);
728  R_PolygonVertex(this.sw_shotorg, '0 0 0', sw_color, a);
729  R_EndPolygon();
730 
731  // draw from min spread radius to max spread radius
732  R_BeginPolygon("", DRAWFLAG_NORMAL, false);
733  R_PolygonVertex(first_min_end, '0 0 0', sw_color, a);
734  R_PolygonVertex(prev_min_end, '0 0 0', sw_color, a);
735  R_PolygonVertex(prev_max_end, '0 0 0', sw_color, a);
736  R_PolygonVertex(first_max_end, '0 0 0', sw_color, a);
737  R_EndPolygon();
738  }
739  }
740 }
741 
742 NET_HANDLE(TE_CSQC_SHOCKWAVEPARTICLE, bool isNew)
743 {
744  Net_ReadShockwaveParticle();
745  return true;
746 }
747 
748 void Net_ReadShockwaveParticle()
749 {
750  entity shockwave = new(shockwave_cone);
751  shockwave.draw = Draw_Shockwave;
752  IL_PUSH(g_drawables, shockwave);
753 
754  shockwave.sw_shotorg = ReadVector();
755  shockwave.sw_shotdir = ReadVector();
756 
757  shockwave.sw_distance = ReadShort();
758  shockwave.sw_spread_max = ReadByte();
759  shockwave.sw_spread_min = ReadByte();
760 
761  shockwave.sv_entnum = ReadByte();
762 
763  shockwave.sw_time = time;
764 }
765 
766 METHOD(Shockwave, wr_impacteffect, void(entity thiswep, entity actor))
767 {
768  // handled by Net_ReadShockwaveParticle
769  //vector org2;
770  //org2 = w_org + w_backoff * 2;
771  //pointparticles(EFFECT_BLASTER_IMPACT, org2, w_backoff * 1000, 1);
772 }
773 
774 #endif
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition: player.qh:148
bool bot_aim(entity this,.entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity)
const float ALPHA_MIN_VISIBLE
Definition: main.qh:128
vector w_shotorg
Definition: tracing.qh:18
vector WarpZoneLib_NearestPointOnBox(vector mi, vector ma, vector org)
Definition: common.qc:565
#define IS_CLIENT(v)
Definition: utils.qh:13
#define W_SetupShot_Range(ent, wepent, antilag, recoil, snd, chan, maxdamage, range, deathtype)
Definition: tracing.qh:35
entity() spawn
float DAMAGE_AIM
Definition: progsdefs.qc:284
int sv_entnum
Definition: main.qh:155
void w_ready(Weapon thiswep, entity actor,.entity weaponentity, int fire)
#define CS_CVAR(this)
Definition: state.qh:51
#define NET_HANDLE(id, param)
Definition: net.qh:12
#define WEP_CVAR(wepname, name)
Definition: all.qh:299
#define IS_MONSTER(v)
Definition: utils.qh:21
vector w_shotdir
Definition: tracing.qh:19
#define METHOD(cname, name, prototype)
Definition: oo.qh:257
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
entity trace_ent
Definition: csprogsdefs.qc:40
void WarpZone_traceline_antilag(entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
Definition: antilag.qc:221
void antilag_restore_all(entity ignore)
Definition: antilag.qc:138
const float ATTN_NORM
Definition: csprogsdefs.qc:226
const float MOVE_NOMONSTERS
Definition: csprogsdefs.qc:253
#define vlen2(v)
Definition: vector.qh:4
float cnt
Definition: powerups.qc:24
void W_PlayStrengthSound(entity player)
Definition: common.qc:44
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
const int CH_WEAPON_A
Definition: sound.qh:7
#define LOG_INFOF(...)
Definition: log.qh:71
vector v_up
Definition: csprogsdefs.qc:31
bool weapon_prepareattack(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
#define NULL
Definition: post.qh:17
const float DRAWFLAG_NORMAL
Definition: csprogsdefs.qc:317
const float VOL_BASE
Definition: sound.qh:36
vector trace_endpos
Definition: csprogsdefs.qc:37
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition: player.qh:146
int autocvar_g_antilag
Definition: antilag.qh:3
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition: damage.qc:583
const int HITTYPE_SECONDARY
Definition: all.qh:25
#define W_SetupShot(ent, wepent, antilag, recoil, snd, chan, maxdamage, deathtype)
Definition: tracing.qh:33
#define IS_DEAD(s)
Definition: utils.qh:26
entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
Definition: common.qc:669
float nextthink
Definition: csprogsdefs.qc:121
float w_deathtype
Definition: damage.qh:97
#define CENTER_OR_VIEWOFS(ent)
Definition: utils.qh:28
vector(float skel, float bonenum) _skel_get_boneabs_hidden
float RAD2DEG
Definition: csprogsdefs.qc:962
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
void accuracy_add(entity this, Weapon w, float fired, float hit)
Definition: accuracy.qc:83
void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
Definition: common.qc:338
entity Notification
always last
Definition: all.qh:82
void weapon_thinkf(entity actor,.entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,.entity weaponentity, int fire) func)
bool accuracy_isgooddamage(entity attacker, entity targ)
Definition: accuracy.qc:112
vector v_right
Definition: csprogsdefs.qc:31
entity realowner
Definition: common.qh:25
#define vec2(...)
Definition: vector.qh:90
void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, int deathtype, float bloodtype, entity dmgowner)
#define new_pure(class)
purely logical entities (.origin doesn&#39;t work)
Definition: oo.qh:62
#define ANTILAG_LATENCY(e)
Definition: antilag.qh:19
#define setthink(e, f)
#define REGISTER_NET_TEMP(id)
Definition: net.qh:33
#define sound(e, c, s, v, a)
Definition: sound.qh:52
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition: weapon.qh:41
float time
Definition: csprogsdefs.qc:16
const int CH_WEAPON_B
Definition: sound.qh:8
int m_id
Definition: weapon.qh:42
void antilag_takeback_all(entity ignore, float lag)
Definition: antilag.qc:125
#define makevectors
Definition: post.qh:21
float trace_fraction
Definition: csprogsdefs.qc:36
float W_WeaponRateFactor(entity this)
Definition: weaponsystem.qc:33
IntrusiveList g_drawables
Definition: main.qh:77
#define IS_PLAYER(v)
Definition: utils.qh:9
vector v_forward
Definition: csprogsdefs.qc:31