make weapons named, not numbered, everywhere
[divverent/nexuiz.git] / data / qcsrc / server / cl_weapons.qc
1 void W_Reload()
2 {
3         if(self.switchweapon == self.weapon)
4         if(self.weaponentity.state == WS_READY)
5                 weapon_action(self.weapon, WR_RELOAD);
6 }
7
8 // switch between weapons
9 void W_SwitchWeapon(float imp)
10 {
11         if (self.switchweapon != imp)
12         {
13                 if (client_hasweapon(self, imp, TRUE, TRUE))
14                         W_SwitchWeapon_Force(self, imp);
15         }
16         else
17         {
18                 W_Reload();
19         }
20 };
21
22 .float weaponcomplainindex;
23 float W_GetCycleWeapon(entity pl, string weaponorder, float dir, float imp, float complain)
24 {
25         float n, i, weaponwant, first_valid, prev_valid, switchtonext, switchtolast, c;
26         n = tokenize_console(weaponorder);
27         switchtonext = switchtolast = 0;
28         first_valid = prev_valid = 0;
29
30         if(dir == 0)
31                 switchtonext = 1;
32
33         c = 0;
34
35         for(i = 0; i < n; ++i)
36         {
37                 weaponwant = stof(argv(i));
38                 if(imp >= 0)
39                         if((get_weaponinfo(weaponwant)).impulse != imp)
40                                 continue;
41
42                 ++c;
43
44                 if(client_hasweapon(pl, weaponwant, TRUE, FALSE))
45                 {
46                         if(switchtonext)
47                                 return weaponwant;
48                         if(!first_valid)
49                                 first_valid = weaponwant;
50                         if(weaponwant == pl.switchweapon)
51                         {
52                                 if(dir >= 0)
53                                         switchtonext = 1;
54                                 else if(prev_valid)
55                                         return prev_valid;
56                                 else
57                                         switchtolast = 1;
58                         }
59                         prev_valid = weaponwant;
60                 }
61         }
62         if(first_valid)
63         {
64                 if(switchtolast)
65                         return prev_valid;
66                 else
67                         return first_valid;
68         }
69         // complain (but only for one weapon on the button that has been pressed)
70         if(complain)
71         {
72                 self.weaponcomplainindex += 1;
73                 c = mod(self.weaponcomplainindex, c) + 1;
74                 for(i = 0; i < n; ++i)
75                 {
76                         weaponwant = stof(argv(i));
77                         if(imp >= 0)
78                                 if((get_weaponinfo(weaponwant)).impulse != imp)
79                                         continue;
80
81                         --c;
82                         if(c == 0)
83                         {
84                                 client_hasweapon(pl, weaponwant, TRUE, TRUE);
85                                 break;
86                         }
87                 }
88         }
89         return 0;
90 }
91
92 void W_CycleWeapon(string weaponorder, float dir)
93 {
94         float w;
95         w = W_GetCycleWeapon(self, weaponorder, dir, -1, 1);
96         if(w > 0)
97                 W_SwitchWeapon(w);
98 }
99
100 void W_NextWeaponOnImpulse(float imp)
101 {
102         float w;
103         w = W_GetCycleWeapon(self, self.cvar_cl_weaponpriority, +1, imp, 1);
104         if(w > 0)
105                 W_SwitchWeapon(w);
106 }
107
108 // next weapon
109 void W_NextWeapon(float list)
110 {
111         if(list == 0)
112                 W_CycleWeapon(weaponpriority_hudselector_0, -1);
113         else if(list == 1)
114                 W_CycleWeapon(weaponpriority_hudselector_1, -1);
115         else if(list == 2)
116                 W_CycleWeapon(self.cvar_cl_weaponpriority, -1);
117 }
118
119 // prev weapon
120 void W_PreviousWeapon(float list)
121 {
122         if(list == 0)
123                 W_CycleWeapon(weaponpriority_hudselector_0, +1);
124         else if(list == 1)
125                 W_CycleWeapon(weaponpriority_hudselector_1, +1);
126         else if(list == 2)
127                 W_CycleWeapon(self.cvar_cl_weaponpriority, +1);
128 }
129
130 string W_FixWeaponOrder_AllowIncomplete(string order)
131 {
132         return W_FixWeaponOrder(order, 0);
133 }
134
135 string W_FixWeaponOrder_ForceComplete(string order)
136 {
137         if(order == "")
138                 order = W_NumberWeaponOrder(cvar_string("cl_weaponpriority"));
139         return W_FixWeaponOrder(order, 1);
140 }
141
142 float w_getbestweapon(entity e)
143 {
144         return W_GetCycleWeapon(e, e.cvar_cl_weaponpriority, 0, -1, 0);
145 };
146
147 // generic weapons table
148 // TODO should they be macros instead?
149 float weapon_action(float wpn, float wrequest)
150 {
151         return (get_weaponinfo(wpn)).weapon_func(wrequest);
152 };
153
154 string W_Name(float weaponid)
155 {
156         return (get_weaponinfo(weaponid)).message;
157 }
158
159 float W_WeaponBit(float wpn)
160 {
161         return (get_weaponinfo(wpn)).weapons;
162 }
163
164 float W_AmmoItemCode(float wpn)
165 {
166         return (get_weaponinfo(wpn)).items;
167 }
168
169 void thrown_wep_think()
170 {
171         self.solid = SOLID_TRIGGER;
172         self.owner = world;
173         SUB_SetFade(self, time + 20, 1);
174 }
175
176 // returns amount of ammo used as string, or -1 for failure, or 0 for no ammo count
177 string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo)
178 {
179         entity oldself, wep;
180         float wa, thisammo, i, j;
181         string s;
182         var .float ammofield;
183
184         wep = spawn();
185
186         setorigin(wep, org);
187         wep.classname = "droppedweapon";
188         wep.velocity = velo;
189         wep.owner = wep.enemy = own;
190         wep.flags |= FL_TOSSED;
191         wep.colormap = own.colormap;
192
193         wa = W_AmmoItemCode(wpn);
194         if(wa == IT_SUPERWEAPON || wa == 0)
195         {
196                 oldself = self;
197                 self = wep;
198                 weapon_defaultspawnfunc(wpn);
199                 self = oldself;
200                 if(startitem_failed)
201                         return string_null;
202                 wep.think = thrown_wep_think;
203                 wep.nextthink = time + 0.5;
204                 return "";
205         }
206         else
207         {
208                 s = "";
209                 oldself = self;
210                 self = wep;
211                 weapon_defaultspawnfunc(wpn);
212                 self = oldself;
213                 if(startitem_failed)
214                         return string_null;
215                 if(doreduce)
216                 {
217                         for(i = 0, j = 1; i < 24; ++i, j *= 2)
218                         {
219                                 if(wa & j)
220                                 {
221                                         ammofield = Item_CounterField(j);
222                                         thisammo = min(own.ammofield, wep.ammofield);
223                                         wep.ammofield = thisammo;
224                                         own.ammofield -= thisammo;
225                                         s = strcat(s, " and ", ftos(thisammo), " ", Item_CounterFieldName(j));
226                                 }
227                         }
228                         s = substring(s, 5, -1);
229                 }
230                 wep.think = thrown_wep_think;
231                 wep.nextthink = time + 0.5;
232                 return s;
233         }
234 }
235
236 float W_IsWeaponThrowable(float w)
237 {
238         float wb, wa;
239         wb = W_WeaponBit(w);
240         if(!wb)
241                 return 0;
242         wa = W_AmmoItemCode(w);
243         if(start_weapons & wb)
244         {
245                 if(wa == IT_SUPERWEAPON && start_items & IT_UNLIMITED_SUPERWEAPONS)
246                         return 0;
247                 if(wa != IT_SUPERWEAPON && start_items & IT_UNLIMITED_WEAPON_AMMO)
248                         return 0;
249                 // start weapons that take no ammo can't be dropped (this prevents dropping the laser, as long as it continues to use no ammo)
250                 if(wa == 0)
251                         return 0;
252         }
253
254         return 1;
255 }
256
257 // toss current weapon
258 void W_ThrowWeapon(vector velo, vector delta, float doreduce)
259 {
260         local float w, wb;
261         string a;
262
263         w = self.weapon;
264         if (w == 0)
265                 return; // just in case
266         if (g_weaponarena)
267                 return;
268         if (g_lms)
269                 return;
270         if (g_nixnex)
271                 return;
272         if (g_nexball && w == WEP_GRENADE_LAUNCHER)
273                 return;
274         if (!cvar("g_pickup_items"))
275                 return;
276         if (g_ca)
277                 return;
278         if(!cvar("g_weapon_throwable"))
279                 return;
280         if(cvar("g_weapon_stay") == 1)
281                 return;
282         if(!W_IsWeaponThrowable(w))
283                 return;
284
285         wb = W_WeaponBit(w);
286         if(self.weapons & wb != wb)
287                 return;
288
289         self.weapons &~= wb;
290         W_SwitchWeapon_Force(self, w_getbestweapon(self));
291         a = W_ThrowNewWeapon(self, w, doreduce, self.origin + delta, velo);
292         if not(a)
293                 return;
294         if(self.health >= 1)
295         {
296                 if(a == "")
297                         sprint(self, strcat("You dropped the ^2", W_Name(w), "\n"));
298                 else
299                         sprint(self, strcat("You dropped the ^2", W_Name(w), " with ", a, "\n"));
300         }
301 };
302
303 // Bringed back weapon frame
304 void W_WeaponFrame()
305 {
306         vector fo, ri, up;
307
308         if (frametime)
309                 self.weapon_frametime = frametime;
310
311         if(((arena_roundbased || g_ca) && time < warmup) || ((time < game_starttime) && !cvar("sv_ready_restart_after_countdown")))
312                 return;
313
314         if (!self.weaponentity || self.health < 1)
315                 return; // Dead player can't use weapons and injure impulse commands
316
317         if(!self.switchweapon)
318         {
319                 self.weapon = 0;
320                 self.weaponentity.state = WS_CLEAR;
321                 self.weaponname = "";
322                 self.items &~= IT_AMMO;
323                 return;
324         }
325
326         makevectors(self.v_angle);
327         fo = v_forward; // save them in case the weapon think functions change it
328         ri = v_right;
329         up = v_up;
330
331         // Change weapon
332         if (self.weapon != self.switchweapon)
333         {
334                 if (self.weaponentity.state == WS_CLEAR)
335                 {
336                         setanim(self, self.anim_draw, FALSE, TRUE, TRUE);
337                         self.weaponentity.state = WS_RAISE;
338                         weapon_action(self.switchweapon, WR_SETUP);
339                         // VorteX: add player model weapon select frame here
340                         // setcustomframe(PlayerWeaponRaise);
341                         weapon_thinkf(WFRAME_IDLE, cvar("g_balance_weaponswitchdelay"), w_ready);
342                         weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, '0 0 0');
343                 }
344                 else if (self.weaponentity.state == WS_READY)
345                 {
346 #ifndef INDEPENDENT_ATTACK_FINISHED
347                         if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
348                         {
349 #endif
350                         // UGLY WORKAROUND: play this on CHAN_WEAPON2 so it can't cut off fire sounds
351                         sound (self, CHAN_WEAPON2, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
352                         self.weaponentity.state = WS_DROP;
353                         // set up weapon switch think in the future, and start drop anim
354                         weapon_thinkf(WFRAME_DONTCHANGE, cvar("g_balance_weaponswitchdelay"), w_clear);
355                         weapon_boblayer1(PLAYER_WEAPONSELECTION_SPEED, PLAYER_WEAPONSELECTION_RANGE);
356 #ifndef INDEPENDENT_ATTACK_FINISHED
357                         }
358 #endif
359                 }
360         }
361
362         // LordHavoc: network timing test code
363         //if (self.button0)
364         //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
365
366         float wb;
367         wb = W_WeaponBit(self.weapon);
368
369         // call the think code which may fire the weapon
370         // and do so multiple times to resolve framerate dependency issues if the
371         // server framerate is very low and the weapon fire rate very high
372         local float c;
373         c = 0;
374         while (c < 5)
375         {
376                 c = c + 1;
377                 if(wb && ((self.weapons & wb) == 0))
378                 {
379                         W_SwitchWeapon_Force(self, w_getbestweapon(self));
380                         wb = 0;
381                 }
382                 if(wb)
383                 {
384                         v_forward = fo;
385                         v_right = ri;
386                         v_up = up;
387                         weapon_action(self.weapon, WR_THINK);
388                 }
389                 if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
390                 {
391                         if(self.weapon_think)
392                         {
393                                 v_forward = fo;
394                                 v_right = ri;
395                                 v_up = up;
396                                 self.weapon_think();
397                         }
398                         else
399                                 bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
400                 }
401         }
402
403         // don't let attack_finished fall behind when not firing (must be after weapon_setup calls!)
404         //if (ATTACK_FINISHED(self) < time)
405         //      ATTACK_FINISHED(self) = time;
406
407         //if (self.weapon_nextthink < time)
408         //      self.weapon_nextthink = time;
409
410         // update currentammo incase it has changed
411 #if 0
412         if (self.items & IT_CELLS)
413                 self.currentammo = self.ammo_cells;
414         else if (self.items & IT_ROCKETS)
415                 self.currentammo = self.ammo_rockets;
416         else if (self.items & IT_NAILS)
417                 self.currentammo = self.ammo_nails;
418         else if (self.items & IT_SHELLS)
419                 self.currentammo = self.ammo_shells;
420         else
421                 self.currentammo = 1;
422 #endif
423 };
424
425 float nixnex_weapon;
426 float nixnex_weapon_ammo;
427 float nixnex_nextchange;
428 float nixnex_nextweapon;
429 float nixnex_nextweapon_ammo;
430 .float nixnex_lastchange_id;
431 .float nixnex_lastinfotime;
432 .float nixnex_nextincr;
433
434 float NixNex_CanChooseWeapon(float wpn)
435 {
436         entity e;
437         e = get_weaponinfo(wpn);
438         if(!e.weapons) // skip dummies
439                 return FALSE;
440         if(g_weaponarena)
441         {
442                 if not(g_weaponarena & e.weapons)
443                         return FALSE;
444         }
445         else
446         {
447                 if(wpn == WEP_LASER && g_nixnex_with_laser)
448                         return FALSE;
449                 if not(e.spawnflags & WEPSPAWNFLAG_NORMAL)
450                         return FALSE;
451         }
452         return TRUE;
453 }
454 void Nixnex_ChooseNextWeapon()
455 {
456         float j;
457         RandomSelection_Init();
458         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
459                 if(NixNex_CanChooseWeapon(j))
460                         RandomSelection_Add(world, j, string_null, 1, (j != nixnex_weapon));
461         nixnex_nextweapon = RandomSelection_chosen_float;
462         nixnex_nextweapon_ammo = W_AmmoItemCode(nixnex_nextweapon);
463 }
464
465 void Nixnex_GiveCurrentWeapon()
466 {
467         float dt;
468         if(g_nixnex)
469         {
470                 if(!nixnex_nextweapon)
471                         Nixnex_ChooseNextWeapon();
472
473                 dt = ceil(nixnex_nextchange - time);
474
475                 if(dt <= 0)
476                 {
477                         nixnex_weapon = nixnex_nextweapon;
478                         nixnex_weapon_ammo = nixnex_nextweapon_ammo;
479                         nixnex_nextweapon = 0;
480                         nixnex_nextchange = time + cvar("g_balance_nixnex_roundtime");
481                         //weapon_action(nixnex_weapon, WR_PRECACHE); // forget it, too slow
482                 }
483                 
484                 if(nixnex_nextchange != self.nixnex_lastchange_id) // this shall only be called once per round!
485                 {
486                         self.nixnex_lastchange_id = nixnex_nextchange;
487                         if (self.items & IT_UNLIMITED_WEAPON_AMMO)
488                         {
489                                 self.ammo_shells = (nixnex_weapon_ammo & IT_SHELLS) ?
490                                         cvar("g_pickup_shells_max") : 0;
491                                 self.ammo_nails = (nixnex_weapon_ammo & IT_NAILS) ?
492                                         cvar("g_pickup_nails_max") : 0;
493                                 self.ammo_rockets = (nixnex_weapon_ammo & IT_ROCKETS) ?
494                                         cvar("g_pickup_rockets_max") : 0;
495                                 self.ammo_cells = (nixnex_weapon_ammo & IT_CELLS) ?
496                                         cvar("g_pickup_cells_max") : 0;
497                                 self.ammo_fuel = (nixnex_weapon_ammo & IT_FUEL) ?
498                                         cvar("g_pickup_fuel_max") : 0;
499                         }
500                         else
501                         {
502                                 self.ammo_shells = (nixnex_weapon_ammo & IT_SHELLS) ?
503                                         cvar("g_balance_nixnex_ammo_shells") : 0;
504                                 self.ammo_nails = (nixnex_weapon_ammo & IT_NAILS) ?
505                                         cvar("g_balance_nixnex_ammo_nails") : 0;
506                                 self.ammo_rockets = (nixnex_weapon_ammo & IT_ROCKETS) ?
507                                         cvar("g_balance_nixnex_ammo_rockets") : 0;
508                                 self.ammo_cells = (nixnex_weapon_ammo & IT_CELLS) ?
509                                         cvar("g_balance_nixnex_ammo_cells") : 0;
510                                 self.ammo_fuel = (nixnex_weapon_ammo & IT_FUEL) ?
511                                         cvar("g_balance_nixnex_ammo_fuel") : 0;
512                         }
513                         self.nixnex_nextincr = time + cvar("g_balance_nixnex_incrtime");
514                         if(dt >= 1 && dt <= 5)
515                                 self.nixnex_lastinfotime = -42;
516                         else
517                                 centerprint(self, strcat("\n\n^2Active weapon: ^3", W_Name(nixnex_weapon)));
518                 }
519                 if(self.nixnex_lastinfotime != dt)
520                 {
521                         self.nixnex_lastinfotime = dt; // initial value 0 should count as "not seen"
522                         if(dt >= 1 && dt <= 5)
523                                 centerprint(self, strcat("^3", ftos(dt), "^2 seconds until weapon change...\n\nNext weapon: ^3", W_Name(nixnex_nextweapon), "\n"));
524                 }
525
526                 if(!(self.items & IT_UNLIMITED_WEAPON_AMMO) && time > self.nixnex_nextincr)
527                 {
528                         if (nixnex_weapon_ammo & IT_SHELLS)
529                                 self.ammo_shells = self.ammo_shells + cvar("g_balance_nixnex_ammoincr_shells");
530                         else if (nixnex_weapon_ammo & IT_NAILS)
531                                 self.ammo_nails = self.ammo_nails + cvar("g_balance_nixnex_ammoincr_nails");
532                         else if (nixnex_weapon_ammo & IT_ROCKETS)
533                                 self.ammo_rockets = self.ammo_rockets + cvar("g_balance_nixnex_ammoincr_rockets");
534                         else if (nixnex_weapon_ammo & IT_CELLS)
535                                 self.ammo_cells = self.ammo_cells + cvar("g_balance_nixnex_ammoincr_cells");
536                         if (nixnex_weapon_ammo & IT_FUEL) // hook uses cells and fuel
537                                 self.ammo_fuel = self.ammo_fuel + cvar("g_balance_nixnex_ammoincr_fuel");
538                         self.nixnex_nextincr = time + cvar("g_balance_nixnex_incrtime");
539                 }
540
541                 self.weapons = 0;
542                 if(g_nixnex_with_laser)
543                         self.weapons = self.weapons | WEPBIT_LASER;
544                 self.weapons = self.weapons | W_WeaponBit(nixnex_weapon);
545
546                 if(self.switchweapon != nixnex_weapon)
547                         if(!client_hasweapon(self, self.switchweapon, TRUE, FALSE))
548                                 if(client_hasweapon(self, nixnex_weapon, TRUE, FALSE))
549                                         W_SwitchWeapon(nixnex_weapon);
550         }
551 }