AssaultCube Reloaded Wiki
Advertisement
// weapon.cpp: all shooting and effects code

#include "cube.h"
#include "bot/bot.h"
#include "hudgun.h"

VARP(autoreload, 0, 1, 1);
VARP(akimboautoswitch, 0, 1, 1);
VARP(akimboendaction, 0, 3, 3); // 0: switch to knife, 1: stay with pistol (if has ammo), 2: switch to grenade (if possible), 3: switch to primary (if has ammo) - all fallback to previous one w/o ammo for target

vec sg[SGRAYS];

int burstshotssettings[NUMGUNS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

void updatelastaction(playerent *d, int millis = lastmillis)
{
    loopi(NUMGUNS) d->weapons[i]->updatetimers(millis);
    d->lastaction = millis;
}

inline void checkweaponswitch_(playerent *p)
{
    if(!p->weaponchanging) return;
    int timeprogress = lastmillis-p->weaponchanging;
    if (timeprogress>SWITCHTIME(p->perk1 == PERK_TIME))
    {
        p->weaponchanging = 0;
    }
    else if (timeprogress>(SWITCHTIME(p->perk1 == PERK_TIME) >> 1) && p->weaponsel != p->nextweaponsel)
    {
        p->prevweaponsel = p->weaponsel;
        p->weaponsel = p->nextweaponsel;
    }
}

void checkweaponswitch()
{
    checkweaponswitch_(player1);
    loopv(players) if (players[i]) checkweaponswitch_(players[i]);
}

void selectweapon(weapon *w)
{
    if(!w || !player1->weaponsel->deselectable()) return;
    if(w->selectable())
    {
        int i = w->type;
        // substitute akimbo
        weapon *akimbo = player1->weapons[GUN_AKIMBO];
        if(w->type==GUN_PISTOL && akimbo->selectable()) w = akimbo;

        player1->weaponswitch(w);
        if(identexists("onWeaponSwitch"))
        {
            string o;
            formatstring(o)("onWeaponSwitch %d", i);
            execute(o);
        }
    }
}

void requestweapon(int *w)
{
    if(/*keypressed &&*/ player1->state == CS_ALIVE && *w >= 0 && *w < NUMGUNS )
    {
        if (player1->akimbo && *w==GUN_PISTOL) *w = GUN_AKIMBO;
        selectweapon(player1->weapons[*w]);
    }
}

void shiftweapon(int *s)
{
    if(keypressed && player1->state == CS_ALIVE)
    {
        if(!player1->weaponsel->deselectable()) return;

        weapon *curweapon = player1->weaponsel;
        weapon *akimbo = player1->weapons[GUN_AKIMBO];

        // collect available weapons
        vector<weapon *> availweapons;
        const int weap_check_order[NUMGUNS] =
        {
            GUN_AKIMBO,
            GUN_KNIFE,
            GUN_GRENADE,
            // secondary
            GUN_PISTOL,
            GUN_HEAL,
            GUN_RPG,
            GUN_PISTOL2,
            GUN_SHOTGUN_PRO,
            // primary
            GUN_SHOTGUN,
            GUN_SUBGUN,
            GUN_SNIPER,
            GUN_SNIPER2,
            GUN_BOLT,
            GUN_ASSAULT,
            GUN_SWORD,
            GUN_ASSAULT2,
            GUN_SNIPER3,
            GUN_ASSAULT_PRO,
            GUN_ACR_PRO,
        };
        loopi(NUMGUNS)
        {
            weapon *w = player1->weapons[weap_check_order[i]];
            if(!w) continue;
            if(w->selectable() || w==curweapon || (w->type==GUN_PISTOL && player1->akimbo))
            {
                availweapons.add(w);
            }
        }

        // replace pistol by akimbo
        if(player1->akimbo)
        {
            availweapons.removeobj(akimbo); // and remove initial akimbo
            int pistolidx = availweapons.find(player1->weapons[GUN_PISTOL]);
            if(pistolidx>=0) availweapons[pistolidx] = akimbo; // insert at pistols position
            if(curweapon->type==GUN_PISTOL) curweapon = akimbo; // fix selection
        }

        // detect the next weapon
        int num = availweapons.length();
        int curidx = availweapons.find(curweapon);
        if(!num || curidx<0) return;
        int idx = (curidx + *s) % num;
        if(idx<0) idx += num;
        weapon *next = availweapons[idx];
        if(next->type!=player1->weaponsel->type) // different weapon
        {
            selectweapon(next);
        }
    }
    else if(player1->isspectating()) updatefollowplayer(*s);
}

bool quicknade = false, nadeattack = false;
VARP(quicknade_hold, 0, 1, 1);

void quicknadethrow(bool on)
{
    if(player1->state != CS_ALIVE) return;
    if(on)
    {
        if(player1->weapons[GUN_GRENADE]->mag > 0)
        {
            if(player1->weaponsel->type != GUN_GRENADE) selectweapon(player1->weapons[GUN_GRENADE]);
            if(player1->weaponsel->type == GUN_GRENADE || quicknade_hold) { player1->attacking = true; nadeattack = true; }
        }
    }
    else if (nadeattack)
    {
        nadeattack = player1->attacking = false;
        if(player1->weaponsel->type == GUN_GRENADE) quicknade = true;
    }
}

void quickknifethrow(bool on)
{
    if(player1->state != CS_ALIVE) return;
    if(on)
    {
        if(player1->weapons[GUN_KNIFE]->mag > 0)
        {
            if(player1->weaponsel->type != GUN_KNIFE) selectweapon(player1->weapons[GUN_KNIFE]);
            if(player1->weaponsel->type == GUN_KNIFE || quicknade_hold) { player1->scoping = true; nadeattack = true; }
        }
    }
    else if (nadeattack)
    {
        nadeattack = player1->scoping = false;
        if(player1->weaponsel->type == GUN_KNIFE) quicknade = true;
    }
}

void setburst(bool enable)
{
    // TODO: AC implemented burst only for cpistol, which is removed
}
COMMAND(setburst, "d");

void currentprimary() { intret(player1->primary); }
void currentsecondary() { intret(player1->secondary); }
void prevweapon() { intret(player1->prevweaponsel->type); }
void curweapon() { intret(player1->weaponsel->type); }
void zoomprogress() { intret((int)(player1->zoomed * 1000)); }
void isscoped() { intret((sniper_weap(player1->weaponsel->type) && player1->zoomed >= ADSZOOM) ? 2 : player1->zoomed ? 1 : 0); }

void magcontent(int *w) { if(*w >= 0 && *w < NUMGUNS) intret(player1->weapons[*w]->mag); else intret(-1); }
void magreserve(int *w) { if(*w >= 0 && *w < NUMGUNS) intret(player1->weapons[*w]->ammo); else intret(-1); }

COMMANDN(weapon, requestweapon, "i");
COMMAND(shiftweapon, "i");
COMMAND(quicknadethrow, "d");
COMMAND(quickknifethrow, "d");
COMMAND(currentprimary, "");
COMMAND(currentsecondary, "");
COMMAND(prevweapon, "");
COMMAND(curweapon, "");
COMMAND(zoomprogress, "");
COMMAND(isscoped, "");
COMMAND(magcontent, "i");
COMMAND(magreserve, "i");

void tryreload(playerent *p)
{
    if(!p || p->state!=CS_ALIVE || p->weaponsel->reloading || p->weaponchanging) return;
    p->weaponsel->reload(false);
}

void selfreload() { tryreload(player1); }
COMMANDN(reload, selfreload, "");

void selfuse()
{
    // for now we're only using it for airstrikes
    addmsg(SV_STREAKUSE, "ri3", (int)(worldhitpos.x*DMF), (int)(worldhitpos.y*DMF), (int)(worldhitpos.z*DMF));
}
COMMANDN(use, selfuse, "");

#include "ballistics.h"

int intersect(playerent *d, const vec &from, const vec &to, vec *end)
{
    float dist;
    if(d->head.x >= 0)
    {
        if(intersectsphere(from, to, d->head, HEADSIZE, dist))
        {
            if(end) (*end = to).sub(from).mul(dist).add(from);
            return HIT_HEAD;
        }
    }
    float y = d->yaw*RAD, p = (d->pitch/4+90)*RAD, c = cosf(p);
    vec bottom(d->o), top(sinf(y)*c, -cosf(y)*c, sinf(p)), mid(top);
    bottom.z -= d->eyeheight;
    float h = d->eyeheight /*+ d->aboveeye*/; // this mod makes the shots pass over the shoulders
    mid.mul(h*(1-TORSOPART)).add(bottom);
    top.mul(h).add(bottom);
    if (intersectcylinder(from, to, mid, top, d->radius, dist))
    {
        if(end) (*end = to).sub(from).mul(dist).add(from);
        return HIT_TORSO;
    }
    if (intersectcylinder(from, to, bottom, mid, d->radius, dist))
    {
        if(end) (*end = to).sub(from).mul(dist).add(from);
        return HIT_LEG;
    }
    return HIT_NONE;

#if 0
    const float eyeheight = d->eyeheight;
    vec o(d->o);
    o.z += (d->aboveeye - eyeheight)/2;
    return intersectbox(o, vec(d->radius, d->radius, (d->aboveeye + eyeheight)/2), from, to, end) ? 1 : 0;
#endif
}

bool intersect(entity *e, const vec &from, const vec &to, vec *end)
{
    mapmodelinfo &mmi = getmminfo(e->attr2);
    if(!&mmi || !mmi.h) return false;

    float lo = float(S(e->x, e->y)->floor+mmi.zoff+e->attr3);
    return intersectbox(vec(e->x, e->y, lo+mmi.h/2.0f), vec(mmi.rad, mmi.rad, mmi.h/2.0f), from, to, end);
}

void playerincrosshair(playerent * &pl, int &hitzone, vec &pos)
{
    const vec &from = camera1->o, &to = worldpos;

    pl = NULL;
    hitzone = HIT_NONE;
    float bestdist = 1e16f;
    loopv(players)
    {
        playerent *o = players[i];
        if(!o || o==focus || o->state==CS_DEAD) continue;
        float dist = camera1->o.dist(o->o);
        int zone = HIT_NONE;
        vec end;
        if(dist < bestdist && (zone = intersect(o, from, to, &end)))
        {
            pl = o;
            hitzone = zone;
            pos = end;
            bestdist = dist;
        }
    }
}

void damageeffect(int damage, const vec &o)
{
    particle_splash(PART_BLOOD, clamp(damage/10/HEALTHSCALE, 0, 100), 1000, o);
}

struct hitweap
{
    float hits;
    int shots;
    hitweap() {hits=shots=0;}
};
hitweap accuracym[NUMGUNS];

inline void attackevent(playerent *owner, int weapon)
{
    if(owner == player1 && identexists("onAttack"))
    {
        defformatstring(onattackevent)("onAttack %d", weapon);
        execute(onattackevent);
    }
}

vector<bounceent *> bounceents;

void removebounceents(playerent *owner)
{
    loopv(bounceents) if(bounceents[i]->owner==owner) { delete bounceents[i]; bounceents.remove(i--); }
}

void movebounceents()
{
    loopv(bounceents) if(bounceents[i])
    {
        bounceent *p = bounceents[i];
        if ((p->bouncetype == BT_NADE || p->bouncetype == BT_GIB || p->bouncetype == BT_SHELL || p->bouncetype == BT_KNIFE) && p->applyphysics()) movebounceent(p, 1, false);
        if(!p->isalive(lastmillis))
        {
            p->destroy();
            delete p;
            bounceents.remove(i--);
        }
    }
}

void clearbounceents()
{
    if(gamespeed==100);
    else if(multiplayer(false)) bounceents.add((bounceent *)player1);
    loopv(bounceents) if(bounceents[i]) { delete bounceents[i]; bounceents.remove(i--); }
}

FVARP(shellsize, 0, 0.3f, 1);

void renderbounceents()
{
    loopv(bounceents)
    {
        bounceent *p = bounceents[i];
        if(!p) continue;
        string model;
        vec o(p->o);

        float scale = 1.f;
        int anim = ANIM_MAPMODEL, basetime = 0;
        switch(p->bouncetype)
        {
            case BT_KNIFE:
                copystring(model, "weapons/knife/static");
                break;
            case BT_NADE:
            if (identexists("modmdlbounce3"))
                copystring(model, getalias("modmdlbounce3"));
                else
                copystring(model, "weapons/grenade/static");
                break;
            case BT_SHELL:
            {
                defformatstring(shellalias)("modmdlweapshell%d", p->info);
                if (identexists(shellalias))
                    copystring(model, getalias(shellalias));
                else
                {
                    static const char *shellmodels[4] = { "shells/pistol", "shells/rifle", "shells/shotgun", "shells/sniper" };
                    copystring(model, shellmodels[p->info-1]);
                }
                scale = shellsize;
                int t = lastmillis - p->millis;
                if (t>p->timetolive - 2000)
                {
                    anim = ANIM_DECAY;
                    basetime = p->millis + p->timetolive - 2000;
                    t -= p->timetolive - 2000;
                    o.z -= t*t / 4000000000.0f*t;
                }
                break;
            }
            case BT_GIB:
            default:
            {
                uint n = (((4*(uint)(size_t)p)+(uint)p->timetolive)%3)+1;

                defformatstring(widn)("modmdlbounce%d", n-1);

                if (identexists(widn))
                copystring(model, getalias(widn));
                else
                formatstring(model)("misc/gib0%u", n);

                int t = lastmillis-p->millis;
                if(t>p->timetolive-2000)
                {
                    anim = ANIM_DECAY;
                    basetime = p->millis+p->timetolive-2000;
                    t -= p->timetolive-2000;
                    o.z -= t*t/4000000000.0f*t;
                }
                break;
            }
        }
        path(model);
        if (p->bouncetype == BT_SHELL) sethudgunperspective(true);
        rendermodel(model, anim|ANIM_LOOP|ANIM_DYNALLOC, 0, PLAYERRADIUS, o, p->yaw+90, p->pitch, 0, basetime, NULL, NULL, scale);
        if (p->bouncetype == BT_SHELL) sethudgunperspective(false);
    }
}

VARP(gib, 0, 1, 1);
VARP(gibnum, 0, 6, 1000);
VARP(gibttl, 0, 7000, 60000);
VARP(gibspeed, 1, 30, 100);

void addgib(playerent *d)
{
    if(!d || !gib || !gibttl || render_void) return;
    audiomgr.playsound(S_GIB, d);

    loopi(gibnum)
    {
        bounceent *p = bounceents.add(new bounceent);
        p->owner = d;
        p->millis = lastmillis;
        p->timetolive = gibttl+rnd(10)*100;
        p->bouncetype = BT_GIB;

        p->o = d->o;
        p->o.z -= d->aboveeye;
        p->inwater = hdr.waterlevel>p->o.z;

        p->yaw = (float)rnd(360);
        p->pitch = (float)rnd(360);

        p->maxspeed = 30.0f;
        p->rotspeed = 3.0f;

        const float angle = (float)rnd(360);
        const float speed = (float)gibspeed;

        p->vel.x = sinf(RAD*angle)*rnd(1000)/1000.0f;
        p->vel.y = cosf(RAD*angle)*rnd(1000)/1000.0f;
        p->vel.z = rnd(1000)/1000.0f;
        p->vel.mul(speed/100.0f);

        p->resetinterp();
    }
}

VARP(accuracy,0,0,1);

void r_accuracy(int h)
{
    if(!accuracy) return;
    vector <char*>lines;
    int rows = 0, cols = 0;
    float spacing = curfont->defaultw*2, x_offset = curfont->defaultw, y_offset = float(2*h) - 2*spacing;

    loopi(NUMGUNS) if(accuracym[i].shots)
    {
        float acc = 100.0f*accuracym[i].hits/(float)accuracym[i].shots;
        string line;
        rows++;
        if(i == GUN_GRENADE || i == GUN_SHOTGUN || i == GUN_SHOTGUN_PRO)
        {
            formatstring(line)("\f5%5.1f%s (%.1f/%d) :\f0%s", acc, "%", accuracym[i].hits, (int)accuracym[i].shots, killname(i, FRAG_NONE));
        }
        else
        {
            formatstring(line)("\f5%5.1f%s (%d/%d) :\f0%s", acc, "%", (int)accuracym[i].hits, (int)accuracym[i].shots, killname(i, FRAG_NONE));
        }
        cols=max(cols,(int)strlen(line));
        lines.add(newstring(line));
    }
    if(rows<1) return;
    cols++;
    blendbox(x_offset, spacing+y_offset, spacing+x_offset+curfont->defaultw*cols, y_offset-curfont->defaulth*rows, true, -1);
    int x=0;
    loopv(lines)
    {
        char *line = lines[i];
        draw_textf(line,spacing*0.5+x_offset,y_offset-x*curfont->defaulth-0.5*spacing);
        x++;
    }
}

void accuracyreset()
{
    loopi(NUMGUNS)
    {
        accuracym[i].hits=accuracym[i].shots=0;
    }
    conoutf(_("Your accuracy has been reset."));
}
COMMAND(accuracyreset, "");

// weapon

weapon::weapon(class playerent *owner, int type) : type(type), owner(owner), info(guns[type]),
    ammo(owner->ammo[type]), mag(owner->mag[type]), gunwait(owner->gunwait[type]), shots(0), reloading(0), lastaction(0) {}

int weapon::flashtime() const { return clamp((int)info.attackdelay/4, 60, 150); }

void weapon::sendshoot(const vec &to)
{
    if (owner != player1 && !isowned(owner)) return;
    owner->shoot = true;
    static uchar buf[MAXTRANS];
    ucharbuf p(buf, MAXTRANS);
    // standard shoot packet
    putint(p, SV_SHOOT);
    putint(p, owner->clientnum);
    putint(p, lastmillis);
    putint(p, owner->weaponsel->type);
    putint(p, (int)(to.x*DMF));
    putint(p, (int)(to.y*DMF));
    putint(p, (int)(to.z*DMF));
    // write positions
    loopv(players)
        if (players[i] && players[i]->state != CS_DEAD)
        {
            putint(p, i);
            putint(p, (int)(players[i]->o.x*DMF));
            putint(p, (int)(players[i]->o.y*DMF));
            putint(p, (int)((players[i]->o.z-players[i]->eyeheight)*DMF));
            putint(p, (int)(players[i]->head.x*DMF));
            putint(p, (int)(players[i]->head.y*DMF));
            putint(p, (int)(players[i]->head.z*DMF));
        }
    putint(p, -1);
    addmsgraw(p, true);
    // disable spawn protection
    if (lastmillis < owner->lastspawn + SPAWNPROTECT)
        owner->lastspawn = lastmillis - SPAWNPROTECT;
    owner->pstatshots[owner->weaponsel->type]++; //NEW
}

bool weapon::modelattacking()
{
    int animtime = min(owner->gunwait[owner->weaponsel->type], (int)owner->weaponsel->info.attackdelay);
    if(lastmillis - owner->lastaction < animtime) return true;
    else return false;
}

void weapon::attacksound()
{
    if(info.sound == S_NULL) return;

    if (!suppressed_weap(type))
        owner->updateradarpos(totalmillis);

    audiomgr.playsound(info.sound, owner, player1 == owner ? SP_HIGH : SP_NORMAL);
}

bool weapon::reload(bool autoreloaded)
{
    if(mag>=info.magsize || ammo<=0) return false;
    updatelastaction(owner);
    reloading = lastmillis;
    gunwait += info.reloadtime;

    int numbullets = min(info.magsize - mag, ammo);
    mag += numbullets;
    ammo -= numbullets;

    if (info.reload != S_NULL)
        audiomgr.playsound(info.reload, owner, player1 == owner ? SP_HIGH : SP_NORMAL);
    if (player1 == owner || isowned(owner))
    {
        addmsg(SV_RELOAD, "ri3", owner->clientnum, lastmillis, owner->weaponsel->type);
        if(identexists("onReload"))
        {
            defformatstring(str)("onReload %d", (int)autoreloaded);
            execute(str);
        }
    }
    return true;
}

VARP(oldfashionedgunstats, 0, 0, 1);

void weapon::renderstats()
{
    char gunstats[64];
    if(oldfashionedgunstats) sprintf(gunstats, "%i/%i", mag, ammo); else sprintf(gunstats, "%i", mag);
    draw_text(gunstats, 360, 823);
    if(!oldfashionedgunstats)
    {
        int offset = text_width(gunstats);
        glScalef(0.5f, 0.5f, 1.0f);
        sprintf(gunstats, "%i", ammo);
        draw_text(gunstats, (360 + offset)*2, 826*2);
        glLoadIdentity();
    }
}

void weapon::attackphysics(const vec &from, const vec &to) // physical fx to the owner
{
    vec unitv;
    float dist = to.dist(from, unitv);
    // kickback
    owner->vel.add(vec(unitv).mul(dynrecoil()*-0.01f / dist * owner->eyeheight / owner->maxeyeheight));
    // recoil
    const float recoilfactor = (m_real(gamemode, mutators) ? 1.35f : 1.f) / (owner->perk1 == PERK1_HAND ? 120.f : 100.f);
    const float recoilshift = (rnd(info.recoilangle * 20 + 1) / 10.f - info.recoilangle) * RAD, recoilval = info.recoil * recoilfactor * sqrtf(rnd(50) + 51);
    owner->pitchvel += cosf(recoilshift) * recoilval;
    owner->yawvel += sinf(recoilshift) * recoilval;
    const float recoilmagnitude = sqrtf(owner->pitchvel * owner->pitchvel + owner->yawvel * owner->yawvel);
    const float maxmagnitude = info.maxrecoil * recoilfactor * 10;
    if(recoilmagnitude > maxmagnitude)
    {
        owner->pitchvel *= maxmagnitude / recoilmagnitude;
        owner->yawvel *= maxmagnitude / recoilmagnitude;
    }
}

void weapon::attackhit(const vec &o)
{
    particle_splash(PART_SPARK, 5, 250, o);
}

VARP(righthanded, 0, 1, 1); // flowtron 20090727

VAR(silencertest, 0, 0, 1);
void weapon::renderhudmodel(int lastaction, int index)
{
    playerent *p = owner;
    vec unitv;
    float dist = worldpos.dist(p->o, unitv);
    unitv.div(dist);

    weaponmove wm;
    if(!intermission) wm.calcmove(unitv, lastaction, p);
//    if(!intermission) wm.calcmove(unitv, p->lastaction, p);
    defformatstring(widn)("modmdlweap%d", type);
    defformatstring(path)("weapons/%s", identexists(widn)?getalias(widn):info.modelname);
    bool emit = (wm.anim&ANIM_INDEX)==ANIM_GUN_SHOOT && (lastmillis - lastaction) < flashtime();
//    bool emit = (wm.anim&ANIM_INDEX)==ANIM_GUN_SHOOT && (lastmillis - p->lastaction) < flashtime();
    int anim = ANIM_DYNALLOC;
    if (righthanded == index) anim |= ANIM_MIRROR;
    if (emit) anim |= ANIM_PARTICLE;
    if (owner->protect(lastmillis, gamemode, mutators)) wm.anim |= ANIM_TRANSLUCENT;
    modelattach a[4]; // a null one is needed at the end
    int numattach = 0;
    if (type != GUN_AKIMBO || ((akimbo *)this)->akimboside != index)
    {
        owner->eject = vec(-1, -1, -1);
        a[0].tag = "tag_eject";
        a[0].pos = &owner->eject;
        owner->muzzle = vec(-1, -1, -1);
        a[1].tag = "tag_muzzle";
        a[1].pos = &owner->muzzle;
        numattach = 2;
    }
    if (silencertest)
    {
        a[numattach].tag = "tag_muzzle";
        a[numattach].m = loadmodel("weapons/silencer");
        if (!a[numattach].m)
            a[numattach].m = loadmodel("shells/pistol");
        // ++numattach;
    }
    rendermodel(path, wm.anim|anim, 0, -1, wm.pos, p->yaw+90, p->pitch+wm.k_rot, 40.0f, wm.basetime, NULL, a, 1.28f, p->zoomed);
}

void weapon::updatetimers(int millis)
{
    if(gunwait) gunwait = max(gunwait - (millis-owner->lastaction), 0);
}

void weapon::onselecting()
{
    updatelastaction(owner);
    audiomgr.playsound(S_GUNCHANGE, owner, owner == player1? SP_HIGH : SP_NORMAL);
}

void weapon::renderhudmodel() { renderhudmodel(owner->lastaction); }
void weapon::renderaimhelp(int teamtype) { drawcrosshair(owner, CROSSHAIR_DEFAULT, teamtype); }
int weapon::dynspread()
{
    if (info.spread <= 1) return 1;
    return (int)(info.spread * (owner->vel.magnitude() / 3.f + owner->pitchvel / 5.f + 0.4f) * 2.4f * owner->eyeheight / owner->maxeyeheight * (1 - sqrtf(owner->zoomed * info.spreadrem / 100.f)));
}
float weapon::dynrecoil() { return info.kick * (1 - owner->zoomed / 2.f); } // 1/2 recoil when ADS
bool weapon::selectable() { return this != owner->weaponsel && owner->state == CS_ALIVE && !owner->weaponchanging &&
    (type == GUN_KNIFE || type == GUN_GRENADE || type == GUN_AKIMBO || type == owner->primary || type == owner->secondary); }
bool weapon::deselectable() { return !reloading; }

void weapon::equipplayer(playerent *pl)
{
    if(!pl) return;
    pl->weapons[GUN_ASSAULT] = new assaultrifle(pl, GUN_ASSAULT);
    pl->weapons[GUN_GRENADE] = new grenades(pl);
    pl->weapons[GUN_KNIFE] = new knife(pl);
    pl->weapons[GUN_PISTOL] = new pistol(pl);
    pl->weapons[GUN_SHOTGUN] = new shotgun(pl, GUN_SHOTGUN);
    pl->weapons[GUN_SNIPER] = new scopedprimary(pl, GUN_SNIPER);
    pl->weapons[GUN_SUBGUN] = new subgun(pl);
    pl->weapons[GUN_AKIMBO] = new akimbo(pl);
    pl->weapons[GUN_BOLT] = new scopedprimary(pl, GUN_BOLT);
    pl->weapons[GUN_HEAL] = new healgun(pl);
    pl->weapons[GUN_SWORD] = new sword(pl);
    pl->weapons[GUN_RPG] = new crossbow(pl);
    pl->weapons[GUN_ASSAULT2] = new assaultrifle(pl, GUN_ASSAULT2);
    pl->weapons[GUN_SNIPER2] = new scopedprimary(pl, GUN_SNIPER2);
    pl->weapons[GUN_SNIPER3] = new scopedprimary(pl, GUN_SNIPER3);
    pl->weapons[GUN_PISTOL2] = new m1911(pl);
    pl->weapons[GUN_ASSAULT_PRO] = new assaultrifle(pl, GUN_ASSAULT_PRO);
    pl->weapons[GUN_SHOTGUN_PRO] = new shotgun(pl, GUN_SHOTGUN_PRO);
    pl->weapons[GUN_ACR_PRO] = new assaultrifle(pl, GUN_ACR_PRO);
    pl->primary = GUN_ASSAULT;
    pl->selectweapon(GUN_ASSAULT);
}

bool weapon::valid(int id) { return id>=0 && id<NUMGUNS; }

// grenadeent

enum { NS_NONE, NS_ACTIVATED = 0, NS_THROWN, NS_EXPLODED };

grenadeent::grenadeent (playerent *owner, int millis)
{
    ASSERT(owner);
    nadestate = NS_NONE;
    local = owner==player1 || isowned(owner);
    bounceent::owner = owner;
    bounceent::millis = id = lastmillis;
    timetolive = NADETTL-millis;
    bouncetype = BT_NADE;
    maxspeed = 30.0f;
    rotspeed = 6.0f;
    distsincebounce = 0.0f;
}

grenadeent::~grenadeent()
{
    if(owner && owner->weapons[GUN_GRENADE]) owner->weapons[GUN_GRENADE]->removebounceent(this);
}

void grenadeent::explode()
{
    if(nadestate!=NS_ACTIVATED && nadestate!=NS_THROWN ) return;
    nadestate = NS_EXPLODED;
    if(local)
        addmsg(SV_EXPLODE, "ri7", owner->clientnum, lastmillis, GUN_GRENADE, id, (int)(o.x*DMF), (int)(o.y*DMF), (int)(o.z*DMF));
}

void grenadeent::activate()
{
    if(nadestate!=NS_NONE) return;
    nadestate = NS_ACTIVATED;

    if(local)
    {
        addmsg(SV_SHOOTC, "ri3", owner->clientnum, millis, owner->weaponsel->type);
        audiomgr.playsound(S_GRENADEPULL, owner, SP_HIGH);
        player1->pstatshots[GUN_GRENADE]++; //NEW
    }
}

void grenadeent::_throw(const vec &from, const vec &vel)
{
    if(nadestate!=NS_ACTIVATED) return;
    nadestate = NS_THROWN;
    this->vel = vel;
    this->o = from;
    this->resetinterp();
    inwater = hdr.waterlevel>o.z;
    if(local)
    {
        addmsg(SV_THROWNADE, "ri8", owner->clientnum, int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF), lastmillis-millis);
        audiomgr.playsound(S_GRENADETHROW, SP_HIGH);
    }
    else audiomgr.playsound(S_GRENADETHROW, owner);
}

