]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/weapons/weaponsystem.qc
1e589309ca2b6f0839344320857c9fe997812747
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / weapons / weaponsystem.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4         #include "../../dpdefs/progsdefs.qh"
5     #include "../../dpdefs/dpextensions.qh"
6     #include "../sys-post.qh"
7     #include "../../common/constants.qh"
8     #include "../../common/util.qh"
9     #include "../../common/animdecide.qh"
10     #include "../../common/monsters/monsters.qh"
11     #include "../../common/weapons/weapons.qh"
12     #include "weaponsystem.qh"
13     #include "../t_items.qh"
14     #include "../autocvars.qh"
15     #include "../defs.qh"
16     #include "../../common/notifications.qh"
17     #include "../mutators/mutators_include.qh"
18     #include "../command/common.qh"
19     #include "../../csqcmodellib/sv_model.qh"
20     #include "../round_handler.qh"
21 #endif
22
23 /*
24 ===========================================================================
25
26   CLIENT WEAPONSYSTEM CODE
27   Bring back W_Weaponframe
28
29 ===========================================================================
30 */
31
32 .float weapon_frametime;
33
34 float W_WeaponRateFactor()
35 {
36         float t;
37         t = 1.0 / g_weaponratefactor;
38
39         weapon_rate = t;
40         MUTATOR_CALLHOOK(WeaponRateFactor);
41         t = weapon_rate;
42
43         return t;
44 }
45
46 // VorteX: static frame globals
47 const float WFRAME_DONTCHANGE = -1;
48 const float WFRAME_FIRE1 = 0;
49 const float WFRAME_FIRE2 = 1;
50 const float WFRAME_IDLE = 2;
51 const float WFRAME_RELOAD = 3;
52 .float wframe;
53
54 void(float fr, float t, void() func) weapon_thinkf;
55
56 float CL_Weaponentity_CustomizeEntityForClient()
57 {
58         self.viewmodelforclient = self.owner;
59         if(IS_SPEC(other))
60                 if(other.enemy == self.owner)
61                         self.viewmodelforclient = other;
62         return true;
63 }
64
65 /*
66  * supported formats:
67  *
68  * 1. simple animated model, muzzle flash handling on h_ model:
69  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
70  *      tags:
71  *        shot = muzzle end (shot origin, also used for muzzle flashes)
72  *        shell = casings ejection point (must be on the right hand side of the gun)
73  *        weapon = attachment for v_tuba.md3
74  *    v_tuba.md3 - first and third person model
75  *    g_tuba.md3 - pickup model
76  *
77  * 2. simple animated model, muzzle flash handling on v_ model:
78  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
79  *      tags:
80  *        weapon = attachment for v_tuba.md3
81  *    v_tuba.md3 - first and third person model
82  *      tags:
83  *        shot = muzzle end (shot origin, also used for muzzle flashes)
84  *        shell = casings ejection point (must be on the right hand side of the gun)
85  *    g_tuba.md3 - pickup model
86  *
87  * 3. fully animated model, muzzle flash handling on h_ model:
88  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
89  *      tags:
90  *        shot = muzzle end (shot origin, also used for muzzle flashes)
91  *        shell = casings ejection point (must be on the right hand side of the gun)
92  *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
93  *    v_tuba.md3 - third person model
94  *    g_tuba.md3 - pickup model
95  *
96  * 4. fully animated model, muzzle flash handling on v_ model:
97  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
98  *      tags:
99  *        shot = muzzle end (shot origin)
100  *        shell = casings ejection point (must be on the right hand side of the gun)
101  *    v_tuba.md3 - third person model
102  *      tags:
103  *        shot = muzzle end (for muzzle flashes)
104  *    g_tuba.md3 - pickup model
105  */
106
107 // writes:
108 //   self.origin, self.angles
109 //   self.weaponentity
110 //   self.movedir, self.view_ofs
111 //   attachment stuff
112 //   anim stuff
113 // to free:
114 //   call again with ""
115 //   remove the ent
116 void CL_WeaponEntity_SetModel(string name)
117 {
118         float v_shot_idx;
119         if (name != "")
120         {
121                 // if there is a child entity, hide it until we're sure we use it
122                 if (self.weaponentity)
123                         self.weaponentity.model = "";
124                 setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
125                 v_shot_idx = gettagindex(self, "shot"); // used later
126                 if(!v_shot_idx)
127                         v_shot_idx = gettagindex(self, "tag_shot");
128
129                 setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
130                 // preset some defaults that work great for renamed zym files (which don't need an animinfo)
131                 self.anim_fire1  = animfixfps(self, '0 1 0.01', '0 0 0');
132                 self.anim_fire2  = animfixfps(self, '1 1 0.01', '0 0 0');
133                 self.anim_idle   = animfixfps(self, '2 1 0.01', '0 0 0');
134                 self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
135
136                 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
137                 // if we don't, this is a "real" animated model
138                 if(gettagindex(self, "weapon"))
139                 {
140                         if (!self.weaponentity)
141                                 self.weaponentity = spawn();
142                         setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
143                         setattachment(self.weaponentity, self, "weapon");
144                 }
145                 else if(gettagindex(self, "tag_weapon"))
146                 {
147                         if (!self.weaponentity)
148                                 self.weaponentity = spawn();
149                         setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
150                         setattachment(self.weaponentity, self, "tag_weapon");
151                 }
152                 else
153                 {
154                         if(self.weaponentity)
155                                 remove(self.weaponentity);
156                         self.weaponentity = world;
157                 }
158
159                 setorigin(self,'0 0 0');
160                 self.angles = '0 0 0';
161                 self.frame = 0;
162                 self.viewmodelforclient = world;
163
164                 float idx;
165
166                 if(v_shot_idx) // v_ model attached to invisible h_ model
167                 {
168                         self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
169                 }
170                 else
171                 {
172                         idx = gettagindex(self, "shot");
173                         if(!idx)
174                                 idx = gettagindex(self, "tag_shot");
175                         if(idx)
176                                 self.movedir = gettaginfo(self, idx);
177                         else
178                         {
179                                 print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
180                                 self.movedir = '0 0 0';
181                         }
182                 }
183
184                 if(self.weaponentity) // v_ model attached to invisible h_ model
185                 {
186                         idx = gettagindex(self.weaponentity, "shell");
187                         if(!idx)
188                                 idx = gettagindex(self.weaponentity, "tag_shell");
189                         if(idx)
190                                 self.spawnorigin = gettaginfo(self.weaponentity, idx);
191                 }
192                 else
193                         idx = 0;
194                 if(!idx)
195                 {
196                         idx = gettagindex(self, "shell");
197                         if(!idx)
198                                 idx = gettagindex(self, "tag_shell");
199                         if(idx)
200                                 self.spawnorigin = gettaginfo(self, idx);
201                         else
202                         {
203                                 print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
204                                 self.spawnorigin = self.movedir;
205                         }
206                 }
207
208                 if(v_shot_idx)
209                 {
210                         self.oldorigin = '0 0 0'; // use regular attachment
211                 }
212                 else
213                 {
214                         if(self.weaponentity)
215                         {
216                                 idx = gettagindex(self, "weapon");
217                                 if(!idx)
218                                         idx = gettagindex(self, "tag_weapon");
219                         }
220                         else
221                         {
222                                 idx = gettagindex(self, "handle");
223                                 if(!idx)
224                                         idx = gettagindex(self, "tag_handle");
225                         }
226                         if(idx)
227                         {
228                                 self.oldorigin = self.movedir - gettaginfo(self, idx);
229                         }
230                         else
231                         {
232                                 print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n");
233                                 self.oldorigin = '0 0 0'; // there is no way to recover from this
234                         }
235                 }
236
237                 self.viewmodelforclient = self.owner;
238         }
239         else
240         {
241                 self.model = "";
242                 if(self.weaponentity)
243                         remove(self.weaponentity);
244                 self.weaponentity = world;
245                 self.movedir = '0 0 0';
246                 self.spawnorigin = '0 0 0';
247                 self.oldorigin = '0 0 0';
248                 self.anim_fire1  = '0 1 0.01';
249                 self.anim_fire2  = '0 1 0.01';
250                 self.anim_idle   = '0 1 0.01';
251                 self.anim_reload = '0 1 0.01';
252         }
253
254         self.view_ofs = '0 0 0';
255
256         if(self.movedir.x >= 0)
257         {
258                 vector v0;
259                 v0 = self.movedir;
260                 self.movedir = shotorg_adjust(v0, false, false);
261                 self.view_ofs = shotorg_adjust(v0, false, true) - v0;
262         }
263         self.owner.stat_shotorg = compressShotOrigin(self.movedir);
264         self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
265
266         self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
267
268         // check if an instant weapon switch occurred
269         setorigin(self, self.view_ofs);
270         // reset animstate now
271         self.wframe = WFRAME_IDLE;
272         setanim(self, self.anim_idle, true, false, true);
273 }
274
275 vector CL_Weapon_GetShotOrg(float wpn)
276 {
277         entity wi, oldself;
278         vector ret;
279         wi = get_weaponinfo(wpn);
280         oldself = self;
281         self = spawn();
282         CL_WeaponEntity_SetModel(wi.mdl);
283         ret = self.movedir;
284         CL_WeaponEntity_SetModel("");
285         remove(self);
286         self = oldself;
287         return ret;
288 }
289
290 void CL_Weaponentity_Think()
291 {
292         float tb;
293         self.nextthink = time;
294         if (intermission_running)
295                 self.frame = self.anim_idle.x;
296         if (self.owner.weaponentity != self)
297         {
298                 if (self.weaponentity)
299                         remove(self.weaponentity);
300                 remove(self);
301                 return;
302         }
303         if (self.owner.deadflag != DEAD_NO)
304         {
305                 self.model = "";
306                 if (self.weaponentity)
307                         self.weaponentity.model = "";
308                 return;
309         }
310         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
311         {
312                 self.weaponname = self.owner.weaponname;
313                 self.dmg = self.owner.modelindex;
314                 self.deadflag = self.owner.deadflag;
315
316                 CL_WeaponEntity_SetModel(self.owner.weaponname);
317         }
318
319         tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
320         self.effects = self.owner.effects & EFMASK_CHEAP;
321         self.effects &= ~EF_LOWPRECISION;
322         self.effects &= ~EF_FULLBRIGHT; // can mask team color, so get rid of it
323         self.effects &= ~EF_TELEPORT_BIT;
324         self.effects &= ~EF_RESTARTANIM_BIT;
325         self.effects |= tb;
326
327         if(self.owner.alpha == default_player_alpha)
328                 self.alpha = default_weapon_alpha;
329         else if(self.owner.alpha != 0)
330                 self.alpha = self.owner.alpha;
331         else
332                 self.alpha = 1;
333
334         self.glowmod = self.owner.weaponentity_glowmod;
335         self.colormap = self.owner.colormap;
336         if (self.weaponentity)
337         {
338                 self.weaponentity.effects = self.effects;
339                 self.weaponentity.alpha = self.alpha;
340                 self.weaponentity.colormap = self.colormap;
341                 self.weaponentity.glowmod = self.glowmod;
342         }
343
344         self.angles = '0 0 0';
345
346         float f = (self.owner.weapon_nextthink - time);
347         if (self.state == WS_RAISE && !intermission_running)
348         {
349                 entity newwep = get_weaponinfo(self.owner.switchweapon);
350                 f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise);
351                 self.angles_x = -90 * f * f;
352         }
353         else if (self.state == WS_DROP && !intermission_running)
354         {
355                 entity oldwep = get_weaponinfo(self.owner.weapon);
356                 f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop);
357                 self.angles_x = -90 * f * f;
358         }
359         else if (self.state == WS_CLEAR)
360         {
361                 f = 1;
362                 self.angles_x = -90 * f * f;
363         }
364 }
365
366 void CL_ExteriorWeaponentity_Think()
367 {
368         float tag_found;
369         self.nextthink = time;
370         if (self.owner.exteriorweaponentity != self)
371         {
372                 remove(self);
373                 return;
374         }
375         if (self.owner.deadflag != DEAD_NO)
376         {
377                 self.model = "";
378                 return;
379         }
380         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
381         {
382                 self.weaponname = self.owner.weaponname;
383                 self.dmg = self.owner.modelindex;
384                 self.deadflag = self.owner.deadflag;
385                 if (self.owner.weaponname != "")
386                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
387                 else
388                         self.model = "";
389
390                 if((tag_found = gettagindex(self.owner, "tag_weapon")))
391                 {
392                         self.tag_index = tag_found;
393                         self.tag_entity = self.owner;
394                 }
395                 else
396                         setattachment(self, self.owner, "bip01 r hand");
397         }
398         self.effects = self.owner.effects;
399         self.effects |= EF_LOWPRECISION;
400         self.effects = self.effects & EFMASK_CHEAP; // eat performance
401         if(self.owner.alpha == default_player_alpha)
402                 self.alpha = default_weapon_alpha;
403         else if(self.owner.alpha != 0)
404                 self.alpha = self.owner.alpha;
405         else
406                 self.alpha = 1;
407
408         self.glowmod = self.owner.weaponentity_glowmod;
409         self.colormap = self.owner.colormap;
410
411         CSQCMODEL_AUTOUPDATE();
412 }
413
414 // spawning weaponentity for client
415 void CL_SpawnWeaponentity()
416 {
417         self.weaponentity = spawn();
418         self.weaponentity.classname = "weaponentity";
419         self.weaponentity.solid = SOLID_NOT;
420         self.weaponentity.owner = self;
421         setmodel(self.weaponentity, ""); // precision set when changed
422         setorigin(self.weaponentity, '0 0 0');
423         self.weaponentity.angles = '0 0 0';
424         self.weaponentity.viewmodelforclient = self;
425         self.weaponentity.flags = 0;
426         self.weaponentity.think = CL_Weaponentity_Think;
427         self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
428         self.weaponentity.nextthink = time;
429
430         self.exteriorweaponentity = spawn();
431         self.exteriorweaponentity.classname = "exteriorweaponentity";
432         self.exteriorweaponentity.solid = SOLID_NOT;
433         self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
434         self.exteriorweaponentity.owner = self;
435         setorigin(self.exteriorweaponentity, '0 0 0');
436         self.exteriorweaponentity.angles = '0 0 0';
437         self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
438         self.exteriorweaponentity.nextthink = time;
439
440         {
441                 entity oldself = self;
442                 self = self.exteriorweaponentity;
443                 CSQCMODEL_AUTOINIT();
444                 self = oldself;
445         }
446 }
447
448 // Weapon subs
449 void w_clear()
450 {
451         if (self.weapon != -1)
452         {
453                 self.weapon = 0;
454                 self.switchingweapon = 0;
455         }
456         if (self.weaponentity)
457         {
458                 self.weaponentity.state = WS_CLEAR;
459                 self.weaponentity.effects = 0;
460         }
461 }
462
463 void w_ready()
464 {
465         if (self.weaponentity)
466                 self.weaponentity.state = WS_READY;
467         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
468 }
469
470 .float prevdryfire;
471 .float prevwarntime;
472 float weapon_prepareattack_checkammo(float secondary)
473 {
474         if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
475         if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
476         {
477                 // always keep the Mine Layer if we placed mines, so that we can detonate them
478                 entity mine;
479                 if(self.weapon == WEP_MINE_LAYER)
480                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
481                         return false;
482
483                 if(self.weapon == WEP_SHOTGUN)
484                 if(!secondary && WEP_CVAR(shotgun, secondary) == 1)
485                         return false; // no clicking, just allow
486
487                 if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
488                 {
489                         sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTEN_NORM);
490                         self.prevdryfire = time;
491                 }
492
493                 if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
494                 {
495                         if(time - self.prevwarntime > 1)
496                         {
497                                 Send_Notification(
498                                         NOTIF_ONE,
499                                         self,
500                                         MSG_MULTI,
501                                         ITEM_WEAPON_PRIMORSEC,
502                                         self.weapon,
503                                         secondary,
504                                         (1 - secondary)
505                                 );
506                         }
507                         self.prevwarntime = time;
508                 }
509                 else // this weapon is totally unable to fire, switch to another one
510                 {
511                         W_SwitchToOtherWeapon(self);
512                 }
513
514                 return false;
515         }
516         return true;
517 }
518 .float race_penalty;
519 float weapon_prepareattack_check(float secondary, float attacktime)
520 {
521         if(!weapon_prepareattack_checkammo(secondary))
522                 return false;
523
524         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
525         //if all players readied up and the countdown is running
526         if(time < game_starttime || time < self.race_penalty) {
527                 return false;
528         }
529
530         if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
531                 return false;
532
533         // do not even think about shooting if switching
534         if(self.switchweapon != self.weapon)
535                 return false;
536
537         if(attacktime >= 0)
538         {
539                 // don't fire if previous attack is not finished
540                 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
541                         return false;
542                 // don't fire while changing weapon
543                 if (self.weaponentity.state != WS_READY)
544                         return false;
545         }
546
547         return true;
548 }
549 float weapon_prepareattack_do(float secondary, float attacktime)
550 {
551         self.weaponentity.state = WS_INUSE;
552
553         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
554
555         // if the weapon hasn't been firing continuously, reset the timer
556         if(attacktime >= 0)
557         {
558                 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
559                 {
560                         ATTACK_FINISHED(self) = time;
561                         //dprint("resetting attack finished to ", ftos(time), "\n");
562                 }
563                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
564         }
565         self.bulletcounter += 1;
566         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
567         return true;
568 }
569 float weapon_prepareattack(float secondary, float attacktime)
570 {
571         if(weapon_prepareattack_check(secondary, attacktime))
572         {
573                 weapon_prepareattack_do(secondary, attacktime);
574                 return true;
575         }
576         else
577                 return false;
578 }
579
580 void weapon_thinkf(float fr, float t, void() func)
581 {
582         vector a;
583         vector of, or, ou;
584         float restartanim;
585
586         if(fr == WFRAME_DONTCHANGE)
587         {
588                 fr = self.weaponentity.wframe;
589                 restartanim = false;
590         }
591         else if (fr == WFRAME_IDLE)
592                 restartanim = false;
593         else
594                 restartanim = true;
595
596         of = v_forward;
597         or = v_right;
598         ou = v_up;
599
600         if (self.weaponentity)
601         {
602                 self.weaponentity.wframe = fr;
603                 a = '0 0 0';
604                 if (fr == WFRAME_IDLE)
605                         a = self.weaponentity.anim_idle;
606                 else if (fr == WFRAME_FIRE1)
607                         a = self.weaponentity.anim_fire1;
608                 else if (fr == WFRAME_FIRE2)
609                         a = self.weaponentity.anim_fire2;
610                 else // if (fr == WFRAME_RELOAD)
611                         a = self.weaponentity.anim_reload;
612                 a.z *= g_weaponratefactor;
613                 setanim(self.weaponentity, a, restartanim == false, restartanim, restartanim);
614         }
615
616         v_forward = of;
617         v_right = or;
618         v_up = ou;
619
620         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
621         {
622                 backtrace("Tried to override initial weapon think function - should this really happen?");
623         }
624
625         t *= W_WeaponRateFactor();
626
627         // VorteX: haste can be added here
628         if (self.weapon_think == w_ready)
629         {
630                 self.weapon_nextthink = time;
631                 //dprint("started firing at ", ftos(time), "\n");
632         }
633         if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
634         {
635                 self.weapon_nextthink = time;
636                 //dprint("reset weapon animation timer at ", ftos(time), "\n");
637         }
638         self.weapon_nextthink = self.weapon_nextthink + t;
639         self.weapon_think = func;
640         //dprint("next ", ftos(self.weapon_nextthink), "\n");
641
642         if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
643         {
644                 if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && fr == WFRAME_FIRE2)
645                         animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
646                 else
647                         animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
648         }
649         else
650         {
651                 if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
652                         self.anim_upper_action = 0;
653         }
654 }
655
656 float forbidWeaponUse()
657 {
658         if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
659                 return 1;
660         if(round_handler_IsActive() && !round_handler_IsRoundStarted())
661                 return 1;
662         if(self.player_blocked)
663                 return 1;
664         if(self.frozen)
665                 return 1;
666         if(self.weapon_blocked)
667                 return 1;
668         return 0;
669 }
670
671 void W_WeaponFrame()
672 {
673         vector fo, ri, up;
674
675         if (frametime)
676                 self.weapon_frametime = frametime;
677
678         if (!self.weaponentity || self.health < 1)
679                 return; // Dead player can't use weapons and injure impulse commands
680
681         if(forbidWeaponUse())
682         if(self.weaponentity.state != WS_CLEAR)
683         {
684                 w_ready();
685                 return;
686         }
687
688         if(!self.switchweapon)
689         {
690                 self.weapon = 0;
691                 self.switchingweapon = 0;
692                 self.weaponentity.state = WS_CLEAR;
693                 self.weaponname = "";
694                 //self.items &= ~IT_AMMO;
695                 return;
696         }
697
698         makevectors(self.v_angle);
699         fo = v_forward; // save them in case the weapon think functions change it
700         ri = v_right;
701         up = v_up;
702
703         // Change weapon
704         if (self.weapon != self.switchweapon)
705         {
706                 if (self.weaponentity.state == WS_CLEAR)
707                 {
708                         // end switching!
709                         self.switchingweapon = self.switchweapon;
710                         entity newwep = get_weaponinfo(self.switchweapon);
711
712                         // the two weapon entities will notice this has changed and update their models
713                         self.weapon = self.switchweapon;
714                         self.weaponname = newwep.mdl;
715                         self.bulletcounter = 0;
716                         self.ammo_field = newwep.ammo_field;
717                         WEP_ACTION(self.switchweapon, WR_SETUP);
718                         self.weaponentity.state = WS_RAISE;
719
720                         // set our clip load to the load of the weapon we switched to, if it's reloadable
721                         if(newwep.spawnflags & WEP_FLAG_RELOADABLE && newwep.reloading_ammo) // prevent accessing undefined cvars
722                         {
723                                 self.clip_load = self.(weapon_load[self.switchweapon]);
724                                 self.clip_size = newwep.reloading_ammo;
725                         }
726                         else
727                                 self.clip_load = self.clip_size = 0;
728
729                         weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
730                 }
731                 else if (self.weaponentity.state == WS_DROP)
732                 {
733                         // in dropping phase we can switch at any time
734                         self.switchingweapon = self.switchweapon;
735                 }
736                 else if (self.weaponentity.state == WS_READY)
737                 {
738                         // start switching!
739                         self.switchingweapon = self.switchweapon;
740                         entity oldwep = get_weaponinfo(self.weapon);
741
742                         // set up weapon switch think in the future, and start drop anim
743                         #ifndef INDEPENDENT_ATTACK_FINISHED
744                         if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
745                         {
746                         #endif
747                                 sound(self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
748                                 self.weaponentity.state = WS_DROP;
749                                 weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
750                         #ifndef INDEPENDENT_ATTACK_FINISHED
751                         }
752                         #endif
753                 }
754         }
755
756         // LordHavoc: network timing test code
757         //if (self.button0)
758         //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
759
760         float w;
761         w = self.weapon;
762
763         // call the think code which may fire the weapon
764         // and do so multiple times to resolve framerate dependency issues if the
765         // server framerate is very low and the weapon fire rate very high
766         float c;
767         c = 0;
768         while (c < W_TICSPERFRAME)
769         {
770                 c = c + 1;
771                 if(w && !(self.weapons & WepSet_FromWeapon(w)))
772                 {
773                         if(self.weapon == self.switchweapon)
774                                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
775                         w = 0;
776                 }
777
778                 v_forward = fo;
779                 v_right = ri;
780                 v_up = up;
781
782                 if(w)
783                         WEP_ACTION(self.weapon, WR_THINK);
784                 else
785                         WEP_ACTION(self.weapon, WR_GONETHINK);
786
787                 if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
788                 {
789                         if(self.weapon_think)
790                         {
791                                 v_forward = fo;
792                                 v_right = ri;
793                                 v_up = up;
794                                 self.weapon_think();
795                         }
796                         else
797                                 bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
798                 }
799         }
800 }
801
802 void W_AttachToShotorg(entity flash, vector offset)
803 {
804         entity xflash;
805         flash.owner = self;
806         flash.angles_z = random() * 360;
807
808         if(gettagindex(self.weaponentity, "shot"))
809                 setattachment(flash, self.weaponentity, "shot");
810         else
811                 setattachment(flash, self.weaponentity, "tag_shot");
812         setorigin(flash, offset);
813
814         xflash = spawn();
815         copyentity(flash, xflash);
816
817         flash.viewmodelforclient = self;
818
819         if(self.weaponentity.oldorigin.x > 0)
820         {
821                 setattachment(xflash, self.exteriorweaponentity, "");
822                 setorigin(xflash, self.weaponentity.oldorigin + offset);
823         }
824         else
825         {
826                 if(gettagindex(self.exteriorweaponentity, "shot"))
827                         setattachment(xflash, self.exteriorweaponentity, "shot");
828                 else
829                         setattachment(xflash, self.exteriorweaponentity, "tag_shot");
830                 setorigin(xflash, offset);
831         }
832 }
833
834 void W_DecreaseAmmo(float ammo_use)
835 {
836         entity wep = get_weaponinfo(self.weapon);
837
838         if(cvar("g_overkill"))
839         if(self.ok_use_ammocharge)
840         {
841                 ok_DecreaseCharge(self, self.weapon);
842                 return; // TODO
843         }
844
845         if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !wep.reloading_ammo)
846                 return;
847
848         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
849         if(wep.reloading_ammo)
850         {
851                 self.clip_load -= ammo_use;
852                 self.(weapon_load[self.weapon]) = self.clip_load;
853         }
854         else if(wep.ammo_field != ammo_none)
855         {
856                 self.(wep.ammo_field) -= ammo_use;
857                 if(self.(wep.ammo_field) < 0)
858                 {
859                         backtrace(sprintf(
860                                 "W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... "
861                                 "Please notify Samual immediately with a copy of this backtrace!\n",
862                                 ammo_use,
863                                 wep.netname,
864                                 GetAmmoPicture(wep.ammo_field),
865                                 self.netname,
866                                 self.(wep.ammo_field)
867                         ));
868                 }
869         }
870 }
871
872 // weapon reloading code
873
874 .float reload_ammo_amount, reload_ammo_min, reload_time;
875 .float reload_complain;
876 .string reload_sound;
877
878 void W_ReloadedAndReady()
879 {
880         // finish the reloading process, and do the ammo transfer
881
882         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
883
884         // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
885         if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO || self.ammo_field == ammo_none)
886                 self.clip_load = self.reload_ammo_amount;
887         else
888         {
889                 // make sure we don't add more ammo than we have
890                 float load = min(self.reload_ammo_amount - self.clip_load, self.(self.ammo_field));
891         self.clip_load += load;
892         self.(self.ammo_field) -= load;
893         }
894         self.(weapon_load[self.weapon]) = self.clip_load;
895
896         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
897         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
898         // so your weapon is disabled for a few seconds without reason
899
900         //ATTACK_FINISHED(self) -= self.reload_time - 1;
901
902         w_ready();
903 }
904
905 void W_Reload(float sent_ammo_min, string sent_sound)
906 {
907         // set global values to work with
908         entity e;
909         e = get_weaponinfo(self.weapon);
910
911         if(cvar("g_overkill"))
912         if(self.ok_use_ammocharge)
913                 return; // TODO
914
915         self.reload_ammo_min = sent_ammo_min;
916         self.reload_ammo_amount = e.reloading_ammo;
917         self.reload_time = e.reloading_time;
918         self.reload_sound = sent_sound;
919
920         // don't reload weapons that don't have the RELOADABLE flag
921         if (!(e.spawnflags & WEP_FLAG_RELOADABLE))
922         {
923                 dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
924                 return;
925         }
926
927         // return if reloading is disabled for this weapon
928         if(!self.reload_ammo_amount)
929                 return;
930
931         // our weapon is fully loaded, no need to reload
932         if (self.clip_load >= self.reload_ammo_amount)
933                 return;
934
935         // no ammo, so nothing to load
936         if(self.ammo_field != ammo_none)
937         if(!self.(self.ammo_field) && self.reload_ammo_min)
938         if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
939         {
940                 if(IS_REAL_CLIENT(self) && self.reload_complain < time)
941                 {
942                         play2(self, "weapons/unavailable.wav");
943                         sprint(self, strcat("You don't have enough ammo to reload the ^2", WEP_NAME(self.weapon), "\n"));
944                         self.reload_complain = time + 1;
945                 }
946                 // switch away if the amount of ammo is not enough to keep using this weapon
947                 if (!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2)))
948                 {
949                         self.clip_load = -1; // reload later
950                         W_SwitchToOtherWeapon(self);
951                 }
952                 return;
953         }
954
955         if (self.weaponentity)
956         {
957                 if (self.weaponentity.wframe == WFRAME_RELOAD)
958                         return;
959
960                 // allow switching away while reloading, but this will cause a new reload!
961                 self.weaponentity.state = WS_READY;
962         }
963
964         // now begin the reloading process
965
966         sound(self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTEN_NORM);
967
968         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
969         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
970         // so your weapon is disabled for a few seconds without reason
971
972         //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
973
974         weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
975
976         if(self.clip_load < 0)
977                 self.clip_load = 0;
978         self.old_clip_load = self.clip_load;
979         self.clip_load = self.(weapon_load[self.weapon]) = -1;
980 }
981
982 entity weapon_dropevent_item;
983 void W_DropEvent(float event, entity player, float weapon_type, entity weapon_item)
984 {
985         entity oldself = self;
986         self = player;
987         weapon_dropevent_item = weapon_item;
988         WEP_ACTION(weapon_type, event);
989         self = oldself;
990 }