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