void grenadeent::moveoutsidebbox(const vec &direction, playerent *boundingbox)
{
    vel = direction;
    o = boundingbox->o;
    inwater = hdr.waterlevel>o.z;

    boundingbox->cancollide = false;
    loopi(10) moveplayer(this, 10, true, 10);
    boundingbox->cancollide = true;
}

void grenadeent::destroy() { explode(); }
bool grenadeent::applyphysics() { return nadestate==NS_THROWN; }

void grenadeent::oncollision()
{
    if(distsincebounce>=1.5f) audiomgr.playsound(S_GRENADEBOUNCE1+rnd(2), &o);
    distsincebounce = 0.0f;
}

void grenadeent::onmoved(const vec &dist)
{
    distsincebounce += dist.magnitude();
}

// grenades

grenades::grenades(playerent *owner) : weapon(owner, GUN_GRENADE), inhandnade(NULL), throwwait(325), throwmillis(0), state(GST_NONE) {}

int grenades::flashtime() const { return 0; }

bool grenades::busy() { return state!=GST_NONE; }

bool grenades::attack(vec &targ)
{
    int attackmillis = lastmillis-owner->lastaction;

    bool quickwait = attackmillis*3>=gunwait && !(m_survivor(gamemode, mutators) && m_team(gamemode, mutators) && arenaintermission);
    bool waitdone = attackmillis>=gunwait && quickwait;
    if(waitdone) gunwait = reloading = 0;

    switch(state)
    {
        case GST_NONE:
            if(waitdone && owner->attacking && this==owner->weaponsel)
            {
                attackevent(owner, type);
                activatenade(); // activate
            }
        break;

        case GST_INHAND:
            if(waitdone || ( quicknade && quickwait ) )
            {
                if(!owner->attacking || this!=owner->weaponsel) thrownade(); // throw
                else if(!inhandnade->isalive(lastmillis)) dropnade(); // drop & have fun
            }
            break;

        case GST_THROWING:
            if(attackmillis >= throwwait) // throw done
            {
                reset();
                if(!mag && this==owner->weaponsel) // switch to primary immediately
                {
                    addmsg(SV_QUICKSWITCH, "ri", owner->clientnum);
                    owner->weaponchanging = lastmillis - 1 - (SWITCHTIME(owner->perk2 == PERK_TIME) / 2);
                    owner->nextweaponsel = owner->weaponsel = owner->weapons[owner->primary];
                }
                return false;
            }
            break;
    }
    return true;
}

