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