]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/weapons/weaponsystem.qc
Use SELFPARAM() in every function that uses self
[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 {SELFPARAM();
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 {SELFPARAM();
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                                 LOG_INFO("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                                 LOG_INFO("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                                 LOG_INFO("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 {SELFPARAM();
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 {SELFPARAM();
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 {SELFPARAM();
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(self);
408 }
409
410 // spawning weaponentity for client
411 void CL_SpawnWeaponentity(entity e)
412 {
413         entity view = e.weaponentity = spawn();
414         view.classname = "weaponentity";
415         view.solid = SOLID_NOT;
416         view.owner = e;
417         setmodel(view, ""); // precision set when changed
418         setorigin(view, '0 0 0');
419         view.angles = '0 0 0';
420         view.viewmodelforclient = e;
421         view.flags = 0;
422         view.think = CL_Weaponentity_Think;
423         view.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
424         view.nextthink = time;
425
426         entity exterior = e.exteriorweaponentity = spawn();
427         exterior.classname = "exteriorweaponentity";
428         exterior.solid = SOLID_NOT;
429         exterior.exteriorweaponentity = exterior;
430         exterior.owner = e;
431         setorigin(exterior, '0 0 0');
432         exterior.angles = '0 0 0';
433         exterior.think = CL_ExteriorWeaponentity_Think;
434         exterior.nextthink = time;
435
436         CSQCMODEL_AUTOINIT(exterior);
437 }
438
439 // Weapon subs
440 void w_clear()
441 {SELFPARAM();
442         if (self.weapon != -1)
443         {
444                 self.weapon = 0;
445                 self.switchingweapon = 0;
446         }
447         if (self.weaponentity)
448         {
449                 self.weaponentity.state = WS_CLEAR;
450                 self.weaponentity.effects = 0;
451         }
452 }
453
454 void w_ready()
455 {SELFPARAM();
456         if (self.weaponentity)
457                 self.weaponentity.state = WS_READY;
458         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
459 }
460
461 .float prevdryfire;
462 .float prevwarntime;
463 float weapon_prepareattack_checkammo(float secondary)
464 {SELFPARAM();
465         if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
466         if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
467         {
468                 // always keep the Mine Layer if we placed mines, so that we can detonate them
469                 entity mine;
470                 if(self.weapon == WEP_MINE_LAYER.m_id)
471                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
472                         return false;
473
474                 if(self.weapon == WEP_SHOTGUN.m_id)
475                 if(!secondary && WEP_CVAR(shotgun, secondary) == 1)
476                         return false; // no clicking, just allow
477
478                 if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
479                 {
480                         sound (self, CH_WEAPON_A, W_Sound("dryfire"), VOL_BASE, ATTEN_NORM);
481                         self.prevdryfire = time;
482                 }
483
484                 if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
485                 {
486                         if(time - self.prevwarntime > 1)
487                         {
488                                 Send_Notification(
489                                         NOTIF_ONE,
490                                         self,
491                                         MSG_MULTI,
492                                         ITEM_WEAPON_PRIMORSEC,
493                                         self.weapon,
494                                         secondary,
495                                         (1 - secondary)
496                                 );
497                         }
498                         self.prevwarntime = time;
499                 }
500                 else // this weapon is totally unable to fire, switch to another one
501                 {
502                         W_SwitchToOtherWeapon(self);
503                 }
504
505                 return false;
506         }
507         return true;
508 }
509 .float race_penalty;
510 float weapon_prepareattack_check(float secondary, float attacktime)
511 {SELFPARAM();
512         if(!weapon_prepareattack_checkammo(secondary))
513                 return false;
514
515         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
516         //if all players readied up and the countdown is running
517         if(time < game_starttime || time < self.race_penalty) {
518                 return false;
519         }
520
521         if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
522                 return false;
523
524         // do not even think about shooting if switching
525         if(self.switchweapon != self.weapon)
526                 return false;
527
528         if(attacktime >= 0)
529         {
530                 // don't fire if previous attack is not finished
531                 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
532                         return false;
533                 // don't fire while changing weapon
534                 if (self.weaponentity.state != WS_READY)
535                         return false;
536         }
537
538         return true;
539 }
540 float weapon_prepareattack_do(float secondary, float attacktime)
541 {SELFPARAM();
542         self.weaponentity.state = WS_INUSE;
543
544         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
545
546         // if the weapon hasn't been firing continuously, reset the timer
547         if(attacktime >= 0)
548         {
549                 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
550                 {
551                         ATTACK_FINISHED(self) = time;
552                         //dprint("resetting attack finished to ", ftos(time), "\n");
553                 }
554                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
555         }
556         self.bulletcounter += 1;
557         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
558         return true;
559 }
560 float weapon_prepareattack(float secondary, float attacktime)
561 {
562         if(weapon_prepareattack_check(secondary, attacktime))
563         {
564                 weapon_prepareattack_do(secondary, attacktime);
565                 return true;
566         }
567         else
568                 return false;
569 }
570
571 void weapon_thinkf(float fr, float t, void() func)
572 {SELFPARAM();
573         vector a;
574         vector of, or, ou;
575         float restartanim;
576
577         if(fr == WFRAME_DONTCHANGE)
578         {
579                 fr = self.weaponentity.wframe;
580                 restartanim = false;
581         }
582         else if (fr == WFRAME_IDLE)
583                 restartanim = false;
584         else
585                 restartanim = true;
586
587         of = v_forward;
588         or = v_right;
589         ou = v_up;
590
591         if (self.weaponentity)
592         {
593                 self.weaponentity.wframe = fr;
594                 a = '0 0 0';
595                 if (fr == WFRAME_IDLE)
596                         a = self.weaponentity.anim_idle;
597                 else if (fr == WFRAME_FIRE1)
598                         a = self.weaponentity.anim_fire1;
599                 else if (fr == WFRAME_FIRE2)
600                         a = self.weaponentity.anim_fire2;
601                 else // if (fr == WFRAME_RELOAD)
602                         a = self.weaponentity.anim_reload;
603                 a.z *= g_weaponratefactor;
604                 setanim(self.weaponentity, a, restartanim == false, restartanim, restartanim);
605         }
606
607         v_forward = of;
608         v_right = or;
609         v_up = ou;
610
611         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
612         {
613                 backtrace("Tried to override initial weapon think function - should this really happen?");
614         }
615
616         t *= W_WeaponRateFactor();
617
618         // VorteX: haste can be added here
619         if (self.weapon_think == w_ready)
620         {
621                 self.weapon_nextthink = time;
622                 //dprint("started firing at ", ftos(time), "\n");
623         }
624         if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
625         {
626                 self.weapon_nextthink = time;
627                 //dprint("reset weapon animation timer at ", ftos(time), "\n");
628         }
629         self.weapon_nextthink = self.weapon_nextthink + t;
630         self.weapon_think = func;
631         //dprint("next ", ftos(self.weapon_nextthink), "\n");
632
633         if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
634         {
635                 if((self.weapon == WEP_SHOCKWAVE.m_id || self.weapon == WEP_SHOTGUN.m_id) && fr == WFRAME_FIRE2)
636                         animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
637                 else
638                         animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
639         }
640         else
641         {
642                 if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
643                         self.anim_upper_action = 0;
644         }
645 }
646
647 float forbidWeaponUse(entity player)
648 {
649         if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
650                 return 1;
651         if(round_handler_IsActive() && !round_handler_IsRoundStarted())
652                 return 1;
653         if(player.player_blocked)
654                 return 1;
655         if(player.frozen)
656                 return 1;
657         if(player.weapon_blocked)
658                 return 1;
659         return 0;
660 }
661
662 void W_WeaponFrame()
663 {SELFPARAM();
664         vector fo, ri, up;
665
666         if (frametime)
667                 self.weapon_frametime = frametime;
668
669         if (!self.weaponentity || self.health < 1)
670                 return; // Dead player can't use weapons and injure impulse commands
671
672         if(forbidWeaponUse(self))
673         if(self.weaponentity.state != WS_CLEAR)
674         {
675                 w_ready();
676                 return;
677         }
678
679         if(!self.switchweapon)
680         {
681                 self.weapon = 0;
682                 self.switchingweapon = 0;
683                 self.weaponentity.state = WS_CLEAR;
684                 self.weaponname = "";
685                 //self.items &= ~IT_AMMO;
686                 return;
687         }
688
689         makevectors(self.v_angle);
690         fo = v_forward; // save them in case the weapon think functions change it
691         ri = v_right;
692         up = v_up;
693
694         // Change weapon
695         if (self.weapon != self.switchweapon)
696         {
697                 if (self.weaponentity.state == WS_CLEAR)
698                 {
699                         // end switching!
700                         self.switchingweapon = self.switchweapon;
701                         entity newwep = get_weaponinfo(self.switchweapon);
702
703                         // the two weapon entities will notice this has changed and update their models
704                         self.weapon = self.switchweapon;
705                         self.weaponname = newwep.mdl;
706                         self.bulletcounter = 0;
707                         self.ammo_field = newwep.ammo_field;
708                         WEP_ACTION(self.switchweapon, WR_SETUP);
709                         self.weaponentity.state = WS_RAISE;
710
711                         // set our clip load to the load of the weapon we switched to, if it's reloadable
712                         if(newwep.spawnflags & WEP_FLAG_RELOADABLE && newwep.reloading_ammo) // prevent accessing undefined cvars
713                         {
714                                 self.clip_load = self.(weapon_load[self.switchweapon]);
715                                 self.clip_size = newwep.reloading_ammo;
716                         }
717                         else
718                                 self.clip_load = self.clip_size = 0;
719
720                         weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
721                 }
722                 else if (self.weaponentity.state == WS_DROP)
723                 {
724                         // in dropping phase we can switch at any time
725                         self.switchingweapon = self.switchweapon;
726                 }
727                 else if (self.weaponentity.state == WS_READY)
728                 {
729                         // start switching!
730                         self.switchingweapon = self.switchweapon;
731                         entity oldwep = get_weaponinfo(self.weapon);
732
733                         // set up weapon switch think in the future, and start drop anim
734                         #ifndef INDEPENDENT_ATTACK_FINISHED
735                         if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
736                         {
737                         #endif
738                                 sound(self, CH_WEAPON_SINGLE, W_Sound("weapon_switch"), VOL_BASE, ATTN_NORM);
739                                 self.weaponentity.state = WS_DROP;
740                                 weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
741                         #ifndef INDEPENDENT_ATTACK_FINISHED
742                         }
743                         #endif
744                 }
745         }
746
747         // LordHavoc: network timing test code
748         //if (self.button0)
749         //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
750
751         float w;
752         w = self.weapon;
753
754         // call the think code which may fire the weapon
755         // and do so multiple times to resolve framerate dependency issues if the
756         // server framerate is very low and the weapon fire rate very high
757         float c;
758         c = 0;
759         while (c < W_TICSPERFRAME)
760         {
761                 c = c + 1;
762                 if(w && !(self.weapons & WepSet_FromWeapon(w)))
763                 {
764                         if(self.weapon == self.switchweapon)
765                                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
766                         w = 0;
767                 }
768
769                 v_forward = fo;
770                 v_right = ri;
771                 v_up = up;
772
773                 if(w)
774                         WEP_ACTION(self.weapon, WR_THINK);
775                 else
776                         WEP_ACTION(self.weapon, WR_GONETHINK);
777
778                 if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
779                 {
780                         if(self.weapon_think)
781                         {
782                                 v_forward = fo;
783                                 v_right = ri;
784                                 v_up = up;
785                                 self.weapon_think();
786                         }
787                         else
788                                 bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
789                 }
790         }
791 }
792
793 void W_AttachToShotorg(entity flash, vector offset)
794 {SELFPARAM();
795         entity xflash;
796         flash.owner = self;
797         flash.angles_z = random() * 360;
798
799         if(gettagindex(self.weaponentity, "shot"))
800                 setattachment(flash, self.weaponentity, "shot");
801         else
802                 setattachment(flash, self.weaponentity, "tag_shot");
803         setorigin(flash, offset);
804
805         xflash = spawn();
806         copyentity(flash, xflash);
807
808         flash.viewmodelforclient = self;
809
810         if(self.weaponentity.oldorigin.x > 0)
811         {
812                 setattachment(xflash, self.exteriorweaponentity, "");
813                 setorigin(xflash, self.weaponentity.oldorigin + offset);
814         }
815         else
816         {
817                 if(gettagindex(self.exteriorweaponentity, "shot"))
818                         setattachment(xflash, self.exteriorweaponentity, "shot");
819                 else
820                         setattachment(xflash, self.exteriorweaponentity, "tag_shot");
821                 setorigin(xflash, offset);
822         }
823 }
824
825 void W_DecreaseAmmo(float ammo_use)
826 {SELFPARAM();
827         entity wep = get_weaponinfo(self.weapon);
828
829         if(cvar("g_overkill"))
830         if(self.ok_use_ammocharge)
831         {
832                 ok_DecreaseCharge(self, self.weapon);
833                 return; // TODO
834         }
835
836         if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !wep.reloading_ammo)
837                 return;
838
839         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
840         if(wep.reloading_ammo)
841         {
842                 self.clip_load -= ammo_use;
843                 self.(weapon_load[self.weapon]) = self.clip_load;
844         }
845         else if(wep.ammo_field != ammo_none)
846         {
847                 self.(wep.ammo_field) -= ammo_use;
848                 if(self.(wep.ammo_field) < 0)
849                 {
850                         backtrace(sprintf(
851                                 "W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... "
852                                 "Please notify Samual immediately with a copy of this backtrace!\n",
853                                 ammo_use,
854                                 wep.netname,
855                                 GetAmmoPicture(wep.ammo_field),
856                                 self.netname,
857                                 self.(wep.ammo_field)
858                         ));
859                 }
860         }
861 }
862
863 // weapon reloading code
864
865 .float reload_ammo_amount, reload_ammo_min, reload_time;
866 .float reload_complain;
867 .string reload_sound;
868
869 void W_ReloadedAndReady()
870 {SELFPARAM();
871         // finish the reloading process, and do the ammo transfer
872
873         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
874
875         // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
876         if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO || self.ammo_field == ammo_none)
877                 self.clip_load = self.reload_ammo_amount;
878         else
879         {
880                 // make sure we don't add more ammo than we have
881                 float load = min(self.reload_ammo_amount - self.clip_load, self.(self.ammo_field));
882         self.clip_load += load;
883         self.(self.ammo_field) -= load;
884         }
885         self.(weapon_load[self.weapon]) = self.clip_load;
886
887         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
888         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
889         // so your weapon is disabled for a few seconds without reason
890
891         //ATTACK_FINISHED(self) -= self.reload_time - 1;
892
893         w_ready();
894 }
895
896 void W_Reload(float sent_ammo_min, string sent_sound)
897 {SELFPARAM();
898         // set global values to work with
899         entity e;
900         e = get_weaponinfo(self.weapon);
901
902         if(cvar("g_overkill"))
903         if(self.ok_use_ammocharge)
904                 return; // TODO
905
906         self.reload_ammo_min = sent_ammo_min;
907         self.reload_ammo_amount = e.reloading_ammo;
908         self.reload_time = e.reloading_time;
909         self.reload_sound = sent_sound;
910
911         // don't reload weapons that don't have the RELOADABLE flag
912         if (!(e.spawnflags & WEP_FLAG_RELOADABLE))
913         {
914                 LOG_TRACE("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
915                 return;
916         }
917
918         // return if reloading is disabled for this weapon
919         if(!self.reload_ammo_amount)
920                 return;
921
922         // our weapon is fully loaded, no need to reload
923         if (self.clip_load >= self.reload_ammo_amount)
924                 return;
925
926         // no ammo, so nothing to load
927         if(self.ammo_field != ammo_none)
928         if(!self.(self.ammo_field) && self.reload_ammo_min)
929         if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
930         {
931                 if(IS_REAL_CLIENT(self) && self.reload_complain < time)
932                 {
933                         play2(self, W_Sound("unavailable"));
934                         sprint(self, strcat("You don't have enough ammo to reload the ^2", WEP_NAME(self.weapon), "\n"));
935                         self.reload_complain = time + 1;
936                 }
937                 // switch away if the amount of ammo is not enough to keep using this weapon
938                 if (!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2)))
939                 {
940                         self.clip_load = -1; // reload later
941                         W_SwitchToOtherWeapon(self);
942                 }
943                 return;
944         }
945
946         if (self.weaponentity)
947         {
948                 if (self.weaponentity.wframe == WFRAME_RELOAD)
949                         return;
950
951                 // allow switching away while reloading, but this will cause a new reload!
952                 self.weaponentity.state = WS_READY;
953         }
954
955         // now begin the reloading process
956
957         sound(self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTEN_NORM);
958
959         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
960         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
961         // so your weapon is disabled for a few seconds without reason
962
963         //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
964
965         weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
966
967         if(self.clip_load < 0)
968                 self.clip_load = 0;
969         self.old_clip_load = self.clip_load;
970         self.clip_load = self.(weapon_load[self.weapon]) = -1;
971 }
972
973 void W_DropEvent(float event, entity player, float weapon_type, entity weapon_item)
974 {SELFPARAM();
975         entity oldself = self;
976         self = player;
977         weapon_dropevent_item = weapon_item;
978         WEP_ACTION(weapon_type, event);
979         self = oldself;
980 }