1 #include "game.h"
2 namespace weapons
3 {
4     VAR(IDF_PERSIST, autoreloading, 0, 2, 4); // 0 = never, 1 = when empty, 2 = weapons that don't add a full clip, 3 = always (+1 zooming weaps too)
5     VAR(IDF_PERSIST, autodelayreload, 0, 0, VAR_MAX);
6 
7     VAR(IDF_PERSIST, skipspawnweapon, 0, 0, 6); // skip spawnweapon; 0 = never, 1 = if numweaps > 1 (+1), 3 = if carry > 0 (+2), 6 = always
8     VAR(IDF_PERSIST, skipclaw, 0, 0, 10); // skip claw; 0 = never, 1 = if numweaps > 1 (+2), 4 = if carry > 0 (+2), 7 = if carry > 0 and is offset (+2), 10 = always
9     VAR(IDF_PERSIST, skippistol, 0, 0, 10); // skip pistol; 0 = never, 1 = if numweaps > 1 (+2), 4 = if carry > 0 (+2), 7 = if carry > 0 and is offset (+2), 10 = always
10     VAR(IDF_PERSIST, skipgrenade, 0, 0, 10); // skip grenade; 0 = never, 1 = if numweaps > 1 (+2), 4 = if carry > 0 (+2), 7 = if carry > 0 and is offset (+2), 10 = always
11     VAR(IDF_PERSIST, skipmine, 0, 0, 10); // skip mine; 0 = never, 1 = if numweaps > 1 (+2), 4 = if carry > 0 (+2), 7 = if carry > 0 and is offset (+2), 10 = always
12     VAR(IDF_PERSIST, skiprocket, 0, 0, 10); // skip mine; 0 = never, 1 = if numweaps > 1 (+2), 4 = if carry > 0 (+2), 7 = if carry > 0 and is offset (+2), 10 = always
13 
14     VAR(IDF_PERSIST, skippickup, 0, 0, 1);
15 
16     int lastweapselect = 0;
17     VAR(IDF_PERSIST, weapselectdelay, 0, 200, VAR_MAX);
18 
19     vector<int> weaplist;
buildweaplist(const char * str)20     void buildweaplist(const char *str)
21     {
22         vector<char *> list;
23         explodelist(str, list);
24         weaplist.shrink(0);
25         loopv(list)
26         {
27             int weap = -1;
28             if(isnumeric(list[i][0])) weap = atoi(list[i]);
29             else loopj(W_ALL) if(!strcasecmp(weaptype[j].name, list[i]))
30             {
31                 weap = j;
32                 break;
33             }
34             if(isweap(weap) && weaplist.find(weap) < 0)
35                 weaplist.add(weap);
36         }
37         list.deletearrays();
38         loopi(W_ALL) if(weaplist.find(i) < 0) weaplist.add(i); // make sure all weapons have a slot
39         changedkeys = lastmillis;
40     }
41     SVARF(IDF_PERSIST, weapselectlist, "", buildweaplist(weapselectlist));
42     VARF(IDF_PERSIST, weapselectslot, 0, 1, 2, buildweaplist(weapselectlist)); // 0 = by id, 1 = by slot, 2 = by list
43 
slot(gameent * d,int n,bool back)44     int slot(gameent *d, int n, bool back)
45     {
46         if(d && weapselectslot)
47         {
48             if(weapselectslot == 2 && weaplist.empty()) buildweaplist(weapselectlist);
49             int p = m_weapon(d->actortype, game::gamemode, game::mutators), w = 0;
50             loopi(W_ALL)
51             {
52                 int weap = weapselectslot == 2 ? weaplist[i] : i;
53                 if(d->holdweap(weap, p, lastmillis))
54                 {
55                     if(n == (back ? w : weap)) return back ? weap : w;
56                     w++;
57                 }
58             }
59             return -1;
60         }
61         return n;
62     }
63 
64     ICOMMAND(0, weapslot, "i", (int *o), intret(slot(game::player1, *o >= 0 ? *o : game::player1->weapselect))); // -1 = weapselect slot
65     ICOMMAND(0, weapselect, "", (), intret(game::player1->weapselect));
66     ICOMMAND(0, ammo, "i", (int *n), intret(isweap(*n) ? game::player1->ammo[*n] : -1));
67     ICOMMAND(0, reloadweap, "i", (int *n), intret(isweap(*n) && w_reload(*n, m_weapon(game::player1->actortype, game::gamemode, game::mutators)) ? 1 : 0));
68     ICOMMAND(0, hasweap, "ii", (int *n, int *o), intret(isweap(*n) && game::player1->hasweap(*n, *o) ? 1 : 0));
69     ICOMMAND(0, getweap, "ii", (int *n, int *o), {
70         if(isweap(*n)) switch(*o)
71         {
72             case -1: result(weaptype[*n].name); break;
73             case 0: result(W(*n, name)); break;
74             case 1: result(hud::itemtex(WEAPON, *n)); break;
75             default: break;
76         }
77     });
78 
weapselect(gameent * d,int weap,int filter,bool local)79     bool weapselect(gameent *d, int weap, int filter, bool local)
80     {
81         if(!gs_playing(game::gamestate)) return false;
82         bool newoff = false;
83         int oldweap = d->weapselect;
84         if(local)
85         {
86             int interrupts = filter;
87             interrupts &= ~(1<<W_S_RELOAD);
88             if(!d->canswitch(weap, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, interrupts))
89             {
90                 if(!d->canswitch(weap, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, filter)) return false;
91                 else if(!isweap(oldweap) || d->weapload[oldweap] <= 0) return false;
92                 else newoff = true;
93             }
94         }
95         if(d->weapswitch(weap, lastmillis, weaponswitchdelay))
96         {
97             if(local)
98             {
99                 if(newoff)
100                 {
101                     int offset = d->weapload[oldweap];
102                     d->ammo[oldweap] = max(d->ammo[oldweap]-offset, 0);
103                     d->weapload[oldweap] = -d->weapload[oldweap];
104                 }
105                 client::addmsg(N_WSELECT, "ri3", d->clientnum, lastmillis-game::maptime, weap);
106             }
107             playsound(WSND(weap, S_W_SWITCH), d->o, d, 0, -1, -1, -1, &d->wschan);
108             return true;
109         }
110         return false;
111     }
112 
weapreload(gameent * d,int weap,int load,int ammo,bool local)113     bool weapreload(gameent *d, int weap, int load, int ammo, bool local)
114     {
115         if(!gs_playing(game::gamestate) || (!local && (d == game::player1 || d->ai))) return false; // this can't be fixed until 1.5
116         if(local)
117         {
118             if(!d->canreload(weap, m_weapon(d->actortype, game::gamemode, game::mutators), false, lastmillis))
119             {
120                 if(d->weapstate[weap] == W_S_POWER) d->setweapstate(weap, W_S_WAIT, 100, lastmillis);
121                 return false;
122             }
123             client::addmsg(N_RELOAD, "ri3", d->clientnum, lastmillis-game::maptime, weap);
124             int oldammo = d->ammo[weap];
125             ammo = min(max(d->ammo[weap], 0) + W(weap, ammoadd), W(weap, ammomax));
126             load = ammo-oldammo;
127         }
128         d->weapload[weap] = load;
129         d->ammo[weap] = min(ammo, W(weap, ammomax));
130         playsound(WSND(weap, S_W_RELOAD), d->o, d, 0, -1, -1, -1, &d->wschan);
131         d->setweapstate(weap, W_S_RELOAD, W(weap, delayreload), lastmillis);
132         return true;
133     }
134 
weaponswitch(gameent * d,int a=-1,int b=-1)135     void weaponswitch(gameent *d, int a = -1, int b = -1)
136     {
137         if(!gs_playing(game::gamestate) || a < -1 || b < -1 || a >= W_ALL || b >= W_ALL) return;
138         if(weapselectdelay && lastweapselect && totalmillis-lastweapselect < weapselectdelay) return;
139         if(d->weapwaited(d->weapselect, lastmillis, (1<<W_S_SWITCH)|(1<<W_S_RELOAD)))
140         {
141             int s = slot(d, d->weapselect);
142             loopi(W_ALL) // only loop the amount of times we have weaps for
143             {
144                 if(a >= 0) s = a;
145                 else s += b;
146                 while(s > W_ALL-1) s -= W_ALL;
147                 while(s < 0) s += W_ALL;
148 
149                 int n = slot(d, s, true);
150                 if(a < 0)
151                 { // weapon skipping when scrolling
152                     int p = m_weapon(d->actortype, game::gamemode, game::mutators);
153                     #define skipweap(q,w) \
154                     { \
155                         if(q && n == w) switch(q) \
156                         { \
157                             case 10: continue; break; \
158                             case 7: case 8: case 9: if(d->carry(p, 5, w) > (q-7)) continue; break; \
159                             case 4: case 5: case 6: if(d->carry(p, 1, w) > (q-3)) continue; break; \
160                             case 1: case 2: case 3: if(d->carry(p, 0, w) > q) continue; break; \
161                             case 0: default: break; \
162                         } \
163                     }
164                     skipweap(skipspawnweapon, p);
165                     skipweap(skipclaw, W_CLAW);
166                     skipweap(skippistol, W_PISTOL);
167                     if(!m_kaboom(game::gamemode, game::mutators))
168                     {
169                         skipweap(skipgrenade, W_GRENADE);
170                         skipweap(skipmine, W_MINE);
171                         skipweap(skiprocket, W_ROCKET);
172                     }
173                 }
174 
175                 if(weapselect(d, n, (1<<W_S_SWITCH)|(1<<W_S_RELOAD)))
176                 {
177                     lastweapselect = totalmillis ? totalmillis : 1;
178                     return;
179                 }
180                 else if(a >= 0) break;
181             }
182         }
183         game::errorsnd(d);
184     }
185     ICOMMAND(0, weapon, "ss", (char *a, char *b), weaponswitch(game::player1, *a ? parseint(a) : -1, *b ? parseint(b) : -1));
186 
canuse(int weap)187     bool canuse(int weap)
188     {
189         if(!skippickup || m_kaboom(game::gamemode, game::mutators)) return true;
190         #define canuseweap(q,w) if(q && weap == w) return false;
191         canuseweap(skipgrenade, W_GRENADE);
192         canuseweap(skipmine, W_MINE);
193         canuseweap(skiprocket, W_ROCKET);
194         return true;
195     }
196 
weapdrop(gameent * d,int w)197     void weapdrop(gameent *d, int w)
198     {
199         if(!gs_playing(game::gamestate)) return;
200         int weap = isweap(w) ? w : d->weapselect, sweap = m_weapon(d->actortype, game::gamemode, game::mutators);
201         d->action[AC_DROP] = false;
202         if(!d->candrop(weap, sweap, lastmillis, m_loadout(game::gamemode, game::mutators), (1<<W_S_SWITCH)))
203         {
204             if(!d->candrop(weap, sweap, lastmillis, m_loadout(game::gamemode, game::mutators), (1<<W_S_SWITCH)|(1<<W_S_RELOAD)) || !isweap(d->weapselect) || d->weapload[d->weapselect] <= 0)
205             {
206                 game::errorsnd(d);
207                 return;
208             }
209             else
210             {
211                 int offset = d->weapload[d->weapselect];
212                 d->ammo[d->weapselect] = max(d->ammo[d->weapselect]-offset, 0);
213                 d->weapload[d->weapselect] = -d->weapload[d->weapselect];
214             }
215         }
216         client::addmsg(N_DROP, "ri3", d->clientnum, lastmillis-game::maptime, weap);
217         d->setweapstate(weap, W_S_WAIT, weaponswitchdelay, lastmillis);
218     }
219 
autoreload(gameent * d,int flags=0)220     bool autoreload(gameent *d, int flags = 0)
221     {
222         if(gs_playing(game::gamestate) && d == game::player1 && W2(d->weapselect, ammosub, WS(flags)) && d->canreload(d->weapselect, m_weapon(d->actortype, game::gamemode, game::mutators), false, lastmillis))
223         {
224             bool noammo = d->ammo[d->weapselect] < W2(d->weapselect, ammosub, WS(flags)),
225                  noattack = !d->action[AC_PRIMARY] && !d->action[AC_SECONDARY];
226             if((noammo || noattack) && !d->action[AC_USE] && d->weapstate[d->weapselect] == W_S_IDLE && (noammo || lastmillis-d->weaptime[d->weapselect] >= autodelayreload))
227                 return autoreloading >= (noammo ? 1 : (W(d->weapselect, ammoadd) < W(d->weapselect, ammomax) ? 2 : (W2(d->weapselect, cooked, true)&W_C_ZOOM ? 4 : 3)));
228         }
229         return false;
230     }
231 
checkweapons(gameent * d)232     void checkweapons(gameent *d)
233     {
234         int sweap = m_weapon(d->actortype, game::gamemode, game::mutators);
235         if(!d->hasweap(d->weapselect, sweap)) weapselect(d, d->bestweap(sweap, true), 1<<W_S_RELOAD, true);
236         else if(d->action[AC_RELOAD] || autoreload(d)) weapreload(d, d->weapselect);
237         else if(d->action[AC_DROP]) weapdrop(d, d->weapselect);
238     }
239 
offsetray(vec & from,vec & to,float spread,float z,vec & dest)240     void offsetray(vec &from, vec &to, float spread, float z, vec &dest)
241     {
242         float f = to.dist(from)*spread/10000.f;
243         for(;;)
244         {
245             #define RNDD rnd(101)-50
246             vec v(RNDD, RNDD, RNDD);
247             if(v.magnitude() > 50) continue;
248             v.mul(f);
249             v.z = z > 0 ? v.z/z : 0;
250             dest = to;
251             dest.add(v);
252             if(dest != from)
253             {
254                 vec dir = vec(dest).sub(from).normalize();
255                 raycubepos(from, dir, dest, 0, RAY_CLIPMAT|RAY_ALPHAPOLY);
256             }
257             return;
258         }
259     }
260 
accmod(gameent * d,bool zooming)261     float accmod(gameent *d, bool zooming)
262     {
263         float r = 0;
264         bool running = d->running(moveslow), moving = d->move || d->strafe;
265         if(running || moving) r += running ? spreadrunning : spreadmoving;
266         else if(zooming) r += spreadzoom;
267         else if(d->crouching()) r += spreadcrouch;
268         else r += spreadstill;
269         if(spreadinair > 0 && d->airmillis && !d->onladder) r += spreadinair;
270         return r;
271     }
272 
doshot(gameent * d,vec & targ,int weap,bool pressed,bool secondary,int force)273     bool doshot(gameent *d, vec &targ, int weap, bool pressed, bool secondary, int force)
274     {
275         bool hadcook = W2(weap, cooked, true)&W_C_KEEP && (d->prevstate[weap] == W_S_ZOOM || d->prevstate[weap] == W_S_POWER),
276              zooming = W2(weap, cooked, true)&W_C_ZOOM && (d->weapstate[weap] == W_S_ZOOM || (pressed && secondary) || (hadcook && d->prevstate[weap] == W_S_ZOOM)), wassecond = secondary;
277         if(hadcook || zooming)
278         {
279             if(!pressed)
280             {
281                 client::addmsg(N_SPHY, "ri5", d->clientnum, SPHY_COOK, W_S_IDLE, 0, 0);
282                 d->setweapstate(weap, W_S_IDLE, 0, lastmillis, 0, true);
283                 return false;
284             }
285             else secondary = zooming;
286         }
287         int offset = 0, sweap = m_weapon(d->actortype, game::gamemode, game::mutators);
288         if(!d->canshoot(weap, secondary ? HIT_ALT : 0, sweap, lastmillis))
289         {
290             if(!d->canshoot(weap, secondary ? HIT_ALT : 0, sweap, lastmillis, (1<<W_S_RELOAD)))
291             {
292                 // if the problem is not enough ammo, do the reload..
293                 if(autoreload(d, secondary ? HIT_ALT : 0)) weapreload(d, weap);
294                 return false;
295             }
296             else if(d->weapload[weap] <= 0 || weap != d->weapselect) return false;
297             else offset = d->weapload[weap];
298         }
299         float scale = 1;
300         int sub = W2(weap, ammosub, secondary), cooked = force;
301         if(W2(weap, cooktime, secondary) || zooming)
302         {
303             float maxscale = 1;
304             if(sub > 1 && d->ammo[weap] < sub) maxscale = d->ammo[weap]/float(sub);
305             int len = int(W2(weap, cooktime, secondary)*maxscale), type = zooming ? W_S_ZOOM : W_S_POWER;
306             if(!cooked)
307             {
308                 if(d->weapstate[weap] != type)
309                 {
310                     if(pressed)
311                     {
312                         if(offset > 0)
313                         {
314                             d->ammo[weap] = max(d->ammo[weap]-offset, 0);
315                             d->weapload[weap] = -offset;
316                         }
317                         int offtime = hadcook && d->prevstate[weap] == type ? lastmillis-d->prevtime[weap] : 0;
318                         client::addmsg(N_SPHY, "ri5", d->clientnum, SPHY_COOK, type, len, offtime);
319                         d->setweapstate(weap, type, len, lastmillis, offtime);
320                     }
321                     else return false;
322                 }
323                 cooked = len ? clamp(lastmillis-d->weaptime[weap], 1, len) : 1;
324                 if(zooming)
325                 {
326                     if(pressed && wassecond) return false;
327                 }
328                 else if(pressed && cooked < len) return false;
329             }
330             scale = len ? cooked/float(W2(weap, cooktime, secondary)) : 1;
331             if(sub > 1 && scale < 1) sub = int(ceilf(sub*scale));
332         }
333         else if(!pressed) return false;
334 
335         vec to, from;
336         vector<shotmsg> shots;
337         #define addshot(p) \
338         { \
339             shotmsg &s = shots.add(); \
340             s.id = d->getprojid(); \
341             s.pos = ivec(int(p.x*DMF), int(p.y*DMF), int(p.z*DMF)); \
342         }
343         int rays = W2(weap, rays, secondary);
344         if(rays > 1 && W2(weap, cooked, secondary)&W_C_RAYS && W2(weap, cooktime, secondary) && scale < 1)
345             rays = max(1, int(ceilf(rays*scale)));
346         if(weap == W_MELEE || WF(false, weap, collide, secondary)&COLLIDE_HITSCAN)
347         {
348             if(weap == W_MELEE)
349             {
350                 from = d->center();
351                 to = vec(from).add(vec(d->yaw*RAD, d->pitch*RAD).mul(d->radius*2.f));
352             }
353             else
354             {
355                 from = d->originpos(weap);
356                 to = d->muzzlepos(weap);
357             }
358             loopi(rays) addshot(to);
359         }
360         else
361         {
362             from = d->muzzlepos(weap);
363             to = targ;
364             float m = accmod(d, W2(d->weapselect, cooked, true)&W_C_ZOOM && secondary);
365             float spread = WSP(weap, secondary, game::gamemode, game::mutators, m);
366             loopi(rays)
367             {
368                 vec dest;
369                 if(spread > 0) offsetray(from, to, spread, W2(weap, spreadz, secondary), dest);
370                 else dest = to;
371                 if(weaptype[weap].thrown[secondary ? 1 : 0] > 0)
372                     dest.z += from.dist(dest)*weaptype[weap].thrown[secondary ? 1 : 0];
373                 addshot(dest);
374             }
375         }
376         projs::shootv(weap, secondary ? HIT_ALT : 0, sub, offset, scale, from, shots, d, true);
377         client::addmsg(N_SHOOT, "ri8iv", d->clientnum, lastmillis-game::maptime, weap, secondary ? HIT_ALT : 0, cooked, int(from.x*DMF), int(from.y*DMF), int(from.z*DMF), shots.length(), shots.length()*sizeof(shotmsg)/sizeof(int), shots.getbuf());
378 
379         return true;
380     }
381 
shoot(gameent * d,vec & targ,int force)382     void shoot(gameent *d, vec &targ, int force)
383     {
384         if(!game::allowmove(d)) return;
385         bool secondary = physics::secondaryweap(d);
386         if(doshot(d, targ, d->weapselect, d->action[secondary ? AC_SECONDARY : AC_PRIMARY], secondary, force))
387             if(!W2(d->weapselect, fullauto, secondary)) d->action[secondary ? AC_SECONDARY : AC_PRIMARY] = false;
388     }
389 
preload()390     void preload()
391     {
392         loopi(W_ALL)
393         {
394             if(*weaptype[i].item) preloadmodel(weaptype[i].item);
395             if(*weaptype[i].vwep) preloadmodel(weaptype[i].vwep);
396             if(*weaptype[i].hwep) preloadmodel(weaptype[i].hwep);
397         }
398     }
399 }
400