void grenades::attackfx(const vec &from, const vec &to, int millis) // other player's grenades
{
    throwmillis = lastmillis-millis;
    if (millis < 0)
    {
        state = GST_INHAND;
        audiomgr.playsound(S_GRENADEPULL, owner); // activate
    }
    else // if(millis > 0) // throw
    {
        grenadeent *g = new grenadeent(owner, millis);
        state = GST_THROWING;
        bounceents.add(g);
        g->_throw(from, to);
    }
}

inline void explosioneffect(const vec &o)
{
    particle_splash(PART_SPARK, 50, 300, o);
    adddynlight(NULL, o, 16, 200, 100, 255, 255, 224);
    adddynlight(NULL, o, 16, 600, 600, 192, 160, 128);
    audiomgr.playsound(S_FEXPLODE, &o);
}

VARP(nadedetail, 0, 7, 16);

void grenades::attackhit(const vec &o)
{
    particle_fireball(PART_FIREBALL, o, owner);
    addscorchmark(o);
    explosioneffect(o);
    extern int shotline, shotlinettl;
    if (shotline && shotlinettl && nadedetail)
    {
        const float halfnadedetail = nadedetail / 2.f;
        loopi(nadedetail) loopj(nadedetail) loopk(nadedetail)
        {
            vec t(i / halfnadedetail - 1, j / halfnadedetail - 1, k / halfnadedetail - 1);
            t.add(o);
            traceShot(o, t);
            addshotline(owner, o, t, 2);
            particle_splash(PART_SPARK, 8, 250, t);
        }
    }
    if (owner == player1)
        accuracym[GUN_GRENADE].shots++;
}

