Xonotic
weaponsystem.qc
Go to the documentation of this file.
1 #include "weaponsystem.qh"
2 
3 #include <common/animdecide.qh>
4 #include <common/constants.qh>
5 #include <common/items/_mod.qh>
9 #include <common/net_linked.qh>
12 #include <common/state.qh>
13 #include <common/util.qh>
14 #include <common/vehicles/all.qh>
15 #include <common/weapons/_all.qh>
16 #include <common/wepent.qh>
18 #include <server/cheats.qh>
19 #include <server/client.qh>
20 #include <server/command/common.qh>
21 #include <server/damage.qh>
22 #include <server/items/items.qh>
23 #include <server/hook.qh>
24 #include <server/mutators/_mod.qh>
25 #include <server/round_handler.qh>
27 #include <server/world.qh>
28 
29 .int state;
30 
32 
34 {
35  float t = 1;
38 
39  MUTATOR_CALLHOOK(WeaponRateFactor, t, this);
40  t = M_ARGV(0, float);
41 
42  return t;
43 }
44 
46 {
47  float t = 1.0 * autocvar_g_weaponspeedfactor;
48 
49  MUTATOR_CALLHOOK(WeaponSpeedFactor, t, this);
50  t = M_ARGV(0, float);
51 
52  return t;
53 }
54 
55 
57 {
58  this.viewmodelforclient = this.owner;
59  if (IS_SPEC(client) && client.enemy == this.owner) this.viewmodelforclient = client;
60  return false;
61 }
62 
64 {
65  entity wi = REGISTRY_GET(Weapons, wpn);
66  entity e = spawn();
67  CL_WeaponEntity_SetModel(e, wi.mdl, false);
68  vector ret = e.movedir;
69  CL_WeaponEntity_SetModel(e, "", false);
70  delete(e);
71  return ret;
72 }
73 
74 .float m_alpha;
75 .string w_weaponname;
76 .int w_dmg;
78 
80 {
81  this.nextthink = time;
82  if (game_stopped) this.frame = this.anim_idle.x;
83  .entity weaponentity = this.weaponentity_fld;
84  if (this.owner.(weaponentity) != this)
85  {
86  // owner has new gun; remove old one
87  if (this.weaponchild) delete(this.weaponchild);
88  if (this.hook) delete(this.hook);
89  delete(this);
90  return;
91  }
92  if (IS_DEAD(this.owner))
93  {
94  // owner died; disappear
95  this.model = "";
96  if (this.weaponchild) this.weaponchild.model = "";
97  return;
98  }
99  if (this.w_weaponname != this.weaponname || this.w_dmg != this.modelindex || this.w_deadflag != this.deadflag)
100  {
101  // owner changed weapons; update appearance
102  this.w_weaponname = this.weaponname;
103  this.w_dmg = this.modelindex;
104  this.w_deadflag = this.deadflag;
105 
106  CL_WeaponEntity_SetModel(this, this.weaponname, true);
107  }
108 
109  this.alpha = -1; // TODO: don't render this entity at all
110 
111  if (this.owner.alpha == default_player_alpha) this.m_alpha = default_weapon_alpha;
112  else if (this.owner.alpha != 0) this.m_alpha = this.owner.alpha;
113  else this.m_alpha = 1;
114 
115  if (this.weaponchild)
116  {
117  this.weaponchild.alpha = this.alpha;
118  this.weaponchild.effects = this.effects;
119  }
120 }
121 
123 {
124  this.nextthink = time;
125  .entity weaponentity = this.weaponentity_fld;
126  entity w_ent = this.owner.(weaponentity);
127  if (this.owner.exteriorweaponentity != this)
128  {
129  delete(this);
130  return;
131  }
132  if (IS_DEAD(this.owner))
133  {
134  this.model = "";
135  return;
136  }
137  if (this.weaponname != w_ent.weaponname || this.dmg != w_ent.modelindex || this.deadflag != w_ent.deadflag)
138  {
139  this.weaponname = w_ent.weaponname;
140  this.dmg = w_ent.modelindex;
141  this.deadflag = w_ent.deadflag;
142  if (w_ent.weaponname != "")
143  {
144  _setmodel(this, W_Model(strcat("v_", w_ent.weaponname, ".md3")));
145  setsize(this, '0 0 0', '0 0 0');
146  }
147  else this.model = "";
148 
149  int tag_found;
150  if ((tag_found = gettagindex(this.owner, "tag_weapon")))
151  {
152  this.tag_index = tag_found;
153  this.tag_entity = this.owner;
154  }
155  else
156  {
157  setattachment(this, this.owner, "bip01 r hand");
158  }
159  }
160  this.effects = this.owner.effects;
161  this.effects |= EF_LOWPRECISION;
162  this.effects = this.effects & EFMASK_CHEAP; // eat performance
163  if (this.owner.alpha == default_player_alpha) this.alpha = default_weapon_alpha;
164  else if (this.owner.alpha != 0) this.alpha = this.owner.alpha;
165  else this.alpha = 1;
166 
167  Weapon wep = this.owner.(weaponentity).m_weapon;
168  if (wep) this.glowmod = weaponentity_glowmod(wep, this.owner, this.owner.clientcolors, this.owner.(weaponentity));
169  this.colormap = this.owner.colormap;
170  this.skin = w_ent.skin;
171 
172  CSQCMODEL_AUTOUPDATE(this);
173 }
174 
175 // spawning weaponentity for client
176 void CL_SpawnWeaponentity(entity actor, .entity weaponentity)
177 {
178  entity w_ent = actor.(weaponentity) = new(weaponentity);
179  w_ent.solid = SOLID_NOT;
180  w_ent.owner = actor;
181  setmodel(w_ent, MDL_Null); // precision set when changed
182  setorigin(w_ent, '0 0 0');
183  w_ent.weaponentity_fld = weaponentity;
185  w_ent.nextthink = time;
186  w_ent.viewmodelforclient = actor;
187  w_ent.draggable = drag_undraggable;
189 
190  wepent_link(w_ent);
191 
192  if (weaponentity == weaponentities[0]) // only one exterior model, thank you very much
193  {
194  entity exterior = actor.exteriorweaponentity = new(exteriorweaponentity);
195  exterior.solid = SOLID_NOT;
196  exterior.owner = actor;
197  exterior.draggable = drag_undraggable;
198  exterior.weaponentity_fld = weaponentity;
199  setorigin(exterior, '0 0 0');
201  exterior.nextthink = time;
202 
203  CSQCMODEL_AUTOINIT(exterior);
204  }
205 }
206 
207 // Weapon subs
208 void w_clear(Weapon thiswep, entity actor, .entity weaponentity, int fire)
209 {
210  entity w_ent = actor.(weaponentity);
211  if (w_ent)
212  {
213  w_ent.m_weapon = WEP_Null;
214  w_ent.m_switchingweapon = WEP_Null;
215  w_ent.state = WS_CLEAR;
216  w_ent.effects = 0;
217  }
218 }
219 
220 void w_ready(Weapon thiswep, entity actor, .entity weaponentity, int fire)
221 {
222  entity w_ent = actor.(weaponentity);
223  if (w_ent) w_ent.state = WS_READY;
224  weapon_thinkf(actor, weaponentity, WFRAME_IDLE, 1000000, w_ready);
225 }
226 
227 .float prevdryfire;
229 bool weapon_prepareattack_checkammo(Weapon thiswep, entity actor, bool secondary, .entity weaponentity)
230 {
231  if ((actor.items & IT_UNLIMITED_AMMO)) return true;
232  bool ammo = false;
233  if (secondary) ammo = thiswep.wr_checkammo2(thiswep, actor, weaponentity);
234  else ammo = thiswep.wr_checkammo1(thiswep, actor, weaponentity);
235  if (ammo) return true;
236  // always keep the Mine Layer if we placed mines, so that we can detonate them
237  if (thiswep == WEP_MINE_LAYER)
238  {
239  IL_EACH(g_mines, it.owner == actor && it.weaponentity_fld == weaponentity,
240  {
241  return false;
242  });
243  }
244 
245  if (thiswep == WEP_SHOTGUN)
246  if (!secondary && WEP_CVAR(shotgun, secondary) == 1) return false; // no clicking, just allow
247 
248  if (thiswep == actor.(weaponentity).m_switchweapon && time - actor.prevdryfire > 1) // only play once BEFORE starting to switch weapons
249  {
250  sound(actor, CH_WEAPON_A, SND_DRYFIRE, VOL_BASE, ATTEN_NORM);
251  actor.prevdryfire = time;
252  }
253 
254  // check if the other firing mode has enough ammo
255  bool ammo_other = false;
256  if (secondary) ammo_other = thiswep.wr_checkammo1(thiswep, actor, weaponentity);
257  else ammo_other = thiswep.wr_checkammo2(thiswep, actor, weaponentity);
258  if (ammo_other)
259  {
260  if (time - actor.prevwarntime > 1)
261  Send_Notification(NOTIF_ONE, actor, MSG_MULTI, ITEM_WEAPON_PRIMORSEC, thiswep.m_id, secondary, (1 - secondary));
262  actor.prevwarntime = time;
263  }
264  else // this weapon is totally unable to fire, switch to another one
265  {
266  W_SwitchToOtherWeapon(actor, weaponentity);
267  }
268 
269  return false;
270 }
271 
273 bool weapon_prepareattack_check(Weapon thiswep, entity actor, .entity weaponentity, bool secondary, float attacktime)
274 {
275  if (actor.weaponentity == NULL) return true;
276  if (!weapon_prepareattack_checkammo(thiswep, actor, secondary, weaponentity)) return false;
277 
278  // if sv_ready_restart_after_countdown is set, don't allow the player to shoot
279  // if all players readied up and the countdown is running
280  if (time < game_starttime || time < actor.race_penalty) return false;
281 
282  if (timeout_status == TIMEOUT_ACTIVE) // don't allow the player to shoot while game is paused
283  return false;
284 
285  // do not even think about shooting if switching
286  if (actor.(weaponentity).m_switchweapon != actor.(weaponentity).m_weapon) return false;
287 
288  if (attacktime >= 0)
289  {
290  // don't fire if previous attack is not finished
291  if (ATTACK_FINISHED(actor, weaponentity) > time + actor.(weaponentity).weapon_frametime * 0.5) return false;
292  entity this = actor.(weaponentity);
293  // don't fire while changing weapon
294  if (this.state != WS_READY) return false;
295  }
296  return true;
297 }
298 
299 void weapon_prepareattack_do(entity actor, .entity weaponentity, bool secondary, float attacktime)
300 {
301  entity this = actor.(weaponentity);
302  if (this == NULL) return;
303  this.state = WS_INUSE;
304 
305  if(StatusEffects_active(STATUSEFFECT_SpawnShield, actor)) // given this is performed often, perform a lighter check first
306  StatusEffects_remove(STATUSEFFECT_SpawnShield, actor, STATUSEFFECT_REMOVE_CLEAR); // kill spawn shield when you fire
307 
308  // if the weapon hasn't been firing continuously, reset the timer
309  if (attacktime >= 0)
310  {
311  if (ATTACK_FINISHED(actor, weaponentity) < time - this.weapon_frametime * 1.5)
312  {
313  ATTACK_FINISHED(actor, weaponentity) = time;
314  // dprint("resetting attack finished to ", ftos(time), "\n");
315  }
316  float arate = W_WeaponRateFactor(actor);
317  ATTACK_FINISHED(actor, weaponentity) = ATTACK_FINISHED(actor, weaponentity) + attacktime * arate;
318 
320  {
321  int slot = weaponslot(weaponentity);
322  for(int wepslot = 0; wepslot < MAX_WEAPONSLOTS; ++wepslot)
323  {
324  if(slot == wepslot)
325  continue;
326  .entity wepent = weaponentities[wepslot];
327  if(actor.(wepent) && actor.(wepent).m_weapon != WEP_Null)
328  {
329  if(ATTACK_FINISHED(actor, wepent) > time + actor.(wepent).weapon_frametime * 0.5)
330  continue; // still cooling down!
331  if (ATTACK_FINISHED(actor, wepent) < time - actor.(wepent).weapon_frametime * 1.5)
332  ATTACK_FINISHED(actor, wepent) = time;
333  ATTACK_FINISHED(actor, wepent) = ATTACK_FINISHED(actor, wepent) + (attacktime * arate) / MAX_WEAPONSLOTS;
334  }
335  }
336  }
337  }
338  this.bulletcounter += 1;
339  // dprint("attack finished ", ftos(ATTACK_FINISHED(actor, weaponentity)), "\n");
340 }
341 
342 bool weapon_prepareattack(Weapon thiswep, entity actor, .entity weaponentity, bool secondary, float attacktime)
343 {
344  if (weapon_prepareattack_check(thiswep, actor, weaponentity, secondary, attacktime))
345  {
346  weapon_prepareattack_do(actor, weaponentity, secondary, attacktime);
347  return true;
348  }
349  return false;
350 }
351 
356 void weapon_thinkf(entity actor, .entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,
357  .entity weaponentity, int fire) func)
358 {
359  entity this = actor.(weaponentity);
360  if (this == NULL) return;
361  bool restartanim;
362  if (fr == WFRAME_DONTCHANGE)
363  {
364  // this can happen when the weapon entity is newly spawned, since it has a clear state and no previous weapon frame
365  if (this.wframe == WFRAME_DONTCHANGE)
366  this.wframe = WFRAME_IDLE;
367  fr = this.wframe;
368  restartanim = false;
369  }
370  else
371  {
372  restartanim = fr != WFRAME_IDLE;
373  }
374 
375  this.wframe = fr;
376 
377  if (this.weapon_think == w_ready && func != w_ready && this.state == WS_RAISE)
378  backtrace("Tried to override initial weapon think function - should this really happen?");
379 
380  t *= W_WeaponRateFactor(actor);
381 
382  // VorteX: haste can be added here
383  if (this.weapon_think == w_ready)
384  {
385  this.weapon_nextthink = time;
386  // dprint("started firing at ", ftos(time), "\n");
387  }
388  float w_frametime_limit = this.weapon_frametime * 1.5;
389  if (this.weapon_nextthink < time - w_frametime_limit || this.weapon_nextthink > time + w_frametime_limit)
390  {
391  this.weapon_nextthink = time;
392  // dprint("reset weapon animation timer at ", ftos(time), "\n");
393  }
394  this.weapon_nextthink += t;
395  this.weapon_think = func;
396  // dprint("next ", ftos(this.weapon_nextthink), "\n");
397 
398  if (this)
399  {
400  FOREACH_CLIENT(true, {
401  if(it == actor || (IS_SPEC(it) && it.enemy == actor))
402  wframe_send(it, this, fr, autocvar_g_weaponratefactor, restartanim);
403  });
404  }
405 
406  if ((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
407  {
408  bool primary_melee = boolean(fr == WFRAME_FIRE1 && (this.m_weapon.spawnflags & WEP_TYPE_MELEE_PRI));
409  bool secondary_melee = boolean(fr == WFRAME_FIRE2 && (this.m_weapon.spawnflags & WEP_TYPE_MELEE_SEC));
410  int act = (primary_melee || secondary_melee) ? ANIMACTION_MELEE : ANIMACTION_SHOOT;
411  animdecide_setaction(actor, act, restartanim);
412  }
413  else if (actor.anim_upper_action == ANIMACTION_SHOOT || actor.anim_upper_action == ANIMACTION_MELEE)
414  {
415  actor.anim_upper_action = 0;
416  }
417 }
418 
420 {
421  if (round_handler_IsActive() && !round_handler_IsRoundStarted()) return true;
422  if (MUTATOR_CALLHOOK(ForbidWeaponUse, player)) return true;
423  return false;
424 }
425 
426 bool weaponLocked(entity player)
427 {
428  if (time < game_starttime && !sv_ready_restart_after_countdown) return true;
429  if (player.player_blocked) return true;
430  if (game_stopped) return true;
431  if (STAT(FROZEN, player)) return true;
432  if (MUTATOR_CALLHOOK(LockWeapon, player)) return true;
433  return false;
434 }
435 
436 void W_ResetGunAlign(entity player, int preferred_alignment)
437 {
438  if(W_DualWielding(player))
439  preferred_alignment = 3; // right align, the second gun will default to left
440 
441  // clear current weapon slots' alignments so we can redo the calculations!
442  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
443  {
444  .entity weaponentity = weaponentities[slot];
445  if (player.(weaponentity))
446  player.(weaponentity).m_gunalign = 0;
447  }
448 
449  // now set the new values
450  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
451  {
452  .entity weaponentity = weaponentities[slot];
453  if (player.(weaponentity))
454  player.(weaponentity).m_gunalign = W_GunAlign(player.(weaponentity), preferred_alignment);
455  }
456 }
457 
459 
460 void W_WeaponFrame(Player actor, .entity weaponentity)
461 {
462  TC(Player, actor);
463  TC(PlayerState, PS(actor));
464  entity this = actor.(weaponentity);
466 
467  if (!this || GetResource(actor, RES_HEALTH) < 1) return; // Dead player can't use weapons and injure impulse commands
468 
469  int button_atck = PHYS_INPUT_BUTTON_ATCK(actor);
470  int button_atck2 = PHYS_INPUT_BUTTON_ATCK2(actor);
471 
472  if (weaponUseForbidden(actor))
473  button_atck = button_atck2 = 0; // forbid primary and secondary fire, switching is allowed
474 
475  if (weaponLocked(actor))
476  {
477  if (this.state != WS_CLEAR)
478  {
479  Weapon wpn = this.m_weapon;
480  w_ready(wpn, actor, weaponentity, button_atck | (button_atck2 << 1));
481  return;
482  }
483  }
484 
485  if(autocvar_g_weaponswitch_debug == 2 && weaponslot(weaponentity) > 0)
486  {
487  .entity wepe1 = weaponentities[0];
488  entity wep1 = actor.(wepe1);
489  this.m_switchweapon = wep1.m_switchweapon;
490  entity store = IS_PLAYER(actor) ? PS(actor) : actor;
491  if(!(this.m_switchweapon.spawnflags & WEP_FLAG_DUALWIELD) && !(store.dual_weapons & wep1.m_switchweapon.m_wepset))
492  {
493  this.m_weapon = WEP_Null;
494  this.m_switchingweapon = WEP_Null;
495  this.m_switchweapon = WEP_Null;
496  this.state = WS_CLEAR;
497  this.weaponname = "";
498  this.clip_load = this.clip_size = this.old_clip_load = 0;
499  return;
500  }
501  }
502 
503  if (this.m_switchweapon == WEP_Null)
504  {
505  if (this.state != WS_CLEAR)
506  w_ready(this.m_weapon, actor, weaponentity, button_atck | (button_atck2 << 1));
507  this.m_weapon = WEP_Null;
508  this.m_switchingweapon = WEP_Null;
509  this.state = WS_CLEAR;
510  this.weaponname = "";
511  this.clip_load = this.clip_size = this.old_clip_load = 0;
512  return;
513  }
514 
515  vector fo, ri, up;
516  MAKE_VECTORS(actor.v_angle, fo, ri, up);
517 
518  // Change weapon
519  if (this.m_weapon != this.m_switchweapon)
520  {
521  switch (this.state)
522  {
523  default:
524  LOG_WARNF("unhandled weaponentity (%i) state for player (%i): %d", this, actor, this.state);
525  break;
526  case WS_INUSE:
527  case WS_RAISE:
528  break;
529  case WS_CLEAR:
530  {
531  // end switching!
532  Weapon newwep = this.m_switchweapon;
533  this.m_switchingweapon = newwep;
534 
535  // the two weapon entities will notice this has changed and update their models
536  this.m_weapon = newwep;
537  this.weaponname = newwep.mdl;
538  this.bulletcounter = 0;
539  newwep.wr_setup(newwep, actor, weaponentity);
540  this.state = WS_RAISE;
541 
542  // set our clip load to the load of the weapon we switched to, if it's reloadable
543  if ((newwep.spawnflags & WEP_FLAG_RELOADABLE) && newwep.reloading_ammo) // prevent accessing undefined cvars
544  {
545  this.clip_load = this.(weapon_load[this.m_switchweapon.m_id]);
546  this.clip_size = newwep.reloading_ammo;
547  }
548  else
549  {
550  this.clip_load = this.clip_size = 0;
551  }
552 
553  weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, newwep.switchdelay_raise, w_ready);
554  break;
555  }
556  case WS_DROP:
557  {
558  // in dropping phase we can switch at any time
559  this.m_switchingweapon = this.m_switchweapon;
560  break;
561  }
562  case WS_READY:
563  {
564  // start switching!
565  this.m_switchingweapon = this.m_switchweapon;
566  entity oldwep = this.m_weapon;
567 
568  // set up weapon switch think in the future, and start drop anim
569  if (INDEPENDENT_ATTACK_FINISHED || ATTACK_FINISHED(actor, weaponentity) <= time + this.weapon_frametime * 0.5)
570  {
571  sound(actor, CH_WEAPON_SINGLE, SND_WEAPON_SWITCH, VOL_BASE, ATTN_NORM);
572  this.state = WS_DROP;
573  weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
574  }
575  break;
576  }
577  }
578  }
579 
580  // LordHavoc: network timing test code
581  // if (actor.button0)
582  // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(actor, weaponentity)), " >= ", ftos(this.weapon_nextthink), "\n");
583 
584  Weapon w = this.m_weapon;
585 
586  // call the think code which may fire the weapon
587  // and do so multiple times to resolve framerate dependency issues if the
588  // server framerate is very low and the weapon fire rate very high
589  for (int c = 0; c < W_TICSPERFRAME; ++c)
590  {
591  if (w != WEP_Null && !(STAT(WEAPONS, actor) & WepSet_FromWeapon(w)))
592  {
593  if (this.m_weapon == this.m_switchweapon)
594  W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
595  w = WEP_Null;
596  }
597 
598  v_forward = fo;
599  v_right = ri;
600  v_up = up;
601 
602  bool block_weapon = false;
603  {
604  bool key_pressed = PHYS_INPUT_BUTTON_HOOK(actor) && !actor.vehicle;
605  if (weaponUseForbidden(actor))
606  key_pressed = false;
607 
608  Weapon off = actor.offhand;
609  if (off && (!(STAT(WEAPONS, actor) & WEPSET(HOOK)) || off != OFFHAND_HOOK))
610  {
611  if (off.offhand_think) off.offhand_think(off, actor, key_pressed);
612  }
613  else
614  {
615  if (key_pressed && this.m_switchweapon != WEP_HOOK && !actor.hook_switchweapon)
616  W_SwitchWeapon(actor, WEP_HOOK, weaponentity);
617  actor.hook_switchweapon = key_pressed;
618  Weapon h = WEP_HOOK;
619  block_weapon = (this.m_weapon == h && (button_atck || key_pressed));
620  h.wr_think(h, actor, weaponentity, block_weapon ? 1 : 0);
621  }
622  }
623 
624  v_forward = fo;
625  v_right = ri;
626  v_up = up;
627 
628  if (!block_weapon)
629  {
630  Weapon e = this.m_weapon;
631  TC(Weapon, e);
632  if (w != WEP_Null)
633  {
634  e.wr_think(e, actor, weaponentity, button_atck | (button_atck2 << 1));
635  }
636  else if (e)
637  {
638  e.wr_gonethink(e, actor, weaponentity);
639  }
640  }
641 
642  if (time + this.weapon_frametime * 0.5 >= this.weapon_nextthink)
643  {
644  if (this.weapon_think)
645  {
646  v_forward = fo;
647  v_right = ri;
648  v_up = up;
649  Weapon wpn = this.m_weapon;
650  this.weapon_think(wpn, actor, weaponentity, button_atck | (button_atck2 << 1));
651  }
652  else
653  {
654  bprint("\{1}^1ERROR: undefined weapon think function for ", actor.netname, "\n");
655  }
656  }
657  }
658 }
659 
660 void W_AttachToShotorg(entity actor, .entity weaponentity, entity flash, vector offset)
661 {
662  flash.owner = actor;
663  flash.angles_z = random() * 360;
664 
665  entity w_ent = actor.(weaponentity);
666  entity exterior = actor.exteriorweaponentity;
667 
668  if (gettagindex(w_ent, "shot")) setattachment(flash, w_ent, "shot");
669  else setattachment(flash, w_ent, "tag_shot");
670  setorigin(flash, offset);
671 
672  entity xflash = spawn();
673  copyentity(flash, xflash);
674 
675  flash.viewmodelforclient = actor;
676 
677  if (w_ent.oldorigin.x > 0)
678  {
679  setattachment(xflash, exterior, "");
680  setorigin(xflash, w_ent.oldorigin + offset);
681  }
682  else
683  {
684  if (gettagindex(exterior, "shot")) setattachment(xflash, exterior, "shot");
685  else setattachment(xflash, exterior, "tag_shot");
686  setorigin(xflash, offset);
687  }
688 }
689 
690 void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use, .entity weaponentity)
691 {
692  if (MUTATOR_CALLHOOK(W_DecreaseAmmo, actor, actor.(weaponentity), ammo_use)) return;
693  if ((actor.items & IT_UNLIMITED_AMMO) && !wep.reloading_ammo) return;
694 
695  ammo_use = M_ARGV(2, float);
696 
697  entity w_ent = actor.(weaponentity);
698 
699  // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
700  if (wep.reloading_ammo)
701  {
702  w_ent.clip_load -= ammo_use;
703  w_ent.(weapon_load[w_ent.m_weapon.m_id]) = w_ent.clip_load;
704  }
705  else if (wep.ammo_type != RES_NONE)
706  {
707  float ammo = GetResource(actor, wep.ammo_type);
708  if (ammo < ammo_use)
709  {
710  backtrace(sprintf(
711  "W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... "
712  "Please notify the developers immediately with a copy of this backtrace!\n",
713  ammo_use, wep.netname, GetAmmoPicture(wep.ammo_type), actor.netname, ammo));
714  }
715  SetResource(actor, wep.ammo_type, ammo - ammo_use);
716  }
717 }
718 
719 // weapon reloading code
720 
723 .string reload_sound;
724 
725 void W_ReloadedAndReady(Weapon thiswep, entity actor, .entity weaponentity, int fire)
726 {
727  // finish the reloading process, and do the ammo transfer
728 
729  entity w_ent = actor.(weaponentity);
730  Weapon wpn = w_ent.m_weapon;
731 
732  w_ent.clip_load = w_ent.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
733 
734  // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
735  if (!w_ent.reload_ammo_min || (actor.items & IT_UNLIMITED_AMMO) || wpn.ammo_type == RES_NONE)
736  {
737  w_ent.clip_load = w_ent.reload_ammo_amount;
738  }
739  else
740  {
741  // make sure we don't add more ammo than we have
742  float ammo = GetResource(actor, wpn.ammo_type);
743  float load = min(w_ent.reload_ammo_amount - w_ent.clip_load, ammo);
744  w_ent.clip_load += load;
745  SetResource(actor, wpn.ammo_type, ammo - load);
746  }
747  w_ent.(weapon_load[w_ent.m_weapon.m_id]) = w_ent.clip_load;
748 
749  // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
750  // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
751  // so your weapon is disabled for a few seconds without reason
752 
753  // ATTACK_FINISHED(actor, weaponentity) -= w_ent.reload_time - 1;
754 
755  w_ready(wpn, actor, weaponentity, PHYS_INPUT_BUTTON_ATCK(actor) | (PHYS_INPUT_BUTTON_ATCK2(actor) << 1));
756 }
757 
758 void W_Reload(entity actor, .entity weaponentity, float sent_ammo_min, Sound sent_sound)
759 {
760  TC(Sound, sent_sound);
761  // set global values to work with
762  entity this = actor.(weaponentity);
763  Weapon e = this.m_weapon;
764 
765  if (MUTATOR_CALLHOOK(W_Reload, actor)) return;
766 
767  this.reload_ammo_min = sent_ammo_min;
768  this.reload_ammo_amount = e.reloading_ammo;
769  this.reload_time = e.reloading_time;
770  strcpy(actor.reload_sound, Sound_fixpath(sent_sound));
771 
772  // don't reload weapons that don't have the RELOADABLE flag
773  if (!(e.spawnflags & WEP_FLAG_RELOADABLE))
774  {
775  LOG_TRACE(
776  "Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
777  return;
778  }
779 
780  // return if reloading is disabled for this weapon
781  if (!this.reload_ammo_amount) return;
782 
783  // our weapon is fully loaded, no need to reload
784  if (this.clip_load >= this.reload_ammo_amount) return;
785 
786  // no ammo, so nothing to load
787  if (e.ammo_type != RES_NONE)
788  {
789  if (!GetResource(actor, e.ammo_type) && this.reload_ammo_min)
790  {
791  if (!(actor.items & IT_UNLIMITED_AMMO))
792  {
793  if (autocvar_g_weaponswitch_debug == 2 && weaponslot(weaponentity) > 0)
794  return; // in this case the primary weapon will do the switching when it runs out of ammo (TODO: do this same check but for other slots)
795  if (IS_REAL_CLIENT(actor) && actor.reload_complain < time)
796  {
797  play2(actor, SND(UNAVAILABLE));
798  sprint(actor, strcat("You don't have enough ammo to reload the ^2", this.m_weapon.m_name, "\n"));
799  actor.reload_complain = time + 1;
800  }
801  // switch away if the amount of ammo is not enough to keep using this weapon
802  if (!(e.wr_checkammo1(e, actor, weaponentity) + e.wr_checkammo2(e, actor, weaponentity)))
803  {
804  this.clip_load = -1; // reload later
805  W_SwitchToOtherWeapon(actor, weaponentity);
806  }
807  return;
808  }
809  }
810  }
811  if (this)
812  {
813  if (this.wframe == WFRAME_RELOAD) return;
814 
815  // allow switching away while reloading, but this will cause a new reload!
816  this.state = WS_READY;
817  }
818 
819  // now begin the reloading process
820 
821  _sound(actor, CH_WEAPON_SINGLE, actor.reload_sound, VOL_BASE, ATTEN_NORM);
822 
823  // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
824  // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
825  // so your weapon is disabled for a few seconds without reason
826 
827  // ATTACK_FINISHED(actor, weaponentity) = max(time, ATTACK_FINISHED(actor, weaponentity)) + this.reload_time + 1;
828 
829  weapon_thinkf(actor, weaponentity, WFRAME_RELOAD, this.reload_time, W_ReloadedAndReady);
830 
831  if (this.clip_load < 0) this.clip_load = 0;
832  this.old_clip_load = this.clip_load;
833  this.clip_load = this.(weapon_load[this.m_weapon.m_id]) = -1;
834 }
835 
836 void W_DropEvent(.void(Weapon, entity actor, .entity) event, entity player, float weapon_type, entity weapon_item, .entity weaponentity)
837 {
838  Weapon w = REGISTRY_GET(Weapons, weapon_type);
839  weapon_dropevent_item = weapon_item;
840  w.event(w, player, weaponentity);
841 }
const float SOLID_NOT
Definition: csprogsdefs.qc:244
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition: player.qh:148
OffhandHook OFFHAND_HOOK
Definition: hook.qh:67
#define WEPSET(id)
Definition: all.qh:37
#define round_handler_IsActive()
#define IL_EACH(this, cond, body)
float weapon_load[REGISTRY_MAX(Weapons)]
Definition: weaponsystem.qh:29
float weapon_frametime
Definition: weaponsystem.qc:31
float colormap
Definition: csprogsdefs.qc:131
float alpha
Definition: items.qc:14
string W_Model(string w_mdl)
Definition: all.qc:288
void W_AttachToShotorg(entity actor,.entity weaponentity, entity flash, vector offset)
const int WEP_FLAG_RELOADABLE
Definition: weapon.qh:201
entity hook
Definition: hook.qh:19
float autocvar_g_weaponratefactor
Definition: weaponsystem.qh:8
void W_SwitchWeapon_Force(Player this, Weapon w,.entity weaponentity)
Definition: selection.qc:243
skin
Definition: ent_cs.qc:143
#define SND(id)
Definition: all.qh:35
const int WS_RAISE
raise frame
Definition: weapon.qh:32
#define PHYS_INPUT_BUTTON_HOOK(s)
Definition: player.qh:151
void W_WeaponFrame(Player actor,.entity weaponentity)
string GetAmmoPicture(Resource ammotype)
Definition: all.qc:206
Header file that describes the resource system.
void W_SwitchToOtherWeapon(entity this,.entity weaponentity)
Definition: selection.qc:253
bool weaponUseForbidden(entity player)
#define w_getbestweapon(ent, wepent)
Definition: selection.qh:23
#define Sound_fixpath(this)
Definition: sound.qh:141
float modelindex
Definition: csprogsdefs.qc:91
const int WS_CLEAR
no weapon selected
Definition: weapon.qh:30
void CL_ExteriorWeaponentity_Think(entity this)
entity() spawn
#define REGISTRY_GET(id, i)
Definition: registry.qh:43
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
void w_ready(Weapon thiswep, entity actor,.entity weaponentity, int fire)
void W_ReloadedAndReady(Weapon thiswep, entity actor,.entity weaponentity, int fire)
#define PS(this)
Definition: state.qh:18
#define WEP_CVAR(wepname, name)
Definition: all.qh:299
void W_ResetGunAlign(entity player, int preferred_alignment)
float dmg
Definition: platforms.qh:6
entity weaponentity_fld
Definition: weaponsystem.qh:27
vector v_angle
Definition: client.qh:144
bool weapon_prepareattack_check(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
const float TIMEOUT_ACTIVE
Definition: common.qh:49
const int WS_DROP
deselecting frame
Definition: weapon.qh:34
int W_GunAlign(entity this, int preferred_align)
#define round_handler_IsRoundStarted()
float reload_complain
const int WS_READY
idle frame
Definition: weapon.qh:38
float prevwarntime
float m_alpha
Definition: weaponsystem.qc:74
float effects
Definition: csprogsdefs.qc:111
int clip_load
Definition: wepent.qh:14
entity owner
Definition: main.qh:73
string model
Definition: csprogsdefs.qc:108
float weapon_nextthink
Definition: view.qc:273
bool hook_switchweapon
#define gettagindex
Definition: dpextensions.qh:16
float bulletcounter
Definition: weaponsystem.qh:25
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
int clip_size
Definition: wepent.qh:15
int weaponslot(.entity weaponentity)
Definition: weapon.qh:16
#define INDEPENDENT_ATTACK_FINISHED
Definition: weaponsystem.qh:11
#define strcpy(this, s)
Definition: string.qh:49
string netname
M: refname : reference name name.
Definition: weapon.qh:76
void W_DropEvent(.void(Weapon, entity actor,.entity) event, entity player, float weapon_type, entity weapon_item,.entity weaponentity)
#define setmodel(this, m)
Definition: model.qh:26
const float ATTN_NORM
Definition: csprogsdefs.qc:226
#define LOG_WARNF(...)
Definition: log.qh:67
int spawnflags
M: flags : WEPSPAWNFLAG_...
Definition: weapon.qh:52
RES_HEALTH
Definition: ent_cs.qc:126
float ammo
Definition: sv_turrets.qh:44
bool weaponLocked(entity player)
Effect is being forcibly removed without calling any additional mechanics.
Definition: all.qh:27
#define IS_SPEC(v)
Definition: utils.qh:10
int state
Definition: weaponsystem.qc:29
void CL_Weaponentity_Think(entity this)
Definition: weaponsystem.qc:79
bool autocvar_g_weaponswitch_debug
Definition: selection.qh:7
const int CH_WEAPON_A
Definition: sound.qh:7
void SetResource(entity e, Resource res_type, float amount)
Sets the current amount of resource the given entity will have.
Definition: cl_resources.qc:26
bool W_DualWielding(entity player)
Definition: common.qc:20
vector v_up
Definition: csprogsdefs.qc:31
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
const int MAX_WEAPONSLOTS
Definition: weapon.qh:13
Resource ammo_type
M: ammotype : main ammo type.
Definition: weapon.qh:48
bool weapon_prepareattack(Weapon thiswep, entity actor,.entity weaponentity, bool secondary, float attacktime)
const int CH_WEAPON_SINGLE
Definition: sound.qh:9
entity viewmodelforclient
void animdecide_setaction(entity e, float action, float restart)
Definition: animdecide.qc:338
const int WEP_TYPE_MELEE_SEC
Definition: weapon.qh:205
entity weapon_dropevent_item
Definition: weaponsystem.qh:21
#define NULL
Definition: post.qh:17
float frametime
Definition: csprogsdefs.qc:17
string w_weaponname
Definition: weaponsystem.qc:75
#define backtrace(msg)
Definition: log.qh:105
const float VOL_BASE
Definition: sound.qh:36
#define TC(T, sym)
Definition: _all.inc:82
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition: player.qh:146
const int ANIMACTION_SHOOT
Definition: animdecide.qh:145
float race_penalty
Definition: sound.qh:119
#define M_ARGV(x, type)
Definition: events.qh:17
#define IS_DEAD(s)
Definition: utils.qh:26
const float ATTEN_NORM
Definition: sound.qh:30
float nextthink
Definition: csprogsdefs.qc:121
vector CL_Weapon_GetShotOrg(int wpn)
Definition: weaponsystem.qc:63
Purpose: common player state, usable on client and server Client: singleton representing the viewed p...
Definition: state.qh:8
vector(float skel, float bonenum) _skel_get_boneabs_hidden
int w_deadflag
Definition: weaponsystem.qc:77
const int WS_INUSE
fire state
Definition: weapon.qh:36
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
float deadflag
Definition: progsdefs.qc:149
float reload_ammo_min
int w_dmg
Definition: weaponsystem.qc:76
#define LOG_TRACE(...)
Definition: log.qh:81
float W_WeaponSpeedFactor(entity this)
Definition: weaponsystem.qc:45
void CL_SpawnWeaponentity(entity actor,.entity weaponentity)
bool sv_ready_restart_after_countdown
Definition: world.qh:119
void weapon_thinkf(entity actor,.entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor,.entity weaponentity, int fire) func)
#define _sound(e, c, s, v, a)
Definition: sound.qh:50
bool W_SwitchWeapon(entity this, Weapon w,.entity weaponentity)
Definition: selection.qc:272
vector v_right
Definition: csprogsdefs.qc:31
string reload_sound
float autocvar_g_weaponspeedfactor
Definition: weaponsystem.qh:9
#define MUTATOR_CALLHOOK(id,...)
Definition: base.qh:140
bool drag_undraggable(entity draggee, entity dragger)
Definition: cheats.qc:897
bool CL_Weaponentity_CustomizeEntityForClient(entity this, entity client)
Definition: weaponsystem.qc:56
float frame
primary framegroup animation (strength = 1 - lerpfrac - lerpfrac3 - lerpfrac4)
Definition: anim.qh:6
entity weaponentities[MAX_WEAPONSLOTS]
Definition: weapon.qh:14
void w_clear(Weapon thiswep, entity actor,.entity weaponentity, int fire)
setorigin(ent, v)
#define setthink(e, f)
int tag_index
#define ATTACK_FINISHED(ent, w)
Definition: weaponsystem.qh:42
const int ANIMACTION_MELEE
Definition: animdecide.qh:147
float default_player_alpha
Definition: world.qh:66
void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use,.entity weaponentity)
#define WepSet_FromWeapon(it)
Definition: all.qh:38
#define sound(e, c, s, v, a)
Definition: sound.qh:52
entity tag_entity
vector glowmod
const int WEP_FLAG_DUALWIELD
Definition: weapon.qh:206
void weapon_prepareattack_do(entity actor,.entity weaponentity, bool secondary, float attacktime)
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
bool autocvar_g_weaponswitch_debug_alternate
Definition: selection.qh:8
float reload_time
string netname
Client name.
Definition: client.qh:90
float default_weapon_alpha
Definition: world.qh:67
int m_id
Definition: weapon.qh:42
float reload_ammo_amount
string mdl
M: modelname : name of model (without g_ v_ or h_ prefixes)
Definition: weapon.qh:58
float prevdryfire
float W_WeaponRateFactor(entity this)
Definition: weaponsystem.qc:33
#define boolean(value)
Definition: bool.qh:9
int old_clip_load
Definition: weaponsystem.qh:32
#define IS_PLAYER(v)
Definition: utils.qh:9
float EF_LOWPRECISION
const int W_TICSPERFRAME
Definition: weaponsystem.qh:14
vector v_forward
Definition: csprogsdefs.qc:31
const int WEP_TYPE_MELEE_PRI
Definition: weapon.qh:204
void W_Reload(entity actor,.entity weaponentity, float sent_ammo_min, Sound sent_sound)
bool weapon_prepareattack_checkammo(Weapon thiswep, entity actor, bool secondary,.entity weaponentity)