#include "game.h" namespace weapons { 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) VAR(IDF_PERSIST, autodelayreload, 0, 0, VAR_MAX); VAR(IDF_PERSIST, skipspawnweapon, 0, 0, 6); // skip spawnweapon; 0 = never, 1 = if numweaps > 1 (+1), 3 = if carry > 0 (+2), 6 = always 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 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 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 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 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 VAR(IDF_PERSIST, skippickup, 0, 0, 1); int lastweapselect = 0; VAR(IDF_PERSIST, weapselectdelay, 0, 200, VAR_MAX); vector weaplist; void buildweaplist(const char *str) { vector list; explodelist(str, list); weaplist.shrink(0); loopv(list) { int weap = -1; if(isnumeric(list[i][0])) weap = atoi(list[i]); else loopj(W_ALL) if(!strcasecmp(weaptype[j].name, list[i])) { weap = j; break; } if(isweap(weap) && weaplist.find(weap) < 0) weaplist.add(weap); } list.deletearrays(); loopi(W_ALL) if(weaplist.find(i) < 0) weaplist.add(i); // make sure all weapons have a slot changedkeys = lastmillis; } SVARF(IDF_PERSIST, weapselectlist, "", buildweaplist(weapselectlist)); VARF(IDF_PERSIST, weapselectslot, 0, 1, 2, buildweaplist(weapselectlist)); // 0 = by id, 1 = by slot, 2 = by list int slot(gameent *d, int n, bool back) { if(d && weapselectslot) { if(weapselectslot == 2 && weaplist.empty()) buildweaplist(weapselectlist); int p = m_weapon(d->actortype, game::gamemode, game::mutators), w = 0; loopi(W_ALL) { int weap = weapselectslot == 2 ? weaplist[i] : i; if(d->holdweap(weap, p, lastmillis)) { if(n == (back ? w : weap)) return back ? weap : w; w++; } } return -1; } return n; } ICOMMAND(0, weapslot, "i", (int *o), intret(slot(game::player1, *o >= 0 ? *o : game::player1->weapselect))); // -1 = weapselect slot ICOMMAND(0, weapselect, "", (), intret(game::player1->weapselect)); ICOMMAND(0, ammo, "i", (int *n), intret(isweap(*n) ? game::player1->ammo[*n] : -1)); ICOMMAND(0, reloadweap, "i", (int *n), intret(isweap(*n) && w_reload(*n, m_weapon(game::player1->actortype, game::gamemode, game::mutators)) ? 1 : 0)); ICOMMAND(0, hasweap, "ii", (int *n, int *o), intret(isweap(*n) && game::player1->hasweap(*n, *o) ? 1 : 0)); ICOMMAND(0, getweap, "ii", (int *n, int *o), { if(isweap(*n)) switch(*o) { case -1: result(weaptype[*n].name); break; case 0: result(W(*n, name)); break; case 1: result(hud::itemtex(WEAPON, *n)); break; default: break; } }); bool weapselect(gameent *d, int weap, int filter, bool local) { if(!gs_playing(game::gamestate)) return false; bool newoff = false; int oldweap = d->weapselect; if(local) { int interrupts = filter; interrupts &= ~(1<canswitch(weap, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, interrupts)) { if(!d->canswitch(weap, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, filter)) return false; else if(!isweap(oldweap) || d->weapload[oldweap] <= 0) return false; else newoff = true; } } if(d->weapswitch(weap, lastmillis, weaponswitchdelay)) { if(local) { if(newoff) { int offset = d->weapload[oldweap]; d->ammo[oldweap] = max(d->ammo[oldweap]-offset, 0); d->weapload[oldweap] = -d->weapload[oldweap]; } client::addmsg(N_WSELECT, "ri3", d->clientnum, lastmillis-game::maptime, weap); } playsound(WSND(weap, S_W_SWITCH), d->o, d, 0, -1, -1, -1, &d->wschan); return true; } return false; } bool weapreload(gameent *d, int weap, int load, int ammo, bool local) { if(!gs_playing(game::gamestate) || (!local && (d == game::player1 || d->ai))) return false; // this can't be fixed until 1.5 if(local) { if(!d->canreload(weap, m_weapon(d->actortype, game::gamemode, game::mutators), false, lastmillis)) { if(d->weapstate[weap] == W_S_POWER) d->setweapstate(weap, W_S_WAIT, 100, lastmillis); return false; } client::addmsg(N_RELOAD, "ri3", d->clientnum, lastmillis-game::maptime, weap); int oldammo = d->ammo[weap]; ammo = min(max(d->ammo[weap], 0) + W(weap, ammoadd), W(weap, ammomax)); load = ammo-oldammo; } d->weapload[weap] = load; d->ammo[weap] = min(ammo, W(weap, ammomax)); playsound(WSND(weap, S_W_RELOAD), d->o, d, 0, -1, -1, -1, &d->wschan); d->setweapstate(weap, W_S_RELOAD, W(weap, delayreload), lastmillis); return true; } void weaponswitch(gameent *d, int a = -1, int b = -1) { if(!gs_playing(game::gamestate) || a < -1 || b < -1 || a >= W_ALL || b >= W_ALL) return; if(weapselectdelay && lastweapselect && totalmillis-lastweapselect < weapselectdelay) return; if(d->weapwaited(d->weapselect, lastmillis, (1<weapselect); loopi(W_ALL) // only loop the amount of times we have weaps for { if(a >= 0) s = a; else s += b; while(s > W_ALL-1) s -= W_ALL; while(s < 0) s += W_ALL; int n = slot(d, s, true); if(a < 0) { // weapon skipping when scrolling int p = m_weapon(d->actortype, game::gamemode, game::mutators); #define skipweap(q,w) \ { \ if(q && n == w) switch(q) \ { \ case 10: continue; break; \ case 7: case 8: case 9: if(d->carry(p, 5, w) > (q-7)) continue; break; \ case 4: case 5: case 6: if(d->carry(p, 1, w) > (q-3)) continue; break; \ case 1: case 2: case 3: if(d->carry(p, 0, w) > q) continue; break; \ case 0: default: break; \ } \ } skipweap(skipspawnweapon, p); skipweap(skipclaw, W_CLAW); skipweap(skippistol, W_PISTOL); if(!m_kaboom(game::gamemode, game::mutators)) { skipweap(skipgrenade, W_GRENADE); skipweap(skipmine, W_MINE); skipweap(skiprocket, W_ROCKET); } } if(weapselect(d, n, (1<= 0) break; } } game::errorsnd(d); } ICOMMAND(0, weapon, "ss", (char *a, char *b), weaponswitch(game::player1, *a ? parseint(a) : -1, *b ? parseint(b) : -1)); bool canuse(int weap) { if(!skippickup || m_kaboom(game::gamemode, game::mutators)) return true; #define canuseweap(q,w) if(q && weap == w) return false; canuseweap(skipgrenade, W_GRENADE); canuseweap(skipmine, W_MINE); canuseweap(skiprocket, W_ROCKET); return true; } void weapdrop(gameent *d, int w) { if(!gs_playing(game::gamestate)) return; int weap = isweap(w) ? w : d->weapselect, sweap = m_weapon(d->actortype, game::gamemode, game::mutators); d->action[AC_DROP] = false; if(!d->candrop(weap, sweap, lastmillis, m_loadout(game::gamemode, game::mutators), (1<candrop(weap, sweap, lastmillis, m_loadout(game::gamemode, game::mutators), (1<weapselect) || d->weapload[d->weapselect] <= 0) { game::errorsnd(d); return; } else { int offset = d->weapload[d->weapselect]; d->ammo[d->weapselect] = max(d->ammo[d->weapselect]-offset, 0); d->weapload[d->weapselect] = -d->weapload[d->weapselect]; } } client::addmsg(N_DROP, "ri3", d->clientnum, lastmillis-game::maptime, weap); d->setweapstate(weap, W_S_WAIT, weaponswitchdelay, lastmillis); } bool autoreload(gameent *d, int flags = 0) { 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)) { bool noammo = d->ammo[d->weapselect] < W2(d->weapselect, ammosub, WS(flags)), noattack = !d->action[AC_PRIMARY] && !d->action[AC_SECONDARY]; if((noammo || noattack) && !d->action[AC_USE] && d->weapstate[d->weapselect] == W_S_IDLE && (noammo || lastmillis-d->weaptime[d->weapselect] >= autodelayreload)) return autoreloading >= (noammo ? 1 : (W(d->weapselect, ammoadd) < W(d->weapselect, ammomax) ? 2 : (W2(d->weapselect, cooked, true)&W_C_ZOOM ? 4 : 3))); } return false; } void checkweapons(gameent *d) { int sweap = m_weapon(d->actortype, game::gamemode, game::mutators); if(!d->hasweap(d->weapselect, sweap)) weapselect(d, d->bestweap(sweap, true), 1<action[AC_RELOAD] || autoreload(d)) weapreload(d, d->weapselect); else if(d->action[AC_DROP]) weapdrop(d, d->weapselect); } void offsetray(vec &from, vec &to, float spread, float z, vec &dest) { float f = to.dist(from)*spread/10000.f; for(;;) { #define RNDD rnd(101)-50 vec v(RNDD, RNDD, RNDD); if(v.magnitude() > 50) continue; v.mul(f); v.z = z > 0 ? v.z/z : 0; dest = to; dest.add(v); if(dest != from) { vec dir = vec(dest).sub(from).normalize(); raycubepos(from, dir, dest, 0, RAY_CLIPMAT|RAY_ALPHAPOLY); } return; } } float accmod(gameent *d, bool zooming) { float r = 0; bool running = d->running(moveslow), moving = d->move || d->strafe; if(running || moving) r += running ? spreadrunning : spreadmoving; else if(zooming) r += spreadzoom; else if(d->crouching()) r += spreadcrouch; else r += spreadstill; if(spreadinair > 0 && d->airmillis && !d->onladder) r += spreadinair; return r; } bool doshot(gameent *d, vec &targ, int weap, bool pressed, bool secondary, int force) { bool hadcook = W2(weap, cooked, true)&W_C_KEEP && (d->prevstate[weap] == W_S_ZOOM || d->prevstate[weap] == W_S_POWER), 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; if(hadcook || zooming) { if(!pressed) { client::addmsg(N_SPHY, "ri5", d->clientnum, SPHY_COOK, W_S_IDLE, 0, 0); d->setweapstate(weap, W_S_IDLE, 0, lastmillis, 0, true); return false; } else secondary = zooming; } int offset = 0, sweap = m_weapon(d->actortype, game::gamemode, game::mutators); if(!d->canshoot(weap, secondary ? HIT_ALT : 0, sweap, lastmillis)) { if(!d->canshoot(weap, secondary ? HIT_ALT : 0, sweap, lastmillis, (1<weapload[weap] <= 0 || weap != d->weapselect) return false; else offset = d->weapload[weap]; } float scale = 1; int sub = W2(weap, ammosub, secondary), cooked = force; if(W2(weap, cooktime, secondary) || zooming) { float maxscale = 1; if(sub > 1 && d->ammo[weap] < sub) maxscale = d->ammo[weap]/float(sub); int len = int(W2(weap, cooktime, secondary)*maxscale), type = zooming ? W_S_ZOOM : W_S_POWER; if(!cooked) { if(d->weapstate[weap] != type) { if(pressed) { if(offset > 0) { d->ammo[weap] = max(d->ammo[weap]-offset, 0); d->weapload[weap] = -offset; } int offtime = hadcook && d->prevstate[weap] == type ? lastmillis-d->prevtime[weap] : 0; client::addmsg(N_SPHY, "ri5", d->clientnum, SPHY_COOK, type, len, offtime); d->setweapstate(weap, type, len, lastmillis, offtime); } else return false; } cooked = len ? clamp(lastmillis-d->weaptime[weap], 1, len) : 1; if(zooming) { if(pressed && wassecond) return false; } else if(pressed && cooked < len) return false; } scale = len ? cooked/float(W2(weap, cooktime, secondary)) : 1; if(sub > 1 && scale < 1) sub = int(ceilf(sub*scale)); } else if(!pressed) return false; vec to, from; vector shots; #define addshot(p) \ { \ shotmsg &s = shots.add(); \ s.id = d->getprojid(); \ s.pos = ivec(int(p.x*DMF), int(p.y*DMF), int(p.z*DMF)); \ } int rays = W2(weap, rays, secondary); if(rays > 1 && W2(weap, cooked, secondary)&W_C_RAYS && W2(weap, cooktime, secondary) && scale < 1) rays = max(1, int(ceilf(rays*scale))); if(weap == W_MELEE || WF(false, weap, collide, secondary)&COLLIDE_HITSCAN) { if(weap == W_MELEE) { from = d->center(); to = vec(from).add(vec(d->yaw*RAD, d->pitch*RAD).mul(d->radius*2.f)); } else { from = d->originpos(weap); to = d->muzzlepos(weap); } loopi(rays) addshot(to); } else { from = d->muzzlepos(weap); to = targ; float m = accmod(d, W2(d->weapselect, cooked, true)&W_C_ZOOM && secondary); float spread = WSP(weap, secondary, game::gamemode, game::mutators, m); loopi(rays) { vec dest; if(spread > 0) offsetray(from, to, spread, W2(weap, spreadz, secondary), dest); else dest = to; if(weaptype[weap].thrown[secondary ? 1 : 0] > 0) dest.z += from.dist(dest)*weaptype[weap].thrown[secondary ? 1 : 0]; addshot(dest); } } projs::shootv(weap, secondary ? HIT_ALT : 0, sub, offset, scale, from, shots, d, true); 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()); return true; } void shoot(gameent *d, vec &targ, int force) { if(!game::allowmove(d)) return; bool secondary = physics::secondaryweap(d); if(doshot(d, targ, d->weapselect, d->action[secondary ? AC_SECONDARY : AC_PRIMARY], secondary, force)) if(!W2(d->weapselect, fullauto, secondary)) d->action[secondary ? AC_SECONDARY : AC_PRIMARY] = false; } void preload() { loopi(W_ALL) { if(*weaptype[i].item) preloadmodel(weaptype[i].item); if(*weaptype[i].vwep) preloadmodel(weaptype[i].vwep); if(*weaptype[i].hwep) preloadmodel(weaptype[i].hwep); } } }