]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/cl_weaponsystem.qc
fix two bugs in hitplotting
[divverent/nexuiz.git] / data / qcsrc / server / cl_weaponsystem.qc
1 /*
2 ===========================================================================
3
4   CLIENT WEAPONSYSTEM CODE
5   Bring back W_Weaponframe
6
7 ===========================================================================
8 */
9
10 void W_SwitchWeapon_Force(entity e, float w)
11 {
12         e.cnt = e.switchweapon;
13         e.switchweapon = w;
14 }
15
16 .float antilag_debug;
17
18 // VorteX: static frame globals
19 float WFRAME_DONTCHANGE = -1;
20 float WFRAME_FIRE1 = 0;
21 float WFRAME_FIRE2 = 1;
22 float WFRAME_IDLE = 2;
23 float WFRAME_RELOAD = 3;
24 .float wframe;
25
26 void(float fr, float t, void() func) weapon_thinkf;
27
28 vector W_HitPlotUnnormalizedUntransform(vector screenforward, vector screenright, vector screenup, vector v)
29 {
30         vector ret;
31         ret_x = screenright * v;
32         ret_y = screenup * v;
33         ret_z = screenforward * v;
34         return ret;
35 }
36
37 vector W_HitPlotNormalizedUntransform(vector org, entity targ, vector screenforward, vector screenright, vector screenup, vector v)
38 {
39         float i, j, k;
40         vector mi, ma, thisv, myv, ret;
41
42         myv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, org);
43
44         // x = 0..1 relative to hitbox; y = 0..1 relative to hitbox; z = distance
45
46         for(i = 0; i < 2; ++i) for(j = 0; j < 2; ++j) for(k = 0; k < 2; ++k)
47         {
48                 thisv = targ.origin;
49                 if(i) thisv_x += targ.maxs_x; else thisv_x += targ.mins_x;
50                 if(j) thisv_y += targ.maxs_y; else thisv_y += targ.mins_y;
51                 if(k) thisv_z += targ.maxs_z; else thisv_z += targ.mins_z;
52                 thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, thisv);
53                 if(i || j || k)
54                 {
55                         if(mi_x > thisv_x) mi_x = thisv_x; if(ma_x < thisv_x) ma_x = thisv_x;
56                         if(mi_y > thisv_y) mi_y = thisv_y; if(ma_y < thisv_y) ma_y = thisv_y;
57                         //if(mi_z > thisv_z) mi_z = thisv_z; if(ma_z < thisv_z) ma_y = thisv_z;
58                 }
59                 else
60                 {
61                         // first run
62                         mi = ma = thisv;
63                 }
64         }
65
66         thisv = W_HitPlotUnnormalizedUntransform(screenforward, screenright, screenup, v);
67         ret_x = (thisv_x - mi_x) / (ma_x - mi_x);
68         ret_y = (thisv_y - mi_y) / (ma_y - mi_y);
69         ret_z = thisv_z - myv_z;
70         return ret;
71 }
72
73 void W_HitPlotAnalysis(entity player, vector screenforward, vector screenright, vector screenup)
74 {
75         vector hitplot; 
76         vector org;
77         float lag;
78
79         if(player.hitplotfh >= 0)
80         {
81                 lag = ANTILAG_LATENCY(player);
82                 org = player.origin + player.view_ofs;
83                 traceline_antilag_force(player, org, org + screenforward * MAX_SHOT_DISTANCE, MOVE_NORMAL, player, lag);
84                 if(trace_ent.flags & FL_CLIENT)
85                 {
86                         antilag_takeback(trace_ent, time - lag);
87                         hitplot = W_HitPlotNormalizedUntransform(org, trace_ent, screenforward, screenright, screenup, trace_endpos);
88                         antilag_restore(trace_ent);
89                         fputs(player.hitplotfh, strcat(ftos(hitplot_x), " ", ftos(hitplot_y), " ", ftos(hitplot_z), "\n"));
90                 }
91         }
92 }
93
94 vector w_shotorg;
95 vector w_shotdir;
96
97 // this function calculates w_shotorg and w_shotdir based on the weapon model
98 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.
99 // make sure you call makevectors first (FIXME?)
100 void W_SetupShot(entity ent, float antilag, float recoil, string snd)
101 {
102         float nudge = 1; // added to traceline target and subtracted from result
103         local vector trueaimpoint;
104         local float oldsolid;
105         vector vecs;
106         oldsolid = ent.dphitcontentsmask;
107         ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
108         traceline(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + v_forward * MAX_SHOT_DISTANCE, MOVE_NOMONSTERS, ent);
109         trueaimpoint = trace_endpos;
110
111         W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
112
113         if(ent.weaponentity.movedir_x > 0)
114         {
115                 vecs = ent.weaponentity.movedir;
116                 vecs_y = -vecs_y;
117         }
118         else
119                 vecs = '0 0 0';
120
121         if(debug_shotorg != '0 0 0')
122                 vecs = debug_shotorg;
123         
124         w_shotorg = ent.origin + ent.view_ofs + v_right * vecs_y + v_up * vecs_z;
125
126         // now move the shotorg forward as much as requested if possible
127         traceline(w_shotorg, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent);
128         w_shotorg = trace_endpos - v_forward * nudge;
129         // calculate the shotdir from the chosen shotorg
130         w_shotdir = normalize(trueaimpoint - w_shotorg);
131
132 #if 0
133         // explanation of g_antilag:
134         // if client reports it was aiming at a player, and the serverside trace
135         // says it would miss, change the aim point to the player's new origin,
136         // but only if the shot at the player's new origin would hit of course
137         //
138         // FIXME: a much better method for bullet weapons would be to leave a
139         // trail of lagged 'ghosts' behind players, and see if the bullet hits the
140         // ghost corresponding to this player's ping time, and if so it would do
141         // damage to the real player
142         if (antilag)
143         if (ent.cursor_trace_ent)                 // client was aiming at someone
144         if (ent.cursor_trace_ent != ent)         // just to make sure
145         if (ent.cursor_trace_ent.takedamage)      // and that person is killable
146         if (ent.cursor_trace_ent.classname == "player") // and actually a player
147         if (cvar("g_antilag") == 1)
148         {
149                 // verify that the shot would miss without antilag
150                 // (avoids an issue where guns would always shoot at their origin)
151                 traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent);
152                 if (!trace_ent.takedamage)
153                 {
154                         // verify that the shot would hit if altered
155                         traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
156                         if (trace_ent == ent.cursor_trace_ent)
157                         {
158                                 // verify that the shot would hit in the past
159                                 if(ent.antilag_debug)
160                                         antilag_takeback(ent.cursor_trace_ent, time - ent.antilag_debug);
161                                 else
162                                         antilag_takeback(ent.cursor_trace_ent, time - ANTILAG_LATENCY(ent));
163
164                                 traceline(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent);
165                                 antilag_restore(ent.cursor_trace_ent);
166
167                                 if(trace_ent == ent.cursor_trace_ent)
168                                 {
169                                         // HIT!
170                                         w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
171                                         dprint("ANTILAG HIT for ", ent.netname, "\n");
172                                 }
173                                 else
174                                 {
175                                         // prydon cursor aimbot or odd network conditions
176                                         dprint("WARNING: antilag ghost trace for ", ent.netname, " failed!\n");
177                                 }
178
179                                 if(cvar("developer") >= 2)
180                                 {
181                                         vector v, vplus, vel;
182                                         float X;
183                                         v     = antilag_takebackorigin(ent.cursor_trace_ent, time - (ANTILAG_LATENCY(ent)       ));
184                                         vplus = antilag_takebackorigin(ent.cursor_trace_ent, time - (ANTILAG_LATENCY(ent) + 0.01));
185                                         vel = (vplus - v) * (1 / 0.01);
186                                         // solve: v + X * vel = closest to ent.origin + ent.view_ofs, v_forward axis
187                                         v     -= (ent.origin + ent.view_ofs);
188                                         // solve: v + X * vel = closest to v_forward axis
189                                         // project into 2D by subtracting v_forward components:
190                                         v   -= (v   * v_forward) * v_forward;
191                                         vel -= (vel * v_forward) * v_forward;
192                                         // solve: v + X * vel = closest to origin
193                                         // (v + X * vel)^2 closest to 0
194                                         // v^2 + 2 * X * (v * vel) + X^2 * vel^2 closest to 0
195                                         X = -(v * vel) / (vel * vel);
196                                         dprint("dead center needs adjustment of ", ftos(X), " (that is, ", ftos(ANTILAG_LATENCY(ent) + X), " instead of ", ftos(ANTILAG_LATENCY(ent)), "\n");
197                                 }
198                         }
199                 }
200         }
201 #else
202         if (antilag)
203         {
204                 if (cvar("g_antilag") == 1) // switch to "ghost" if not hitting original
205                 {
206                         traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent);
207                         if (!trace_ent.takedamage)
208                         {
209                                 traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
210                                 if (trace_ent.takedamage && trace_ent.classname == "player")
211                                 {
212                                         entity e;
213                                         e = trace_ent;
214                                         traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
215                                         if(trace_ent == e)
216                                                 w_shotdir = normalize(trace_ent.origin - w_shotorg);
217                                 }
218                         }
219                 }
220                 else if(cvar("g_antilag") == 3) // client side hitscan
221                 {
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 #endif
243
244         ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
245
246         if (!g_norecoil)
247                 ent.punchangle_x = recoil * -1;
248
249         if (snd != "")
250         {
251                 sound (ent, CHAN_WEAPON, snd, VOL_BASE, ATTN_NORM);
252         }
253
254         if (ent.items & IT_STRENGTH)
255         if (!g_minstagib)
256                 sound (ent, CHAN_AUTO, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
257 };
258
259 void LaserTarget_Think()
260 {
261         entity e;
262         vector offset;
263         float uselaser;
264         uselaser = 0;
265
266         // list of weapons that will use the laser, and the options that enable it
267         if(self.owner.laser_on && self.owner.weapon == WEP_ROCKET_LAUNCHER && g_laserguided_missile)
268                 uselaser = 1;
269         // example
270         //if(self.owner.weapon == WEP_ELECTRO && cvar("g_laserguided_electro"))
271         //      uselaser = 1;
272
273
274
275         // if a laser-enabled weapon isn't selected, delete any existing laser and quit
276         if(!uselaser)
277         {
278                 // rocket launcher isn't selected, so no laser target.
279                 if(self.lasertarget != world)
280                 {
281                         remove(self.lasertarget);
282                         self.lasertarget = world;
283                 }
284                 return;
285         }
286
287         if(!self.lasertarget)
288         {
289                 // we don't have a lasertarget entity, so spawn one
290                 //bprint("create laser target\n");
291                 e = self.lasertarget = spawn();
292                 e.owner = self.owner;                   // Its owner is my owner
293                 e.classname = "laser_target";
294                 e.movetype = MOVETYPE_NOCLIP;   // don't touch things
295                 setmodel(e, "models/laser_dot.mdl");    // what it looks like, precision set below
296                 e.scale = 1.25;                         // make it larger
297                 e.alpha = 0.25;                         // transparency
298                 e.colormod = '255 0 0' * (1/255) * 8;   // change colors
299                 e.effects = EF_FULLBRIGHT | EF_LOWPRECISION;
300                 // make it dynamically glow
301                 // you should avoid over-using this, as it can slow down the player's computer.
302                 e.glow_color = 251; // red color
303                 e.glow_size = 12;
304         }
305         else
306                 e = self.lasertarget;
307
308         // move the laser dot to where the player is looking
309
310         makevectors(self.owner.v_angle); // set v_forward etc to the direction the player is looking
311         offset = '0 0 26' + v_right*3;
312         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
313         setorigin(e, trace_endpos + v_forward*8); // move me to where the traceline ended
314         if(trace_plane_normal != '0 0 0')
315                 e.angles = vectoangles(trace_plane_normal);
316         else
317                 e.angles = vectoangles(v_forward);
318 }
319
320 float CL_Weaponentity_CustomizeEntityForClient()
321 {
322         self.viewmodelforclient = self.owner;
323         if(other.classname == "spectator")
324                 if(other.enemy == self.owner)
325                         self.viewmodelforclient = other;
326         return TRUE;
327 }
328
329 float qcweaponanimation;
330 vector weapon_offset = '0 -10 0';
331 vector weapon_adjust = '10 0 -15';
332 .vector weapon_morph0origin;
333 .vector weapon_morph0angles;
334 .float  weapon_morph0time;
335 .vector weapon_morph1origin;
336 .vector weapon_morph1angles;
337 .float  weapon_morph1time;
338 .vector weapon_morph2origin;
339 .vector weapon_morph2angles;
340 .float  weapon_morph2time;
341 .vector weapon_morph3origin;
342 .vector weapon_morph3angles;
343 .float  weapon_morph3time;
344 .vector weapon_morph4origin;
345 .vector weapon_morph4angles;
346 .float  weapon_morph4time;
347 .string weaponname;
348 #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)
349
350 /*
351  * supported formats:
352  *
353  * 1. simple animated model, muzzlr flash handling on h_ model:
354  *    h_tuba.dpm, h_tuba.dpm.animinfo - invisible model controlling the animation
355  *      tags:
356  *        shot = muzzle end (shot origin, also used for muzzle flashes)
357  *        shell = casings ejection point (must be on the right hand side of the gun)
358  *        weapon = attachment for v_tuba.md3
359  *    v_tuba.md3 - first and third person model
360  *    g_tuba.md3 - pickup model
361  *
362  * 2. fully animated model, muzzle flash handling on h_ model:
363  *    h_tuba.dpm, h_tuba.dpm.animinfo - animated first person model
364  *      tags:
365  *        shot = muzzle end (shot origin, also used for muzzle flashes)
366  *        shell = casings ejection point (must be on the right hand side of the gun)
367  *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
368  *    v_tuba.md3 - third person model
369  *    g_tuba.md3 - pickup model
370  *
371  * 3. fully animated model, muzzle flash handling on v_ model:
372  *    h_tuba.dpm, h_tuba.dpm.animinfo - animated first person model
373  *      tags:
374  *        shot = muzzle end (shot origin)
375  *        shell = casings ejection point (must be on the right hand side of the gun)
376  *    v_tuba.md3 - third person model
377  *      tags:
378  *        shot = muzzle end (for muzzle flashes)
379  *    g_tuba.md3 - pickup model
380  */
381
382 void CL_Weaponentity_Think()
383 {
384         float tb, v_shot_idx;
385         self.nextthink = time;
386         if (intermission_running)
387                 self.frame = self.anim_idle_x;
388         if (self.owner.weaponentity != self)
389         {
390                 if (self.weaponentity)
391                         remove(self.weaponentity);
392                 remove(self);
393                 return;
394         }
395         if (self.owner.deadflag != DEAD_NO)
396         {
397                 self.model = "";
398                 if (self.weaponentity)
399                         self.weaponentity.model = "";
400                 return;
401         }
402         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
403         {
404                 self.cnt = self.owner.weapon;
405                 self.dmg = self.owner.modelindex;
406                 self.deadflag = self.owner.deadflag;
407
408                 string animfilename;
409                 float animfile;
410                 if (self.owner.weaponname != "")
411                 {
412                         // if there is a child entity, hide it until we're sure we use it
413                         if (self.weaponentity)
414                                 self.weaponentity.model = "";
415                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
416                         v_shot_idx = gettagindex(self, "shot"); // used later
417
418                         if(qcweaponanimation)
419                         {
420                                 self.angles = '0 0 0';
421                                 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
422                                 self.movedir = weapon_offset_x * v_forward - weapon_offset_y * v_right + weapon_offset_z * v_up + weapon_adjust;
423                                 self.movedir_x += 32;
424                                 self.spawnorigin = self.movedir;
425                                 // oldorigin - not calculated here
426                         }
427                         else
428                         {
429                                 setmodel(self, strcat("models/weapons/h_", self.owner.weaponname, ".dpm")); // precision set below
430                                 animfilename = strcat("models/weapons/h_", self.owner.weaponname, ".dpm.animinfo");
431                                 animfile = fopen(animfilename, FILE_READ);
432                                 // preset some defaults that work great for renamed zym files (which don't need an animinfo)
433                                 self.anim_fire1  = '0 1 0.01';
434                                 self.anim_fire2  = '1 1 0.01';
435                                 self.anim_idle   = '2 1 0.01';
436                                 self.anim_reload = '3 1 0.01';
437                                 if (animfile >= 0)
438                                 {
439                                         animparseerror = FALSE;
440                                         self.anim_fire1  = animparseline(animfile);
441                                         self.anim_fire2  = animparseline(animfile);
442                                         self.anim_idle   = animparseline(animfile);
443                                         self.anim_reload = animparseline(animfile);
444                                         fclose(animfile);
445                                         if (animparseerror)
446                                                 print("Parse error in ", animfilename, ", some player animations are broken\n");
447                                 }
448
449                                 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
450                                 // if we don't, this is a "real" animated model
451                                 if(gettagindex(self, "weapon"))
452                                 {
453                                         if (!self.weaponentity)
454                                                 self.weaponentity = spawn();
455                                         setmodel(self.weaponentity, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision does not matter
456                                         setattachment(self.weaponentity, self, "weapon");
457                                 }
458                                 else
459                                 {
460                                         if(self.weaponentity)
461                                                 remove(self.weaponentity);
462                                         self.weaponentity = world;
463                                 }
464
465                                 self.origin = '0 0 0';
466                                 self.angles = '0 0 0';
467                                 self.frame = 0;
468                                 self.viewmodelforclient = world;
469
470                                 float idx;
471                                 idx = gettagindex(self, "shot");
472                                 if(idx)
473                                 {
474                                         self.movedir = gettaginfo(self, idx);
475                                 }
476                                 else
477                                 {
478                                         print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
479                                         self.movedir = '0 0 0';
480                                 }
481
482                                 idx = gettagindex(self, "shell");
483                                 if(idx)
484                                 {
485                                         self.spawnorigin = gettaginfo(self, idx);
486                                 }
487                                 else
488                                 {
489                                         print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
490                                         self.spawnorigin = self.movedir;
491                                 }
492
493                                 if(v_shot_idx)
494                                 {
495                                         self.oldorigin = '0 0 0';
496                                 }
497                                 else
498                                 {
499                                         if(self.weaponentity)
500                                                 idx = gettagindex(self, "weapon");
501                                         else
502                                                 idx = gettagindex(self, "handle");
503                                         if(idx)
504                                         {
505                                                 self.oldorigin = self.movedir - gettaginfo(self, idx);
506                                         }
507                                         else
508                                         {
509                                                 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");
510                                                 self.oldorigin = '0 0 0'; // there is no way to recover from this
511                                         }
512                                 }
513
514                                 self.viewmodelforclient = self.owner;
515                         }
516                 }
517                 else
518                 {
519                         self.model = "";
520                         if(self.weaponentity)
521                                 remove(self.weaponentity);
522                         self.weaponentity = world;
523                         self.movedir = '0 0 0';
524                         self.spawnorigin = '0 0 0';
525                         self.oldorigin = '0 0 0';
526                         self.anim_fire1  = '0 1 0.01';
527                         self.anim_fire2  = '0 1 0.01';
528                         self.anim_idle   = '0 1 0.01';
529                         self.anim_reload = '0 1 0.01';
530                 }
531
532                 self.view_ofs = '0 0 0';
533
534                 if(self.movedir_x >= 0)
535                 {
536                         vector v0;
537                         v0 = self.movedir;
538                         self.movedir = shotorg_adjust(v0, FALSE, FALSE);
539                         self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
540                 }
541                 self.owner.stat_shotorg = compressShotOrigin(self.movedir);
542                 self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
543
544                 self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
545
546                 // check if an instant weapon switch occurred
547                 if (qcweaponanimation)
548                 {
549                         if (self.state == WS_READY)
550                         {
551                                 self.angles = '0 0 0';
552                                 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
553                                 setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
554                         }
555                 }
556                 else
557                         setorigin(self, self.view_ofs);
558                 // reset animstate now
559                 self.wframe = WFRAME_IDLE;
560                 self.weapon_morph0time = 0;
561                 self.weapon_morph1time = 0;
562                 self.weapon_morph2time = 0;
563                 self.weapon_morph3time = 0;
564                 self.weapon_morph4time = 0;
565                 setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
566         }
567
568         tb = (self.effects & EF_TELEPORT_BIT);
569         self.effects = self.owner.effects & EFMASK_CHEAP;
570         self.effects &~= EF_LOWPRECISION;
571         self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it
572         self.effects &~= EF_TELEPORT_BIT;
573         self.effects |= tb;
574
575         if(self.owner.alpha != 0)
576                 self.alpha = self.owner.alpha;
577         else
578                 self.alpha = 1;
579         
580         self.colormap = self.owner.colormap;
581         if (self.weaponentity)
582         {
583                 self.weaponentity.effects = self.effects;
584                 self.weaponentity.alpha = self.alpha;
585                 self.weaponentity.colormap = self.colormap;
586         }
587
588         self.angles = '0 0 0';
589         local float f;
590         f = 0;
591         if (self.state == WS_RAISE)
592         {
593                 f = (self.owner.weapon_nextthink - time) / cvar("g_balance_weaponswitchdelay");
594                 self.angles_x = -90 * f * f;
595                 if (qcweaponanimation)
596                 {
597                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
598                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
599                 }
600         }
601         else if (self.state == WS_DROP)
602         {
603                 f = 1 - (self.owner.weapon_nextthink - time) / cvar("g_balance_weaponswitchdelay");
604                 self.angles_x = -90 * f * f;
605                 if (qcweaponanimation)
606                 {
607                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
608                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
609                 }
610         }
611         else if (self.state == WS_CLEAR)
612         {
613                 f = 1;
614                 self.angles_x = -90 * f * f;
615                 if (qcweaponanimation)
616                 {
617                         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
618                         setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
619                 }
620         }
621         else if (qcweaponanimation && time < self.owner.weapon_morph1time)
622         {
623                 f = (time - self.owner.weapon_morph0time) / (self.owner.weapon_morph1time - self.owner.weapon_morph0time);
624                 f = 1 - pow(1 - f, 3);
625                 self.angles = self.owner.weapon_morph0angles * (1 - f) + self.owner.weapon_morph1angles * f;
626                 setorigin(self, self.owner.weapon_morph0origin * (1 - f) + self.owner.weapon_morph1origin * f);
627         }
628         else if (qcweaponanimation && time < self.owner.weapon_morph2time)
629         {
630                 f = (time - self.owner.weapon_morph1time) / (self.owner.weapon_morph2time - self.owner.weapon_morph1time);
631                 f = 1 - pow(1 - f, 3);
632                 self.angles = self.owner.weapon_morph1angles * (1 - f) + self.owner.weapon_morph2angles * f;
633                 setorigin(self, self.owner.weapon_morph1origin * (1 - f) + self.owner.weapon_morph2origin * f);
634         }
635         else if (qcweaponanimation && time < self.owner.weapon_morph3time)
636         {
637                 f = (time - self.owner.weapon_morph2time) / (self.owner.weapon_morph3time - self.owner.weapon_morph2time);
638                 f = 1 - pow(1 - f, 3);
639                 self.angles = self.owner.weapon_morph2angles * (1 - f) + self.owner.weapon_morph3angles * f;
640                 setorigin(self, self.owner.weapon_morph2origin * (1 - f) + self.owner.weapon_morph3origin * f);
641         }
642         else if (qcweaponanimation && time < self.owner.weapon_morph4time)
643         {
644                 f = (time - self.owner.weapon_morph3time) / (self.owner.weapon_morph4time - self.owner.weapon_morph3time);
645                 f = 1 - pow(1 - f, 3);
646                 self.angles = self.owner.weapon_morph3angles * (1 - f) + self.owner.weapon_morph4angles * f;
647                 setorigin(self, self.owner.weapon_morph3origin * (1 - f) + self.owner.weapon_morph4origin * f);
648         }
649         else if (qcweaponanimation)
650         {
651                 // begin a new idle morph
652                 self.owner.weapon_morph0time   = time;
653                 self.owner.weapon_morph0angles = self.angles;
654                 self.owner.weapon_morph0origin = self.origin;
655
656                 float r;
657                 float t;
658
659                 r = random();
660                 if (r < 0.1)
661                 {
662                         // turn gun to the left to look at it
663                         t = 2;
664                         self.owner.weapon_morph1time   = time + t * 0.2;
665                         self.owner.weapon_morph1angles = randomvec() * 3 + '-5 30 0';
666                         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');
667                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
668
669                         self.owner.weapon_morph2time   = time + t * 0.6;
670                         self.owner.weapon_morph2angles = randomvec() * 3 + '-5 30 0';
671                         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');
672                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
673
674                         self.owner.weapon_morph3time   = time + t;
675                         self.owner.weapon_morph3angles = '0 0 0';
676                         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');
677                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
678                 }
679                 else if (r < 0.2)
680                 {
681                         // raise the gun a bit
682                         t = 2;
683                         self.owner.weapon_morph1time   = time + t * 0.2;
684                         self.owner.weapon_morph1angles = randomvec() * 3 + '30 -10 0';
685                         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');
686                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
687
688                         self.owner.weapon_morph2time   = time + t * 0.5;
689                         self.owner.weapon_morph2angles = randomvec() * 3 + '30 -10 5';
690                         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');
691                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
692
693                         self.owner.weapon_morph3time   = time + t;
694                         self.owner.weapon_morph3angles = '0 0 0';
695                         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');
696                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
697                 }
698                 else if (r < 0.3)
699                 {
700                         // tweak it a bit
701                         t = 5;
702                         self.owner.weapon_morph1time   = time + t * 0.3;
703                         self.owner.weapon_morph1angles = randomvec() * 6;
704                         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');
705                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
706
707                         self.owner.weapon_morph2time   = time + t * 0.7;
708                         self.owner.weapon_morph2angles = randomvec() * 6;
709                         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');
710                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
711
712                         self.owner.weapon_morph3time   = time + t;
713                         self.owner.weapon_morph3angles = '0 0 0';
714                         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');
715                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
716                 }
717                 else
718                 {
719                         // hold it mostly steady
720                         t = random() * 6 + 4;
721                         self.owner.weapon_morph1time   = time + t * 0.2;
722                         self.owner.weapon_morph1angles = randomvec() * 1;
723                         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');
724                         self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
725
726                         self.owner.weapon_morph2time   = time + t * 0.5;
727                         self.owner.weapon_morph2angles = randomvec() * 1;
728                         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');
729                         self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
730
731                         self.owner.weapon_morph3time   = time + t * 0.7;
732                         self.owner.weapon_morph3angles = randomvec() * 1;
733                         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');
734                         self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
735                 }
736
737                 self.owner.weapon_morph4time   = time + t;
738                 self.owner.weapon_morph4angles = '0 0 0';
739                 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');
740                 self.owner.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self);
741
742         }
743
744         // create or update the lasertarget entity
745         LaserTarget_Think();
746 };
747
748 void CL_ExteriorWeaponentity_Think()
749 {
750         float tag_found;
751         self.nextthink = time;
752         if (self.owner.exteriorweaponentity != self)
753         {
754                 remove(self);
755                 return;
756         }
757         if (self.owner.deadflag != DEAD_NO)
758         {
759                 self.model = "";
760                 return;
761         }
762         if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
763         {
764                 self.cnt = self.owner.weapon;
765                 self.dmg = self.owner.modelindex;
766                 self.deadflag = self.owner.deadflag;
767                 if (self.owner.weaponname != "")
768                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
769                 else
770                         self.model = "";
771
772                 if((tag_found = gettagindex(self.owner, "tag_weapon")))
773                 {
774                         self.tag_index = tag_found;
775                         self.tag_entity = self.owner;
776                 }
777                 else
778                         setattachment(self, self.owner, "bip01 r hand");
779
780                 // if that didn't find a tag, hide the exterior weapon model
781                 if (!self.tag_index)
782                         self.model = "";
783         }
784         self.effects = self.owner.effects | EF_LOWPRECISION;
785         self.effects = self.effects & EFMASK_CHEAP; // eat performance
786         if(self.owner.alpha != 0)
787                 self.alpha = self.owner.alpha;
788         else
789                 self.alpha = 1;
790         
791         self.colormap = self.owner.colormap;
792 };
793
794 // spawning weaponentity for client
795 void CL_SpawnWeaponentity()
796 {
797         self.weaponentity = spawn();
798         self.weaponentity.classname = "weaponentity";
799         self.weaponentity.solid = SOLID_NOT;
800         self.weaponentity.owner = self;
801         setmodel(self.weaponentity, ""); // precision set when changed
802         self.weaponentity.origin = '0 0 0';
803         self.weaponentity.angles = '0 0 0';
804         self.weaponentity.viewmodelforclient = self;
805         self.weaponentity.flags = 0;
806         self.weaponentity.think = CL_Weaponentity_Think;
807         self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
808         self.weaponentity.nextthink = time;
809
810         self.exteriorweaponentity = spawn();
811         self.exteriorweaponentity.classname = "exteriorweaponentity";
812         self.exteriorweaponentity.solid = SOLID_NOT;
813         self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
814         self.exteriorweaponentity.owner = self;
815         self.exteriorweaponentity.origin = '0 0 0';
816         self.exteriorweaponentity.angles = '0 0 0';
817         self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
818         self.exteriorweaponentity.nextthink = time;
819 };
820
821 .float hasweapon_complain_spam;
822
823 float client_hasweapon(entity cl, float wpn, float andammo, float complain)
824 {
825         local float weaponbit, f;
826         local entity oldself;
827
828         if(time < self.hasweapon_complain_spam)
829                 complain = 0;
830         if(complain)
831                 self.hasweapon_complain_spam = time + 0.2;
832
833         if (wpn < WEP_FIRST || wpn > WEP_LAST)
834         {
835                 if (complain)
836                         sprint(self, "Invalid weapon\n");
837                 return FALSE;
838         }
839         weaponbit = W_WeaponBit(wpn);
840         if (cl.weapons & weaponbit)
841         {
842                 if (andammo)
843                 {
844                         if(cl.items & IT_UNLIMITED_WEAPON_AMMO)
845                         {
846                                 f = 1;
847                         }
848                         else
849                         {
850                                 oldself = self;
851                                 self = cl;
852                                 f = weapon_action(wpn, WR_CHECKAMMO1);
853                                 f = f + weapon_action(wpn, WR_CHECKAMMO2);
854                                 self = oldself;
855                         }
856                         if (!f)
857                         {
858                                 if (complain)
859                                         sprint(cl, strcat("You don't have any ammo for the ^2", W_Name(wpn), "\n"));
860                                 return FALSE;
861                         }
862                 }
863                 return TRUE;
864         }
865         if (complain)
866         {
867                 // DRESK - 3/16/07
868                 // Report Proper Weapon Status / Modified Weapon Ownership Message
869                 if(weaponsInMap & weaponbit)
870                 {
871                         sprint(cl, strcat("You do not have the ^2", W_Name(wpn), "\n") );
872
873                         if(cvar("g_showweaponspawns"))
874                         {
875                                 entity e;
876                                 string s;
877
878                                 e = get_weaponinfo(wpn);
879                                 s = e.model2;
880
881                                 for(e = world; (e = findfloat(e, weapons, weaponbit)); )
882                                 {
883                                         if(e.classname == "droppedweapon")
884                                                 continue;
885                                         if not(e.flags & FL_ITEM)
886                                                 continue;
887                                         WaypointSprite_Spawn(
888                                                 s,
889                                                 1, 0,
890                                                 world, e.origin,
891                                                 self, 0,
892                                                 world, enemy,
893                                                 0
894                                         );
895                                 }
896                         }
897                 }
898                 else
899                         sprint(cl, strcat("The ^2", W_Name(wpn), "^7 is ^1NOT AVAILABLE^7 in this map\n") );
900         }
901         return FALSE;
902 };
903
904 // Weapon subs
905 void w_clear()
906 {
907         if (self.weapon != -1)
908                 self.weapon = 0;
909         if (self.weaponentity)
910         {
911                 self.weaponentity.state = WS_CLEAR;
912                 self.weaponentity.effects = 0;
913         }
914 };
915
916 void w_ready()
917 {
918         if (self.weaponentity)
919                 self.weaponentity.state = WS_READY;
920         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
921 };
922
923 // Setup weapon for client (after this raise frame will be launched)
924 void weapon_setup(float windex)
925 {
926         entity e;
927         qcweaponanimation = cvar("sv_qcweaponanimation");
928         e = get_weaponinfo(windex);
929         self.items &~= IT_AMMO;
930         self.items = self.items | e.items;
931
932         // the two weapon entities will notice this has changed and update their models
933         self.weapon = windex;
934         self.weaponname = e.mdl;
935         self.bulletcounter = 0;
936 };
937
938 // perform weapon to attack (weaponstate and attack_finished check is here)
939 .float race_penalty;
940 float weapon_prepareattack(float secondary, float attacktime)
941 {
942         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
943         //if all players readied up and the countdown is running
944         if (cvar("sv_ready_restart_after_countdown"))
945                 if(time < game_starttime || time < self.race_penalty) {
946                         return FALSE;
947                 }
948         
949         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
950         if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))
951         {
952                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
953                 return FALSE;
954         }
955
956         if (timeoutStatus == 2) //don't allow the player to shoot while game is paused
957                 return FALSE;
958
959         // do not even think about shooting if switching
960         if(self.switchweapon != self.weapon)
961                 return FALSE;
962
963         // don't fire if previous attack is not finished
964         if(attacktime >= 0)
965                 if (ATTACK_FINISHED(self) > time + frametime * 0.5)
966                         return FALSE;
967         // don't fire while changing weapon
968         if (self.weaponentity.state != WS_READY)
969                 return FALSE;
970         self.weaponentity.state = WS_INUSE;
971
972         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
973
974         // if the weapon hasn't been firing continuously, reset the timer
975         if(attacktime >= 0)
976         {
977                 if (ATTACK_FINISHED(self) < time - frametime * 1.5)
978                 {
979                         ATTACK_FINISHED(self) = time;
980                         //dprint("resetting attack finished to ", ftos(time), "\n");
981                 }
982                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime;
983         }
984         self.bulletcounter += 1;
985         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
986         return TRUE;
987 };
988
989 void weapon_thinkf(float fr, float t, void() func)
990 {
991         vector a;
992         vector of, or, ou;
993         float restartanim;
994
995         if(fr == WFRAME_DONTCHANGE)
996         {
997                 fr = self.weaponentity.wframe;
998                 restartanim = FALSE;
999         }
1000         else if (fr == WFRAME_IDLE)
1001                 restartanim = FALSE;
1002         else
1003                 restartanim = TRUE;
1004
1005         of = v_forward;
1006         or = v_right;
1007         ou = v_up;
1008
1009         if (self.weaponentity)
1010         {
1011                 self.weaponentity.wframe = fr;
1012                 if (qcweaponanimation)
1013                 {
1014                         if (fr != WFRAME_IDLE)
1015                         {
1016                                 self.weapon_morph0time = time;
1017                                 self.weapon_morph0angles = self.weaponentity.angles;
1018                                 self.weapon_morph0origin = self.weaponentity.origin;
1019
1020                                 self.weapon_morph1angles = '0 0 0';
1021                                 self.weapon_morph1time = time + t;
1022                                 makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
1023                                 self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1024
1025                                 self.weapon_morph2angles = '0 0 0';
1026                                 self.weapon_morph2time = time + t;
1027                                 makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');
1028                                 self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1029
1030                                 self.weapon_morph3angles = '0 0 0';
1031                                 self.weapon_morph3time = time + t;
1032                                 makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');
1033                                 self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1034
1035                                 self.weapon_morph4angles = '0 0 0';
1036                                 self.weapon_morph4time = time + t;
1037                                 makevectors(self.weapon_morph4angles_x * '-1 0 0' + self.weapon_morph4angles_y * '0 1 0' + self.weapon_morph4angles_z * '0 0 1');
1038                                 self.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1039
1040                                 if (fr == WFRAME_FIRE1)
1041                                 {
1042                                         self.weapon_morph1angles = '5 0 0';
1043                                         self.weapon_morph1time = time + t * 0.1;
1044                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
1045                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1046                                         self.weapon_morph4time = time + t + 1; // delay idle effect
1047                                 }
1048                                 else if (fr == WFRAME_FIRE2)
1049                                 {
1050                                         self.weapon_morph1angles = '10 0 0';
1051                                         self.weapon_morph1time = time + t * 0.1;
1052                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
1053                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1054                                         self.weapon_morph4time = time + t + 1; // delay idle effect
1055                                 }
1056                                 else if (fr == WFRAME_RELOAD)
1057                                 {
1058                                         self.weapon_morph1time = time + t * 0.05;
1059                                         self.weapon_morph1angles = '-10 40 0';
1060                                         makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
1061                                         self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1062
1063                                         self.weapon_morph2time = time + t * 0.15;
1064                                         self.weapon_morph2angles = '-10 40 5';
1065                                         makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');
1066                                         self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1067                 
1068                                         self.weapon_morph3time = time + t * 0.25;
1069                                         self.weapon_morph3angles = '-10 40 0';
1070                                         makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');
1071                                         self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
1072                                 }
1073                         }
1074                 }
1075                 else
1076                 {
1077                         if (fr == WFRAME_IDLE)
1078                                 a = self.weaponentity.anim_idle;
1079                         else if (fr == WFRAME_FIRE1)
1080                                 a = self.weaponentity.anim_fire1;
1081                         else if (fr == WFRAME_FIRE2)
1082                                 a = self.weaponentity.anim_fire2;
1083                         else if (fr == WFRAME_RELOAD)
1084                                 a = self.weaponentity.anim_reload;
1085                         setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
1086                 }
1087
1088                 if(restartanim)
1089                 {
1090                         BITXOR_ASSIGN(self.weaponentity.effects, EF_TELEPORT_BIT);
1091                 }
1092         }
1093
1094         v_forward = of;
1095         v_right = or;
1096         v_up = ou;
1097
1098         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
1099         {
1100                 backtrace("Tried to override initial weapon think function - should this really happen?");
1101         }
1102
1103         if(g_runematch)
1104         {
1105                 if(self.runes & RUNE_SPEED)
1106                 {
1107                         if(self.runes & CURSE_SLOW)
1108                                 t = t * cvar("g_balance_rune_speed_combo_atkrate");
1109                         else
1110                                 t = t * cvar("g_balance_rune_speed_atkrate");
1111                 }
1112                 else if(self.runes & CURSE_SLOW)
1113                 {
1114                         t = t * cvar("g_balance_curse_slow_atkrate");
1115                 }
1116         }
1117
1118         // VorteX: haste can be added here
1119         if (self.weapon_think == w_ready)
1120         {
1121                 self.weapon_nextthink = time;
1122                 //dprint("started firing at ", ftos(time), "\n");
1123         }
1124         if (self.weapon_nextthink < time - frametime * 1.5 || self.weapon_nextthink > time + frametime * 1.5)
1125         {
1126                 self.weapon_nextthink = time;
1127                 //dprint("reset weapon animation timer at ", ftos(time), "\n");
1128         }
1129         self.weapon_nextthink = self.weapon_nextthink + t;
1130         self.weapon_think = func;
1131         //dprint("next ", ftos(self.weapon_nextthink), "\n");
1132
1133         if (restartanim)
1134         if (t)
1135         if (!self.crouch) // shoot anim stands up, this looks bad
1136         {
1137                 local vector anim;
1138                 anim = self.anim_shoot;
1139                 anim_z = anim_y / t;
1140                 setanim(self, anim, FALSE, TRUE, TRUE);
1141         }
1142 };
1143
1144 void weapon_boblayer1(float spd, vector org)
1145 {
1146         // VorteX: haste can be added here
1147 };
1148
1149 vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity)
1150 {
1151         vector mdirection;
1152         float mspeed;
1153         float outspeed;
1154         float nstyle;
1155         vector outvelocity;
1156
1157         mdirection = normalize(mvelocity);
1158         mspeed = vlen(mvelocity);
1159
1160         nstyle = cvar("g_projectiles_newton_style");
1161         if(nstyle == 0)
1162         {
1163                 // absolute velocity
1164                 outvelocity = mvelocity;
1165         }
1166         else if(nstyle == 1)
1167         {
1168                 // true Newtonian projectiles
1169                 outvelocity = pvelocity + mvelocity;
1170         }
1171         else if(nstyle == 2)
1172         {
1173                 // true Newtonian projectiles with automatic aim adjustment
1174                 //
1175                 // solve: |outspeed * mdirection - pvelocity| = mspeed
1176                 // outspeed^2 - 2 * outspeed * (mdirection * pvelocity) + pvelocity^2 - mspeed^2 = 0
1177                 // outspeed = (mdirection * pvelocity) +- sqrt((mdirection * pvelocity)^2 - pvelocity^2 + mspeed^2)
1178                 // PLUS SIGN!
1179                 // not defined?
1180                 // then...
1181                 // pvelocity^2 - (mdirection * pvelocity)^2 > mspeed^2
1182                 // velocity without mdirection component > mspeed
1183                 // fire at smallest possible mspeed that works?
1184                 // |(mdirection * pvelocity) * pvelocity - pvelocity| = mspeed
1185
1186                 vector solution;
1187                 solution = solve_quadratic(1, -2 * (mdirection * pvelocity), pvelocity * pvelocity - mspeed * mspeed);
1188                 if(solution_z)
1189                         outspeed = solution_y; // the larger one
1190                 else
1191                 {
1192                         //outspeed = 0; // slowest possible shot
1193                         outspeed = solution_x; // the real part (that is, the average!)
1194                         //dprint("impossible shot, adjusting\n");
1195                 }
1196
1197                 outspeed = bound(mspeed * 0.7, outspeed, mspeed * 5.0);
1198                 outvelocity = mdirection * outspeed;
1199         }
1200         else if(nstyle == 3)
1201         {
1202                 // pseudo-Newtonian:
1203                 outspeed = mspeed + mdirection * pvelocity;
1204                 outspeed = bound(mspeed * 0.7, outspeed, mspeed * 5.0);
1205                 outvelocity = mdirection * outspeed;
1206         }
1207         else if(nstyle == 4)
1208         {
1209                 // tZorkian:
1210                 outspeed = mspeed + vlen(pvelocity);
1211                 outvelocity = mdirection * outspeed;
1212         }
1213         else
1214                 error("g_projectiles_newton_style must be 0 (absolute), 1 (Newtonian), 2 (Newtonian + aimfix), 3 (pseudo Newtonian) or 4 (tZorkian)!");
1215
1216         return outvelocity;
1217 }
1218
1219 void W_SetupProjectileVelocity(entity missile)
1220 {
1221         if(missile.owner == world)
1222                 error("Unowned missile");
1223
1224         missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, missile.velocity);
1225 }
1226
1227 void W_AttachToShotorg(entity flash, vector offset)
1228 {
1229         entity xflash;
1230         flash.owner = self;
1231         flash.angles_z = random() * 360;
1232         if(qcweaponanimation)
1233         {
1234                 setorigin(flash, w_shotorg + w_shotdir * 50);
1235                 flash.angles = vectoangles(w_shotdir);
1236                 flash.angles_z = random() * 360;
1237         }
1238         else
1239         {
1240                 setattachment(flash, self.weaponentity, "shot");
1241                 setorigin(flash, offset);
1242
1243                 xflash = spawn();
1244                 copyentity(flash, xflash);
1245
1246                 flash.viewmodelforclient = self;
1247
1248                 if(self.weaponentity.oldorigin_x > 0)
1249                 {
1250                         setattachment(xflash, self.exteriorweaponentity, "");
1251                         setorigin(xflash, self.weaponentity.oldorigin + offset);
1252                 }
1253                 else
1254                 {
1255                         setattachment(xflash, self.exteriorweaponentity, "shot");
1256                 }
1257         }
1258 }