int grenades::modelanim()
{
    if(state == GST_THROWING) return ANIM_GUN_THROW;
    else
    {
        int animtime = min(gunwait, (int)info.attackdelay);
        if(state == GST_INHAND || lastmillis - owner->lastaction < animtime) return ANIM_GUN_SHOOT;
    }
    return ANIM_GUN_IDLE;
}

void grenades::activatenade()
{
    if(!mag) return;
    throwmillis = 0;

    inhandnade = new grenadeent(owner);
    bounceents.add(inhandnade);

    updatelastaction(owner);
    mag--;
    gunwait = info.attackdelay;
    owner->lastattackweapon = this;
    state = GST_INHAND;
    inhandnade->activate();
}

void grenades::thrownade()
{
    if (quicknade && owner->weaponsel->type == GUN_GRENADE) selectweapon(owner->prevweaponsel);
    quicknade = false;
    if(!inhandnade) return;
    const float speed = cosf(RAD*owner->pitch);
    vec vel(sinf(RAD*owner->yaw)*speed, -cosf(RAD*owner->yaw)*speed, sinf(RAD*owner->pitch));
    vel.mul(NADEPOWER);
    thrownade(vel);
}

void grenades::thrownade(const vec &vel)
{
    inhandnade->moveoutsidebbox(vel, owner);
    inhandnade->_throw(inhandnade->o, vel);
    inhandnade = NULL;

    throwmillis = lastmillis;
    updatelastaction(owner);
    state = GST_THROWING;
    if(this==owner->weaponsel) owner->attacking = false;
}

