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