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