summaryrefslogtreecommitdiff
path: root/src/fpsgame/weapon.cpp
diff options
context:
space:
mode:
authorxolatile2025-08-04 22:53:42 +0200
committerxolatile2025-08-04 22:53:42 +0200
commitd309df4ce4d8ad0ed995a8e1c4267412a7782021 (patch)
tree999ca8d785ecc1681e5eb7538ce2e6a18d244fa5 /src/fpsgame/weapon.cpp
parent29d613d9cb65a0faa7e3f80e75bea0b6d910cb9a (diff)
downloadxolatile-badassbug-d309df4ce4d8ad0ed995a8e1c4267412a7782021.tar.xz
xolatile-badassbug-d309df4ce4d8ad0ed995a8e1c4267412a7782021.tar.zst
Bunch of small changes...
Diffstat (limited to 'src/fpsgame/weapon.cpp')
-rw-r--r--src/fpsgame/weapon.cpp1905
1 files changed, 952 insertions, 953 deletions
diff --git a/src/fpsgame/weapon.cpp b/src/fpsgame/weapon.cpp
index d911aba..1bdb302 100644
--- a/src/fpsgame/weapon.cpp
+++ b/src/fpsgame/weapon.cpp
@@ -3,975 +3,974 @@
namespace game
{
- static const int OFFSETMILLIS = 500;
- vec rays[MAXRAYS];
-
- struct hitmsg
- {
- int target, lifesequence, info1, info2;
- ivec dir;
- };
- vector<hitmsg> hits;
-
- ICOMMAND(getweapon, "", (), intret(player1->gunselect));
-
- void gunselect(int gun, fpsent *d)
- {
- if(gun!=d->gunselect)
- {
- addmsg(N_GUNSELECT, "rci", d, gun);
- playsound(S_WEAPLOAD, d == player1 ? NULL : &d->o);
- }
- d->gunselect = gun;
- }
-
- void nextweapon(int dir, bool force = false)
- {
- if(player1->state!=CS_ALIVE) return;
- dir = (dir < 0 ? NUMGUNS-1 : 1);
- int gun = player1->gunselect;
- loopi(NUMGUNS)
- {
- gun = (gun + dir)%NUMGUNS;
- if(force || player1->ammo[gun]) break;
- }
- if(gun != player1->gunselect) gunselect(gun, player1);
- else playsound(S_NOAMMO);
- }
- ICOMMAND(nextweapon, "ii", (int *dir, int *force), nextweapon(*dir, *force!=0));
-
- int getweapon(const char *name)
- {
- const char *abbrevs[] = { "FI", "SG", "CG", "RL", "RI", "GL", "PI" };
- if(isdigit(name[0])) return parseint(name);
- else loopi(sizeof(abbrevs)/sizeof(abbrevs[0])) if(!strcasecmp(abbrevs[i], name)) return i;
- return -1;
- }
-
- void setweapon(const char *name, bool force = false)
- {
- int gun = getweapon(name);
- if(player1->state!=CS_ALIVE || gun<GUN_FIST || gun>GUN_PISTOL) return;
- if(force || player1->ammo[gun]) gunselect(gun, player1);
- else playsound(S_NOAMMO);
- }
- ICOMMAND(setweapon, "si", (char *name, int *force), setweapon(name, *force!=0));
-
- void cycleweapon(int numguns, int *guns, bool force = false)
- {
- if(numguns<=0 || player1->state!=CS_ALIVE) return;
- int offset = 0;
- loopi(numguns) if(guns[i] == player1->gunselect) { offset = i+1; break; }
- loopi(numguns)
- {
- int gun = guns[(i+offset)%numguns];
- if(gun>=0 && gun<NUMGUNS && (force || player1->ammo[gun]))
- {
- gunselect(gun, player1);
- return;
- }
- }
- playsound(S_NOAMMO);
- }
- ICOMMAND(cycleweapon, "V", (tagval *args, int numargs),
- {
- int numguns = min(numargs, 7);
- int guns[7];
- loopi(numguns) guns[i] = getweapon(args[i].getstr());
- cycleweapon(numguns, guns);
- });
-
- void weaponswitch(fpsent *d)
- {
- if(d->state!=CS_ALIVE) return;
- int s = d->gunselect;
- if (s!=GUN_CG && d->ammo[GUN_CG]) s = GUN_CG;
- else if(s!=GUN_RL && d->ammo[GUN_RL]) s = GUN_RL;
- else if(s!=GUN_SG && d->ammo[GUN_SG]) s = GUN_SG;
- else if(s!=GUN_RIFLE && d->ammo[GUN_RIFLE]) s = GUN_RIFLE;
- else if(s!=GUN_GL && d->ammo[GUN_GL]) s = GUN_GL;
- else if(s!=GUN_PISTOL && d->ammo[GUN_PISTOL]) s = GUN_PISTOL;
- else s = GUN_FIST;
-
- gunselect(s, d);
- }
-
- ICOMMAND(weapon, "V", (tagval *args, int numargs),
- {
- if(player1->state!=CS_ALIVE) return;
- loopi(7)
- {
- const char *name = i < numargs ? args[i].getstr() : "";
- if(name[0])
- {
- int gun = getweapon(name);
- if(gun >= GUN_FIST && gun <= GUN_PISTOL && gun != player1->gunselect && player1->ammo[gun]) { gunselect(gun, player1); return; }
- } else { weaponswitch(player1); return; }
- }
- playsound(S_NOAMMO);
- });
-
- void offsetray(const vec &from, const vec &to, int spread, float range, vec &dest)
- {
- vec offset;
- do offset = vec(rndscale(1), rndscale(1), rndscale(1)).sub(0.5f);
- while(offset.squaredlen() > 0.5f*0.5f);
- offset.mul((to.dist(from)/1024)*spread);
- offset.z /= 2;
- dest = vec(offset).add(to);
- if(dest != from)
- {
- vec dir = vec(dest).sub(from).normalize();
- raycubepos(from, dir, dest, range, RAY_CLIPMAT|RAY_ALPHAPOLY);
- }
- }
-
- void createrays(int gun, const vec &from, const vec &to) // create random spread of rays
- {
- loopi(guns[gun].rays) offsetray(from, to, guns[gun].spread, guns[gun].range, rays[i]);
- }
-
- enum { BNC_GRENADE };
-
- struct bouncer : physent
- {
- int lifetime, bounces;
- float lastyaw, roll;
- bool local;
- fpsent *owner;
- int bouncetype, variant;
- vec offset;
- int offsetmillis;
- float offsetheight;
- int id;
- entitylight light;
-
- bouncer() : bounces(0), roll(0), variant(0)
- {
- type = ENT_BOUNCE;
- }
-
- vec offsetpos()
- {
- vec pos(o);
- if(offsetmillis > 0)
- {
- pos.add(vec(offset).mul(offsetmillis/float(OFFSETMILLIS)));
- if(offsetheight >= 0) pos.z = max(pos.z, o.z - max(offsetheight - eyeheight, 0.0f));
- }
- return pos;
- }
-
- void limitoffset()
- {
- if(bouncetype == BNC_GRENADE && offsetmillis > 0 && offset.z < 0)
- offsetheight = raycube(vec(o.x + offset.x, o.y + offset.y, o.z), vec(0, 0, -1), -offset.z);
- else offsetheight = -1;
- }
- };
-
- vector<bouncer *> bouncers;
-
- vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d);
-
- void newbouncer(const vec &from, const vec &to, bool local, int id, fpsent *owner, int type, int lifetime, int speed, entitylight *light = NULL)
- {
- bouncer &bnc = *bouncers.add(new bouncer);
- bnc.o = from;
- bnc.radius = bnc.xradius = bnc.yradius = 1.5f;
- bnc.eyeheight = bnc.radius;
- bnc.aboveeye = bnc.radius;
- bnc.lifetime = lifetime;
- bnc.local = local;
- bnc.owner = owner;
- bnc.bouncetype = type;
- bnc.id = local ? lastmillis : id;
- if(light) bnc.light = *light;
-
- bnc.collidetype = COLLIDE_ELLIPSE_PRECISE;
-
- vec dir(to);
- dir.sub(from).safenormalize();
- bnc.vel = dir;
- bnc.vel.mul(speed);
-
- avoidcollision(&bnc, dir, owner, 0.1f);
-
- if(type==BNC_GRENADE)
- {
- bnc.offset = hudgunorigin(GUN_GL, from, to, owner);
- if(owner==followingplayer(player1) && !isthirdperson()) bnc.offset.sub(owner->o).rescale(16).add(owner->o);
- }
- else bnc.offset = from;
- bnc.offset.sub(bnc.o);
- bnc.offsetmillis = OFFSETMILLIS;
- bnc.limitoffset();
-
- bnc.resetinterp();
- }
-
- void bounced(physent *d, const vec &surface)
- {
- if(d->type != ENT_BOUNCE) return;
- bouncer *b = (bouncer *)d;
- if(b->bounces >= 2) return;
- b->bounces++;
- adddecal(DECAL_BLOOD, vec(b->o).sub(vec(surface).mul(b->radius)), surface, 2.96f/b->bounces, bvec(0x60, 0xFF, 0xFF), rnd(4));
- }
-
- void updatebouncers(int time)
- {
- loopv(bouncers)
- {
- bouncer &bnc = *bouncers[i];
- if(bnc.bouncetype==BNC_GRENADE && bnc.vel.magnitude() > 50.0f)
- {
- vec pos = bnc.offsetpos();
- regular_particle_splash(PART_SMOKE, 1, 150, pos, 0x404040, 2.4f, 50, -20);
- }
- vec old(bnc.o);
- bool stopped = false;
- if(bnc.bouncetype==BNC_GRENADE) stopped = bounce(&bnc, 0.6f, 0.5f, 0.8f) || (bnc.lifetime -= time)<0;
-
- if(stopped)
- {
- if(bnc.bouncetype==BNC_GRENADE)
- {
- int qdam = guns[GUN_GL].damage*(bnc.owner->quadmillis ? 4 : 1);
- hits.setsize(0);
- explode(bnc.local, bnc.owner, bnc.o, NULL, qdam, GUN_GL);
- adddecal(DECAL_SCORCH, bnc.o, vec(0, 0, 1), guns[GUN_GL].exprad/2);
- if(bnc.local)
- addmsg(N_EXPLODE, "rci3iv", bnc.owner, lastmillis-maptime, GUN_GL, bnc.id-maptime,
- hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
- }
- delete bouncers.remove(i--);
- }
- else
- {
- bnc.roll += old.sub(bnc.o).magnitude()/(4*RAD);
- bnc.offsetmillis = max(bnc.offsetmillis-time, 0);
- bnc.limitoffset();
- }
- }
- }
-
- void removebouncers(fpsent *owner)
- {
- loopv(bouncers) if(bouncers[i]->owner==owner) { delete bouncers[i]; bouncers.remove(i--); }
- }
-
- void clearbouncers() { bouncers.deletecontents(); }
-
- struct projectile
- {
- vec dir, o, to, offset;
- float speed;
- fpsent *owner;
- int gun;
- bool local;
- int offsetmillis;
- int id;
- entitylight light;
- };
- vector<projectile> projs;
-
- void clearprojectiles() { projs.shrink(0); }
-
- void newprojectile(const vec &from, const vec &to, float speed, bool local, int id, fpsent *owner, int gun)
- {
- projectile &p = projs.add();
- p.dir = vec(to).sub(from).safenormalize();
- p.o = from;
- p.to = to;
- p.offset = hudgunorigin(gun, from, to, owner);
- p.offset.sub(from);
- p.speed = speed;
- p.local = local;
- p.owner = owner;
- p.gun = gun;
- p.offsetmillis = OFFSETMILLIS;
- p.id = local ? lastmillis : id;
- }
-
- void removeprojectiles(fpsent *owner)
- {
- // can't use loopv here due to strange GCC optimizer bug
- int len = projs.length();
- loopi(len) if(projs[i].owner==owner) { projs.remove(i--); len--; }
- }
-
- VARP(blood, 0, 1, 1);
-
- void damageeffect(int damage, fpsent *d, bool thirdperson)
- {
- vec p = d->o;
- p.z += 0.6f*(d->eyeheight + d->aboveeye) - d->eyeheight;
- if(blood) particle_splash(PART_BLOOD, damage/10, 1000, p, 0x60FFFF, 2.96f);
- if(thirdperson)
- {
- defformatstring(ds, "%d", damage);
- particle_textcopy(d->abovehead(), ds, PART_TEXT, 2000, 0xFF4B19, 4.0f, -8);
- }
- }
-
- void spawnbouncer(const vec &p, const vec &vel, fpsent *d, int type, entitylight *light = NULL)
- {
- vec to(rnd(100)-50, rnd(100)-50, rnd(100)-50);
- if(to.iszero()) to.z += 1;
- to.normalize();
- to.add(p);
- newbouncer(p, to, true, 0, d, type, rnd(1000)+1000, rnd(100)+20, light);
- }
-
- void hit(int damage, dynent *d, fpsent *at, const vec &vel, int gun, float info1, int info2 = 1)
- {
- if(at==player1 && d!=at)
- {
- extern int hitsound;
- if(hitsound && lasthit != lastmillis) playsound(S_HIT);
- lasthit = lastmillis;
- }
-
- fpsent *f = (fpsent *)d;
-
- f->lastpain = lastmillis;
- if(at->type==ENT_PLAYER && !isteam(at->team, f->team)) at->totaldamage += damage;
-
- if(f->type==ENT_AI || !m_mp(gamemode) || f==at) f->hitpush(damage, vel, at, gun);
-
- pwhit(gun, damage);
-
- if(!m_mp(gamemode)) damaged(damage, f, at);
- else
- {
- hitmsg &h = hits.add();
- h.target = f->clientnum;
- h.lifesequence = f->lifesequence;
- h.info1 = int(info1*DMF);
- h.info2 = info2;
- h.dir = f==at ? ivec(0, 0, 0) : ivec(vec(vel).mul(DNF));
- if(at==player1)
- {
- damageeffect(damage, f);
- if(f==player1)
- {
- damageblend(damage);
- damagecompass(damage, at ? at->o : f->o);
- playsound(S_PAIN6);
- }
- else playsound(S_PAIN1+rnd(5), &f->o);
- }
- }
- }
-
- void hitpush(int damage, dynent *d, fpsent *at, vec &from, vec &to, int gun, int rays)
- {
- hit(damage, d, at, vec(to).sub(from).safenormalize(), gun, from.dist(to), rays);
- }
-
- float projdist(dynent *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(dynent *o, const vec &v, int qdam, fpsent *at, int gun)
- {
- if(o->state!=CS_ALIVE) return;
- vec dir;
- float dist = projdist(o, dir, v);
- if(dist<guns[gun].exprad)
- {
- int damage = (int)(qdam*(1-dist/EXP_DISTSCALE/guns[gun].exprad));
- if(o==at) damage /= EXP_SELFDAMDIV;
- hit(damage, o, at, dir, gun, dist);
- }
- }
-
- FVARP(explodebright, 0, 1, 1);
-
- void explode(bool local, fpsent *owner, const vec &v, dynent *safe, int damage, int gun)
- {
- particle_splash(PART_SPARK, 200, 300, v, 0xB49B4B, 0.24f);
- playsound(gun!=GUN_GL ? S_RLHIT : S_FEXPLODE, &v);
- int color = gun!=GUN_GL ? 0xFF8080 : 0x80FFFF;
- if((gun==GUN_RL || gun==GUN_GL) && explodebright < 1) color = vec::hexcolor(color).mul(explodebright).tohexcolor();
- particle_fireball(v, guns[gun].exprad, gun!=GUN_GL ? PART_EXPLOSION : PART_EXPLOSION_BLUE, gun!=GUN_GL ? -1 : int((guns[gun].exprad-4.0f)*15), color, 4.0f);
- if(gun==GUN_RL) adddynlight(v, 1.15f*guns[gun].exprad, vec(2, 1.5f, 1), 700, 100, 0, guns[gun].exprad/2, vec(1, 0.75f, 0.5f));
- else if(gun==GUN_GL) adddynlight(v, 1.15f*guns[gun].exprad, vec(0.5f, 1.5f, 2), 600, 100, 0, 8, vec(0.25f, 1, 1));
- else adddynlight(v, 1.15f*guns[gun].exprad, vec(2, 1.5f, 1), 700, 100);
- if(!local) return;
- int numdyn = numdynents();
- loopi(numdyn)
- {
- dynent *o = iterdynents(i);
- if(o->o.reject(v, o->radius + guns[gun].exprad) || o==safe) continue;
- radialeffect(o, v, damage, owner, gun);
- }
- }
-
- void projsplash(projectile &p, vec &v, dynent *safe, int damage)
- {
- if(guns[p.gun].part)
- {
- particle_splash(PART_SPARK, 100, 200, v, 0xB49B4B, 0.24f);
- playsound(S_FEXPLODE, &v);
- // no push?
- }
- else
- {
- explode(p.local, p.owner, v, safe, damage, GUN_RL);
- adddecal(DECAL_SCORCH, v, vec(p.dir).neg(), guns[p.gun].exprad/2);
- }
- }
-
- void explodeeffects(int gun, fpsent *d, bool local, int id)
- {
- if(local) return;
- switch(gun)
- {
- case GUN_RL:
- loopv(projs)
- {
- projectile &p = projs[i];
- if(p.gun == gun && p.owner == d && p.id == id && !p.local)
- {
- vec pos(p.o);
- pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
- explode(p.local, p.owner, pos, NULL, 0, GUN_RL);
- adddecal(DECAL_SCORCH, pos, vec(p.dir).neg(), guns[gun].exprad/2);
- projs.remove(i);
- break;
- }
- }
- break;
- case GUN_GL:
- loopv(bouncers)
- {
- bouncer &b = *bouncers[i];
- if(b.bouncetype == BNC_GRENADE && b.owner == d && b.id == id && !b.local)
- {
- vec pos = b.offsetpos();
- explode(b.local, b.owner, pos, NULL, 0, GUN_GL);
- adddecal(DECAL_SCORCH, pos, vec(0, 0, 1), guns[gun].exprad/2);
- delete bouncers.remove(i);
- break;
- }
- }
- break;
- default:
- break;
- }
- }
-
- bool projdamage(dynent *o, projectile &p, vec &v, int qdam)
- {
- if(o->state!=CS_ALIVE) return false;
- if(!intersect(o, p.o, v)) return false;
- projsplash(p, v, o, qdam);
- vec dir;
- projdist(o, dir, v);
- hit(qdam, o, p.owner, dir, p.gun, 0);
- return true;
- }
-
- void updateprojectiles(int time)
- {
- loopv(projs)
- {
- projectile &p = projs[i];
- p.offsetmillis = max(p.offsetmillis-time, 0);
- int qdam = guns[p.gun].damage*(p.owner->quadmillis ? 4 : 1);
- vec dv;
- float dist = p.to.dist(p.o, dv);
- dv.mul(time/max(dist*1000/p.speed, float(time)));
- vec v = vec(p.o).add(dv);
- bool exploded = false;
- hits.setsize(0);
- if(p.local)
- {
- vec halfdv = vec(dv).mul(0.5f), bo = vec(p.o).add(halfdv);
- float br = max(fabs(halfdv.x), fabs(halfdv.y)) + 1;
- loopj(numdynents())
- {
- dynent *o = iterdynents(j);
- if(p.owner==o || o->o.reject(bo, o->radius + br)) continue;
- if(projdamage(o, p, v, qdam)) { exploded = true; break; }
- }
- }
- if(!exploded)
- {
- if(dist<4)
- {
- if(p.o!=p.to) // if original target was moving, reevaluate endpoint
- {
- if(raycubepos(p.o, p.dir, p.to, 0, RAY_CLIPMAT|RAY_ALPHAPOLY)>=4) continue;
- }
- projsplash(p, v, NULL, qdam);
- exploded = true;
- }
- else
- {
- vec pos(v);
- pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
- regular_particle_splash(PART_SMOKE, 2, 300, pos, 0x404040, 2.4f, 50, -20);
- }
- }
- if(exploded)
- {
- if(p.local)
- addmsg(N_EXPLODE, "rci3iv", p.owner, lastmillis-maptime, p.gun, p.id-maptime,
- hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
- projs.remove(i--);
- }
- else p.o = v;
- }
- }
-
- extern int chainsawhudgun;
-
- VARP(muzzleflash, 0, 1, 1);
- VARP(muzzlelight, 0, 1, 1);
-
- void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int id, int prevaction) // create visual effect from a shot
- {
- int sound = guns[gun].sound, pspeed = 25;
- switch(gun)
- {
- case GUN_FIST:
- if(d->type==ENT_PLAYER && chainsawhudgun) sound = S_CHAINSAW_ATTACK;
- break;
-
- case GUN_SG:
- {
- if(!local) createrays(gun, from, to);
- if(muzzleflash && d->muzzle.x >= 0)
- particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH3, 0xFFFFFF, 2.75f, d);
- loopi(guns[gun].rays)
- {
- if(d->quadmillis)
- particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, rays[i], d), rays[i], 0x802010, 0.6f, 36);
- particle_splash(PART_SPARK, 20, 250, rays[i], 0xB49B4B, 0.24f);
- particle_flare(hudgunorigin(gun, from, rays[i], d), rays[i], 300, PART_STREAK, 0xFFC864, 0.28f);
- if(!local) adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f);
- }
- if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 30, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d);
- break;
- }
-
- case GUN_CG:
- case GUN_PISTOL:
- {
- particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f);
- if(d->quadmillis)
- particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36);
- particle_flare(hudgunorigin(gun, from, to, d), to, 600, PART_STREAK, 0xFFC864, 0.28f);
- if(muzzleflash && d->muzzle.x >= 0)
- particle_flare(d->muzzle, d->muzzle, gun==GUN_CG ? 100 : 200, PART_MUZZLE_FLASH1, 0xFFFFFF, gun==GUN_CG ? 2.25f : 1.25f, d);
- if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 2.0f);
- if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), gun==GUN_CG ? 30 : 15, vec(0.5f, 0.375f, 0.25f), gun==GUN_CG ? 50 : 100, gun==GUN_CG ? 50 : 100, DL_FLASH, 0, vec(0, 0, 0), d);
- break;
- }
-
- case GUN_RL:
- if(d->quadmillis)
- particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36);
- if(muzzleflash && d->muzzle.x >= 0)
- particle_flare(d->muzzle, d->muzzle, 250, PART_MUZZLE_FLASH2, 0xFFFFFF, 3.0f, d);
- pspeed = guns[gun].projspeed;
- newprojectile(from, to, (float)pspeed, local, id, d, gun);
- break;
-
- case GUN_GL:
- {
- float dist = from.dist(to);
- vec up = to;
- up.z += dist/8;
- if(muzzleflash && d->muzzle.x >= 0)
- particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH2, 0xFFFFFF, 1.5f, d);
- if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 20, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d);
- newbouncer(from, up, local, id, d, BNC_GRENADE, guns[gun].ttl, guns[gun].projspeed);
- break;
- }
-
- case GUN_RIFLE:
- particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f);
- if(d->quadmillis)
- particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36);
- particle_trail(PART_SMOKE, 500, hudgunorigin(gun, from, to, d), to, 0x404040, 0.6f, 18);
- if(muzzleflash && d->muzzle.x >= 0)
- particle_flare(d->muzzle, d->muzzle, 150, PART_MUZZLE_FLASH3, 0xFFFFFF, 1.25f, d);
- if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 3.0f);
- if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 25, vec(0.5f, 0.375f, 0.25f), 75, 75, DL_FLASH, 0, vec(0, 0, 0), d);
- break;
- }
-
- bool looped = false;
- if(d->attacksound >= 0 && d->attacksound != sound) d->stopattacksound();
- if(d->idlesound >= 0) d->stopidlesound();
- fpsent *h = followingplayer(player1);
- switch(sound)
- {
- case S_CHAINSAW_ATTACK:
- if(d->attacksound >= 0) looped = true;
- d->attacksound = sound;
- d->attackchan = playsound(sound, d==h ? NULL : &d->o, NULL, 0, -1, 100, d->attackchan);
- break;
- default:
- playsound(sound, d==h ? NULL : &d->o);
- break;
- }
- if(d->quadmillis && lastmillis-prevaction>200 && !looped) playsound(S_ITEMPUP, d==h ? NULL : &d->o);
- }
-
- void particletrack(physent *owner, vec &o, vec &d)
- {
- if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
- fpsent *pl = (fpsent *)owner;
- if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return;
- float dist = o.dist(d);
- o = pl->muzzle;
- if(dist <= 0) d = o;
- else
- {
- vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d);
- float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
- d.mul(min(newdist, dist)).add(owner->o);
- }
- }
-
- void dynlighttrack(physent *owner, vec &o, vec &hud)
- {
- if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
- fpsent *pl = (fpsent *)owner;
- if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return;
- o = pl->muzzle;
- hud = owner == followingplayer(player1) ? vec(pl->o).add(vec(0, 0, 2)) : pl->muzzle;
- }
-
- float intersectdist = 1e16f;
-
- bool intersect(dynent *d, const vec &from, const vec &to, float &dist) // if lineseg hits entity bounding box
- {
- vec bottom(d->o), top(d->o);
- bottom.z -= d->eyeheight;
- top.z += d->aboveeye;
- return linecylinderintersect(from, to, bottom, top, d->radius, dist);
- }
-
- dynent *intersectclosest(const vec &from, const vec &to, fpsent *at, float &bestdist)
- {
- dynent *best = NULL;
- bestdist = 1e16f;
- loopi(numdynents())
- {
- dynent *o = iterdynents(i);
- if(o==at || o->state!=CS_ALIVE) continue;
- float dist;
- if(!intersect(o, from, to, dist)) continue;
- if(dist<bestdist)
- {
- best = o;
- bestdist = dist;
- }
- }
- return best;
- }
-
- void shorten(vec &from, vec &target, float dist)
- {
- target.sub(from).mul(min(1.0f, dist)).add(from);
- }
-
- void raydamage(vec &from, vec &to, fpsent *d)
- {
- int qdam = guns[d->gunselect].damage;
- if(d->quadmillis) qdam *= 4;
- dynent *o;
- float dist;
- if(guns[d->gunselect].rays > 1)
- {
- dynent *hits[MAXRAYS];
- int maxrays = guns[d->gunselect].rays;
- loopi(maxrays)
- {
- if((hits[i] = intersectclosest(from, rays[i], d, dist))) shorten(from, rays[i], dist);
- else adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f);
- }
- loopi(maxrays) if(hits[i])
- {
- o = hits[i];
- hits[i] = NULL;
- int numhits = 1;
- for(int j = i+1; j < maxrays; j++) if(hits[j] == o)
- {
- hits[j] = NULL;
- numhits++;
- }
- hitpush(numhits*qdam, o, d, from, to, d->gunselect, numhits);
- }
- }
- else if((o = intersectclosest(from, to, d, dist)))
- {
- shorten(from, to, dist);
- hitpush(qdam, o, d, from, to, d->gunselect, 1);
- }
- else if(d->gunselect!=GUN_FIST) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), d->gunselect==GUN_RIFLE ? 3.0f : 2.0f);
- }
-
- void shoot(fpsent *d, const vec &targ)
- {
- int prevaction = d->lastaction, attacktime = lastmillis-prevaction;
- if(attacktime<d->gunwait) return;
- d->gunwait = 0;
- if((d==player1 || d->ai) && !d->attacking) return;
- d->lastaction = lastmillis;
- d->lastattackgun = d->gunselect;
- if(!d->ammo[d->gunselect])
- {
- if(d==player1)
- {
- msgsound(S_NOAMMO, d);
- d->gunwait = 600;
- d->lastattackgun = -1;
- weaponswitch(d);
- }
- return;
- }
- if(d->gunselect) d->ammo[d->gunselect]--;
-
- pwshot(d->gunselect); /// PW
-
- vec from = d->o, to = targ, dir = vec(to).sub(from).safenormalize();
- float dist = to.dist(from);
- vec kickback = vec(dir).mul(guns[d->gunselect].kickamount*-2.5f);
- d->vel.add(kickback);
- float shorten = 0;
- if(guns[d->gunselect].range && dist > guns[d->gunselect].range)
- shorten = guns[d->gunselect].range;
- float barrier = raycube(d->o, dir, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
- if(barrier > 0 && barrier < dist && (!shorten || barrier < shorten))
- shorten = barrier;
- if(shorten) to = vec(dir).mul(shorten).add(from);
-
- if(guns[d->gunselect].rays > 1) createrays(d->gunselect, from, to);
- else if(guns[d->gunselect].spread) offsetray(from, to, guns[d->gunselect].spread, guns[d->gunselect].range, to);
-
- hits.setsize(0);
-
- if(!guns[d->gunselect].projspeed) raydamage(from, to, d);
-
- shoteffects(d->gunselect, from, to, d, true, 0, prevaction);
-
- if(d==player1 || d->ai)
- {
- addmsg(N_SHOOT, "rci2i6iv", d, lastmillis-maptime, d->gunselect,
- (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
- (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
- hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
- }
-
- d->gunwait = guns[d->gunselect].attackdelay;
- if(d->gunselect == GUN_PISTOL && d->ai) d->gunwait += int(d->gunwait*(((101-d->skill)+rnd(111-d->skill))/100.f));
- d->totalshots += guns[d->gunselect].damage*(d->quadmillis ? 4 : 1)*guns[d->gunselect].rays;
- }
-
- void adddynlights()
- {
- loopv(projs)
- {
- projectile &p = projs[i];
- if(p.gun!=GUN_RL) continue;
- vec pos(p.o);
- pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
- adddynlight(pos, 20, vec(1, 0.75f, 0.5f));
- }
- loopv(bouncers)
- {
- bouncer &bnc = *bouncers[i];
- if(bnc.bouncetype!=BNC_GRENADE) continue;
- vec pos = bnc.offsetpos();
- adddynlight(pos, 8, vec(0.25f, 1, 1));
- }
- }
-
- static const char * const projnames[2] = { "projectiles/grenade", "projectiles/rocket" };
-
- void preloadbouncers() {
- loopi(sizeof(projnames)/sizeof(projnames[0])) preloadmodel(projnames[i]);
- }
-
- void renderbouncers()
- {
- float yaw, pitch;
- loopv(bouncers)
- {
- bouncer &bnc = *bouncers[i];
- vec pos = bnc.offsetpos();
- vec vel(bnc.vel);
- if(vel.magnitude() <= 25.0f) yaw = bnc.lastyaw;
- else
- {
- vectoyawpitch(vel, yaw, pitch);
- yaw += 90;
- bnc.lastyaw = yaw;
- }
- pitch = -bnc.roll;
- if(bnc.bouncetype==BNC_GRENADE)
- rendermodel(&bnc.light, "projectiles/grenade", ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST|MDL_DYNSHADOW);
- else
- {
- const char *mdl = NULL;
- int cull = MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED;
- float fade = 1;
- if(bnc.lifetime < 250) fade = bnc.lifetime/250.0f;
- rendermodel(&bnc.light, mdl, ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, cull, NULL, NULL, 0, 0, fade);
- }
- }
- }
-
- void renderprojectiles()
- {
- float yaw, pitch;
- loopv(projs)
- {
- projectile &p = projs[i];
- if(p.gun!=GUN_RL) continue;
- float dist = min(p.o.dist(p.to)/32.0f, 1.0f);
- vec pos = vec(p.o).add(vec(p.offset).mul(dist*p.offsetmillis/float(OFFSETMILLIS))),
- v = dist < 1e-6f ? p.dir : vec(p.to).sub(pos).normalize();
- // the amount of distance in front of the smoke trail needs to change if the model does
- vectoyawpitch(v, yaw, pitch);
- yaw += 90;
- v.mul(3);
- v.add(pos);
- rendermodel(&p.light, "projectiles/rocket", ANIM_MAPMODEL|ANIM_LOOP, v, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST);
- }
- }
-
- void checkattacksound(fpsent *d, bool local)
- {
- int gun = -1;
- switch(d->attacksound)
- {
- case S_CHAINSAW_ATTACK:
- if(chainsawhudgun) gun = GUN_FIST;
- break;
- default:
- return;
- }
- if(gun >= 0 && gun < NUMGUNS &&
- d->clientnum >= 0 && d->state == CS_ALIVE &&
- d->lastattackgun == gun && lastmillis - d->lastaction < guns[gun].attackdelay + 50)
- {
- d->attackchan = playsound(d->attacksound, local ? NULL : &d->o, NULL, 0, -1, -1, d->attackchan);
- if(d->attackchan < 0) d->attacksound = -1;
- }
- else d->stopattacksound();
- }
-
- void checkidlesound(fpsent *d, bool local)
- {
- int sound = -1, radius = 0;
- if(d->clientnum >= 0 && d->state == CS_ALIVE) switch(d->gunselect)
- {
- case GUN_FIST:
- if(chainsawhudgun && d->attacksound < 0)
- {
- sound = S_CHAINSAW_IDLE;
- radius = 50;
- }
- break;
- }
- if(d->idlesound != sound)
- {
- if(d->idlesound >= 0) d->stopidlesound();
- if(sound >= 0)
- {
- d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, 100, d->idlechan, radius);
- if(d->idlechan >= 0) d->idlesound = sound;
- }
- }
- else if(sound >= 0)
- {
- d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, -1, d->idlechan, radius);
- if(d->idlechan < 0) d->idlesound = -1;
- }
- }
-
- void removeweapons(fpsent *d)
- {
- removebouncers(d);
- removeprojectiles(d);
- }
-
- void updateweapons(int curtime)
- {
- updateprojectiles(curtime);
- pwcalcaccuracy();
- if(player1->clientnum>=0 && player1->state==CS_ALIVE) shoot(player1, worldpos); // only shoot when connected to server
- updatebouncers(curtime); // need to do this after the player shoots so grenades don't end up inside player's BB next frame
- fpsent *following = followingplayer();
- if(!following) following = player1;
- loopv(players)
- {
- fpsent *d = players[i];
- checkattacksound(d, d==following);
- checkidlesound(d, d==following);
- }
- }
-
- void avoidweapons(ai::avoidset &obstacles, float radius)
- {
- loopv(projs)
- {
- projectile &p = projs[i];
- obstacles.avoidnear(NULL, p.o.z + guns[p.gun].exprad + 1, p.o, radius + guns[p.gun].exprad);
- }
- loopv(bouncers)
- {
- bouncer &bnc = *bouncers[i];
- if(bnc.bouncetype != BNC_GRENADE) continue;
- obstacles.avoidnear(NULL, bnc.o.z + guns[GUN_GL].exprad + 1, bnc.o, radius + guns[GUN_GL].exprad);
- }
- }
+ static const int OFFSETMILLIS = 500;
+ vec rays[MAXRAYS];
+
+ struct hitmsg
+ {
+ int target, lifesequence, info1, info2;
+ ivec dir;
+ };
+ vector<hitmsg> hits;
+
+ ICOMMAND(getweapon, "", (), intret(player1->gunselect));
+
+ void gunselect(int gun, fpsent *d)
+ {
+ if(gun!=d->gunselect)
+ {
+ addmsg(N_GUNSELECT, "rci", d, gun);
+ playsound(S_WEAPLOAD, d == player1 ? NULL : &d->o);
+ }
+ d->gunselect = gun;
+ }
+
+ void nextweapon(int dir, bool force = false)
+ {
+ if(player1->state!=CS_ALIVE) return;
+ dir = (dir < 0 ? NUMGUNS-1 : 1);
+ int gun = player1->gunselect;
+ loopi(NUMGUNS)
+ {
+ gun = (gun + dir)%NUMGUNS;
+ if(force || player1->ammo[gun]) break;
+ }
+ if(gun != player1->gunselect) gunselect(gun, player1);
+ else playsound(S_NOAMMO);
+ }
+ ICOMMAND(nextweapon, "ii", (int *dir, int *force), nextweapon(*dir, *force!=0));
+
+ int getweapon(const char *name)
+ {
+ const char *abbrevs[] = { "FI", "SG", "CG", "RL", "RI", "GL", "PI" };
+ if(isdigit(name[0])) return parseint(name);
+ else loopi(sizeof(abbrevs)/sizeof(abbrevs[0])) if(!strcasecmp(abbrevs[i], name)) return i;
+ return -1;
+ }
+
+ void setweapon(const char *name, bool force = false)
+ {
+ int gun = getweapon(name);
+ if(player1->state!=CS_ALIVE || gun<GUN_FIST || gun>GUN_PISTOL) return;
+ if(force || player1->ammo[gun]) gunselect(gun, player1);
+ else playsound(S_NOAMMO);
+ }
+ ICOMMAND(setweapon, "si", (char *name, int *force), setweapon(name, *force!=0));
+
+ void cycleweapon(int numguns, int *guns, bool force = false)
+ {
+ if(numguns<=0 || player1->state!=CS_ALIVE) return;
+ int offset = 0;
+ loopi(numguns) if(guns[i] == player1->gunselect) { offset = i+1; break; }
+ loopi(numguns)
+ {
+ int gun = guns[(i+offset)%numguns];
+ if(gun>=0 && gun<NUMGUNS && (force || player1->ammo[gun]))
+ {
+ gunselect(gun, player1);
+ return;
+ }
+ }
+ playsound(S_NOAMMO);
+ }
+ ICOMMAND(cycleweapon, "V", (tagval *args, int numargs),
+ {
+ int numguns = min(numargs, 7);
+ int guns[7];
+ loopi(numguns) guns[i] = getweapon(args[i].getstr());
+ cycleweapon(numguns, guns);
+ });
+
+ void weaponswitch(fpsent *d)
+ {
+ if(d->state!=CS_ALIVE) return;
+ int s = d->gunselect;
+ if (s!=GUN_CG && d->ammo[GUN_CG]) s = GUN_CG;
+ else if(s!=GUN_RL && d->ammo[GUN_RL]) s = GUN_RL;
+ else if(s!=GUN_SG && d->ammo[GUN_SG]) s = GUN_SG;
+ else if(s!=GUN_RIFLE && d->ammo[GUN_RIFLE]) s = GUN_RIFLE;
+ else if(s!=GUN_GL && d->ammo[GUN_GL]) s = GUN_GL;
+ else if(s!=GUN_PISTOL && d->ammo[GUN_PISTOL]) s = GUN_PISTOL;
+ else s = GUN_FIST;
+
+ gunselect(s, d);
+ }
+
+ ICOMMAND(weapon, "V", (tagval *args, int numargs),
+ {
+ if(player1->state!=CS_ALIVE) return;
+ loopi(7)
+ {
+ const char *name = i < numargs ? args[i].getstr() : "";
+ if(name[0])
+ {
+ int gun = getweapon(name);
+ if(gun >= GUN_FIST && gun <= GUN_PISTOL && gun != player1->gunselect && player1->ammo[gun]) { gunselect(gun, player1); return; }
+ } else { weaponswitch(player1); return; }
+ }
+ playsound(S_NOAMMO);
+ });
+
+ void offsetray(const vec &from, const vec &to, int spread, float range, vec &dest)
+ {
+ vec offset;
+ do offset = vec(rndscale(1), rndscale(1), rndscale(1)).sub(0.5f);
+ while(offset.squaredlen() > 0.5f*0.5f);
+ offset.mul((to.dist(from)/1024)*spread);
+ offset.z /= 2;
+ dest = vec(offset).add(to);
+ if(dest != from)
+ {
+ vec dir = vec(dest).sub(from).normalize();
+ raycubepos(from, dir, dest, range, RAY_CLIPMAT|RAY_ALPHAPOLY);
+ }
+ }
+
+ void createrays(int gun, const vec &from, const vec &to) // create random spread of rays
+ {
+ loopi(guns[gun].rays) offsetray(from, to, guns[gun].spread, guns[gun].range, rays[i]);
+ }
+
+ enum { BNC_GRENADE };
+
+ struct bouncer : physent
+ {
+ int lifetime, bounces;
+ float lastyaw, roll;
+ bool local;
+ fpsent *owner;
+ int bouncetype, variant;
+ vec offset;
+ int offsetmillis;
+ float offsetheight;
+ int id;
+ entitylight light;
+
+ bouncer() : bounces(0), roll(0), variant(0)
+ {
+ type = ENT_BOUNCE;
+ }
+
+ vec offsetpos()
+ {
+ vec pos(o);
+ if(offsetmillis > 0)
+ {
+ pos.add(vec(offset).mul(offsetmillis/float(OFFSETMILLIS)));
+ if(offsetheight >= 0) pos.z = max(pos.z, o.z - max(offsetheight - eyeheight, 0.0f));
+ }
+ return pos;
+ }
+
+ void limitoffset()
+ {
+ if(bouncetype == BNC_GRENADE && offsetmillis > 0 && offset.z < 0)
+ offsetheight = raycube(vec(o.x + offset.x, o.y + offset.y, o.z), vec(0, 0, -1), -offset.z);
+ else offsetheight = -1;
+ }
+ };
+
+ vector<bouncer *> bouncers;
+
+ vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d);
+
+ void newbouncer(const vec &from, const vec &to, bool local, int id, fpsent *owner, int type, int lifetime, int speed, entitylight *light = NULL)
+ {
+ bouncer &bnc = *bouncers.add(new bouncer);
+ bnc.o = from;
+ bnc.radius = bnc.xradius = bnc.yradius = 1.5f;
+ bnc.eyeheight = bnc.radius;
+ bnc.aboveeye = bnc.radius;
+ bnc.lifetime = lifetime;
+ bnc.local = local;
+ bnc.owner = owner;
+ bnc.bouncetype = type;
+ bnc.id = local ? lastmillis : id;
+ if(light) bnc.light = *light;
+
+ bnc.collidetype = COLLIDE_ELLIPSE_PRECISE;
+
+ vec dir(to);
+ dir.sub(from).safenormalize();
+ bnc.vel = dir;
+ bnc.vel.mul(speed);
+
+ avoidcollision(&bnc, dir, owner, 0.1f);
+
+ if(type==BNC_GRENADE)
+ {
+ bnc.offset = hudgunorigin(GUN_GL, from, to, owner);
+ if(owner==followingplayer(player1) && !isthirdperson()) bnc.offset.sub(owner->o).rescale(16).add(owner->o);
+ }
+ else bnc.offset = from;
+ bnc.offset.sub(bnc.o);
+ bnc.offsetmillis = OFFSETMILLIS;
+ bnc.limitoffset();
+
+ bnc.resetinterp();
+ }
+
+ void bounced(physent *d, const vec &surface)
+ {
+ if(d->type != ENT_BOUNCE) return;
+ bouncer *b = (bouncer *)d;
+ if(b->bounces >= 2) return;
+ b->bounces++;
+ adddecal(DECAL_BLOOD, vec(b->o).sub(vec(surface).mul(b->radius)), surface, 2.96f/b->bounces, bvec(0x60, 0xFF, 0xFF), rnd(4));
+ }
+
+ void updatebouncers(int time)
+ {
+ loopv(bouncers)
+ {
+ bouncer &bnc = *bouncers[i];
+ if(bnc.bouncetype==BNC_GRENADE && bnc.vel.magnitude() > 50.0f)
+ {
+ vec pos = bnc.offsetpos();
+ regular_particle_splash(PART_SMOKE, 1, 150, pos, 0x404040, 2.4f, 50, -20);
+ }
+ vec old(bnc.o);
+ bool stopped = false;
+ if(bnc.bouncetype==BNC_GRENADE) stopped = bounce(&bnc, 0.6f, 0.5f, 0.8f) || (bnc.lifetime -= time)<0;
+
+ if(stopped)
+ {
+ if(bnc.bouncetype==BNC_GRENADE)
+ {
+ int qdam = guns[GUN_GL].damage*(bnc.owner->quadmillis ? 4 : 1);
+ hits.setsize(0);
+ explode(bnc.local, bnc.owner, bnc.o, NULL, qdam, GUN_GL);
+ adddecal(DECAL_SCORCH, bnc.o, vec(0, 0, 1), guns[GUN_GL].exprad/2);
+ if(bnc.local)
+ addmsg(N_EXPLODE, "rci3iv", bnc.owner, lastmillis-maptime, GUN_GL, bnc.id-maptime,
+ hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
+ }
+ delete bouncers.remove(i--);
+ }
+ else
+ {
+ bnc.roll += old.sub(bnc.o).magnitude()/(4*RAD);
+ bnc.offsetmillis = max(bnc.offsetmillis-time, 0);
+ bnc.limitoffset();
+ }
+ }
+ }
+
+ void removebouncers(fpsent *owner)
+ {
+ loopv(bouncers) if(bouncers[i]->owner==owner) { delete bouncers[i]; bouncers.remove(i--); }
+ }
+
+ void clearbouncers() { bouncers.deletecontents(); }
+
+ struct projectile
+ {
+ vec dir, o, to, offset;
+ float speed;
+ fpsent *owner;
+ int gun;
+ bool local;
+ int offsetmillis;
+ int id;
+ entitylight light;
+ };
+ vector<projectile> projs;
+
+ void clearprojectiles() { projs.shrink(0); }
+
+ void newprojectile(const vec &from, const vec &to, float speed, bool local, int id, fpsent *owner, int gun)
+ {
+ projectile &p = projs.add();
+ p.dir = vec(to).sub(from).safenormalize();
+ p.o = from;
+ p.to = to;
+ p.offset = hudgunorigin(gun, from, to, owner);
+ p.offset.sub(from);
+ p.speed = speed;
+ p.local = local;
+ p.owner = owner;
+ p.gun = gun;
+ p.offsetmillis = OFFSETMILLIS;
+ p.id = local ? lastmillis : id;
+ }
+
+ void removeprojectiles(fpsent *owner)
+ {
+ // can't use loopv here due to strange GCC optimizer bug
+ int len = projs.length();
+ loopi(len) if(projs[i].owner==owner) { projs.remove(i--); len--; }
+ }
+
+ VARP(blood, 0, 1, 1);
+
+ void damageeffect(int damage, fpsent *d, bool thirdperson)
+ {
+ vec p = d->o;
+ p.z += 0.6f*(d->eyeheight + d->aboveeye) - d->eyeheight;
+ if(blood) particle_splash(PART_BLOOD, damage/10, 1000, p, 0x60FFFF, 2.96f);
+ if(thirdperson)
+ {
+ defformatstring(ds, "%d", damage);
+ particle_textcopy(d->abovehead(), ds, PART_TEXT, 2000, 0xFF4B19, 4.0f, -8);
+ }
+ }
+
+ void spawnbouncer(const vec &p, const vec &vel, fpsent *d, int type, entitylight *light = NULL)
+ {
+ vec to(rnd(100)-50, rnd(100)-50, rnd(100)-50);
+ if(to.iszero()) to.z += 1;
+ to.normalize();
+ to.add(p);
+ newbouncer(p, to, true, 0, d, type, rnd(1000)+1000, rnd(100)+20, light);
+ }
+
+ void hit(int damage, dynent *d, fpsent *at, const vec &vel, int gun, float info1, int info2 = 1)
+ {
+ if(at==player1 && d!=at)
+ {
+ extern int hitsound;
+ if(hitsound && lasthit != lastmillis) playsound(S_HIT);
+ lasthit = lastmillis;
+ }
+
+ fpsent *f = (fpsent *)d;
+
+ f->lastpain = lastmillis;
+ if(at->type==ENT_PLAYER && !isteam(at->team, f->team)) at->totaldamage += damage;
+
+ if(f->type==ENT_AI || !m_mp(gamemode) || f==at) f->hitpush(damage, vel, at, gun);
+
+ pwhit(gun, damage);
+
+ if(!m_mp(gamemode)) damaged(damage, f, at);
+ else
+ {
+ hitmsg &h = hits.add();
+ h.target = f->clientnum;
+ h.lifesequence = f->lifesequence;
+ h.info1 = int(info1*DMF);
+ h.info2 = info2;
+ h.dir = f==at ? ivec(0, 0, 0) : ivec(vec(vel).mul(DNF));
+ if(at==player1)
+ {
+ damageeffect(damage, f);
+ if(f==player1)
+ {
+ damagecompass(damage, at ? at->o : f->o);
+ playsound(S_PAIN6);
+ }
+ else playsound(S_PAIN1+rnd(5), &f->o);
+ }
+ }
+ }
+
+ void hitpush(int damage, dynent *d, fpsent *at, vec &from, vec &to, int gun, int rays)
+ {
+ hit(damage, d, at, vec(to).sub(from).safenormalize(), gun, from.dist(to), rays);
+ }
+
+ float projdist(dynent *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(dynent *o, const vec &v, int qdam, fpsent *at, int gun)
+ {
+ if(o->state!=CS_ALIVE) return;
+ vec dir;
+ float dist = projdist(o, dir, v);
+ if(dist<guns[gun].exprad)
+ {
+ int damage = (int)(qdam*(1-dist/EXP_DISTSCALE/guns[gun].exprad));
+ if(o==at) damage /= EXP_SELFDAMDIV;
+ hit(damage, o, at, dir, gun, dist);
+ }
+ }
+
+ FVARP(explodebright, 0, 1, 1);
+
+ void explode(bool local, fpsent *owner, const vec &v, dynent *safe, int damage, int gun)
+ {
+ particle_splash(PART_SPARK, 200, 300, v, 0xB49B4B, 0.24f);
+ playsound(gun!=GUN_GL ? S_RLHIT : S_FEXPLODE, &v);
+ int color = gun!=GUN_GL ? 0xFF8080 : 0x80FFFF;
+ if((gun==GUN_RL || gun==GUN_GL) && explodebright < 1) color = vec::hexcolor(color).mul(explodebright).tohexcolor();
+ particle_fireball(v, guns[gun].exprad, gun!=GUN_GL ? PART_EXPLOSION : PART_EXPLOSION_BLUE, gun!=GUN_GL ? -1 : int((guns[gun].exprad-4.0f)*15), color, 4.0f);
+ if(gun==GUN_RL) adddynlight(v, 1.15f*guns[gun].exprad, vec(2, 1.5f, 1), 700, 100, 0, guns[gun].exprad/2, vec(1, 0.75f, 0.5f));
+ else if(gun==GUN_GL) adddynlight(v, 1.15f*guns[gun].exprad, vec(0.5f, 1.5f, 2), 600, 100, 0, 8, vec(0.25f, 1, 1));
+ else adddynlight(v, 1.15f*guns[gun].exprad, vec(2, 1.5f, 1), 700, 100);
+ if(!local) return;
+ int numdyn = numdynents();
+ loopi(numdyn)
+ {
+ dynent *o = iterdynents(i);
+ if(o->o.reject(v, o->radius + guns[gun].exprad) || o==safe) continue;
+ radialeffect(o, v, damage, owner, gun);
+ }
+ }
+
+ void projsplash(projectile &p, vec &v, dynent *safe, int damage)
+ {
+ if(guns[p.gun].part)
+ {
+ particle_splash(PART_SPARK, 100, 200, v, 0xB49B4B, 0.24f);
+ playsound(S_FEXPLODE, &v);
+ // no push?
+ }
+ else
+ {
+ explode(p.local, p.owner, v, safe, damage, GUN_RL);
+ adddecal(DECAL_SCORCH, v, vec(p.dir).neg(), guns[p.gun].exprad/2);
+ }
+ }
+
+ void explodeeffects(int gun, fpsent *d, bool local, int id)
+ {
+ if(local) return;
+ switch(gun)
+ {
+ case GUN_RL:
+ loopv(projs)
+ {
+ projectile &p = projs[i];
+ if(p.gun == gun && p.owner == d && p.id == id && !p.local)
+ {
+ vec pos(p.o);
+ pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
+ explode(p.local, p.owner, pos, NULL, 0, GUN_RL);
+ adddecal(DECAL_SCORCH, pos, vec(p.dir).neg(), guns[gun].exprad/2);
+ projs.remove(i);
+ break;
+ }
+ }
+ break;
+ case GUN_GL:
+ loopv(bouncers)
+ {
+ bouncer &b = *bouncers[i];
+ if(b.bouncetype == BNC_GRENADE && b.owner == d && b.id == id && !b.local)
+ {
+ vec pos = b.offsetpos();
+ explode(b.local, b.owner, pos, NULL, 0, GUN_GL);
+ adddecal(DECAL_SCORCH, pos, vec(0, 0, 1), guns[gun].exprad/2);
+ delete bouncers.remove(i);
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ bool projdamage(dynent *o, projectile &p, vec &v, int qdam)
+ {
+ if(o->state!=CS_ALIVE) return false;
+ if(!intersect(o, p.o, v)) return false;
+ projsplash(p, v, o, qdam);
+ vec dir;
+ projdist(o, dir, v);
+ hit(qdam, o, p.owner, dir, p.gun, 0);
+ return true;
+ }
+
+ void updateprojectiles(int time)
+ {
+ loopv(projs)
+ {
+ projectile &p = projs[i];
+ p.offsetmillis = max(p.offsetmillis-time, 0);
+ int qdam = guns[p.gun].damage*(p.owner->quadmillis ? 4 : 1);
+ vec dv;
+ float dist = p.to.dist(p.o, dv);
+ dv.mul(time/max(dist*1000/p.speed, float(time)));
+ vec v = vec(p.o).add(dv);
+ bool exploded = false;
+ hits.setsize(0);
+ if(p.local)
+ {
+ vec halfdv = vec(dv).mul(0.5f), bo = vec(p.o).add(halfdv);
+ float br = max(fabs(halfdv.x), fabs(halfdv.y)) + 1;
+ loopj(numdynents())
+ {
+ dynent *o = iterdynents(j);
+ if(p.owner==o || o->o.reject(bo, o->radius + br)) continue;
+ if(projdamage(o, p, v, qdam)) { exploded = true; break; }
+ }
+ }
+ if(!exploded)
+ {
+ if(dist<4)
+ {
+ if(p.o!=p.to) // if original target was moving, reevaluate endpoint
+ {
+ if(raycubepos(p.o, p.dir, p.to, 0, RAY_CLIPMAT|RAY_ALPHAPOLY)>=4) continue;
+ }
+ projsplash(p, v, NULL, qdam);
+ exploded = true;
+ }
+ else
+ {
+ vec pos(v);
+ pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
+ regular_particle_splash(PART_SMOKE, 2, 300, pos, 0x404040, 2.4f, 50, -20);
+ }
+ }
+ if(exploded)
+ {
+ if(p.local)
+ addmsg(N_EXPLODE, "rci3iv", p.owner, lastmillis-maptime, p.gun, p.id-maptime,
+ hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
+ projs.remove(i--);
+ }
+ else p.o = v;
+ }
+ }
+
+ extern int chainsawhudgun;
+
+ VARP(muzzleflash, 0, 1, 1);
+ VARP(muzzlelight, 0, 1, 1);
+
+ void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int id, int prevaction) // create visual effect from a shot
+ {
+ int sound = guns[gun].sound, pspeed = 25;
+ switch(gun)
+ {
+ case GUN_FIST:
+ if(d->type==ENT_PLAYER && chainsawhudgun) sound = S_CHAINSAW_ATTACK;
+ break;
+
+ case GUN_SG:
+ {
+ if(!local) createrays(gun, from, to);
+ if(muzzleflash && d->muzzle.x >= 0)
+ particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH3, 0xFFFFFF, 2.75f, d);
+ loopi(guns[gun].rays)
+ {
+ if(d->quadmillis)
+ particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, rays[i], d), rays[i], 0x802010, 0.6f, 36);
+ particle_splash(PART_SPARK, 20, 250, rays[i], 0xB49B4B, 0.24f);
+ particle_flare(hudgunorigin(gun, from, rays[i], d), rays[i], 300, PART_STREAK, 0xFFC864, 0.28f);
+ if(!local) adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f);
+ }
+ if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 30, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d);
+ break;
+ }
+
+ case GUN_CG:
+ case GUN_PISTOL:
+ {
+ particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f);
+ if(d->quadmillis)
+ particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36);
+ particle_flare(hudgunorigin(gun, from, to, d), to, 600, PART_STREAK, 0xFFC864, 0.28f);
+ if(muzzleflash && d->muzzle.x >= 0)
+ particle_flare(d->muzzle, d->muzzle, gun==GUN_CG ? 100 : 200, PART_MUZZLE_FLASH1, 0xFFFFFF, gun==GUN_CG ? 2.25f : 1.25f, d);
+ if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 2.0f);
+ if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), gun==GUN_CG ? 30 : 15, vec(0.5f, 0.375f, 0.25f), gun==GUN_CG ? 50 : 100, gun==GUN_CG ? 50 : 100, DL_FLASH, 0, vec(0, 0, 0), d);
+ break;
+ }
+
+ case GUN_RL:
+ if(d->quadmillis)
+ particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36);
+ if(muzzleflash && d->muzzle.x >= 0)
+ particle_flare(d->muzzle, d->muzzle, 250, PART_MUZZLE_FLASH2, 0xFFFFFF, 3.0f, d);
+ pspeed = guns[gun].projspeed;
+ newprojectile(from, to, (float)pspeed, local, id, d, gun);
+ break;
+
+ case GUN_GL:
+ {
+ float dist = from.dist(to);
+ vec up = to;
+ up.z += dist/8;
+ if(muzzleflash && d->muzzle.x >= 0)
+ particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH2, 0xFFFFFF, 1.5f, d);
+ if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 20, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d);
+ newbouncer(from, up, local, id, d, BNC_GRENADE, guns[gun].ttl, guns[gun].projspeed);
+ break;
+ }
+
+ case GUN_RIFLE:
+ particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f);
+ if(d->quadmillis)
+ particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36);
+ particle_trail(PART_SMOKE, 500, hudgunorigin(gun, from, to, d), to, 0x404040, 0.6f, 18);
+ if(muzzleflash && d->muzzle.x >= 0)
+ particle_flare(d->muzzle, d->muzzle, 150, PART_MUZZLE_FLASH3, 0xFFFFFF, 1.25f, d);
+ if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 3.0f);
+ if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 25, vec(0.5f, 0.375f, 0.25f), 75, 75, DL_FLASH, 0, vec(0, 0, 0), d);
+ break;
+ }
+
+ bool looped = false;
+ if(d->attacksound >= 0 && d->attacksound != sound) d->stopattacksound();
+ if(d->idlesound >= 0) d->stopidlesound();
+ fpsent *h = followingplayer(player1);
+ switch(sound)
+ {
+ case S_CHAINSAW_ATTACK:
+ if(d->attacksound >= 0) looped = true;
+ d->attacksound = sound;
+ d->attackchan = playsound(sound, d==h ? NULL : &d->o, NULL, 0, -1, 100, d->attackchan);
+ break;
+ default:
+ playsound(sound, d==h ? NULL : &d->o);
+ break;
+ }
+ if(d->quadmillis && lastmillis-prevaction>200 && !looped) playsound(S_ITEMPUP, d==h ? NULL : &d->o);
+ }
+
+ void particletrack(physent *owner, vec &o, vec &d)
+ {
+ if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
+ fpsent *pl = (fpsent *)owner;
+ if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return;
+ float dist = o.dist(d);
+ o = pl->muzzle;
+ if(dist <= 0) d = o;
+ else
+ {
+ vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d);
+ float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
+ d.mul(min(newdist, dist)).add(owner->o);
+ }
+ }
+
+ void dynlighttrack(physent *owner, vec &o, vec &hud)
+ {
+ if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
+ fpsent *pl = (fpsent *)owner;
+ if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return;
+ o = pl->muzzle;
+ hud = owner == followingplayer(player1) ? vec(pl->o).add(vec(0, 0, 2)) : pl->muzzle;
+ }
+
+ float intersectdist = 1e16f;
+
+ bool intersect(dynent *d, const vec &from, const vec &to, float &dist) // if lineseg hits entity bounding box
+ {
+ vec bottom(d->o), top(d->o);
+ bottom.z -= d->eyeheight;
+ top.z += d->aboveeye;
+ return linecylinderintersect(from, to, bottom, top, d->radius, dist);
+ }
+
+ dynent *intersectclosest(const vec &from, const vec &to, fpsent *at, float &bestdist)
+ {
+ dynent *best = NULL;
+ bestdist = 1e16f;
+ loopi(numdynents())
+ {
+ dynent *o = iterdynents(i);
+ if(o==at || o->state!=CS_ALIVE) continue;
+ float dist;
+ if(!intersect(o, from, to, dist)) continue;
+ if(dist<bestdist)
+ {
+ best = o;
+ bestdist = dist;
+ }
+ }
+ return best;
+ }
+
+ void shorten(vec &from, vec &target, float dist)
+ {
+ target.sub(from).mul(min(1.0f, dist)).add(from);
+ }
+
+ void raydamage(vec &from, vec &to, fpsent *d)
+ {
+ int qdam = guns[d->gunselect].damage;
+ if(d->quadmillis) qdam *= 4;
+ dynent *o;
+ float dist;
+ if(guns[d->gunselect].rays > 1)
+ {
+ dynent *hits[MAXRAYS];
+ int maxrays = guns[d->gunselect].rays;
+ loopi(maxrays)
+ {
+ if((hits[i] = intersectclosest(from, rays[i], d, dist))) shorten(from, rays[i], dist);
+ else adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f);
+ }
+ loopi(maxrays) if(hits[i])
+ {
+ o = hits[i];
+ hits[i] = NULL;
+ int numhits = 1;
+ for(int j = i+1; j < maxrays; j++) if(hits[j] == o)
+ {
+ hits[j] = NULL;
+ numhits++;
+ }
+ hitpush(numhits*qdam, o, d, from, to, d->gunselect, numhits);
+ }
+ }
+ else if((o = intersectclosest(from, to, d, dist)))
+ {
+ shorten(from, to, dist);
+ hitpush(qdam, o, d, from, to, d->gunselect, 1);
+ }
+ else if(d->gunselect!=GUN_FIST) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), d->gunselect==GUN_RIFLE ? 3.0f : 2.0f);
+ }
+
+ void shoot(fpsent *d, const vec &targ)
+ {
+ int prevaction = d->lastaction, attacktime = lastmillis-prevaction;
+ if(attacktime<d->gunwait) return;
+ d->gunwait = 0;
+ if((d==player1 || d->ai) && !d->attacking) return;
+ d->lastaction = lastmillis;
+ d->lastattackgun = d->gunselect;
+ if(!d->ammo[d->gunselect])
+ {
+ if(d==player1)
+ {
+ msgsound(S_NOAMMO, d);
+ d->gunwait = 600;
+ d->lastattackgun = -1;
+ weaponswitch(d);
+ }
+ return;
+ }
+ if(d->gunselect) d->ammo[d->gunselect]--;
+
+ pwshot(d->gunselect); /// PW
+
+ vec from = d->o, to = targ, dir = vec(to).sub(from).safenormalize();
+ float dist = to.dist(from);
+ vec kickback = vec(dir).mul(guns[d->gunselect].kickamount*-2.5f);
+ d->vel.add(kickback);
+ float shorten = 0;
+ if(guns[d->gunselect].range && dist > guns[d->gunselect].range)
+ shorten = guns[d->gunselect].range;
+ float barrier = raycube(d->o, dir, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
+ if(barrier > 0 && barrier < dist && (!shorten || barrier < shorten))
+ shorten = barrier;
+ if(shorten) to = vec(dir).mul(shorten).add(from);
+
+ if(guns[d->gunselect].rays > 1) createrays(d->gunselect, from, to);
+ else if(guns[d->gunselect].spread) offsetray(from, to, guns[d->gunselect].spread, guns[d->gunselect].range, to);
+
+ hits.setsize(0);
+
+ if(!guns[d->gunselect].projspeed) raydamage(from, to, d);
+
+ shoteffects(d->gunselect, from, to, d, true, 0, prevaction);
+
+ if(d==player1 || d->ai)
+ {
+ addmsg(N_SHOOT, "rci2i6iv", d, lastmillis-maptime, d->gunselect,
+ (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
+ (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
+ hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
+ }
+
+ d->gunwait = guns[d->gunselect].attackdelay;
+ if(d->gunselect == GUN_PISTOL && d->ai) d->gunwait += int(d->gunwait*(((101-d->skill)+rnd(111-d->skill))/100.f));
+ d->totalshots += guns[d->gunselect].damage*(d->quadmillis ? 4 : 1)*guns[d->gunselect].rays;
+ }
+
+ void adddynlights()
+ {
+ loopv(projs)
+ {
+ projectile &p = projs[i];
+ if(p.gun!=GUN_RL) continue;
+ vec pos(p.o);
+ pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
+ adddynlight(pos, 20, vec(1, 0.75f, 0.5f));
+ }
+ loopv(bouncers)
+ {
+ bouncer &bnc = *bouncers[i];
+ if(bnc.bouncetype!=BNC_GRENADE) continue;
+ vec pos = bnc.offsetpos();
+ adddynlight(pos, 8, vec(0.25f, 1, 1));
+ }
+ }
+
+ static const char * const projnames[2] = { "projectiles/grenade", "projectiles/rocket" };
+
+ void preloadbouncers() {
+ loopi(sizeof(projnames)/sizeof(projnames[0])) preloadmodel(projnames[i]);
+ }
+
+ void renderbouncers()
+ {
+ float yaw, pitch;
+ loopv(bouncers)
+ {
+ bouncer &bnc = *bouncers[i];
+ vec pos = bnc.offsetpos();
+ vec vel(bnc.vel);
+ if(vel.magnitude() <= 25.0f) yaw = bnc.lastyaw;
+ else
+ {
+ vectoyawpitch(vel, yaw, pitch);
+ yaw += 90;
+ bnc.lastyaw = yaw;
+ }
+ pitch = -bnc.roll;
+ if(bnc.bouncetype==BNC_GRENADE)
+ rendermodel(&bnc.light, "projectiles/grenade", ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST|MDL_DYNSHADOW);
+ else
+ {
+ const char *mdl = NULL;
+ int cull = MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED;
+ float fade = 1;
+ if(bnc.lifetime < 250) fade = bnc.lifetime/250.0f;
+ rendermodel(&bnc.light, mdl, ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, cull, NULL, NULL, 0, 0, fade);
+ }
+ }
+ }
+
+ void renderprojectiles()
+ {
+ float yaw, pitch;
+ loopv(projs)
+ {
+ projectile &p = projs[i];
+ if(p.gun!=GUN_RL) continue;
+ float dist = min(p.o.dist(p.to)/32.0f, 1.0f);
+ vec pos = vec(p.o).add(vec(p.offset).mul(dist*p.offsetmillis/float(OFFSETMILLIS))),
+ v = dist < 1e-6f ? p.dir : vec(p.to).sub(pos).normalize();
+ // the amount of distance in front of the smoke trail needs to change if the model does
+ vectoyawpitch(v, yaw, pitch);
+ yaw += 90;
+ v.mul(3);
+ v.add(pos);
+ rendermodel(&p.light, "projectiles/rocket", ANIM_MAPMODEL|ANIM_LOOP, v, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST);
+ }
+ }
+
+ void checkattacksound(fpsent *d, bool local)
+ {
+ int gun = -1;
+ switch(d->attacksound)
+ {
+ case S_CHAINSAW_ATTACK:
+ if(chainsawhudgun) gun = GUN_FIST;
+ break;
+ default:
+ return;
+ }
+ if(gun >= 0 && gun < NUMGUNS &&
+ d->clientnum >= 0 && d->state == CS_ALIVE &&
+ d->lastattackgun == gun && lastmillis - d->lastaction < guns[gun].attackdelay + 50)
+ {
+ d->attackchan = playsound(d->attacksound, local ? NULL : &d->o, NULL, 0, -1, -1, d->attackchan);
+ if(d->attackchan < 0) d->attacksound = -1;
+ }
+ else d->stopattacksound();
+ }
+
+ void checkidlesound(fpsent *d, bool local)
+ {
+ int sound = -1, radius = 0;
+ if(d->clientnum >= 0 && d->state == CS_ALIVE) switch(d->gunselect)
+ {
+ case GUN_FIST:
+ if(chainsawhudgun && d->attacksound < 0)
+ {
+ sound = S_CHAINSAW_IDLE;
+ radius = 50;
+ }
+ break;
+ }
+ if(d->idlesound != sound)
+ {
+ if(d->idlesound >= 0) d->stopidlesound();
+ if(sound >= 0)
+ {
+ d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, 100, d->idlechan, radius);
+ if(d->idlechan >= 0) d->idlesound = sound;
+ }
+ }
+ else if(sound >= 0)
+ {
+ d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, -1, d->idlechan, radius);
+ if(d->idlechan < 0) d->idlesound = -1;
+ }
+ }
+
+ void removeweapons(fpsent *d)
+ {
+ removebouncers(d);
+ removeprojectiles(d);
+ }
+
+ void updateweapons(int curtime)
+ {
+ updateprojectiles(curtime);
+ pwcalcaccuracy();
+ if(player1->clientnum>=0 && player1->state==CS_ALIVE) shoot(player1, worldpos); // only shoot when connected to server
+ updatebouncers(curtime); // need to do this after the player shoots so grenades don't end up inside player's BB next frame
+ fpsent *following = followingplayer();
+ if(!following) following = player1;
+ loopv(players)
+ {
+ fpsent *d = players[i];
+ checkattacksound(d, d==following);
+ checkidlesound(d, d==following);
+ }
+ }
+
+ void avoidweapons(ai::avoidset &obstacles, float radius)
+ {
+ loopv(projs)
+ {
+ projectile &p = projs[i];
+ obstacles.avoidnear(NULL, p.o.z + guns[p.gun].exprad + 1, p.o, radius + guns[p.gun].exprad);
+ }
+ loopv(bouncers)
+ {
+ bouncer &bnc = *bouncers[i];
+ if(bnc.bouncetype != BNC_GRENADE) continue;
+ obstacles.avoidnear(NULL, bnc.o.z + guns[GUN_GL].exprad + 1, bnc.o, radius + guns[GUN_GL].exprad);
+ }
+ }
};
/// Rough accuracy code, client-side only.
int pwshotsfired [NUMGUNS] = { 0 };
-int pwshotshit [NUMGUNS] = { 0 };
+int pwshotshit [NUMGUNS] = { 0 };
int pwdamagedealt [NUMGUNS] = { 0 };
-int pwaccuracy [NUMGUNS] = { 0 };
+int pwaccuracy [NUMGUNS] = { 0 };
int pwavgaccuracy = 0;
void pwshot(int gun) {
- pwshotsfired[gun]++;
+ pwshotsfired[gun]++;
}
void pwhit(int gun, int damage) {
- pwshotshit[gun]++;
- pwdamagedealt[gun] += damage;
+ pwshotshit[gun]++;
+ pwdamagedealt[gun] += damage;
}
void pwcalcaccuracy(void) {
- pwavgaccuracy = 0;
- loopi(NUMGUNS)
- if(pwshotsfired[i])
- pwaccuracy[i] = (pwdamagedealt[i] * 100) / (guns[i].damage * guns[i].rays * pwshotsfired[i]);
- else
- pwaccuracy[i] = 0;
- loopi(NUMGUNS) pwavgaccuracy += pwaccuracy[i];
- pwavgaccuracy /= NUMGUNS;
+ pwavgaccuracy = 0;
+ loopi(NUMGUNS)
+ if(pwshotsfired[i])
+ pwaccuracy[i] = (pwdamagedealt[i] * 100) / (guns[i].damage * guns[i].rays * pwshotsfired[i]);
+ else
+ pwaccuracy[i] = 0;
+ loopi(NUMGUNS) pwavgaccuracy += pwaccuracy[i];
+ pwavgaccuracy /= NUMGUNS;
}
void pwreset(void) {
- loopi(NUMGUNS) pwshotsfired[i] = pwshotshit[i] = pwdamagedealt[i] = pwaccuracy[i] = 0;
- loopi(7) pwitemspicked[i] = 0;
- pwavgaccuracy = 0;
+ loopi(NUMGUNS) pwshotsfired[i] = pwshotshit[i] = pwdamagedealt[i] = pwaccuracy[i] = 0;
+ loopi(7) pwitemspicked[i] = 0;
+ pwavgaccuracy = 0;
}