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