]> de.git.xonotic.org Git - voretournament/voretournament.git/blob - data/qcsrc/server/cl_weaponsystem.qc
Fix g_nodepthtestplayers
[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         {\r
227                 ent.punchangle_x += crandom() * recoil;\r
228                 ent.punchangle_y += crandom() * recoil;\r
229         }\r
230 \r
231         if (snd != "")\r
232         {\r
233                 sound (ent, CHAN_WEAPON, snd, VOL_BASE, ATTN_NORM);\r
234         }\r
235 \r
236         if (ent.items & IT_STRENGTH && maxdamage)\r
237                 sound (ent, CHAN_AUTO, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);\r
238 };\r
239 \r
240 #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
241 #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
242 #define W_SetupShot(ent,antilag,recoil,snd,maxdamage) W_SetupShot_ProjectileSize(ent, '0 0 0', '0 0 0', antilag, recoil, snd, maxdamage)\r
243 \r
244 .string weaponname;\r
245 \r
246 float CL_Weaponentity_CustomizeEntityForClient()\r
247 {\r
248         self.viewmodelforclient = self.owner;\r
249         if(other.classname == "spectator")\r
250                 if(other.enemy == self.owner)\r
251                         self.viewmodelforclient = other;\r
252         return TRUE;\r
253 }\r
254 \r
255 float CL_ExteriorWeaponentity_CustomizeEntityForClient()\r
256 {\r
257         // hide the exterior weapon entity of a predator from their prey\r
258         // otherwise, the stomach model the predator is transformed in (see Client_customizeentityforclient) will have a weapon model attached to it\r
259         if(self.owner.weaponname == "" || self.owner.deadflag != DEAD_NO)\r
260                 return TRUE;\r
261 \r
262         float chase;\r
263         chase = other.cvar_chase_active;\r
264 \r
265         if(other.spectatee_status && other.spectatee_status == num_for_edict(other.enemy))\r
266                 other = other.enemy; // also do this for the player we are spectating\r
267 \r
268         if not(chase || other.classname == "observer") // the observer check prevents a bug\r
269         if(other.predator == self.owner)\r
270         {\r
271                 setmodel(self, "");\r
272                 return TRUE;\r
273         }\r
274 \r
275         if(cvar("g_nodepthtestplayers"))\r
276                 self.effects |= EF_NODEPTHTEST;\r
277         else\r
278                 self.effects &~= EF_NODEPTHTEST;\r
279         if not(self.owner.stat_eaten)\r
280                 setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3"));\r
281         else if(cvar("g_vore_neighborprey_distance") && other.predator == self.owner.predator && !(chase || other.classname == "observer"))\r
282         {\r
283                 setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // allow seeing neighboring prey's weapon model\r
284                 self.effects |= EF_NODEPTHTEST; // don't hide behind the stomach's own EF_NODEPTHTEST\r
285         }\r
286         else\r
287                 setmodel(self, ""); // hide prey's weapon model\r
288         return TRUE;\r
289 }\r
290 \r
291 float qcweaponanimation;\r
292 vector weapon_offset = '0 -10 0';\r
293 vector weapon_adjust = '10 0 -15';\r
294 .vector weapon_morph0origin;\r
295 .vector weapon_morph0angles;\r
296 .float  weapon_morph0time;\r
297 .vector weapon_morph1origin;\r
298 .vector weapon_morph1angles;\r
299 .float  weapon_morph1time;\r
300 .vector weapon_morph2origin;\r
301 .vector weapon_morph2angles;\r
302 .float  weapon_morph2time;\r
303 .vector weapon_morph3origin;\r
304 .vector weapon_morph3angles;\r
305 .float  weapon_morph3time;\r
306 .vector weapon_morph4origin;\r
307 .vector weapon_morph4angles;\r
308 .float  weapon_morph4time;\r
309 #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
310 \r
311 /*\r
312  * supported formats:\r
313  *\r
314  * 1. simple animated model, muzzle flash handling on h_ model:\r
315  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation\r
316  *      tags:\r
317  *        shot = muzzle end (shot origin, also used for muzzle flashes)\r
318  *        shell = casings ejection point (must be on the right hand side of the gun)\r
319  *        weapon = attachment for v_tuba.md3\r
320  *    v_tuba.md3 - first and third person model\r
321  *    g_tuba.md3 - pickup model\r
322  *\r
323  * 2. simple animated model, muzzle flash handling on v_ model:\r
324  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation\r
325  *      tags:\r
326  *        weapon = attachment for v_tuba.md3\r
327  *    v_tuba.md3 - first and third person model\r
328  *      tags:\r
329  *        shot = muzzle end (shot origin, also used for muzzle flashes)\r
330  *        shell = casings ejection point (must be on the right hand side of the gun)\r
331  *    g_tuba.md3 - pickup model\r
332  *\r
333  * 3. fully animated model, muzzle flash handling on h_ model:\r
334  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model\r
335  *      tags:\r
336  *        shot = muzzle end (shot origin, also used for muzzle flashes)\r
337  *        shell = casings ejection point (must be on the right hand side of the gun)\r
338  *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)\r
339  *    v_tuba.md3 - third person model\r
340  *    g_tuba.md3 - pickup model\r
341  *\r
342  * 4. fully animated model, muzzle flash handling on v_ model:\r
343  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model\r
344  *      tags:\r
345  *        shot = muzzle end (shot origin)\r
346  *        shell = casings ejection point (must be on the right hand side of the gun)\r
347  *    v_tuba.md3 - third person model\r
348  *      tags:\r
349  *        shot = muzzle end (for muzzle flashes)\r
350  *    g_tuba.md3 - pickup model\r
351  */\r
352 \r
353 void CL_Weaponentity_Think()\r
354 {\r
355         float tb, v_shot_idx;\r
356         self.nextthink = time;\r
357         if (intermission_running)\r
358                 self.frame = self.anim_idle_x;\r
359         if (self.owner.weaponentity != self)\r
360         {\r
361                 if (self.weaponentity)\r
362                         remove(self.weaponentity);\r
363                 remove(self);\r
364                 return;\r
365         }\r
366         if (self.owner.deadflag != DEAD_NO)\r
367         {\r
368                 self.model = "";\r
369                 if (self.weaponentity)\r
370                         self.weaponentity.model = "";\r
371                 return;\r
372         }\r
373         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)\r
374         if (substring(self.owner.model, -4, 4) != ".md3") // not a stomach model\r
375         {\r
376                 self.cnt = self.owner.weapon;\r
377                 self.dmg = self.owner.modelindex;\r
378                 self.deadflag = self.owner.deadflag;\r
379 \r
380                 string animfilename;\r
381                 float animfile;\r
382                 if (self.owner.weaponname != "")\r
383                 {\r
384                         // if there is a child entity, hide it until we're sure we use it\r
385                         if (self.weaponentity)\r
386                                 self.weaponentity.model = "";\r
387                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below\r
388                         v_shot_idx = gettagindex(self, "shot"); // used later\r
389                         if(!v_shot_idx)\r
390                                 v_shot_idx = gettagindex(self, "tag_shot");\r
391 \r
392                         if(qcweaponanimation)\r
393                         {\r
394                                 self.angles = '0 0 0';\r
395                                 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
396                                 self.movedir = weapon_offset_x * v_forward - weapon_offset_y * v_right + weapon_offset_z * v_up + weapon_adjust;\r
397                                 self.movedir_x += 32;\r
398                                 self.spawnorigin = self.movedir;\r
399                                 // oldorigin - not calculated here\r
400                         }\r
401                         else\r
402                         {\r
403                                 setmodel(self, strcat("models/weapons/h_", self.owner.weaponname, ".iqm")); // precision set below\r
404                                 animfilename = strcat("models/weapons/h_", self.owner.weaponname, ".iqm.animinfo");\r
405                                 animfile = fopen(animfilename, FILE_READ);\r
406                                 // preset some defaults that work great for renamed zym files (which don't need an animinfo)\r
407                                 self.anim_fire1  = '0 1 0.01';\r
408                                 self.anim_fire2  = '1 1 0.01';\r
409                                 self.anim_idle   = '2 1 0.01';\r
410                                 self.anim_reload = '3 1 0.01';\r
411                                 if (animfile >= 0)\r
412                                 {\r
413                                         animparseerror = FALSE;\r
414                                         self.anim_fire1  = animparseline(animfile);\r
415                                         self.anim_fire2  = animparseline(animfile);\r
416                                         self.anim_idle   = animparseline(animfile);\r
417                                         self.anim_reload = animparseline(animfile);\r
418                                         fclose(animfile);\r
419                                         if (animparseerror)\r
420                                                 print("Parse error in ", animfilename, ", some player animations are broken\n");\r
421                                 }\r
422 \r
423                                 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)\r
424                                 // if we don't, this is a "real" animated model\r
425                                 if(gettagindex(self, "weapon"))\r
426                                 {\r
427                                         if (!self.weaponentity)\r
428                                                 self.weaponentity = spawn();\r
429                                         setmodel(self.weaponentity, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision does not matter\r
430                                         setattachment(self.weaponentity, self, "weapon");\r
431                                 }\r
432                                 else if(gettagindex(self, "tag_weapon"))\r
433                                 {\r
434                                         if (!self.weaponentity)\r
435                                                 self.weaponentity = spawn();\r
436                                         setmodel(self.weaponentity, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision does not matter\r
437                                         setattachment(self.weaponentity, self, "tag_weapon");\r
438                                 }\r
439                                 else\r
440                                 {\r
441                                         if(self.weaponentity)\r
442                                                 remove(self.weaponentity);\r
443                                         self.weaponentity = world;\r
444                                 }\r
445 \r
446                                 setorigin(self,'0 0 0');\r
447                                 self.angles = '0 0 0';\r
448                                 self.frame = 0;\r
449                                 self.viewmodelforclient = world;\r
450 \r
451                                 float idx;\r
452 \r
453                                 if(v_shot_idx) // v_ model attached to invisible h_ model\r
454                                 {\r
455                                         self.movedir = gettaginfo(self.weaponentity, v_shot_idx);\r
456                                 }\r
457                                 else\r
458                                 {\r
459                                         idx = gettagindex(self, "shot");\r
460                                         if(!idx)\r
461                                                 idx = gettagindex(self, "tag_shot");\r
462                                         if(idx)\r
463                                                 self.movedir = gettaginfo(self, idx);\r
464                                         else\r
465                                         {\r
466                                                 print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");\r
467                                                 self.movedir = '0 0 0';\r
468                                         }\r
469                                 }\r
470 \r
471                                 if(self.weaponentity) // v_ model attached to invisible h_ model\r
472                                 {\r
473                                         idx = gettagindex(self.weaponentity, "shell");\r
474                                         if(!idx)\r
475                                                 idx = gettagindex(self.weaponentity, "tag_shell");\r
476                                         if(idx)\r
477                                                 self.spawnorigin = gettaginfo(self.weaponentity, idx);\r
478                                 }\r
479                                 else\r
480                                         idx = 0;\r
481                                 if(!idx)\r
482                                 {\r
483                                         idx = gettagindex(self, "shell");\r
484                                         if(!idx)\r
485                                                 idx = gettagindex(self, "tag_shell");\r
486                                         if(idx)\r
487                                                 self.spawnorigin = gettaginfo(self, idx);\r
488                                         else\r
489                                         {\r
490                                                 print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");\r
491                                                 self.spawnorigin = self.movedir;\r
492                                         }\r
493                                 }\r
494 \r
495                                 if(v_shot_idx)\r
496                                 {\r
497                                         self.oldorigin = '0 0 0'; // use regular attachment\r
498                                 }\r
499                                 else\r
500                                 {\r
501                                         if(self.weaponentity)\r
502                                         {\r
503                                                 idx = gettagindex(self, "weapon");\r
504                                                 if(!idx)\r
505                                                         idx = gettagindex(self, "tag_weapon");\r
506                                         }\r
507                                         else\r
508                                         {\r
509                                                 idx = gettagindex(self, "handle");\r
510                                                 if(!idx)\r
511                                                         idx = gettagindex(self, "tag_handle");\r
512                                         }\r
513                                         if(idx)\r
514                                         {\r
515                                                 self.oldorigin = self.movedir - gettaginfo(self, idx);\r
516                                         }\r
517                                         else\r
518                                         {\r
519                                                 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
520                                                 self.oldorigin = '0 0 0'; // there is no way to recover from this\r
521                                         }\r
522                                 }\r
523 \r
524                                 self.viewmodelforclient = self.owner;\r
525                         }\r
526                 }\r
527                 else\r
528                 {\r
529                         self.model = "";\r
530                         if(self.weaponentity)\r
531                                 remove(self.weaponentity);\r
532                         self.weaponentity = world;\r
533                         self.movedir = '0 0 0';\r
534                         self.spawnorigin = '0 0 0';\r
535                         self.oldorigin = '0 0 0';\r
536                         self.anim_fire1  = '0 1 0.01';\r
537                         self.anim_fire2  = '0 1 0.01';\r
538                         self.anim_idle   = '0 1 0.01';\r
539                         self.anim_reload = '0 1 0.01';\r
540                 }\r
541 \r
542                 self.view_ofs = '0 0 0';\r
543 \r
544                 if(self.movedir_x >= 0)\r
545                 {\r
546                         vector v0;\r
547                         v0 = self.movedir;\r
548                         self.movedir = shotorg_adjust(v0, FALSE, FALSE);\r
549                         self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;\r
550                 }\r
551                 self.owner.stat_shotorg = compressShotOrigin(self.movedir);\r
552                 self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly\r
553 \r
554                 self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount\r
555 \r
556                 // check if an instant weapon switch occurred\r
557                 if (qcweaponanimation)\r
558                 {\r
559                         if (self.state == WS_READY)\r
560                         {\r
561                                 self.angles = '0 0 0';\r
562                                 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
563                                 setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
564                         }\r
565                 }\r
566                 else\r
567                         setorigin(self, self.view_ofs);\r
568                 // reset animstate now\r
569                 self.wframe = WFRAME_IDLE;\r
570                 self.weapon_morph0time = 0;\r
571                 self.weapon_morph1time = 0;\r
572                 self.weapon_morph2time = 0;\r
573                 self.weapon_morph3time = 0;\r
574                 self.weapon_morph4time = 0;\r
575                 setanim(self, self.anim_idle, TRUE, FALSE, TRUE);\r
576         }\r
577 \r
578         tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));\r
579         self.effects = self.owner.effects & EFMASK_CHEAP;\r
580         self.effects &~= EF_LOWPRECISION;\r
581         self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it\r
582         self.effects &~= EF_TELEPORT_BIT;\r
583         self.effects &~= EF_RESTARTANIM_BIT;\r
584         self.effects |= tb;\r
585 \r
586         if(self.owner.alpha == default_player_alpha)\r
587                 self.alpha = default_weapon_alpha;\r
588         else if(self.owner.alpha != 0 && !self.owner.stat_eaten)\r
589                 self.alpha = self.owner.alpha;\r
590         else\r
591                 self.alpha = 1;\r
592 \r
593         self.colormap = self.owner.colormap;\r
594         self.colormod = self.owner.colormod;\r
595         self.glowmod = self.owner.weaponentity_glowmod;\r
596         if (self.weaponentity)\r
597         {\r
598                 self.weaponentity.effects = self.effects;\r
599                 self.weaponentity.alpha = self.alpha;\r
600                 self.weaponentity.colormap = self.colormap;\r
601                 self.weaponentity.colormod = self.owner.colormod; // used by the regurgitating colors\r
602                 self.weaponentity.glowmod = self.glowmod;\r
603         }\r
604 \r
605         self.angles = '0 0 0';\r
606         local float f;\r
607         f = 0;\r
608         if (self.state == WS_RAISE && !intermission_running)\r
609         {\r
610                 f = (self.owner.weapon_nextthink - time) * g_weaponratefactor / cvar("g_balance_weaponswitchdelay");\r
611                 self.angles_x = -90 * f * f;\r
612                 if (qcweaponanimation)\r
613                 {\r
614                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
615                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
616                 }\r
617         }\r
618         else if (self.state == WS_DROP && !intermission_running)\r
619         {\r
620                 f = 1 - (self.owner.weapon_nextthink - time) * g_weaponratefactor / cvar("g_balance_weaponswitchdelay");\r
621                 self.angles_x = -90 * f * f;\r
622                 if (qcweaponanimation)\r
623                 {\r
624                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
625                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
626                 }\r
627         }\r
628         else if (self.state == WS_CLEAR)\r
629         {\r
630                 f = 1;\r
631                 self.angles_x = -90 * f * f;\r
632                 if (qcweaponanimation)\r
633                 {\r
634                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');\r
635                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));\r
636                 }\r
637         }\r
638         else if (qcweaponanimation && time < self.owner.weapon_morph1time)\r
639         {\r
640                 f = (time - self.owner.weapon_morph0time) / (self.owner.weapon_morph1time - self.owner.weapon_morph0time);\r
641                 f = 1 - pow(1 - f, 3);\r
642                 self.angles = self.owner.weapon_morph0angles * (1 - f) + self.owner.weapon_morph1angles * f;\r
643                 setorigin(self, self.owner.weapon_morph0origin * (1 - f) + self.owner.weapon_morph1origin * f);\r
644         }\r
645         else if (qcweaponanimation && time < self.owner.weapon_morph2time)\r
646         {\r
647                 f = (time - self.owner.weapon_morph1time) / (self.owner.weapon_morph2time - self.owner.weapon_morph1time);\r
648                 f = 1 - pow(1 - f, 3);\r
649                 self.angles = self.owner.weapon_morph1angles * (1 - f) + self.owner.weapon_morph2angles * f;\r
650                 setorigin(self, self.owner.weapon_morph1origin * (1 - f) + self.owner.weapon_morph2origin * f);\r
651         }\r
652         else if (qcweaponanimation && time < self.owner.weapon_morph3time)\r
653         {\r
654                 f = (time - self.owner.weapon_morph2time) / (self.owner.weapon_morph3time - self.owner.weapon_morph2time);\r
655                 f = 1 - pow(1 - f, 3);\r
656                 self.angles = self.owner.weapon_morph2angles * (1 - f) + self.owner.weapon_morph3angles * f;\r
657                 setorigin(self, self.owner.weapon_morph2origin * (1 - f) + self.owner.weapon_morph3origin * f);\r
658         }\r
659         else if (qcweaponanimation && time < self.owner.weapon_morph4time)\r
660         {\r
661                 f = (time - self.owner.weapon_morph3time) / (self.owner.weapon_morph4time - self.owner.weapon_morph3time);\r
662                 f = 1 - pow(1 - f, 3);\r
663                 self.angles = self.owner.weapon_morph3angles * (1 - f) + self.owner.weapon_morph4angles * f;\r
664                 setorigin(self, self.owner.weapon_morph3origin * (1 - f) + self.owner.weapon_morph4origin * f);\r
665         }\r
666         else if (qcweaponanimation)\r
667         {\r
668                 // begin a new idle morph\r
669                 self.owner.weapon_morph0time   = time;\r
670                 self.owner.weapon_morph0angles = self.angles;\r
671                 self.owner.weapon_morph0origin = self.origin;\r
672 \r
673                 float r;\r
674                 float t;\r
675 \r
676                 r = random();\r
677                 if (r < 0.1)\r
678                 {\r
679                         // turn gun to the left to look at it\r
680                         t = 2;\r
681                         self.owner.weapon_morph1time   = time + t * 0.2;\r
682                         self.owner.weapon_morph1angles = randomvec() * 3 + '-5 30 0';\r
683                         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
684                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
685 \r
686                         self.owner.weapon_morph2time   = time + t * 0.6;\r
687                         self.owner.weapon_morph2angles = randomvec() * 3 + '-5 30 0';\r
688                         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
689                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
690 \r
691                         self.owner.weapon_morph3time   = time + t;\r
692                         self.owner.weapon_morph3angles = '0 0 0';\r
693                         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
694                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
695                 }\r
696                 else if (r < 0.2)\r
697                 {\r
698                         // raise the gun a bit\r
699                         t = 2;\r
700                         self.owner.weapon_morph1time   = time + t * 0.2;\r
701                         self.owner.weapon_morph1angles = randomvec() * 3 + '30 -10 0';\r
702                         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
703                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
704 \r
705                         self.owner.weapon_morph2time   = time + t * 0.5;\r
706                         self.owner.weapon_morph2angles = randomvec() * 3 + '30 -10 5';\r
707                         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
708                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
709 \r
710                         self.owner.weapon_morph3time   = time + t;\r
711                         self.owner.weapon_morph3angles = '0 0 0';\r
712                         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
713                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
714                 }\r
715                 else if (r < 0.3)\r
716                 {\r
717                         // tweak it a bit\r
718                         t = 5;\r
719                         self.owner.weapon_morph1time   = time + t * 0.3;\r
720                         self.owner.weapon_morph1angles = randomvec() * 6;\r
721                         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
722                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
723 \r
724                         self.owner.weapon_morph2time   = time + t * 0.7;\r
725                         self.owner.weapon_morph2angles = randomvec() * 6;\r
726                         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
727                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
728 \r
729                         self.owner.weapon_morph3time   = time + t;\r
730                         self.owner.weapon_morph3angles = '0 0 0';\r
731                         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
732                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
733                 }\r
734                 else\r
735                 {\r
736                         // hold it mostly steady\r
737                         t = random() * 6 + 4;\r
738                         self.owner.weapon_morph1time   = time + t * 0.2;\r
739                         self.owner.weapon_morph1angles = randomvec() * 1;\r
740                         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
741                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);\r
742 \r
743                         self.owner.weapon_morph2time   = time + t * 0.5;\r
744                         self.owner.weapon_morph2angles = randomvec() * 1;\r
745                         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
746                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);\r
747 \r
748                         self.owner.weapon_morph3time   = time + t * 0.7;\r
749                         self.owner.weapon_morph3angles = randomvec() * 1;\r
750                         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
751                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);\r
752                 }\r
753 \r
754                 self.owner.weapon_morph4time   = time + t;\r
755                 self.owner.weapon_morph4angles = '0 0 0';\r
756                 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
757                 self.owner.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self);\r
758 \r
759         }\r
760 \r
761         // if we are a micro or macro, size the weapon model accordingly\r
762         if(cvar("g_healthsize") && cvar("g_healthsize_weapon_scalefactor"))\r
763         if(self.owner.scale) // prevents some exceptions\r
764         {\r
765                 self.scale = pow(1 / self.owner.scale, cvar("g_healthsize_weapon_scalefactor"));\r
766                 if(self.scale < 0.1)\r
767                         self.scale = 0.1; // stuff breaks if scale is smaller than this\r
768                 self.origin_z = (1 - self.scale) * cvar("g_healthsize_weapon_scalefactor_pos");\r
769 \r
770                 // copy properties to the static weapon entity as well\r
771                 if(self.weaponentity != world) // prevents assignment to world\r
772                 {\r
773                         self.weaponentity.scale = self.scale;\r
774                         self.weaponentity.origin = self.origin;\r
775                 }\r
776         }\r
777 };\r
778 \r
779 void CL_ExteriorWeaponentity_Think()\r
780 {\r
781         float tag_found;\r
782         vector ang;\r
783         self.nextthink = time;\r
784         if (self.owner.exteriorweaponentity != self)\r
785         {\r
786                 remove(self);\r
787                 return;\r
788         }\r
789         if (self.owner.deadflag != DEAD_NO)\r
790         {\r
791                 self.model = "";\r
792                 return;\r
793         }\r
794         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)\r
795         if (substring(self.owner.model, -4, 4) != ".md3") // not a stomach model\r
796         {\r
797                 self.cnt = self.owner.weapon;\r
798                 self.dmg = self.owner.modelindex;\r
799                 self.deadflag = self.owner.deadflag;\r
800                 if (self.owner.weaponname != "")\r
801                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below\r
802                 else\r
803                         self.model = "";\r
804 \r
805                 if((tag_found = gettagindex(self.owner, "tag_weapon")))\r
806                 {\r
807                         self.tag_index = tag_found;\r
808                         self.tag_entity = self.owner;\r
809                 }\r
810                 else\r
811                         setattachment(self, self.owner, "bip01 r hand");\r
812 \r
813                 // if that didn't find a tag, hide the exterior weapon model\r
814                 if (!self.tag_index)\r
815                         self.model = "";\r
816         }\r
817         self.effects = self.owner.effects;\r
818         if(sv_pitch_min == sv_pitch_max)\r
819                 self.effects |= EF_LOWPRECISION;\r
820         else\r
821                 self.effects &~= EF_LOWPRECISION;\r
822         self.effects = self.effects & EFMASK_CHEAP; // eat performance\r
823         if(self.owner.alpha == default_player_alpha)\r
824                 self.alpha = default_weapon_alpha;\r
825         else if(self.owner.alpha != 0)\r
826                 self.alpha = self.owner.alpha;\r
827         else\r
828                 self.alpha = 1;\r
829         self.colormod = self.owner.colormod; // used by the regurgitating colors\r
830 \r
831         ang_x = bound(sv_pitch_min, self.owner.v_angle_x, sv_pitch_max);\r
832         ang_y = 0;\r
833         ang_z = 0;\r
834 \r
835         if(sv_pitch_fixyaw) // workaround for stupid player models that don't aim forward\r
836         {\r
837                 ang_y = self.owner.v_angle_y;\r
838                 makevectors(ang);\r
839                 var vector v = v_forward;\r
840                 var float t = self.tag_entity.frame1time;\r
841                 var float f = self.tag_entity.frame;\r
842                 self.tag_entity.frame1time = time;\r
843                 self.tag_entity.frame = self.tag_entity.anim_idle_x;\r
844                 gettaginfo(self.tag_entity, self.tag_index);\r
845                 self.tag_entity.frame1time = t;\r
846                 self.tag_entity.frame = f;\r
847                 // untransform v according to this coordinate space\r
848                 vector w;\r
849                 w_x = v_forward * v;\r
850                 w_y = -v_right * v;\r
851                 w_z = v_up * v;\r
852                 self.angles = vectoangles(w);\r
853         }\r
854         else\r
855         {\r
856                 ang_x = -/* don't ask */ang_x;\r
857                 self.angles = ang;\r
858         }\r
859 \r
860         // if we are a micro or macro, size the weapon model accordingly\r
861         if(self.owner.scale && cvar("g_healthsize_exteriorweapon_scalefactor"))\r
862         if(self.model != "")\r
863         {\r
864                 self.scale = 1 / self.owner.scale; // for some reason, the exterior weapon entity copies the player's scale somewhere else, so undo that first\r
865                 self.scale = (1 - cvar("g_healthsize_exteriorweapon_scalefactor")) + cvar("g_healthsize_exteriorweapon_scalefactor") * self.scale;\r
866         }\r
867 \r
868         self.colormap = self.owner.colormap;\r
869         self.glowmod = self.owner.weaponentity_glowmod;\r
870         self.customizeentityforclient = CL_ExteriorWeaponentity_CustomizeEntityForClient;\r
871 };\r
872 \r
873 // spawning weaponentity for client\r
874 void CL_SpawnWeaponentity()\r
875 {\r
876         self.weaponentity = spawn();\r
877         self.weaponentity.classname = "weaponentity";\r
878         self.weaponentity.solid = SOLID_NOT;\r
879         self.weaponentity.owner = self;\r
880         setmodel(self.weaponentity, ""); // precision set when changed\r
881         setorigin(self.weaponentity, '0 0 0');\r
882         self.weaponentity.angles = '0 0 0';\r
883         self.weaponentity.viewmodelforclient = self;\r
884         self.weaponentity.flags = 0;\r
885         self.weaponentity.think = CL_Weaponentity_Think;\r
886         self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;\r
887         self.weaponentity.nextthink = time;\r
888 \r
889         self.exteriorweaponentity = spawn();\r
890         self.exteriorweaponentity.classname = "exteriorweaponentity";\r
891         self.exteriorweaponentity.solid = SOLID_NOT;\r
892         self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;\r
893         self.exteriorweaponentity.owner = self;\r
894         setorigin(self.exteriorweaponentity, '0 0 0');\r
895         self.exteriorweaponentity.angles = '0 0 0';\r
896         self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;\r
897         self.exteriorweaponentity.nextthink = time;\r
898 };\r
899 \r
900 .float hasweapon_complain_spam;\r
901 \r
902 float client_hasweapon(entity cl, float wpn, float andammo, float complain)\r
903 {\r
904         local float weaponbit;\r
905         //local float weaponbit, f;\r
906         //local entity oldself;\r
907 \r
908         if(time < self.hasweapon_complain_spam)\r
909                 complain = 0;\r
910         if(complain)\r
911                 self.hasweapon_complain_spam = time + 0.2;\r
912 \r
913         if (wpn < WEP_FIRST || wpn > WEP_LAST)\r
914         {\r
915                 if (complain)\r
916                         sprint(self, "Invalid weapon\n");\r
917                 return FALSE;\r
918         }\r
919         weaponbit = W_WeaponBit(wpn);\r
920         if (cl.weapons & weaponbit)\r
921         {\r
922                 // since we don't have an infinite ammo weapon in vore tournament, allow switching to the grabber even when it has no ammo\r
923                 /*if (andammo)\r
924                 {\r
925                         if(cl.items & IT_UNLIMITED_WEAPON_AMMO)\r
926                         {\r
927                                 f = 1;\r
928                         }\r
929                         else\r
930                         {\r
931                                 oldself = self;\r
932                                 self = cl;\r
933                                 f = weapon_action(wpn, WR_CHECKAMMO1);\r
934                                 f = f + weapon_action(wpn, WR_CHECKAMMO2);\r
935                                 self = oldself;\r
936                         }\r
937                         if (!f)\r
938                         {\r
939                                 if (complain)\r
940                                 if(clienttype(cl) == CLIENTTYPE_REAL)\r
941                                 {\r
942                                         play2(cl, "misc/unavailable.wav");\r
943                                         sprint(cl, strcat("You don't have any ammo for the ^2", W_Name(wpn), "\n"));\r
944                                 }\r
945                                 return FALSE;\r
946                         }\r
947                 }*/\r
948                 return TRUE;\r
949         }\r
950         if (complain)\r
951         {\r
952                 // DRESK - 3/16/07\r
953                 // Report Proper Weapon Status / Modified Weapon Ownership Message\r
954                 if(weaponsInMap & weaponbit)\r
955                 {\r
956                         sprint(cl, strcat("You do not have the ^2", W_Name(wpn), "\n") );\r
957 \r
958                         if(cvar("g_showweaponspawns"))\r
959                         {\r
960                                 entity e;\r
961                                 string s;\r
962 \r
963                                 e = get_weaponinfo(wpn);\r
964                                 s = e.model2;\r
965 \r
966                                 for(e = world; (e = findfloat(e, weapons, weaponbit)); )\r
967                                 {\r
968                                         if(e.classname == "droppedweapon")\r
969                                                 continue;\r
970                                         if not(e.flags & FL_ITEM)\r
971                                                 continue;\r
972                                         WaypointSprite_Spawn(\r
973                                                 s,\r
974                                                 1, 0,\r
975                                                 world, e.origin,\r
976                                                 self, 0,\r
977                                                 world, enemy,\r
978                                                 0\r
979                                         );\r
980                                 }\r
981                         }\r
982                 }\r
983                 else\r
984                         sprint(cl, strcat("The ^2", W_Name(wpn), "^7 is ^1NOT AVAILABLE^7 in this map\n") );\r
985 \r
986                 play2(cl, "misc/unavailable.wav");\r
987         }\r
988         return FALSE;\r
989 };\r
990 \r
991 // Weapon subs\r
992 void w_clear()\r
993 {\r
994         if (self.weapon != -1)\r
995                 self.weapon = 0;\r
996         if (self.weaponentity)\r
997         {\r
998                 self.weaponentity.state = WS_CLEAR;\r
999                 self.weaponentity.effects = 0;\r
1000         }\r
1001 };\r
1002 \r
1003 void w_ready()\r
1004 {\r
1005         if (self.weaponentity)\r
1006                 self.weaponentity.state = WS_READY;\r
1007         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);\r
1008 };\r
1009 \r
1010 // Setup weapon for client (after this raise frame will be launched)\r
1011 void weapon_setup(float windex)\r
1012 {\r
1013         entity e;\r
1014         qcweaponanimation = cvar("sv_qcweaponanimation");\r
1015         e = get_weaponinfo(windex);\r
1016         self.items &~= IT_AMMO;\r
1017         self.items = self.items | e.items;\r
1018 \r
1019         // the two weapon entities will notice this has changed and update their models\r
1020         self.weapon = windex;\r
1021         self.weaponname = e.mdl;\r
1022 };\r
1023 \r
1024 // perform weapon to attack (weaponstate and attack_finished check is here)\r
1025 void W_SwitchToOtherWeapon(entity pl)\r
1026 {\r
1027         // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)\r
1028         float w, ww;\r
1029         w = W_WeaponBit(pl.weapon);\r
1030         pl.weapons &~= w;\r
1031         ww = w_getbestweapon(pl);\r
1032         pl.weapons |= w;\r
1033         if(ww)\r
1034                 W_SwitchWeapon_Force(pl, ww);\r
1035 }\r
1036 \r
1037 .float prevdryfire;\r
1038 float weapon_prepareattack_checkammo(float secondary)\r
1039 {\r
1040         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)\r
1041         if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))\r
1042         {\r
1043                 if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons\r
1044                 {\r
1045                         sound (self, CHAN_AUTO, "misc/unavailable.wav", VOL_BASE, ATTN_NORM);\r
1046                         self.prevdryfire = time;\r
1047                 }\r
1048 \r
1049                 // since we don't have an infinite ammo weapon in vore tournament, allow switching to the grabber even when it has no ammo\r
1050                 //W_SwitchToOtherWeapon(self);\r
1051                 return FALSE;\r
1052         }\r
1053         return TRUE;\r
1054 }\r
1055 .float race_penalty;\r
1056 float weapon_prepareattack_check(float secondary, float attacktime)\r
1057 {\r
1058         if(!weapon_prepareattack_checkammo(secondary))\r
1059                 return FALSE;\r
1060 \r
1061         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot\r
1062         //if all players readied up and the countdown is running\r
1063         if(time < game_starttime || time < self.race_penalty) {\r
1064                 return FALSE;\r
1065         }\r
1066 \r
1067         if (timeoutStatus == 2) //don't allow the player to shoot while game is paused\r
1068                 return FALSE;\r
1069 \r
1070         // do not even think about shooting if switching\r
1071         if(self.switchweapon != self.weapon)\r
1072                 return FALSE;\r
1073 \r
1074         if(attacktime >= 0)\r
1075         {\r
1076                 // don't fire if previous attack is not finished\r
1077                 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)\r
1078                         return FALSE;\r
1079                 // don't fire while changing weapon\r
1080                 if (self.weaponentity.state != WS_READY)\r
1081                         return FALSE;\r
1082         }\r
1083 \r
1084         return TRUE;\r
1085 }\r
1086 float weapon_prepareattack_do(float secondary, float attacktime)\r
1087 {\r
1088         self.weaponentity.state = WS_INUSE;\r
1089 \r
1090         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire\r
1091 \r
1092         // if the weapon hasn't been firing continuously, reset the timer\r
1093         if(attacktime >= 0)\r
1094         {\r
1095                 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)\r
1096                 {\r
1097                         ATTACK_FINISHED(self) = time;\r
1098                         //dprint("resetting attack finished to ", ftos(time), "\n");\r
1099                 }\r
1100                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();\r
1101         }\r
1102         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");\r
1103         return TRUE;\r
1104 }\r
1105 float weapon_prepareattack(float secondary, float attacktime)\r
1106 {\r
1107         if(weapon_prepareattack_check(secondary, attacktime))\r
1108         {\r
1109                 weapon_prepareattack_do(secondary, attacktime);\r
1110                 return TRUE;\r
1111         }\r
1112         else\r
1113                 return FALSE;\r
1114 }\r
1115 \r
1116 void weapon_thinkf(float fr, float t, void() func)\r
1117 {\r
1118         vector a;\r
1119         vector of, or, ou;\r
1120         float restartanim;\r
1121 \r
1122         if(fr == WFRAME_DONTCHANGE)\r
1123         {\r
1124                 fr = self.weaponentity.wframe;\r
1125                 restartanim = FALSE;\r
1126         }\r
1127         else if (fr == WFRAME_IDLE)\r
1128                 restartanim = FALSE;\r
1129         else\r
1130                 restartanim = TRUE;\r
1131 \r
1132         of = v_forward;\r
1133         or = v_right;\r
1134         ou = v_up;\r
1135 \r
1136         if (self.weaponentity)\r
1137         {\r
1138                 self.weaponentity.wframe = fr;\r
1139                 if (qcweaponanimation)\r
1140                 {\r
1141                         if (fr != WFRAME_IDLE)\r
1142                         {\r
1143                                 self.weapon_morph0time = time;\r
1144                                 self.weapon_morph0angles = self.weaponentity.angles;\r
1145                                 self.weapon_morph0origin = self.weaponentity.origin;\r
1146 \r
1147                                 self.weapon_morph1angles = '0 0 0';\r
1148                                 self.weapon_morph1time = time + t;\r
1149                                 makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1150                                 self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1151 \r
1152                                 self.weapon_morph2angles = '0 0 0';\r
1153                                 self.weapon_morph2time = time + t;\r
1154                                 makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');\r
1155                                 self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1156 \r
1157                                 self.weapon_morph3angles = '0 0 0';\r
1158                                 self.weapon_morph3time = time + t;\r
1159                                 makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');\r
1160                                 self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1161 \r
1162                                 self.weapon_morph4angles = '0 0 0';\r
1163                                 self.weapon_morph4time = time + t;\r
1164                                 makevectors(self.weapon_morph4angles_x * '-1 0 0' + self.weapon_morph4angles_y * '0 1 0' + self.weapon_morph4angles_z * '0 0 1');\r
1165                                 self.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1166 \r
1167                                 if (fr == WFRAME_FIRE1)\r
1168                                 {\r
1169                                         self.weapon_morph1angles = '5 0 0';\r
1170                                         self.weapon_morph1time = time + t * 0.1;\r
1171                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1172                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1173                                         self.weapon_morph4time = time + t + 1; // delay idle effect\r
1174                                 }\r
1175                                 else if (fr == WFRAME_FIRE2)\r
1176                                 {\r
1177                                         self.weapon_morph1angles = '10 0 0';\r
1178                                         self.weapon_morph1time = time + t * 0.1;\r
1179                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1180                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1181                                         self.weapon_morph4time = time + t + 1; // delay idle effect\r
1182                                 }\r
1183                                 else if (fr == WFRAME_RELOAD)\r
1184                                 {\r
1185                                         self.weapon_morph1time = time + t * 0.05;\r
1186                                         self.weapon_morph1angles = '-10 40 0';\r
1187                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');\r
1188                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1189 \r
1190                                         self.weapon_morph2time = time + t * 0.15;\r
1191                                         self.weapon_morph2angles = '-10 40 5';\r
1192                                         makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');\r
1193                                         self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1194 \r
1195                                         self.weapon_morph3time = time + t * 0.25;\r
1196                                         self.weapon_morph3angles = '-10 40 0';\r
1197                                         makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');\r
1198                                         self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);\r
1199                                 }\r
1200                         }\r
1201                 }\r
1202                 else\r
1203                 {\r
1204                         if (fr == WFRAME_IDLE)\r
1205                                 a = self.weaponentity.anim_idle;\r
1206                         else if (fr == WFRAME_FIRE1)\r
1207                                 a = self.weaponentity.anim_fire1;\r
1208                         else if (fr == WFRAME_FIRE2)\r
1209                                 a = self.weaponentity.anim_fire2;\r
1210                         else if (fr == WFRAME_RELOAD)\r
1211                                 a = self.weaponentity.anim_reload;\r
1212                         a_z *= g_weaponratefactor;\r
1213                         setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);\r
1214                 }\r
1215         }\r
1216 \r
1217         v_forward = of;\r
1218         v_right = or;\r
1219         v_up = ou;\r
1220 \r
1221         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)\r
1222         {\r
1223                 backtrace("Tried to override initial weapon think function - should this really happen?");\r
1224         }\r
1225 \r
1226         t *= W_WeaponRateFactor();\r
1227 \r
1228         // VorteX: haste can be added here\r
1229         if (self.weapon_think == w_ready)\r
1230         {\r
1231                 self.weapon_nextthink = time;\r
1232                 //dprint("started firing at ", ftos(time), "\n");\r
1233         }\r
1234         if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)\r
1235         {\r
1236                 self.weapon_nextthink = time;\r
1237                 //dprint("reset weapon animation timer at ", ftos(time), "\n");\r
1238         }\r
1239         self.weapon_nextthink = self.weapon_nextthink + t;\r
1240         self.weapon_think = func;\r
1241         //dprint("next ", ftos(self.weapon_nextthink), "\n");\r
1242 \r
1243         if (restartanim)\r
1244         if (t)\r
1245         if (!self.crouch) // shoot anim stands up, this looks bad\r
1246         {\r
1247                 local vector anim;\r
1248                 anim = self.anim_shoot;\r
1249                 anim_z = anim_y / (t + sys_frametime);\r
1250                 setanim(self, anim, FALSE, TRUE, TRUE);\r
1251         }\r
1252 };\r
1253 \r
1254 void weapon_boblayer1(float spd, vector org)\r
1255 {\r
1256         // VorteX: haste can be added here\r
1257 };\r
1258 \r
1259 vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity)\r
1260 {\r
1261         vector mdirection;\r
1262         float mspeed;\r
1263         float outspeed;\r
1264         float nstyle;\r
1265         vector outvelocity;\r
1266 \r
1267         mvelocity = mvelocity * g_weaponspeedfactor;\r
1268 \r
1269         mdirection = normalize(mvelocity);\r
1270         mspeed = vlen(mvelocity);\r
1271 \r
1272         nstyle = cvar("g_projectiles_newton_style");\r
1273         if(nstyle == 0)\r
1274         {\r
1275                 // absolute velocity\r
1276                 outvelocity = mvelocity;\r
1277         }\r
1278         else if(nstyle == 1)\r
1279         {\r
1280                 // true Newtonian projectiles\r
1281                 outvelocity = pvelocity + mvelocity;\r
1282         }\r
1283         else if(nstyle == 2)\r
1284         {\r
1285                 // true Newtonian projectiles with automatic aim adjustment\r
1286                 //\r
1287                 // solve: |outspeed * mdirection - pvelocity| = mspeed\r
1288                 // outspeed^2 - 2 * outspeed * (mdirection * pvelocity) + pvelocity^2 - mspeed^2 = 0\r
1289                 // outspeed = (mdirection * pvelocity) +- sqrt((mdirection * pvelocity)^2 - pvelocity^2 + mspeed^2)\r
1290                 // PLUS SIGN!\r
1291                 // not defined?\r
1292                 // then...\r
1293                 // pvelocity^2 - (mdirection * pvelocity)^2 > mspeed^2\r
1294                 // velocity without mdirection component > mspeed\r
1295                 // fire at smallest possible mspeed that works?\r
1296                 // |(mdirection * pvelocity) * pvelocity - pvelocity| = mspeed\r
1297 \r
1298                 vector solution;\r
1299                 solution = solve_quadratic(1, -2 * (mdirection * pvelocity), pvelocity * pvelocity - mspeed * mspeed);\r
1300                 if(solution_z)\r
1301                         outspeed = solution_y; // the larger one\r
1302                 else\r
1303                 {\r
1304                         //outspeed = 0; // slowest possible shot\r
1305                         outspeed = solution_x; // the real part (that is, the average!)\r
1306                         //dprint("impossible shot, adjusting\n");\r
1307                 }\r
1308 \r
1309                 outspeed = bound(mspeed * cvar("g_projectiles_newton_style_2_minfactor"), outspeed, mspeed * cvar("g_projectiles_newton_style_2_maxfactor"));\r
1310                 outvelocity = mdirection * outspeed;\r
1311         }\r
1312         else if(nstyle == 3)\r
1313         {\r
1314                 // pseudo-Newtonian:\r
1315                 outspeed = mspeed + mdirection * pvelocity;\r
1316                 outspeed = bound(mspeed * 0.7, outspeed, mspeed * 5.0);\r
1317                 outvelocity = mdirection * outspeed;\r
1318         }\r
1319         else if(nstyle == 4)\r
1320         {\r
1321                 // tZorkian:\r
1322                 outspeed = mspeed + vlen(pvelocity);\r
1323                 outvelocity = mdirection * outspeed;\r
1324         }\r
1325         else\r
1326                 error("g_projectiles_newton_style must be 0 (absolute), 1 (Newtonian), 2 (Newtonian + aimfix), 3 (pseudo Newtonian) or 4 (tZorkian)!");\r
1327 \r
1328         return outvelocity;\r
1329 }\r
1330 \r
1331 void W_AttachToShotorg(entity flash, vector offset)\r
1332 {\r
1333         entity xflash;\r
1334         flash.owner = self;\r
1335         flash.angles_z = random() * 360;\r
1336         if(qcweaponanimation)\r
1337         {\r
1338                 setorigin(flash, w_shotorg + w_shotdir * 50);\r
1339                 flash.angles = vectoangles(w_shotdir);\r
1340                 flash.angles_z = random() * 360;\r
1341         }\r
1342         else\r
1343         {\r
1344                 setattachment(flash, self.weaponentity, "shot");\r
1345                 setorigin(flash, offset);\r
1346 \r
1347                 xflash = spawn();\r
1348                 copyentity(flash, xflash);\r
1349 \r
1350                 flash.viewmodelforclient = self;\r
1351 \r
1352                 if(self.weaponentity.oldorigin_x > 0)\r
1353                 {\r
1354                         setattachment(xflash, self.exteriorweaponentity, "");\r
1355                         setorigin(xflash, self.weaponentity.oldorigin + offset);\r
1356                 }\r
1357                 else\r
1358                 {\r
1359                         setattachment(xflash, self.exteriorweaponentity, "shot");\r
1360                 }\r
1361         }\r
1362 }\r
1363 \r
1364 vector cliptoplane(vector v, vector p)\r
1365 {\r
1366         return v - (v * p) * p;\r
1367 }\r
1368 \r
1369 vector solve_cubic_pq(float p, float q)\r
1370 {\r
1371         float D, u, v, a;\r
1372         D = q*q/4.0 + p*p*p/27.0;\r
1373         if(D < 0)\r
1374         {\r
1375                 // irreducibilis\r
1376                 a = 1.0/3.0 * acos(-q/2.0 * sqrt(-27.0/(p*p*p)));\r
1377                 u = sqrt(-4.0/3.0 * p);\r
1378                 // a in range 0..pi/3\r
1379                 // cos(a)\r
1380                 // cos(a + 2pi/3)\r
1381                 // cos(a + 4pi/3)\r
1382                 return\r
1383                         u *\r
1384                         (\r
1385                                 '1 0 0' * cos(a + 2.0/3.0*M_PI)\r
1386                                 +\r
1387                                 '0 1 0' * cos(a + 4.0/3.0*M_PI)\r
1388                                 +\r
1389                                 '0 0 1' * cos(a)\r
1390                         );\r
1391         }\r
1392         else if(D == 0)\r
1393         {\r
1394                 // simple\r
1395                 if(p == 0)\r
1396                         return '0 0 0';\r
1397                 u = 3*q/p;\r
1398                 v = -u/2;\r
1399                 if(u >= v)\r
1400                         return '1 1 0' * v + '0 0 1' * u;\r
1401                 else\r
1402                         return '0 1 1' * v + '1 0 0' * u;\r
1403         }\r
1404         else\r
1405         {\r
1406                 // cardano\r
1407                 u = cbrt(-q/2.0 + sqrt(D));\r
1408                 v = cbrt(-q/2.0 - sqrt(D));\r
1409                 return '1 1 1' * (u + v);\r
1410         }\r
1411 }\r
1412 vector solve_cubic_abcd(float a, float b, float c, float d)\r
1413 {\r
1414         // y = 3*a*x + b\r
1415         // x = (y - b) / 3a\r
1416         float p, q;\r
1417         vector v;\r
1418         p = (9*a*c - 3*b*b);\r
1419         q = (27*a*a*d - 9*a*b*c + 2*b*b*b);\r
1420         v = solve_cubic_pq(p, q);\r
1421         v = (v -  b * '1 1 1') * (1.0 / (3.0 * a));\r
1422         if(a < 0)\r
1423                 v += '1 0 -1' * (v_z - v_x); // swap x, z\r
1424         return v;\r
1425 }\r
1426 \r
1427 vector findperpendicular(vector v)\r
1428 {\r
1429         vector p;\r
1430         p_x = v_z;\r
1431         p_y = -v_x;\r
1432         p_z = v_y;\r
1433         return normalize(cliptoplane(p, v));\r
1434 }\r
1435 \r
1436 vector W_CalculateProjectileSpread(vector forward, float spread)\r
1437 {\r
1438         float sigma;\r
1439         vector v1, v2;\r
1440         float dx, dy, r;\r
1441         float sstyle;\r
1442         spread *= g_weaponspreadfactor;\r
1443         if(spread <= 0)\r
1444                 return forward;\r
1445         sstyle = cvar("g_projectiles_spread_style");\r
1446         \r
1447         if(sstyle == 0)\r
1448         {\r
1449                 // this is the baseline for the spread value!\r
1450                 // standard deviation: sqrt(2/5)\r
1451                 // density function: sqrt(1-r^2)\r
1452                 return forward + randomvec() * spread;\r
1453         }\r
1454         else if(sstyle == 1)\r
1455         {\r
1456                 // same thing, basically\r
1457                 return normalize(forward + cliptoplane(randomvec() * spread, forward));\r
1458         }\r
1459         else if(sstyle == 2)\r
1460         {\r
1461                 // circle spread... has at sigma=1 a standard deviation of sqrt(1/2)\r
1462                 sigma = spread * 0.89442719099991587855; // match baseline stddev\r
1463                 v1 = findperpendicular(forward);\r
1464                 v2 = cross(forward, v1);\r
1465                 // random point on unit circle\r
1466                 dx = random() * 2 * M_PI;\r
1467                 dy = sin(dx);\r
1468                 dx = cos(dx);\r
1469                 // radius in our dist function\r
1470                 r = random();\r
1471                 r = sqrt(r);\r
1472                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1473         }\r
1474         else if(sstyle == 3) // gauss 3d\r
1475         {\r
1476                 sigma = spread * 0.44721359549996; // match baseline stddev\r
1477                 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right\r
1478                 v1 = forward;\r
1479                 v1_x += gsl_ran_gaussian(sigma);\r
1480                 v1_y += gsl_ran_gaussian(sigma);\r
1481                 v1_z += gsl_ran_gaussian(sigma);\r
1482                 return v1;\r
1483         }\r
1484         else if(sstyle == 4) // gauss 2d\r
1485         {\r
1486                 sigma = spread * 0.44721359549996; // match baseline stddev\r
1487                 // note: 2D gaussian has sqrt(2) times the stddev of 1D, so this factor is right\r
1488                 v1_x = gsl_ran_gaussian(sigma);\r
1489                 v1_y = gsl_ran_gaussian(sigma);\r
1490                 v1_z = gsl_ran_gaussian(sigma);\r
1491                 return normalize(forward + cliptoplane(v1, forward));\r
1492         }\r
1493         else if(sstyle == 5) // 1-r\r
1494         {\r
1495                 sigma = spread * 1.154700538379252; // match baseline stddev\r
1496                 v1 = findperpendicular(forward);\r
1497                 v2 = cross(forward, v1);\r
1498                 // random point on unit circle\r
1499                 dx = random() * 2 * M_PI;\r
1500                 dy = sin(dx);\r
1501                 dx = cos(dx);\r
1502                 // radius in our dist function\r
1503                 r = random();\r
1504                 r = solve_cubic_abcd(-2, 3, 0, -r) * '0 1 0';\r
1505                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1506         }\r
1507         else if(sstyle == 6) // 1-r^2\r
1508         {\r
1509                 sigma = spread * 1.095445115010332; // match baseline stddev\r
1510                 v1 = findperpendicular(forward);\r
1511                 v2 = cross(forward, v1);\r
1512                 // random point on unit circle\r
1513                 dx = random() * 2 * M_PI;\r
1514                 dy = sin(dx);\r
1515                 dx = cos(dx);\r
1516                 // radius in our dist function\r
1517                 r = random();\r
1518                 r = sqrt(1 - r);\r
1519                 r = sqrt(1 - r);\r
1520                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1521         }\r
1522         else if(sstyle == 7) // (1-r) (2-r)\r
1523         {\r
1524                 sigma = spread * 1.224744871391589; // match baseline stddev\r
1525                 v1 = findperpendicular(forward);\r
1526                 v2 = cross(forward, v1);\r
1527                 // random point on unit circle\r
1528                 dx = random() * 2 * M_PI;\r
1529                 dy = sin(dx);\r
1530                 dx = cos(dx);\r
1531                 // radius in our dist function\r
1532                 r = random();\r
1533                 r = 1 - sqrt(r);\r
1534                 r = 1 - sqrt(r);\r
1535                 return normalize(forward + (v1 * dx + v2 * dy) * r * sigma);\r
1536         }\r
1537         else\r
1538                 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
1539         return '0 0 0';\r
1540         /*\r
1541          * how to derive falloff functions:\r
1542          * rho(r) := (2-r) * (1-r);\r
1543          * a : 0;\r
1544          * b : 1;\r
1545          * rhor(r) := r * rho(r);\r
1546          * cr(t) := integrate(rhor(r), r, a, t);\r
1547          * scr(t) := integrate(rhor(r) * r^2, r, a, t);\r
1548          * variance : scr(b) / cr(b);\r
1549          * solve(cr(r) = rand * cr(b), r), programmmode:false;\r
1550          * sqrt(0.4 / variance), numer;\r
1551          */\r
1552 }\r
1553 \r
1554 #if 0\r
1555 float mspercallsum;\r
1556 float mspercallsstyle;\r
1557 float mspercallcount;\r
1558 #endif\r
1559 void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread)\r
1560 {\r
1561         if(missile.owner == world)\r
1562                 error("Unowned missile");\r
1563 \r
1564         dir = dir + upDir * (pUpSpeed / pSpeed);\r
1565         dir_z += pZSpeed / pSpeed;\r
1566         pSpeed *= vlen(dir);\r
1567         dir = normalize(dir);\r
1568 \r
1569 #if 0\r
1570         if(cvar("g_projectiles_spread_style") != mspercallsstyle)\r
1571         {\r
1572                 mspercallsum = mspercallcount = 0;\r
1573                 mspercallsstyle = cvar("g_projectiles_spread_style");\r
1574         }\r
1575         mspercallsum -= gettime(GETTIME_HIRES);\r
1576 #endif\r
1577         dir = W_CalculateProjectileSpread(dir, spread);\r
1578 #if 0\r
1579         mspercallsum += gettime(GETTIME_HIRES);\r
1580         mspercallcount += 1;\r
1581         print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");\r
1582 #endif\r
1583 \r
1584         missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir);\r
1585 }\r
1586 \r
1587 void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)\r
1588 {\r
1589         W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread);\r
1590 }\r
1591 \r
1592 #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
1593 #define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"))\r
1594 \r
1595 void W_DisplayDigitThink()\r
1596 {\r
1597         self.nextthink = time;\r
1598         float w_load, w_ammo;\r
1599         w_load = self.owner.weapon_load[self.owner.weapon];\r
1600         w_ammo = self.owner.(self.owner.current_ammo);\r
1601 \r
1602         // the owner has switched to another weapon, remove the digits\r
1603         if(self.weapon != self.owner.weapon || self.owner.classname != "player" || self.deadflag != DEAD_NO)\r
1604         {\r
1605                 self.nextthink = 0;\r
1606                 remove(self);\r
1607                 self = world;\r
1608                 return;\r
1609         }\r
1610 \r
1611         entity gun;\r
1612         if(self.dmg) // exterior weapon\r
1613         {\r
1614                 // keep the digit attached to the same bone as the gun\r
1615                 setattachment(self, self.owner, "bip01 r hand");\r
1616                 gun = self.owner.exteriorweaponentity;\r
1617         }\r
1618         else // view weapon\r
1619         {\r
1620                 // keep the digit attached to the same bone as the gun\r
1621                 // TODO: Does this work with self-animated weapons too?\r
1622                 if(gettagindex(self.owner.weaponentity, "weapon"))\r
1623                         setattachment(self, self.owner.weaponentity, "weapon");\r
1624                 else if(gettagindex(self.owner.weaponentity, "tag_weapon"))\r
1625                         setattachment(self, self.owner.weaponentity, "tag_weapon");\r
1626                 gun = self.owner.weaponentity;\r
1627         }\r
1628 \r
1629         // copy all properties of the weapon to the digit\r
1630         self.origin = gun.origin;\r
1631         self.angles = gun.angles;\r
1632         self.scale = gun.scale;\r
1633         self.effects = gun.effects;\r
1634         self.alpha = gun.alpha;\r
1635         self.colormap = gun.colormap;\r
1636         self.colormod = gun.colormod; // used by the regurgitating colors\r
1637         self.glowmod = gun.glowmod;\r
1638 \r
1639         string txt;\r
1640         if(self.team) // weapon load display\r
1641         {\r
1642                 if(w_load <= 0)\r
1643                 {\r
1644                         self.skin = 11; // unavailable digit\r
1645                         return;\r
1646                 }\r
1647                 else\r
1648                 {\r
1649                         txt = ftos(floor(w_load));\r
1650                         txt = substring(txt, self.cnt - 1, 1);\r
1651                 }\r
1652 \r
1653                 if(self.owner.weapon_load[self.owner.weapon] <= ceil(cvar("g_gundisplay_warn_load")))\r
1654                 {\r
1655                         // in warning mode, only keep red color\r
1656                         if(!self.colormod)\r
1657                                 self.colormod = '1 1 1';\r
1658                         self.colormod_y = 0;\r
1659                         self.colormod_z = 0;\r
1660                 }\r
1661         }\r
1662         else // ammo display\r
1663         {\r
1664                 txt = ftos(floor(w_ammo));\r
1665                 txt = substring(txt, self.cnt - 1, 1);\r
1666 \r
1667                 if(w_ammo <= ceil(cvar("g_gundisplay_warn_ammo")))\r
1668                 {\r
1669                         // in warning mode, only keep red color\r
1670                         if(!self.colormod)\r
1671                                 self.colormod = '1 1 1';\r
1672                         self.colormod_y = 0;\r
1673                         self.colormod_z = 0;\r
1674                 }\r
1675         }\r
1676 \r
1677         if((!txt || txt == ""))\r
1678                 self.skin = 10; // empty digit\r
1679         else\r
1680                 self.skin = stof(txt);\r
1681 }\r
1682 \r
1683 void W_DisplayDigitSetup(entity own, float num, float load, float exterior)\r
1684 {\r
1685         entity digit, e;\r
1686         digit = spawn();\r
1687         digit.owner = own;\r
1688         digit.weapon = own.weapon;\r
1689         digit.dmg = exterior;\r
1690         digit.team = load;\r
1691         digit.cnt = num;\r
1692         e = get_weaponinfo(digit.weapon);\r
1693 \r
1694         if(load)\r
1695         {\r
1696                 // weapon load digit\r
1697                 setmodel(digit, strcat("models/weapons/v_", e.netname, "_digit1-", ftos(num) , ".md3"));\r
1698         }\r
1699         else\r
1700         {\r
1701                 // ammo count digit\r
1702                 setmodel(digit, strcat("models/weapons/v_", e.netname, "_digit2-", ftos(num) , ".md3"));\r
1703         }\r
1704         digit.think = W_DisplayDigitThink;\r
1705         digit.nextthink = time;\r
1706 }\r
1707 \r
1708 void W_Display(entity own, float load_num, float ammo_num)\r
1709 {\r
1710         float i;\r
1711         for(i = 1; i <= load_num; i++)\r
1712         {\r
1713                 W_DisplayDigitSetup(own, i, TRUE, FALSE); // weapon load digit, view model\r
1714                 W_DisplayDigitSetup(own, i, TRUE, TRUE); // weapon load digit, exterior model\r
1715         }\r
1716         for(i = 1; i <= ammo_num; i++)\r
1717         {\r
1718                 W_DisplayDigitSetup(own, i, FALSE, FALSE); // ammo count digit, view model\r
1719                 W_DisplayDigitSetup(own, i, FALSE, TRUE); // ammo count digit, exterior model\r
1720         }\r
1721 }\r
1722 \r
1723 void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload)\r
1724 {\r
1725         if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)\r
1726                 return;\r
1727 \r
1728         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo\r
1729         if(ammo_reload)\r
1730         {\r
1731                 self.clip_load -= ammo_use;\r
1732                 self.(weapon_load[self.weapon]) = self.clip_load;\r
1733         }\r
1734         else\r
1735                 self.(self.current_ammo) -= ammo_use;\r
1736 }\r
1737 \r
1738 // weapon reloading code\r
1739 \r
1740 .float reload_ammo_amount, reload_ammo_min, reload_time;\r
1741 .float reload_complain;\r
1742 .string reload_sound;\r
1743 \r
1744 void W_ReloadedAndReady()\r
1745 {\r
1746         // if we are inside the stomach, don't allow reloading, and schedule the weapon to reload once when we're out\r
1747         if(self.stat_eaten)\r
1748         {\r
1749                 self.clip_load = self.(weapon_load[self.weapon]) = -1;\r
1750                 w_ready(); // don't keep executing each frame\r
1751                 return;\r
1752         }\r
1753 \r
1754         // finish the reloading process, and do the ammo transfer\r
1755 \r
1756         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading\r
1757 \r
1758         // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load\r
1759         if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)\r
1760                 self.clip_load = self.reload_ammo_amount;\r
1761         else\r
1762         {\r
1763                 while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo) >= 1) // make sure we don't add more ammo than we have\r
1764                 {\r
1765                         self.clip_load += 1;\r
1766                         self.(self.current_ammo) -= 1;\r
1767                 }\r
1768         }\r
1769         self.(weapon_load[self.weapon]) = self.clip_load;\r
1770 \r
1771         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,\r
1772         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,\r
1773         // so your weapon is disabled for a few seconds without reason\r
1774 \r
1775         //ATTACK_FINISHED(self) -= self.reload_time - 1;\r
1776 \r
1777         w_ready();\r
1778 }\r
1779 \r
1780 void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)\r
1781 {\r
1782         // if we are inside the stomach, don't allow reloading\r
1783         if(self.stat_eaten)\r
1784                 return;\r
1785 \r
1786         // set global values to work with\r
1787         self.reload_ammo_min = sent_ammo_min;\r
1788         self.reload_ammo_amount = sent_ammo_amount;\r
1789         self.reload_time = sent_time;\r
1790         self.reload_sound = sent_sound;\r
1791 \r
1792         // check if we meet the necessary conditions to reload\r
1793 \r
1794         entity e;\r
1795         e = get_weaponinfo(self.weapon);\r
1796 \r
1797         // don't reload weapons that don't have the RELOADABLE flag\r
1798         if not(e.spawnflags & WEP_FLAG_RELOADABLE)\r
1799         {\r
1800                 dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");\r
1801                 return;\r
1802         }\r
1803         // return if reloading is disabled for this weapon\r
1804         if(!self.reload_ammo_amount)\r
1805                 return;\r
1806         // our weapon is fully loaded, no need to reload\r
1807         if (self.clip_load >= self.reload_ammo_amount)\r
1808                 return;\r
1809         // no ammo, so nothing to load\r
1810         if(self.(self.current_ammo) < 1 && self.reload_ammo_min)\r
1811         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)\r
1812         {\r
1813                 if(clienttype(self) == CLIENTTYPE_REAL && self.reload_complain < time)\r
1814                 {\r
1815                         play2(self, "misc/unavailable.wav");\r
1816                         sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));\r
1817                         self.reload_complain = time + 1;\r
1818                 }\r
1819                 // switch away if the amount of ammo is not enough to keep using this weapon\r
1820                 // disabled since we only have one weapon in VT, so nothing else to switch to if we're out of ammo\r
1821                 /*if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2))\r
1822                 {\r
1823                         self.clip_load = -1; // reload later\r
1824                         W_SwitchToOtherWeapon(self);\r
1825                 }*/\r
1826                 return;\r
1827         }\r
1828 \r
1829         if (self.weaponentity)\r
1830         {\r
1831                 if (self.weaponentity.wframe == WFRAME_RELOAD)\r
1832                         return;\r
1833 \r
1834                 // allow switching away while reloading, but this will cause a new reload!\r
1835                 self.weaponentity.state = WS_READY;\r
1836         }\r
1837 \r
1838         // now begin the reloading process\r
1839 \r
1840         // weapon reload effects\r
1841         if(self.weapon == WEP_GRABBER && self.clip_load >= 0) // only when we first begin reloading\r
1842         {\r
1843                 vector org;\r
1844                 org = self.origin + self.view_ofs + self.weaponentity.spawnorigin_x * v_forward - self.weaponentity.spawnorigin_y * v_right + self.weaponentity.spawnorigin_z * v_up;\r
1845                 SpawnCasing (org, ((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self);\r
1846                 pointparticles(particleeffectnum("grabber_reload"), org, '0 0 0', 1);\r
1847         }\r
1848         sound (self, CHAN_WEAPON2, self.reload_sound, VOL_BASE, ATTN_NORM);\r
1849 \r
1850         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,\r
1851         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,\r
1852         // so your weapon is disabled for a few seconds without reason\r
1853 \r
1854         //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;\r
1855 \r
1856         weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);\r
1857 \r
1858         if(self.clip_load < 0)\r
1859                 self.clip_load = 0;\r
1860         self.old_clip_load = self.clip_load;\r
1861         self.clip_load = self.(weapon_load[self.weapon]) = -1;\r
1862 }