Remove old bullet_counter, left from the Nexuiz weapons
[voretournament/voretournament.git] / data / qcsrc / server / cl_weaponsystem.qc
1 /*\r
2 ===========================================================================\r
3 \r
4   CLIENT WEAPONSYSTEM CODE\r
5   Bring back W_Weaponframe\r
6 \r
7 ===========================================================================\r
8 */\r
9 \r
10 .float weapon_frametime;\r
11 \r
12 float W_WeaponRateFactor()\r
13 {\r
14         float t;\r
15         t = 1.0 / g_weaponratefactor;\r
16 \r
17         return t;\r
18 }\r
19 \r
20 void W_SwitchWeapon_Force(entity e, float w)\r
21 {\r
22         e.cnt = e.switchweapon;\r
23         e.switchweapon = w;\r
24 }\r
25 \r
26 .float antilag_debug;\r
27 \r
28 // VorteX: static frame globals\r
29 float WFRAME_DONTCHANGE = -1;\r
30 float WFRAME_FIRE1 = 0;\r
31 float WFRAME_FIRE2 = 1;\r
32 float WFRAME_IDLE = 2;\r
33 float WFRAME_RELOAD = 3;\r
34 .float wframe;\r
35 \r
36 void(float fr, float t, void() func) weapon_thinkf;\r
37 \r
38 vector W_HitPlotUnnormalizedUntransform(vector screenforward, vector screenright, vector screenup, vector v)\r
39 {\r
40         vector ret;\r
41         ret_x = screenright * v;\r
42         ret_y = screenup * v;\r
43         ret_z = screenforward * v;\r
44         return ret;\r
45 }\r
46 \r
47 vector W_HitPlotNormalizedUntransform(vector org, entity targ, vector screenforward, vector screenright, vector screenup, vector v)\r
48 {\r
49         float i, j, k;\r
50         vector mi, ma, thisv, myv, ret;\r
51 \r
52         myv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, org);\r
53 \r
54         // x = 0..1 relative to hitbox; y = 0..1 relative to hitbox; z = distance\r
55 \r
56         for(i = 0; i < 2; ++i) for(j = 0; j < 2; ++j) for(k = 0; k < 2; ++k)\r
57         {\r
58                 thisv = targ.origin;\r
59                 if(i) thisv_x += targ.maxs_x; else thisv_x += targ.mins_x;\r
60                 if(j) thisv_y += targ.maxs_y; else thisv_y += targ.mins_y;\r
61                 if(k) thisv_z += targ.maxs_z; else thisv_z += targ.mins_z;\r
62                 thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, thisv);\r
63                 if(i || j || k)\r
64                 {\r
65                         if(mi_x > thisv_x) mi_x = thisv_x; if(ma_x < thisv_x) ma_x = thisv_x;\r
66                         if(mi_y > thisv_y) mi_y = thisv_y; if(ma_y < thisv_y) ma_y = thisv_y;\r
67                         //if(mi_z > thisv_z) mi_z = thisv_z; if(ma_z < thisv_z) ma_y = thisv_z;\r
68                 }\r
69                 else\r
70                 {\r
71                         // first run\r
72                         mi = ma = thisv;\r
73                 }\r
74         }\r
75 \r
76         thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, v);\r
77         ret_x = (thisv_x - mi_x) / (ma_x - mi_x);\r
78         ret_y = (thisv_y - mi_y) / (ma_y - mi_y);\r
79         ret_z = thisv_z - myv_z;\r
80         return ret;\r
81 }\r
82 \r
83 void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright, vector screenup)\r
84 {\r
85         vector hitplot;\r
86         vector org;\r
87         float lag;\r
88 \r
89         if(player.hitplotfh >= 0)\r
90         {\r
91                 lag = ANTILAG_LATENCY(player);\r
92                 if(lag < 0.001)\r
93                         lag = 0;\r
94                 if(clienttype(player) != CLIENTTYPE_REAL)\r
95                         lag = 0; // only antilag for clients\r
96 \r
97                 org = player.origin + player.view_ofs;\r
98                 traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag);\r
99                 if(trace_ent.flags & FL_CLIENT)\r
100                 {\r
101                         antilag_takeback(trace_ent, time - lag);\r
102                         hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos);\r
103                         antilag_restore(trace_ent);\r
104                         fputs(player.hitplotfh, strcat(ftos(hitplot_x), " ", ftos(hitplot_y), " ", ftos(hitplot_z), " ", ftos(player.switchweapon), "\n"));\r
105                         //print(strcat(ftos(hitplot_x), " ", ftos(hitplot_y), " ", ftos(hitplot_z), "\n"));\r
106                 }\r
107         }\r
108 }\r
109 \r
110 vector w_shotorg;\r
111 vector w_shotdir;\r
112 \r
113 // this function calculates w_shotorg and w_shotdir based on the weapon model\r
114 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.\r
115 // make sure you call makevectors first (FIXME?)\r
116 void W_SetupShot_Dir_ProjectileSize(entity ent, vector s_forward, vector mi, vector ma, float antilag, float recoil, string snd, float maxdamage)\r
117 {\r
118         float nudge = 1; // added to traceline target and subtracted from result\r
119         local vector trueaimpoint;\r
120         local float oldsolid;\r
121         vector vecs, dv;\r
122         oldsolid = ent.dphitcontentsmask;\r
123         ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;\r
124         if(antilag)\r
125                 WarpZone_traceline_antilag(world, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));\r
126                 // passing world, because we do NOT want it to touch dphitcontentsmask\r
127         else\r
128                 WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * MAX_SHOT_DISTANCE, MOVE_NOMONSTERS, ent);\r
129         ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;\r
130 \r
131         vector vf, vr, vu;\r
132         vf = v_forward;\r
133         vr = v_right;\r
134         vu = v_up;\r
135         trueaimpoint = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support\r
136         v_forward = vf;\r
137         v_right = vr;\r
138         v_up = vu;\r
139 \r
140         // track max damage\r
141         if not(inWarmupStage) {\r
142                 entity w;\r
143                 w = get_weaponinfo(ent.weapon);\r
144                 if(w.spawnflags & WEP_TYPE_SPLASH) {  // splash damage\r
145                         ent.stats_fired[ent.weapon - 1] += maxdamage;\r
146                         ent.stat_fired = ent.weapon + 64 * floor(ent.stats_fired[ent.weapon - 1]);\r
147                 }\r
148         }\r
149 \r
150         W_HitPlotAnalysis(ent, v_forward, v_right, v_up);\r
151 \r
152         if(ent.weaponentity.movedir_x > 0)\r
153         {\r
154                 vecs = ent.weaponentity.movedir;\r
155                 vecs_y = -vecs_y;\r
156         }\r
157         else\r
158                 vecs = '0 0 0';\r
159 \r
160         if(debug_shotorg != '0 0 0')\r
161                 vecs = debug_shotorg;\r
162 \r
163         dv = v_right * vecs_y + v_up * vecs_z;\r
164         w_shotorg = ent.origin + ent.view_ofs + dv;\r
165 \r
166         // now move the shotorg forward as much as requested if possible\r
167         if(antilag)\r
168         {\r
169                 if(ent.antilag_debug)\r
170                         tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ent.antilag_debug);\r
171                 else\r
172                         tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));\r
173         }\r
174         else\r
175                 tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent);\r
176         w_shotorg = trace_endpos - v_forward * nudge;\r
177         // calculate the shotdir from the chosen shotorg\r
178         w_shotdir = normalize(trueaimpoint - w_shotorg);\r
179 \r
180         if (antilag)\r
181         if (!ent.cvar_cl_noantilag)\r
182         {\r
183                 if (cvar("g_antilag") == 1) // switch to "ghost" if not hitting original\r
184                 {\r
185                         traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent);\r
186                         if (!trace_ent.takedamage)\r
187                         {\r
188                                 traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));\r
189                                 if (trace_ent.takedamage && trace_ent.classname == "player")\r
190                                 {\r
191                                         entity e;\r
192                                         e = trace_ent;\r
193                                         traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);\r
194                                         if(trace_ent == e)\r
195                                                 w_shotdir = normalize(trace_ent.origin - w_shotorg);\r
196                                 }\r
197                         }\r
198                 }\r
199                 else if(cvar("g_antilag") == 3) // client side hitscan\r
200                 {\r
201                         // this part MUST use prydon cursor\r
202                         if (ent.cursor_trace_ent)                 // client was aiming at someone\r
203                         if (ent.cursor_trace_ent != ent)         // just to make sure\r
204                         if (ent.cursor_trace_ent.takedamage)      // and that person is killable\r
205                         if (ent.cursor_trace_ent.classname == "player") // and actually a player\r
206                         {\r
207                                 // verify that the shot would miss without antilag\r
208                                 // (avoids an issue where guns would always shoot at their origin)\r
209                                 traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent);\r
210                                 if (!trace_ent.takedamage)\r
211                                 {\r
212                                         // verify that the shot would hit if altered\r
213                                         traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);\r
214                                         if (trace_ent == ent.cursor_trace_ent)\r
215                                                 w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);\r
216                                         else\r
217                                                 print("antilag fail\n");\r
218                                 }\r
219                         }\r
220                 }\r
221         }\r
222 \r
223         ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)\r
224 \r
225         if (!g_norecoil)\r
226                 ent.punchangle_x = recoil * -1;\r
227 \r
228         if (snd != "")\r
229         {\r
230                 sound (ent, CHAN_WEAPON, snd, VOL_BASE, ATTN_NORM);\r
231         }\r
232 \r
233         if (ent.items & IT_STRENGTH)\r
234         sound (ent, CHAN_AUTO, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);\r
235 };\r
236 \r
237 #define W_SetupShot_ProjectileSize(ent,mi,ma,antilag,recoil,snd,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, v_forward, mi, ma, antilag, recoil, snd, maxdamage)\r
238 #define W_SetupShot_Dir(ent,s_forward,antilag,recoil,snd,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, s_forward, '0 0 0', '0 0 0', antilag, recoil, snd, maxdamage)\r
239 #define W_SetupShot(ent,antilag,recoil,snd,maxdamage) W_SetupShot_ProjectileSize(ent, '0 0 0', '0 0 0', antilag, recoil, snd, maxdamage)\r
240 \r
241 .string weaponname;\r
242 \r
243 float CL_Weaponentity_CustomizeEntityForClient()\r
244 {\r
245         self.viewmodelforclient = self.owner;\r
246         if(other.classname == "spectator")\r
247                 if(other.enemy == self.owner)\r
248                         self.viewmodelforclient = other;\r
249         return TRUE;\r
250 }\r
251 \r
252 float CL_ExteriorWeaponentity_CustomizeEntityForClient()\r
253 {\r
254         // hide the exterior weapon entity of a predator from their prey\r
255         // otherwise, the stomach model the predator is transformed in (see Client_customizeentityforclient) will have a weapon model attached to it\r
256         if(self.owner.weaponname == "")\r
257                 return TRUE;\r
258         if(other.cvar_chase_active > 0 || other.classname == "observer") // the classname check prevents a bug\r
259         {\r
260                 setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3"));\r
261                 return TRUE;\r
262         }\r
263         if(other.spectatee_status)\r
264                 other = other.enemy; // also do this for the player we are spectating\r
265         if(other.eater == self.owner)\r
266         {\r
267                 setmodel(self, "");\r
268                 return TRUE;\r
269         }\r
270         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3"));\r
271         return TRUE;\r
272 }\r
273 \r
274 float qcweaponanimation;\r
275 vector weapon_offset = '0 -10 0';\r
276 vector weapon_adjust = '10 0 -15';\r
277 .vector weapon_morph0origin;\r
278 .vector weapon_morph0angles;\r
279 .float  weapon_morph0time;\r
280 .vector weapon_morph1origin;\r
281 .vector weapon_morph1angles;\r
282 .float  weapon_morph1time;\r
283 .vector weapon_morph2origin;\r
284 .vector weapon_morph2angles;\r
285 .float  weapon_morph2time;\r
286 .vector weapon_morph3origin;\r
287 .vector weapon_morph3angles;\r
288 .float  weapon_morph3time;\r
289 .vector weapon_morph4origin;\r
290 .vector weapon_morph4angles;\r
291 .float  weapon_morph4time;\r
292 #define QCWEAPONANIMATION_ORIGIN(e) ((weapon_offset_x + e.view_ofs_x) * v_forward - (weapon_offset_y + e.view_ofs_y) * v_right + (weapon_offset_z + e.view_ofs_z) * v_up + weapon_adjust)\r
293 \r
294 /*\r
295  * supported formats:\r
296  *\r
297  * 1. simple animated model, muzzle flash handling on h_ model:\r
298  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation\r
299  *      tags:\r
300  *        shot = muzzle end (shot origin, also used for muzzle flashes)\r
301  *        shell = casings ejection point (must be on the right hand side of the gun)\r
302  *        weapon = attachment for v_tuba.md3\r
303  *    v_tuba.md3 - first and third person model\r
304  *    g_tuba.md3 - pickup model\r
305  *\r
306  * 2. simple animated model, muzzle flash handling on v_ model:\r
307  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation\r
308  *      tags:\r
309  *        weapon = attachment for v_tuba.md3\r
310  *    v_tuba.md3 - first and third person model\r
311  *      tags:\r
312  *        shot = muzzle end (shot origin, also used for muzzle flashes)\r
313  *        shell = casings ejection point (must be on the right hand side of the gun)\r
314  *    g_tuba.md3 - pickup model\r
315  *\r
316  * 3. fully animated model, muzzle flash handling on h_ model:\r
317  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model\r
318  *      tags:\r
319  *        shot = muzzle end (shot origin, also used for muzzle flashes)\r
320  *        shell = casings ejection point (must be on the right hand side of the gun)\r
321  *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)\r
322  *    v_tuba.md3 - third person model\r
323  *    g_tuba.md3 - pickup model\r
324  *\r
325  * 4. fully animated model, muzzle flash handling on v_ model:\r
326  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model\r
327  *      tags:\r
328  *        shot = muzzle end (shot origin)\r
329  *        shell = casings ejection point (must be on the right hand side of the gun)\r
330  *    v_tuba.md3 - third person model\r
331  *      tags:\r
332  *        shot = muzzle end (for muzzle flashes)\r
333  *    g_tuba.md3 - pickup model\r
334  */\r
335 \r
336 void CL_Weaponentity_Think()\r
337 {\r
338         float tb, v_shot_idx;\r
339         self.nextthink = time;\r
340         if (intermission_running)\r
341                 self.frame = self.anim_idle_x;\r
342         if (self.owner.weaponentity != self)\r
343         {\r
344                 if (self.weaponentity)\r
345                         remove(self.weaponentity);\r
346                 remove(self);\r
347                 return;\r
348         }\r
349         if (self.owner.deadflag != DEAD_NO)\r
350         {\r
351                 self.model = "";\r
352                 if (self.weaponentity)\r
353                         self.weaponentity.model = "";\r
354                 return;\r
355         }\r
356         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)\r
357         {\r
358                 self.cnt = self.owner.weapon;\r
359                 self.dmg = self.owner.modelindex;\r
360                 self.deadflag = self.owner.deadflag;\r
361 \r
362                 string animfilename;\r
363                 float animfile;\r
364                 if (self.owner.weaponname != "")\r
365                 {\r
366                         // if there is a child entity, hide it until we're sure we use it\r
367                         if (self.weaponentity)\r
368                                 self.weaponentity.model = "";\r
369                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below\r
370                         v_shot_idx = gettagindex(self, "shot"); // used later\r
371                         if(!v_shot_idx)\r
372                                 v_shot_idx = gettagindex(self, "tag_shot");\r
373 \r
374                         if(qcweaponanimation)\r
375                         {\r
376                                 self.angles = '0 0 0';\r
377                                 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
378                                 self.movedir = weapon_offset_x * v_forward - weapon_offset_y * v_right + weapon_offset_z * v_up + weapon_adjust;\r
379                                 self.movedir_x += 32;\r
380                                 self.spawnorigin = self.movedir;\r
381                                 // oldorigin - not calculated here\r
382                         }\r
383                         else\r
384                         {\r
385                                 setmodel(self, strcat("models/weapons/h_", self.owner.weaponname, ".dpm")); // precision set below\r
386                                 animfilename = strcat("models/weapons/h_", self.owner.weaponname, ".dpm.animinfo");\r
387                                 animfile = fopen(animfilename, FILE_READ);\r
388                                 // preset some defaults that work great for renamed zym files (which don't need an animinfo)\r
389                                 self.anim_fire1  = '0 1 0.01';\r
390                                 self.anim_fire2  = '1 1 0.01';\r
391                                 self.anim_idle   = '2 1 0.01';\r
392                                 self.anim_reload = '3 1 0.01';\r
393                                 if (animfile >= 0)\r
394                                 {\r
395                                         animparseerror = FALSE;\r
396                                         self.anim_fire1  = animparseline(animfile);\r
397                                         self.anim_fire2  = animparseline(animfile);\r
398                                         self.anim_idle   = animparseline(animfile);\r
399                                         self.anim_reload = animparseline(animfile);\r
400                                         fclose(animfile);\r
401                                         if (animparseerror)\r
402                                                 print("Parse error in ", animfilename, ", some player animations are broken\n");\r
403                                 }\r
404 \r
405                                 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)\r
406                                 // if we don't, this is a "real" animated model\r
407                                 if(gettagindex(self, "weapon"))\r
408                                 {\r
409                                         if (!self.weaponentity)\r
410                                                 self.weaponentity = spawn();\r
411                                         setmodel(self.weaponentity, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision does not matter\r
412                                         setattachment(self.weaponentity, self, "weapon");\r
413                                 }\r
414                                 else if(gettagindex(self, "tag_weapon"))\r
415                                 {\r
416                                         if (!self.weaponentity)\r
417                                                 self.weaponentity = spawn();\r
418                                         setmodel(self.weaponentity, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision does not matter\r
419                                         setattachment(self.weaponentity, self, "tag_weapon");\r
420                                 }\r
421                                 else\r
422                                 {\r
423                                         if(self.weaponentity)\r
424                                                 remove(self.weaponentity);\r
425                                         self.weaponentity = world;\r
426                                 }\r
427 \r
428                                 setorigin(self,'0 0 0');\r
429                                 self.angles = '0 0 0';\r
430                                 self.frame = 0;\r
431                                 self.viewmodelforclient = world;\r
432 \r
433                                 float idx;\r
434 \r
435                                 if(v_shot_idx) // v_ model attached to invisible h_ model\r
436                                 {\r
437                                         self.movedir = gettaginfo(self.weaponentity, v_shot_idx);\r
438                                 }\r
439                                 else\r
440                                 {\r
441                                         idx = gettagindex(self, "shot");\r
442                                         if(!idx)\r
443                                                 idx = gettagindex(self, "tag_shot");\r
444                                         if(idx)\r
445                                                 self.movedir = gettaginfo(self, idx);\r
446                                         else\r
447                                         {\r
448                                                 print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");\r
449                                                 self.movedir = '0 0 0';\r
450                                         }\r
451                                 }\r
452 \r
453                                 if(self.weaponentity) // v_ model attached to invisible h_ model\r
454                                 {\r
455                                         idx = gettagindex(self.weaponentity, "shell");\r
456                                         if(!idx)\r
457                                                 idx = gettagindex(self.weaponentity, "tag_shell");\r
458                                         if(idx)\r
459                                                 self.spawnorigin = gettaginfo(self.weaponentity, idx);\r
460                                 }\r
461                                 else\r
462                                         idx = 0;\r
463                                 if(!idx)\r
464                                 {\r
465                                         idx = gettagindex(self, "shell");\r
466                                         if(!idx)\r
467                                                 idx = gettagindex(self, "tag_shell");\r
468                                         if(idx)\r
469                                                 self.spawnorigin = gettaginfo(self, idx);\r
470                                         else\r
471                                         {\r
472                                                 print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");\r
473                                                 self.spawnorigin = self.movedir;\r
474                                         }\r
475                                 }\r
476 \r
477                                 if(v_shot_idx)\r
478                                 {\r
479                                         self.oldorigin = '0 0 0'; // use regular attachment\r
480                                 }\r
481                                 else\r
482                                 {\r
483                                         if(self.weaponentity)\r
484                                         {\r
485                                                 idx = gettagindex(self, "weapon");\r
486                                                 if(!idx)\r
487                                                         idx = gettagindex(self, "tag_weapon");\r
488                                         }\r
489                                         else\r
490                                         {\r
491                                                 idx = gettagindex(self, "handle");\r
492                                                 if(!idx)\r
493                                                         idx = gettagindex(self, "tag_handle");\r
494                                         }\r
495                                         if(idx)\r
496                                         {\r
497                                                 self.oldorigin = self.movedir - gettaginfo(self, idx);\r
498                                         }\r
499                                         else\r
500                                         {\r
501                                                 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");\r
502                                                 self.oldorigin = '0 0 0'; // there is no way to recover from this\r
503                                         }\r
504                                 }\r
505 \r
506                                 self.viewmodelforclient = self.owner;\r
507                         }\r
508                 }\r
509                 else\r
510                 {\r
511                         self.model = "";\r
512                         if(self.weaponentity)\r
513                                 remove(self.weaponentity);\r
514                         self.weaponentity = world;\r
515                         self.movedir = '0 0 0';\r
516                         self.spawnorigin = '0 0 0';\r
517                         self.oldorigin = '0 0 0';\r
518                         self.anim_fire1  = '0 1 0.01';\r
519                         self.anim_fire2  = '0 1 0.01';\r
520                         self.anim_idle   = '0 1 0.01';\r
521                         self.anim_reload = '0 1 0.01';\r
522                 }\r
523 \r
524                 self.view_ofs = '0 0 0';\r
525 \r
526                 if(self.movedir_x >= 0)\r
527                 {\r
528                         vector v0;\r
529                         v0 = self.movedir;\r
530                         self.movedir = shotorg_adjust(v0, FALSE, FALSE);\r
531                         self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;\r
532                 }\r
533                 self.owner.stat_shotorg = compressShotOrigin(self.movedir);\r
534                 self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly\r
535 \r
536                 self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount\r
537 \r
538                 // check if an instant weapon switch occurred\r
539                 if (qcweaponanimation)\r
540                 {\r
541                         if (self.state == WS_READY)\r
542                         {\r
543                                 self.angles = '0 0 0';\r
544                                 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
545                                 setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
546                         }\r
547                 }\r
548                 else\r
549                         setorigin(self, self.view_ofs);\r
550                 // reset animstate now\r
551                 self.wframe = WFRAME_IDLE;\r
552                 self.weapon_morph0time = 0;\r
553                 self.weapon_morph1time = 0;\r
554                 self.weapon_morph2time = 0;\r
555                 self.weapon_morph3time = 0;\r
556                 self.weapon_morph4time = 0;\r
557                 setanim(self, self.anim_idle, TRUE, FALSE, TRUE);\r
558         }\r
559 \r
560         tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));\r
561         self.effects = self.owner.effects & EFMASK_CHEAP;\r
562         self.effects &~= EF_LOWPRECISION;\r
563         self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it\r
564         self.effects &~= EF_TELEPORT_BIT;\r
565         self.effects &~= EF_RESTARTANIM_BIT;\r
566         self.effects |= tb;\r
567 \r
568         if(self.owner.alpha == default_player_alpha)\r
569                 self.alpha = default_weapon_alpha;\r
570         else if(self.owner.alpha != 0)\r
571                 self.alpha = self.owner.alpha;\r
572         else\r
573                 self.alpha = 1;\r
574 \r
575         self.colormap = self.owner.colormap;\r
576         if (self.weaponentity)\r
577         {\r
578                 self.weaponentity.effects = self.effects;\r
579                 self.weaponentity.alpha = self.alpha;\r
580                 self.weaponentity.colormap = self.colormap;\r
581                 self.weaponentity.colormod = self.owner.colormod; // used by the regurgitating colors\r
582         }\r
583 \r
584         self.angles = '0 0 0';\r
585         local float f;\r
586         f = 0;\r
587         if (self.state == WS_RAISE && !intermission_running)\r
588         {\r
589                 f = (self.owner.weapon_nextthink - time) * g_weaponratefactor / cvar("g_balance_weaponswitchdelay");\r
590                 self.angles_x = -90 * f * f;\r
591                 if (qcweaponanimation)\r
592                 {\r
593                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
594                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
595                 }\r
596         }\r
597         else if (self.state == WS_DROP && !intermission_running)\r
598         {\r
599                 f = 1 - (self.owner.weapon_nextthink - time) * g_weaponratefactor / cvar("g_balance_weaponswitchdelay");\r
600                 self.angles_x = -90 * f * f;\r
601                 if (qcweaponanimation)\r
602                 {\r
603                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
604                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
605                 }\r
606         }\r
607         else if (self.state == WS_CLEAR)\r
608         {\r
609                 f = 1;\r
610                 self.angles_x = -90 * f * f;\r
611                 if (qcweaponanimation)\r
612                 {\r
613                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
614                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
615                 }\r
616         }\r
617         else if (qcweaponanimation && time < self.owner.weapon_morph1time)\r
618         {\r
619                 f = (time - self.owner.weapon_morph0time) / (self.owner.weapon_morph1time - self.owner.weapon_morph0time);\r
620                 f = 1 - pow(1 - f, 3);\r
621                 self.angles = self.owner.weapon_morph0angles * (1 - f) + self.owner.weapon_morph1angles * f;\r
622                 setorigin(self, self.owner.weapon_morph0origin * (1 - f) + self.owner.weapon_morph1origin * f);\r
623         }\r
624         else if (qcweaponanimation && time < self.owner.weapon_morph2time)\r
625         {\r
626                 f = (time - self.owner.weapon_morph1time) / (self.owner.weapon_morph2time - self.owner.weapon_morph1time);\r
627                 f = 1 - pow(1 - f, 3);\r
628                 self.angles = self.owner.weapon_morph1angles * (1 - f) + self.owner.weapon_morph2angles * f;\r
629                 setorigin(self, self.owner.weapon_morph1origin * (1 - f) + self.owner.weapon_morph2origin * f);\r
630         }\r
631         else if (qcweaponanimation && time < self.owner.weapon_morph3time)\r
632         {\r
633                 f = (time - self.owner.weapon_morph2time) / (self.owner.weapon_morph3time - self.owner.weapon_morph2time);\r
634                 f = 1 - pow(1 - f, 3);\r
635                 self.angles = self.owner.weapon_morph2angles * (1 - f) + self.owner.weapon_morph3angles * f;\r
636                 setorigin(self, self.owner.weapon_morph2origin * (1 - f) + self.owner.weapon_morph3origin * f);\r
637         }\r
638         else if (qcweaponanimation && time < self.owner.weapon_morph4time)\r
639         {\r
640                 f = (time - self.owner.weapon_morph3time) / (self.owner.weapon_morph4time - self.owner.weapon_morph3time);\r
641                 f = 1 - pow(1 - f, 3);\r
642                 self.angles = self.owner.weapon_morph3angles * (1 - f) + self.owner.weapon_morph4angles * f;\r
643                 setorigin(self, self.owner.weapon_morph3origin * (1 - f) + self.owner.weapon_morph4origin * f);\r
644         }\r
645         else if (qcweaponanimation)\r
646         {\r
647                 // begin a new idle morph\r
648                 self.owner.weapon_morph0time   = time;\r
649                 self.owner.weapon_morph0angles = self.angles;\r
650                 self.owner.weapon_morph0origin = self.origin;\r
651 \r
652                 float r;\r
653                 float t;\r
654 \r
655                 r = random();\r
656                 if (r < 0.1)\r
657                 {\r
658                         // turn gun to the left to look at it\r
659                         t = 2;\r
660                         self.owner.weapon_morph1time   = time + t * 0.2;\r
661                         self.owner.weapon_morph1angles = randomvec() * 3 + '-5 30 0';\r
662                         makevectors(self.owner.weapon_morph1angles_x * '-1 0 0' + self.owner.weapon_morph1angles_y * '0 1 0' + self.owner.weapon_morph1angles_z * '0 0 1');\r
663                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
664 \r
665                         self.owner.weapon_morph2time   = time + t * 0.6;\r
666                         self.owner.weapon_morph2angles = randomvec() * 3 + '-5 30 0';\r
667                         makevectors(self.owner.weapon_morph2angles_x * '-1 0 0' + self.owner.weapon_morph2angles_y * '0 1 0' + self.owner.weapon_morph2angles_z * '0 0 1');\r
668                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
669 \r
670                         self.owner.weapon_morph3time   = time + t;\r
671                         self.owner.weapon_morph3angles = '0 0 0';\r
672                         makevectors(self.owner.weapon_morph3angles_x * '-1 0 0' + self.owner.weapon_morph3angles_y * '0 1 0' + self.owner.weapon_morph3angles_z * '0 0 1');\r
673                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
674                 }\r
675                 else if (r < 0.2)\r
676                 {\r
677                         // raise the gun a bit\r
678                         t = 2;\r
679                         self.owner.weapon_morph1time   = time + t * 0.2;\r
680                         self.owner.weapon_morph1angles = randomvec() * 3 + '30 -10 0';\r
681                         makevectors(self.owner.weapon_morph1angles_x * '-1 0 0' + self.owner.weapon_morph1angles_y * '0 1 0' + self.owner.weapon_morph1angles_z * '0 0 1');\r
682                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
683 \r
684                         self.owner.weapon_morph2time   = time + t * 0.5;\r
685                         self.owner.weapon_morph2angles = randomvec() * 3 + '30 -10 5';\r
686                         makevectors(self.owner.weapon_morph2angles_x * '-1 0 0' + self.owner.weapon_morph2angles_y * '0 1 0' + self.owner.weapon_morph2angles_z * '0 0 1');\r
687                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
688 \r
689                         self.owner.weapon_morph3time   = time + t;\r
690                         self.owner.weapon_morph3angles = '0 0 0';\r
691                         makevectors(self.owner.weapon_morph3angles_x * '-1 0 0' + self.owner.weapon_morph3angles_y * '0 1 0' + self.owner.weapon_morph3angles_z * '0 0 1');\r
692                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
693                 }\r
694                 else if (r < 0.3)\r
695                 {\r
696                         // tweak it a bit\r
697                         t = 5;\r
698                         self.owner.weapon_morph1time   = time + t * 0.3;\r
699                         self.owner.weapon_morph1angles = randomvec() * 6;\r
700                         makevectors(self.owner.weapon_morph1angles_x * '-1 0 0' + self.owner.weapon_morph1angles_y * '0 1 0' + self.owner.weapon_morph1angles_z * '0 0 1');\r
701                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
702 \r
703                         self.owner.weapon_morph2time   = time + t * 0.7;\r
704                         self.owner.weapon_morph2angles = randomvec() * 6;\r
705                         makevectors(self.owner.weapon_morph2angles_x * '-1 0 0' + self.owner.weapon_morph2angles_y * '0 1 0' + self.owner.weapon_morph2angles_z * '0 0 1');\r
706                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
707 \r
708                         self.owner.weapon_morph3time   = time + t;\r
709                         self.owner.weapon_morph3angles = '0 0 0';\r
710                         makevectors(self.owner.weapon_morph3angles_x * '-1 0 0' + self.owner.weapon_morph3angles_y * '0 1 0' + self.owner.weapon_morph3angles_z * '0 0 1');\r
711                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
712                 }\r
713                 else\r
714                 {\r
715                         // hold it mostly steady\r
716                         t = random() * 6 + 4;\r
717                         self.owner.weapon_morph1time   = time + t * 0.2;\r
718                         self.owner.weapon_morph1angles = randomvec() * 1;\r
719                         makevectors(self.owner.weapon_morph1angles_x * '-1 0 0' + self.owner.weapon_morph1angles_y * '0 1 0' + self.owner.weapon_morph1angles_z * '0 0 1');\r
720                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
721 \r
722                         self.owner.weapon_morph2time   = time + t * 0.5;\r
723                         self.owner.weapon_morph2angles = randomvec() * 1;\r
724                         makevectors(self.owner.weapon_morph2angles_x * '-1 0 0' + self.owner.weapon_morph2angles_y * '0 1 0' + self.owner.weapon_morph2angles_z * '0 0 1');\r
725                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
726 \r
727                         self.owner.weapon_morph3time   = time + t * 0.7;\r
728                         self.owner.weapon_morph3angles = randomvec() * 1;\r
729                         makevectors(self.owner.weapon_morph3angles_x * '-1 0 0' + self.owner.weapon_morph3angles_y * '0 1 0' + self.owner.weapon_morph3angles_z * '0 0 1');\r
730                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
731                 }\r
732 \r
733                 self.owner.weapon_morph4time   = time + t;\r
734                 self.owner.weapon_morph4angles = '0 0 0';\r
735                 makevectors(self.owner.weapon_morph4angles_x * '-1 0 0' + self.owner.weapon_morph4angles_y * '0 1 0' + self.owner.weapon_morph4angles_z * '0 0 1');\r
736                 self.owner.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self);\r
737 \r
738         }\r
739 \r
740         // create or update the lasertarget entity\r
741 };\r
742 \r
743 void CL_ExteriorWeaponentity_Think()\r
744 {\r
745         float tag_found;\r
746         vector ang;\r
747         self.nextthink = time;\r
748         if (self.owner.exteriorweaponentity != self)\r
749         {\r
750                 remove(self);\r
751                 return;\r
752         }\r
753         if (self.owner.deadflag != DEAD_NO)\r
754         {\r
755                 self.model = "";\r
756                 return;\r
757         }\r
758         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)\r
759         {\r
760                 self.cnt = self.owner.weapon;\r
761                 self.dmg = self.owner.modelindex;\r
762                 self.deadflag = self.owner.deadflag;\r
763                 if (self.owner.weaponname != "")\r
764                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below\r
765                 else\r
766                         self.model = "";\r
767 \r
768                 if((tag_found = gettagindex(self.owner, "tag_weapon")))\r
769                 {\r
770                         self.tag_index = tag_found;\r
771                         self.tag_entity = self.owner;\r
772                 }\r
773                 else\r
774                         setattachment(self, self.owner, "bip01 r hand");\r
775 \r
776                 // if that didn't find a tag, hide the exterior weapon model\r
777                 if (!self.tag_index)\r
778                         self.model = "";\r
779         }\r
780         self.effects = self.owner.effects;\r
781         if(sv_pitch_min == sv_pitch_max)\r
782                 self.effects |= EF_LOWPRECISION;\r
783         else\r
784                 self.effects &~= EF_LOWPRECISION;\r
785         self.effects = self.effects & EFMASK_CHEAP; // eat performance\r
786         if(self.owner.alpha == default_player_alpha)\r
787                 self.alpha = default_weapon_alpha;\r
788         else if(self.owner.alpha != 0)\r
789                 self.alpha = self.owner.alpha;\r
790         else\r
791                 self.alpha = 1;\r
792         self.colormod = self.owner.colormod; // used by the regurgitating colors\r
793 \r
794         ang_x = bound(sv_pitch_min, self.owner.v_angle_x, sv_pitch_max);\r
795         ang_y = 0;\r
796         ang_z = 0;\r
797 \r
798         if(sv_pitch_fixyaw) // workaround for stupid player models that don't aim forward\r
799         {\r
800                 ang_y = self.owner.v_angle_y;\r
801                 makevectors(ang);\r
802                 var vector v = v_forward;\r
803                 var float t = self.tag_entity.frame1time;\r
804                 var float f = self.tag_entity.frame;\r
805                 self.tag_entity.frame1time = time;\r
806                 self.tag_entity.frame = self.tag_entity.anim_idle_x;\r
807                 gettaginfo(self.tag_entity, self.tag_index);\r
808                 self.tag_entity.frame1time = t;\r
809                 self.tag_entity.frame = f;\r
810                 // untransform v according to this coordinate space\r
811                 vector w;\r
812                 w_x = v_forward * v;\r
813                 w_y = -v_right * v;\r
814                 w_z = v_up * v;\r
815                 self.angles = vectoangles(w);\r
816         }\r
817         else\r
818         {\r
819                 ang_x = -/* don't ask */ang_x;\r
820                 self.angles = ang;\r
821         }\r
822 \r
823         self.colormap = self.owner.colormap;\r
824         self.customizeentityforclient = CL_ExteriorWeaponentity_CustomizeEntityForClient;\r
825 };\r
826 \r
827 // spawning weaponentity for client\r
828 void CL_SpawnWeaponentity()\r
829 {\r
830         self.weaponentity = spawn();\r
831         self.weaponentity.classname = "weaponentity";\r
832         self.weaponentity.solid = SOLID_NOT;\r
833         self.weaponentity.owner = self;\r
834         setmodel(self.weaponentity, ""); // precision set when changed\r
835         setorigin(self.weaponentity, '0 0 0');\r
836         self.weaponentity.angles = '0 0 0';\r
837         self.weaponentity.viewmodelforclient = self;\r
838         self.weaponentity.flags = 0;\r
839         self.weaponentity.think = CL_Weaponentity_Think;\r
840         self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;\r
841         self.weaponentity.nextthink = time;\r
842 \r
843         self.exteriorweaponentity = spawn();\r
844         self.exteriorweaponentity.classname = "exteriorweaponentity";\r
845         self.exteriorweaponentity.solid = SOLID_NOT;\r
846         self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;\r
847         self.exteriorweaponentity.owner = self;\r
848         setorigin(self.exteriorweaponentity, '0 0 0');\r
849         self.exteriorweaponentity.angles = '0 0 0';\r
850         self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;\r
851         self.exteriorweaponentity.nextthink = time;\r
852 };\r
853 \r
854 .float hasweapon_complain_spam;\r
855 \r
856 float client_hasweapon(entity cl, float wpn, float andammo, float complain)\r
857 {\r
858         local float weaponbit, f;\r
859         local entity oldself;\r
860 \r
861         if(time < self.hasweapon_complain_spam)\r
862                 complain = 0;\r
863         if(complain)\r
864                 self.hasweapon_complain_spam = time + 0.2;\r
865 \r
866         if (wpn < WEP_FIRST || wpn > WEP_LAST)\r
867         {\r
868                 if (complain)\r
869                         sprint(self, "Invalid weapon\n");\r
870                 return FALSE;\r
871         }\r
872         weaponbit = W_WeaponBit(wpn);\r
873         if (cl.weapons & weaponbit)\r
874         {\r
875                 if (andammo)\r
876                 {\r
877                         if(cl.items & IT_UNLIMITED_WEAPON_AMMO)\r
878                         {\r
879                                 f = 1;\r
880                         }\r
881                         else\r
882                         {\r
883                                 oldself = self;\r
884                                 self = cl;\r
885                                 f = weapon_action(wpn, WR_CHECKAMMO1);\r
886                                 f = f + weapon_action(wpn, WR_CHECKAMMO2);\r
887                                 self = oldself;\r
888                         }\r
889                         if (!f)\r
890                         {\r
891                                 if (complain)\r
892                                 if(clienttype(cl) == CLIENTTYPE_REAL)\r
893                                 {\r
894                                         play2(cl, "weapons/unavailable.wav");\r
895                                         sprint(cl, strcat("You don't have any ammo for the ^2", W_Name(wpn), "\n"));\r
896                                 }\r
897                                 return FALSE;\r
898                         }\r
899                 }\r
900                 return TRUE;\r
901         }\r
902         if (complain)\r
903         {\r
904                 // DRESK - 3/16/07\r
905                 // Report Proper Weapon Status / Modified Weapon Ownership Message\r
906                 if(weaponsInMap & weaponbit)\r
907                 {\r
908                         sprint(cl, strcat("You do not have the ^2", W_Name(wpn), "\n") );\r
909 \r
910                         if(cvar("g_showweaponspawns"))\r
911                         {\r
912                                 entity e;\r
913                                 string s;\r
914 \r
915                                 e = get_weaponinfo(wpn);\r
916                                 s = e.model2;\r
917 \r
918                                 for(e = world; (e = findfloat(e, weapons, weaponbit)); )\r
919                                 {\r
920                                         if(e.classname == "droppedweapon")\r
921                                                 continue;\r
922                                         if not(e.flags & FL_ITEM)\r
923                                                 continue;\r
924                                         WaypointSprite_Spawn(\r
925                                                 s,\r
926                                                 1, 0,\r
927                                                 world, e.origin,\r
928                                                 self, 0,\r
929                                                 world, enemy,\r
930                                                 0\r
931                                         );\r
932                                 }\r
933                         }\r
934                 }\r
935                 else\r
936                         sprint(cl, strcat("The ^2", W_Name(wpn), "^7 is ^1NOT AVAILABLE^7 in this map\n") );\r
937 \r
938                 play2(cl, "weapons/unavailable.wav");\r
939         }\r
940         return FALSE;\r
941 };\r
942 \r
943 // Weapon subs\r
944 void w_clear()\r
945 {\r
946         if (self.weapon != -1)\r
947                 self.weapon = 0;\r
948         if (self.weaponentity)\r
949         {\r
950                 self.weaponentity.state = WS_CLEAR;\r
951                 self.weaponentity.effects = 0;\r
952         }\r
953 };\r
954 \r
955 void w_ready()\r
956 {\r
957         if (self.weaponentity)\r
958                 self.weaponentity.state = WS_READY;\r
959         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);\r
960 };\r
961 \r
962 // Setup weapon for client (after this raise frame will be launched)\r
963 void weapon_setup(float windex)\r
964 {\r
965         entity e;\r
966         qcweaponanimation = cvar("sv_qcweaponanimation");\r
967         e = get_weaponinfo(windex);\r
968         self.items &~= IT_AMMO;\r
969         self.items = self.items | e.items;\r
970 \r
971         // the two weapon entities will notice this has changed and update their models\r
972         self.weapon = windex;\r
973         self.weaponname = e.mdl;\r
974 };\r
975 \r
976 // perform weapon to attack (weaponstate and attack_finished check is here)\r
977 .float race_penalty;\r
978 float weapon_prepareattack(float secondary, float attacktime)\r
979 {\r
980         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot\r
981         //if all players readied up and the countdown is running\r
982         if(time < game_starttime || time < self.race_penalty) {\r
983                 return FALSE;\r
984         }\r
985 \r
986         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)\r
987         if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))\r
988         {\r
989                 // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)\r
990                 float w, ww;\r
991                 w = W_WeaponBit(self.weapon);\r
992                 self.weapons &~= w;\r
993                 ww = w_getbestweapon(self);\r
994                 self.weapons |= w;\r
995                 if(ww)\r
996                         W_SwitchWeapon_Force(self, ww);\r
997                 return FALSE;\r
998         }\r
999 \r
1000         if (timeoutStatus == 2) //don't allow the player to shoot while game is paused\r
1001                 return FALSE;\r
1002 \r
1003         // do not even think about shooting if switching\r
1004         if(self.switchweapon != self.weapon)\r
1005                 return FALSE;\r
1006 \r
1007         if(attacktime >= 0)\r
1008         {\r
1009                 // don't fire if previous attack is not finished\r
1010                 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)\r
1011                         return FALSE;\r
1012                 // don't fire while changing weapon\r
1013                 if (self.weaponentity.state != WS_READY)\r
1014                         return FALSE;\r
1015         }\r
1016         self.weaponentity.state = WS_INUSE;\r
1017 \r
1018         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire\r
1019 \r
1020         // if the weapon hasn't been firing continuously, reset the timer\r
1021         if(attacktime >= 0)\r
1022         {\r
1023                 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)\r
1024                 {\r
1025                         ATTACK_FINISHED(self) = time;\r
1026                         //dprint("resetting attack finished to ", ftos(time), "\n");\r
1027                 }\r
1028                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();\r
1029         }\r
1030         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");\r
1031         return TRUE;\r
1032 };\r
1033 \r
1034 void weapon_thinkf(float fr, float t, void() func)\r
1035 {\r
1036         vector a;\r
1037         vector of, or, ou;\r
1038         float restartanim;\r
1039 \r
1040         if(fr == WFRAME_DONTCHANGE)\r
1041         {\r
1042                 fr = self.weaponentity.wframe;\r
1043                 restartanim = FALSE;\r
1044         }\r
1045         else if (fr == WFRAME_IDLE)\r
1046                 restartanim = FALSE;\r
1047         else\r
1048                 restartanim = TRUE;\r
1049 \r
1050         of = v_forward;\r
1051         or = v_right;\r
1052         ou = v_up;\r
1053 \r
1054         if (self.weaponentity)\r
1055         {\r
1056                 self.weaponentity.wframe = fr;\r
1057                 if (qcweaponanimation)\r
1058                 {\r
1059                         if (fr != WFRAME_IDLE)\r
1060                         {\r
1061                                 self.weapon_morph0time = time;\r
1062                                 self.weapon_morph0angles = self.weaponentity.angles;\r
1063                                 self.weapon_morph0origin = self.weaponentity.origin;\r
1064 \r
1065                                 self.weapon_morph1angles = '0 0 0';\r
1066                                 self.weapon_morph1time = time + t;\r
1067                                 makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1068                                 self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1069 \r
1070                                 self.weapon_morph2angles = '0 0 0';\r
1071                                 self.weapon_morph2time = time + t;\r
1072                                 makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');\r
1073                                 self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1074 \r
1075                                 self.weapon_morph3angles = '0 0 0';\r
1076                                 self.weapon_morph3time = time + t;\r
1077                                 makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');\r
1078                                 self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1079 \r
1080                                 self.weapon_morph4angles = '0 0 0';\r
1081                                 self.weapon_morph4time = time + t;\r
1082                                 makevectors(self.weapon_morph4angles_x * '-1 0 0' + self.weapon_morph4angles_y * '0 1 0' + self.weapon_morph4angles_z * '0 0 1');\r
1083                                 self.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1084 \r
1085                                 if (fr == WFRAME_FIRE1)\r
1086                                 {\r
1087                                         self.weapon_morph1angles = '5 0 0';\r
1088                                         self.weapon_morph1time = time + t * 0.1;\r
1089                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1090                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1091                                         self.weapon_morph4time = time + t + 1; // delay idle effect\r
1092                                 }\r
1093                                 else if (fr == WFRAME_FIRE2)\r
1094                                 {\r
1095                                         self.weapon_morph1angles = '10 0 0';\r
1096                                         self.weapon_morph1time = time + t * 0.1;\r
1097                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1098                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1099                                         self.weapon_morph4time = time + t + 1; // delay idle effect\r
1100                                 }\r
1101                                 else if (fr == WFRAME_RELOAD)\r
1102                                 {\r
1103                                         self.weapon_morph1time = time + t * 0.05;\r
1104                                         self.weapon_morph1angles = '-10 40 0';\r
1105                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1106                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1107 \r
1108                                         self.weapon_morph2time = time + t * 0.15;\r
1109                                         self.weapon_morph2angles = '-10 40 5';\r
1110                                         makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');\r
1111                                         self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1112 \r
1113                                         self.weapon_morph3time = time + t * 0.25;\r
1114                                         self.weapon_morph3angles = '-10 40 0';\r
1115                                         makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');\r
1116                                         self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1117                                 }\r
1118                         }\r
1119                 }\r
1120                 else\r
1121                 {\r
1122                         if (fr == WFRAME_IDLE)\r
1123                                 a = self.weaponentity.anim_idle;\r
1124                         else if (fr == WFRAME_FIRE1)\r
1125                                 a = self.weaponentity.anim_fire1;\r
1126                         else if (fr == WFRAME_FIRE2)\r
1127                                 a = self.weaponentity.anim_fire2;\r
1128                         else if (fr == WFRAME_RELOAD)\r
1129                                 a = self.weaponentity.anim_reload;\r
1130                         a_z *= g_weaponratefactor;\r
1131                         setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);\r
1132                 }\r
1133         }\r
1134 \r
1135         v_forward = of;\r
1136         v_right = or;\r
1137         v_up = ou;\r
1138 \r
1139         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)\r
1140         {\r
1141                 backtrace("Tried to override initial weapon think function - should this really happen?");\r
1142         }\r
1143 \r
1144         t *= W_WeaponRateFactor();\r
1145 \r
1146         // VorteX: haste can be added here\r
1147         if (self.weapon_think == w_ready)\r
1148         {\r
1149                 self.weapon_nextthink = time;\r
1150                 //dprint("started firing at ", ftos(time), "\n");\r
1151         }\r
1152         if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)\r
1153         {\r
1154                 self.weapon_nextthink = time;\r
1155                 //dprint("reset weapon animation timer at ", ftos(time), "\n");\r
1156         }\r
1157         self.weapon_nextthink = self.weapon_nextthink + t;\r
1158         self.weapon_think = func;\r
1159         //dprint("next ", ftos(self.weapon_nextthink), "\n");\r
1160 \r
1161         if (restartanim)\r
1162         if (t)\r
1163         if (!self.crouch) // shoot anim stands up, this looks bad\r
1164         {\r
1165                 local vector anim;\r
1166                 anim = self.anim_shoot;\r
1167                 anim_z = anim_y / (t + sys_frametime);\r
1168                 setanim(self, anim, FALSE, TRUE, TRUE);\r
1169         }\r
1170 };\r
1171 \r
1172 void weapon_boblayer1(float spd, vector org)\r
1173 {\r
1174         // VorteX: haste can be added here\r
1175 };\r
1176 \r
1177 vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity)\r
1178 {\r
1179         vector mdirection;\r
1180         float mspeed;\r
1181         float outspeed;\r
1182         float nstyle;\r
1183         vector outvelocity;\r
1184 \r
1185         mvelocity = mvelocity * g_weaponspeedfactor;\r
1186 \r
1187         mdirection = normalize(mvelocity);\r
1188         mspeed = vlen(mvelocity);\r
1189 \r
1190         nstyle = cvar("g_projectiles_newton_style");\r
1191         if(nstyle == 0)\r
1192         {\r
1193                 // absolute velocity\r
1194                 outvelocity = mvelocity;\r
1195         }\r
1196         else if(nstyle == 1)\r
1197         {\r
1198                 // true Newtonian projectiles\r
1199                 outvelocity = pvelocity + mvelocity;\r
1200         }\r
1201         else if(nstyle == 2)\r
1202         {\r
1203                 // true Newtonian projectiles with automatic aim adjustment\r
1204                 //\r
1205                 // solve: |outspeed * mdirection - pvelocity| = mspeed\r
1206                 // outspeed^2 - 2 * outspeed * (mdirection * pvelocity) + pvelocity^2 - mspeed^2 = 0\r
1207                 // outspeed = (mdirection * pvelocity) +- sqrt((mdirection * pvelocity)^2 - pvelocity^2 + mspeed^2)\r
1208                 // PLUS SIGN!\r
1209                 // not defined?\r
1210                 // then...\r
1211                 // pvelocity^2 - (mdirection * pvelocity)^2 > mspeed^2\r
1212                 // velocity without mdirection component > mspeed\r
1213                 // fire at smallest possible mspeed that works?\r
1214                 // |(mdirection * pvelocity) * pvelocity - pvelocity| = mspeed\r
1215 \r
1216                 vector solution;\r
1217                 solution = solve_quadratic(1, -2 * (mdirection * pvelocity), pvelocity * pvelocity - mspeed * mspeed);\r
1218                 if(solution_z)\r
1219                         outspeed = solution_y; // the larger one\r
1220                 else\r
1221                 {\r
1222                         //outspeed = 0; // slowest possible shot\r
1223                         outspeed = solution_x; // the real part (that is, the average!)\r
1224                         //dprint("impossible shot, adjusting\n");\r
1225                 }\r
1226 \r
1227                 outspeed = bound(mspeed * cvar("g_projectiles_newton_style_2_minfactor"), outspeed, mspeed * cvar("g_projectiles_newton_style_2_maxfactor"));\r
1228                 outvelocity = mdirection * outspeed;\r
1229         }\r
1230         else if(nstyle == 3)\r
1231         {\r
1232                 // pseudo-Newtonian:\r
1233                 outspeed = mspeed + mdirection * pvelocity;\r
1234                 outspeed = bound(mspeed * 0.7, outspeed, mspeed * 5.0);\r
1235                 outvelocity = mdirection * outspeed;\r
1236         }\r
1237         else if(nstyle == 4)\r
1238         {\r
1239                 // tZorkian:\r
1240                 outspeed = mspeed + vlen(pvelocity);\r
1241                 outvelocity = mdirection * outspeed;\r
1242         }\r
1243         else\r
1244                 error("g_projectiles_newton_style must be 0 (absolute), 1 (Newtonian), 2 (Newtonian + aimfix), 3 (pseudo Newtonian) or 4 (tZorkian)!");\r
1245 \r
1246         return outvelocity;\r
1247 }\r
1248 \r
1249 void W_AttachToShotorg(entity flash, vector offset)\r
1250 {\r
1251         entity xflash;\r
1252         flash.owner = self;\r
1253         flash.angles_z = random() * 360;\r
1254         if(qcweaponanimation)\r
1255         {\r
1256                 setorigin(flash, w_shotorg + w_shotdir * 50);\r
1257                 flash.angles = vectoangles(w_shotdir);\r
1258                 flash.angles_z = random() * 360;\r
1259         }\r
1260         else\r
1261         {\r
1262                 setattachment(flash, self.weaponentity, "shot");\r
1263                 setorigin(flash, offset);\r
1264 \r
1265                 xflash = spawn();\r
1266                 copyentity(flash, xflash);\r
1267 \r
1268                 flash.viewmodelforclient = self;\r
1269 \r
1270                 if(self.weaponentity.oldorigin_x > 0)\r
1271                 {\r
1272                         setattachment(xflash, self.exteriorweaponentity, "");\r
1273                         setorigin(xflash, self.weaponentity.oldorigin + offset);\r
1274                 }\r
1275                 else\r
1276                 {\r
1277                         setattachment(xflash, self.exteriorweaponentity, "shot");\r
1278                 }\r
1279         }\r
1280 }\r
1281 \r
1282 vector cliptoplane(vector v, vector p)\r
1283 {\r
1284         return v - (v * p) * p;\r
1285 }\r
1286 \r
1287 vector solve_cubic_pq(float p, float q)\r
1288 {\r
1289         float D, u, v, a;\r
1290         D = q*q/4.0 + p*p*p/27.0;\r
1291         if(D < 0)\r
1292         {\r
1293                 // irreducibilis\r
1294                 a = 1.0/3.0 * acos(-q/2.0 * sqrt(-27.0/(p*p*p)));\r
1295                 u = sqrt(-4.0/3.0 * p);\r
1296                 // a in range 0..pi/3\r
1297                 // cos(a)\r
1298                 // cos(a + 2pi/3)\r
1299                 // cos(a + 4pi/3)\r
1300                 return\r
1301                         u *\r
1302                         (\r
1303                                 '1 0 0' * cos(a + 2.0/3.0*M_PI)\r
1304                                 +\r
1305                                 '0 1 0' * cos(a + 4.0/3.0*M_PI)\r
1306                                 +\r
1307                                 '0 0 1' * cos(a)\r
1308                         );\r
1309         }\r
1310         else if(D == 0)\r
1311         {\r
1312                 // simple\r
1313                 if(p == 0)\r
1314                         return '0 0 0';\r
1315                 u = 3*q/p;\r
1316                 v = -u/2;\r
1317                 if(u >= v)\r
1318                         return '1 1 0' * v + '0 0 1' * u;\r
1319                 else\r
1320                         return '0 1 1' * v + '1 0 0' * u;\r
1321         }\r
1322         else\r
1323         {\r
1324                 // cardano\r
1325                 u = cbrt(-q/2.0 + sqrt(D));\r
1326                 v = cbrt(-q/2.0 - sqrt(D));\r
1327                 return '1 1 1' * (u + v);\r
1328         }\r
1329 }\r
1330 vector solve_cubic_abcd(float a, float b, float c, float d)\r
1331 {\r
1332         // y = 3*a*x + b\r
1333         // x = (y - b) / 3a\r
1334         float p, q;\r
1335         vector v;\r
1336         p = (9*a*c - 3*b*b);\r
1337         q = (27*a*a*d - 9*a*b*c + 2*b*b*b);\r
1338         v = solve_cubic_pq(p, q);\r
1339         v = (v -  b * '1 1 1') * (1.0 / (3.0 * a));\r
1340         if(a < 0)\r
1341                 v += '1 0 -1' * (v_z - v_x); // swap x, z\r
1342         return v;\r
1343 }\r
1344 \r
1345 vector findperpendicular(vector v)\r
1346 {\r
1347         vector p;\r
1348         p_x = v_z;\r
1349         p_y = -v_x;\r
1350         p_z = v_y;\r
1351         return normalize(cliptoplane(p, v));\r
1352 }\r
1353 \r
1354 vector W_CalculateProjectileSpread(vector forward, float spread)\r
1355 {\r
1356         float sigma;\r
1357         vector v1, v2;\r
1358         float dx, dy, r;\r
1359         float sstyle;\r
1360         spread *= g_weaponspreadfactor;\r
1361         if(spread <= 0)\r
1362                 return forward;\r
1363         sstyle = cvar("g_projectiles_spread_style");\r
1364         \r
1365         if(sstyle == 0)\r
1366         {\r
1367                 // this is the baseline for the spread value!\r
1368                 // standard deviation: sqrt(2/5)\r
1369                 // density function: sqrt(1-r^2)\r
1370                 return forward + randomvec() * spread;\r
1371         }\r
1372         else if(sstyle == 1)\r
1373         {\r
1374                 // same thing, basically\r
1375                 return normalize(forward + cliptoplane(randomvec() * spread, forward));\r
1376         }\r
1377         else if(sstyle == 2)\r
1378         {\r
1379                 // circle spread... has at sigma=1 a standard deviation of sqrt(1/2)\r
1380                 sigma = spread * 0.89442719099991587855; // match baseline stddev\r
1381                 v1 = findperpendicular(forward);\r
1382                 v2 = cross(forward, v1);\r
1383                 // random point on unit circle\r
1384                 dx = random() * 2 * M_PI;\r
1385                 dy = sin(dx);\r
1386                 dx = cos(dx);\r
1387                 // radius in our dist function\r
1388                 r = random();\r
1389                 r = sqrt(r);\r
1390                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1391         }\r
1392         else if(sstyle == 3) // gauss 3d\r
1393         {\r
1394                 sigma = spread * 0.44721359549996; // match baseline stddev\r
1395                 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right\r
1396                 v1 = forward;\r
1397                 v1_x += gsl_ran_gaussian(sigma);\r
1398                 v1_y += gsl_ran_gaussian(sigma);\r
1399                 v1_z += gsl_ran_gaussian(sigma);\r
1400                 return v1;\r
1401         }\r
1402         else if(sstyle == 4) // gauss 2d\r
1403         {\r
1404                 sigma = spread * 0.44721359549996; // match baseline stddev\r
1405                 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right\r
1406                 v1_x = gsl_ran_gaussian(sigma);\r
1407                 v1_y = gsl_ran_gaussian(sigma);\r
1408                 v1_z = gsl_ran_gaussian(sigma);\r
1409                 return normalize(forward + cliptoplane(v1, forward));\r
1410         }\r
1411         else if(sstyle == 5) // 1-r\r
1412         {\r
1413                 sigma = spread * 1.154700538379252; // match baseline stddev\r
1414                 v1 = findperpendicular(forward);\r
1415                 v2 = cross(forward, v1);\r
1416                 // random point on unit circle\r
1417                 dx = random() * 2 * M_PI;\r
1418                 dy = sin(dx);\r
1419                 dx = cos(dx);\r
1420                 // radius in our dist function\r
1421                 r = random();\r
1422                 r = solve_cubic_abcd(-2, 3, 0, -r) * '0 1 0';\r
1423                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1424         }\r
1425         else if(sstyle == 6) // 1-r^2\r
1426         {\r
1427                 sigma = spread * 1.095445115010332; // match baseline stddev\r
1428                 v1 = findperpendicular(forward);\r
1429                 v2 = cross(forward, v1);\r
1430                 // random point on unit circle\r
1431                 dx = random() * 2 * M_PI;\r
1432                 dy = sin(dx);\r
1433                 dx = cos(dx);\r
1434                 // radius in our dist function\r
1435                 r = random();\r
1436                 r = sqrt(1 - r);\r
1437                 r = sqrt(1 - r);\r
1438                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1439         }\r
1440         else if(sstyle == 7) // (1-r) (2-r)\r
1441         {\r
1442                 sigma = spread * 1.224744871391589; // match baseline stddev\r
1443                 v1 = findperpendicular(forward);\r
1444                 v2 = cross(forward, v1);\r
1445                 // random point on unit circle\r
1446                 dx = random() * 2 * M_PI;\r
1447                 dy = sin(dx);\r
1448                 dx = cos(dx);\r
1449                 // radius in our dist function\r
1450                 r = random();\r
1451                 r = 1 - sqrt(r);\r
1452                 r = 1 - sqrt(r);\r
1453                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1454         }\r
1455         else\r
1456                 error("g_projectiles_spread_style must be 0 (sphere), 1 (flattened sphere), 2 (circle), 3 (gauss 3D), 4 (gauss plane), 5 (linear falloff), 6 (quadratic falloff), 7 (stronger falloff)!");\r
1457         return '0 0 0';\r
1458         /*\r
1459          * how to derive falloff functions:\r
1460          * rho(r) := (2-r) * (1-r);\r
1461          * a : 0;\r
1462          * b : 1;\r
1463          * rhor(r) := r * rho(r);\r
1464          * cr(t) := integrate(rhor(r), r, a, t);\r
1465          * scr(t) := integrate(rhor(r) * r^2, r, a, t);\r
1466          * variance : scr(b) / cr(b);\r
1467          * solve(cr(r) = rand * cr(b), r), programmmode:false;\r
1468          * sqrt(0.4 / variance), numer;\r
1469          */\r
1470 }\r
1471 \r
1472 #if 0\r
1473 float mspercallsum;\r
1474 float mspercallsstyle;\r
1475 float mspercallcount;\r
1476 #endif\r
1477 void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread)\r
1478 {\r
1479         if(missile.owner == world)\r
1480                 error("Unowned missile");\r
1481 \r
1482         dir = dir + upDir * (pUpSpeed / pSpeed);\r
1483         dir_z += pZSpeed / pSpeed;\r
1484         pSpeed *= vlen(dir);\r
1485         dir = normalize(dir);\r
1486 \r
1487 #if 0\r
1488         if(cvar("g_projectiles_spread_style") != mspercallsstyle)\r
1489         {\r
1490                 mspercallsum = mspercallcount = 0;\r
1491                 mspercallsstyle = cvar("g_projectiles_spread_style");\r
1492         }\r
1493         mspercallsum -= gettime(GETTIME_HIRES);\r
1494 #endif\r
1495         dir = W_CalculateProjectileSpread(dir, spread);\r
1496 #if 0\r
1497         mspercallsum += gettime(GETTIME_HIRES);\r
1498         mspercallcount += 1;\r
1499         print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");\r
1500 #endif\r
1501 \r
1502         missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir);\r
1503 }\r
1504 \r
1505 void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)\r
1506 {\r
1507         W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread);\r
1508 }\r
1509 \r
1510 #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"))\r
1511 #define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"))\r