diff options
Diffstat (limited to 'src/fpsgame/weapon.cpp')
| -rw-r--r-- | src/fpsgame/weapon.cpp | 1905 |
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; } |
