1
// weapon.cpp: all shooting and effects code
8
VARP(autoreload, 0, 1, 1);
12
void updatelastaction(playerent *d)
14
loopi(NUMGUNS) d->weapons[i]->updatetimers();
15
d->lastaction = lastmillis;
18
void checkweaponswitch()
20
if(!player1->weaponchanging) return;
21
int timeprogress = lastmillis-player1->weaponchanging;
22
if(timeprogress>weapon::weaponchangetime)
24
addmsg(SV_WEAPCHANGE, "ri", player1->weaponsel->type);
25
player1->weaponchanging = 0;
27
else if(timeprogress>weapon::weaponchangetime/2)
29
player1->weaponsel = player1->nextweaponsel;
33
void selectweapon(weapon *w)
35
if(!w || !player1->weaponsel->deselectable()) return;
39
weapon *akimbo = player1->weapons[GUN_AKIMBO];
40
if(w->type==GUN_PISTOL && akimbo->selectable()) w = akimbo;
42
player1->weaponswitch(w);
46
void selectweaponi(int w)
48
if(player1->state == CS_ALIVE && w >= 0 && w < NUMGUNS)
50
selectweapon(player1->weapons[w]);
54
void shiftweapon(int s)
56
if(player1->state == CS_ALIVE)
58
if(!player1->weaponsel->deselectable()) return;
60
weapon *curweapon = player1->weaponsel;
61
weapon *akimbo = player1->weapons[GUN_AKIMBO];
63
// collect available weapons
64
vector<weapon *> availweapons;
67
weapon *w = player1->weapons[i];
69
if(w->selectable() || w==curweapon || (w->type==GUN_PISTOL && player1->akimbo))
75
// replace pistol by akimbo
78
availweapons.removeobj(akimbo); // and remove initial akimbo
79
int pistolidx = availweapons.find(player1->weapons[GUN_PISTOL]);
80
if(pistolidx>=0) availweapons[pistolidx] = akimbo; // insert at pistols position
81
if(curweapon->type==GUN_PISTOL) curweapon = akimbo; // fix selection
84
// detect the next weapon
85
int num = availweapons.length();
86
int curidx = availweapons.find(curweapon);
87
if(!num || curidx<0) return;
88
int idx = (curidx+s) % num;
90
weapon *next = availweapons[idx];
91
if(next->type!=player1->weaponsel->type) // different weapon
96
else if(player1->isspectating()) updatefollowplayer(s);
99
int currentprimary() { return player1->primweap->type; }
100
int prevweapon() { return player1->prevweaponsel->type; }
101
int curweapon() { return player1->weaponsel->type; }
103
int magcontent(int w) { if(w >= 0 && w < NUMGUNS) return player1->weapons[w]->mag; else return -1;}
104
int magreserve(int w) { if(w >= 0 && w < NUMGUNS) return player1->weapons[w]->ammo; else return -1;}
106
COMMANDN(weapon, selectweaponi, ARG_1INT);
107
COMMAND(shiftweapon, ARG_1INT);
108
COMMAND(currentprimary, ARG_IVAL);
109
COMMAND(prevweapon, ARG_IVAL);
110
COMMAND(curweapon, ARG_IVAL);
111
COMMAND(magcontent, ARG_1EXP);
112
COMMAND(magreserve, ARG_1EXP);
114
void tryreload(playerent *p)
116
if(!p || p->state!=CS_ALIVE || p->weaponsel->reloading || p->weaponchanging) return;
117
p->weaponsel->reload();
120
void selfreload() { tryreload(player1); }
121
COMMANDN(reload, selfreload, ARG_NONE);
123
void createrays(vec &from, vec &to) // create random spread of rays for the shotgun
125
float f = to.dist(from)*SGSPREAD/1000;
128
#define RNDD (rnd(101)-50)*f
129
vec r(RNDD, RNDD, RNDD);
136
static inline bool intersectbox(const vec &o, const vec &rad, const vec &from, const vec &to, vec *end) // if lineseg hits entity bounding box
147
float c2 = v.squaredlen();
162
&& p->z >= o.z-rad.z)
170
static inline bool intersectsphere(const vec &from, const vec &to, vec center, float radius, float &dist)
175
float v = center.dot(ray),
176
inside = radius*radius - center.squaredlen();
177
if(inside < 0 && v < 0) return false;
178
float raysq = ray.squaredlen(), d = inside*raysq + v*v;
179
if(d < 0) return false;
180
dist = (v - sqrtf(d)) / raysq;
181
return dist >= 0 && dist <= 1;
184
static inline bool intersectcylinder(const vec &from, const vec &to, const vec &start, const vec &end, float radius, float &dist)
186
vec d(end), m(from), n(to);
193
if(md < 0 && md + nd < 0) return false;
194
if(md > dd && md + nd > dd) return false;
195
float nn = n.squaredlen(),
198
k = m.squaredlen() - radius*radius,
202
if(c > 0) return false;
203
if(md < 0) dist = -mn / nn;
204
else if(md > dd) dist = (nd - mn) / nn;
208
float b = dd*mn - nd*md,
210
if(discrim < 0) return false;
211
dist = (-b - sqrtf(discrim)) / a;
212
float offset = md + dist*nd;
215
if(nd < 0) return false;
217
if(k + dist*(2*mn + dist*nn) > 0) return false;
221
if(nd >= 0) return false;
222
dist = (dd - md) / nd;
223
if(k + dd - 2*md + dist*(2*(mn-nd) + dist*nn) > 0) return false;
225
return dist >= 0 && dist <= 1;
228
int intersect(playerent *d, const vec &from, const vec &to, vec *end)
233
if(intersectsphere(from, to, d->head, HEADSIZE, dist))
235
if(end) (*end = to).sub(from).mul(dist).add(from);
239
float y = d->yaw*RAD, p = (d->pitch/4+90)*RAD, c = cosf(p);
240
vec bottom(d->o), top(sinf(y)*c, -cosf(y)*c, sinf(p));
241
bottom.z -= d->eyeheight;
242
top.mul(d->eyeheight + d->aboveeye).add(bottom);
243
if(intersectcylinder(from, to, bottom, top, d->radius, dist))
245
if(end) (*end = to).sub(from).mul(dist).add(from);
251
const float eyeheight = d->eyeheight;
253
o.z += (d->aboveeye - eyeheight)/2;
254
return intersectbox(o, vec(d->radius, d->radius, (d->aboveeye + eyeheight)/2), from, to, end) ? 1 : 0;
258
bool intersect(entity *e, const vec &from, const vec &to, vec *end)
260
mapmodelinfo &mmi = getmminfo(e->attr2);
261
if(!&mmi || !mmi.h) return false;
263
float lo = float(S(e->x, e->y)->floor+mmi.zoff+e->attr3);
264
return intersectbox(vec(e->x, e->y, lo+mmi.h/2.0f), vec(mmi.rad, mmi.rad, mmi.h/2.0f), from, to, end);
267
playerent *intersectclosest(const vec &from, const vec &to, playerent *at, int &hitzone, bool aiming = true)
269
playerent *best = NULL;
270
float bestdist = 1e16f;
272
if(at!=player1 && player1->state==CS_ALIVE && (zone = intersect(player1, from, to)))
275
bestdist = at->o.dist(player1->o);
280
playerent *o = players[i];
281
if(!o || o==at || (o->state!=CS_ALIVE && (aiming || (o->state!=CS_EDITING && o->state!=CS_LAGGED)))) continue;
282
float dist = at->o.dist(o->o);
283
if(dist < bestdist && (zone = intersect(o, from, to)))
293
playerent *playerincrosshair()
295
if(camera1->type == ENT_PLAYER || (camera1->type == ENT_CAMERA && player1->spectatemode == SM_DEATHCAM))
298
return intersectclosest(camera1->o, worldpos, (playerent *)camera1, hitzone, false);
303
void damageeffect(int damage, playerent *d)
305
particle_splash(3, damage/10, 1000, d->o);
311
void hit(int damage, playerent *d, playerent *at, const vec &vel, int gun, bool gib, int info)
313
if(d==player1 || d->type==ENT_BOT || !m_mp(gamemode)) d->hitpush(damage, vel, at, gun);
315
if(!m_mp(gamemode)) dodamage(damage, d, at, gib);
318
hitmsg &h = hits.add();
319
h.target = d->clientnum;
320
h.lifesequence = d->lifesequence;
324
h.dir = ivec(0, 0, 0);
325
d->damageroll(damage);
326
updatedmgindicator(at->o);
328
damageeffect(damage, d);
329
playsound(S_PAIN6, SP_HIGH);
333
h.dir = ivec(int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF));
334
damageeffect(damage, d);
335
playsound(S_PAIN1+rnd(5), d);
340
void hitpush(int damage, playerent *d, playerent *at, vec &from, vec &to, int gun, bool gib, int info)
345
hit(damage, d, at, v, gun, gib, info);
348
float expdist(playerent *o, vec &dir, const vec &v)
351
middle.z += (o->aboveeye-o->eyeheight)/2;
352
float dist = middle.dist(v, dir);
358
void radialeffect(playerent *o, vec &v, int qdam, playerent *at, int gun)
360
if(o->state!=CS_ALIVE) return;
362
float dist = expdist(o, dir, v);
365
int damage = (int)(qdam*(1-dist/EXPDAMRAD));
366
hit(damage, o, at, dir, gun, true, int(dist*DMF));
370
vector<bounceent *> bounceents;
372
void removebounceents(playerent *owner)
374
loopv(bounceents) if(bounceents[i]->owner==owner) { delete bounceents[i]; bounceents.remove(i--); }
377
void movebounceents()
379
loopv(bounceents) if(bounceents[i])
381
bounceent *p = bounceents[i];
382
if((p->bouncetype==BT_NADE || p->bouncetype==BT_GIB) && p->applyphysics()) movebounceent(p, 1, false);
383
if(!p->isalive(lastmillis))
387
bounceents.remove(i--);
392
void clearbounceents()
395
else if(multiplayer(false)) bounceents.add((bounceent *)player1);
396
loopv(bounceents) if(bounceents[i]) { delete bounceents[i]; bounceents.remove(i--); }
399
void renderbounceents()
403
bounceent *p = bounceents[i];
408
int anim = ANIM_MAPMODEL, basetime = 0;
409
switch(p->bouncetype)
412
s_strcpy(model, "weapons/grenade/static");
417
uint n = (((4*(uint)(size_t)p)+(uint)p->timetolive)%3)+1;
418
s_sprintf(model)("misc/gib0%u", n);
419
int t = lastmillis-p->millis;
420
if(t>p->timetolive-2000)
423
basetime = p->millis+p->timetolive-2000;
424
t -= p->timetolive-2000;
425
o.z -= t*t/4000000000.0f*t;
431
rendermodel(model, anim|ANIM_LOOP|ANIM_DYNALLOC, 0, 1.1f, o, p->yaw+90, p->pitch, 0, basetime);
436
VARP(gibnum, 0, 6, 1000);
437
VARP(gibttl, 0, 7000, 60000);
438
VARP(gibspeed, 1, 30, 100);
440
void addgib(playerent *d)
442
if(!d || !gib || !gibttl) return;
447
bounceent *p = bounceents.add(new bounceent);
449
p->millis = lastmillis;
450
p->timetolive = gibttl+rnd(10)*100;
451
p->bouncetype = BT_GIB;
454
p->o.z -= d->aboveeye;
455
p->inwater = hdr.waterlevel>p->o.z;
457
p->yaw = (float)rnd(360);
458
p->pitch = (float)rnd(360);
463
const float angle = (float)rnd(360);
464
const float speed = (float)gibspeed;
466
p->vel.x = sinf(RAD*angle)*rnd(1000)/1000.0f;
467
p->vel.y = cosf(RAD*angle)*rnd(1000)/1000.0f;
468
p->vel.z = rnd(1000)/1000.0f;
469
p->vel.mul(speed/100.0f);
475
void shorten(vec &from, vec &to, vec &target)
477
target.sub(from).normalize().mul(from.dist(to)).add(from);
480
void raydamage(vec &from, vec &to, playerent *d)
482
int dam = d->weaponsel->info.damage;
486
if(d->weaponsel->type==GUN_SHOTGUN)
489
playerent *cl = NULL;
492
bool raysleft = false;
495
loop(r, SGRAYS) if((done&(1<<r))==0 && (cl = intersectclosest(from, sg[r], d, hitzone)))
502
shorten(from, o->o, sg[r]);
504
else raysleft = true;
506
if(hitrays) hitpush(hitrays*dam, o, d, from, to, d->weaponsel->type, false, hitrays);
510
else if((o = intersectclosest(from, to, d, hitzone)))
513
if(d->weaponsel->type==GUN_KNIFE) gib = true;
514
else if(d==player1 && d->weaponsel->type==GUN_SNIPER && hitzone==2)
520
hitpush(dam, o, d, from, to, d->weaponsel->type, gib, gib ? 1 : 0);
521
shorten(from, o->o, to);
527
weapon::weapon(struct playerent *owner, int type) : type(type), owner(owner), info(guns[type]),
528
ammo(owner->ammo[type]), mag(owner->mag[type]), gunwait(owner->gunwait[type]), reloading(0)
532
const int weapon::weaponchangetime = 400;
533
const float weapon::weaponbeloweye = 0.2f;
535
int weapon::flashtime() const { return max((int)info.attackdelay, 120)/4; }
537
void weapon::sendshoot(vec &from, vec &to)
539
if(owner!=player1) return;
540
addmsg(SV_SHOOT, "ri2i6iv", lastmillis, owner->weaponsel->type,
541
(int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
542
(int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
543
hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
546
bool weapon::modelattacking()
548
int animtime = min(owner->gunwait[owner->weaponsel->type], (int)owner->weaponsel->info.attackdelay);
549
if(lastmillis - owner->lastaction < animtime) return true;
553
void weapon::attacksound()
555
if(info.sound == S_NULL) return;
556
bool local = (owner == player1);
557
playsound(info.sound, owner, local ? SP_HIGH : SP_NORMAL);
560
bool weapon::reload()
562
if(mag>=info.magsize || ammo<=0) return false;
563
updatelastaction(owner);
564
reloading = lastmillis;
565
gunwait += info.reloadtime;
567
int numbullets = min(info.magsize - mag, ammo);
571
bool local = (player1 == owner);
572
if(owner->type==ENT_BOT) playsound(info.reload, owner);
573
else playsoundc(info.reload);
574
if(local) addmsg(SV_RELOAD, "ri2", lastmillis, owner->weaponsel->type);
578
void weapon::renderstats()
581
sprintf(gunstats, "%i/%i", mag, ammo);
582
draw_text(gunstats, 690, 827);
585
//VAR(recoiltest, 0, 0, 1); // FIXME ON RELEASE
588
VAR(recoilincrease, 1, 2, 10);
589
VAR(recoilbase, 0, 40, 1000);
590
VAR(maxrecoil, 0, 1000, 1000);
592
void weapon::attackphysics(vec &from, vec &to) // physical fx to the owner
594
const guninfo &g = info;
596
float dist = to.dist(from, unitv);
598
int spread = dynspread();
599
float recoil = dynrecoil()*-0.01f;
604
#define RNDD (rnd(spread)-spread/2)*f
605
vec r(RNDD, RNDD, RNDD);
610
owner->vel.add(vec(unitv).mul(recoil/dist).mul(owner->crouching ? 0.75 : 1.0f));
612
int numshots = info.isauto ? shots : 1;
615
owner->pitchvel = min(powf(numshots/(float)(recoilincrease), 2.0f)+(float)(recoilbase)/10.0f, (float)(maxrecoil)/10.0f);
619
owner->pitchvel = min(powf(numshots/(float)(g.recoilincrease), 2.0f)+(float)(g.recoilbase)/10.0f, (float)(g.maxrecoil)/10.0f);
623
void weapon::renderhudmodel(int lastaction, int index)
626
float dist = worldpos.dist(owner->o, unitv);
630
if(!intermission) wm.calcmove(unitv, lastaction);
631
s_sprintfd(path)("weapons/%s", info.modelname);
632
bool emit = (wm.anim&ANIM_INDEX)==ANIM_GUN_SHOOT && (lastmillis - lastaction) < flashtime();
633
rendermodel(path, wm.anim|ANIM_DYNALLOC|(index ? ANIM_MIRROR : 0)|(emit ? ANIM_PARTICLE : 0), 0, -1, wm.pos, player1->yaw+90, player1->pitch+wm.k_rot, 40.0f, wm.basetime, NULL, NULL, 1.28f);
636
void weapon::updatetimers()
638
if(gunwait) gunwait = max(gunwait - (lastmillis-owner->lastaction), 0);
641
void weapon::onselecting()
643
updatelastaction(owner);
644
playsound(S_GUNCHANGE, owner == player1? SP_HIGH : SP_NORMAL);
647
void weapon::renderhudmodel() { renderhudmodel(owner->lastaction); }
648
void weapon::renderaimhelp(bool teamwarning) { drawcrosshair(owner, teamwarning ? CROSSHAIR_TEAMMATE : CROSSHAIR_DEFAULT); }
649
int weapon::dynspread() { return info.spread; }
650
float weapon::dynrecoil() { return info.recoil; }
651
bool weapon::selectable() { return this != owner->weaponsel && owner->state == CS_ALIVE && !owner->weaponchanging; }
652
bool weapon::deselectable() { return !reloading; }
654
void weapon::equipplayer(playerent *pl)
657
pl->weapons[GUN_ASSAULT] = new assaultrifle(pl);
658
pl->weapons[GUN_GRENADE] = new grenades(pl);
659
pl->weapons[GUN_KNIFE] = new knife(pl);
660
pl->weapons[GUN_PISTOL] = new pistol(pl);
661
pl->weapons[GUN_SHOTGUN] = new shotgun(pl);
662
pl->weapons[GUN_SNIPER] = new sniperrifle(pl);
663
pl->weapons[GUN_SUBGUN] = new subgun(pl);
664
pl->weapons[GUN_AKIMBO] = new akimbo(pl);
665
pl->selectweapon(GUN_ASSAULT);
666
pl->setprimary(GUN_ASSAULT);
667
pl->setnextprimary(GUN_ASSAULT);
670
bool weapon::valid(int id) { return id>=0 && id<NUMGUNS; }
674
enum { NS_NONE, NS_ACTIVATED = 0, NS_THROWED, NS_EXPLODED };
676
grenadeent::grenadeent(playerent *owner, int millis)
680
local = owner==player1;
681
bounceent::owner = owner;
682
bounceent::millis = lastmillis;
683
timetolive = 2000-millis;
684
bouncetype = BT_NADE;
687
distsincebounce = 0.0f;
690
grenadeent::~grenadeent()
692
if(owner && owner->weapons[GUN_GRENADE]) owner->weapons[GUN_GRENADE]->removebounceent(this);
695
void grenadeent::explode()
697
if(nadestate!=NS_ACTIVATED && nadestate!=NS_THROWED ) return;
698
nadestate = NS_EXPLODED;
700
hits.setsizenodelete(0);
703
addmsg(SV_EXPLODE, "ri3iv", lastmillis, GUN_GRENADE, millis, // fixme
704
hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
705
playsound(S_FEXPLODE, &o);
708
void grenadeent::splash()
710
particle_splash(0, 50, 300, o);
711
particle_fireball(5, o);
713
adddynlight(NULL, o, 16, 200, 100, 255, 255, 224);
714
adddynlight(NULL, o, 16, 600, 600, 192, 160, 128);
715
if(owner != player1) return;
716
int damage = guns[GUN_GRENADE].damage;
717
radialeffect(owner, o, damage, owner, GUN_GRENADE);
720
playerent *p = players[i];
722
radialeffect(p, o, damage, owner, GUN_GRENADE);
726
void grenadeent::activate(const vec &from, const vec &to)
728
if(nadestate!=NS_NONE) return;
729
nadestate = NS_ACTIVATED;
733
addmsg(SV_SHOOT, "ri2i6i", millis, owner->weaponsel->type,
734
(int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
735
(int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
737
playsound(S_GRENADEPULL, SP_HIGH);
741
void grenadeent::_throw(const vec &from, const vec &vel)
743
if(nadestate!=NS_ACTIVATED) return;
744
nadestate = NS_THROWED;
748
inwater = hdr.waterlevel>o.z;
752
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);
753
playsound(S_GRENADETHROW, SP_HIGH);
755
else playsound(S_GRENADETHROW, owner);
758
void grenadeent::moveoutsidebbox(const vec &direction, playerent *boundingbox)
762
inwater = hdr.waterlevel>o.z;
764
boundingbox->cancollide = false;
765
loopi(10) moveplayer(this, 10, true, 10);
766
boundingbox->cancollide = true;
769
void grenadeent::destroy() { explode(); }
770
bool grenadeent::applyphysics() { return nadestate==NS_THROWED; }
772
void grenadeent::oncollision()
774
if(distsincebounce>=1.5f) playsound(S_GRENADEBOUNCE1+rnd(2), &o);
775
distsincebounce = 0.0f;
778
void grenadeent::onmoved(const vec &dist)
780
distsincebounce += dist.magnitude();
785
grenades::grenades(playerent *owner) : weapon(owner, GUN_GRENADE), inhandnade(NULL), throwwait((13*1000)/40), throwmillis(0), state(GST_NONE) {}
787
int grenades::flashtime() const { return 0; }
789
bool grenades::busy() { return state!=GST_NONE; }
791
bool grenades::attack(vec &targ)
793
int attackmillis = lastmillis-owner->lastaction;
796
bool waitdone = attackmillis>=gunwait;
797
if(waitdone) gunwait = reloading = 0;
802
if(waitdone && owner->attacking && this==owner->weaponsel) activatenade(to); // activate
808
if(!owner->attacking || this!=owner->weaponsel) thrownade(); // throw
809
else if(!inhandnade->isalive(lastmillis)) dropnade(); // drop & have fun
814
if(attackmillis >= throwwait) // throw done
817
if(!mag && this==owner->weaponsel) // switch to primary immediately
819
owner->weaponchanging = lastmillis-1-(weaponchangetime/2);
820
owner->nextweaponsel = owner->weaponsel = owner->primweap;
829
void grenades::attackfx(const vec &from, const vec &to, int millis) // other player's grenades
831
throwmillis = lastmillis-millis;
832
if(millis == 0) playsound(S_GRENADEPULL, owner); // activate
833
else if(millis > 0) // throw
835
grenadeent *g = new grenadeent(owner, millis);
841
int grenades::modelanim()
843
if(state == GST_THROWING) return ANIM_GUN_THROW;
846
int animtime = min(gunwait, (int)info.attackdelay);
847
if(state == GST_INHAND || lastmillis - owner->lastaction < animtime) return ANIM_GUN_SHOOT;
849
return ANIM_GUN_IDLE;
852
void grenades::activatenade(const vec &to)
857
inhandnade = new grenadeent(owner);
858
bounceents.add(inhandnade);
860
updatelastaction(owner);
862
gunwait = info.attackdelay;
863
owner->lastattackweapon = this;
865
inhandnade->activate(owner->o, to);
868
void grenades::thrownade()
870
if(!inhandnade) return;
871
const float speed = cosf(RAD*owner->pitch);
872
vec vel(sinf(RAD*owner->yaw)*speed, -cosf(RAD*owner->yaw)*speed, sinf(RAD*owner->pitch));
877
void grenades::thrownade(const vec &vel)
879
inhandnade->moveoutsidebbox(vel, owner);
880
inhandnade->_throw(inhandnade->o, vel);
883
throwmillis = lastmillis;
884
updatelastaction(owner);
885
state = GST_THROWING;
886
if(this==owner->weaponsel) owner->attacking = false;
889
void grenades::dropnade()
895
void grenades::renderstats()
898
sprintf(gunstats, "%i", mag);
899
draw_text(gunstats, 690, 827);
902
bool grenades::selectable() { return weapon::selectable() && state != GST_INHAND && mag; }
903
void grenades::reset() { throwmillis = 0; state = GST_NONE; }
905
void grenades::onselecting() { reset(); playsound(S_GUNCHANGE); }
906
void grenades::onownerdies()
909
if(owner==player1 && inhandnade) dropnade();
912
void grenades::removebounceent(bounceent *b)
914
if(b == inhandnade) { inhandnade = NULL; reset(); }
919
gun::gun(playerent *owner, int type) : weapon(owner, type) {}
921
bool gun::attack(vec &targ)
923
int attackmillis = lastmillis-owner->lastaction;
924
if(timebalance < gunwait) attackmillis += timebalance;
925
if(attackmillis<gunwait) return false;
926
timebalance = gunwait ? attackmillis - gunwait : 0;
927
gunwait = reloading = 0;
929
if(!owner->attacking)
936
updatelastaction(owner);
939
playsoundc(S_NOAMMO);
941
owner->lastattackweapon = NULL;
947
owner->lastattackweapon = this;
950
if(!info.isauto) owner->attacking = false;
954
from.z -= weaponbeloweye;
956
attackphysics(from, to);
958
hits.setsizenodelete(0);
959
raydamage(from, to, owner);
960
attackfx(from, to, 0);
962
gunwait = info.attackdelay;
969
void gun::attackfx(const vec &from, const vec &to, int millis)
971
addbullethole(owner, from, to);
972
addshotline(owner, from, to);
973
particle_splash(0, 5, 250, to);
974
adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
978
int gun::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT|ANIM_LOOP : ANIM_GUN_IDLE; }
979
void gun::checkautoreload() { if(autoreload && owner==player1 && !mag) reload(); }
984
shotgun::shotgun(playerent *owner) : gun(owner, GUN_SHOTGUN) {}
986
bool shotgun::attack(vec &targ)
989
from.z -= weaponbeloweye;
990
createrays(from, targ);
991
return gun::attack(targ);
994
void shotgun::attackfx(const vec &from, const vec &to, int millis)
996
loopi(SGRAYS) particle_splash(0, 5, 200, sg[i]);
997
if(addbullethole(owner, from, to))
999
int holes = 3+rnd(5);
1000
loopi(holes) addbullethole(owner, from, sg[i], 0, false);
1002
adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
1006
bool shotgun::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
1011
subgun::subgun(playerent *owner) : gun(owner, GUN_SUBGUN) {}
1012
bool subgun::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
1017
sniperrifle::sniperrifle(playerent *owner) : gun(owner, GUN_SNIPER), scoped(false) {}
1019
void sniperrifle::attackfx(const vec &from, const vec &to, int millis)
1021
addbullethole(owner, from, to);
1022
addshotline(owner, from, to);
1023
particle_splash(0, 50, 200, to);
1024
particle_trail(1, 500, from, to);
1025
adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
1029
bool sniperrifle::reload()
1031
bool r = weapon::reload();
1032
if(owner==player1 && r) scoped = false;
1036
#define SCOPESETTLETIME 100
1037
int sniperrifle::dynspread()
1041
int scopetime = lastmillis - scoped_since;
1042
if(scopetime > SCOPESETTLETIME)
1045
return max((info.spread * (SCOPESETTLETIME - scopetime)) / SCOPESETTLETIME, 1);
1049
float sniperrifle::dynrecoil() { return scoped && lastmillis - scoped_since > SCOPESETTLETIME ? info.recoil / 3 : info.recoil; }
1050
bool sniperrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
1051
void sniperrifle::onselecting() { weapon::onselecting(); scoped = false; }
1052
void sniperrifle::ondeselecting() { scoped = false; }
1053
void sniperrifle::onownerdies() { scoped = false; }
1054
void sniperrifle::renderhudmodel() { if(!scoped) weapon::renderhudmodel(); }
1056
void sniperrifle::renderaimhelp(bool teamwarning)
1058
if(scoped) drawscope();
1059
if(scoped || teamwarning) drawcrosshair(owner, teamwarning ? CROSSHAIR_TEAMMATE : CROSSHAIR_SCOPE, NULL, 24.0f);
1062
void sniperrifle::setscope(bool enable)
1064
if(this == owner->weaponsel && !reloading && owner->state == CS_ALIVE)
1066
if(scoped == false && enable == true) scoped_since = lastmillis;
1074
assaultrifle::assaultrifle(playerent *owner) : gun(owner, GUN_ASSAULT) {}
1076
int assaultrifle::dynspread() { return shots > 3 ? 70 : info.spread; }
1077
float assaultrifle::dynrecoil() { return info.recoil + (rnd(8)*-0.01f); }
1078
bool assaultrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
1083
pistol::pistol(playerent *owner) : gun(owner, GUN_PISTOL) {}
1084
bool pistol::selectable() { return weapon::selectable() && !m_nopistol; }
1089
akimbo::akimbo(playerent *owner) : gun(owner, GUN_AKIMBO), akimbomillis(0)
1091
akimbolastaction[0] = akimbolastaction[1] = 0;
1094
bool akimbo::attack(vec &targ)
1096
if(gun::attack(targ))
1098
akimbolastaction[akimboside?1:0] = lastmillis;
1099
akimboside = !akimboside;
1105
void akimbo::onammopicked()
1107
akimbomillis = lastmillis + 30000;
1110
if(owner->weaponsel->type!=GUN_SNIPER && owner->weaponsel->type!=GUN_GRENADE) owner->weaponswitch(this);
1111
addmsg(SV_AKIMBO, "ri", lastmillis);
1115
void akimbo::onselecting()
1118
akimbolastaction[0] = akimbolastaction[1] = lastmillis;
1121
bool akimbo::selectable() { return weapon::selectable() && !m_nopistol && owner->akimbo; }
1122
void akimbo::updatetimers() { weapon::updatetimers(); /*loopi(2) akimbolastaction[i] = lastmillis;*/ }
1123
void akimbo::reset() { akimbolastaction[0] = akimbolastaction[1] = akimbomillis = 0; akimboside = false; }
1125
void akimbo::renderhudmodel()
1127
weapon::renderhudmodel(akimbolastaction[0], 0);
1128
weapon::renderhudmodel(akimbolastaction[1], 1);
1131
bool akimbo::timerout() { return akimbomillis && akimbomillis <= lastmillis; }
1136
knife::knife(playerent *owner) : weapon(owner, GUN_KNIFE) {}
1138
int knife::flashtime() const { return 0; }
1140
bool knife::attack(vec &targ)
1142
int attackmillis = lastmillis-owner->lastaction;
1143
if(attackmillis<gunwait) return false;
1144
gunwait = reloading = 0;
1146
if(!owner->attacking) return false;
1147
updatelastaction(owner);
1149
owner->lastattackweapon = this;
1150
owner->attacking = false;
1152
vec from = owner->o;
1154
from.z -= weaponbeloweye;
1157
float dist = to.dist(from, unitv);
1159
unitv.mul(3); // punch range
1163
hits.setsizenodelete(0);
1164
raydamage(from, to, owner);
1165
attackfx(from, to, 0);
1166
sendshoot(from, to);
1167
gunwait = info.attackdelay;
1171
int knife::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT : ANIM_GUN_IDLE; }
1173
void knife::drawstats() {}
1174
void knife::attackfx(const vec &from, const vec &to, int millis) { attacksound(); }
1175
void knife::renderstats() { }
1178
void setscope(bool enable)
1180
if(player1->weaponsel->type != GUN_SNIPER) return;
1181
sniperrifle *sr = (sniperrifle *)player1->weaponsel;
1182
sr->setscope(enable);
1185
COMMAND(setscope, ARG_1INT);
1188
void shoot(playerent *p, vec &targ)
1190
if(p->state==CS_DEAD || p->weaponchanging) return;
1191
weapon *weap = p->weaponsel;
1197
weapon *bweap = player1->weapons[i];
1198
if(bweap != weap && bweap->busy()) bweap->attack(targ);
1207
akimbo &a = *((akimbo *)player1->weapons[GUN_AKIMBO]);
1210
weapon &p = *player1->weapons[GUN_PISTOL];
1211
player1->akimbo = false;
1213
// transfer ammo to pistol
1214
p.mag = min((int)p.info.magsize, max(a.mag, p.mag));
1215
p.ammo = max(p.ammo, p.ammo);
1216
// fix akimbo magcontent
1219
if(player1->weaponsel->type==GUN_AKIMBO) player1->weaponswitch(&p);
1220
playsoundc(S_AKIMBOOUT);