]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/weapons/weaponsystem.qc
Rename main.qc to weaponsystem.qc
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / weapons / weaponsystem.qc
1 /*
2 ===========================================================================
3
4   CLIENT WEAPONSYSTEM CODE
5   Bring back W_Weaponframe
6
7 ===========================================================================
8 */
9
10 .float weapon_frametime;
11
12 float W_WeaponRateFactor()
13 {
14         float t;
15         t = 1.0 / g_weaponratefactor;
16
17         return t;
18 }
19
20 void W_SwitchWeapon_Force(entity e, float w)
21 {
22         e.cnt = e.switchweapon;
23         e.switchweapon = w;
24         e.selectweapon = w;
25 }
26
27 .float antilag_debug;
28
29 // VorteX: static frame globals
30 float WFRAME_DONTCHANGE = -1;
31 float WFRAME_FIRE1 = 0;
32 float WFRAME_FIRE2 = 1;
33 float WFRAME_IDLE = 2;
34 float WFRAME_RELOAD = 3;
35 .float wframe;
36
37 void(float fr, float t, void() func) weapon_thinkf;
38
39 vector w_shotorg;
40 vector w_shotdir;
41 vector w_shotend;
42
43 .float prevstrengthsound;
44 .float prevstrengthsoundattempt;
45 void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound
46 {
47         if((player.items & IT_STRENGTH)
48                 && ((time > player.prevstrengthsound + autocvar_sv_strengthsound_antispam_time) // prevent insane sound spam
49                 || (time > player.prevstrengthsoundattempt + autocvar_sv_strengthsound_antispam_refire_threshold)))
50                 {
51                         sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
52                         player.prevstrengthsound = time;
53                 }
54                 player.prevstrengthsoundattempt = time;
55 }
56
57 // this function calculates w_shotorg and w_shotdir based on the weapon model
58 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.
59 // make sure you call makevectors first (FIXME?)
60 void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector mi, vector ma, float antilag, float recoil, string snd, float chan, float maxdamage, float range)
61 {
62         float nudge = 1; // added to traceline target and subtracted from result
63         float oldsolid;
64         vector vecs, dv;
65         oldsolid = ent.dphitcontentsmask;
66         if(ent.weapon == WEP_RIFLE)
67                 ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
68         else
69                 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
70         if(antilag)
71                 WarpZone_traceline_antilag(world, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
72                 // passing world, because we do NOT want it to touch dphitcontentsmask
73         else
74                 WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent);
75         ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
76
77         vector vf, vr, vu;
78         vf = v_forward;
79         vr = v_right;
80         vu = v_up;
81         w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
82         v_forward = vf;
83         v_right = vr;
84         v_up = vu;
85
86         // un-adjust trueaim if shotend is too close
87         if(vlen(w_shotend - (ent.origin + ent.view_ofs)) < autocvar_g_trueaim_minrange)
88                 w_shotend = ent.origin + ent.view_ofs + s_forward * autocvar_g_trueaim_minrange;
89
90         // track max damage
91         if(accuracy_canbegooddamage(ent))
92                 accuracy_add(ent, ent.weapon, maxdamage, 0);
93
94         W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
95
96         if(ent.weaponentity.movedir_x > 0)
97                 vecs = ent.weaponentity.movedir;
98         else
99                 vecs = '0 0 0';
100
101         dv = v_right * -vecs_y + v_up * vecs_z;
102         w_shotorg = ent.origin + ent.view_ofs + dv;
103
104         // now move the shotorg forward as much as requested if possible
105         if(antilag)
106         {
107                 if(ent.antilag_debug)
108                         tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ent.antilag_debug);
109                 else
110                         tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
111         }
112         else
113                 tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent);
114         w_shotorg = trace_endpos - v_forward * nudge;
115         // calculate the shotdir from the chosen shotorg
116         w_shotdir = normalize(w_shotend - w_shotorg);
117
118         if (antilag)
119         if (!ent.cvar_cl_noantilag)
120         {
121                 if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
122                 {
123                         traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
124                         if (!trace_ent.takedamage)
125                         {
126                                 traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
127                                 if (trace_ent.takedamage && IS_PLAYER(trace_ent))
128                                 {
129                                         entity e;
130                                         e = trace_ent;
131                                         traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
132                                         if(trace_ent == e)
133                                                 w_shotdir = normalize(trace_ent.origin - w_shotorg);
134                                 }
135                         }
136                 }
137                 else if(autocvar_g_antilag == 3) // client side hitscan
138                 {
139                         // this part MUST use prydon cursor
140                         if (ent.cursor_trace_ent)                 // client was aiming at someone
141                         if (ent.cursor_trace_ent != ent)         // just to make sure
142                         if (ent.cursor_trace_ent.takedamage)      // and that person is killable
143                         if (IS_PLAYER(ent.cursor_trace_ent)) // and actually a player
144                         {
145                                 // verify that the shot would miss without antilag
146                                 // (avoids an issue where guns would always shoot at their origin)
147                                 traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
148                                 if (!trace_ent.takedamage)
149                                 {
150                                         // verify that the shot would hit if altered
151                                         traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
152                                         if (trace_ent == ent.cursor_trace_ent)
153                                                 w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
154                                         else
155                                                 print("antilag fail\n");
156                                 }
157                         }
158                 }
159         }
160
161         ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
162
163         if (!g_norecoil)
164                 ent.punchangle_x = recoil * -1;
165
166         if (snd != "")
167         {
168                 sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
169                 W_PlayStrengthSound(ent);
170         }
171
172         // nudge w_shotend so a trace to w_shotend hits
173         w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
174 }
175
176 #define W_SetupShot_Dir_ProjectileSize(ent,s_forward,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize_Range(ent, s_forward, mi, ma, antilag, recoil, snd, chan, maxdamage, MAX_SHOT_DISTANCE)
177 #define W_SetupShot_ProjectileSize(ent,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, v_forward, mi, ma, antilag, recoil, snd, chan, maxdamage)
178 #define W_SetupShot_Dir(ent,s_forward,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, s_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
179 #define W_SetupShot(ent,antilag,recoil,snd,chan,maxdamage) W_SetupShot_ProjectileSize(ent, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
180 #define W_SetupShot_Range(ent,antilag,recoil,snd,chan,maxdamage,range) W_SetupShot_Dir_ProjectileSize_Range(ent, v_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage, range)
181
182 float CL_Weaponentity_CustomizeEntityForClient()
183 {
184         self.viewmodelforclient = self.owner;
185         if(IS_SPEC(other))
186                 if(other.enemy == self.owner)
187                         self.viewmodelforclient = other;
188         return TRUE;
189 }
190
191 /*
192  * supported formats:
193  *
194  * 1. simple animated model, muzzle flash handling on h_ model:
195  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
196  *      tags:
197  *        shot = muzzle end (shot origin, also used for muzzle flashes)
198  *        shell = casings ejection point (must be on the right hand side of the gun)
199  *        weapon = attachment for v_tuba.md3
200  *    v_tuba.md3 - first and third person model
201  *    g_tuba.md3 - pickup model
202  *
203  * 2. simple animated model, muzzle flash handling on v_ model:
204  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
205  *      tags:
206  *        weapon = attachment for v_tuba.md3
207  *    v_tuba.md3 - first and third person model
208  *      tags:
209  *        shot = muzzle end (shot origin, also used for muzzle flashes)
210  *        shell = casings ejection point (must be on the right hand side of the gun)
211  *    g_tuba.md3 - pickup model
212  *
213  * 3. fully animated model, muzzle flash handling on h_ model:
214  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
215  *      tags:
216  *        shot = muzzle end (shot origin, also used for muzzle flashes)
217  *        shell = casings ejection point (must be on the right hand side of the gun)
218  *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
219  *    v_tuba.md3 - third person model
220  *    g_tuba.md3 - pickup model
221  *
222  * 4. fully animated model, muzzle flash handling on v_ model:
223  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
224  *      tags:
225  *        shot = muzzle end (shot origin)
226  *        shell = casings ejection point (must be on the right hand side of the gun)
227  *    v_tuba.md3 - third person model
228  *      tags:
229  *        shot = muzzle end (for muzzle flashes)
230  *    g_tuba.md3 - pickup model
231  */
232
233 // writes:
234 //   self.origin, self.angles
235 //   self.weaponentity
236 //   self.movedir, self.view_ofs
237 //   attachment stuff
238 //   anim stuff
239 // to free:
240 //   call again with ""
241 //   remove the ent
242 void CL_WeaponEntity_SetModel(string name)
243 {
244         float v_shot_idx;
245         if (name != "")
246         {
247                 // if there is a child entity, hide it until we're sure we use it
248                 if (self.weaponentity)
249                         self.weaponentity.model = "";
250                 setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
251                 v_shot_idx = gettagindex(self, "shot"); // used later
252                 if(!v_shot_idx)
253                         v_shot_idx = gettagindex(self, "tag_shot");
254
255                 setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
256                 // preset some defaults that work great for renamed zym files (which don't need an animinfo)
257                 self.anim_fire1  = animfixfps(self, '0 1 0.01', '0 0 0');
258                 self.anim_fire2  = animfixfps(self, '1 1 0.01', '0 0 0');
259                 self.anim_idle   = animfixfps(self, '2 1 0.01', '0 0 0');
260                 self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
261
262                 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
263                 // if we don't, this is a "real" animated model
264                 if(gettagindex(self, "weapon"))
265                 {
266                         if (!self.weaponentity)
267                                 self.weaponentity = spawn();
268                         setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
269                         setattachment(self.weaponentity, self, "weapon");
270                 }
271                 else if(gettagindex(self, "tag_weapon"))
272                 {
273                         if (!self.weaponentity)
274                                 self.weaponentity = spawn();
275                         setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
276                         setattachment(self.weaponentity, self, "tag_weapon");
277                 }
278                 else
279                 {
280                         if(self.weaponentity)
281                                 remove(self.weaponentity);
282                         self.weaponentity = world;
283                 }
284
285                 setorigin(self,'0 0 0');
286                 self.angles = '0 0 0';
287                 self.frame = 0;
288                 self.viewmodelforclient = world;
289
290                 float idx;
291
292                 if(v_shot_idx) // v_ model attached to invisible h_ model
293                 {
294                         self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
295                 }
296                 else
297                 {
298                         idx = gettagindex(self, "shot");
299                         if(!idx)
300                                 idx = gettagindex(self, "tag_shot");
301                         if(idx)
302                                 self.movedir = gettaginfo(self, idx);
303                         else
304                         {
305                                 print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
306                                 self.movedir = '0 0 0';
307                         }
308                 }
309
310                 if(self.weaponentity) // v_ model attached to invisible h_ model
311                 {
312                         idx = gettagindex(self.weaponentity, "shell");
313                         if(!idx)
314                                 idx = gettagindex(self.weaponentity, "tag_shell");
315                         if(idx)
316                                 self.spawnorigin = gettaginfo(self.weaponentity, idx);
317                 }
318                 else
319                         idx = 0;
320                 if(!idx)
321                 {
322                         idx = gettagindex(self, "shell");
323                         if(!idx)
324                                 idx = gettagindex(self, "tag_shell");
325                         if(idx)
326                                 self.spawnorigin = gettaginfo(self, idx);
327                         else
328                         {
329                                 print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
330                                 self.spawnorigin = self.movedir;
331                         }
332                 }
333
334                 if(v_shot_idx)
335                 {
336                         self.oldorigin = '0 0 0'; // use regular attachment
337                 }
338                 else
339                 {
340                         if(self.weaponentity)
341                         {
342                                 idx = gettagindex(self, "weapon");
343                                 if(!idx)
344                                         idx = gettagindex(self, "tag_weapon");
345                         }
346                         else
347                         {
348                                 idx = gettagindex(self, "handle");
349                                 if(!idx)
350                                         idx = gettagindex(self, "tag_handle");
351                         }
352                         if(idx)
353                         {
354                                 self.oldorigin = self.movedir - gettaginfo(self, idx);
355                         }
356                         else
357                         {
358                                 print("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");
359                                 self.oldorigin = '0 0 0'; // there is no way to recover from this
360                         }
361                 }
362
363                 self.viewmodelforclient = self.owner;
364         }
365         else
366         {
367                 self.model = "";
368                 if(self.weaponentity)
369                         remove(self.weaponentity);
370                 self.weaponentity = world;
371                 self.movedir = '0 0 0';
372                 self.spawnorigin = '0 0 0';
373                 self.oldorigin = '0 0 0';
374                 self.anim_fire1  = '0 1 0.01';
375                 self.anim_fire2  = '0 1 0.01';
376                 self.anim_idle   = '0 1 0.01';
377                 self.anim_reload = '0 1 0.01';
378         }
379
380         self.view_ofs = '0 0 0';
381
382         if(self.movedir_x >= 0)
383         {
384                 vector v0;
385                 v0 = self.movedir;
386                 self.movedir = shotorg_adjust(v0, FALSE, FALSE);
387                 self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
388         }
389         self.owner.stat_shotorg = compressShotOrigin(self.movedir);
390         self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
391
392         self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
393
394         // check if an instant weapon switch occurred
395         setorigin(self, self.view_ofs);
396         // reset animstate now
397         self.wframe = WFRAME_IDLE;
398         setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
399 }
400
401 vector CL_Weapon_GetShotOrg(float wpn)
402 {
403         entity wi, oldself;
404         vector ret;
405         wi = get_weaponinfo(wpn);
406         oldself = self;
407         self = spawn();
408         CL_WeaponEntity_SetModel(wi.mdl);
409         ret = self.movedir;
410         CL_WeaponEntity_SetModel("");
411         remove(self);
412         self = oldself;
413         return ret;
414 }
415
416 void CL_Weaponentity_Think()
417 {
418         float tb;
419         self.nextthink = time;
420         if (intermission_running)
421                 self.frame = self.anim_idle_x;
422         if (self.owner.weaponentity != self)
423         {
424                 if (self.weaponentity)
425                         remove(self.weaponentity);
426                 remove(self);
427                 return;
428         }
429         if (self.owner.deadflag != DEAD_NO)
430         {
431                 self.model = "";
432                 if (self.weaponentity)
433                         self.weaponentity.model = "";
434                 return;
435         }
436         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
437         {
438                 self.weaponname = self.owner.weaponname;
439                 self.dmg = self.owner.modelindex;
440                 self.deadflag = self.owner.deadflag;
441
442                 CL_WeaponEntity_SetModel(self.owner.weaponname);
443         }
444
445         tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
446         self.effects = self.owner.effects & EFMASK_CHEAP;
447         self.effects &~= EF_LOWPRECISION;
448         self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it
449         self.effects &~= EF_TELEPORT_BIT;
450         self.effects &~= EF_RESTARTANIM_BIT;
451         self.effects |= tb;
452
453         if(self.owner.alpha == default_player_alpha)
454                 self.alpha = default_weapon_alpha;
455         else if(self.owner.alpha != 0)
456                 self.alpha = self.owner.alpha;
457         else
458                 self.alpha = 1;
459
460         self.glowmod = self.owner.weaponentity_glowmod;
461         self.colormap = self.owner.colormap;
462         if (self.weaponentity)
463         {
464                 self.weaponentity.effects = self.effects;
465                 self.weaponentity.alpha = self.alpha;
466                 self.weaponentity.colormap = self.colormap;
467                 self.weaponentity.glowmod = self.glowmod;
468         }
469
470         self.angles = '0 0 0';
471         
472         float f = (self.owner.weapon_nextthink - time);
473         if (self.state == WS_RAISE && !intermission_running)
474         {
475                 entity newwep = get_weaponinfo(self.owner.switchweapon);
476                 f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise);
477                 //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), (self.owner.weapon_nextthink - time)));
478                 self.angles_x = -90 * f * f;
479         }
480         else if (self.state == WS_DROP && !intermission_running)
481         {
482                 entity oldwep = get_weaponinfo(self.owner.weapon);
483                 f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop);
484                 //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), (self.owner.weapon_nextthink - time)));
485                 self.angles_x = -90 * f * f;
486         }
487         else if (self.state == WS_CLEAR)
488         {
489                 f = 1;
490                 self.angles_x = -90 * f * f;
491         }
492 }
493
494 void CL_ExteriorWeaponentity_Think()
495 {
496         float tag_found;
497         self.nextthink = time;
498         if (self.owner.exteriorweaponentity != self)
499         {
500                 remove(self);
501                 return;
502         }
503         if (self.owner.deadflag != DEAD_NO)
504         {
505                 self.model = "";
506                 return;
507         }
508         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
509         {
510                 self.weaponname = self.owner.weaponname;
511                 self.dmg = self.owner.modelindex;
512                 self.deadflag = self.owner.deadflag;
513                 if (self.owner.weaponname != "")
514                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
515                 else
516                         self.model = "";
517
518                 if((tag_found = gettagindex(self.owner, "tag_weapon")))
519                 {
520                         self.tag_index = tag_found;
521                         self.tag_entity = self.owner;
522                 }
523                 else
524                         setattachment(self, self.owner, "bip01 r hand");
525         }
526         self.effects = self.owner.effects;
527         self.effects |= EF_LOWPRECISION;
528         self.effects = self.effects & EFMASK_CHEAP; // eat performance
529         if(self.owner.alpha == default_player_alpha)
530                 self.alpha = default_weapon_alpha;
531         else if(self.owner.alpha != 0)
532                 self.alpha = self.owner.alpha;
533         else
534                 self.alpha = 1;
535
536         self.glowmod = self.owner.weaponentity_glowmod;
537         self.colormap = self.owner.colormap;
538
539         CSQCMODEL_AUTOUPDATE();
540 }
541
542 // spawning weaponentity for client
543 void CL_SpawnWeaponentity()
544 {
545         self.weaponentity = spawn();
546         self.weaponentity.classname = "weaponentity";
547         self.weaponentity.solid = SOLID_NOT;
548         self.weaponentity.owner = self;
549         setmodel(self.weaponentity, ""); // precision set when changed
550         setorigin(self.weaponentity, '0 0 0');
551         self.weaponentity.angles = '0 0 0';
552         self.weaponentity.viewmodelforclient = self;
553         self.weaponentity.flags = 0;
554         self.weaponentity.think = CL_Weaponentity_Think;
555         self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
556         self.weaponentity.nextthink = time;
557
558         self.exteriorweaponentity = spawn();
559         self.exteriorweaponentity.classname = "exteriorweaponentity";
560         self.exteriorweaponentity.solid = SOLID_NOT;
561         self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
562         self.exteriorweaponentity.owner = self;
563         setorigin(self.exteriorweaponentity, '0 0 0');
564         self.exteriorweaponentity.angles = '0 0 0';
565         self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
566         self.exteriorweaponentity.nextthink = time;
567
568         {
569                 entity oldself = self;
570                 self = self.exteriorweaponentity;
571                 CSQCMODEL_AUTOINIT();
572                 self = oldself;
573         }
574 }
575
576 // Weapon subs
577 void w_clear()
578 {
579         if (self.weapon != -1)
580         {
581                 self.weapon = 0;
582                 self.switchingweapon = 0;
583         }
584         if (self.weaponentity)
585         {
586                 self.weaponentity.state = WS_CLEAR;
587                 self.weaponentity.effects = 0;
588         }
589 }
590
591 void w_ready()
592 {
593         if (self.weaponentity)
594                 self.weaponentity.state = WS_READY;
595         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
596 }
597
598 .float prevdryfire;
599 .float prevwarntime;
600 float weapon_prepareattack_checkammo(float secondary)
601 {
602         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
603         if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
604         {
605                 // always keep the Mine Layer if we placed mines, so that we can detonate them
606                 entity mine;
607                 if(self.weapon == WEP_MINE_LAYER)
608                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
609                         return FALSE;
610
611                 if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
612                 {
613                         sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM);
614                         self.prevdryfire = time;
615                 }
616
617                 if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
618                 {
619                         if(time - self.prevwarntime > 1)
620                         {
621                                 Send_Notification(
622                                         NOTIF_ONE,
623                                         self,
624                                         MSG_MULTI,
625                                         ITEM_WEAPON_PRIMORSEC,
626                                         self.weapon,
627                                         secondary,
628                                         (1 - secondary)
629                                 );
630                         }
631                         self.prevwarntime = time;
632                 }
633                 else // this weapon is totally unable to fire, switch to another one
634                 {
635                         W_SwitchToOtherWeapon(self);
636                 }
637                 
638                 return FALSE;
639         }
640         return TRUE;
641 }
642 .float race_penalty;
643 float weapon_prepareattack_check(float secondary, float attacktime)
644 {
645         if(!weapon_prepareattack_checkammo(secondary))
646                 return FALSE;
647
648         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
649         //if all players readied up and the countdown is running
650         if(time < game_starttime || time < self.race_penalty) {
651                 return FALSE;
652         }
653
654         if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
655                 return FALSE;
656
657         // do not even think about shooting if switching
658         if(self.switchweapon != self.weapon)
659                 return FALSE;
660
661         if(attacktime >= 0)
662         {
663                 // don't fire if previous attack is not finished
664                 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
665                         return FALSE;
666                 // don't fire while changing weapon
667                 if (self.weaponentity.state != WS_READY)
668                         return FALSE;
669         }
670
671         return TRUE;
672 }
673 float weapon_prepareattack_do(float secondary, float attacktime)
674 {
675         self.weaponentity.state = WS_INUSE;
676
677         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
678
679         // if the weapon hasn't been firing continuously, reset the timer
680         if(attacktime >= 0)
681         {
682                 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
683                 {
684                         ATTACK_FINISHED(self) = time;
685                         //dprint("resetting attack finished to ", ftos(time), "\n");
686                 }
687                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
688         }
689         self.bulletcounter += 1;
690         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
691         return TRUE;
692 }
693 float weapon_prepareattack(float secondary, float attacktime)
694 {
695         if(weapon_prepareattack_check(secondary, attacktime))
696         {
697                 weapon_prepareattack_do(secondary, attacktime);
698                 return TRUE;
699         }
700         else
701                 return FALSE;
702 }
703
704 void weapon_thinkf(float fr, float t, void() func)
705 {
706         vector a;
707         vector of, or, ou;
708         float restartanim;
709
710         if(fr == WFRAME_DONTCHANGE)
711         {
712                 fr = self.weaponentity.wframe;
713                 restartanim = FALSE;
714         }
715         else if (fr == WFRAME_IDLE)
716                 restartanim = FALSE;
717         else
718                 restartanim = TRUE;
719
720         of = v_forward;
721         or = v_right;
722         ou = v_up;
723
724         if (self.weaponentity)
725         {
726                 self.weaponentity.wframe = fr;
727                 a = '0 0 0';
728                 if (fr == WFRAME_IDLE)
729                         a = self.weaponentity.anim_idle;
730                 else if (fr == WFRAME_FIRE1)
731                         a = self.weaponentity.anim_fire1;
732                 else if (fr == WFRAME_FIRE2)
733                         a = self.weaponentity.anim_fire2;
734                 else // if (fr == WFRAME_RELOAD)
735                         a = self.weaponentity.anim_reload;
736                 a_z *= g_weaponratefactor;
737                 setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
738         }
739
740         v_forward = of;
741         v_right = or;
742         v_up = ou;
743
744         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
745         {
746                 backtrace("Tried to override initial weapon think function - should this really happen?");
747         }
748
749         t *= W_WeaponRateFactor();
750
751         // VorteX: haste can be added here
752         if (self.weapon_think == w_ready)
753         {
754                 self.weapon_nextthink = time;
755                 //dprint("started firing at ", ftos(time), "\n");
756         }
757         if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
758         {
759                 self.weapon_nextthink = time;
760                 //dprint("reset weapon animation timer at ", ftos(time), "\n");
761         }
762         self.weapon_nextthink = self.weapon_nextthink + t;
763         self.weapon_think = func;
764         //dprint("next ", ftos(self.weapon_nextthink), "\n");
765
766         if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
767         {
768                 if(self.weapon == WEP_SHOTGUN && fr == WFRAME_FIRE2)
769                         animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
770                 else
771                         animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
772         }
773         else
774         {
775                 if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
776                         self.anim_upper_action = 0;
777         }
778 }
779
780 float forbidWeaponUse()
781 {
782         if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
783                 return 1;
784         if(round_handler_IsActive() && !round_handler_IsRoundStarted())
785                 return 1;
786         if(self.player_blocked)
787                 return 1;
788         if(self.freezetag_frozen)
789                 return 1;
790         return 0;
791 }
792
793 void W_WeaponFrame()
794 {
795         vector fo, ri, up;
796
797         if (frametime)
798                 self.weapon_frametime = frametime;
799
800         if (!self.weaponentity || self.health < 1)
801                 return; // Dead player can't use weapons and injure impulse commands
802
803         if(forbidWeaponUse())
804         if(self.weaponentity.state != WS_CLEAR)
805         {
806                 w_ready();
807                 return;
808         }
809
810         if(!self.switchweapon)
811         {
812                 self.weapon = 0;
813                 self.switchingweapon = 0;
814                 self.weaponentity.state = WS_CLEAR;
815                 self.weaponname = "";
816                 self.items &~= IT_AMMO;
817                 return;
818         }
819
820         makevectors(self.v_angle);
821         fo = v_forward; // save them in case the weapon think functions change it
822         ri = v_right;
823         up = v_up;
824
825         // Change weapon
826         if (self.weapon != self.switchweapon)
827         {
828                 if (self.weaponentity.state == WS_CLEAR)
829                 {
830                         // end switching!
831                         self.switchingweapon = self.switchweapon;
832                         entity newwep = get_weaponinfo(self.switchweapon);
833
834                         self.items &~= IT_AMMO;
835                         self.items = self.items | (newwep.items & IT_AMMO);
836
837                         // the two weapon entities will notice this has changed and update their models
838                         self.weapon = self.switchweapon;
839                         self.weaponname = newwep.mdl;
840                         self.bulletcounter = 0; // WEAPONTODO
841                         WEP_ACTION(self.switchweapon, WR_SETUP);
842                         self.weaponentity.state = WS_RAISE;
843
844                         // set our clip load to the load of the weapon we switched to, if it's reloadable
845                         if(newwep.spawnflags & WEP_FLAG_RELOADABLE && cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"))) // prevent accessing undefined cvars
846                         {
847                                 self.clip_load = self.(weapon_load[self.switchweapon]);
848                                 self.clip_size = cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"));
849                         }
850                         else
851                                 self.clip_load = self.clip_size = 0;
852
853                         // VorteX: add player model weapon select frame here
854                         // setcustomframe(PlayerWeaponRaise);
855                         weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
856                         //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname))));
857                         weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, '0 0 0');
858                 }
859                 else if (self.weaponentity.state == WS_DROP)
860                 {
861                         // in dropping phase we can switch at any time
862                         self.switchingweapon = self.switchweapon;
863                 }
864                 else if (self.weaponentity.state == WS_READY)
865                 {
866                         // start switching!
867                         self.switchingweapon = self.switchweapon;
868
869                         entity oldwep = get_weaponinfo(self.weapon);
870                         
871 #ifndef INDEPENDENT_ATTACK_FINISHED
872                         if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
873                         {
874 #endif
875                         sound (self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
876                         self.weaponentity.state = WS_DROP;
877                         // set up weapon switch think in the future, and start drop anim
878                         weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
879                         //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname))));
880                         weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, PLAYER_WEAPONSELECTION_RANGE);
881 #ifndef INDEPENDENT_ATTACK_FINISHED
882                         }
883 #endif
884                 }
885         }
886
887         // LordHavoc: network timing test code
888         //if (self.button0)
889         //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
890
891         float w;
892         w = self.weapon;
893
894         // call the think code which may fire the weapon
895         // and do so multiple times to resolve framerate dependency issues if the
896         // server framerate is very low and the weapon fire rate very high
897         float c;
898         c = 0;
899         while (c < W_TICSPERFRAME)
900         {
901                 c = c + 1;
902                 if(w && !WEPSET_CONTAINS_EW(self, w))
903                 {
904                         if(self.weapon == self.switchweapon)
905                                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
906                         w = 0;
907                 }
908
909                 v_forward = fo;
910                 v_right = ri;
911                 v_up = up;
912
913                 if(w)
914                         WEP_ACTION(self.weapon, WR_THINK);
915                 else
916                         WEP_ACTION(self.weapon, WR_GONETHINK);
917
918                 if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
919                 {
920                         if(self.weapon_think)
921                         {
922                                 v_forward = fo;
923                                 v_right = ri;
924                                 v_up = up;
925                                 self.weapon_think();
926                         }
927                         else
928                                 bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
929                 }
930         }
931
932 #if 0
933         if (self.items & IT_CELLS)
934                 self.currentammo = self.ammo_cells;
935         else if (self.items & IT_ROCKETS)
936                 self.currentammo = self.ammo_rockets;
937         else if (self.items & IT_NAILS)
938                 self.currentammo = self.ammo_nails;
939         else if (self.items & IT_SHELLS)
940                 self.currentammo = self.ammo_shells;
941         else
942                 self.currentammo = 1;
943 #endif
944 }
945
946 void weapon_boblayer1(float spd, vector org)
947 {
948         // VorteX: haste can be added here
949 }
950
951 vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity, float forceAbsolute)
952 {
953         vector mdirection;
954         float mspeed;
955         vector outvelocity;
956
957         mvelocity = mvelocity * g_weaponspeedfactor;
958
959         mdirection = normalize(mvelocity);
960         mspeed = vlen(mvelocity);
961
962         outvelocity = get_shotvelocity(pvelocity, mdirection, mspeed, (forceAbsolute ? 0 : autocvar_g_projectiles_newton_style), autocvar_g_projectiles_newton_style_2_minfactor, autocvar_g_projectiles_newton_style_2_maxfactor);
963
964         return outvelocity;
965 }
966
967 void W_AttachToShotorg(entity flash, vector offset)
968 {
969         entity xflash;
970         flash.owner = self;
971         flash.angles_z = random() * 360;
972
973         if(gettagindex(self.weaponentity, "shot"))
974                 setattachment(flash, self.weaponentity, "shot");
975         else
976                 setattachment(flash, self.weaponentity, "tag_shot");
977         setorigin(flash, offset);
978
979         xflash = spawn();
980         copyentity(flash, xflash);
981
982         flash.viewmodelforclient = self;
983
984         if(self.weaponentity.oldorigin_x > 0)
985         {
986                 setattachment(xflash, self.exteriorweaponentity, "");
987                 setorigin(xflash, self.weaponentity.oldorigin + offset);
988         }
989         else
990         {
991                 if(gettagindex(self.exteriorweaponentity, "shot"))
992                         setattachment(xflash, self.exteriorweaponentity, "shot");
993                 else
994                         setattachment(xflash, self.exteriorweaponentity, "tag_shot");
995                 setorigin(xflash, offset);
996         }
997 }
998
999 #if 0
1000 float mspercallsum;
1001 float mspercallsstyle;
1002 float mspercallcount;
1003 #endif
1004 void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
1005 {
1006         if(missile.owner == world)
1007                 error("Unowned missile");
1008
1009         dir = dir + upDir * (pUpSpeed / pSpeed);
1010         dir_z += pZSpeed / pSpeed;
1011         pSpeed *= vlen(dir);
1012         dir = normalize(dir);
1013
1014 #if 0
1015         if(autocvar_g_projectiles_spread_style != mspercallsstyle)
1016         {
1017                 mspercallsum = mspercallcount = 0;
1018                 mspercallsstyle = autocvar_g_projectiles_spread_style;
1019         }
1020         mspercallsum -= gettime(GETTIME_HIRES);
1021 #endif
1022         dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
1023 #if 0
1024         mspercallsum += gettime(GETTIME_HIRES);
1025         mspercallcount += 1;
1026         print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
1027 #endif
1028
1029         missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir, forceAbsolute);
1030 }
1031
1032 void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)
1033 {
1034         W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread, FALSE);
1035 }
1036
1037 #define W_SETUPPROJECTILEVELOCITY_UP(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), cvar(#s "_speed_up"), cvar(#s "_speed_z"), cvar(#s "_spread"), FALSE)
1038 #define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"), FALSE)
1039
1040 void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload) // WEAPONTODO: why does this have ammo_type?
1041 {
1042         if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
1043                 return;
1044
1045         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
1046         if(ammo_reload)
1047         {
1048                 self.clip_load -= ammo_use;
1049                 self.(weapon_load[self.weapon]) = self.clip_load;
1050         }
1051         else
1052                 self.(self.current_ammo) -= ammo_use;
1053 }
1054
1055 // weapon reloading code
1056
1057 .float reload_ammo_amount, reload_ammo_min, reload_time;
1058 .float reload_complain;
1059 .string reload_sound;
1060
1061 void W_ReloadedAndReady()
1062 {
1063         // finish the reloading process, and do the ammo transfer
1064
1065         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
1066
1067         // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
1068         if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)
1069                 self.clip_load = self.reload_ammo_amount;
1070         else
1071         {
1072                 while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have
1073                 {
1074                         self.clip_load += 1;
1075                         self.(self.current_ammo) -= 1;
1076                 }
1077         }
1078         self.(weapon_load[self.weapon]) = self.clip_load;
1079
1080         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
1081         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
1082         // so your weapon is disabled for a few seconds without reason
1083
1084         //ATTACK_FINISHED(self) -= self.reload_time - 1;
1085
1086         w_ready();
1087 }
1088
1089 void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)
1090 {
1091         // set global values to work with
1092
1093         self.reload_ammo_min = sent_ammo_min;
1094         self.reload_ammo_amount = sent_ammo_amount;
1095         self.reload_time = sent_time;
1096         self.reload_sound = sent_sound;
1097
1098         // check if we meet the necessary conditions to reload
1099
1100         entity e;
1101         e = get_weaponinfo(self.weapon);
1102
1103         // don't reload weapons that don't have the RELOADABLE flag
1104         if not(e.spawnflags & WEP_FLAG_RELOADABLE)
1105         {
1106                 dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
1107                 return;
1108         }
1109
1110         // return if reloading is disabled for this weapon
1111         if(!self.reload_ammo_amount)
1112                 return;
1113
1114         // our weapon is fully loaded, no need to reload
1115         if (self.clip_load >= self.reload_ammo_amount)
1116                 return;
1117
1118         // no ammo, so nothing to load
1119         if(!self.(self.current_ammo) && self.reload_ammo_min)
1120         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
1121         {
1122                 if(IS_REAL_CLIENT(self) && self.reload_complain < time)
1123                 {
1124                         play2(self, "weapons/unavailable.wav");
1125                         sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
1126                         self.reload_complain = time + 1;
1127                 }
1128                 // switch away if the amount of ammo is not enough to keep using this weapon
1129                 if not(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))
1130                 {
1131                         self.clip_load = -1; // reload later
1132                         W_SwitchToOtherWeapon(self);
1133                 }
1134                 return;
1135         }
1136
1137         if (self.weaponentity)
1138         {
1139                 if (self.weaponentity.wframe == WFRAME_RELOAD)
1140                         return;
1141
1142                 // allow switching away while reloading, but this will cause a new reload!
1143                 self.weaponentity.state = WS_READY;
1144         }
1145
1146         // now begin the reloading process
1147
1148         sound (self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTN_NORM);
1149
1150         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
1151         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
1152         // so your weapon is disabled for a few seconds without reason
1153
1154         //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
1155
1156         weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
1157
1158         if(self.clip_load < 0)
1159                 self.clip_load = 0;
1160         self.old_clip_load = self.clip_load;
1161         self.clip_load = self.(weapon_load[self.weapon]) = -1;
1162 }