2 ===========================================================================
4 CLIENT WEAPONSYSTEM CODE
5 Bring back W_Weaponframe
7 ===========================================================================
10 void W_SwitchWeapon_Force(entity e, float w)
12 e.cnt = e.switchweapon;
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;
26 void(float fr, float t, void() func) weapon_thinkf;
31 // this function calculates w_shotorg and w_shotdir based on the weapon model
32 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.
33 // make sure you call makevectors first (FIXME?)
34 void W_SetupShot(entity ent, vector vecs, float antilag, float recoil, string snd)
36 float nudge = 1; // added to traceline target and subtracted from result
37 local vector trueaimpoint;
39 oldsolid = self.dphitcontentsmask;
40 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
41 traceline(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * MAX_SHOT_DISTANCE, MOVE_NOMONSTERS, self);
42 trueaimpoint = trace_endpos;
44 if(ent.weaponentity.movedir_x > 0)
46 vecs = ent.weaponentity.movedir;
50 vecs = shotorg_adjust(vecs, TRUE, FALSE);
52 if(debug_shotorg != '0 0 0')
55 w_shotorg = ent.origin + ent.view_ofs + v_right * vecs_y + v_up * vecs_z;
57 // now move the shotorg forward as much as requested if possible
58 traceline(w_shotorg, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, self);
59 w_shotorg = trace_endpos - v_forward * nudge;
60 // calculate the shotdir from the chosen shotorg
61 w_shotdir = normalize(trueaimpoint - w_shotorg);
64 // explanation of g_antilag:
65 // if client reports it was aiming at a player, and the serverside trace
66 // says it would miss, change the aim point to the player's new origin,
67 // but only if the shot at the player's new origin would hit of course
69 // FIXME: a much better method for bullet weapons would be to leave a
70 // trail of lagged 'ghosts' behind players, and see if the bullet hits the
71 // ghost corresponding to this player's ping time, and if so it would do
72 // damage to the real player
74 if (self.cursor_trace_ent) // client was aiming at someone
75 if (self.cursor_trace_ent != self) // just to make sure
76 if (self.cursor_trace_ent.takedamage) // and that person is killable
77 if (self.cursor_trace_ent.classname == "player") // and actually a player
78 if (cvar("g_antilag") == 1)
80 // verify that the shot would miss without antilag
81 // (avoids an issue where guns would always shoot at their origin)
82 traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
83 if (!trace_ent.takedamage)
85 // verify that the shot would hit if altered
86 traceline(w_shotorg, self.cursor_trace_ent.origin, MOVE_NORMAL, self);
87 if (trace_ent == self.cursor_trace_ent)
89 // verify that the shot would hit in the past
90 if(self.antilag_debug)
91 antilag_takeback(self.cursor_trace_ent, time - self.antilag_debug);
93 antilag_takeback(self.cursor_trace_ent, time - ANTILAG_LATENCY(self));
95 traceline(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
96 antilag_restore(self.cursor_trace_ent);
98 if(trace_ent == self.cursor_trace_ent)
101 w_shotdir = normalize(self.cursor_trace_ent.origin - w_shotorg);
102 dprint("ANTILAG HIT for ", self.netname, "\n");
106 // prydon cursor aimbot or odd network conditions
107 dprint("WARNING: antilag ghost trace for ", self.netname, " failed!\n");
110 if(cvar("developer") >= 2)
112 vector v, vplus, vel;
114 v = antilag_takebackorigin(self.cursor_trace_ent, time - (ANTILAG_LATENCY(self) ));
115 vplus = antilag_takebackorigin(self.cursor_trace_ent, time - (ANTILAG_LATENCY(self) + 0.01));
116 vel = (vplus - v) * (1 / 0.01);
117 // solve: v + X * vel = closest to self.origin + self.view_ofs, v_forward axis
118 v -= (self.origin + self.view_ofs);
119 // solve: v + X * vel = closest to v_forward axis
120 // project into 2D by subtracting v_forward components:
121 v -= (v * v_forward) * v_forward;
122 vel -= (vel * v_forward) * v_forward;
123 // solve: v + X * vel = closest to origin
124 // (v + X * vel)^2 closest to 0
125 // v^2 + 2 * X * (v * vel) + X^2 * vel^2 closest to 0
126 X = -(v * vel) / (vel * vel);
127 dprint("dead center needs adjustment of ", ftos(X), " (that is, ", ftos(ANTILAG_LATENCY(self) + X), " instead of ", ftos(ANTILAG_LATENCY(self)), "\n");
135 if (cvar("g_antilag") == 1) // switch to "ghost" if not hitting original
137 traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
138 if (!trace_ent.takedamage)
140 traceline_antilag_force (self, w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self, ANTILAG_LATENCY(self));
141 if (trace_ent.takedamage && trace_ent.classname == "player")
145 traceline(w_shotorg, e.origin, MOVE_NORMAL, self);
147 w_shotdir = normalize(trace_ent.origin - w_shotorg);
151 else if(cvar("g_antilag") == 3) // client side hitscan
153 if (self.cursor_trace_ent) // client was aiming at someone
154 if (self.cursor_trace_ent != self) // just to make sure
155 if (self.cursor_trace_ent.takedamage) // and that person is killable
156 if (self.cursor_trace_ent.classname == "player") // and actually a player
158 // verify that the shot would miss without antilag
159 // (avoids an issue where guns would always shoot at their origin)
160 traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
161 if (!trace_ent.takedamage)
163 // verify that the shot would hit if altered
164 traceline(w_shotorg, self.cursor_trace_ent.origin, MOVE_NORMAL, self);
165 if (trace_ent == self.cursor_trace_ent)
166 w_shotdir = normalize(self.cursor_trace_ent.origin - w_shotorg);
168 print("antilag fail\n");
175 self.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
178 self.punchangle_x = recoil * -1;
182 sound (self, CHAN_WEAPON, snd, VOL_BASE, ATTN_NORM);
185 if (self.items & IT_STRENGTH)
187 sound (self, CHAN_AUTO, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
190 void LaserTarget_Think()
197 // list of weapons that will use the laser, and the options that enable it
198 if(self.owner.laser_on && self.owner.weapon == WEP_ROCKET_LAUNCHER && g_laserguided_missile)
201 //if(self.owner.weapon == WEP_ELECTRO && cvar("g_laserguided_electro"))
206 // if a laser-enabled weapon isn't selected, delete any existing laser and quit
209 // rocket launcher isn't selected, so no laser target.
210 if(self.lasertarget != world)
212 remove(self.lasertarget);
213 self.lasertarget = world;
218 if(!self.lasertarget)
220 // we don't have a lasertarget entity, so spawn one
221 //bprint("create laser target\n");
222 e = self.lasertarget = spawn();
223 e.owner = self.owner; // Its owner is my owner
224 e.classname = "laser_target";
225 e.movetype = MOVETYPE_NOCLIP; // don't touch things
226 setmodel(e, "models/laser_dot.mdl"); // what it looks like, precision set below
227 e.scale = 1.25; // make it larger
228 e.alpha = 0.25; // transparency
229 e.colormod = '255 0 0' * (1/255) * 8; // change colors
230 e.effects = EF_FULLBRIGHT | EF_LOWPRECISION;
231 // make it dynamically glow
232 // you should avoid over-using this, as it can slow down the player's computer.
233 e.glow_color = 251; // red color
237 e = self.lasertarget;
239 // move the laser dot to where the player is looking
241 makevectors(self.owner.v_angle); // set v_forward etc to the direction the player is looking
242 offset = '0 0 26' + v_right*3;
243 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
244 setorigin(e, trace_endpos + v_forward*8); // move me to where the traceline ended
245 if(trace_plane_normal != '0 0 0')
246 e.angles = vectoangles(trace_plane_normal);
248 e.angles = vectoangles(v_forward);
251 float CL_Weaponentity_CustomizeEntityForClient()
253 self.viewmodelforclient = self.owner;
254 if(other.classname == "spectator")
255 if(other.enemy == self.owner)
256 self.viewmodelforclient = other;
260 float qcweaponanimation;
261 vector weapon_offset = '0 -10 0';
262 vector weapon_adjust = '10 0 -15';
263 .vector weapon_morph0origin;
264 .vector weapon_morph0angles;
265 .float weapon_morph0time;
266 .vector weapon_morph1origin;
267 .vector weapon_morph1angles;
268 .float weapon_morph1time;
269 .vector weapon_morph2origin;
270 .vector weapon_morph2angles;
271 .float weapon_morph2time;
272 .vector weapon_morph3origin;
273 .vector weapon_morph3angles;
274 .float weapon_morph3time;
275 .vector weapon_morph4origin;
276 .vector weapon_morph4angles;
277 .float weapon_morph4time;
279 #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)
281 void CL_Weaponentity_Think()
284 self.nextthink = time;
285 if (intermission_running)
286 self.frame = self.anim_idle_x;
287 if (self.owner.weaponentity != self)
289 if (self.weaponentity)
290 remove(self.weaponentity);
294 if (self.owner.deadflag != DEAD_NO)
297 if (self.weaponentity)
298 self.weaponentity.model = "";
301 if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
303 self.cnt = self.owner.weapon;
304 self.dmg = self.owner.modelindex;
305 self.deadflag = self.owner.deadflag;
310 if (self.owner.weaponname != "")
312 // if there is a child entity, hide it until we're sure we use it
313 if (self.weaponentity)
314 self.weaponentity.model = "";
315 if (qcweaponanimation)
316 setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
319 animfilename = strcat("models/weapons/h_", self.owner.weaponname, ".zym");
320 modelfile = fopen(animfilename, FILE_READ);
324 self.anim_fire1 = '0 1 0.01';
325 self.anim_fire2 = '1 1 0.01';
326 self.anim_idle = '2 1 0.01';
327 self.anim_reload = '3 1 0.01';
328 if (!self.weaponentity)
329 self.weaponentity = spawn();
330 setmodel(self, strcat("models/weapons/h_", self.owner.weaponname, ".zym")); // precision set below
331 setmodel(self.weaponentity, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision does not matter
332 setattachment(self.weaponentity, self, "weapon");
336 animfilename = strcat("models/weapons/h_", self.owner.weaponname, ".dpm.animinfo");
337 animfile = fopen(animfilename, FILE_READ);
340 if (!self.weaponentity)
341 self.weaponentity = spawn();
342 setmodel(self, strcat("models/weapons/h_", self.owner.weaponname, ".dpm")); // precision set below
343 setmodel(self.weaponentity, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision does not matter
344 setattachment(self.weaponentity, self, "weapon");
348 animfilename = strcat("models/weapons/w_", self.owner.weaponname, ".dpm.animinfo");
349 animfile = fopen(animfilename, FILE_READ);
351 setmodel(self, strcat("models/weapons/w_", self.owner.weaponname, ".dpm")); // precision set below
355 animparseerror = FALSE;
356 self.anim_fire1 = animparseline(animfile);
357 self.anim_fire2 = animparseline(animfile);
358 self.anim_idle = animparseline(animfile);
359 self.anim_reload = animparseline(animfile);
362 print("Parse error in ", animfilename, ", some player animations are broken\n");
366 self.anim_fire1 = '0 1 0.01';
367 self.anim_fire2 = '1 1 0.01';
368 self.anim_idle = '2 1 0.01';
369 self.anim_reload = '3 1 0.01';
370 setmodel(self, strcat("models/weapons/w_", self.owner.weaponname, ".zym")); // precision set below
378 if(qcweaponanimation)
380 self.angles = '0 0 0';
381 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
382 self.movedir = weapon_offset_x * v_forward - weapon_offset_y * v_right + weapon_offset_z * v_up + weapon_adjust;
383 self.movedir_x += 30;
388 idx = gettagindex(self, "shot");
391 print("WARNING: the weapon ", self.model, " does not support the correct shot origin tag.\n");
392 idx = gettagindex(self, "bone02");
396 self.origin = '0 0 0';
397 self.angles = '0 0 0';
399 self.viewmodelforclient = world;
400 self.movedir = gettaginfo(self, idx);
401 self.viewmodelforclient = self.owner;
404 self.movedir = '0 0 0';
407 self.view_ofs = '0 0 0';
409 if(self.movedir_x >= 0)
413 self.movedir = shotorg_adjust(v0, FALSE, FALSE);
414 self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
417 // check if an instant weapon switch occurred
418 if (qcweaponanimation)
420 if (self.state == WS_READY)
422 self.angles = '0 0 0';
423 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
424 setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
428 setorigin(self, self.view_ofs);
429 // reset animstate now
430 self.wframe = WFRAME_IDLE;
431 self.weapon_morph0time = 0;
432 self.weapon_morph1time = 0;
433 self.weapon_morph2time = 0;
434 self.weapon_morph3time = 0;
435 self.weapon_morph4time = 0;
436 setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
439 tb = (self.effects & EF_TELEPORT_BIT);
440 self.effects = self.owner.effects - (self.owner.effects & EF_LOWPRECISION);// | EF_LOWPRECISION;
441 self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it
442 self.effects &~= EF_TELEPORT_BIT;
445 if(self.owner.alpha != 0)
446 self.alpha = self.owner.alpha;
450 self.colormap = self.owner.colormap;
451 if (self.weaponentity)
453 self.weaponentity.effects = self.effects & EF_ADDITIVE;
454 self.weaponentity.alpha = self.alpha;
455 self.weaponentity.colormap = self.colormap;
458 self.angles = '0 0 0';
461 if (self.state == WS_RAISE)
463 f = (self.owner.weapon_nextthink - time) / cvar("g_balance_weaponswitchdelay");
464 self.angles_x = -90 * f * f;
465 if (qcweaponanimation)
467 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
468 setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
471 else if (self.state == WS_DROP)
473 f = 1 - (self.owner.weapon_nextthink - time) / cvar("g_balance_weaponswitchdelay");
474 self.angles_x = -90 * f * f;
475 if (qcweaponanimation)
477 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
478 setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
481 else if (self.state == WS_CLEAR)
484 self.angles_x = -90 * f * f;
485 if (qcweaponanimation)
487 makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0' + self.angles_z * '0 0 1');
488 setorigin(self, QCWEAPONANIMATION_ORIGIN(self));
491 else if (qcweaponanimation && time < self.owner.weapon_morph1time)
493 f = (time - self.owner.weapon_morph0time) / (self.owner.weapon_morph1time - self.owner.weapon_morph0time);
494 f = 1 - pow(1 - f, 3);
495 self.angles = self.owner.weapon_morph0angles * (1 - f) + self.owner.weapon_morph1angles * f;
496 setorigin(self, self.owner.weapon_morph0origin * (1 - f) + self.owner.weapon_morph1origin * f);
498 else if (qcweaponanimation && time < self.owner.weapon_morph2time)
500 f = (time - self.owner.weapon_morph1time) / (self.owner.weapon_morph2time - self.owner.weapon_morph1time);
501 f = 1 - pow(1 - f, 3);
502 self.angles = self.owner.weapon_morph1angles * (1 - f) + self.owner.weapon_morph2angles * f;
503 setorigin(self, self.owner.weapon_morph1origin * (1 - f) + self.owner.weapon_morph2origin * f);
505 else if (qcweaponanimation && time < self.owner.weapon_morph3time)
507 f = (time - self.owner.weapon_morph2time) / (self.owner.weapon_morph3time - self.owner.weapon_morph2time);
508 f = 1 - pow(1 - f, 3);
509 self.angles = self.owner.weapon_morph2angles * (1 - f) + self.owner.weapon_morph3angles * f;
510 setorigin(self, self.owner.weapon_morph2origin * (1 - f) + self.owner.weapon_morph3origin * f);
512 else if (qcweaponanimation && time < self.owner.weapon_morph4time)
514 f = (time - self.owner.weapon_morph3time) / (self.owner.weapon_morph4time - self.owner.weapon_morph3time);
515 f = 1 - pow(1 - f, 3);
516 self.angles = self.owner.weapon_morph3angles * (1 - f) + self.owner.weapon_morph4angles * f;
517 setorigin(self, self.owner.weapon_morph3origin * (1 - f) + self.owner.weapon_morph4origin * f);
519 else if (qcweaponanimation)
521 // begin a new idle morph
522 self.owner.weapon_morph0time = time;
523 self.owner.weapon_morph0angles = self.angles;
524 self.owner.weapon_morph0origin = self.origin;
532 // turn gun to the left to look at it
534 self.owner.weapon_morph1time = time + t * 0.2;
535 self.owner.weapon_morph1angles = randomvec() * 3 + '-5 30 0';
536 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');
537 self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
539 self.owner.weapon_morph2time = time + t * 0.6;
540 self.owner.weapon_morph2angles = randomvec() * 3 + '-5 30 0';
541 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');
542 self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
544 self.owner.weapon_morph3time = time + t;
545 self.owner.weapon_morph3angles = '0 0 0';
546 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');
547 self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
551 // raise the gun a bit
553 self.owner.weapon_morph1time = time + t * 0.2;
554 self.owner.weapon_morph1angles = randomvec() * 3 + '30 -10 0';
555 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');
556 self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
558 self.owner.weapon_morph2time = time + t * 0.5;
559 self.owner.weapon_morph2angles = randomvec() * 3 + '30 -10 5';
560 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');
561 self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
563 self.owner.weapon_morph3time = time + t;
564 self.owner.weapon_morph3angles = '0 0 0';
565 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');
566 self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
572 self.owner.weapon_morph1time = time + t * 0.3;
573 self.owner.weapon_morph1angles = randomvec() * 6;
574 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');
575 self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
577 self.owner.weapon_morph2time = time + t * 0.7;
578 self.owner.weapon_morph2angles = randomvec() * 6;
579 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');
580 self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
582 self.owner.weapon_morph3time = time + t;
583 self.owner.weapon_morph3angles = '0 0 0';
584 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');
585 self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
589 // hold it mostly steady
590 t = random() * 6 + 4;
591 self.owner.weapon_morph1time = time + t * 0.2;
592 self.owner.weapon_morph1angles = randomvec() * 1;
593 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');
594 self.owner.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self);
596 self.owner.weapon_morph2time = time + t * 0.5;
597 self.owner.weapon_morph2angles = randomvec() * 1;
598 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');
599 self.owner.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self);
601 self.owner.weapon_morph3time = time + t * 0.7;
602 self.owner.weapon_morph3angles = randomvec() * 1;
603 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');
604 self.owner.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self);
607 self.owner.weapon_morph4time = time + t;
608 self.owner.weapon_morph4angles = '0 0 0';
609 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');
610 self.owner.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self);
614 // create or update the lasertarget entity
618 void CL_ExteriorWeaponentity_Think()
621 self.nextthink = time;
622 if (self.owner.exteriorweaponentity != self)
627 if (self.owner.deadflag != DEAD_NO)
632 if (self.cnt != self.owner.weapon || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
634 self.cnt = self.owner.weapon;
635 self.dmg = self.owner.modelindex;
636 self.deadflag = self.owner.deadflag;
637 if (self.owner.weaponname != "")
638 setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
642 if((tag_found = gettagindex(self.owner, "tag_weapon")))
644 self.tag_index = tag_found;
645 self.tag_entity = self.owner;
648 setattachment(self, self.owner, "bip01 r hand");
650 // if that didn't find a tag, hide the exterior weapon model
654 self.effects = self.owner.effects | EF_LOWPRECISION;
655 self.effects = self.effects & EFMASK_CHEAP; // eat performance
656 if(self.owner.alpha != 0)
657 self.alpha = self.owner.alpha;
661 self.colormap = self.owner.colormap;
664 // spawning weaponentity for client
665 void CL_SpawnWeaponentity()
667 self.weaponentity = spawn();
668 self.weaponentity.classname = "weaponentity";
669 self.weaponentity.solid = SOLID_NOT;
670 self.weaponentity.owner = self;
671 setmodel(self.weaponentity, ""); // precision set when changed
672 self.weaponentity.origin = '0 0 0';
673 self.weaponentity.angles = '0 0 0';
674 self.weaponentity.viewmodelforclient = self;
675 self.weaponentity.flags = 0;
676 self.weaponentity.think = CL_Weaponentity_Think;
677 self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
678 self.weaponentity.nextthink = time;
680 self.exteriorweaponentity = spawn();
681 self.exteriorweaponentity.classname = "exteriorweaponentity";
682 self.exteriorweaponentity.solid = SOLID_NOT;
683 self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
684 self.exteriorweaponentity.owner = self;
685 self.exteriorweaponentity.origin = '0 0 0';
686 self.exteriorweaponentity.angles = '0 0 0';
687 self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
688 self.exteriorweaponentity.nextthink = time;
691 .float hasweapon_complain_spam;
693 float client_hasweapon(entity cl, float wpn, float andammo, float complain)
695 local float weaponbit, f;
696 local entity oldself;
698 if(time < self.hasweapon_complain_spam)
701 self.hasweapon_complain_spam = time + 0.2;
703 if (wpn < WEP_FIRST || wpn > WEP_LAST)
706 sprint(self, "Invalid weapon\n");
709 weaponbit = W_WeaponBit(wpn);
710 if (cl.weapons & weaponbit)
714 if(cl.items & IT_UNLIMITED_WEAPON_AMMO)
722 f = weapon_action(wpn, WR_CHECKAMMO1);
723 f = f + weapon_action(wpn, WR_CHECKAMMO2);
729 sprint(cl, strcat("You don't have any ammo for the ^2", W_Name(wpn), "\n"));
738 // Report Proper Weapon Status / Modified Weapon Ownership Message
739 if(weaponsInMap & weaponbit)
741 sprint(cl, strcat("You do not have the ^2", W_Name(wpn), "\n") );
743 if(cvar("g_showweaponspawns"))
748 e = get_weaponinfo(wpn);
751 for(e = world; (e = findfloat(e, weapons, weaponbit)); )
753 if(e.classname == "droppedweapon")
755 if not(e.flags & FL_ITEM)
757 WaypointSprite_Spawn(
769 sprint(cl, strcat("The ^2", W_Name(wpn), "^7 is ^1NOT AVAILABLE^7 in this map\n") );
777 if (self.weapon != -1)
779 if (self.weaponentity)
781 self.weaponentity.state = WS_CLEAR;
782 self.weaponentity.effects = 0;
788 if (self.weaponentity)
789 self.weaponentity.state = WS_READY;
790 weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
793 // Setup weapon for client (after this raise frame will be launched)
794 void weapon_setup(float windex)
797 qcweaponanimation = cvar("sv_qcweaponanimation");
798 e = get_weaponinfo(windex);
799 self.items &~= IT_AMMO;
800 self.items = self.items | e.items;
802 // the two weapon entities will notice this has changed and update their models
803 self.weapon = windex;
804 self.weaponname = e.mdl;
805 self.bulletcounter = 0;
808 // perform weapon to attack (weaponstate and attack_finished check is here)
810 float weapon_prepareattack(float secondary, float attacktime)
812 //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
813 //if all players readied up and the countdown is running
814 if (cvar("sv_ready_restart_after_countdown"))
815 if(time < game_starttime || time < self.race_penalty) {
819 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
820 if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))
822 W_SwitchWeapon_Force(self, w_getbestweapon(self));
826 if (timeoutStatus == 2) //don't allow the player to shoot while game is paused
829 // do not even think about shooting if switching
830 if(self.switchweapon != self.weapon)
833 // don't fire if previous attack is not finished
835 if (ATTACK_FINISHED(self) > time + frametime * 0.5)
837 // don't fire while changing weapon
838 if (self.weaponentity.state != WS_READY)
840 self.weaponentity.state = WS_INUSE;
842 self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
844 // if the weapon hasn't been firing continuously, reset the timer
847 if (ATTACK_FINISHED(self) < time - frametime * 1.5)
849 ATTACK_FINISHED(self) = time;
850 //dprint("resetting attack finished to ", ftos(time), "\n");
852 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime;
854 self.bulletcounter += 1;
855 //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
859 void weapon_thinkf(float fr, float t, void() func)
865 if(fr == WFRAME_DONTCHANGE)
867 fr = self.weaponentity.wframe;
870 else if (fr == WFRAME_IDLE)
879 if (self.weaponentity)
881 self.weaponentity.wframe = fr;
882 if (qcweaponanimation)
884 if (fr != WFRAME_IDLE)
886 self.weapon_morph0time = time;
887 self.weapon_morph0angles = self.weaponentity.angles;
888 self.weapon_morph0origin = self.weaponentity.origin;
890 self.weapon_morph1angles = '0 0 0';
891 self.weapon_morph1time = time + t;
892 makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
893 self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
895 self.weapon_morph2angles = '0 0 0';
896 self.weapon_morph2time = time + t;
897 makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');
898 self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
900 self.weapon_morph3angles = '0 0 0';
901 self.weapon_morph3time = time + t;
902 makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');
903 self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
905 self.weapon_morph4angles = '0 0 0';
906 self.weapon_morph4time = time + t;
907 makevectors(self.weapon_morph4angles_x * '-1 0 0' + self.weapon_morph4angles_y * '0 1 0' + self.weapon_morph4angles_z * '0 0 1');
908 self.weapon_morph4origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
910 if (fr == WFRAME_FIRE1)
912 self.weapon_morph1angles = '5 0 0';
913 self.weapon_morph1time = time + t * 0.1;
914 makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
915 self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
916 self.weapon_morph4time = time + t + 1; // delay idle effect
918 else if (fr == WFRAME_FIRE2)
920 self.weapon_morph1angles = '10 0 0';
921 self.weapon_morph1time = time + t * 0.1;
922 makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
923 self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
924 self.weapon_morph4time = time + t + 1; // delay idle effect
926 else if (fr == WFRAME_RELOAD)
928 self.weapon_morph1time = time + t * 0.05;
929 self.weapon_morph1angles = '-10 40 0';
930 makevectors(self.weapon_morph1angles_x * '-1 0 0' + self.weapon_morph1angles_y * '0 1 0' + self.weapon_morph1angles_z * '0 0 1');
931 self.weapon_morph1origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
933 self.weapon_morph2time = time + t * 0.15;
934 self.weapon_morph2angles = '-10 40 5';
935 makevectors(self.weapon_morph2angles_x * '-1 0 0' + self.weapon_morph2angles_y * '0 1 0' + self.weapon_morph2angles_z * '0 0 1');
936 self.weapon_morph2origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
938 self.weapon_morph3time = time + t * 0.25;
939 self.weapon_morph3angles = '-10 40 0';
940 makevectors(self.weapon_morph3angles_x * '-1 0 0' + self.weapon_morph3angles_y * '0 1 0' + self.weapon_morph3angles_z * '0 0 1');
941 self.weapon_morph3origin = QCWEAPONANIMATION_ORIGIN(self.weaponentity);
947 if (fr == WFRAME_IDLE)
948 a = self.weaponentity.anim_idle;
949 else if (fr == WFRAME_FIRE1)
950 a = self.weaponentity.anim_fire1;
951 else if (fr == WFRAME_FIRE2)
952 a = self.weaponentity.anim_fire2;
953 else if (fr == WFRAME_RELOAD)
954 a = self.weaponentity.anim_reload;
955 setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
960 BITXOR_ASSIGN(self.weaponentity.effects, EF_TELEPORT_BIT);
968 if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
970 backtrace("Tried to override initial weapon think function - should this really happen?");
975 if(self.runes & RUNE_SPEED)
977 if(self.runes & CURSE_SLOW)
978 t = t * cvar("g_balance_rune_speed_combo_atkrate");
980 t = t * cvar("g_balance_rune_speed_atkrate");
982 else if(self.runes & CURSE_SLOW)
984 t = t * cvar("g_balance_curse_slow_atkrate");
988 // VorteX: haste can be added here
989 if (self.weapon_think == w_ready)
991 self.weapon_nextthink = time;
992 //dprint("started firing at ", ftos(time), "\n");
994 if (self.weapon_nextthink < time - frametime * 1.5 || self.weapon_nextthink > time + frametime * 1.5)
996 self.weapon_nextthink = time;
997 //dprint("reset weapon animation timer at ", ftos(time), "\n");
999 self.weapon_nextthink = self.weapon_nextthink + t;
1000 self.weapon_think = func;
1001 //dprint("next ", ftos(self.weapon_nextthink), "\n");
1005 if (!self.crouch) // shoot anim stands up, this looks bad
1008 anim = self.anim_shoot;
1009 anim_z = anim_y / t;
1010 setanim(self, anim, FALSE, TRUE, TRUE);
1014 void weapon_boblayer1(float spd, vector org)
1016 // VorteX: haste can be added here
1019 vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity)
1027 mdirection = normalize(mvelocity);
1028 mspeed = vlen(mvelocity);
1030 nstyle = cvar("g_projectiles_newton_style");
1033 // absolute velocity
1034 outvelocity = mvelocity;
1036 else if(nstyle == 1)
1038 // true Newtonian projectiles
1039 outvelocity = pvelocity + mvelocity;
1041 else if(nstyle == 2)
1043 // true Newtonian projectiles with automatic aim adjustment
1045 // solve: |outspeed * mdirection - pvelocity| = mspeed
1046 // outspeed^2 - 2 * outspeed * (mdirection * pvelocity) + pvelocity^2 - mspeed^2 = 0
1047 // outspeed = (mdirection * pvelocity) +- sqrt((mdirection * pvelocity)^2 - pvelocity^2 + mspeed^2)
1051 // pvelocity^2 - (mdirection * pvelocity)^2 > mspeed^2
1052 // velocity without mdirection component > mspeed
1053 // fire at smallest possible mspeed that works?
1054 // |(mdirection * pvelocity) * pvelocity - pvelocity| = mspeed
1059 p = mdirection * pvelocity;
1060 q = pvelocity * pvelocity - mspeed * mspeed;
1064 //dprint("impossible shot, adjusting\n");
1067 outspeed = p + sqrt(D);
1068 outspeed = bound(mspeed * 0.7, outspeed, mspeed * 5.0);
1069 outvelocity = mdirection * outspeed;
1071 else if(nstyle == 3)
1073 // pseudo-Newtonian:
1074 outspeed = mspeed + mdirection * pvelocity;
1075 outspeed = bound(mspeed * 0.7, outspeed, mspeed * 5.0);
1076 outvelocity = mdirection * outspeed;
1078 else if(nstyle == 4)
1081 outspeed = mspeed + vlen(pvelocity);
1082 outvelocity = mdirection * outspeed;
1085 error("g_projectiles_newton_style must be 0 (absolute), 1 (Newtonian), 2 (Newtonian + aimfix), 3 (pseudo Newtonian) or 4 (tZorkian)!");
1090 void W_SetupProjectileVelocity(entity missile)
1092 if(missile.owner == world)
1093 error("Unowned missile");
1095 missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, missile.velocity);