- collect most sound infos in one table and use it to generate the menus - no more sound category checks with ranges - now only PAINx and FOOTSTEP* sounds use hardcoded index numbers enumsounds <list of categories> Lists sound index numbers and short descriptions for all sounds of one or more categories. The argument should contain one or more of PAIN, OWNPAIN, WEAPON, PICKUP, MOVEMENT, BULLET, OTHER, VOICECOM, TEAM, PUBLIC, FFA, FLAGONLY. All sounds with at least one of the flags will be listed. Flag names prefixed with "!" will exclude all matching sound from the list. Example: /echo (enumsounds "VOICECOM !PUBLIC") will list all voicecom sounds that are for teams only. DOCREF enumsounds
1671 lines
48 KiB
C++
1671 lines
48 KiB
C++
// weapon.cpp: all shooting and effects code
|
|
|
|
#include "cube.h"
|
|
#include "bot/bot.h"
|
|
#include "hudgun.h"
|
|
|
|
VARP(autoreload, 0, 1, 1);
|
|
VARP(akimboautoswitch, 0, 1, 1);
|
|
VARP(akimboendaction, 0, 3, 3); // 0: switch to knife, 1: stay with pistol (if has ammo), 2: switch to grenade (if possible), 3: switch to primary (if has ammo) - all fallback to previous one w/o ammo for target
|
|
|
|
struct sgray {
|
|
int ds; // damage flag: 0:outer, 1:medium, 2:center
|
|
vec rv; // ray vector
|
|
};
|
|
|
|
sgray sgr[SGRAYS*3];
|
|
|
|
int burstshotssettings[NUMGUNS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
|
|
|
|
void updatelastaction(playerent *d, int millis = lastmillis)
|
|
{
|
|
loopi(NUMGUNS) d->weapons[i]->updatetimers(millis);
|
|
d->lastaction = millis;
|
|
}
|
|
|
|
void checkweaponswitch()
|
|
{
|
|
if(!player1->weaponchanging) return;
|
|
int timeprogress = lastmillis-player1->weaponchanging;
|
|
if(timeprogress>weapon::weaponchangetime)
|
|
{
|
|
addmsg(SV_WEAPCHANGE, "ri", player1->weaponsel->type);
|
|
player1->weaponchanging = 0;
|
|
}
|
|
else if(timeprogress>(weapon::weaponchangetime>>1) && player1->weaponsel != player1->nextweaponsel)
|
|
{
|
|
player1->prevweaponsel = player1->weaponsel;
|
|
player1->weaponsel = player1->nextweaponsel;
|
|
}
|
|
}
|
|
|
|
void selectweapon(weapon *w)
|
|
{
|
|
if(!w || !player1->weaponsel->deselectable()) return;
|
|
if(w->selectable())
|
|
{
|
|
if(player1->attacking && player1->state == CS_ALIVE && player1->weaponsel->type == GUN_GRENADE) attack(false);
|
|
int i = w->type;
|
|
// substitute akimbo
|
|
weapon *akimbo = player1->weapons[GUN_AKIMBO];
|
|
if(w->type==GUN_PISTOL && akimbo->selectable()) w = akimbo;
|
|
|
|
player1->weaponswitch(w);
|
|
exechook(HOOK_SP, "onWeaponSwitch", "%d", i);
|
|
}
|
|
}
|
|
|
|
void requestweapon(char *ws)
|
|
{
|
|
int w = getlistindex(ws, gunnames, true, 0);
|
|
if(keypressed && player1->state == CS_ALIVE && w >= 0)
|
|
{
|
|
if (player1->akimbo && w == GUN_PISTOL) w = GUN_AKIMBO;
|
|
selectweapon(player1->weapons[w]);
|
|
}
|
|
}
|
|
COMMANDN(weapon, requestweapon, "s");
|
|
|
|
void shiftweapon(int *s)
|
|
{
|
|
if(keypressed && player1->state == CS_ALIVE)
|
|
{
|
|
if(!player1->weaponsel->deselectable()) return;
|
|
|
|
weapon *curweapon = player1->weaponsel;
|
|
weapon *akimbo = player1->weapons[GUN_AKIMBO];
|
|
|
|
// collect available weapons
|
|
vector<weapon *> availweapons;
|
|
loopi(NUMGUNS)
|
|
{
|
|
weapon *w = player1->weapons[i];
|
|
if(!w) continue;
|
|
if(w->selectable() || w==curweapon || (w->type==GUN_PISTOL && player1->akimbo))
|
|
{
|
|
availweapons.add(w);
|
|
}
|
|
}
|
|
|
|
// replace pistol by akimbo
|
|
if(player1->akimbo)
|
|
{
|
|
availweapons.removeobj(akimbo); // and remove initial akimbo
|
|
int pistolidx = availweapons.find(player1->weapons[GUN_PISTOL]);
|
|
if(pistolidx>=0) availweapons[pistolidx] = akimbo; // insert at pistols position
|
|
if(curweapon->type==GUN_PISTOL) curweapon = akimbo; // fix selection
|
|
}
|
|
|
|
// detect the next weapon
|
|
int num = availweapons.length();
|
|
int curidx = availweapons.find(curweapon);
|
|
if(!num || curidx<0) return;
|
|
int idx = (curidx + *s) % num;
|
|
if(idx<0) idx += num;
|
|
weapon *next = availweapons[idx];
|
|
if(next->type!=player1->weaponsel->type) // different weapon
|
|
{
|
|
selectweapon(next);
|
|
}
|
|
}
|
|
else if(player1->isspectating()) updatefollowplayer(*s);
|
|
}
|
|
COMMAND(shiftweapon, "i");
|
|
|
|
bool quicknade = false;
|
|
|
|
void quicknadethrow(bool on)
|
|
{
|
|
if(player1->state != CS_ALIVE) return;
|
|
if(on)
|
|
{
|
|
if(player1->weapons[GUN_GRENADE]->mag > 0)
|
|
{
|
|
if(player1->weaponsel->type != GUN_GRENADE) selectweapon(player1->weapons[GUN_GRENADE]);
|
|
if(player1->weaponsel->type == GUN_GRENADE || player1->nextweaponsel->type == GUN_GRENADE)
|
|
{
|
|
if(!(player1->weaponsel->type == GUN_GRENADE && player1->nextweaponsel->type != GUN_GRENADE)) attack(true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
attack(false);
|
|
if(player1->weaponsel->type == GUN_GRENADE) quicknade = true;
|
|
}
|
|
}
|
|
COMMAND(quicknadethrow, "d");
|
|
|
|
COMMANDF(currentprimary, "", () { intret(player1->primweap->type); });
|
|
COMMANDF(prevweapon, "", () { intret(player1->prevweaponsel->type); });
|
|
COMMANDF(curweapon, "", () { intret(player1->weaponsel->type); });
|
|
|
|
COMMANDF(magcontent, "s", (char *ws)
|
|
{
|
|
int w = getlistindex(ws, gunnames, true, -1);
|
|
if(w >= 0) intret(player1->weapons[w]->mag);
|
|
else intret(-1);
|
|
});
|
|
|
|
COMMANDF(magreserve, "s", (char *ws)
|
|
{
|
|
int w = getlistindex(ws, gunnames, true, -1);
|
|
if(w >= 0) intret(player1->weapons[w]->ammo);
|
|
else intret(-1);
|
|
});
|
|
|
|
void tryreload(playerent *p)
|
|
{
|
|
if(!p || p->state!=CS_ALIVE || p->weaponsel->reloading || p->weaponchanging) return;
|
|
p->weaponsel->reload(false);
|
|
}
|
|
|
|
COMMANDF(reload, "", () { tryreload(player1); });
|
|
|
|
void createrays(const vec &from, const vec &to) // create random spread of rays for the shotgun
|
|
{
|
|
vec dir = vec(from).sub(to);
|
|
float f = dir.magnitude() / 10.0f;
|
|
dir.normalize();
|
|
vec spoke;
|
|
spoke.orthogonal(dir);
|
|
spoke.normalize();
|
|
spoke.mul(f);
|
|
loopk(3)
|
|
{
|
|
float base;
|
|
int wrange;
|
|
switch(k)
|
|
{
|
|
case 0: base = SGCObase / 100.0f; wrange = SGCOrange; break;
|
|
case 1: base = SGCMbase / 100.0f; wrange = SGCMrange; break;
|
|
case 2:
|
|
default: base = SGCCbase / 100.0f; wrange = SGCCrange; break;
|
|
}
|
|
float rnddir = rndscale(PI2);
|
|
loopi(SGRAYS)
|
|
{
|
|
int j = k * SGRAYS + i;
|
|
sgr[j].ds = k;
|
|
vec p(spoke);
|
|
int rndmul = rnd(wrange);
|
|
float veclen = base + rndmul/100.0f;
|
|
p.mul(veclen);
|
|
|
|
p.rotate(PI2 / SGRAYS * i + rnddir, dir);
|
|
vec rray = vec(to);
|
|
rray.add(p);
|
|
sgr[j].rv = rray;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline bool intersectbox(const vec &o, const vec &rad, const vec &from, const vec &to, vec *end) // if lineseg hits entity bounding box
|
|
{
|
|
const vec *p;
|
|
vec v = to, w = o;
|
|
v.sub(from);
|
|
w.sub(from);
|
|
float c1 = w.dot(v);
|
|
|
|
if(c1<=0) p = &from;
|
|
else
|
|
{
|
|
float c2 = v.squaredlen();
|
|
if(c2<=c1) p = &to;
|
|
else
|
|
{
|
|
float f = c1/c2;
|
|
v.mul(f).add(from);
|
|
p = &v;
|
|
}
|
|
}
|
|
|
|
if(p->x <= o.x+rad.x
|
|
&& p->x >= o.x-rad.x
|
|
&& p->y <= o.y+rad.y
|
|
&& p->y >= o.y-rad.y
|
|
&& p->z <= o.z+rad.z
|
|
&& p->z >= o.z-rad.z)
|
|
{
|
|
if(end) *end = *p;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline bool intersectsphere(const vec &from, const vec &to, vec center, float radius, float &dist)
|
|
{
|
|
vec ray(to);
|
|
ray.sub(from);
|
|
center.sub(from);
|
|
float v = center.dot(ray),
|
|
inside = radius*radius - center.squaredlen();
|
|
if(inside < 0 && v < 0) return false;
|
|
float raysq = ray.squaredlen(), d = inside*raysq + v*v;
|
|
if(d < 0) return false;
|
|
dist = (v - sqrtf(d)) / raysq;
|
|
return dist >= 0 && dist <= 1;
|
|
}
|
|
|
|
static inline bool intersectcylinder(const vec &from, const vec &to, const vec &start, const vec &end, float radius, float &dist)
|
|
{
|
|
vec d(end), m(from), n(to);
|
|
d.sub(start);
|
|
m.sub(start);
|
|
n.sub(from);
|
|
float md = m.dot(d),
|
|
nd = n.dot(d),
|
|
dd = d.squaredlen();
|
|
if(md < 0 && md + nd < 0) return false;
|
|
if(md > dd && md + nd > dd) return false;
|
|
float nn = n.squaredlen(),
|
|
mn = m.dot(n),
|
|
a = dd*nn - nd*nd,
|
|
k = m.squaredlen() - radius*radius,
|
|
c = dd*k - md*md;
|
|
if(fabs(a) < 0.005f)
|
|
{
|
|
if(c > 0) return false;
|
|
if(md < 0) dist = -mn / nn;
|
|
else if(md > dd) dist = (nd - mn) / nn;
|
|
else dist = 0;
|
|
return true;
|
|
}
|
|
else if(c > 0)
|
|
{
|
|
float b = dd*mn - nd*md,
|
|
discrim = b*b - a*c;
|
|
if(discrim < 0) return false;
|
|
dist = (-b - sqrtf(discrim)) / a;
|
|
}
|
|
else dist = 0;
|
|
float offset = md + dist*nd;
|
|
if(offset < 0)
|
|
{
|
|
if(nd <= 0) return false;
|
|
dist = -md / nd;
|
|
if(k + dist*(2*mn + dist*nn) > 0) return false;
|
|
}
|
|
else if(offset > dd)
|
|
{
|
|
if(nd >= 0) return false;
|
|
dist = (dd - md) / nd;
|
|
if(k + dd - 2*md + dist*(2*(mn-nd) + dist*nn) > 0) return false;
|
|
}
|
|
return dist >= 0 && dist <= 1;
|
|
}
|
|
|
|
|
|
int intersect(playerent *d, const vec &from, const vec &to, vec *end)
|
|
{
|
|
float dist;
|
|
if(d->head.x >= 0)
|
|
{
|
|
if(intersectsphere(from, to, d->head, HEADSIZE, dist))
|
|
{
|
|
if(end) (*end = to).sub(from).mul(dist).add(from);
|
|
return 2;
|
|
}
|
|
}
|
|
float y = d->yaw*RAD, p = (d->pitch/4+90)*RAD, c = cosf(p);
|
|
vec bottom(d->o), top(sinf(y)*c, -cosf(y)*c, sinf(p))/*, mid(top)*/;
|
|
bottom.z -= d->eyeheight;
|
|
float h = d->eyeheight /*+ d->aboveeye*/; // this mod makes the shots pass over the shoulders
|
|
// mid.mul(h*0.5).add(bottom); // this mod divides the hitbox in 2
|
|
top.mul(h).add(bottom);
|
|
if( intersectcylinder(from, to, bottom, top, d->radius, dist) ) // FIXME if using 2 hitboxes
|
|
{
|
|
if(end) (*end = to).sub(from).mul(dist).add(from);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
|
|
#if 0
|
|
const float eyeheight = d->eyeheight;
|
|
vec o(d->o);
|
|
o.z += (d->aboveeye - eyeheight)/2;
|
|
return intersectbox(o, vec(d->radius, d->radius, (d->aboveeye + eyeheight)/2), from, to, end) ? 1 : 0;
|
|
#endif
|
|
}
|
|
|
|
bool intersect(entity *e, const vec &from, const vec &to, vec *end)
|
|
{
|
|
mapmodelinfo *mmi = getmminfo(e->attr2);
|
|
if(!mmi || !mmi->h) return false;
|
|
|
|
float lo = float(S(e->x, e->y)->floor + mmi->zoff + e->attr3);
|
|
return intersectbox(vec(e->x, e->y, lo + mmi->h / 2.0f), vec(mmi->rad, mmi->rad, mmi->h / 2.0f), from, to, end);
|
|
}
|
|
|
|
playerent *intersectclosest(const vec &from, const vec &to, const playerent *at, float &bestdistsquared, int &hitzone, bool aiming = true)
|
|
{
|
|
playerent *best = NULL;
|
|
bestdistsquared = 1e16f;
|
|
int zone;
|
|
if(at!=player1 && player1->state==CS_ALIVE && (zone = intersect(player1, from, to)))
|
|
{
|
|
best = player1;
|
|
bestdistsquared = at->o.squareddist(player1->o);
|
|
hitzone = zone;
|
|
}
|
|
loopv(players)
|
|
{
|
|
playerent *o = players[i];
|
|
if(!o || o==at || (o->state!=CS_ALIVE && (aiming || (o->state!=CS_EDITING && o->state!=CS_LAGGED)))) continue;
|
|
float distsquared = at->o.squareddist(o->o);
|
|
if(distsquared < bestdistsquared && (zone = intersect(o, from, to)))
|
|
{
|
|
best = o;
|
|
bestdistsquared = distsquared;
|
|
hitzone = zone;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
playerent *playerincrosshair()
|
|
{
|
|
if(camera1->type == ENT_PLAYER || (camera1->type == ENT_CAMERA && player1->spectatemode == SM_DEATHCAM))
|
|
{
|
|
float dist;
|
|
int hitzone;
|
|
return intersectclosest(camera1->o, worldpos, (playerent *)camera1, dist, hitzone, false);
|
|
}
|
|
else return NULL;
|
|
}
|
|
|
|
void damageeffect(int damage, playerent *d)
|
|
{
|
|
particle_splash(PART_BLOOD, damage/10, 1000, d->o);
|
|
}
|
|
|
|
struct hitweap
|
|
{
|
|
float hits;
|
|
int shots;
|
|
hitweap() {hits=shots=0;}
|
|
};
|
|
hitweap accuracym[NUMGUNS];
|
|
|
|
inline void attackevent(playerent *owner, int weapon)
|
|
{
|
|
if(owner == player1) exechook(HOOK_SP, "onAttack", "%d", weapon);
|
|
}
|
|
|
|
vector<hitmsg> hits;
|
|
|
|
void hit(int damage, playerent *d, playerent *at, const vec &vel, int gun, bool gib, int info)
|
|
{
|
|
if(d==player1 || d->type==ENT_BOT || !m_mp(gamemode)) d->hitpush(damage, vel, at, gun);
|
|
|
|
if(at == player1 && d != player1)
|
|
{
|
|
extern int hitsound;
|
|
extern int lasthit;
|
|
if(hitsound == 2 && lasthit != lastmillis)
|
|
{
|
|
defformatstring(hitsnd)("sound %d %d;", S_HITSOUND, SP_HIGHEST);
|
|
addsleep(60, hitsnd);
|
|
}
|
|
lasthit = lastmillis;
|
|
}
|
|
|
|
if(!m_mp(gamemode)) dodamage(damage, d, at, gun, gib);
|
|
else
|
|
{
|
|
hitmsg &h = hits.add();
|
|
h.target = d->clientnum;
|
|
h.lifesequence = d->lifesequence;
|
|
h.info = info;
|
|
if(d==player1)
|
|
{
|
|
h.dir = ivec(0, 0, 0);
|
|
d->damageroll(damage);
|
|
if(d != at) updatedmgindicator(player1, at->o);
|
|
damageblend(damage, d);
|
|
damageeffect(damage, d);
|
|
audiomgr.playsound(S_PAIN6, SP_HIGH);
|
|
}
|
|
else
|
|
{
|
|
h.dir = ivec(int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF));
|
|
// damageeffect(damage, d);
|
|
// audiomgr.playsound(S_PAIN1+rnd(5), d);
|
|
}
|
|
}
|
|
}
|
|
|
|
void hitpush(int damage, playerent *d, playerent *at, vec &from, vec &to, int gun, bool gib, int info)
|
|
{
|
|
vec v(to);
|
|
v.sub(from);
|
|
v.normalize();
|
|
hit(damage, d, at, v, gun, gib, info);
|
|
}
|
|
|
|
float expdist(playerent *o, vec &dir, const vec &v)
|
|
{
|
|
vec middle = o->o;
|
|
middle.z += (o->aboveeye-o->eyeheight)/2;
|
|
float dist = middle.dist(v, dir);
|
|
dir.div(dist);
|
|
if(dist<0) dist = 0;
|
|
return dist;
|
|
}
|
|
|
|
void radialeffect(playerent *o, vec &v, int qdam, playerent *at, int gun)
|
|
{
|
|
if(o->state!=CS_ALIVE) return;
|
|
vec dir;
|
|
float dist = expdist(o, dir, v);
|
|
if(dist<EXPDAMRAD)
|
|
{
|
|
if(at == player1) accuracym[gun].hits += 1.0f-(float)dist/EXPDAMRAD;
|
|
int damage = (int)(qdam*(1-dist/EXPDAMRAD));
|
|
hit(damage, o, at, dir, gun, true, int(dist*DMF));
|
|
}
|
|
}
|
|
|
|
vector<bounceent *> bounceents;
|
|
|
|
void removebounceents(playerent *owner)
|
|
{
|
|
loopv(bounceents) if(bounceents[i]->owner==owner) { delete bounceents[i]; bounceents.remove(i--); }
|
|
}
|
|
|
|
void movebounceents()
|
|
{
|
|
loopv(bounceents) if(bounceents[i])
|
|
{
|
|
bounceent *p = bounceents[i];
|
|
if((p->bouncetype==BT_NADE || p->bouncetype==BT_GIB) && p->applyphysics()) movebounceent(p, 1, false);
|
|
if(!p->isalive(lastmillis))
|
|
{
|
|
p->destroy();
|
|
delete p;
|
|
bounceents.remove(i--);
|
|
}
|
|
}
|
|
}
|
|
|
|
void clearbounceents()
|
|
{
|
|
if(gamespeed==100);
|
|
else if(multiplayer(NULL)) bounceents.add((bounceent *)player1);
|
|
loopv(bounceents) if(bounceents[i]) { delete bounceents[i]; bounceents.remove(i--); }
|
|
}
|
|
|
|
void renderbounceents()
|
|
{
|
|
loopv(bounceents)
|
|
{
|
|
bounceent *p = bounceents[i];
|
|
if(!p) continue;
|
|
string model;
|
|
vec o(p->o);
|
|
|
|
int anim = ANIM_MAPMODEL, basetime = 0;
|
|
switch(p->bouncetype)
|
|
{
|
|
case BT_NADE:
|
|
copystring(model, "weapons/grenade/static");
|
|
break;
|
|
case BT_GIB:
|
|
default:
|
|
{
|
|
uint n = (((4*(uint)(size_t)p)+(uint)p->timetolive)%3)+1;
|
|
formatstring(model)("misc/gib0%u", n);
|
|
int t = lastmillis-p->millis;
|
|
if(t>p->timetolive-2000)
|
|
{
|
|
anim = ANIM_DECAY;
|
|
basetime = p->millis+p->timetolive-2000;
|
|
t -= p->timetolive-2000;
|
|
o.z -= t*t/4000000000.0f*t;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
rendermodel(model, anim|ANIM_LOOP|ANIM_DYNALLOC, 0, 1.1f, o, 0, p->yaw+90, p->pitch, 0, basetime);
|
|
}
|
|
}
|
|
|
|
VARP(gib, 0, 1, 1);
|
|
VARP(gibnum, 0, 6, 1000);
|
|
VARP(gibttl, 0, 7000, 60000);
|
|
VARP(gibspeed, 1, 30, 100);
|
|
|
|
void addgib(playerent *d)
|
|
{
|
|
if(!d || !gib || !gibttl) return;
|
|
audiomgr.playsound(S_GIB, d);
|
|
|
|
loopi(gibnum)
|
|
{
|
|
bounceent *p = bounceents.add(new bounceent);
|
|
p->owner = d;
|
|
p->millis = lastmillis;
|
|
p->timetolive = gibttl+rnd(10)*100;
|
|
p->bouncetype = BT_GIB;
|
|
|
|
p->o = d->o;
|
|
p->o.z -= d->aboveeye;
|
|
p->inwater = waterlevel > p->o.z;
|
|
|
|
p->yaw = (float)rnd(360);
|
|
p->pitch = (float)rnd(360);
|
|
|
|
p->maxspeed = 30.0f;
|
|
p->rotspeed = 3.0f;
|
|
|
|
const float angle = (float)rnd(360);
|
|
const float speed = (float)gibspeed;
|
|
|
|
p->vel.x = sinf(RAD*angle)*rnd(1000)/1000.0f;
|
|
p->vel.y = cosf(RAD*angle)*rnd(1000)/1000.0f;
|
|
p->vel.z = rnd(1000)/1000.0f;
|
|
p->vel.mul(speed/100.0f);
|
|
|
|
p->resetinterp();
|
|
}
|
|
}
|
|
|
|
void shorten(const vec &from, vec &target, float distsquared)
|
|
{
|
|
target.sub(from);
|
|
float m = target.squaredlen();
|
|
if(m < 0.07f) target.add(vec(0.1f, 0.1f, 0.1f)); // if "from == target" just fake a target to avoid zero-length fx vectors
|
|
else target.mul(max(0.07f, sqrtf(distsquared / m)));
|
|
target.add(from);
|
|
}
|
|
|
|
void raydamage(vec &from, vec &to, playerent *d)
|
|
{
|
|
int dam = d->weaponsel->info.damage;
|
|
int hitzone = -1;
|
|
playerent *o = NULL;
|
|
float distsquared, hitdistsquared = 0.0f;
|
|
bool hit = false;
|
|
int rayscount = 0, hitscount = 0;
|
|
if(d->weaponsel->type==GUN_SHOTGUN)
|
|
{
|
|
playerent *hits[3*SGRAYS];
|
|
loopk(3)
|
|
loopi(SGRAYS)
|
|
{
|
|
rayscount++;
|
|
int h = k*SGRAYS + i;
|
|
if((hits[h] = intersectclosest(from, sgr[h].rv, d, distsquared, hitzone)))
|
|
shorten(from, sgr[h].rv, (hitdistsquared = distsquared));
|
|
}
|
|
loopk(3)
|
|
loopi(SGRAYS)
|
|
{
|
|
int h = k*SGRAYS + i;
|
|
if(hits[h])
|
|
{
|
|
o = hits[h];
|
|
hits[h] = NULL;
|
|
int numhits_o, numhits_m, numhits_c;
|
|
numhits_o = numhits_m = numhits_c = 0;
|
|
switch(sgr[h].ds)
|
|
{
|
|
case 0: numhits_o++; break;
|
|
case 1: numhits_m++; break;
|
|
case 2: numhits_c++; break;
|
|
default: break;
|
|
}
|
|
for(int j = i+1; j < 3*SGRAYS; j++) if(hits[j] == o)
|
|
{
|
|
hits[j] = NULL;
|
|
switch(sgr[j].ds)
|
|
{
|
|
case 0: numhits_o++; break;
|
|
case 1: numhits_m++; break;
|
|
case 2: numhits_c++; break;
|
|
default: break;
|
|
}
|
|
}
|
|
int numhits = numhits_o + numhits_m + numhits_c;
|
|
int dmgreal = 0;
|
|
float dmg4r = 0.0f;
|
|
bool withBONUS = false;
|
|
if(SGDMGBONUS)
|
|
{
|
|
float d2o = SGDMGDISTB;
|
|
if(o) d2o = vec(from).sub(o->o).magnitude();
|
|
if(d2o <= (SGDMGDISTB/10.0f) && numhits)
|
|
{
|
|
dmg4r += SGDMGBONUS;
|
|
withBONUS = true;
|
|
}
|
|
}
|
|
dmg4r += (SGCOdmg / 10.0f * SGDMGTOTAL / 100.0f) * numhits_o / 21.0f;
|
|
dmg4r += (SGCMdmg / 10.0f * SGDMGTOTAL / 100.0f) * numhits_m / 21.0f;
|
|
dmg4r += (SGCCdmg / 10.0f * SGDMGTOTAL / 100.0f) * numhits_c / 21.0f;
|
|
dmgreal = (int) ceil(dmg4r);
|
|
int info = (withBONUS ? SGDMGBONUS : 0) | (numhits_c << 8) | (numhits_m << 16) | (numhits_o << 24);
|
|
if(numhits) hitpush(dmgreal, o, d, from, to, d->weaponsel->type, numhits == SGRAYS * 3, info);
|
|
|
|
if(d == player1) hit = true;
|
|
hitscount+=numhits;
|
|
}
|
|
}
|
|
if(hitscount) shorten(from, to, hitdistsquared);
|
|
}
|
|
else if((o = intersectclosest(from, to, d, distsquared, hitzone)))
|
|
{
|
|
bool gib = false;
|
|
switch(d->weaponsel->type)
|
|
{
|
|
case GUN_KNIFE: gib = true; break;
|
|
case GUN_SNIPER: if(d==player1 && hitzone==2) { dam *= 3; gib = true; }; break;
|
|
default: break;
|
|
}
|
|
bool info = gib;
|
|
hitpush(dam, o, d, from, to, d->weaponsel->type, gib, info ? 1 : 0);
|
|
if(d == player1) hit = true;
|
|
shorten(from, to, distsquared);
|
|
hitscount++;
|
|
}
|
|
|
|
if(d==player1)
|
|
{
|
|
if(!rayscount) rayscount = 1;
|
|
if(hit) accuracym[d->weaponsel->type].hits += (float)hitscount / rayscount;
|
|
accuracym[d->weaponsel->type].shots++;
|
|
}
|
|
}
|
|
|
|
const char *weapstr(int i) { return valid_weapon(i) ? guns[i].title : "x"; }
|
|
|
|
VARP(accuracy,0,0,1);
|
|
|
|
void r_accuracy(int h)
|
|
{
|
|
int i = player1->weaponsel->type;
|
|
if(accuracy && valid_weapon(i) && i != GUN_CPISTOL)
|
|
{
|
|
int x_offset = 2 * HUDPOS_X_BOTTOMLEFT, y_offset = 2 * (h - 1.75 * FONTH);
|
|
string line;
|
|
float acc = accuracym[i].shots ? 100.0 * accuracym[i].hits / (float)accuracym[i].shots : 0;
|
|
if(i == GUN_GRENADE || i == GUN_SHOTGUN)
|
|
{
|
|
formatstring(line)("\f5%5.1f%% (%.1f/%d): \f0%s", acc, accuracym[i].hits, (int)accuracym[i].shots, weapstr(i));
|
|
}
|
|
else
|
|
{
|
|
formatstring(line)("\f5%5.1f%% (%d/%d): \f0%s", acc, (int)accuracym[i].hits, accuracym[i].shots, weapstr(i));
|
|
}
|
|
blendbox(x_offset, y_offset + FONTH, x_offset + text_width(line) + 2 * FONTH, y_offset - FONTH, false, -1);
|
|
draw_textf("%s", x_offset + FONTH, y_offset - 0.5 * FONTH, line);
|
|
}
|
|
}
|
|
|
|
void accuracyinfo()
|
|
{
|
|
vector <char*>lines;
|
|
loopi(NUMGUNS) if(i != GUN_CPISTOL && accuracym[i].shots)
|
|
{
|
|
float acc = 100.0 * accuracym[i].hits / (float)accuracym[i].shots;
|
|
string line;
|
|
if(i == GUN_GRENADE || i == GUN_SHOTGUN)
|
|
{
|
|
formatstring(line)("\f0%-10s\t\f5 %.1f%% (%.1f/%d)", weapstr(i), acc, accuracym[i].hits, (int)accuracym[i].shots);
|
|
}
|
|
else
|
|
{
|
|
formatstring(line)("\f0%-10s\t\f5 %.1f%% (%d/%d)", weapstr(i), acc, (int)accuracym[i].hits, accuracym[i].shots);
|
|
}
|
|
lines.add(newstring(line));
|
|
}
|
|
loopv(lines) conoutf("%s", lines[i]);
|
|
lines.deletearrays();
|
|
}
|
|
|
|
COMMAND(accuracyinfo, "");
|
|
|
|
void accuracyreset()
|
|
{
|
|
loopi(NUMGUNS)
|
|
{
|
|
accuracym[i].hits=accuracym[i].shots=0;
|
|
}
|
|
conoutf("Your accuracy has been reset");
|
|
}
|
|
COMMAND(accuracyreset, "");
|
|
// weapon
|
|
|
|
weapon::weapon(class playerent *owner, int type) : type(type), owner(owner), info(guns[type]),
|
|
ammo(owner->ammo[type]), mag(owner->mag[type]), gunwait(owner->gunwait[type]), reloading(0)
|
|
{
|
|
}
|
|
|
|
const int weapon::weaponchangetime = 400;
|
|
const float weapon::weaponbeloweye = 0.2f;
|
|
|
|
int weapon::flashtime() const { return max((int)info.attackdelay, 120)/4; }
|
|
|
|
void weapon::sendshoot(vec &from, vec &to, int millis)
|
|
{
|
|
if(owner!=player1) return;
|
|
owner->shoot = true;
|
|
addmsg(SV_SHOOT, "ri2i3iv", millis, owner->weaponsel->type,
|
|
(int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
|
|
hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
|
|
player1->pstatshots[player1->weaponsel->type]++; //NEW
|
|
}
|
|
|
|
bool weapon::modelattacking()
|
|
{
|
|
int animtime = min(owner->gunwait[owner->weaponsel->type], (int)owner->weaponsel->info.attackdelay);
|
|
if(lastmillis - owner->lastaction < animtime) return true;
|
|
else return false;
|
|
}
|
|
|
|
void weapon::attacksound()
|
|
{
|
|
if(info.sound == S_NULL) return;
|
|
bool local = (owner == player1);
|
|
audiomgr.playsound(info.sound, owner, local ? SP_HIGH : SP_NORMAL);
|
|
}
|
|
|
|
bool weapon::reload(bool autoreloaded)
|
|
{
|
|
if(mag>=info.magsize || ammo<=0) return false;
|
|
updatelastaction(owner);
|
|
reloading = lastmillis;
|
|
gunwait += info.reloadtime;
|
|
|
|
int numbullets = min(info.magsize - mag, ammo);
|
|
mag += numbullets;
|
|
ammo -= numbullets;
|
|
|
|
bool local = (player1 == owner);
|
|
if(info.reload != S_NULL) audiomgr.playsound(info.reload, owner, local ? SP_HIGH : SP_NORMAL);
|
|
if(local)
|
|
{
|
|
addmsg(SV_RELOAD, "ri2", lastmillis, owner->weaponsel->type);
|
|
exechook(HOOK_SP, "onReload", "%d", (int)autoreloaded);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
VARP(oldfashionedgunstats, 0, 0, 1);
|
|
|
|
void weapon::renderstats()
|
|
{
|
|
string gunstats;
|
|
if(oldfashionedgunstats) formatstring(gunstats)("%d/%d", mag, ammo); else formatstring(gunstats)("%d", mag);
|
|
draw_text(gunstats, HUDPOS_WEAPON + HUDPOS_NUMBERSPACING, 823);
|
|
if(!oldfashionedgunstats)
|
|
{
|
|
int offset = text_width(gunstats);
|
|
glScalef(0.5f, 0.5f, 1.0f);
|
|
formatstring(gunstats)("%d", ammo);
|
|
draw_text(gunstats, (HUDPOS_WEAPON + HUDPOS_NUMBERSPACING + offset)*2, 826*2);
|
|
glLoadIdentity();
|
|
}
|
|
}
|
|
|
|
|
|
static int recoiltest = 0;//VAR(recoiltest, 0, 0, 1); // DISABLE ON RELEASE
|
|
static int recoilincrease = 2; //VAR(recoilincrease, 1, 2, 10);
|
|
static int recoilbase = 40;//VAR(recoilbase, 0, 40, 1000);
|
|
static int maxrecoil = 1000;//VAR(maxrecoil, 0, 1000, 1000);
|
|
|
|
void weapon::attackphysics(vec &from, vec &to) // physical fx to the owner
|
|
{
|
|
const guninfo &g = info;
|
|
vec unitv;
|
|
float dist = to.dist(from, unitv);
|
|
float f = dist/1000;
|
|
int spread = dynspread();
|
|
float recoil = dynrecoil()*-0.01f;
|
|
|
|
// spread
|
|
if(spread>1)
|
|
{
|
|
#define RNDD (rnd(spread)-spread/2)*f
|
|
vec r(RNDD, RNDD, RNDD);
|
|
to.add(r);
|
|
#undef RNDD
|
|
}
|
|
// kickback & recoil
|
|
if(recoiltest)
|
|
{
|
|
owner->vel.add(vec(unitv).mul(recoil/dist).mul(owner->crouching ? 0.75 : 1.0f));
|
|
owner->pitchvel = min(powf(shots/(float)(recoilincrease), 2.0f)+(float)(recoilbase)/10.0f, (float)(maxrecoil)/10.0f);
|
|
}
|
|
else
|
|
{
|
|
owner->vel.add(vec(unitv).mul(recoil/dist).mul(owner->crouching ? 0.75 : 1.0f));
|
|
owner->pitchvel = min(powf(shots/(float)(g.recoilincrease), 2.0f)+(float)(g.recoilbase)/10.0f, (float)(g.maxrecoil)/10.0f);
|
|
}
|
|
}
|
|
|
|
VARP(righthanded, 0, 1, 1); // flowtron 20090727
|
|
|
|
void weapon::renderhudmodel(int lastaction, int index)
|
|
{
|
|
playerent *p = owner;
|
|
vec unitv;
|
|
float dist = worldpos.dist(p->o, unitv);
|
|
unitv.div(dist);
|
|
|
|
weaponmove wm;
|
|
if(!intermission) wm.calcmove(unitv, lastaction, p);
|
|
// if(!intermission) wm.calcmove(unitv, p->lastaction, p);
|
|
defformatstring(path)("weapons/%s", info.modelname);
|
|
bool emit = (wm.anim&ANIM_INDEX)==ANIM_GUN_SHOOT && (lastmillis - lastaction) < flashtime();
|
|
// bool emit = (wm.anim&ANIM_INDEX)==ANIM_GUN_SHOOT && (lastmillis - p->lastaction) < flashtime();
|
|
rendermodel(path, wm.anim|ANIM_DYNALLOC|(righthanded==index ? ANIM_MIRROR : 0)|(emit ? ANIM_PARTICLE : 0), 0, -1, wm.pos, 0, p->yaw+90, p->pitch+wm.k_rot, 40.0f, wm.basetime, NULL, NULL, 1.28f);
|
|
}
|
|
|
|
void weapon::updatetimers(int millis)
|
|
{
|
|
if(gunwait) gunwait = max(gunwait - (millis-owner->lastaction), 0);
|
|
}
|
|
|
|
void weapon::onselecting()
|
|
{
|
|
updatelastaction(owner);
|
|
bool local = (owner == player1);
|
|
audiomgr.playsound(S_GUNCHANGE, owner, local ? SP_HIGH : SP_NORMAL);
|
|
}
|
|
|
|
void weapon::renderhudmodel() { renderhudmodel(owner->lastaction); }
|
|
void weapon::renderaimhelp(bool teamwarning)
|
|
{
|
|
if(!editmode) drawcrosshair(owner, teamwarning ? CROSSHAIR_TEAMMATE : owner->weaponsel->type);
|
|
else drawcrosshair(owner, CROSSHAIR_EDIT);
|
|
}
|
|
int weapon::dynspread() { return info.spread; }
|
|
float weapon::dynrecoil() { return info.recoil; }
|
|
bool weapon::selectable() { return this != owner->weaponsel && owner->state == CS_ALIVE && !owner->weaponchanging; }
|
|
bool weapon::deselectable() { return !reloading; }
|
|
|
|
void weapon::equipplayer(playerent *pl)
|
|
{
|
|
if(!pl) return;
|
|
pl->weapons[GUN_ASSAULT] = new assaultrifle(pl);
|
|
pl->weapons[GUN_GRENADE] = new grenades(pl);
|
|
pl->weapons[GUN_KNIFE] = new knife(pl);
|
|
pl->weapons[GUN_PISTOL] = new pistol(pl);
|
|
pl->weapons[GUN_CPISTOL] = new cpistol(pl);
|
|
pl->weapons[GUN_CARBINE] = new carbine(pl);
|
|
pl->weapons[GUN_SHOTGUN] = new shotgun(pl);
|
|
pl->weapons[GUN_SNIPER] = new sniperrifle(pl);
|
|
pl->weapons[GUN_SUBGUN] = new subgun(pl);
|
|
pl->weapons[GUN_AKIMBO] = new akimbo(pl);
|
|
pl->selectweapon(GUN_ASSAULT);
|
|
pl->setprimary(GUN_ASSAULT);
|
|
pl->setnextprimary(GUN_ASSAULT);
|
|
}
|
|
|
|
// grenadeent
|
|
|
|
enum { NS_NONE, NS_ACTIVATED = 0, NS_THROWN, NS_EXPLODED };
|
|
|
|
grenadeent::grenadeent (playerent *owner, int millis)
|
|
{
|
|
ASSERT(owner);
|
|
nadestate = NS_NONE;
|
|
local = owner==player1;
|
|
bounceent::owner = owner;
|
|
bounceent::millis = lastmillis;
|
|
timetolive = 2000-millis;
|
|
bouncetype = BT_NADE;
|
|
maxspeed = 30.0f;
|
|
rotspeed = 6.0f;
|
|
distsincebounce = 0.0f;
|
|
}
|
|
|
|
grenadeent::~grenadeent()
|
|
{
|
|
if(owner && owner->weapons[GUN_GRENADE]) owner->weapons[GUN_GRENADE]->removebounceent(this);
|
|
}
|
|
|
|
void grenadeent::explode()
|
|
{
|
|
if(nadestate!=NS_ACTIVATED && nadestate!=NS_THROWN ) return;
|
|
nadestate = NS_EXPLODED;
|
|
static vec n(0,0,0);
|
|
hits.setsize(0);
|
|
splash();
|
|
if(local)
|
|
addmsg(SV_EXPLODE, "ri3iv", lastmillis, GUN_GRENADE, millis, hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
|
|
audiomgr.playsound(S_FEXPLODE, &o);
|
|
}
|
|
|
|
void grenadeent::splash()
|
|
{
|
|
particle_splash(PART_SPARK, 50, 300, o);
|
|
particle_fireball(PART_FIREBALL, o);
|
|
addscorchmark(o);
|
|
adddynlight(NULL, o, 16, 200, 100, 255, 255, 224);
|
|
adddynlight(NULL, o, 16, 600, 600, 192, 160, 128);
|
|
if(owner == player1)
|
|
{
|
|
accuracym[GUN_GRENADE].shots++;
|
|
}
|
|
else if(!m_botmode)
|
|
{
|
|
return;
|
|
}
|
|
int damage = guns[GUN_GRENADE].damage;
|
|
|
|
radialeffect(owner->type == ENT_BOT ? player1 : owner, o, damage, owner, GUN_GRENADE);
|
|
loopv(players)
|
|
{
|
|
playerent *p = players[i];
|
|
if(!p) continue;
|
|
radialeffect(p, o, damage, owner, GUN_GRENADE);
|
|
}
|
|
}
|
|
|
|
void grenadeent::activate(const vec &from, const vec &to)
|
|
{
|
|
if(nadestate!=NS_NONE) return;
|
|
nadestate = NS_ACTIVATED;
|
|
|
|
if(local)
|
|
{
|
|
addmsg(SV_SHOOT, "ri2i3i", millis, owner->weaponsel->type,
|
|
// (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
|
|
(int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
|
|
0);
|
|
audiomgr.playsound(S_GRENADEPULL, SP_HIGH);
|
|
player1->pstatshots[GUN_GRENADE]++; //NEW
|
|
}
|
|
}
|
|
|
|
void grenadeent::_throw(const vec &from, const vec &vel)
|
|
{
|
|
if(nadestate!=NS_ACTIVATED) return;
|
|
nadestate = NS_THROWN;
|
|
this->vel = vel;
|
|
this->o = from;
|
|
this->resetinterp();
|
|
inwater = waterlevel > o.z;
|
|
if(local)
|
|
{
|
|
addmsg(SV_THROWNADE, "ri7", int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DMF), int(vel.y*DMF), int(vel.z*DMF), lastmillis-millis);
|
|
audiomgr.playsound(S_GRENADETHROW, SP_HIGH);
|
|
}
|
|
else audiomgr.playsound(S_GRENADETHROW, owner);
|
|
}
|
|
|
|
void grenadeent::moveoutsidebbox(const vec &direction, playerent *boundingbox)
|
|
{
|
|
vel = direction;
|
|
o = boundingbox->o;
|
|
inwater = waterlevel > o.z;
|
|
|
|
boundingbox->cancollide = false;
|
|
loopi(10) moveplayer(this, 10, true, 10);
|
|
boundingbox->cancollide = true;
|
|
}
|
|
|
|
void grenadeent::destroy() { explode(); }
|
|
bool grenadeent::applyphysics() { return nadestate==NS_THROWN; }
|
|
|
|
void grenadeent::oncollision()
|
|
{
|
|
if(distsincebounce>=1.5f) audiomgr.playsound(rnd(2) ? S_GRENADEBOUNCE1 : S_GRENADEBOUNCE2, &o);
|
|
distsincebounce = 0.0f;
|
|
}
|
|
|
|
void grenadeent::onmoved(const vec &dist)
|
|
{
|
|
distsincebounce += dist.magnitude();
|
|
}
|
|
|
|
// grenades
|
|
|
|
grenades::grenades(playerent *owner) : weapon(owner, GUN_GRENADE), inhandnade(NULL), throwwait((13*1000)/40), throwmillis(0), cookingmillis(0), state(GST_NONE) {}
|
|
|
|
int grenades::flashtime() const { return 0; }
|
|
|
|
bool grenades::busy() { return state!=GST_NONE; }
|
|
|
|
bool grenades::attack(vec &targ)
|
|
{
|
|
int attackmillis = lastmillis-owner->lastaction;
|
|
vec &to = targ;
|
|
|
|
bool waitdone = attackmillis>=gunwait && !(m_arena && m_teammode && arenaintermission);
|
|
if(waitdone) gunwait = reloading = 0;
|
|
|
|
switch(state)
|
|
{
|
|
case GST_NONE:
|
|
if(waitdone && owner->attacking && this==owner->weaponsel)
|
|
{
|
|
attackevent(owner, type);
|
|
activatenade(to); // activate
|
|
}
|
|
break;
|
|
|
|
case GST_INHAND:
|
|
if(waitdone)
|
|
{
|
|
if(!owner->attacking || this!=owner->weaponsel) thrownade(); // throw
|
|
else if(!inhandnade->isalive(lastmillis)) dropnade(); // drop & have fun
|
|
}
|
|
break;
|
|
|
|
case GST_THROWING:
|
|
if(attackmillis >= throwwait) // throw done
|
|
{
|
|
reset();
|
|
if(!mag && this==owner->weaponsel) // switch to primary immediately
|
|
{
|
|
owner->weaponchanging = lastmillis-1-(weaponchangetime/2);
|
|
owner->nextweaponsel = owner->weaponsel = owner->primweap;
|
|
}
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void grenades::attackfx(const vec &from, const vec &to, int millis) // other player's grenades
|
|
{
|
|
throwmillis = lastmillis-millis;
|
|
cookingmillis = millis;
|
|
if(millis == 0 || millis == -1) audiomgr.playsound(S_GRENADEPULL, owner); // activate
|
|
else if(millis > 0) // throw
|
|
{
|
|
grenadeent *g = new grenadeent(owner, millis);
|
|
bounceents.add(g);
|
|
g->_throw(from, to);
|
|
}
|
|
}
|
|
|
|
int grenades::modelanim()
|
|
{
|
|
if(state == GST_THROWING) return ANIM_GUN_THROW;
|
|
else
|
|
{
|
|
int animtime = min(gunwait, (int)info.attackdelay);
|
|
if(state == GST_INHAND || lastmillis - owner->lastaction < animtime) return ANIM_GUN_SHOOT;
|
|
}
|
|
return ANIM_GUN_IDLE;
|
|
}
|
|
|
|
void grenades::activatenade(const vec &to)
|
|
{
|
|
if(!mag) return;
|
|
throwmillis = 0;
|
|
|
|
inhandnade = new grenadeent(owner);
|
|
bounceents.add(inhandnade);
|
|
|
|
updatelastaction(owner);
|
|
mag--;
|
|
gunwait = info.attackdelay;
|
|
owner->lastattackweapon = this;
|
|
state = GST_INHAND;
|
|
inhandnade->activate(owner->o, to);
|
|
}
|
|
|
|
void grenades::thrownade()
|
|
{
|
|
if(!inhandnade) return;
|
|
const float speed = cosf(RAD*owner->pitch);
|
|
vec vel(sinf(RAD*owner->yaw)*speed, -cosf(RAD*owner->yaw)*speed, sinf(RAD*owner->pitch));
|
|
vel.mul(1.5f);
|
|
thrownade(vel);
|
|
}
|
|
|
|
void grenades::thrownade(const vec &vel)
|
|
{
|
|
inhandnade->moveoutsidebbox(vel, owner);
|
|
inhandnade->_throw(inhandnade->o, vel);
|
|
inhandnade = NULL;
|
|
|
|
throwmillis = lastmillis;
|
|
updatelastaction(owner);
|
|
state = GST_THROWING;
|
|
if(this==owner->weaponsel) owner->attacking = false;
|
|
if(quicknade && owner->weaponsel->type == GUN_GRENADE) selectweapon(owner->prevweaponsel);
|
|
}
|
|
|
|
void grenades::dropnade()
|
|
{
|
|
vec n(0,0,0);
|
|
thrownade(n);
|
|
}
|
|
|
|
void grenades::renderstats()
|
|
{
|
|
char gunstats[64];
|
|
sprintf(gunstats, "%d", mag);
|
|
draw_text(gunstats, oldfashionedgunstats ? HUDPOS_GRENADE + HUDPOS_NUMBERSPACING + 25 : HUDPOS_GRENADE + HUDPOS_NUMBERSPACING, 823);
|
|
}
|
|
|
|
bool grenades::selectable() { return weapon::selectable() && state != GST_INHAND && mag; }
|
|
void grenades::reset() { throwmillis = 0; cookingmillis = 0; quicknade = false; state = GST_NONE; }
|
|
|
|
void grenades::onselecting()
|
|
{
|
|
reset();
|
|
bool local = (owner == player1);
|
|
audiomgr.playsound(S_GUNCHANGE, owner, local ? SP_HIGH : SP_NORMAL);
|
|
}
|
|
|
|
void grenades::onownerdies()
|
|
{
|
|
reset();
|
|
if(owner==player1 && inhandnade) dropnade();
|
|
}
|
|
|
|
void grenades::removebounceent(bounceent *b)
|
|
{
|
|
if(b == inhandnade) { inhandnade = NULL; reset(); }
|
|
}
|
|
|
|
// gun base class
|
|
|
|
gun::gun(playerent *owner, int type) : weapon(owner, type) {}
|
|
|
|
bool gun::attack(vec &targ)
|
|
{
|
|
int attackmillis = lastmillis-owner->lastaction - gunwait;
|
|
if(attackmillis<0) return false;
|
|
gunwait = reloading = 0;
|
|
|
|
if(!owner->attacking)
|
|
{
|
|
shots = 0;
|
|
checkautoreload();
|
|
return false;
|
|
}
|
|
|
|
attackmillis = lastmillis - min(attackmillis, curtime);
|
|
updatelastaction(owner, attackmillis);
|
|
if(!mag)
|
|
{
|
|
bool local = (owner == player1);
|
|
audiomgr.playsound(S_NOAMMO, owner, local ? SP_HIGH : SP_NORMAL);
|
|
gunwait += 250;
|
|
owner->lastattackweapon = NULL;
|
|
shots = 0;
|
|
checkautoreload();
|
|
return false;
|
|
}
|
|
|
|
owner->lastattackweapon = this;
|
|
shots++;
|
|
|
|
if(!info.isauto) owner->attacking = false;
|
|
|
|
if(burstshotssettings[this->type] > 0 && shots >= burstshotssettings[this->type]) owner->attacking = false;
|
|
|
|
vec from = owner->o;
|
|
vec to = targ;
|
|
from.z -= weaponbeloweye;
|
|
|
|
attackphysics(from, to);
|
|
|
|
attackevent(owner, type);
|
|
|
|
hits.setsize(0);
|
|
raydamage(from, to, owner);
|
|
attackfx(from, to, 0);
|
|
|
|
gunwait = info.attackdelay;
|
|
mag--;
|
|
|
|
sendshoot(from, to, attackmillis);
|
|
|
|
return true;
|
|
}
|
|
|
|
void gun::attackfx(const vec &from, const vec &to, int millis)
|
|
{
|
|
if(from.squareddist(to) > 0.07f)
|
|
{
|
|
addbullethole(owner, from, to);
|
|
addshotline(owner, from, to);
|
|
}
|
|
particle_splash(PART_SPARK, 5, 250, to);
|
|
adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
|
|
attacksound();
|
|
}
|
|
|
|
int gun::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT|ANIM_LOOP : ANIM_GUN_IDLE; }
|
|
void gun::checkautoreload() { if(autoreload && owner==player1 && !mag) reload(true); }
|
|
void gun::onownerdies() { shots = 0; }
|
|
|
|
|
|
// shotgun
|
|
|
|
shotgun::shotgun(playerent *owner) : gun(owner, GUN_SHOTGUN) {}
|
|
|
|
void shotgun::attackphysics(vec &from, vec &to)
|
|
{
|
|
createrays(from, to);
|
|
gun::attackphysics(from, to);
|
|
}
|
|
|
|
bool shotgun::attack(vec &targ)
|
|
{
|
|
return gun::attack(targ);
|
|
}
|
|
|
|
void shotgun::attackfx(const vec &from, const vec &to, int millis)
|
|
{
|
|
loopi(SGRAYS) particle_splash(PART_SPARK, 5, 200, sgr[i].rv);
|
|
|
|
if(addbullethole(owner, from, to))
|
|
loopk(3) loopi(3) addbullethole(owner, from, sgr[k*SGRAYS+i*SGRAYS/3].rv, 0, false);
|
|
adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
|
|
attacksound();
|
|
}
|
|
|
|
bool shotgun::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
|
|
|
|
|
|
// subgun
|
|
|
|
subgun::subgun(playerent *owner) : gun(owner, GUN_SUBGUN) {}
|
|
bool subgun::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
|
|
int subgun::dynspread() { return shots > 2 ? 70 : ( info.spread + ( shots > 0 ? ( shots == 1 ? 5 : 10 ) : 0 ) ); } // CHANGED: 2010nov19 was: min(info.spread + 10 * shots, 80)
|
|
|
|
|
|
// sniperrifle
|
|
|
|
sniperrifle::sniperrifle(playerent *owner) : gun(owner, GUN_SNIPER), scoped(false) {}
|
|
|
|
void sniperrifle::attackfx(const vec &from, const vec &to, int millis)
|
|
{
|
|
if(from.squareddist(to) > 0.07f)
|
|
{
|
|
addbullethole(owner, from, to);
|
|
addshotline(owner, from, to);
|
|
particle_trail(PART_SMOKE, 500, from, to);
|
|
}
|
|
particle_splash(PART_SPARK, 50, 200, to);
|
|
adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
|
|
attacksound();
|
|
}
|
|
|
|
bool sniperrifle::reload(bool autoreloaded)
|
|
{
|
|
bool r = weapon::reload(autoreloaded);
|
|
if(owner==player1 && r) { scoped = false; player1->scoping = false; }
|
|
return r;
|
|
}
|
|
|
|
#define SCOPESETTLETIME 180
|
|
int sniperrifle::dynspread()
|
|
{
|
|
if(scoped)
|
|
{
|
|
int scopetime = lastmillis - scoped_since;
|
|
if(scopetime > SCOPESETTLETIME)
|
|
return 1;
|
|
else
|
|
return max((info.spread * (SCOPESETTLETIME - scopetime)) / SCOPESETTLETIME, 1);
|
|
}
|
|
return info.spread;
|
|
}
|
|
float sniperrifle::dynrecoil() { return scoped && lastmillis - scoped_since > SCOPESETTLETIME ? info.recoil / 3 : info.recoil; }
|
|
bool sniperrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
|
|
void sniperrifle::onselecting() { weapon::onselecting(); scoped = false; player1->scoping = false; }
|
|
void sniperrifle::ondeselecting() { scoped = false; player1->scoping = false; }
|
|
void sniperrifle::onownerdies() { scoped = false; player1->scoping = false; shots = 0; }
|
|
void sniperrifle::renderhudmodel() { if(!scoped) weapon::renderhudmodel(); }
|
|
|
|
void sniperrifle::renderaimhelp(bool teamwarning)
|
|
{
|
|
if(scoped) drawscope();
|
|
if(!editmode)
|
|
{
|
|
if(scoped || teamwarning) drawcrosshair(owner, teamwarning ? CROSSHAIR_TEAMMATE : CROSSHAIR_SCOPE, NULL, 24.0f);
|
|
}
|
|
else drawcrosshair(owner, CROSSHAIR_EDIT);
|
|
}
|
|
|
|
void sniperrifle::setscope(bool enable)
|
|
{
|
|
if(this == owner->weaponsel && !reloading && owner->state == CS_ALIVE)
|
|
{
|
|
if(scoped == false && enable == true) scoped_since = lastmillis;
|
|
if(enable != scoped) owner->scoping = enable;
|
|
scoped = enable;
|
|
}
|
|
}
|
|
|
|
// carbine
|
|
|
|
carbine::carbine(playerent *owner) : gun(owner, GUN_CARBINE) {}
|
|
|
|
bool carbine::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
|
|
|
|
|
|
// assaultrifle
|
|
|
|
assaultrifle::assaultrifle(playerent *owner) : gun(owner, GUN_ASSAULT) {}
|
|
|
|
int assaultrifle::dynspread() { return shots > 2 ? 55 : ( info.spread + ( shots > 0 ? ( shots == 1 ? 5 : 15 ) : 0 ) ); }
|
|
float assaultrifle::dynrecoil() { return info.recoil + (rnd(8)*-0.01f); }
|
|
bool assaultrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
|
|
|
|
// combat pistol
|
|
|
|
cpistol::cpistol(playerent *owner) : gun(owner, GUN_CPISTOL), bursting(false) {}
|
|
bool cpistol::selectable() { return false; /*return weapon::selectable() && !m_noprimary && this == owner->primweap;*/ }
|
|
void cpistol::onselecting() { weapon::onselecting(); bursting = false; }
|
|
void cpistol::ondeselecting() { bursting = false; }
|
|
bool cpistol::reload(bool autoreloaded)
|
|
{
|
|
bool r = weapon::reload(autoreloaded);
|
|
if(owner==player1 && r) { bursting = false; }
|
|
return r;
|
|
}
|
|
|
|
bool burst = false;
|
|
int burstshots = 0;
|
|
|
|
bool cpistol::attack(vec &targ) // modded from gun::attack // FIXME
|
|
{
|
|
int attackmillis = lastmillis-owner->lastaction - gunwait;
|
|
if(attackmillis<0) return false;
|
|
gunwait = reloading = 0;
|
|
|
|
if (bursting) burst = true;
|
|
|
|
if(!owner->attacking)
|
|
{
|
|
shots = 0;
|
|
checkautoreload();
|
|
return false;
|
|
}
|
|
|
|
attackmillis = lastmillis - min(attackmillis, curtime);
|
|
updatelastaction(owner, attackmillis);
|
|
if(!mag)
|
|
{
|
|
audiomgr.playsoundc(S_NOAMMO);
|
|
gunwait += 250;
|
|
owner->lastattackweapon = NULL;
|
|
shots = 0;
|
|
checkautoreload();
|
|
return false;
|
|
}
|
|
|
|
owner->lastattackweapon = this;
|
|
shots++;
|
|
if (burst) burstshots++;
|
|
|
|
if(!burst) owner->attacking = false;
|
|
|
|
vec from = owner->o;
|
|
vec to = targ;
|
|
from.z -= weaponbeloweye;
|
|
|
|
attackphysics(from, to);
|
|
|
|
hits.setsize(0);
|
|
raydamage(from, to, owner);
|
|
attackfx(from, to, 0);
|
|
|
|
if ( burst && burstshots > 2 )
|
|
{
|
|
gunwait = 500;
|
|
burstshots = 0;
|
|
burst = owner->attacking = false;
|
|
}
|
|
else if ( burst )
|
|
{
|
|
gunwait = 80;
|
|
}
|
|
else
|
|
{
|
|
gunwait = info.attackdelay;
|
|
}
|
|
mag--;
|
|
|
|
sendshoot(from, to, attackmillis);
|
|
return true;
|
|
}
|
|
|
|
void cpistol::setburst(bool enable)
|
|
{
|
|
if(this == owner->weaponsel && !reloading && owner->state == CS_ALIVE)
|
|
{
|
|
bursting = enable;
|
|
}
|
|
}
|
|
|
|
void setburst(bool enable)
|
|
{
|
|
if(player1->weaponsel->type != GUN_CPISTOL) return;
|
|
if(intermission) return;
|
|
cpistol *cp = (cpistol *)player1->weaponsel;
|
|
cp->setburst(enable);
|
|
if (!burst)
|
|
{
|
|
if ( enable && burstshots == 0 ) attack(true);
|
|
}
|
|
else
|
|
{
|
|
if ( burstshots == 0 ) burst = player1->attacking = enable;
|
|
}
|
|
}
|
|
|
|
COMMAND(setburst, "d");
|
|
|
|
// pistol
|
|
|
|
pistol::pistol(playerent *owner) : gun(owner, GUN_PISTOL) {}
|
|
bool pistol::selectable() { return weapon::selectable() && !m_nopistol; }
|
|
|
|
|
|
// akimbo
|
|
|
|
akimbo::akimbo(playerent *owner) : gun(owner, GUN_AKIMBO), akimboside(0), akimbomillis(0)
|
|
{
|
|
akimbolastaction[0] = akimbolastaction[1] = 0;
|
|
}
|
|
|
|
void akimbo::attackfx(const vec &from, const vec &to, int millis)
|
|
{
|
|
akimbolastaction[akimboside] = owner->lastaction;
|
|
akimboside = (akimboside+1)%2;
|
|
gun::attackfx(from, to, millis);
|
|
}
|
|
|
|
void akimbo::onammopicked()
|
|
{
|
|
akimbomillis = lastmillis + 30000;
|
|
if(owner==player1)
|
|
{
|
|
// if(owner->weaponsel->type!=GUN_SNIPER && owner->weaponsel->type!=GUN_GRENADE) owner->weaponswitch(this);
|
|
if(akimboautoswitch || owner->weaponsel->type==GUN_PISTOL) owner->weaponswitch(this); // Give the client full control over akimbo auto-switching // Bukz 2011apr23
|
|
addmsg(SV_AKIMBO, "ri", lastmillis);
|
|
}
|
|
}
|
|
|
|
void akimbo::onselecting()
|
|
{
|
|
gun::onselecting();
|
|
akimbolastaction[0] = akimbolastaction[1] = lastmillis;
|
|
}
|
|
|
|
bool akimbo::selectable() { return weapon::selectable() && !m_nopistol && owner->akimbo; }
|
|
void akimbo::updatetimers(int millis) { weapon::updatetimers(millis); /*loopi(2) akimbolastaction[i] = millis;*/ }
|
|
void akimbo::reset() { akimbolastaction[0] = akimbolastaction[1] = akimbomillis = akimboside = 0; }
|
|
|
|
void akimbo::renderhudmodel()
|
|
{
|
|
weapon::renderhudmodel(akimbolastaction[0], 0);
|
|
weapon::renderhudmodel(akimbolastaction[1], 1);
|
|
}
|
|
|
|
bool akimbo::timerout() { return akimbomillis && akimbomillis <= lastmillis; }
|
|
|
|
|
|
// knife
|
|
|
|
knife::knife(playerent *owner) : weapon(owner, GUN_KNIFE) {}
|
|
|
|
int knife::flashtime() const { return 0; }
|
|
|
|
bool knife::attack(vec &targ)
|
|
{
|
|
int attackmillis = lastmillis-owner->lastaction - gunwait;
|
|
if(attackmillis<0) return false;
|
|
gunwait = reloading = 0;
|
|
|
|
if(!owner->attacking) return false;
|
|
|
|
attackmillis = lastmillis - min(attackmillis, curtime);
|
|
updatelastaction(owner, attackmillis);
|
|
|
|
owner->lastattackweapon = this;
|
|
owner->attacking = false;
|
|
|
|
vec from = owner->o;
|
|
vec to = targ;
|
|
from.z -= weaponbeloweye;
|
|
|
|
vec unitv;
|
|
float dist = to.dist(from, unitv);
|
|
unitv.div(dist);
|
|
unitv.mul(3); // punch range
|
|
to = from;
|
|
to.add(unitv);
|
|
if ( owner->pitch < 0 ) to.z += 2.5 * sin( owner->pitch * 0.01745329 );
|
|
|
|
attackevent(owner, type);
|
|
|
|
hits.setsize(0);
|
|
raydamage(from, to, owner);
|
|
attackfx(from, to, 0);
|
|
sendshoot(from, to, attackmillis);
|
|
gunwait = info.attackdelay;
|
|
|
|
return true;
|
|
}
|
|
|
|
int knife::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT : ANIM_GUN_IDLE; }
|
|
|
|
void knife::drawstats() {}
|
|
void knife::attackfx(const vec &from, const vec &to, int millis) { attacksound(); }
|
|
void knife::renderstats() { }
|
|
|
|
|
|
void setscope(bool enable)
|
|
{
|
|
if(player1->weaponsel->type != GUN_SNIPER) return;
|
|
sniperrifle *sr = (sniperrifle *)player1->weaponsel;
|
|
sr->setscope(enable);
|
|
}
|
|
|
|
COMMANDF(setscope, "i", (int *on) { setscope(*on != 0); });
|
|
|
|
|
|
void shoot(playerent *p, vec &targ)
|
|
{
|
|
if(p->state!=CS_ALIVE || p->weaponchanging) return;
|
|
weapon *weap = p->weaponsel;
|
|
if(weap)
|
|
{
|
|
weap->attack(targ);
|
|
loopi(NUMGUNS)
|
|
{
|
|
weapon *bweap = p->weapons[i];
|
|
if(bweap != weap && bweap->busy()) bweap->attack(targ);
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkakimbo()
|
|
{
|
|
if(player1->akimbo)
|
|
{
|
|
akimbo &a = *((akimbo *)player1->weapons[GUN_AKIMBO]);
|
|
if(a.timerout() || player1->state == CS_DEAD || player1->state == CS_SPECTATE)
|
|
{
|
|
weapon &p = *player1->weapons[GUN_PISTOL];
|
|
player1->akimbo = false;
|
|
a.reset();
|
|
// transfer ammo to pistol
|
|
p.mag = min((int)p.info.magsize, max(a.mag, p.mag));
|
|
p.ammo = max(p.ammo, p.ammo);
|
|
// fix akimbo magcontent
|
|
a.mag = 0;
|
|
a.ammo = 0;
|
|
if(player1->weaponsel->type==GUN_AKIMBO || (player1->weaponchanging && player1->nextweaponsel->type==GUN_AKIMBO))
|
|
{
|
|
|
|
switch(akimboendaction)
|
|
{
|
|
case 0: player1->weaponswitch(player1->weapons[GUN_KNIFE]); break;
|
|
case 1:
|
|
{
|
|
if(player1->weapons[GUN_PISTOL]->ammo) player1->weaponswitch(&p);
|
|
else player1->weaponswitch(player1->weapons[GUN_KNIFE]);
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if(player1->mag[GUN_GRENADE]) player1->weaponswitch(player1->weapons[GUN_GRENADE]);
|
|
else {
|
|
if(player1->weapons[GUN_PISTOL]->ammo) player1->weaponswitch(&p);
|
|
else player1->weaponswitch(player1->weapons[GUN_KNIFE]);
|
|
}
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
if(player1->ammo[player1->primary]) player1->weaponswitch(player1->weapons[player1->primary]);
|
|
else {
|
|
if(player1->mag[GUN_GRENADE]) player1->weaponswitch(player1->weapons[GUN_GRENADE]);
|
|
else {
|
|
if(player1->weapons[GUN_PISTOL]->ammo) player1->weaponswitch(&p);
|
|
else player1->weaponswitch(player1->weapons[GUN_KNIFE]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
/*
|
|
case 0: player1->weaponswitch(&p); break;
|
|
case 1:
|
|
{
|
|
if( player1->ammo[player1->primary] ) player1->weaponswitch(player1->weapons[player1->primary]);
|
|
else player1->weaponswitch(&p);
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if( player1->mag[GUN_GRENADE] ) player1->weaponswitch(player1->weapons[GUN_GRENADE]);
|
|
else
|
|
{
|
|
if( player1->ammo[player1->primary] ) player1->weaponswitch(player1->weapons[player1->primary]);
|
|
else player1->weaponswitch(&p);
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
*/
|
|
}
|
|
}
|
|
if(player1->state != CS_DEAD && player1->state != CS_SPECTATE) audiomgr.playsoundc(S_AKIMBOOUT);
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkweaponstate()
|
|
{
|
|
checkweaponswitch();
|
|
checkakimbo();
|
|
}
|