]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/weapons/main.qc
Remove the weapon_setup function, it's stupid
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / weapons / main.qc
1 /*
2 ===========================================================================
3
4   CLIENT WEAPONSYSTEM CODE
5   Bring back W_Weaponframe
6
7 ===========================================================================
8 */
9
10 .float weapon_frametime;
11
12 float W_WeaponRateFactor()
13 {
14         float t;
15         t = 1.0 / g_weaponratefactor;
16
17         return t;
18 }
19
20 void W_SwitchWeapon_Force(entity e, float w)
21 {
22         e.cnt = e.switchweapon;
23         e.switchweapon = w;
24         e.selectweapon = w;
25 }
26
27 .float antilag_debug;
28
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;
35 .float wframe;
36
37 void(float fr, float t, void() func) weapon_thinkf;
38
39 vector w_shotorg;
40 vector w_shotdir;
41 vector w_shotend;
42
43 .float prevstrengthsound;
44 .float prevstrengthsoundattempt;
45 void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound
46 {
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)))
50                 {
51                         sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
52                         player.prevstrengthsound = time;
53                 }
54                 player.prevstrengthsoundattempt = time;
55 }
56
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)
61 {
62         float nudge = 1; // added to traceline target and subtracted from result
63         float oldsolid;
64         vector vecs, dv;
65         oldsolid = ent.dphitcontentsmask;
66         if(ent.weapon == WEP_RIFLE)
67                 ent.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE;
68         else
69                 ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
70         if(antilag)
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
73         else
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;
76
77         vector vf, vr, vu;
78         vf = v_forward;
79         vr = v_right;
80         vu = v_up;
81         w_shotend = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); // warpzone support
82         v_forward = vf;
83         v_right = vr;
84         v_up = vu;
85
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;
89
90         // track max damage
91         if(accuracy_canbegooddamage(ent))
92                 accuracy_add(ent, ent.weapon, maxdamage, 0);
93
94         W_HitPlotAnalysis(ent, v_forward, v_right, v_up);
95
96         if(ent.weaponentity.movedir_x > 0)
97                 vecs = ent.weaponentity.movedir;
98         else
99                 vecs = '0 0 0';
100
101         dv = v_right * -vecs_y + v_up * vecs_z;
102         w_shotorg = ent.origin + ent.view_ofs + dv;
103
104         // now move the shotorg forward as much as requested if possible
105         if(antilag)
106         {
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);
109                 else
110                         tracebox_antilag(ent, w_shotorg, mi, ma, w_shotorg + v_forward * (vecs_x + nudge), MOVE_NORMAL, ent, ANTILAG_LATENCY(ent));
111         }
112         else
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);
117
118         if (antilag)
119         if (!ent.cvar_cl_noantilag)
120         {
121                 if (autocvar_g_antilag == 1) // switch to "ghost" if not hitting original
122                 {
123                         traceline(w_shotorg, w_shotorg + w_shotdir * range, MOVE_NORMAL, ent);
124                         if (!trace_ent.takedamage)
125                         {
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))
128                                 {
129                                         entity e;
130                                         e = trace_ent;
131                                         traceline(w_shotorg, e.origin, MOVE_NORMAL, ent);
132                                         if(trace_ent == e)
133                                                 w_shotdir = normalize(trace_ent.origin - w_shotorg);
134                                 }
135                         }
136                 }
137                 else if(autocvar_g_antilag == 3) // client side hitscan
138                 {
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
144                         {
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)
149                                 {
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);
154                                         else
155                                                 print("antilag fail\n");
156                                 }
157                         }
158                 }
159         }
160
161         ent.dphitcontentsmask = oldsolid; // restore solid type (generally SOLID_SLIDEBOX)
162
163         if (!g_norecoil)
164                 ent.punchangle_x = recoil * -1;
165
166         if (snd != "")
167         {
168                 sound (ent, chan, snd, VOL_BASE, ATTN_NORM);
169                 W_PlayStrengthSound(ent);
170         }
171
172         // nudge w_shotend so a trace to w_shotend hits
173         w_shotend = w_shotend + normalize(w_shotend - w_shotorg) * nudge;
174 }
175
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)
181
182 float CL_Weaponentity_CustomizeEntityForClient()
183 {
184         self.viewmodelforclient = self.owner;
185         if(IS_SPEC(other))
186                 if(other.enemy == self.owner)
187                         self.viewmodelforclient = other;
188         return TRUE;
189 }
190
191 /*
192  * supported formats:
193  *
194  * 1. simple animated model, muzzle flash handling on h_ model:
195  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
196  *      tags:
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
202  *
203  * 2. simple animated model, muzzle flash handling on v_ model:
204  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
205  *      tags:
206  *        weapon = attachment for v_tuba.md3
207  *    v_tuba.md3 - first and third person model
208  *      tags:
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
212  *
213  * 3. fully animated model, muzzle flash handling on h_ model:
214  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
215  *      tags:
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
221  *
222  * 4. fully animated model, muzzle flash handling on v_ model:
223  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
224  *      tags:
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
228  *      tags:
229  *        shot = muzzle end (for muzzle flashes)
230  *    g_tuba.md3 - pickup model
231  */
232
233 // writes:
234 //   self.origin, self.angles
235 //   self.weaponentity
236 //   self.movedir, self.view_ofs
237 //   attachment stuff
238 //   anim stuff
239 // to free:
240 //   call again with ""
241 //   remove the ent
242 void CL_WeaponEntity_SetModel(string name)
243 {
244         float v_shot_idx;
245         if (name != "")
246         {
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
252                 if(!v_shot_idx)
253                         v_shot_idx = gettagindex(self, "tag_shot");
254
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');
261
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"))
265                 {
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");
270                 }
271                 else if(gettagindex(self, "tag_weapon"))
272                 {
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");
277                 }
278                 else
279                 {
280                         if(self.weaponentity)
281                                 remove(self.weaponentity);
282                         self.weaponentity = world;
283                 }
284
285                 setorigin(self,'0 0 0');
286                 self.angles = '0 0 0';
287                 self.frame = 0;
288                 self.viewmodelforclient = world;
289
290                 float idx;
291
292                 if(v_shot_idx) // v_ model attached to invisible h_ model
293                 {
294                         self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
295                 }
296                 else
297                 {
298                         idx = gettagindex(self, "shot");
299                         if(!idx)
300                                 idx = gettagindex(self, "tag_shot");
301                         if(idx)
302                                 self.movedir = gettaginfo(self, idx);
303                         else
304                         {
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';
307                         }
308                 }
309
310                 if(self.weaponentity) // v_ model attached to invisible h_ model
311                 {
312                         idx = gettagindex(self.weaponentity, "shell");
313                         if(!idx)
314                                 idx = gettagindex(self.weaponentity, "tag_shell");
315                         if(idx)
316                                 self.spawnorigin = gettaginfo(self.weaponentity, idx);
317                 }
318                 else
319                         idx = 0;
320                 if(!idx)
321                 {
322                         idx = gettagindex(self, "shell");
323                         if(!idx)
324                                 idx = gettagindex(self, "tag_shell");
325                         if(idx)
326                                 self.spawnorigin = gettaginfo(self, idx);
327                         else
328                         {
329                                 print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
330                                 self.spawnorigin = self.movedir;
331                         }
332                 }
333
334                 if(v_shot_idx)
335                 {
336                         self.oldorigin = '0 0 0'; // use regular attachment
337                 }
338                 else
339                 {
340                         if(self.weaponentity)
341                         {
342                                 idx = gettagindex(self, "weapon");
343                                 if(!idx)
344                                         idx = gettagindex(self, "tag_weapon");
345                         }
346                         else
347                         {
348                                 idx = gettagindex(self, "handle");
349                                 if(!idx)
350                                         idx = gettagindex(self, "tag_handle");
351                         }
352                         if(idx)
353                         {
354                                 self.oldorigin = self.movedir - gettaginfo(self, idx);
355                         }
356                         else
357                         {
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
360                         }
361                 }
362
363                 self.viewmodelforclient = self.owner;
364         }
365         else
366         {
367                 self.model = "";
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';
378         }
379
380         self.view_ofs = '0 0 0';
381
382         if(self.movedir_x >= 0)
383         {
384                 vector v0;
385                 v0 = self.movedir;
386                 self.movedir = shotorg_adjust(v0, FALSE, FALSE);
387                 self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
388         }
389         self.owner.stat_shotorg = compressShotOrigin(self.movedir);
390         self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
391
392         self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
393
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);
399 }
400
401 vector CL_Weapon_GetShotOrg(float wpn)
402 {
403         entity wi, oldself;
404         vector ret;
405         wi = get_weaponinfo(wpn);
406         oldself = self;
407         self = spawn();
408         CL_WeaponEntity_SetModel(wi.mdl);
409         ret = self.movedir;
410         CL_WeaponEntity_SetModel("");
411         remove(self);
412         self = oldself;
413         return ret;
414 }
415
416 void CL_Weaponentity_Think()
417 {
418         float tb;
419         self.nextthink = time;
420         if (intermission_running)
421                 self.frame = self.anim_idle_x;
422         if (self.owner.weaponentity != self)
423         {
424                 if (self.weaponentity)
425                         remove(self.weaponentity);
426                 remove(self);
427                 return;
428         }
429         if (self.owner.deadflag != DEAD_NO)
430         {
431                 self.model = "";
432                 if (self.weaponentity)
433                         self.weaponentity.model = "";
434                 return;
435         }
436         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
437         {
438                 self.weaponname = self.owner.weaponname;
439                 self.dmg = self.owner.modelindex;
440                 self.deadflag = self.owner.deadflag;
441
442                 CL_WeaponEntity_SetModel(self.owner.weaponname);
443         }
444
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;
451         self.effects |= tb;
452
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;
457         else
458                 self.alpha = 1;
459
460         self.glowmod = self.owner.weaponentity_glowmod;
461         self.colormap = self.owner.colormap;
462         if (self.weaponentity)
463         {
464                 self.weaponentity.effects = self.effects;
465                 self.weaponentity.alpha = self.alpha;
466                 self.weaponentity.colormap = self.colormap;
467                 self.weaponentity.glowmod = self.glowmod;
468         }
469
470         self.angles = '0 0 0';
471         
472         float f = (self.owner.weapon_nextthink - time);
473         if (self.state == WS_RAISE && !intermission_running)
474         {
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;
479         }
480         else if (self.state == WS_DROP && !intermission_running)
481         {
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;
486         }
487         else if (self.state == WS_CLEAR)
488         {
489                 f = 1;
490                 self.angles_x = -90 * f * f;
491         }
492 }
493
494 void CL_ExteriorWeaponentity_Think()
495 {
496         float tag_found;
497         self.nextthink = time;
498         if (self.owner.exteriorweaponentity != self)
499         {
500                 remove(self);
501                 return;
502         }
503         if (self.owner.deadflag != DEAD_NO)
504         {
505                 self.model = "";
506                 return;
507         }
508         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
509         {
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
515                 else
516                         self.model = "";
517
518                 if((tag_found = gettagindex(self.owner, "tag_weapon")))
519                 {
520                         self.tag_index = tag_found;
521                         self.tag_entity = self.owner;
522                 }
523                 else
524                         setattachment(self, self.owner, "bip01 r hand");
525         }
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;
533         else
534                 self.alpha = 1;
535
536         self.glowmod = self.owner.weaponentity_glowmod;
537         self.colormap = self.owner.colormap;
538
539         CSQCMODEL_AUTOUPDATE();
540 }
541
542 // spawning weaponentity for client
543 void CL_SpawnWeaponentity()
544 {
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;
557
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;
567
568         {
569                 entity oldself = self;
570                 self = self.exteriorweaponentity;
571                 CSQCMODEL_AUTOINIT();
572                 self = oldself;
573         }
574 }
575
576 void Send_WeaponComplain (entity e, float wpn, string wpnname, float type)
577 {
578         msg_entity = e;
579         WriteByte(MSG_ONE, SVC_TEMPENTITY);
580         WriteByte(MSG_ONE, TE_CSQC_WEAPONCOMPLAIN);
581         WriteByte(MSG_ONE, wpn);
582         WriteString(MSG_ONE, wpnname);
583         WriteByte(MSG_ONE, type);
584 }
585
586 .float hasweapon_complain_spam;
587
588 float client_hasweapon(entity cl, float wpn, float andammo, float complain)
589 {
590         float f;
591         entity oldself;
592
593         if(time < self.hasweapon_complain_spam)
594                 complain = 0;
595         if(complain)
596                 self.hasweapon_complain_spam = time + 0.2;
597
598         if (wpn < WEP_FIRST || wpn > WEP_LAST)
599         {
600                 if (complain)
601                         sprint(self, "Invalid weapon\n");
602                 return FALSE;
603         }
604         if (WEPSET_CONTAINS_EW(cl, wpn))
605         {
606                 if (andammo)
607                 {
608                         if(cl.items & IT_UNLIMITED_WEAPON_AMMO)
609                         {
610                                 f = 1;
611                         }
612                         else
613                         {
614                                 oldself = self;
615                                 self = cl;
616                                 f = WEP_ACTION(wpn, WR_CHECKAMMO1);
617                                 f = f + WEP_ACTION(wpn, WR_CHECKAMMO2);
618
619                                 // always allow selecting the Mine Layer if we placed mines, so that we can detonate them
620                                 entity mine;
621                                 if(wpn == WEP_MINE_LAYER)
622                                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
623                                         f = 1;
624
625                                 self = oldself;
626                         }
627                         if (!f)
628                         {
629                                 if (complain)
630                                 if(IS_REAL_CLIENT(cl))
631                                 {
632                                         play2(cl, "weapons/unavailable.wav");
633                                         Send_WeaponComplain (cl, wpn, W_Name(wpn), 0);
634                                 }
635                                 return FALSE;
636                         }
637                 }
638                 return TRUE;
639         }
640         if (complain)
641         {
642                 // DRESK - 3/16/07
643                 // Report Proper Weapon Status / Modified Weapon Ownership Message
644                 if (WEPSET_CONTAINS_AW(weaponsInMap, wpn))
645                 {
646                         Send_WeaponComplain(cl, wpn, W_Name(wpn), 1);
647
648                         if(autocvar_g_showweaponspawns)
649                         {
650                                 entity e;
651                                 string s;
652
653                                 e = get_weaponinfo(wpn);
654                                 s = e.model2;
655
656                                 for(e = world; (e = findfloat(e, weapon, wpn)); )
657                                 {
658                                         if(e.classname == "droppedweapon")
659                                                 continue;
660                                         if not(e.flags & FL_ITEM)
661                                                 continue;
662                                         WaypointSprite_Spawn(
663                                                 s,
664                                                 1, 0,
665                                                 world, e.origin,
666                                                 self, 0,
667                                                 world, enemy,
668                                                 0,
669                                                 RADARICON_NONE, '0 0 0'
670                                         );
671                                 }
672                         }
673                 }
674                 else
675                 {
676                         Send_WeaponComplain (cl, wpn, W_Name(wpn), 2);
677                 }
678
679                 play2(cl, "weapons/unavailable.wav");
680         }
681         return FALSE;
682 }
683
684 // Weapon subs
685 void w_clear()
686 {
687         if (self.weapon != -1)
688         {
689                 self.weapon = 0;
690                 self.switchingweapon = 0;
691         }
692         if (self.weaponentity)
693         {
694                 self.weaponentity.state = WS_CLEAR;
695                 self.weaponentity.effects = 0;
696         }
697 }
698
699 void w_ready()
700 {
701         if (self.weaponentity)
702                 self.weaponentity.state = WS_READY;
703         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
704 }
705
706 // perform weapon to attack (weaponstate and attack_finished check is here)
707 void W_SwitchToOtherWeapon(entity pl)
708 {
709         // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)
710         float w, ww;
711         w = pl.weapon;
712         if(WEPSET_CONTAINS_EW(pl, w))
713         {
714                 WEPSET_ANDNOT_EW(pl, w);
715                 ww = w_getbestweapon(pl);
716                 WEPSET_OR_EW(pl, w);
717         }
718         else
719                 ww = w_getbestweapon(pl);
720         if(ww)
721                 W_SwitchWeapon_Force(pl, ww);
722 }
723
724 .float prevdryfire;
725 .float prevwarntime;
726 float weapon_prepareattack_checkammo(float secondary)
727 {
728         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
729         if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
730         {
731                 // always keep the Mine Layer if we placed mines, so that we can detonate them
732                 entity mine;
733                 if(self.weapon == WEP_MINE_LAYER)
734                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
735                         return FALSE;
736
737                 if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
738                 {
739                         sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM);
740                         self.prevdryfire = time;
741                 }
742
743                 if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
744                 {
745                         if(time - self.prevwarntime > 1)
746                         {
747                                 Send_Notification(
748                                         NOTIF_ONE,
749                                         self,
750                                         MSG_MULTI,
751                                         ITEM_WEAPON_PRIMORSEC,
752                                         self.weapon,
753                                         secondary,
754                                         (1 - secondary)
755                                 );
756                         }
757                         self.prevwarntime = time;
758                 }
759                 else // this weapon is totally unable to fire, switch to another one
760                 {
761                         W_SwitchToOtherWeapon(self);
762                 }
763                 
764                 return FALSE;
765         }
766         return TRUE;
767 }
768 .float race_penalty;
769 float weapon_prepareattack_check(float secondary, float attacktime)
770 {
771         if(!weapon_prepareattack_checkammo(secondary))
772                 return FALSE;
773
774         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
775         //if all players readied up and the countdown is running
776         if(time < game_starttime || time < self.race_penalty) {
777                 return FALSE;
778         }
779
780         if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
781                 return FALSE;
782
783         // do not even think about shooting if switching
784         if(self.switchweapon != self.weapon)
785                 return FALSE;
786
787         if(attacktime >= 0)
788         {
789                 // don't fire if previous attack is not finished
790                 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
791                         return FALSE;
792                 // don't fire while changing weapon
793                 if (self.weaponentity.state != WS_READY)
794                         return FALSE;
795         }
796
797         return TRUE;
798 }
799 float weapon_prepareattack_do(float secondary, float attacktime)
800 {
801         self.weaponentity.state = WS_INUSE;
802
803         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
804
805         // if the weapon hasn't been firing continuously, reset the timer
806         if(attacktime >= 0)
807         {
808                 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
809                 {
810                         ATTACK_FINISHED(self) = time;
811                         //dprint("resetting attack finished to ", ftos(time), "\n");
812                 }
813                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
814         }
815         self.bulletcounter += 1;
816         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
817         return TRUE;
818 }
819 float weapon_prepareattack(float secondary, float attacktime)
820 {
821         if(weapon_prepareattack_check(secondary, attacktime))
822         {
823                 weapon_prepareattack_do(secondary, attacktime);
824                 return TRUE;
825         }
826         else
827                 return FALSE;
828 }
829
830 void weapon_thinkf(float fr, float t, void() func)
831 {
832         vector a;
833         vector of, or, ou;
834         float restartanim;
835
836         if(fr == WFRAME_DONTCHANGE)
837         {
838                 fr = self.weaponentity.wframe;
839                 restartanim = FALSE;
840         }
841         else if (fr == WFRAME_IDLE)
842                 restartanim = FALSE;
843         else
844                 restartanim = TRUE;
845
846         of = v_forward;
847         or = v_right;
848         ou = v_up;
849
850         if (self.weaponentity)
851         {
852                 self.weaponentity.wframe = fr;
853                 a = '0 0 0';
854                 if (fr == WFRAME_IDLE)
855                         a = self.weaponentity.anim_idle;
856                 else if (fr == WFRAME_FIRE1)
857                         a = self.weaponentity.anim_fire1;
858                 else if (fr == WFRAME_FIRE2)
859                         a = self.weaponentity.anim_fire2;
860                 else // if (fr == WFRAME_RELOAD)
861                         a = self.weaponentity.anim_reload;
862                 a_z *= g_weaponratefactor;
863                 setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
864         }
865
866         v_forward = of;
867         v_right = or;
868         v_up = ou;
869
870         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
871         {
872                 backtrace("Tried to override initial weapon think function - should this really happen?");
873         }
874
875         t *= W_WeaponRateFactor();
876
877         // VorteX: haste can be added here
878         if (self.weapon_think == w_ready)
879         {
880                 self.weapon_nextthink = time;
881                 //dprint("started firing at ", ftos(time), "\n");
882         }
883         if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
884         {
885                 self.weapon_nextthink = time;
886                 //dprint("reset weapon animation timer at ", ftos(time), "\n");
887         }
888         self.weapon_nextthink = self.weapon_nextthink + t;
889         self.weapon_think = func;
890         //dprint("next ", ftos(self.weapon_nextthink), "\n");
891
892         if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
893         {
894                 if(self.weapon == WEP_SHOTGUN && fr == WFRAME_FIRE2)
895                         animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
896                 else
897                         animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
898         }
899         else
900         {
901                 if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
902                         self.anim_upper_action = 0;
903         }
904 }
905
906 void weapon_boblayer1(float spd, vector org)
907 {
908         // VorteX: haste can be added here
909 }
910
911 vector W_CalculateProjectileVelocity(vector pvelocity, vector mvelocity, float forceAbsolute)
912 {
913         vector mdirection;
914         float mspeed;
915         vector outvelocity;
916
917         mvelocity = mvelocity * g_weaponspeedfactor;
918
919         mdirection = normalize(mvelocity);
920         mspeed = vlen(mvelocity);
921
922         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);
923
924         return outvelocity;
925 }
926
927 void W_AttachToShotorg(entity flash, vector offset)
928 {
929         entity xflash;
930         flash.owner = self;
931         flash.angles_z = random() * 360;
932
933         if(gettagindex(self.weaponentity, "shot"))
934                 setattachment(flash, self.weaponentity, "shot");
935         else
936                 setattachment(flash, self.weaponentity, "tag_shot");
937         setorigin(flash, offset);
938
939         xflash = spawn();
940         copyentity(flash, xflash);
941
942         flash.viewmodelforclient = self;
943
944         if(self.weaponentity.oldorigin_x > 0)
945         {
946                 setattachment(xflash, self.exteriorweaponentity, "");
947                 setorigin(xflash, self.weaponentity.oldorigin + offset);
948         }
949         else
950         {
951                 if(gettagindex(self.exteriorweaponentity, "shot"))
952                         setattachment(xflash, self.exteriorweaponentity, "shot");
953                 else
954                         setattachment(xflash, self.exteriorweaponentity, "tag_shot");
955                 setorigin(xflash, offset);
956         }
957 }
958
959 #if 0
960 float mspercallsum;
961 float mspercallsstyle;
962 float mspercallcount;
963 #endif
964 void W_SetupProjectileVelocityEx(entity missile, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute)
965 {
966         if(missile.owner == world)
967                 error("Unowned missile");
968
969         dir = dir + upDir * (pUpSpeed / pSpeed);
970         dir_z += pZSpeed / pSpeed;
971         pSpeed *= vlen(dir);
972         dir = normalize(dir);
973
974 #if 0
975         if(autocvar_g_projectiles_spread_style != mspercallsstyle)
976         {
977                 mspercallsum = mspercallcount = 0;
978                 mspercallsstyle = autocvar_g_projectiles_spread_style;
979         }
980         mspercallsum -= gettime(GETTIME_HIRES);
981 #endif
982         dir = W_CalculateSpread(dir, spread, g_weaponspreadfactor, autocvar_g_projectiles_spread_style);
983 #if 0
984         mspercallsum += gettime(GETTIME_HIRES);
985         mspercallcount += 1;
986         print("avg: ", ftos(mspercallcount / mspercallsum), " per sec\n");
987 #endif
988
989         missile.velocity = W_CalculateProjectileVelocity(missile.owner.velocity, pSpeed * dir, forceAbsolute);
990 }
991
992 void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread)
993 {
994         W_SetupProjectileVelocityEx(missile, w_shotdir, v_up, pSpeed, 0, 0, spread, FALSE);
995 }
996
997 #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)
998 #define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"), FALSE)
999
1000 void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload) // WEAPONTODO: why does this have ammo_type?
1001 {
1002         if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
1003                 return;
1004
1005         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
1006         if(ammo_reload)
1007         {
1008                 self.clip_load -= ammo_use;
1009                 self.(weapon_load[self.weapon]) = self.clip_load;
1010         }
1011         else
1012                 self.(self.current_ammo) -= ammo_use;
1013 }
1014
1015 // weapon reloading code
1016
1017 .float reload_ammo_amount, reload_ammo_min, reload_time;
1018 .float reload_complain;
1019 .string reload_sound;
1020
1021 void W_ReloadedAndReady()
1022 {
1023         // finish the reloading process, and do the ammo transfer
1024
1025         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
1026
1027         // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
1028         if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)
1029                 self.clip_load = self.reload_ammo_amount;
1030         else
1031         {
1032                 while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have
1033                 {
1034                         self.clip_load += 1;
1035                         self.(self.current_ammo) -= 1;
1036                 }
1037         }
1038         self.(weapon_load[self.weapon]) = self.clip_load;
1039
1040         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
1041         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
1042         // so your weapon is disabled for a few seconds without reason
1043
1044         //ATTACK_FINISHED(self) -= self.reload_time - 1;
1045
1046         w_ready();
1047 }
1048
1049 void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)
1050 {
1051         // set global values to work with
1052
1053         self.reload_ammo_min = sent_ammo_min;
1054         self.reload_ammo_amount = sent_ammo_amount;
1055         self.reload_time = sent_time;
1056         self.reload_sound = sent_sound;
1057
1058         // check if we meet the necessary conditions to reload
1059
1060         entity e;
1061         e = get_weaponinfo(self.weapon);
1062
1063         // don't reload weapons that don't have the RELOADABLE flag
1064         if not(e.spawnflags & WEP_FLAG_RELOADABLE)
1065         {
1066                 dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
1067                 return;
1068         }
1069
1070         // return if reloading is disabled for this weapon
1071         if(!self.reload_ammo_amount)
1072                 return;
1073
1074         // our weapon is fully loaded, no need to reload
1075         if (self.clip_load >= self.reload_ammo_amount)
1076                 return;
1077
1078         // no ammo, so nothing to load
1079         if(!self.(self.current_ammo) && self.reload_ammo_min)
1080         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
1081         {
1082                 if(IS_REAL_CLIENT(self) && self.reload_complain < time)
1083                 {
1084                         play2(self, "weapons/unavailable.wav");
1085                         sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
1086                         self.reload_complain = time + 1;
1087                 }
1088                 // switch away if the amount of ammo is not enough to keep using this weapon
1089                 if not(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))
1090                 {
1091                         self.clip_load = -1; // reload later
1092                         W_SwitchToOtherWeapon(self);
1093                 }
1094                 return;
1095         }
1096
1097         if (self.weaponentity)
1098         {
1099                 if (self.weaponentity.wframe == WFRAME_RELOAD)
1100                         return;
1101
1102                 // allow switching away while reloading, but this will cause a new reload!
1103                 self.weaponentity.state = WS_READY;
1104         }
1105
1106         // now begin the reloading process
1107
1108         sound (self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTN_NORM);
1109
1110         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
1111         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
1112         // so your weapon is disabled for a few seconds without reason
1113
1114         //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
1115
1116         weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
1117
1118         if(self.clip_load < 0)
1119                 self.clip_load = 0;
1120         self.old_clip_load = self.clip_load;
1121         self.clip_load = self.(weapon_load[self.weapon]) = -1;
1122 }