2 ===========================================================================
4 CLIENT WEAPONSYSTEM CODE
5 Bring back W_Weaponframe
7 ===========================================================================
10 .float weapon_frametime;
12 float W_WeaponRateFactor()
15 t = 1.0 / g_weaponratefactor;
20 void W_SwitchWeapon_Force(entity e, float w)
22 e.cnt = e.switchweapon;
29 // VorteX: static frame globals
30 float WFRAME_DONTCHANGE = -1;
31 float WFRAME_FIRE1 = 0;
32 float WFRAME_FIRE2 = 1;
33 float WFRAME_IDLE = 2;
34 float WFRAME_RELOAD = 3;
37 void(float fr, float t, void() func) weapon_thinkf;
43 .float prevstrengthsound;
44 .float prevstrengthsoundattempt;
45 void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound
47 if((player.items & IT_STRENGTH)
48 && ((time > player.prevstrengthsound + autocvar_sv_strengthsound_antispam_time) // prevent insane sound spam
49 || (time > player.prevstrengthsoundattempt + autocvar_sv_strengthsound_antispam_refire_threshold)))
51 sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
52 player.prevstrengthsound = time;
54 player.prevstrengthsoundattempt = time;
57 // this function calculates w_shotorg and w_shotdir based on the weapon model
58 // offset, trueaim and antilag, and won't put w_shotorg inside a wall.
59 // make sure you call makevectors first (FIXME?)
60 void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector mi, vector ma, float antilag, float recoil, string snd, float chan, float maxdamage, float range)
62 float nudge = 1; // added to traceline target and subtracted from result
65 oldsolid = ent.dphitcontentsmask;
66 if(ent.weapon == WEP_RIFLE)
67 ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
69 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
71 WarpZone_traceline_antilag(world, ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
72 // passing world, because we do NOT want it to touch dphitcontentsmask
74 WarpZone_TraceLine(ent.origin + ent.view_ofs, ent.origin + ent.view_ofs + s_forward * range, MOVE_NOMONSTERS, ent);
75 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
81 w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
86 // un-adjust trueaim if shotend is too close
87 if(vlen(w_shotend - (ent.origin + ent.view_ofs)) < autocvar_g_trueaim_minrange)
88 w_shotend = ent.origin + ent.view_ofs + s_forward * autocvar_g_trueaim_minrange;
91 if(accuracy_canbegooddamage(ent))
92 accuracy_add(ent, ent.weapon, maxdamage, 0);
94 W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
96 if(ent.weaponentity.movedir_x > 0)
97 vecs = ent.weaponentity.movedir;
101 dv = v_right * -vecs_y + v_up * vecs_z;
102 w_shotorg = ent.origin + ent.view_ofs + dv;
104 // now move the shotorg forward as much as requested if possible
107 if(ent.antilag_debug)
108 tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ent.antilag_debug);
110 tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
113 tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent);
114 w_shotorg = trace_endpos - v_forward * nudge;
115 // calculate the shotdir from the chosen shotorg
116 w_shotdir = normalize(w_shotend - w_shotorg);
119 if (!ent.cvar_cl_noantilag)
121 if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
123 traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
124 if (!trace_ent.takedamage)
126 traceline_antilag_force (ent, w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
127 if (trace_ent.takedamage && IS_PLAYER(trace_ent))
131 traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
133 w_shotdir = normalize(trace_ent.origin - w_shotorg);
137 else if(autocvar_g_antilag == 3) // client side hitscan
139 // this part MUST use prydon cursor
140 if (ent.cursor_trace_ent) // client was aiming at someone
141 if (ent.cursor_trace_ent != ent) // just to make sure
142 if (ent.cursor_trace_ent.takedamage) // and that person is killable
143 if (IS_PLAYER(ent.cursor_trace_ent)) // and actually a player
145 // verify that the shot would miss without antilag
146 // (avoids an issue where guns would always shoot at their origin)
147 traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
148 if (!trace_ent.takedamage)
150 // verify that the shot would hit if altered
151 traceline(w_shotorg, ent.cursor_trace_ent.origin, MOVE_NORMAL, ent);
152 if (trace_ent == ent.cursor_trace_ent)
153 w_shotdir = normalize(ent.cursor_trace_ent.origin - w_shotorg);
155 print("antilag fail\n");
161 ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
164 ent.punchangle_x = recoil * -1;
168 sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
169 W_PlayStrengthSound(ent);
172 // nudge w_shotend so a trace to w_shotend hits
173 w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
176 #define W_SetupShot_Dir_ProjectileSize(ent,s_forward,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize_Range(ent, s_forward, mi, ma, antilag, recoil, snd, chan, maxdamage, MAX_SHOT_DISTANCE)
177 #define W_SetupShot_ProjectileSize(ent,mi,ma,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, v_forward, mi, ma, antilag, recoil, snd, chan, maxdamage)
178 #define W_SetupShot_Dir(ent,s_forward,antilag,recoil,snd,chan,maxdamage) W_SetupShot_Dir_ProjectileSize(ent, s_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
179 #define W_SetupShot(ent,antilag,recoil,snd,chan,maxdamage) W_SetupShot_ProjectileSize(ent, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage)
180 #define W_SetupShot_Range(ent,antilag,recoil,snd,chan,maxdamage,range) W_SetupShot_Dir_ProjectileSize_Range(ent, v_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage, range)
182 float CL_Weaponentity_CustomizeEntityForClient()
184 self.viewmodelforclient = self.owner;
186 if(other.enemy == self.owner)
187 self.viewmodelforclient = other;
194 * 1. simple animated model, muzzle flash handling on h_ model:
195 * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
197 * shot = muzzle end (shot origin, also used for muzzle flashes)
198 * shell = casings ejection point (must be on the right hand side of the gun)
199 * weapon = attachment for v_tuba.md3
200 * v_tuba.md3 - first and third person model
201 * g_tuba.md3 - pickup model
203 * 2. simple animated model, muzzle flash handling on v_ model:
204 * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
206 * weapon = attachment for v_tuba.md3
207 * v_tuba.md3 - first and third person model
209 * shot = muzzle end (shot origin, also used for muzzle flashes)
210 * shell = casings ejection point (must be on the right hand side of the gun)
211 * g_tuba.md3 - pickup model
213 * 3. fully animated model, muzzle flash handling on h_ model:
214 * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
216 * shot = muzzle end (shot origin, also used for muzzle flashes)
217 * shell = casings ejection point (must be on the right hand side of the gun)
218 * handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
219 * v_tuba.md3 - third person model
220 * g_tuba.md3 - pickup model
222 * 4. fully animated model, muzzle flash handling on v_ model:
223 * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
225 * shot = muzzle end (shot origin)
226 * shell = casings ejection point (must be on the right hand side of the gun)
227 * v_tuba.md3 - third person model
229 * shot = muzzle end (for muzzle flashes)
230 * g_tuba.md3 - pickup model
234 // self.origin, self.angles
236 // self.movedir, self.view_ofs
240 // call again with ""
242 void CL_WeaponEntity_SetModel(string name)
247 // if there is a child entity, hide it until we're sure we use it
248 if (self.weaponentity)
249 self.weaponentity.model = "";
250 setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
251 v_shot_idx = gettagindex(self, "shot"); // used later
253 v_shot_idx = gettagindex(self, "tag_shot");
255 setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
256 // preset some defaults that work great for renamed zym files (which don't need an animinfo)
257 self.anim_fire1 = animfixfps(self, '0 1 0.01', '0 0 0');
258 self.anim_fire2 = animfixfps(self, '1 1 0.01', '0 0 0');
259 self.anim_idle = animfixfps(self, '2 1 0.01', '0 0 0');
260 self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
262 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
263 // if we don't, this is a "real" animated model
264 if(gettagindex(self, "weapon"))
266 if (!self.weaponentity)
267 self.weaponentity = spawn();
268 setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
269 setattachment(self.weaponentity, self, "weapon");
271 else if(gettagindex(self, "tag_weapon"))
273 if (!self.weaponentity)
274 self.weaponentity = spawn();
275 setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
276 setattachment(self.weaponentity, self, "tag_weapon");
280 if(self.weaponentity)
281 remove(self.weaponentity);
282 self.weaponentity = world;
285 setorigin(self,'0 0 0');
286 self.angles = '0 0 0';
288 self.viewmodelforclient = world;
292 if(v_shot_idx) // v_ model attached to invisible h_ model
294 self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
298 idx = gettagindex(self, "shot");
300 idx = gettagindex(self, "tag_shot");
302 self.movedir = gettaginfo(self, idx);
305 print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
306 self.movedir = '0 0 0';
310 if(self.weaponentity) // v_ model attached to invisible h_ model
312 idx = gettagindex(self.weaponentity, "shell");
314 idx = gettagindex(self.weaponentity, "tag_shell");
316 self.spawnorigin = gettaginfo(self.weaponentity, idx);
322 idx = gettagindex(self, "shell");
324 idx = gettagindex(self, "tag_shell");
326 self.spawnorigin = gettaginfo(self, idx);
329 print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
330 self.spawnorigin = self.movedir;
336 self.oldorigin = '0 0 0'; // use regular attachment
340 if(self.weaponentity)
342 idx = gettagindex(self, "weapon");
344 idx = gettagindex(self, "tag_weapon");
348 idx = gettagindex(self, "handle");
350 idx = gettagindex(self, "tag_handle");
354 self.oldorigin = self.movedir - gettaginfo(self, idx);
358 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");
359 self.oldorigin = '0 0 0'; // there is no way to recover from this
363 self.viewmodelforclient = self.owner;
368 if(self.weaponentity)
369 remove(self.weaponentity);
370 self.weaponentity = world;
371 self.movedir = '0 0 0';
372 self.spawnorigin = '0 0 0';
373 self.oldorigin = '0 0 0';
374 self.anim_fire1 = '0 1 0.01';
375 self.anim_fire2 = '0 1 0.01';
376 self.anim_idle = '0 1 0.01';
377 self.anim_reload = '0 1 0.01';
380 self.view_ofs = '0 0 0';
382 if(self.movedir_x >= 0)
386 self.movedir = shotorg_adjust(v0, FALSE, FALSE);
387 self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
389 self.owner.stat_shotorg = compressShotOrigin(self.movedir);
390 self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
392 self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
394 // check if an instant weapon switch occurred
395 setorigin(self, self.view_ofs);
396 // reset animstate now
397 self.wframe = WFRAME_IDLE;
398 setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
401 vector CL_Weapon_GetShotOrg(float wpn)
405 wi = get_weaponinfo(wpn);
408 CL_WeaponEntity_SetModel(wi.mdl);
410 CL_WeaponEntity_SetModel("");
416 void CL_Weaponentity_Think()
419 self.nextthink = time;
420 if (intermission_running)
421 self.frame = self.anim_idle_x;
422 if (self.owner.weaponentity != self)
424 if (self.weaponentity)
425 remove(self.weaponentity);
429 if (self.owner.deadflag != DEAD_NO)
432 if (self.weaponentity)
433 self.weaponentity.model = "";
436 if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
438 self.weaponname = self.owner.weaponname;
439 self.dmg = self.owner.modelindex;
440 self.deadflag = self.owner.deadflag;
442 CL_WeaponEntity_SetModel(self.owner.weaponname);
445 tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
446 self.effects = self.owner.effects & EFMASK_CHEAP;
447 self.effects &~= EF_LOWPRECISION;
448 self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it
449 self.effects &~= EF_TELEPORT_BIT;
450 self.effects &~= EF_RESTARTANIM_BIT;
453 if(self.owner.alpha == default_player_alpha)
454 self.alpha = default_weapon_alpha;
455 else if(self.owner.alpha != 0)
456 self.alpha = self.owner.alpha;
460 self.glowmod = self.owner.weaponentity_glowmod;
461 self.colormap = self.owner.colormap;
462 if (self.weaponentity)
464 self.weaponentity.effects = self.effects;
465 self.weaponentity.alpha = self.alpha;
466 self.weaponentity.colormap = self.colormap;
467 self.weaponentity.glowmod = self.glowmod;
470 self.angles = '0 0 0';
472 float f = (self.owner.weapon_nextthink - time);
473 if (self.state == WS_RAISE && !intermission_running)
475 entity newwep = get_weaponinfo(self.owner.switchweapon);
476 f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise);
477 //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname)), (self.owner.weapon_nextthink - time)));
478 self.angles_x = -90 * f * f;
480 else if (self.state == WS_DROP && !intermission_running)
482 entity oldwep = get_weaponinfo(self.owner.weapon);
483 f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop);
484 //print(sprintf("CL_Weaponentity_Think(): cvar: %s, value: %f, nextthink: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname)), (self.owner.weapon_nextthink - time)));
485 self.angles_x = -90 * f * f;
487 else if (self.state == WS_CLEAR)
490 self.angles_x = -90 * f * f;
494 void CL_ExteriorWeaponentity_Think()
497 self.nextthink = time;
498 if (self.owner.exteriorweaponentity != self)
503 if (self.owner.deadflag != DEAD_NO)
508 if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
510 self.weaponname = self.owner.weaponname;
511 self.dmg = self.owner.modelindex;
512 self.deadflag = self.owner.deadflag;
513 if (self.owner.weaponname != "")
514 setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
518 if((tag_found = gettagindex(self.owner, "tag_weapon")))
520 self.tag_index = tag_found;
521 self.tag_entity = self.owner;
524 setattachment(self, self.owner, "bip01 r hand");
526 self.effects = self.owner.effects;
527 self.effects |= EF_LOWPRECISION;
528 self.effects = self.effects & EFMASK_CHEAP; // eat performance
529 if(self.owner.alpha == default_player_alpha)
530 self.alpha = default_weapon_alpha;
531 else if(self.owner.alpha != 0)
532 self.alpha = self.owner.alpha;
536 self.glowmod = self.owner.weaponentity_glowmod;
537 self.colormap = self.owner.colormap;
539 CSQCMODEL_AUTOUPDATE();
542 // spawning weaponentity for client
543 void CL_SpawnWeaponentity()
545 self.weaponentity = spawn();
546 self.weaponentity.classname = "weaponentity";
547 self.weaponentity.solid = SOLID_NOT;
548 self.weaponentity.owner = self;
549 setmodel(self.weaponentity, ""); // precision set when changed
550 setorigin(self.weaponentity, '0 0 0');
551 self.weaponentity.angles = '0 0 0';
552 self.weaponentity.viewmodelforclient = self;
553 self.weaponentity.flags = 0;
554 self.weaponentity.think = CL_Weaponentity_Think;
555 self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
556 self.weaponentity.nextthink = time;
558 self.exteriorweaponentity = spawn();
559 self.exteriorweaponentity.classname = "exteriorweaponentity";
560 self.exteriorweaponentity.solid = SOLID_NOT;
561 self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
562 self.exteriorweaponentity.owner = self;
563 setorigin(self.exteriorweaponentity, '0 0 0');
564 self.exteriorweaponentity.angles = '0 0 0';
565 self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
566 self.exteriorweaponentity.nextthink = time;
569 entity oldself = self;
570 self = self.exteriorweaponentity;
571 CSQCMODEL_AUTOINIT();
579 if (self.weapon != -1)
582 self.switchingweapon = 0;
584 if (self.weaponentity)
586 self.weaponentity.state = WS_CLEAR;
587 self.weaponentity.effects = 0;
593 if (self.weaponentity)
594 self.weaponentity.state = WS_READY;
595 weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
600 float weapon_prepareattack_checkammo(float secondary)
602 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
603 if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
605 // always keep the Mine Layer if we placed mines, so that we can detonate them
607 if(self.weapon == WEP_MINE_LAYER)
608 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
611 if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
613 sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM);
614 self.prevdryfire = time;
617 if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
619 if(time - self.prevwarntime > 1)
625 ITEM_WEAPON_PRIMORSEC,
631 self.prevwarntime = time;
633 else // this weapon is totally unable to fire, switch to another one
635 W_SwitchToOtherWeapon(self);
643 float weapon_prepareattack_check(float secondary, float attacktime)
645 if(!weapon_prepareattack_checkammo(secondary))
648 //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
649 //if all players readied up and the countdown is running
650 if(time < game_starttime || time < self.race_penalty) {
654 if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
657 // do not even think about shooting if switching
658 if(self.switchweapon != self.weapon)
663 // don't fire if previous attack is not finished
664 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
666 // don't fire while changing weapon
667 if (self.weaponentity.state != WS_READY)
673 float weapon_prepareattack_do(float secondary, float attacktime)
675 self.weaponentity.state = WS_INUSE;
677 self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
679 // if the weapon hasn't been firing continuously, reset the timer
682 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
684 ATTACK_FINISHED(self) = time;
685 //dprint("resetting attack finished to ", ftos(time), "\n");
687 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
689 self.bulletcounter += 1;
690 //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
693 float weapon_prepareattack(float secondary, float attacktime)
695 if(weapon_prepareattack_check(secondary, attacktime))
697 weapon_prepareattack_do(secondary, attacktime);
704 void weapon_thinkf(float fr, float t, void() func)
710 if(fr == WFRAME_DONTCHANGE)
712 fr = self.weaponentity.wframe;
715 else if (fr == WFRAME_IDLE)
724 if (self.weaponentity)
726 self.weaponentity.wframe = fr;
728 if (fr == WFRAME_IDLE)
729 a = self.weaponentity.anim_idle;
730 else if (fr == WFRAME_FIRE1)
731 a = self.weaponentity.anim_fire1;
732 else if (fr == WFRAME_FIRE2)
733 a = self.weaponentity.anim_fire2;
734 else // if (fr == WFRAME_RELOAD)
735 a = self.weaponentity.anim_reload;
736 a_z *= g_weaponratefactor;
737 setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
744 if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
746 backtrace("Tried to override initial weapon think function - should this really happen?");
749 t *= W_WeaponRateFactor();
751 // VorteX: haste can be added here
752 if (self.weapon_think == w_ready)
754 self.weapon_nextthink = time;
755 //dprint("started firing at ", ftos(time), "\n");
757 if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
759 self.weapon_nextthink = time;
760 //dprint("reset weapon animation timer at ", ftos(time), "\n");
762 self.weapon_nextthink = self.weapon_nextthink + t;
763 self.weapon_think = func;
764 //dprint("next ", ftos(self.weapon_nextthink), "\n");
766 if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
768 if(self.weapon == WEP_SHOTGUN && fr == WFRAME_FIRE2)
769 animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
771 animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
775 if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
776 self.anim_upper_action = 0;
780 float forbidWeaponUse()
782 if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
784 if(round_handler_IsActive() && !round_handler_IsRoundStarted())
786 if(self.player_blocked)
788 if(self.freezetag_frozen)
798 self.weapon_frametime = frametime;
800 if (!self.weaponentity || self.health < 1)
801 return; // Dead player can't use weapons and injure impulse commands
803 if(forbidWeaponUse())
804 if(self.weaponentity.state != WS_CLEAR)
810 if(!self.switchweapon)
813 self.switchingweapon = 0;
814 self.weaponentity.state = WS_CLEAR;
815 self.weaponname = "";
816 self.items &~= IT_AMMO;
820 makevectors(self.v_angle);
821 fo = v_forward; // save them in case the weapon think functions change it
826 if (self.weapon != self.switchweapon)
828 if (self.weaponentity.state == WS_CLEAR)
831 self.switchingweapon = self.switchweapon;
832 entity newwep = get_weaponinfo(self.switchweapon);
834 self.items &~= IT_AMMO;
835 self.items = self.items | (newwep.items & IT_AMMO);
837 // the two weapon entities will notice this has changed and update their models
838 self.weapon = self.switchweapon;
839 self.weaponname = newwep.mdl;
840 self.bulletcounter = 0; // WEAPONTODO
841 WEP_ACTION(self.switchweapon, WR_SETUP);
842 self.weaponentity.state = WS_RAISE;
844 // set our clip load to the load of the weapon we switched to, if it's reloadable
845 if(newwep.spawnflags & WEP_FLAG_RELOADABLE && cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"))) // prevent accessing undefined cvars
847 self.clip_load = self.(weapon_load[self.switchweapon]);
848 self.clip_size = cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"));
851 self.clip_load = self.clip_size = 0;
853 // VorteX: add player model weapon select frame here
854 // setcustomframe(PlayerWeaponRaise);
855 weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
856 //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_raise", newwep.netname), cvar(sprintf("g_balance_%s_switchdelay_raise", newwep.netname))));
857 weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, '0 0 0');
859 else if (self.weaponentity.state == WS_DROP)
861 // in dropping phase we can switch at any time
862 self.switchingweapon = self.switchweapon;
864 else if (self.weaponentity.state == WS_READY)
867 self.switchingweapon = self.switchweapon;
869 entity oldwep = get_weaponinfo(self.weapon);
871 #ifndef INDEPENDENT_ATTACK_FINISHED
872 if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
875 sound (self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
876 self.weaponentity.state = WS_DROP;
877 // set up weapon switch think in the future, and start drop anim
878 weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
879 //print(sprintf("W_WeaponFrame(): cvar: %s, value: %f\n", sprintf("g_balance_%s_switchdelay_drop", oldwep.netname), cvar(sprintf("g_balance_%s_switchdelay_drop", oldwep.netname))));
880 weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, PLAYER_WEAPONSELECTION_RANGE);
881 #ifndef INDEPENDENT_ATTACK_FINISHED
887 // LordHavoc: network timing test code
889 // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
894 // call the think code which may fire the weapon
895 // and do so multiple times to resolve framerate dependency issues if the
896 // server framerate is very low and the weapon fire rate very high
899 while (c < W_TICSPERFRAME)
902 if(w && !WEPSET_CONTAINS_EW(self, w))
904 if(self.weapon == self.switchweapon)
905 W_SwitchWeapon_Force(self, w_getbestweapon(self));
914 WEP_ACTION(self.weapon, WR_THINK);
916 WEP_ACTION(self.weapon, WR_GONETHINK);
918 if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
920 if(self.weapon_think)
928 bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
933 if (self.items & IT_CELLS)
934 self.currentammo = self.ammo_cells;
935 else if (self.items & IT_ROCKETS)
936 self.currentammo = self.ammo_rockets;
937 else if (self.items & IT_NAILS)
938 self.currentammo = self.ammo_nails;
939 else if (self.items & IT_SHELLS)
940 self.currentammo = self.ammo_shells;
942 self.currentammo = 1;
946 void weapon_boblayer1(float spd, vector org)
948 // VorteX: haste can be added here
951 vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity, float forceAbsolute)
957 mvelocity = mvelocity * g_weaponspeedfactor;
959 mdirection = normalize(mvelocity);
960 mspeed = vlen(mvelocity);
962 outvelocity = get_shotvelocity(pvelocity, mdirection, mspeed, (forceAbsolute ? 0 : autocvar_g_projectiles_newton_style), autocvar_g_projectiles_newton_style_2_minfactor, autocvar_g_projectiles_newton_style_2_maxfactor);
967 void W_AttachToShotorg(entity flash, vector offset)
971 flash.angles_z = random() * 360;
973 if(gettagindex(self.weaponentity, "shot"))
974 setattachment(flash, self.weaponentity, "shot");
976 setattachment(flash, self.weaponentity, "tag_shot");
977 setorigin(flash, offset);
980 copyentity(flash, xflash);
982 flash.viewmodelforclient = self;
984 if(self.weaponentity.oldorigin_x > 0)
986 setattachment(xflash, self.exteriorweaponentity, "");
987 setorigin(xflash, self.weaponentity.oldorigin + offset);
991 if(gettagindex(self.exteriorweaponentity, "shot"))
992 setattachment(xflash, self.exteriorweaponentity, "shot");
994 setattachment(xflash, self.exteriorweaponentity, "tag_shot");
995 setorigin(xflash, offset);
1001 float mspercallsstyle;
1002 float mspercallcount;
1004 void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
1006 if(missile.owner == world)
1007 error("Unowned missile");
1009 dir = dir + upDir * (pUpSpeed / pSpeed);
1010 dir_z += pZSpeed / pSpeed;
1011 pSpeed *= vlen(dir);
1012 dir = normalize(dir);
1015 if(autocvar_g_projectiles_spread_style != mspercallsstyle)
1017 mspercallsum = mspercallcount = 0;
1018 mspercallsstyle = autocvar_g_projectiles_spread_style;
1020 mspercallsum -= gettime(GETTIME_HIRES);
1022 dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
1024 mspercallsum += gettime(GETTIME_HIRES);
1025 mspercallcount += 1;
1026 print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
1029 missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir, forceAbsolute);
1032 void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)
1034 W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread, FALSE);
1037 #define W_SETUPPROJECTILEVELOCITY_UP(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), cvar(#s "_speed_up"), cvar(#s "_speed_z"), cvar(#s "_spread"), FALSE)
1038 #define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"), FALSE)
1040 void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload) // WEAPONTODO: why does this have ammo_type?
1042 if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
1045 // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
1048 self.clip_load -= ammo_use;
1049 self.(weapon_load[self.weapon]) = self.clip_load;
1052 self.(self.current_ammo) -= ammo_use;
1055 // weapon reloading code
1057 .float reload_ammo_amount, reload_ammo_min, reload_time;
1058 .float reload_complain;
1059 .string reload_sound;
1061 void W_ReloadedAndReady()
1063 // finish the reloading process, and do the ammo transfer
1065 self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
1067 // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
1068 if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)
1069 self.clip_load = self.reload_ammo_amount;
1072 while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have
1074 self.clip_load += 1;
1075 self.(self.current_ammo) -= 1;
1078 self.(weapon_load[self.weapon]) = self.clip_load;
1080 // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
1081 // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
1082 // so your weapon is disabled for a few seconds without reason
1084 //ATTACK_FINISHED(self) -= self.reload_time - 1;
1089 void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)
1091 // set global values to work with
1093 self.reload_ammo_min = sent_ammo_min;
1094 self.reload_ammo_amount = sent_ammo_amount;
1095 self.reload_time = sent_time;
1096 self.reload_sound = sent_sound;
1098 // check if we meet the necessary conditions to reload
1101 e = get_weaponinfo(self.weapon);
1103 // don't reload weapons that don't have the RELOADABLE flag
1104 if not(e.spawnflags & WEP_FLAG_RELOADABLE)
1106 dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
1110 // return if reloading is disabled for this weapon
1111 if(!self.reload_ammo_amount)
1114 // our weapon is fully loaded, no need to reload
1115 if (self.clip_load >= self.reload_ammo_amount)
1118 // no ammo, so nothing to load
1119 if(!self.(self.current_ammo) && self.reload_ammo_min)
1120 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
1122 if(IS_REAL_CLIENT(self) && self.reload_complain < time)
1124 play2(self, "weapons/unavailable.wav");
1125 sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
1126 self.reload_complain = time + 1;
1128 // switch away if the amount of ammo is not enough to keep using this weapon
1129 if not(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))
1131 self.clip_load = -1; // reload later
1132 W_SwitchToOtherWeapon(self);
1137 if (self.weaponentity)
1139 if (self.weaponentity.wframe == WFRAME_RELOAD)
1142 // allow switching away while reloading, but this will cause a new reload!
1143 self.weaponentity.state = WS_READY;
1146 // now begin the reloading process
1148 sound (self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTN_NORM);
1150 // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
1151 // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
1152 // so your weapon is disabled for a few seconds without reason
1154 //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
1156 weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
1158 if(self.clip_load < 0)
1160 self.old_clip_load = self.clip_load;
1161 self.clip_load = self.(weapon_load[self.weapon]) = -1;