Files
assaultcube-/source/src/weapon.cpp
T
ac-stef d0b309320b really cleanup sound
- 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
2017-05-22 17:30:51 +02:00

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();
}