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