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