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