void grenades::dropnade()
{
    vec n(0,0,0);
    thrownade(n);
}

void grenades::renderstats()
{
    char gunstats[64];
    sprintf(gunstats, "%i", mag);
    draw_text(gunstats, 830, 823);
}

bool grenades::selectable() { return weapon::selectable() && state != GST_INHAND && mag; }
void grenades::reset() { throwmillis = 0; state = GST_NONE; }

void grenades::onselecting() { reset(); weapon::onselecting(); }
void grenades::onownerdies() { reset(); if (owner == player1 && inhandnade) dropnade(); }

void grenades::removebounceent(bounceent *b)
{
    if(b == inhandnade) { inhandnade = NULL; reset(); }
}

// gun base class

gun::gun(playerent *owner, int type, int shelltype) : weapon(owner, type), autoreloading(false), shelltype(shelltype) {}

bool gun::attack(vec &targ)
{
    // Cancel auto-reload
    if(owner == player1 && owner->attacking)
        autoreloading = false;

    int attackmillis = lastmillis-owner->lastaction - gunwait;
    if(attackmillis<0) return false;
    gunwait = reloading = 0;

    if(!owner->attacking)
    {
        shots = 0;
        if(owner == player1 || isowned(player1)) checkautoreload();
        return false;
    }

    attackmillis = lastmillis - min(attackmillis, curtime);
    updatelastaction(owner, attackmillis);
    if(!mag)
    {
        audiomgr.playsoundc(S_NOAMMO, owner);
        gunwait += 250;
        owner->lastattackweapon = NULL;
        shots = 0;
        owner->attacking = false;
        if (!checkautoreload())
        {
            if (owner->secondary != owner->primary)
            {
                if (type != owner->secondary && (owner->weapons[owner->secondary]->mag || owner->weapons[owner->secondary]->ammo))
                    selectweapon(owner->weapons[owner->secondary]);
                else if (type != owner->primary && (owner->weapons[owner->primary]->mag || owner->weapons[owner->primary]->ammo))
                    selectweapon(owner->weapons[owner->primary]);
                else audiomgr.playsoundc(S_NOAMMO, owner);
            }
            else audiomgr.playsoundc(S_NOAMMO, owner);
        }
        return false;
    }

    owner->lastattackweapon = this;
    shots++;

    if(!info.isauto) owner->attacking = false;

    if(burstshotssettings[this->type] > 0 && shots >= burstshotssettings[this->type]) owner->attacking = false;

    vec from = owner->o;
    vec to = targ;

    attackphysics(from, to);

    attackevent(owner, type);

    attacksound();
    attackshell(to);

    gunwait = info.attackdelay;
    mag--;

    sendshoot(to);

    return true;
}

