]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/weapons/weaponsystem.qc
Move some more things to selection.qc and out of weaponsystem.qc
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / weapons / weaponsystem.qc
1 /*
2 ===========================================================================
3
4   CLIENT WEAPONSYSTEM CODE
5   Bring back W_Weaponframe
6
7 ===========================================================================
8 */
9
10 .float weapon_frametime;
11
12 float W_WeaponRateFactor()
13 {
14         float t;
15         t = 1.0 / g_weaponratefactor;
16
17         return t;
18 }
19
20 // VorteX: static frame globals
21 float WFRAME_DONTCHANGE = -1;
22 float WFRAME_FIRE1 = 0;
23 float WFRAME_FIRE2 = 1;
24 float WFRAME_IDLE = 2;
25 float WFRAME_RELOAD = 3;
26 .float wframe;
27
28 void(float fr, float t, void() func) weapon_thinkf;
29
30 .float prevstrengthsound;
31 .float prevstrengthsoundattempt;
32 void W_PlayStrengthSound(entity player) // void W_PlayStrengthSound
33 {
34         if((player.items & IT_STRENGTH)
35                 && ((time > player.prevstrengthsound + autocvar_sv_strengthsound_antispam_time) // prevent insane sound spam
36                 || (time > player.prevstrengthsoundattempt + autocvar_sv_strengthsound_antispam_refire_threshold)))
37                 {
38                         sound(player, CH_TRIGGER, "weapons/strength_fire.wav", VOL_BASE, ATTN_NORM);
39                         player.prevstrengthsound = time;
40                 }
41                 player.prevstrengthsoundattempt = time;
42 }
43
44 float CL_Weaponentity_CustomizeEntityForClient()
45 {
46         self.viewmodelforclient = self.owner;
47         if(IS_SPEC(other))
48                 if(other.enemy == self.owner)
49                         self.viewmodelforclient = other;
50         return TRUE;
51 }
52
53 /*
54  * supported formats:
55  *
56  * 1. simple animated model, muzzle flash handling on h_ model:
57  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
58  *      tags:
59  *        shot = muzzle end (shot origin, also used for muzzle flashes)
60  *        shell = casings ejection point (must be on the right hand side of the gun)
61  *        weapon = attachment for v_tuba.md3
62  *    v_tuba.md3 - first and third person model
63  *    g_tuba.md3 - pickup model
64  *
65  * 2. simple animated model, muzzle flash handling on v_ model:
66  *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
67  *      tags:
68  *        weapon = attachment for v_tuba.md3
69  *    v_tuba.md3 - first and third person model
70  *      tags:
71  *        shot = muzzle end (shot origin, also used for muzzle flashes)
72  *        shell = casings ejection point (must be on the right hand side of the gun)
73  *    g_tuba.md3 - pickup model
74  *
75  * 3. fully animated model, muzzle flash handling on h_ model:
76  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
77  *      tags:
78  *        shot = muzzle end (shot origin, also used for muzzle flashes)
79  *        shell = casings ejection point (must be on the right hand side of the gun)
80  *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
81  *    v_tuba.md3 - third person model
82  *    g_tuba.md3 - pickup model
83  *
84  * 4. fully animated model, muzzle flash handling on v_ model:
85  *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
86  *      tags:
87  *        shot = muzzle end (shot origin)
88  *        shell = casings ejection point (must be on the right hand side of the gun)
89  *    v_tuba.md3 - third person model
90  *      tags:
91  *        shot = muzzle end (for muzzle flashes)
92  *    g_tuba.md3 - pickup model
93  */
94
95 // writes:
96 //   self.origin, self.angles
97 //   self.weaponentity
98 //   self.movedir, self.view_ofs
99 //   attachment stuff
100 //   anim stuff
101 // to free:
102 //   call again with ""
103 //   remove the ent
104 void CL_WeaponEntity_SetModel(string name)
105 {
106         float v_shot_idx;
107         if (name != "")
108         {
109                 // if there is a child entity, hide it until we're sure we use it
110                 if (self.weaponentity)
111                         self.weaponentity.model = "";
112                 setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
113                 v_shot_idx = gettagindex(self, "shot"); // used later
114                 if(!v_shot_idx)
115                         v_shot_idx = gettagindex(self, "tag_shot");
116
117                 setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
118                 // preset some defaults that work great for renamed zym files (which don't need an animinfo)
119                 self.anim_fire1  = animfixfps(self, '0 1 0.01', '0 0 0');
120                 self.anim_fire2  = animfixfps(self, '1 1 0.01', '0 0 0');
121                 self.anim_idle   = animfixfps(self, '2 1 0.01', '0 0 0');
122                 self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
123
124                 // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
125                 // if we don't, this is a "real" animated model
126                 if(gettagindex(self, "weapon"))
127                 {
128                         if (!self.weaponentity)
129                                 self.weaponentity = spawn();
130                         setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
131                         setattachment(self.weaponentity, self, "weapon");
132                 }
133                 else if(gettagindex(self, "tag_weapon"))
134                 {
135                         if (!self.weaponentity)
136                                 self.weaponentity = spawn();
137                         setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
138                         setattachment(self.weaponentity, self, "tag_weapon");
139                 }
140                 else
141                 {
142                         if(self.weaponentity)
143                                 remove(self.weaponentity);
144                         self.weaponentity = world;
145                 }
146
147                 setorigin(self,'0 0 0');
148                 self.angles = '0 0 0';
149                 self.frame = 0;
150                 self.viewmodelforclient = world;
151
152                 float idx;
153
154                 if(v_shot_idx) // v_ model attached to invisible h_ model
155                 {
156                         self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
157                 }
158                 else
159                 {
160                         idx = gettagindex(self, "shot");
161                         if(!idx)
162                                 idx = gettagindex(self, "tag_shot");
163                         if(idx)
164                                 self.movedir = gettaginfo(self, idx);
165                         else
166                         {
167                                 print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
168                                 self.movedir = '0 0 0';
169                         }
170                 }
171
172                 if(self.weaponentity) // v_ model attached to invisible h_ model
173                 {
174                         idx = gettagindex(self.weaponentity, "shell");
175                         if(!idx)
176                                 idx = gettagindex(self.weaponentity, "tag_shell");
177                         if(idx)
178                                 self.spawnorigin = gettaginfo(self.weaponentity, idx);
179                 }
180                 else
181                         idx = 0;
182                 if(!idx)
183                 {
184                         idx = gettagindex(self, "shell");
185                         if(!idx)
186                                 idx = gettagindex(self, "tag_shell");
187                         if(idx)
188                                 self.spawnorigin = gettaginfo(self, idx);
189                         else
190                         {
191                                 print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
192                                 self.spawnorigin = self.movedir;
193                         }
194                 }
195
196                 if(v_shot_idx)
197                 {
198                         self.oldorigin = '0 0 0'; // use regular attachment
199                 }
200                 else
201                 {
202                         if(self.weaponentity)
203                         {
204                                 idx = gettagindex(self, "weapon");
205                                 if(!idx)
206                                         idx = gettagindex(self, "tag_weapon");
207                         }
208                         else
209                         {
210                                 idx = gettagindex(self, "handle");
211                                 if(!idx)
212                                         idx = gettagindex(self, "tag_handle");
213                         }
214                         if(idx)
215                         {
216                                 self.oldorigin = self.movedir - gettaginfo(self, idx);
217                         }
218                         else
219                         {
220                                 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");
221                                 self.oldorigin = '0 0 0'; // there is no way to recover from this
222                         }
223                 }
224
225                 self.viewmodelforclient = self.owner;
226         }
227         else
228         {
229                 self.model = "";
230                 if(self.weaponentity)
231                         remove(self.weaponentity);
232                 self.weaponentity = world;
233                 self.movedir = '0 0 0';
234                 self.spawnorigin = '0 0 0';
235                 self.oldorigin = '0 0 0';
236                 self.anim_fire1  = '0 1 0.01';
237                 self.anim_fire2  = '0 1 0.01';
238                 self.anim_idle   = '0 1 0.01';
239                 self.anim_reload = '0 1 0.01';
240         }
241
242         self.view_ofs = '0 0 0';
243
244         if(self.movedir_x >= 0)
245         {
246                 vector v0;
247                 v0 = self.movedir;
248                 self.movedir = shotorg_adjust(v0, FALSE, FALSE);
249                 self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
250         }
251         self.owner.stat_shotorg = compressShotOrigin(self.movedir);
252         self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
253
254         self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
255
256         // check if an instant weapon switch occurred
257         setorigin(self, self.view_ofs);
258         // reset animstate now
259         self.wframe = WFRAME_IDLE;
260         setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
261 }
262
263 vector CL_Weapon_GetShotOrg(float wpn)
264 {
265         entity wi, oldself;
266         vector ret;
267         wi = get_weaponinfo(wpn);
268         oldself = self;
269         self = spawn();
270         CL_WeaponEntity_SetModel(wi.mdl);
271         ret = self.movedir;
272         CL_WeaponEntity_SetModel("");
273         remove(self);
274         self = oldself;
275         return ret;
276 }
277
278 void CL_Weaponentity_Think()
279 {
280         float tb;
281         self.nextthink = time;
282         if (intermission_running)
283                 self.frame = self.anim_idle_x;
284         if (self.owner.weaponentity != self)
285         {
286                 if (self.weaponentity)
287                         remove(self.weaponentity);
288                 remove(self);
289                 return;
290         }
291         if (self.owner.deadflag != DEAD_NO)
292         {
293                 self.model = "";
294                 if (self.weaponentity)
295                         self.weaponentity.model = "";
296                 return;
297         }
298         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
299         {
300                 self.weaponname = self.owner.weaponname;
301                 self.dmg = self.owner.modelindex;
302                 self.deadflag = self.owner.deadflag;
303
304                 CL_WeaponEntity_SetModel(self.owner.weaponname);
305         }
306
307         tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
308         self.effects = self.owner.effects & EFMASK_CHEAP;
309         self.effects &~= EF_LOWPRECISION;
310         self.effects &~= EF_FULLBRIGHT; // can mask team color, so get rid of it
311         self.effects &~= EF_TELEPORT_BIT;
312         self.effects &~= EF_RESTARTANIM_BIT;
313         self.effects |= tb;
314
315         if(self.owner.alpha == default_player_alpha)
316                 self.alpha = default_weapon_alpha;
317         else if(self.owner.alpha != 0)
318                 self.alpha = self.owner.alpha;
319         else
320                 self.alpha = 1;
321
322         self.glowmod = self.owner.weaponentity_glowmod;
323         self.colormap = self.owner.colormap;
324         if (self.weaponentity)
325         {
326                 self.weaponentity.effects = self.effects;
327                 self.weaponentity.alpha = self.alpha;
328                 self.weaponentity.colormap = self.colormap;
329                 self.weaponentity.glowmod = self.glowmod;
330         }
331
332         self.angles = '0 0 0';
333         
334         float f = (self.owner.weapon_nextthink - time);
335         if (self.state == WS_RAISE && !intermission_running)
336         {
337                 entity newwep = get_weaponinfo(self.owner.switchweapon);
338                 f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise);
339                 //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)));
340                 self.angles_x = -90 * f * f;
341         }
342         else if (self.state == WS_DROP && !intermission_running)
343         {
344                 entity oldwep = get_weaponinfo(self.owner.weapon);
345                 f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop);
346                 //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)));
347                 self.angles_x = -90 * f * f;
348         }
349         else if (self.state == WS_CLEAR)
350         {
351                 f = 1;
352                 self.angles_x = -90 * f * f;
353         }
354 }
355
356 void CL_ExteriorWeaponentity_Think()
357 {
358         float tag_found;
359         self.nextthink = time;
360         if (self.owner.exteriorweaponentity != self)
361         {
362                 remove(self);
363                 return;
364         }
365         if (self.owner.deadflag != DEAD_NO)
366         {
367                 self.model = "";
368                 return;
369         }
370         if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
371         {
372                 self.weaponname = self.owner.weaponname;
373                 self.dmg = self.owner.modelindex;
374                 self.deadflag = self.owner.deadflag;
375                 if (self.owner.weaponname != "")
376                         setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
377                 else
378                         self.model = "";
379
380                 if((tag_found = gettagindex(self.owner, "tag_weapon")))
381                 {
382                         self.tag_index = tag_found;
383                         self.tag_entity = self.owner;
384                 }
385                 else
386                         setattachment(self, self.owner, "bip01 r hand");
387         }
388         self.effects = self.owner.effects;
389         self.effects |= EF_LOWPRECISION;
390         self.effects = self.effects & EFMASK_CHEAP; // eat performance
391         if(self.owner.alpha == default_player_alpha)
392                 self.alpha = default_weapon_alpha;
393         else if(self.owner.alpha != 0)
394                 self.alpha = self.owner.alpha;
395         else
396                 self.alpha = 1;
397
398         self.glowmod = self.owner.weaponentity_glowmod;
399         self.colormap = self.owner.colormap;
400
401         CSQCMODEL_AUTOUPDATE();
402 }
403
404 // spawning weaponentity for client
405 void CL_SpawnWeaponentity()
406 {
407         self.weaponentity = spawn();
408         self.weaponentity.classname = "weaponentity";
409         self.weaponentity.solid = SOLID_NOT;
410         self.weaponentity.owner = self;
411         setmodel(self.weaponentity, ""); // precision set when changed
412         setorigin(self.weaponentity, '0 0 0');
413         self.weaponentity.angles = '0 0 0';
414         self.weaponentity.viewmodelforclient = self;
415         self.weaponentity.flags = 0;
416         self.weaponentity.think = CL_Weaponentity_Think;
417         self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
418         self.weaponentity.nextthink = time;
419
420         self.exteriorweaponentity = spawn();
421         self.exteriorweaponentity.classname = "exteriorweaponentity";
422         self.exteriorweaponentity.solid = SOLID_NOT;
423         self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
424         self.exteriorweaponentity.owner = self;
425         setorigin(self.exteriorweaponentity, '0 0 0');
426         self.exteriorweaponentity.angles = '0 0 0';
427         self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
428         self.exteriorweaponentity.nextthink = time;
429
430         {
431                 entity oldself = self;
432                 self = self.exteriorweaponentity;
433                 CSQCMODEL_AUTOINIT();
434                 self = oldself;
435         }
436 }
437
438 // Weapon subs
439 void w_clear()
440 {
441         if (self.weapon != -1)
442         {
443                 self.weapon = 0;
444                 self.switchingweapon = 0;
445         }
446         if (self.weaponentity)
447         {
448                 self.weaponentity.state = WS_CLEAR;
449                 self.weaponentity.effects = 0;
450         }
451 }
452
453 void w_ready()
454 {
455         if (self.weaponentity)
456                 self.weaponentity.state = WS_READY;
457         weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
458 }
459
460 .float prevdryfire;
461 .float prevwarntime;
462 float weapon_prepareattack_checkammo(float secondary)
463 {
464         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
465         if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
466         {
467                 // always keep the Mine Layer if we placed mines, so that we can detonate them
468                 entity mine;
469                 if(self.weapon == WEP_MINE_LAYER)
470                 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
471                         return FALSE;
472
473                 if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
474                 {
475                         sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM);
476                         self.prevdryfire = time;
477                 }
478
479                 if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
480                 {
481                         if(time - self.prevwarntime > 1)
482                         {
483                                 Send_Notification(
484                                         NOTIF_ONE,
485                                         self,
486                                         MSG_MULTI,
487                                         ITEM_WEAPON_PRIMORSEC,
488                                         self.weapon,
489                                         secondary,
490                                         (1 - secondary)
491                                 );
492                         }
493                         self.prevwarntime = time;
494                 }
495                 else // this weapon is totally unable to fire, switch to another one
496                 {
497                         W_SwitchToOtherWeapon(self);
498                 }
499                 
500                 return FALSE;
501         }
502         return TRUE;
503 }
504 .float race_penalty;
505 float weapon_prepareattack_check(float secondary, float attacktime)
506 {
507         if(!weapon_prepareattack_checkammo(secondary))
508                 return FALSE;
509
510         //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
511         //if all players readied up and the countdown is running
512         if(time < game_starttime || time < self.race_penalty) {
513                 return FALSE;
514         }
515
516         if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
517                 return FALSE;
518
519         // do not even think about shooting if switching
520         if(self.switchweapon != self.weapon)
521                 return FALSE;
522
523         if(attacktime >= 0)
524         {
525                 // don't fire if previous attack is not finished
526                 if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
527                         return FALSE;
528                 // don't fire while changing weapon
529                 if (self.weaponentity.state != WS_READY)
530                         return FALSE;
531         }
532
533         return TRUE;
534 }
535 float weapon_prepareattack_do(float secondary, float attacktime)
536 {
537         self.weaponentity.state = WS_INUSE;
538
539         self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
540
541         // if the weapon hasn't been firing continuously, reset the timer
542         if(attacktime >= 0)
543         {
544                 if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
545                 {
546                         ATTACK_FINISHED(self) = time;
547                         //dprint("resetting attack finished to ", ftos(time), "\n");
548                 }
549                 ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
550         }
551         self.bulletcounter += 1;
552         //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
553         return TRUE;
554 }
555 float weapon_prepareattack(float secondary, float attacktime)
556 {
557         if(weapon_prepareattack_check(secondary, attacktime))
558         {
559                 weapon_prepareattack_do(secondary, attacktime);
560                 return TRUE;
561         }
562         else
563                 return FALSE;
564 }
565
566 void weapon_thinkf(float fr, float t, void() func)
567 {
568         vector a;
569         vector of, or, ou;
570         float restartanim;
571
572         if(fr == WFRAME_DONTCHANGE)
573         {
574                 fr = self.weaponentity.wframe;
575                 restartanim = FALSE;
576         }
577         else if (fr == WFRAME_IDLE)
578                 restartanim = FALSE;
579         else
580                 restartanim = TRUE;
581
582         of = v_forward;
583         or = v_right;
584         ou = v_up;
585
586         if (self.weaponentity)
587         {
588                 self.weaponentity.wframe = fr;
589                 a = '0 0 0';
590                 if (fr == WFRAME_IDLE)
591                         a = self.weaponentity.anim_idle;
592                 else if (fr == WFRAME_FIRE1)
593                         a = self.weaponentity.anim_fire1;
594                 else if (fr == WFRAME_FIRE2)
595                         a = self.weaponentity.anim_fire2;
596                 else // if (fr == WFRAME_RELOAD)
597                         a = self.weaponentity.anim_reload;
598                 a_z *= g_weaponratefactor;
599                 setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
600         }
601
602         v_forward = of;
603         v_right = or;
604         v_up = ou;
605
606         if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
607         {
608                 backtrace("Tried to override initial weapon think function - should this really happen?");
609         }
610
611         t *= W_WeaponRateFactor();
612
613         // VorteX: haste can be added here
614         if (self.weapon_think == w_ready)
615         {
616                 self.weapon_nextthink = time;
617                 //dprint("started firing at ", ftos(time), "\n");
618         }
619         if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
620         {
621                 self.weapon_nextthink = time;
622                 //dprint("reset weapon animation timer at ", ftos(time), "\n");
623         }
624         self.weapon_nextthink = self.weapon_nextthink + t;
625         self.weapon_think = func;
626         //dprint("next ", ftos(self.weapon_nextthink), "\n");
627
628         if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
629         {
630                 if(self.weapon == WEP_SHOTGUN && fr == WFRAME_FIRE2)
631                         animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
632                 else
633                         animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
634         }
635         else
636         {
637                 if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
638                         self.anim_upper_action = 0;
639         }
640 }
641
642 float forbidWeaponUse()
643 {
644         if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
645                 return 1;
646         if(round_handler_IsActive() && !round_handler_IsRoundStarted())
647                 return 1;
648         if(self.player_blocked)
649                 return 1;
650         if(self.freezetag_frozen)
651                 return 1;
652         return 0;
653 }
654
655 void W_WeaponFrame()
656 {
657         vector fo, ri, up;
658
659         if (frametime)
660                 self.weapon_frametime = frametime;
661
662         if (!self.weaponentity || self.health < 1)
663                 return; // Dead player can't use weapons and injure impulse commands
664
665         if(forbidWeaponUse())
666         if(self.weaponentity.state != WS_CLEAR)
667         {
668                 w_ready();
669                 return;
670         }
671
672         if(!self.switchweapon)
673         {
674                 self.weapon = 0;
675                 self.switchingweapon = 0;
676                 self.weaponentity.state = WS_CLEAR;
677                 self.weaponname = "";
678                 self.items &~= IT_AMMO;
679                 return;
680         }
681
682         makevectors(self.v_angle);
683         fo = v_forward; // save them in case the weapon think functions change it
684         ri = v_right;
685         up = v_up;
686
687         // Change weapon
688         if (self.weapon != self.switchweapon)
689         {
690                 if (self.weaponentity.state == WS_CLEAR)
691                 {
692                         // end switching!
693                         self.switchingweapon = self.switchweapon;
694                         entity newwep = get_weaponinfo(self.switchweapon);
695
696                         self.items &~= IT_AMMO;
697                         self.items = self.items | (newwep.items & IT_AMMO);
698
699                         // the two weapon entities will notice this has changed and update their models
700                         self.weapon = self.switchweapon;
701                         self.weaponname = newwep.mdl;
702                         self.bulletcounter = 0; // WEAPONTODO
703                         WEP_ACTION(self.switchweapon, WR_SETUP);
704                         self.weaponentity.state = WS_RAISE;
705
706                         // set our clip load to the load of the weapon we switched to, if it's reloadable
707                         if(newwep.spawnflags & WEP_FLAG_RELOADABLE && cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"))) // prevent accessing undefined cvars
708                         {
709                                 self.clip_load = self.(weapon_load[self.switchweapon]);
710                                 self.clip_size = cvar(strcat("g_balance_", newwep.netname, "_reload_ammo"));
711                         }
712                         else
713                                 self.clip_load = self.clip_size = 0;
714
715                         // VorteX: add player model weapon select frame here
716                         // setcustomframe(PlayerWeaponRaise);
717                         weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
718                         //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))));
719                         weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, '0 0 0');
720                 }
721                 else if (self.weaponentity.state == WS_DROP)
722                 {
723                         // in dropping phase we can switch at any time
724                         self.switchingweapon = self.switchweapon;
725                 }
726                 else if (self.weaponentity.state == WS_READY)
727                 {
728                         // start switching!
729                         self.switchingweapon = self.switchweapon;
730
731                         entity oldwep = get_weaponinfo(self.weapon);
732                         
733 #ifndef INDEPENDENT_ATTACK_FINISHED
734                         if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
735                         {
736 #endif
737                         sound (self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
738                         self.weaponentity.state = WS_DROP;
739                         // set up weapon switch think in the future, and start drop anim
740                         weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
741                         //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))));
742                         weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, PLAYER_WEAPONSELECTION_RANGE);
743 #ifndef INDEPENDENT_ATTACK_FINISHED
744                         }
745 #endif
746                 }
747         }
748
749         // LordHavoc: network timing test code
750         //if (self.button0)
751         //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
752
753         float w;
754         w = self.weapon;
755
756         // call the think code which may fire the weapon
757         // and do so multiple times to resolve framerate dependency issues if the
758         // server framerate is very low and the weapon fire rate very high
759         float c;
760         c = 0;
761         while (c < W_TICSPERFRAME)
762         {
763                 c = c + 1;
764                 if(w && !WEPSET_CONTAINS_EW(self, w))
765                 {
766                         if(self.weapon == self.switchweapon)
767                                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
768                         w = 0;
769                 }
770
771                 v_forward = fo;
772                 v_right = ri;
773                 v_up = up;
774
775                 if(w)
776                         WEP_ACTION(self.weapon, WR_THINK);
777                 else
778                         WEP_ACTION(self.weapon, WR_GONETHINK);
779
780                 if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
781                 {
782                         if(self.weapon_think)
783                         {
784                                 v_forward = fo;
785                                 v_right = ri;
786                                 v_up = up;
787                                 self.weapon_think();
788                         }
789                         else
790                                 bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
791                 }
792         }
793
794 #if 0
795         if (self.items & IT_CELLS)
796                 self.currentammo = self.ammo_cells;
797         else if (self.items & IT_ROCKETS)
798                 self.currentammo = self.ammo_rockets;
799         else if (self.items & IT_NAILS)
800                 self.currentammo = self.ammo_nails;
801         else if (self.items & IT_SHELLS)
802                 self.currentammo = self.ammo_shells;
803         else
804                 self.currentammo = 1;
805 #endif
806 }
807
808 void weapon_boblayer1(float spd, vector org)
809 {
810         // VorteX: haste can be added here
811 }
812 void W_AttachToShotorg(entity flash, vector offset)
813 {
814         entity xflash;
815         flash.owner = self;
816         flash.angles_z = random() * 360;
817
818         if(gettagindex(self.weaponentity, "shot"))
819                 setattachment(flash, self.weaponentity, "shot");
820         else
821                 setattachment(flash, self.weaponentity, "tag_shot");
822         setorigin(flash, offset);
823
824         xflash = spawn();
825         copyentity(flash, xflash);
826
827         flash.viewmodelforclient = self;
828
829         if(self.weaponentity.oldorigin_x > 0)
830         {
831                 setattachment(xflash, self.exteriorweaponentity, "");
832                 setorigin(xflash, self.weaponentity.oldorigin + offset);
833         }
834         else
835         {
836                 if(gettagindex(self.exteriorweaponentity, "shot"))
837                         setattachment(xflash, self.exteriorweaponentity, "shot");
838                 else
839                         setattachment(xflash, self.exteriorweaponentity, "tag_shot");
840                 setorigin(xflash, offset);
841         }
842 }
843
844 void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload) // WEAPONTODO: why does this have ammo_type?
845 {
846         if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !ammo_reload)
847                 return;
848
849         // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
850         if(ammo_reload)
851         {
852                 self.clip_load -= ammo_use;
853                 self.(weapon_load[self.weapon]) = self.clip_load;
854         }
855         else
856                 self.(self.current_ammo) -= ammo_use;
857 }
858
859 // weapon reloading code
860
861 .float reload_ammo_amount, reload_ammo_min, reload_time;
862 .float reload_complain;
863 .string reload_sound;
864
865 void W_ReloadedAndReady()
866 {
867         // finish the reloading process, and do the ammo transfer
868
869         self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
870
871         // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
872         if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO)
873                 self.clip_load = self.reload_ammo_amount;
874         else
875         {
876                 while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have
877                 {
878                         self.clip_load += 1;
879                         self.(self.current_ammo) -= 1;
880                 }
881         }
882         self.(weapon_load[self.weapon]) = self.clip_load;
883
884         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
885         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
886         // so your weapon is disabled for a few seconds without reason
887
888         //ATTACK_FINISHED(self) -= self.reload_time - 1;
889
890         w_ready();
891 }
892
893 void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)
894 {
895         // set global values to work with
896
897         self.reload_ammo_min = sent_ammo_min;
898         self.reload_ammo_amount = sent_ammo_amount;
899         self.reload_time = sent_time;
900         self.reload_sound = sent_sound;
901
902         // check if we meet the necessary conditions to reload
903
904         entity e;
905         e = get_weaponinfo(self.weapon);
906
907         // don't reload weapons that don't have the RELOADABLE flag
908         if not(e.spawnflags & WEP_FLAG_RELOADABLE)
909         {
910                 dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
911                 return;
912         }
913
914         // return if reloading is disabled for this weapon
915         if(!self.reload_ammo_amount)
916                 return;
917
918         // our weapon is fully loaded, no need to reload
919         if (self.clip_load >= self.reload_ammo_amount)
920                 return;
921
922         // no ammo, so nothing to load
923         if(!self.(self.current_ammo) && self.reload_ammo_min)
924         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
925         {
926                 if(IS_REAL_CLIENT(self) && self.reload_complain < time)
927                 {
928                         play2(self, "weapons/unavailable.wav");
929                         sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));
930                         self.reload_complain = time + 1;
931                 }
932                 // switch away if the amount of ammo is not enough to keep using this weapon
933                 if not(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))
934                 {
935                         self.clip_load = -1; // reload later
936                         W_SwitchToOtherWeapon(self);
937                 }
938                 return;
939         }
940
941         if (self.weaponentity)
942         {
943                 if (self.weaponentity.wframe == WFRAME_RELOAD)
944                         return;
945
946                 // allow switching away while reloading, but this will cause a new reload!
947                 self.weaponentity.state = WS_READY;
948         }
949
950         // now begin the reloading process
951
952         sound (self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTN_NORM);
953
954         // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
955         // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
956         // so your weapon is disabled for a few seconds without reason
957
958         //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
959
960         weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
961
962         if(self.clip_load < 0)
963                 self.clip_load = 0;
964         self.old_clip_load = self.clip_load;
965         self.clip_load = self.(weapon_load[self.weapon]) = -1;
966 }