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