VARP(shellttl, 0, 4000, 20000);

void gun::attackshell(const vec &to)
{
    extern int hudgun;
    if (!shellttl || !shelltype || (owner == focus && !hudgun)) return;
    bounceent *s = bounceents.add(new bounceent);
    s->owner = owner;
    s->millis = lastmillis;
    s->timetolive = gibttl;
    s->bouncetype = BT_SHELL;
    s->info = shelltype;

    const bool akimboflip = (type != GUN_AKIMBO || ((akimbo *)this)->akimboside == 0) != righthanded;
    s->vel = vec(1, rnd(101) / 800.f - .1f, (rnd(51) + 50) / 100.f);
    s->vel.rotate_around_z(owner->yaw*RAD);
    if (owner->eject.x >= 0)
        s->o = owner->eject;
    else
    {
        // "fake" shell position
        s->o = owner->o;
        s->o.add(vec(s->vel.x * owner->radius, s->vel.y * owner->radius, -WEAPONBELOWEYE));
    }
    s->vel.mul(.02f * (rnd(3) + 5));
    if (akimboflip) s->vel.rotate_around_z(180 * RAD);
    vec ownervel = owner->vel;
    ownervel.mul(0.6f); // tweaked until it "felt right"
    ownervel.z *= 0.3f; // tweaked until it "felt right"
    s->vel.add(ownervel);
    s->inwater = hdr.waterlevel > owner->o.z;
    s->cancollide = false;

    s->yaw = owner->yaw + 180;
    s->pitch = -owner->pitch;

    s->maxspeed = 30.f;
    s->rotspeed = 3.f;

    s->resetinterp();
}

void gun::attackfx(const vec &from, const vec &to, int millis)
{
    addbullethole(owner, from, to);
    addshotline(owner, from, to, millis & 1);
    particle_splash(PART_SPARK, 5, 250, to);
    adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
    if ((millis & 1) && owner != player1 && !isowned(owner))
    {
        attacksound();
        attackshell(to);
    }
}

int gun::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT|ANIM_LOOP : ANIM_GUN_IDLE; }

bool gun::reload(bool autoreloaded)
{
    if (owner == player1)
        autoreloading = (mag + reloadsize(type) < magsize(type)) && ammo;
    return weapon::reload(autoreloaded);
}

bool gun::checkautoreload()
{
    if (owner != player1) return false;
    if (autoreloading || (autoreload && !mag && ammo))
    {
        reload(true);
        return true;
    }
    return false;
}


// shotgun

//shotgun::shotgun(playerent *owner) : gun(owner, GUN_SHOTGUN, 3) {}

int shotgun::dynspread() { return info.spread * (1 - owner->zoomed * info.spreadrem / 100.f); }

void shotgun::attackfx(const vec &from, const vec &to, int millis)
{
    static uchar filter1 = 0, filter2 = 0;
    if (millis & 1)
    {
        loopi(SGRAYS)
            particle_splash(PART_SPARK, 5, 200, sg[i]);
        if (addbullethole(owner, from, to))
            loopi(SGRAYS)
            {
                if (++filter1 >= 3) filter1 = 0;
                else addshotline(owner, from, sg[i], 3);
                addbullethole(owner, from, sg[i], 0, false);
            }
        adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
        if (owner != player1 && !isowned(owner))
        {
            attackshell(to);
            attacksound();
        }
    }
    else
    {
        if (++filter2 >= 2) filter2 = 0;
        else addshotline(owner, from, to, 2);
        addbullethole(owner, from, to, 0, false);
    }
    adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
}

void shotgun::renderaimhelp(int teamtype){ drawcrosshair(owner, CROSSHAIR_SHOTGUN, teamtype); }


// sword

sword::sword(playerent *owner) : weapon(owner, GUN_SWORD) {}

bool sword::attack(vec &targ)
{
    int attackmillis = lastmillis - owner->lastaction;
    if (attackmillis<gunwait) return false;
    gunwait = reloading = 0;

    if (!owner->attacking) return false;
    updatelastaction(owner);

    owner->lastattackweapon = this;
    owner->attacking = info.isauto;

    attacksound();

    sendshoot(targ);
    gunwait = info.attackdelay;
    return true;
}
int sword::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT : ANIM_GUN_IDLE; }

void sword::attackfx(const vec &from, const vec &to, int millis) { if (owner != player1 && !isowned(owner)) attacksound(); }

int sword::flashtime() const { return 0; }

// crossbow (RPG)

crossbow::crossbow(playerent *owner) : gun(owner, GUN_RPG, 0) {}
int crossbow::modelanim()
{
    // very simple and stupid animation system
    return mag ? ANIM_GUN_IDLE : ANIM_GUN_SHOOT;
}

void crossbow::attackfx(const vec &from2, const vec &to, int millis)
{
    vec from(from2);
    if (millis & 1)
    {
        if (owner->muzzle.x >= 0)
            from = owner->muzzle;
        else from.z -= WEAPONBELOWEYE;
        if (owner != player1 && !isowned(owner)) attacksound();
    }
    addshotline(owner, from, to, millis & 1);
    particle_trail(PART_SHOTLINE_RPG, 400, from, to);
    particle_splash(PART_SPARK, 5, 250, to);
}

void crossbow::attackhit(const vec &o)
{
    particle_fireball(PART_FIREBALL_RPG, o, owner);
    explosioneffect(o);
}


// scopedprimary

