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