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