void scopedprimary::attackfx(const vec &from2, const vec &to, int millis)
{
    vec from(from2);
    if (millis & 1)
    {
        if (owner->muzzle.x >= 0)
            from = owner->muzzle;
        else from.z -= WEAPONBELOWEYE;
        if (owner != player1 && !isowned(owner))
        {
            attackshell(to);
            attacksound();
        }
    }

    addbullethole(owner, from, to);
    addshotline(owner, from, to, 0);
    particle_splash(PART_SPARK, 50, 200, to);
    particle_trail(PART_SMOKE, 500, from, to);
    adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
}

float scopedprimary::dynrecoil() { return weapon::dynrecoil() * (1 - owner->zoomed / 3.f); } // 1/2 * 2/3 = 1/3 recoil when ADS
void scopedprimary::renderhudmodel() { if (owner->zoomed < ADSZOOM) weapon::renderhudmodel(); }

void scopedprimary::renderaimhelp(int teamtype)
{
    if (owner->zoomed < ADSZOOM)
        weapon::renderaimhelp(teamtype);
    else
    {
        drawscope();
        drawcrosshair(owner, CROSSHAIR_SCOPE, teamtype);
    }
}


// assaultrifle

float assaultrifle::dynrecoil() { return weapon::dynrecoil() + (rnd(8)*-0.01f); }


// akimbo

akimbo::akimbo(playerent *owner) : gun(owner, GUN_AKIMBO, 1), akimboside(0), akimbomillis(0)
{
    akimbolastaction[0] = akimbolastaction[1] = 0;
}

bool akimbo::attack(vec &targ){
    if (gun::attack(targ))
    {
        akimbolastaction[akimboside & 1] = lastmillis;
        akimboside ^= true;
        return true;
    }
    return false;
}

void akimbo::onammopicked()
{
    akimbomillis = lastmillis + 30000;
    if (owner == player1 || isowned(owner))
    {
        // if(owner->weaponsel->type!=GUN_SNIPER && owner->weaponsel->type!=GUN_GRENADE) owner->weaponswitch(this);
        if(akimboautoswitch || owner->weaponsel->type==GUN_PISTOL) owner->weaponswitch(this); // Give the client full control over akimbo auto-switching // Bukz 2011apr23
        addmsg(SV_AKIMBO, "ri2", owner->clientnum, lastmillis);
    }
}

void akimbo::onselecting()
{
    gun::onselecting();
    akimbolastaction[0] = akimbolastaction[1] = lastmillis;
}

bool akimbo::selectable() { return weapon::selectable() && !m_nosecondary(gamemode, mutators) && owner->akimbo; }
void akimbo::updatetimers(int millis) { weapon::updatetimers(millis); /*loopi(2) akimbolastaction[i] = millis;*/ }
void akimbo::reset() { akimbolastaction[0] = akimbolastaction[1] = akimbomillis = akimboside = 0; }

void akimbo::renderhudmodel()
{
    weapon::renderhudmodel(akimbolastaction[0], 0);
    weapon::renderhudmodel(akimbolastaction[1], 1);
}

bool akimbo::timerout() { return akimbomillis && akimbomillis <= lastmillis; }


// healgun

healgun::healgun(playerent *owner) : gun(owner, GUN_HEAL, 0) {}

void healgun::attackfx(const vec &from2, const vec &to, int millis)
{
    vec from(from2);
    if (millis & 1)
    {
        if (owner->muzzle.x >= 0)
            from = owner->muzzle;
        else from.z -= WEAPONBELOWEYE;
        if (owner != player1 && !isowned(owner)) attacksound();
    }

    addshotline(owner, from, to, 0);
    particle_trail(PART_SHOTLINE_HEAL, 400, from, to);
    particle_splash(PART_SPARK, 3, 200, to);
}


vector<cconfirm> confirms;
vector<cknife> knives;


// knifeent
// FIXME: too much stuff is copied from grenadeent, and it would make more sense
// to have grenadeent and knifeent inherit from another class throwent
knifeent::knifeent(playerent *owner, int millis)
{
    ASSERT(owner);
    knifestate = NS_NONE;
    local = owner == player1 || isowned(owner);
    bounceent::owner = owner;
    bounceent::millis = lastmillis;
    timetolive = KNIFETTL - millis;
    bouncetype = BT_KNIFE;
    maxspeed = 25.0f;
    radius = .2f;
    aboveeye = .25f;
    eyeheight = maxeyeheight = .25f;
    yaw = owner->yaw + 180;
    pitch = 75 - owner->pitch;
    roll = owner->roll;
    rotspeed = 0;
    hit = NULL;
}

knifeent::~knifeent()
{
    if (owner && owner->weapons[GUN_KNIFE])
        owner->weapons[GUN_KNIFE]->removebounceent(this);
}

void knifeent::explode()
{
    if (knifestate != NS_ACTIVATED && knifestate != NS_THROWN) return;
    knifestate = NS_EXPLODED;
    if (local)
        addmsg(SV_EXPLODE, "ri7", owner->clientnum, lastmillis, GUN_KNIFE, hit ? hit->clientnum : -1, (int)(o.x*DMF), (int)(o.y*DMF), (int)(o.z*DMF));
    audiomgr.playsound(S_GRENADEBOUNCE1 + rnd(2), &o);
    timetolive = 0;
}

void knifeent::activate()
{
    if (knifestate != NS_NONE) return;
    knifestate = NS_ACTIVATED;

    if (local) addmsg(SV_SHOOTC, "ri3", owner->clientnum, millis, GUN_KNIFE);
    //audiomgr.playsound(S_KNIFEPULL, owner,  local ? SP_HIGH : SP_NORMAL);
}

void knifeent::_throw(const vec &from, const vec &vel)
{
    if (knifestate != NS_ACTIVATED) return;
    knifestate = NS_THROWN;
    this->vel = vel;
    o = from;
    resetinterp();
    inwater = hdr.waterlevel>o.z;

    if (local) addmsg(SV_THROWKNIFE, "ri7", owner->clientnum, int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF));
    audiomgr.playsound(S_GRENADETHROW, SP_HIGH); // S_KNIFETHROW
}

void knifeent::moveoutsidebbox(const vec &direction, playerent *boundingbox)
{
    vel = direction;
    o = boundingbox->o;
    inwater = hdr.waterlevel>o.z;

    boundingbox->cancollide = false;
    loopi(10) moveplayer(this, 10, true, 10);
    boundingbox->cancollide = true;
}

void knifeent::destroy() { explode(); }
bool knifeent::applyphysics() { return timetolive && knifestate == NS_THROWN; }

void knifeent::oncollision()
{
    if (vel.magnitude() < 2.f) timetolive = 0;
    else vel.mul(0.4f);
}

bool knifeent::trystick(playerent *pl)
{
    hit = pl;
    timetolive = 0;
    return true;
}


// knife

knife::knife(playerent *owner) : weapon(owner, GUN_KNIFE), inhandknife(NULL), state(GST_NONE) {}

int knife::flashtime() const { return 0; }

bool knife::busy() { return state != GST_NONE; }

bool knife::attack(vec &targ)
{
    int attackmillis = lastmillis-owner->lastaction - gunwait;
    if (owner->scoping || state)
    {
        const bool quickwait = attackmillis*3>=gunwait && !(m_survivor(gamemode, mutators) && m_team(gamemode, mutators) && arenaintermission);
        const bool waitdone = attackmillis + gunwait >= 500 && quickwait;
        switch (state)
        {
            case GST_NONE:
                if (waitdone && owner->scoping && this == owner->weaponsel) activateknife(); // activate
                break;
            case GST_INHAND:
                if (inhandknife && (waitdone || ( quicknade && quickwait )))
                {
                    if (!owner->scoping || this != owner->weaponsel) throwknife(); // throw
                    else if (!inhandknife->isalive(lastmillis)) throwknife(true);
                }
                break;
            case GST_THROWING:
                if (attackmillis + gunwait >= 250)
                {
                    reset();
                    if (!ammo && this==owner->weaponsel)
                    {
                        addmsg(SV_QUICKSWITCH, "ri", owner->clientnum);
                        owner->weaponchanging = lastmillis - 1 - (SWITCHTIME(owner->perk2 == PERK_TIME) / 2);
                        owner->nextweaponsel = owner->weaponsel = owner->weapons[owner->primary];
                    }
                    return false;
                }
                break;
        }
        return true;
    }
    if(attackmillis<0) return false;
    gunwait = reloading = 0;

    if(!owner->attacking) return false;

    attackmillis = lastmillis - min(attackmillis, curtime);
    updatelastaction(owner, attackmillis);

    owner->lastattackweapon = this;
    owner->attacking = info.isauto;

    attacksound();
    sendshoot(targ);
    gunwait = info.attackdelay;

    return true;
}

void knife::reset() { state = GST_NONE; }
bool knife::selectable() { return weapon::selectable() && mag; }
int knife::modelanim()
{
    if (state == GST_THROWING) return ANIM_GUN_THROW;
    else
    {
        //int animtime = min(gunwait, (int)info.attackdelay);
        if (state == GST_INHAND /*|| lastmillis - owner->lastaction < animtime*/) return ANIM_GUN_RELOAD;
    }
    return modelattacking() ? ANIM_GUN_SHOOT : ANIM_GUN_IDLE;
}

void knife::onownerdies()
{
    reset();
    if (owner == player1 && inhandknife)
        throwknife(true); // muscle spasm
}

void knife::removebounceent(bounceent *b)
{
    if (b == inhandknife) { inhandknife = NULL; reset(); }
}

void knife::activateknife()
{
    if (!ammo) return;

    inhandknife = new knifeent(owner);
    bounceents.add(inhandknife);

    updatelastaction(owner);
    ammo--;
    gunwait = info.attackdelay;
    owner->lastattackweapon = this;
    state = GST_INHAND;
    inhandknife->activate();
}

void knife::throwknife(bool weak)
{
    if (quicknade && owner->weaponsel->type == GUN_KNIFE) selectweapon(owner->prevweaponsel);
    quicknade = false;
    if (!inhandknife) return;
    vec vel(sinf(RAD*owner->yaw) * cosf(RAD*owner->pitch), -cosf(RAD*owner->yaw) * cosf(RAD*owner->pitch), sinf(RAD*owner->pitch));
    vel.mul(weak ? NADEPOWER : KNIFEPOWER);
    throwknife(vel);
}

void knife::throwknife(const vec &vel)
{
    inhandknife->moveoutsidebbox(vel, owner);
    inhandknife->_throw(inhandknife->o, vel);
    inhandknife = NULL;

    updatelastaction(owner);
    state = GST_THROWING;
    if (this == owner->weaponsel) owner->attacking = false;
}

void knife::attackfx(const vec &from, const vec &to, int millis)
{
    if (from.iszero() && to.iszero() && millis < 0)
    {
        state = GST_INHAND;
        //audiomgr.playsound(S_KNIFEEPULL, owner, SP_HIGH);
    }
    else if (millis == 1)
    {
        knifeent *g = new knifeent(owner);
        state = GST_THROWING;
        bounceents.add(g);
        g->_throw(from, to);
    }
    else if ((millis & 1) && owner != player1 && !isowned(owner)) attacksound();
}


void setscope(bool enable)
{
    if (intermission || player1->state != CS_ALIVE || player1->scoping == enable) return;
    if (player1->weaponsel->type == GUN_KNIFE || (ads_gun(player1->weaponsel->type) && ads_classic_allowed(player1->weaponsel->type)))
        player1->scoping = enable;
}

COMMANDF(setscope, "i", (int *on) { setscope(*on != 0); });


void shoot(playerent *p, vec &targ)
{
    if(p->state!=CS_ALIVE || p->weaponchanging) return;
    weapon *weap = p->weaponsel;
    if(weap)
    {
        weap->attack(targ);
        loopi(NUMGUNS)
        {
            weapon *bweap = p->weapons[i];
            if(bweap != weap && bweap->busy()) bweap->attack(targ);
        }
    }
}

void checkakimbo()
{
    if(player1->akimbo)
    {
        akimbo &a = *((akimbo *)player1->weapons[GUN_AKIMBO]);
        if(a.timerout() || player1->state == CS_DEAD)
        {
            weapon &p = *player1->weapons[GUN_PISTOL];
            player1->akimbo = false;
            a.reset();
            // transfer ammo to pistol
            p.mag = min((int)p.info.magsize, max(a.mag, p.mag));
            p.ammo = max(p.ammo, p.ammo);
            // fix akimbo magcontent
            a.mag = 0;
            a.ammo = 0;
            if(player1->weaponsel->type==GUN_AKIMBO)
            {

                switch(akimboendaction)
                {
                    case 0: player1->weaponswitch(player1->weapons[GUN_KNIFE]); break;
                    case 1:
                    {
                        if(player1->weapons[GUN_PISTOL]->ammo) player1->weaponswitch(&p);
                        else player1->weaponswitch(player1->weapons[GUN_KNIFE]);
                        break;
                    }
                    case 2:
                    {
                        if(player1->mag[GUN_GRENADE]) player1->weaponswitch(player1->weapons[GUN_GRENADE]);
                        else {
                            if(player1->weapons[GUN_PISTOL]->ammo) player1->weaponswitch(&p);
                            else player1->weaponswitch(player1->weapons[GUN_KNIFE]);
                        }
                        break;
                    }
                    case 3:
                    {
                        if(player1->ammo[player1->primary]) player1->weaponswitch(player1->weapons[player1->primary]);
                        else {
                            if(player1->mag[GUN_GRENADE]) player1->weaponswitch(player1->weapons[GUN_GRENADE]);
                            else {
                                if(player1->weapons[GUN_PISTOL]->ammo) player1->weaponswitch(&p);
                                else player1->weaponswitch(player1->weapons[GUN_KNIFE]);
                            }
                        }
                        break;
                    }
                    default: break;
                /*
                    case 0: player1->weaponswitch(&p); break;
                    case 1:
                    {
                        if( player1->ammo[player1->primary] ) player1->weaponswitch(player1->weapons[player1->primary]);
                        else player1->weaponswitch(&p);
                        break;
                    }
                    case 2:
                    {
                        if( player1->mag[GUN_GRENADE] ) player1->weaponswitch(player1->weapons[GUN_GRENADE]);
                        else
                        {
                            if( player1->ammo[player1->primary] ) player1->weaponswitch(player1->weapons[player1->primary]);
                            else player1->weaponswitch(&p);
                        }
                        break;
                    }
                    default: break;
                */
                }
            }
            if(player1->state != CS_DEAD) audiomgr.playsoundc(S_AKIMBOOUT);
        }
    }
}

void checkweaponstate()
{
    checkweaponswitch();
    checkakimbo();
}
Advertisement