code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "fish.h"
#include "anims.h"
#include "engine.h"
#include "sequence.h"
#include "random.h"
#include "runlist.h"
#include "exhumed.h"
#include "move.h"
#include "trigdat.h"
#include "init.h"
#include "sound.h"
#include "save.h"
#include <assert.h>

#define kMaxFishes  128
#define kMaxChunks  128

static actionSeq ActionSeq[] = {
    {8,  0},
    {8,  0},
    {0,  0},
    {24, 0},
    {8,  0},
    {32, 1},
    {33, 1},
    {34, 1},
    {35, 1},
    {39, 1}
};

struct Fish
{
    short nHealth;
    short nFrame;
    short nAction;
    short nSprite;
    short nTarget;
    short field_A;
    short field_C;
    short nRun;
};

struct Chunk
{
    short nSprite;
    short field_2;
    short field_4;
    short field_6;
};

short FishCount = 0;
short nChunksFree = 0;
int nFreeChunk[kMaxChunks] = {0};
Fish FishList[kMaxFishes];
Chunk FishChunk[kMaxChunks];

void InitFishes()
{
    FishCount = 0;
    nChunksFree = kMaxChunks;

    for (int i = 0; i < kMaxChunks; i++) {
        nFreeChunk[i] = i;
    }
}

int BuildFishLimb(short nFish, short edx)
{
    if (nChunksFree <= 0) {
        return -1;
    }

    short nSprite = FishList[nFish].nSprite;

    nChunksFree--;

    int nFree = nFreeChunk[nChunksFree];

    int nSprite2 = insertsprite(sprite[nSprite].sectnum, 99);
    assert(nSprite2 >= 0 && nSprite2 < kMaxSprites);

    FishChunk[nFree].nSprite = nSprite2;
    FishChunk[nFree].field_4 = edx + 40;
    FishChunk[nFree].field_2 = RandomSize(3) % SeqSize[SeqOffsets[kSeqFish] + edx + 40];

    sprite[nSprite2].x = sprite[nSprite].x;
    sprite[nSprite2].y = sprite[nSprite].y;
    sprite[nSprite2].z = sprite[nSprite].z;
    sprite[nSprite2].cstat = 0;
    sprite[nSprite2].shade = -12;
    sprite[nSprite2].pal = 0;
    sprite[nSprite2].xvel = (RandomSize(5) - 16) << 8;
    sprite[nSprite2].yvel = (RandomSize(5) - 16) << 8;
    sprite[nSprite2].xrepeat = 64;
    sprite[nSprite2].yrepeat = 64;
    sprite[nSprite2].xoffset = 0;
    sprite[nSprite2].yoffset = 0;
    sprite[nSprite2].zvel = (-(RandomByte() + 512)) * 2;

    seq_GetSeqPicnum(kSeqFish, FishChunk[nFree].field_4, 0);

    sprite[nSprite2].picnum = edx;
    sprite[nSprite2].lotag = runlist_HeadRun() + 1;
    sprite[nSprite2].clipdist = 0;

//	GrabTimeSlot(3);

    sprite[nSprite2].extra = -1;
    sprite[nSprite2].owner = runlist_AddRunRec(sprite[nSprite2].lotag - 1, nFree, kRunFishLimb);
    sprite[nSprite2].hitag = runlist_AddRunRec(NewRun, nFree, kRunFishLimb);

    return nFree | 0x200000;
}

void BuildBlood(int x, int y, int z, short nSector)
{
    BuildAnim(-1, kSeqFish, 36, x, y, z, nSector, 75, 128);
}

void FuncFishLimb(int a, int UNUSED(nDamage), int nRun)
{
    short nFish = RunData[nRun].nVal;
    short nSprite = FishChunk[nFish].nSprite;
    assert(nSprite >= 0 && nSprite < kMaxSprites);

    int nSeq = SeqOffsets[kSeqFish] + FishChunk[nFish].field_4;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        case 0x20000:
        {
            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, FishChunk[nFish].field_2);

            Gravity(nSprite);

            FishChunk[nFish].field_2++;

            if (FishChunk[nFish].field_2 >= SeqSize[nSeq])
            {
                FishChunk[nFish].field_2 = 0;
                if (RandomBit()) {
                    BuildBlood(sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum);
                }
            }

            int FloorZ = sector[sprite[nSprite].sectnum].floorz;

            if (FloorZ <= sprite[nSprite].z)
            {
                sprite[nSprite].z += 256;

                if ((sprite[nSprite].z - FloorZ) > 25600)
                {
                    sprite[nSprite].zvel = 0;
                    runlist_DoSubRunRec(sprite[nSprite].owner);
                    runlist_FreeRun(sprite[nSprite].lotag - 1);
                    runlist_SubRunRec(sprite[nSprite].hitag);
                    mydeletesprite(nSprite);
                }
                else if ((sprite[nSprite].z - FloorZ) > 0)
                {
                    sprite[nSprite].zvel = 1024;
                }

                return;
            }
            else
            {
                if (movesprite(nSprite, sprite[nSprite].xvel << 8, sprite[nSprite].yvel << 8, sprite[nSprite].zvel, 2560, -2560, CLIPMASK1))
                {
                    sprite[nSprite].xvel = 0;
                    sprite[nSprite].yvel = 0;
                }
            }

            return;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, nSeq, FishChunk[nFish].field_2, 1);
            return;
        }
    }
}

int BuildFish(int nSprite, int x, int y, int z, int nSector, int nAngle)
{
    short nFish = FishCount;
    FishCount++;

    if (nFish >= kMaxFishes) {
        return -1;
    }

    if (nSprite == -1)
    {
        nSprite = insertsprite(nSector, 103);
    }
    else
    {
        x = sprite[nSprite].x;
        y = sprite[nSprite].y;
        z = sprite[nSprite].z;
        nAngle = sprite[nSprite].ang;
        changespritestat(nSprite, 103);
    }

    assert(nSprite >= 0 && nSprite < kMaxSprites);

    sprite[nSprite].x = x;
    sprite[nSprite].y = y;
    sprite[nSprite].z = z;
    sprite[nSprite].cstat = 0x101;
    sprite[nSprite].shade = -12;
    sprite[nSprite].clipdist = 80;
    sprite[nSprite].xrepeat = 40;
    sprite[nSprite].yrepeat = 40;
    sprite[nSprite].pal = sector[sprite[nSprite].sectnum].ceilingpal;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].picnum = seq_GetSeqPicnum(kSeqFish, ActionSeq[0].a, 0);
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].ang = nAngle;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].extra = -1;

//	GrabTimeSlot(3);

    FishList[nFish].nAction = 0;
    FishList[nFish].nHealth = 200;
    FishList[nFish].nSprite = nSprite;
    FishList[nFish].nTarget = -1;
    FishList[nFish].field_C = 60;
    FishList[nFish].nFrame = 0;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nFish, kRunFish);
    FishList[nFish].nRun = runlist_AddRunRec(NewRun, nFish, kRunFish);

    nCreaturesLeft++;

    return nFish | 0x120000;
}

void IdleFish(short nFish, short edx)
{
    short nSprite = FishList[nFish].nSprite;

    sprite[nSprite].ang += (256 - RandomSize(9)) + 1024;
    sprite[nSprite].ang &= kAngleMask;

    sprite[nSprite].xvel = Cos(sprite[nSprite].ang) >> 8;
    sprite[nSprite].yvel = Sin(sprite[nSprite].ang) >> 8;

    FishList[nFish].nAction = 0;
    FishList[nFish].nFrame = 0;

    sprite[nSprite].zvel = RandomSize(9);

    if (!edx)
    {
        if (RandomBit()) {
            sprite[nSprite].zvel = -sprite[nSprite].zvel;
        }
    }
    else if (edx < 0)
    {
        sprite[nSprite].zvel = -sprite[nSprite].zvel;
    }
}

void DestroyFish(short nFish)
{
    short nSprite = FishList[nFish].nSprite;

    runlist_DoSubRunRec(sprite[nSprite].owner);
    runlist_FreeRun(sprite[nSprite].lotag - 1);
    runlist_SubRunRec(FishList[nFish].nRun);
    mydeletesprite(nSprite);
}

void FuncFish(int a, int nDamage, int nRun)
{
    short nFish = RunData[nRun].nVal;
    assert(nFish >= 0 && nFish < kMaxFishes);

    short nSprite = FishList[nFish].nSprite;
    short nAction = FishList[nFish].nAction;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        default:
        {
            DebugOut("unknown msg %d for Fish\n", nMessage);
            return;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, SeqOffsets[kSeqFish] + ActionSeq[nAction].a, FishList[nFish].nFrame, ActionSeq[nAction].b);
            tsprite[a & 0xFFFF].owner = -1;
            return;
        }

        case 0xA0000:
        {
            if (FishList[nFish].nHealth <= 0) {
                return;
            }
            else
            {
                nDamage = runlist_CheckRadialDamage(nSprite);
                if (!nDamage) {
                    return;
                }

                FishList[nFish].field_C = 10;
            }
            // fall through
            fallthrough__;
        }
        case 0x80000:
        {
            if (!nDamage) {
                return;
            }

            FishList[nFish].nHealth -= nDamage;
            if (FishList[nFish].nHealth <= 0)
            {
                FishList[nFish].nHealth = 0;
                nCreaturesLeft--;

                sprite[nSprite].cstat &= 0xFEFE;

                if (nMessage == 0x80000)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        BuildFishLimb(nFish, i);
                    }

                    PlayFXAtXYZ(StaticSound[kSoundFishDies], sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum);
                    DestroyFish(nFish);
                }
                else
                {
                    FishList[nFish].nAction = 9;
                    FishList[nFish].nFrame = 0;
                }

                return;
            }
            else
            {
                short nTarget = a & 0xFFFF;
                if (nTarget >= 0 && sprite[nTarget].statnum < 199)
                {
                    FishList[nFish].nTarget = nTarget;
                }

                FishList[nFish].nAction = 4;
                FishList[nFish].nFrame = 0;
                FishList[nFish].field_C += 10;
            }

            return;
        }

        case 0x20000:
        {
            if (!(SectFlag[sprite[nSprite].sectnum] & kSectUnderwater))
            {
                Gravity(nSprite);
            }

            short nSeq = SeqOffsets[kSeqFish] + ActionSeq[nAction].a;

            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, FishList[nFish].nFrame);

            seq_MoveSequence(nSprite, nSeq, FishList[nFish].nFrame);

            FishList[nFish].nFrame++;
            if (FishList[nFish].nFrame >= SeqSize[nSeq]) {
                FishList[nFish].nFrame = 0;
            }

            short nTarget = FishList[nFish].nTarget;

            switch (nAction)
            {
                default:
                    return;

                case 0:
                {
                    FishList[nFish].field_C--;
                    if (FishList[nFish].field_C <= 0)
                    {
                        nTarget = FindPlayer(nSprite, 60);
                        if (nTarget >= 0)
                        {
                            FishList[nFish].nTarget = nTarget;
                            FishList[nFish].nAction = 2;
                            FishList[nFish].nFrame = 0;

                            int nAngle = GetMyAngle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].z - sprite[nSprite].z);
                            sprite[nSprite].zvel = Sin(nAngle) >> 5;

                            FishList[nFish].field_C = RandomSize(6) + 90;
                        }
                        else
                        {
                            IdleFish(nFish, 0);
                        }
                    }

                    break;
                }

                case 1:
                    return;

                case 2:
                case 3:
                {
                    FishList[nFish].field_C--;
                    if (FishList[nFish].field_C <= 0)
                    {
                        IdleFish(nFish, 0);
                        return;
                    }
                    else
                    {
                        PlotCourseToSprite(nSprite, nTarget);
                        int nHeight = GetSpriteHeight(nSprite) >> 1;

                        int z = klabs(sprite[nTarget].z - sprite[nSprite].z);

                        if (z <= nHeight)
                        {
                            sprite[nSprite].xvel = (Cos(sprite[nSprite].ang) >> 5) - (Cos(sprite[nSprite].ang) >> 7);
                            sprite[nSprite].yvel = (Sin(sprite[nSprite].ang) >> 5) - (Sin(sprite[nSprite].ang) >> 7);
                        }
                        else
                        {
                            sprite[nSprite].xvel = 0;
                            sprite[nSprite].yvel = 0;
                        }

                        sprite[nSprite].zvel = (sprite[nTarget].z - sprite[nSprite].z) >> 3;
                    }
                    break;
                }

                case 4:
                {
                    if (FishList[nFish].nFrame == 0)
                    {
                        IdleFish(nFish, 0);
                    }
                    return;
                }

                case 8:
                {
                    return;
                }

                case 9:
                {
                    if (FishList[nFish].nFrame == 0)
                    {
                        DestroyFish(nFish);
                    }
                    return;
                }
            }

            int x = sprite[nSprite].x;
            int y = sprite[nSprite].y;
            int z = sprite[nSprite].z;
            short nSector = sprite[nSprite].sectnum;

            // loc_2EF54
            int nMov = movesprite(nSprite, sprite[nSprite].xvel << 13, sprite[nSprite].yvel << 13, sprite[nSprite].zvel << 2, 0, 0, CLIPMASK0);

            if (!(SectFlag[sprite[nSprite].sectnum] & kSectUnderwater))
            {
                mychangespritesect(nSprite, nSector);
                sprite[nSprite].x = x;
                sprite[nSprite].y = y;
                sprite[nSprite].z = z;

                IdleFish(nFish, 0);
                return;
            }
            else
            {
                if (nAction >= 5) {
                    return;
                }

                if (!nMov)
                {
                    if (nAction == 3)
                    {
                        FishList[nFish].nAction = 2;
                        FishList[nFish].nFrame = 0;
                    }
                    return;
                }

                if ((nMov & 0x30000) == 0)
                {
                    if ((nMov & 0xC000) == 0x8000)
                    {
                        IdleFish(nFish, 0);
                    }
                    else if ((nMov & 0xC000) == 0xC000)
                    {
                        if (sprite[nMov & 0x3FFF].statnum == 100)
                        {
                            FishList[nFish].nTarget = nMov & 0x3FFF;
                            sprite[nSprite].ang = GetMyAngle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);

                            if (nAction != 3)
                            {
                                FishList[nFish].nAction = 3;
                                FishList[nFish].nFrame = 0;
                            }

                            if (!FishList[nFish].nFrame)
                            {
                                runlist_DamageEnemy(nTarget, nSprite, 2);
                            }
                        }
                    }
                }
                else if (nMov & 0x20000)
                {
                    IdleFish(nFish, -1);
                }
                else
                {
                    IdleFish(nFish, 1);
                }
            }

            return;
        }
    }
}

class FishLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void FishLoadSave::Load()
{
    Read(&FishCount, sizeof(FishCount));
    Read(&nChunksFree, sizeof(nChunksFree));
    Read(nFreeChunk, sizeof(nFreeChunk[0]) * kMaxChunks);
    Read(FishList, sizeof(FishList[0]) * kMaxFishes);
    Read(FishChunk, sizeof(FishChunk[0]) * kMaxChunks);
}

void FishLoadSave::Save()
{
    Write(&FishCount, sizeof(FishCount));
    Write(&nChunksFree, sizeof(nChunksFree));
    Write(nFreeChunk, sizeof(nFreeChunk[0]) * kMaxChunks);
    Write(FishList, sizeof(FishList[0]) * kMaxFishes);
    Write(FishChunk, sizeof(FishChunk[0]) * kMaxChunks);
}

static FishLoadSave* myLoadSave;

void FishLoadSaveConstruct()
{
    myLoadSave = new FishLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef __fish_h__
#define __fish_h__

#include "aistuff.h"

void InitFishes();
int BuildFish(int nSprite, int x, int y, int z, int nSector, int nAngle);
void FuncFish(int, int, int);
void FuncFishLimb(int a, int b, int c);

#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "grenade.h"
#include "engine.h"
#include "player.h"
#include "runlist.h"
#include "exhumed.h"
#include "sound.h"
#include "move.h"
#include "init.h"
#include "bullet.h"
#include "gun.h"
#include "anims.h"
#include "lighting.h"
#include "sequence.h"
#include "random.h"
#include "save.h"
#include <assert.h>

struct Grenade
{
    int16_t field_0;
    int16_t field_2;
    int16_t nSprite;
    int16_t field_6;
    int16_t field_8;
    int16_t field_A;
    int16_t field_C;
    int16_t field_E;
    int field_10;
    int x;
    int y;
};

int nGrenadeCount = 0;
int nGrenadesFree;

int16_t GrenadeFree[kMaxGrenades];
Grenade GrenadeList[kMaxGrenades];

void InitGrenades()
{
    nGrenadeCount = 0;

    for (int i = 0; i < kMaxGrenades; i++) {
        GrenadeFree[i] = i;
    }

    nGrenadesFree = kMaxGrenades;
}

int GrabGrenade()
{
    return GrenadeFree[--nGrenadesFree];
}

void DestroyGrenade(int nGrenade)
{
    runlist_DoSubRunRec(GrenadeList[nGrenade].field_6);
    runlist_SubRunRec(GrenadeList[nGrenade].field_8);
    runlist_DoSubRunRec(sprite[GrenadeList[nGrenade].nSprite].lotag - 1);

    mydeletesprite(GrenadeList[nGrenade].nSprite);
    GrenadeFree[nGrenadesFree] = nGrenade;

    nGrenadesFree++;
}

void BounceGrenade(int nGrenade, int nAngle)
{
    GrenadeList[nGrenade].field_10 >>= 1;

    GrenadeList[nGrenade].x = (Cos(nAngle) >> 5) * GrenadeList[nGrenade].field_10;
    GrenadeList[nGrenade].y = (Sin(nAngle) >> 5) * GrenadeList[nGrenade].field_10;

    D3PlayFX(StaticSound[kSoundGrenadeDrop], GrenadeList[nGrenade].nSprite);
}

int ThrowGrenade(int nPlayer, int UNUSED(edx), int UNUSED(ebx), int ecx, int push1)
{
    if (nPlayerGrenade[nPlayer] < 0)
        return -1;

    int nGrenade = nPlayerGrenade[nPlayer];

    int nGrenadeSprite = GrenadeList[nGrenade].nSprite;
    int nPlayerSprite = PlayerList[nPlayer].nSprite;

    int nAngle = sprite[nPlayerSprite].ang;

    mychangespritesect(nGrenadeSprite, nPlayerViewSect[nPlayer]);

    sprite[nGrenadeSprite].x = sprite[nPlayerSprite].x;
    sprite[nGrenadeSprite].y = sprite[nPlayerSprite].y;
    sprite[nGrenadeSprite].z = sprite[nPlayerSprite].z;

    if (nAngle < 0) {
        nAngle = sprite[nPlayerSprite].ang;
    }

    sprite[nGrenadeSprite].cstat &= 0x7FFF;
    sprite[nGrenadeSprite].ang = nAngle;

    if (push1 >= -3000)
    {
        int nVel = totalvel[nPlayer] << 5;

        GrenadeList[nGrenade].field_10 = ((90 - GrenadeList[nGrenade].field_E) * (90 - GrenadeList[nGrenade].field_E)) + nVel;
        sprite[nGrenadeSprite].zvel = (-64 * push1) - 4352;

        int nMov = movesprite(nGrenadeSprite, Cos(nAngle) * (sprite[nPlayerSprite].clipdist << 3), Sin(nAngle) * (sprite[nPlayerSprite].clipdist << 3), ecx, 0, 0, CLIPMASK1);
        if (nMov & 0x8000)
        {
            nAngle = GetWallNormal(nMov & 0x3FFF);
            BounceGrenade(nGrenade, nAngle);
        }
    }
    else
    {
        GrenadeList[nGrenade].field_10 = 0;
        sprite[nGrenadeSprite].zvel = sprite[nPlayerSprite].zvel;
    }

    GrenadeList[nGrenade].x = Cos(nAngle) >> 4;
    GrenadeList[nGrenade].x *= GrenadeList[nGrenade].field_10;

    GrenadeList[nGrenade].y = Sin(nAngle) >> 4;
    GrenadeList[nGrenade].y *= GrenadeList[nGrenade].field_10;

    nPlayerGrenade[nPlayer] = -1;

    return nGrenadeSprite;
}

int BuildGrenade(int nPlayer)
{
    if (nGrenadesFree == 0)
        return -1;

    int nGrenade = GrabGrenade();

    int nSprite = insertsprite(nPlayerViewSect[nPlayer], 201);
    assert(nSprite >= 0 && nSprite < kMaxSprites);

    int nPlayerSprite = PlayerList[nPlayer].nSprite;

    sprite[nSprite].x = sprite[nPlayerSprite].x;
    sprite[nSprite].y = sprite[nPlayerSprite].y;
    sprite[nSprite].z = sprite[nPlayerSprite].z - 3840;
    sprite[nSprite].shade = -64;
    sprite[nSprite].xrepeat = 20;
    sprite[nSprite].yrepeat = 20;
    sprite[nSprite].cstat = 0x8000;
    sprite[nSprite].picnum = 1;
    sprite[nSprite].pal = 0;
    sprite[nSprite].clipdist = 30;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].ang = sprite[nPlayerSprite].ang;
    sprite[nSprite].owner = nPlayerSprite;
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;
    sprite[nSprite].extra = -1;

//	GrabTimeSlot(3);

    GrenadeList[nGrenade].field_E = 90;
    GrenadeList[nGrenade].field_2 = 0;
    GrenadeList[nGrenade].field_0 = 16;
    GrenadeList[nGrenade].field_10 = -1;
    GrenadeList[nGrenade].nSprite = nSprite;
    GrenadeList[nGrenade].field_A = 0;
    GrenadeList[nGrenade].field_C = 0;
    GrenadeList[nGrenade].field_6 = runlist_AddRunRec(sprite[nSprite].lotag - 1, nGrenade, kRunGrenade);
    GrenadeList[nGrenade].field_8 = runlist_AddRunRec(NewRun, nGrenade, kRunGrenade);

    nGrenadePlayer[nGrenade] = nPlayer;
    nPlayerGrenade[nPlayer] = nGrenade;

    return nGrenade;
}

void ExplodeGrenade(int nGrenade)
{
    int nSeq, nRepeat;

    int nPlayer = nGrenadePlayer[nGrenade];
    int nGrenadeSprite = GrenadeList[nGrenade].nSprite;
    int nGrenadeSect = sprite[nGrenadeSprite].sectnum;

    GrenadeList[nGrenade].field_C = 1;

    if (SectFlag[nGrenadeSect] & kSectUnderwater)
    {
        nSeq = kSeqGrenBubb;
        nRepeat = 60;
    }
    else
    {
        if (sprite[nGrenadeSprite].z < sector[nGrenadeSect].floorz)
        {
            nSeq = kSeqGrenPow;
            nRepeat = 200;
        }
        else
        {
            nSeq = kSeqGrenBoom;
            nRepeat = 150;
        }
    }

    if (GrenadeList[nGrenade].field_10 < 0)
    {
        int nPlayerSprite = PlayerList[nPlayer].nSprite;
        int nAngle = sprite[nPlayerSprite].ang;

        sprite[nGrenadeSprite].z = sprite[nPlayerSprite].z;
        sprite[nGrenadeSprite].x = (Cos(nAngle) >> 5) + sprite[nPlayerSprite].x;
        sprite[nGrenadeSprite].y = (Sin(nAngle) >> 5) + sprite[nPlayerSprite].y;

        changespritesect(nGrenadeSprite, sprite[nPlayerSprite].sectnum);

        if (!PlayerList[nPlayer].invincibility) {
            PlayerList[nPlayer].nHealth = 1;
        }
    }

    int nDamage = BulletInfo[kWeaponGrenade].nDamage;

    if (nPlayerDouble[nPlayer] > 0) {
        nDamage *= 2;
    }

    runlist_RadialDamageEnemy(nGrenadeSprite, nDamage, BulletInfo[kWeaponGrenade].nRadius);

    BuildAnim(-1, nSeq, 0, sprite[nGrenadeSprite].x, sprite[nGrenadeSprite].y, sprite[nGrenadeSprite].z, sprite[nGrenadeSprite].sectnum, nRepeat, 4);
    AddFlash(sprite[nGrenadeSprite].sectnum, sprite[nGrenadeSprite].x, sprite[nGrenadeSprite].y, sprite[nGrenadeSprite].z, 128);

    nGrenadePlayer[nGrenade] = -1;
    DestroyGrenade(nGrenade);
}

void FuncGrenade(int a, int UNUSED(nDamage), int nRun)
{
    int nGrenade = RunData[nRun].nVal;
    assert(nGrenade >= 0 && nGrenade < kMaxGrenades);

    int nGrenadeSprite = GrenadeList[nGrenade].nSprite;
    int nSeq;

    if (GrenadeList[nGrenade].field_C)
    {
        nSeq = SeqOffsets[kSeqGrenBoom];
    }
    else
    {
        nSeq = SeqOffsets[kSeqGrenRoll] + GrenadeList[nGrenade].field_A;
    }

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, nSeq, GrenadeList[nGrenade].field_2 >> 8, 1);
            break;
        }

        default:
        {
            DebugOut("unknown msg %d for grenade\n", nMessage);
            return;
        }

        case 0x20000:
        {
            seq_MoveSequence(nGrenadeSprite, nSeq, GrenadeList[nGrenade].field_2 >> 8);
            sprite[nGrenadeSprite].picnum = seq_GetSeqPicnum2(nSeq, GrenadeList[nGrenade].field_2 >> 8);

            GrenadeList[nGrenade].field_E--;
            if (!GrenadeList[nGrenade].field_E)
            {
                int nPlayer = nGrenadePlayer[nGrenade];

                if (GrenadeList[nGrenade].field_10 < 0)
                {
                    PlayerList[nPlayer].nWeaponState = 0;
                    PlayerList[nPlayer].nWeaponFrame = 0;

                    if (PlayerList[nPlayer].nAmmo[kWeaponGrenade])
                    {
                        PlayerList[nPlayer].bIsFiring = kFalse;
                    }
                    else
                    {
                        SelectNewWeapon(nPlayer);

                        PlayerList[nPlayer].nCurrentWeapon = PlayerList[nPlayer].nNewWeapon;
                        PlayerList[nPlayer].nNewWeapon = -1;
                    }
                }

                ExplodeGrenade(nGrenade);
                return;
            }
            else
            {
                if (GrenadeList[nGrenade].field_10 < 0) {
                    return;
                }

                int nFrame = (GrenadeList[nGrenade].field_2 + GrenadeList[nGrenade].field_0) >> 8;

                GrenadeList[nGrenade].field_2 += GrenadeList[nGrenade].field_0;

                if (nFrame < 0)
                {
                    GrenadeList[nGrenade].field_2 += SeqSize[nSeq] << 8;
                }
                else
                {
                    if (nFrame >= SeqSize[nSeq])
                    {
                        if (GrenadeList[nGrenade].field_C)
                        {
                            DestroyGrenade(nGrenade);
                            return;
                        }
                        else
                        {
                            GrenadeList[nGrenade].field_2 = GrenadeList[nGrenade].field_C;
                        }
                    }
                }

                if (GrenadeList[nGrenade].field_C) {
                    return;
                }

                int zVel = sprite[nGrenadeSprite].zvel;

                Gravity(nGrenadeSprite);
                int nMov = movesprite(nGrenadeSprite, GrenadeList[nGrenade].x, GrenadeList[nGrenade].y, sprite[nGrenadeSprite].zvel, sprite[nGrenadeSprite].clipdist >> 1, sprite[nGrenadeSprite].clipdist >> 1, CLIPMASK1);

                if (!nMov)
                    return;

                if (nMov & 0x20000)
                {
                    if (zVel)
                    {
                        if (SectDamage[sprite[nGrenadeSprite].sectnum] > 0)
                        {
                            ExplodeGrenade(nGrenade);
                            return;
                        }

                        GrenadeList[nGrenade].field_0 = (uint8_t)totalmoves; // limit to 8bits?

                        D3PlayFX(StaticSound[kSoundGrenadeDrop], nGrenadeSprite);

                        sprite[nGrenadeSprite].zvel = -(zVel >> 1);

                        if (sprite[nGrenadeSprite].zvel > -1280)
                        {
                            D3PlayFX(StaticSound[kSoundGrenadeRoll], nGrenadeSprite);
                            GrenadeList[nGrenade].field_0 = 0;
                            GrenadeList[nGrenade].field_2 = 0;
                            sprite[nGrenadeSprite].zvel = 0;
                            GrenadeList[nGrenade].field_A = 1;
                        }
                    }

                    GrenadeList[nGrenade].field_0 = 255 - (RandomByte() * 2);
                    GrenadeList[nGrenade].x -= (GrenadeList[nGrenade].x >> 4);
                    GrenadeList[nGrenade].y -= (GrenadeList[nGrenade].y >> 4);
                }

                // loc_2CF60:
                if ((nMov & 0xC000) >= 0x8000)
                {
                    if ((nMov & 0xC000) <= 0x8000)
                    {
                        BounceGrenade(nGrenade, GetWallNormal(nMov & 0x3FFF));
                    }
                    else if ((nMov & 0xC000) == 0xC000)
                    {
                        BounceGrenade(nGrenade, sprite[nMov & 0x3FFF].ang);
                    }
                }

                GrenadeList[nGrenade].field_2 = 0;
                return;
            }

            break;
        }

        case 0xA0000:
        {
            if (nGrenadeSprite != nRadialSpr && !GrenadeList[nGrenade].field_C)
            {
                if (runlist_CheckRadialDamage(nGrenadeSprite) > 280)
                {
                    GrenadeList[nGrenade].field_E = RandomSize(4) + 1;
                }
            }
            break;
        }
    }
}

class GrenadeLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void GrenadeLoadSave::Load()
{
    Read(&nGrenadeCount, sizeof(nGrenadeCount));
    Read(&nGrenadesFree, sizeof(nGrenadesFree));

    Read(GrenadeFree, sizeof(GrenadeFree));
    Read(GrenadeList, sizeof(GrenadeList));
}

void GrenadeLoadSave::Save()
{
    Write(&nGrenadeCount, sizeof(nGrenadeCount));
    Write(&nGrenadesFree, sizeof(nGrenadesFree));

    Write(GrenadeFree, sizeof(GrenadeFree));
    Write(GrenadeList, sizeof(GrenadeList));
}

static GrenadeLoadSave* myLoadSave;

void GrenadeLoadSaveConstruct()
{
    myLoadSave = new GrenadeLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef __grenade_h__
#define __grenade_h__

#include "aistuff.h"

#define kMaxGrenades	50

void InitGrenades();
int BuildGrenade(int nPlayer);
void DestroyGrenade(int nGrenade);
int ThrowGrenade(int nPlayer, int edx, int ebx, int ecx, int push1);
void FuncGrenade(int, int, int);

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "baselayer.h"
#include "cache1d.h"
#include "exhumed.h"
#include "common.h"
#include "compat.h"
#include "crc32.h"
#include "grpscan.h"
#include "scriptfile.h"

#include "vfs.h"

#ifndef EDUKE32_STANDALONE
// custom GRP support for the startup window, file format reflects the structure below
#define GAMELISTFILE "games.list"
//    name          crc             size      flags                 dependency
static internalgrpinfo_t const internalgrpfiles[] =
{
    { "Powerslave", POWERSLAVE_CRC, 27020745, GAMEFLAG_POWERSLAVE, 0 },
    { "Exhumed",    EXHUMED_CRC,    27108170, GAMEFLAG_EXHUMED,    0 },
    { "Powerslave Demo", POWERSLAVE_DEMO_CRC, 15904838, GAMEFLAG_POWERSLAVE | GAMEFLAG_DEMO, 0 },
    { "Exhumed Demo",    EXHUMED_DEMO_CRC,    16481687, GAMEFLAG_EXHUMED | GAMEFLAG_DEMO,    0 },
};
#endif

struct grpfile_t *foundgrps = NULL;
struct grpinfo_t *listgrps = NULL;

static void LoadList(const char * filename)
{
    scriptfile *script = scriptfile_fromfile(filename);

    if (!script)
        return;

#ifndef EDUKE32_STANDALONE
    scriptfile_addsymbolvalue("GAMEFLAG_POWERSLAVE", GAMEFLAG_POWERSLAVE);
    scriptfile_addsymbolvalue("POWERSLAVE_CRC", POWERSLAVE_CRC);
    scriptfile_addsymbolvalue("EXHUMED_CRC", EXHUMED_CRC);
    scriptfile_addsymbolvalue("EXHUMED_DEMO_CRC", EXHUMED_DEMO_CRC);
    scriptfile_addsymbolvalue("POWERSLAVE_DEMO_CRC", POWERSLAVE_DEMO_CRC);
#endif

    while (!scriptfile_eof(script))
    {
        enum
        {
            T_GRPINFO,
            T_GAMENAME,
            T_CRC,
            T_SIZE,
            T_DEPCRC,
            T_DEFNAME,
            T_FLAGS,
        };

        static const tokenlist profiletokens[] =
        {
            { "grpinfo",            T_GRPINFO },
        };

        int32_t token = getatoken(script,profiletokens,ARRAY_SIZE(profiletokens));
        switch (token)
        {
        case T_GRPINFO:
        {
            int32_t gsize = 0, gcrcval = 0, gflags = GAMEFLAG_POWERSLAVE, gdepcrc = POWERSLAVE_CRC;
            char *gname = NULL, *gdef = NULL;
            char *grpend = NULL;

            static const tokenlist grpinfotokens[] =
            {
                { "name",           T_GAMENAME },
                { "defname",        T_DEFNAME },
                { "crc",            T_CRC },
                { "dependency",     T_DEPCRC },
                { "size",           T_SIZE },
                { "flags",          T_FLAGS },

            };

            if (scriptfile_getbraces(script,&grpend)) break;

            while (script->textptr < grpend)
            {
                int32_t token = getatoken(script,grpinfotokens,ARRAY_SIZE(grpinfotokens));

                switch (token)
                {
                case T_GAMENAME:
                    scriptfile_getstring(script,&gname); break;
                case T_DEFNAME:
                    scriptfile_getstring(script,&gdef); break;

                case T_FLAGS:
                    scriptfile_getsymbol(script,&gflags); gflags &= GAMEFLAGMASK; break;
                case T_DEPCRC:
                    scriptfile_getsymbol(script,&gdepcrc); break;
                case T_CRC:
                    scriptfile_getsymbol(script,&gcrcval); break;
                case T_SIZE:
                    scriptfile_getnumber(script,&gsize); break;
                default:
                    break;
                }

                grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t));
                fg->next = listgrps;
                listgrps = fg;

                if (gname)
                    fg->name = Xstrdup(gname);

                fg->size = gsize;
                fg->crcval = gcrcval;
                fg->dependency = gdepcrc;
                fg->game = gflags;

                if (gdef)
                    fg->defname = dup_filename(gdef);
            }
            break;
        }

        default:
            break;
        }
    }

    scriptfile_close(script);
    scriptfile_clearsymbols();
}

static void LoadGameList(void)
{
#ifndef EDUKE32_STANDALONE
    for (size_t i = 0; i < ARRAY_SIZE(internalgrpfiles); i++)
    {
        grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t));

        fg->name = Xstrdup(internalgrpfiles[i].name);
        fg->crcval = internalgrpfiles[i].crcval;
        fg->size = internalgrpfiles[i].size;
        fg->game = internalgrpfiles[i].game;
        fg->dependency = internalgrpfiles[i].dependency;

        fg->next = listgrps;
        listgrps = fg;
    }
#endif

#ifdef USE_PHYSFS
    auto const base = PHYSFS_getBaseDir();
#else
    static char const base[] = "/";
#endif
    BUILDVFS_FIND_REC * const srch = klistpath(base, "*.grpinfo", BUILDVFS_FIND_FILE);

    for (BUILDVFS_FIND_REC *sidx = srch; sidx; sidx = sidx->next)
        LoadList(sidx->name);

    klistfree(srch);
}

static void FreeGameList(void)
{
    while (listgrps)
    {
        Xfree(listgrps->name);
        Xfree(listgrps->defname);

        grpinfo_t * const fg = listgrps->next;
        Xfree(listgrps);
        listgrps = fg;
    }
}

#define GRPCACHEFILE "grpfiles.cache"
static struct grpcache
{
    struct grpcache *next;
    int32_t size;
    int32_t mtime;
    int32_t crcval;
    char name[BMAX_PATH];
}
*grpcache = NULL, *usedgrpcache = NULL;

static int32_t LoadGroupsCache(void)
{
    struct grpcache *fg;

    int32_t fsize, fmtime, fcrcval;
    char *fname;

    scriptfile *script;

    script = scriptfile_fromfile(GRPCACHEFILE);
    if (!script) return -1;

    while (!scriptfile_eof(script))
    {
        if (scriptfile_getstring(script, &fname)) break;    // filename
        if (scriptfile_getnumber(script, &fsize)) break;    // filesize
        if (scriptfile_getnumber(script, &fmtime)) break;   // modification time
        if (scriptfile_getnumber(script, &fcrcval)) break;  // crc checksum

        fg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
        fg->next = grpcache;
        grpcache = fg;

        Bstrncpy(fg->name, fname, BMAX_PATH);
        fg->size = fsize;
        fg->mtime = fmtime;
        fg->crcval = fcrcval;
    }

    scriptfile_close(script);
    return 0;
}

static void FreeGroupsCache(void)
{
    while (grpcache)
    {
        struct grpcache * const fg = grpcache->next;
        Xfree(grpcache);
        grpcache = fg;
    }
}

static void RemoveGroup(grpfile_t *igrp)
{
    for (grpfile_t *prev = NULL, *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp == igrp)
        {
            if (grp == foundgrps)
                foundgrps = grp->next;
            else
                prev->next = grp->next;

            Xfree((char *)grp->filename);
            Xfree(grp);

            return;
        }

        prev = grp;
    }
}

grpfile_t * FindGroup(int32_t crcval)
{
    grpfile_t *grp;

    for (grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->crcval == crcval)
            return grp;
    }

    return NULL;
}

#ifndef USE_PHYSFS
static grpinfo_t const * FindGrpInfo(int32_t crcval, int32_t size)
{
    grpinfo_t *grpinfo;

    for (grpinfo = listgrps; grpinfo; grpinfo=grpinfo->next)
    {
        if (grpinfo->crcval == crcval && grpinfo->size == size)
            return grpinfo;
    }

    return NULL;
}

static void ProcessGroups(BUILDVFS_FIND_REC *srch)
{
    BUILDVFS_FIND_REC *sidx;
    struct grpcache *fg, *fgg;
    char *fn;
    struct Bstat st;

    static constexpr int ReadSize = 65536;

    auto buf = (uint8_t *)Xmalloc(ReadSize);

    for (sidx = srch; sidx; sidx = sidx->next)
    {
        for (fg = grpcache; fg; fg = fg->next)
        {
            if (!Bstrcmp(fg->name, sidx->name)) break;
        }

        if (fg)
        {
            if (findfrompath(sidx->name, &fn)) continue; // failed to resolve the filename
            if (Bstat(fn, &st))
            {
                Xfree(fn);
                continue;
            } // failed to stat the file
            Xfree(fn);
            if (fg->size == (int32_t)st.st_size && fg->mtime == (int32_t)st.st_mtime)
            {
                grpinfo_t const * const grptype = FindGrpInfo(fg->crcval, fg->size);
                if (grptype)
                {
                    grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t));
                    grp->filename = Xstrdup(sidx->name);
                    grp->type = grptype;
                    grp->next = foundgrps;
                    foundgrps = grp;
                }

                fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
                strcpy(fgg->name, fg->name);
                fgg->size = fg->size;
                fgg->mtime = fg->mtime;
                fgg->crcval = fg->crcval;
                fgg->next = usedgrpcache;
                usedgrpcache = fgg;
                continue;
            }
        }

        {
            int32_t b, fh;
            int32_t crcval = 0;

            fh = openfrompath(sidx->name, BO_RDONLY|BO_BINARY, BS_IREAD);
            if (fh < 0) continue;
            if (Bfstat(fh, &st)) continue;

            initprintf(" Checksumming %s...", sidx->name);
            do
            {
                b = read(fh, buf, ReadSize);
                if (b > 0) crcval = Bcrc32((uint8_t *)buf, b, crcval);
            }
            while (b == ReadSize);
            close(fh);
            initprintf(" Done\n");

            grpinfo_t const * const grptype = FindGrpInfo(crcval, st.st_size);
            if (grptype)
            {
                grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t));
                grp->filename = Xstrdup(sidx->name);
                grp->type = grptype;
                grp->next = foundgrps;
                foundgrps = grp;
            }

            fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache));
            Bstrncpy(fgg->name, sidx->name, BMAX_PATH);
            fgg->size = st.st_size;
            fgg->mtime = st.st_mtime;
            fgg->crcval = crcval;
            fgg->next = usedgrpcache;
            usedgrpcache = fgg;
        }
    }

    Xfree(buf);
}
#endif

int32_t ScanGroups(void)
{
#ifndef USE_PHYSFS
    struct grpcache *fg, *fgg;

    initprintf("Searching for game data...\n");

    LoadGameList();
    LoadGroupsCache();

    static char const *extensions[] =
    {
        "*.grp",
        "*.ssi",
        "*.dat",
    };

    for (char const *extension : extensions)
    {
        BUILDVFS_FIND_REC *srch = klistpath("/", extension, BUILDVFS_FIND_FILE);
        ProcessGroups(srch);
        klistfree(srch);
    }

    FreeGroupsCache();

    for (grpfile_t *grp = foundgrps; grp; grp=grp->next)
    {
        if (grp->type->dependency)
        {
            if (FindGroup(grp->type->dependency) == NULL) // couldn't find dependency
            {
                //initprintf("removing %s\n", grp->name);
                RemoveGroup(grp);
                grp = foundgrps;
                // start from the beginning so we can remove anything that depended on this grp
                continue;
            }
        }
    }

    if (usedgrpcache)
    {
        int32_t i = 0;
        buildvfs_FILE fp = buildvfs_fopen_write(GRPCACHEFILE);
        if (fp)
        {
            for (fg = usedgrpcache; fg; fg=fgg)
            {
                fgg = fg->next;
                fprintf(fp, "\"%s\" %d %d %d\n", fg->name, fg->size, fg->mtime, fg->crcval);
                Xfree(fg);
                i++;
            }
            buildvfs_fclose(fp);
        }
//        initprintf("Found %d recognized GRP %s.\n",i,i>1?"files":"file");

        return 0;
    }

    initprintf("Found no recognized game data!\n");
#endif

    return 0;
}

void FreeGroups(void)
{
    while (foundgrps)
    {
        Xfree((char *)foundgrps->filename);
        grpfile_t * const fg = foundgrps->next;
        Xfree(foundgrps);
        foundgrps = fg;
    }

    FreeGameList();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef grpscan_h_
#define grpscan_h_

#ifdef __cplusplus
extern "C" {
#endif

#define MAXLISTNAMELEN 32

// List of internally-known GRP files
#define POWERSLAVE_CRC      (int32_t)0x303CBD89
#define EXHUMED_CRC         (int32_t)0xE3B172F1
#define POWERSLAVE_DEMO_CRC (int32_t)0x1D8C7645
#define EXHUMED_DEMO_CRC    (int32_t)0x1A6E27FA

#define GAMEFLAG_POWERSLAVE       0x00000001
#define GAMEFLAG_EXHUMED          0x00000002

#define GAMEFLAG_DEMO         0x00000020

//#define GAMEFLAG_ADDON      0x00000010
//#define GAMEFLAG_SHAREWARE  0x00000020
#define GAMEFLAGMASK        0x000000FF // flags allowed from grpinfo

typedef struct internalgrpinfo_t {
    char const *name;
    int32_t const crcval;
    int32_t const size;
    int32_t const game;
    int32_t const dependency;
    // char const *scriptname;
    // void(*postprocessing)(int32_t);
} internalgrpinfo_t;

typedef struct grpinfo_t {
	char *name;
	int32_t crcval;
	int32_t size;
	int32_t game;
    int32_t dependency;
    // char *scriptname;
    char *defname;
    // char *rtsname;
    // void (*postprocessing)(int32_t);
	struct grpinfo_t *next;
} grpinfo_t;

typedef struct grpfile_t {
	char *filename;
	struct grpinfo_t const *type;
	struct grpfile_t *next;
} grpfile_t;

extern grpfile_t *foundgrps;
extern grpinfo_t *listgrps;

extern grpfile_t * FindGroup(int32_t crcval);

int32_t ScanGroups(void);
void FreeGroups(void);

#ifdef __cplusplus
}
#endif

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "build.h"
#include "gun.h"
#include "engine.h"
#include "init.h"
#include "player.h"
#include "exhumed.h"
#include "view.h"
#include "move.h"
#include "status.h"
#include "bubbles.h"
#include "typedefs.h"
#include "sound.h"
#include "ra.h"
#include "snake.h"
#include "grenade.h"
#include "lighting.h"
#include "light.h"
#include "input.h"
#include "util.h"
#include "anims.h"
#include "runlist.h"
#include "bullet.h"
#include "trigdat.h"
#include "object.h"
#include "save.h"
#include <string.h>
#include <assert.h>

Weapon WeaponInfo[] = {
    { kSeqSword,   { 0, 1, 3,  7, -1,  2,  4, 5, 6, 8, 9, 10 }, 0, 0, 0, kTrue },
    { kSeqPistol,  { 0, 3, 2,  4, -1,  1,  0, 0, 0, 0, 0, 0 },  1, 0, 1, kFalse },
    { kSeqM60,     { 0, 5, 6, 16, -1, 21,  0, 0, 0, 0, 0, 0 },  2, 0, 1, kFalse },
    { kSeqFlamer,  { 0, 2, 5,  5,  6,  1,  0, 0, 0, 0, 0, 0 },  3, 4, 1, kFalse },
    { kSeqGrenade, { 0, 2, 3,  4, -1,  1,  0, 0, 0, 0, 0, 0 },  4, 0, 1, kTrue },
    { kSeqCobra,   { 0, 1, 2,  2, -1,  4,  0, 0, 0, 0, 0, 0 },  5, 0, 1, kTrue },
    { kSeqRavolt,  { 0, 1, 2,  3, -1,  4,  0, 0, 0, 0, 0, 0 },  6, 0, 1, kTrue },
    { kSeqRothands,{ 0, 1, 2, -1, -1, -1,  0, 0, 0, 0, 0, 0 },  7, 0, 0, kTrue },
    { kSeqDead,    { 1, 0, 0,  0,  0,  0,  0, 0, 0, 0, 0, 0 },  0, 1, 0, kFalse },
    { kSeqDeadEx,  { 1, 0, 0,  0,  0,  0,  0, 0, 0, 0, 0, 0 },  0, 1, 0, kFalse },
    { kSeqDeadBrn, { 1, 0, 0,  0,  0,  0,  0, 0, 0, 0, 0, 0 },  0, 1, 0, kFalse }
};

short nTemperature[kMaxPlayers];
short nMinAmmo[] = { 0, 24, 51, 50, 1, 0, 0 };
short word_96E26 = 0;

void RestoreMinAmmo(short nPlayer)
{
    for (int i = 0; i < kMaxWeapons; i++)
    {
        if (i == kWeaponGrenade) {
            continue;
        }

        if ((1 << i) & nPlayerWeapons[nPlayer])
        {
            if (nMinAmmo[i] > PlayerList[nPlayer].nAmmo[i]) {
                PlayerList[nPlayer].nAmmo[i] = nMinAmmo[i];
            }
        }
    }

    CheckClip(nPlayer);
}

void FillWeapons(short nPlayer)
{
    nPlayerWeapons[nPlayer] = 0xFFFF; // turn on all bits

    for (int i = 0; i < kMaxWeapons; i++)
    {
        if (WeaponInfo[i].bUsesAmmo) {
            PlayerList[nPlayer].nAmmo[i] = 99;
        }
    }

    CheckClip(nPlayer);

    if (nPlayer == nLocalPlayer)
    {
        short nWeapon = PlayerList[nPlayer].nCurrentWeapon;
        SetCounter(PlayerList[nPlayer].nAmmo[nWeapon]);
    }
}

void ResetPlayerWeapons(short nPlayer)
{
    for (int i = 0; i < kMaxWeapons; i++)
    {
        PlayerList[nPlayer].nAmmo[i] = 0;
    }

    PlayerList[nPlayer].nCurrentWeapon = 0;
    PlayerList[nPlayer].nWeaponState = 0;
    PlayerList[nPlayer].nWeaponFrame = 0;

    nPlayerGrenade[nPlayer] = -1;
    nPlayerWeapons[nPlayer] = 0x1; // turn on bit 1 only
}

void InitWeapons()
{
    memset(nPlayerGrenade, 0, sizeof(nPlayerGrenade));
    memset(nGrenadePlayer, 0, sizeof(nGrenadePlayer));
}

void SetNewWeapon(short nPlayer, short nWeapon)
{
    if (nWeapon == kWeaponMummified)
    {
        PlayerList[nPlayer].nLastWeapon = PlayerList[nPlayer].nCurrentWeapon;
        PlayerList[nPlayer].bIsFiring = kFalse;
        PlayerList[nPlayer].nWeaponState = 5;
        SetPlayerMummified(nPlayer, kTrue);

        PlayerList[nPlayer].nWeaponFrame = 0;
    }
    else
    {
        if (nWeapon < 0)
        {
            nPlayerOldWeapon[nPlayer] = PlayerList[nPlayer].nCurrentWeapon;
        }
        else if (nWeapon != kWeaponGrenade || PlayerList[nPlayer].nAmmo[kWeaponGrenade] > 0)
        {
            short nCurrentWeapon = PlayerList[nPlayer].nCurrentWeapon;

            if (nCurrentWeapon != kWeaponMummified)
            {
                if (PlayerList[nPlayer].bIsFiring || nWeapon == nCurrentWeapon) {
                    return;
                }
            }
            else
            {
                PlayerList[nPlayer].nCurrentWeapon = nWeapon;
                PlayerList[nPlayer].nWeaponFrame = 0;
            }
        }
        else {
            return;
        }
    }

    PlayerList[nPlayer].nNewWeapon = nWeapon;

    if (nPlayer == nLocalPlayer)
    {
        int nCounter;

        if (nWeapon >= kWeaponSword && nWeapon <= kWeaponRing) {
            nCounter = PlayerList[nPlayer].nAmmo[nWeapon];
        }
        else {
            nCounter = 0;
        }

        SetCounterImmediate(nCounter);
    }
}

void SetNewWeaponImmediate(short nPlayer, short nWeapon)
{
    SetNewWeapon(nPlayer, nWeapon);

    PlayerList[nPlayer].nCurrentWeapon = nWeapon;
    PlayerList[nPlayer].nNewWeapon = -1;
    PlayerList[nPlayer].nWeaponFrame = 0;
    PlayerList[nPlayer].nWeaponState = 0;
}

void SetNewWeaponIfBetter(short nPlayer, short nWeapon)
{
    if (nWeapon > PlayerList[nPlayer].nCurrentWeapon) {
        SetNewWeapon(nPlayer, nWeapon);
    }
}

void SelectNextWeapon()
{
    if (PlayerList[nLocalPlayer].bIsFiring)
        return;

    int nCurrentWeapon = PlayerList[nLocalPlayer].nCurrentWeapon;
    if (nCurrentWeapon == kWeaponMummified)
        return; // can't change weapon when mummified

    int nStartWeapon = nCurrentWeapon++;
    int nBits = nPlayerWeapons[nLocalPlayer];

    while (nCurrentWeapon != nStartWeapon)
    {
        if (((nBits >> nCurrentWeapon) & 1) && nCurrentWeapon != kWeaponMummified)
        {
            int nAmmoType = WeaponInfo[nCurrentWeapon].nAmmoType;
            if (!WeaponInfo[nCurrentWeapon].bUsesAmmo || PlayerList[nLocalPlayer].nAmmo[nAmmoType])
            {
                StopFiringWeapon(nLocalPlayer);
                SetNewWeapon(nLocalPlayer, nCurrentWeapon);
                return;
            }
        }

        nCurrentWeapon++;
        if (nCurrentWeapon >= kWeaponMummified)
            nCurrentWeapon = 0;
    }
}

void SelectPreviousWeapon()
{
    if (PlayerList[nLocalPlayer].bIsFiring)
        return;

    int nCurrentWeapon = PlayerList[nLocalPlayer].nCurrentWeapon;
    if (nCurrentWeapon == kWeaponMummified)
        return; // can't change weapon when mummified

    int nStartWeapon = nCurrentWeapon--;
    int nBits = nPlayerWeapons[nLocalPlayer];

    while (nCurrentWeapon != nStartWeapon)
    {
        if (((nBits >> nCurrentWeapon) & 1) && nCurrentWeapon != kWeaponMummified)
        {
            int nAmmoType = WeaponInfo[nCurrentWeapon].nAmmoType;
            if (!WeaponInfo[nCurrentWeapon].bUsesAmmo || PlayerList[nLocalPlayer].nAmmo[nAmmoType])
            {
                StopFiringWeapon(nLocalPlayer);
                SetNewWeapon(nLocalPlayer, nCurrentWeapon);
                return;
            }
        }

        nCurrentWeapon--;
        if (nCurrentWeapon < 0)
            nCurrentWeapon = kWeaponMummified - 1;
    }
}

void SelectNewWeapon(short nPlayer)
{
    int nWeapon = kWeaponRing; // start at the highest weapon number

    uint16_t nAvailableWeaponBits = nPlayerWeapons[nPlayer];
    uint16_t nBitToCheck = 0x40; // bit 7 turned on

    while (nBitToCheck)
    {
        if (nAvailableWeaponBits & nBitToCheck)
        {
            // we have this weapon
            if (!WeaponInfo[nWeapon].bUsesAmmo || PlayerList[nPlayer].nAmmo[WeaponInfo[nWeapon].nAmmoType])
                break;
        }

        nWeapon--;
        nBitToCheck >>= 1;
    }

    if (nWeapon < 0)
        nWeapon = kWeaponSword;

    PlayerList[nPlayer].bIsFiring = kFalse;

    SetNewWeapon(nPlayer, nWeapon);
}

void StopFiringWeapon(short nPlayer)
{
    PlayerList[nPlayer].bIsFiring = kFalse;
}

void FireWeapon(short nPlayer)
{
    if (!PlayerList[nPlayer].bIsFiring) {
        PlayerList[nPlayer].bIsFiring = kTrue;
    }
}

void SetWeaponStatus(short nPlayer)
{
    if (nPlayer != nLocalPlayer)
        return;

    short nWeapon = PlayerList[nPlayer].nCurrentWeapon;

    if (nWeapon < 0)
    {
        nCounterBullet = -1;
        SetCounterImmediate(0);
    }
    else
    {
        nCounterBullet = WeaponInfo[nWeapon].nAmmoType;
        SetCounterImmediate(PlayerList[nPlayer].nAmmo[nCounterBullet]);
    }
}

uint8_t WeaponCanFire(short nPlayer)
{
    short nWeapon = PlayerList[nPlayer].nCurrentWeapon;
    short nSector = nPlayerViewSect[nPlayer];

    if (!(SectFlag[nSector] & kSectUnderwater) || WeaponInfo[nWeapon].bFireUnderwater)
    {
        short nAmmoType = WeaponInfo[nWeapon].nAmmoType;

        if (WeaponInfo[nWeapon].bUsesAmmo <= PlayerList[nPlayer].nAmmo[nAmmoType]) {
            return kTrue;
        }
    }

    return kFalse;
}

// UNUSED
void ResetSwordSeqs()
{
    WeaponInfo[kWeaponSword].b[2] = 3;
    WeaponInfo[kWeaponSword].b[3] = 7;
}

int CheckCloseRange(short nPlayer, int *x, int *y, int *z, short *nSector)
{
    short hitSect, hitWall, hitSprite;
    int hitX, hitY, hitZ;

    short nSprite = PlayerList[nPlayer].nSprite;

    int xVect = Sin(sprite[nSprite].ang + 512);
    int yVect = Sin(sprite[nSprite].ang);

    vec3_t startPos = { *x, *y, *z };
    hitdata_t hitData;
    hitscan(&startPos, *nSector, xVect, yVect, 0, &hitData, CLIPMASK1);
    hitX = hitData.x;
    hitY = hitData.y;
    hitZ = hitData.z;
    hitSprite = hitData.sprite;
    hitSect = hitData.sect;
    hitWall = hitData.wall;

    int ecx = sintable[150] >> 3;

    uint32_t xDiff = klabs(hitX - *x);
    uint32_t yDiff = klabs(hitY - *y);

    uint32_t sqrtNum = xDiff * xDiff + yDiff * yDiff;

    if (sqrtNum > INT_MAX)
    {
        OSD_Printf("%s %d: overflow\n", EDUKE32_FUNCTION, __LINE__);
        sqrtNum = INT_MAX;
    }

    if (ksqrt(sqrtNum) >= ecx)
        return 0;

    *x = hitX;
    *y = hitY;
    *z = hitZ;
    *nSector = hitSect;

    if (hitSprite > -1) {
        return hitSprite | 0xC000;
    }
    if (hitWall > -1) {
        return hitWall | 0x8000;
    }

    return 0;
}

void CheckClip(short nPlayer)
{
    if (nPlayerClip[nPlayer] <= 0)
    {
        nPlayerClip[nPlayer] = PlayerList[nPlayer].nAmmo[kWeaponM60];

        if (nPlayerClip[nPlayer] > 99) {
            nPlayerClip[nPlayer] = 99;
        }
    }
}

void MoveWeapons(short nPlayer)
{
    static int dword_96E22 = 0;

    short nSectFlag = SectFlag[nPlayerViewSect[nPlayer]];

    if ((nSectFlag & kSectUnderwater) && (totalmoves & 1)) {
        return;
    }

    nPilotLightFrame++;

    if (nPilotLightFrame >= nPilotLightCount)
        nPilotLightFrame = 0;

    if (!PlayerList[nPlayer].bIsFiring || (nSectFlag & kSectUnderwater))
        nTemperature[nPlayer] = 0;

    short nPlayerSprite = PlayerList[nPlayer].nSprite;
    short nWeapon = PlayerList[nPlayer].nCurrentWeapon;

    if (nWeapon < -1)
    {
        if (PlayerList[nPlayer].nNewWeapon != -1)
        {
            PlayerList[nPlayer].nCurrentWeapon = PlayerList[nPlayer].nNewWeapon;
            PlayerList[nPlayer].nWeaponState = 0;
            PlayerList[nPlayer].nWeaponFrame = 0;
            PlayerList[nPlayer].nNewWeapon = -1;
        }

        return;
    }

    // loc_26ACC
    short eax = PlayerList[nPlayer].nWeaponState;
    short nSeq = WeaponInfo[nWeapon].nSeq;

    short var_3C = WeaponInfo[nWeapon].b[eax] + SeqOffsets[nSeq];

    int var_1C = (nPlayerDouble[nPlayer] > 0) + 1;

    frames = var_1C - 1;

    for (frames = var_1C; frames > 0; frames--)
    {
        seq_MoveSequence(nPlayerSprite, var_3C, PlayerList[nPlayer].nWeaponFrame);

        PlayerList[nPlayer].nWeaponFrame++;

        dword_96E22++;
        if (dword_96E22 >= 15) {
            dword_96E22 = 0;
        }

        if (PlayerList[nPlayer].nWeaponFrame >= SeqSize[var_3C])
        {
            if (PlayerList[nPlayer].nNewWeapon == -1)
            {
                switch (PlayerList[nPlayer].nWeaponState)
                {
                    default:
                        break;

                    case 0:
                    {
                        PlayerList[nPlayer].nWeaponState = 1;
                        SetWeaponStatus(nPlayer);
                        break;
                    }
                    case 1:
                    {
                        if (PlayerList[nPlayer].bIsFiring)
                        {
                            if (!WeaponCanFire(nPlayer))
                            {
                                if (!dword_96E22) {
                                    D3PlayFX(StaticSound[4], PlayerList[nPlayer].nSprite);
                                }
                            }
                            else
                            {
                                if (nWeapon == kWeaponRing)
                                {
                                    if (Ra[nPlayer].nTarget == -1)
                                        break;

                                    Ra[nPlayer].nAction = 0;
                                    Ra[nPlayer].nFrame  = 0;
                                    Ra[nPlayer].field_C = 1;
                                }

                                PlayerList[nPlayer].nWeaponState = 2;

                                if (nWeapon == 0)
                                    break;

                                if (nWeapon == kWeaponGrenade)
                                {
                                    BuildGrenade(nPlayer);
                                    AddAmmo(nPlayer, 4, -1);
                                }
                                else if (nWeapon == kWeaponMummified)
                                {
                                    ShootStaff(nPlayer);
                                }
                            }
                        }
                        break;
                    }

                    case 2:
                    case 6:
                    case 7:
                    case 8:
                    {
                        if (nWeapon == kWeaponPistol && nPistolClip[nPlayer] <= 0)
                        {
                            PlayerList[nPlayer].nWeaponState = 3;
                            PlayerList[nPlayer].nWeaponFrame = 0;

                            nPistolClip[nPlayer] = Min(6, PlayerList[nPlayer].nAmmo[kWeaponPistol]);
                            break;
                        }
                        else if (nWeapon == kWeaponGrenade)
                        {
                            if (!PlayerList[nPlayer].bIsFiring)
                            {
                                PlayerList[nPlayer].nWeaponState = 3;
                                break;
                            }
                            else
                            {
                                PlayerList[nPlayer].nWeaponFrame = SeqSize[var_3C] - 1;
                                continue;
                            }
                        }
                        else if (nWeapon == kWeaponMummified)
                        {
                            PlayerList[nPlayer].nWeaponState = 0;
                            PlayerList[nPlayer].nCurrentWeapon = PlayerList[nPlayer].nLastWeapon;

                            nWeapon = PlayerList[nPlayer].nCurrentWeapon;

                            SetPlayerMummified(nPlayer, kFalse);
                            break;
                        }
                        else
                        {
                            // loc_26D88:
                            if (PlayerList[nPlayer].bIsFiring && WeaponCanFire(nPlayer))
                            {
                                if (nWeapon != kWeaponM60 && nWeapon != kWeaponPistol) {
                                    PlayerList[nPlayer].nWeaponState = 3;
                                }
                            }
                            else
                            {
                                if (WeaponInfo[nWeapon].b[4] == -1)
                                {
                                    PlayerList[nPlayer].nWeaponState = 1;
                                }
                                else
                                {
                                    if (nWeapon == kWeaponFlamer && (nSectFlag & kSectUnderwater))
                                    {
                                        PlayerList[nPlayer].nWeaponState = 1;
                                    }
                                    else
                                    {
                                        PlayerList[nPlayer].nWeaponState = 4;
                                    }
                                }
                            }

                            break;
                        }
                    }

                    case 3:
                    case 9:
                    case 10:
                    case 11:
                    {
                        if (nWeapon == kWeaponMummified)
                        {
                            PlayerList[nPlayer].nCurrentWeapon = PlayerList[nPlayer].nLastWeapon;

                            nWeapon = PlayerList[nPlayer].nCurrentWeapon;

                            PlayerList[nPlayer].nWeaponState = 0;
                            break;
                        }
                        else if (nWeapon == kWeaponRing)
                        {
                            if (!WeaponInfo[nWeapon].bUsesAmmo || PlayerList[nPlayer].nAmmo[WeaponInfo[nWeapon].nAmmoType])
                            {
                                if (!PlayerList[nPlayer].bIsFiring) {
                                    PlayerList[nPlayer].nWeaponState = 1;
                                }
                                else {
                                    break;
                                }
                            }
                            else
                            {
                                SelectNewWeapon(nPlayer);
                            }

                            Ra[nPlayer].field_C = 0;
                            break;
                        }
                        else if (nWeapon == kWeaponM60)
                        {
                            CheckClip(nPlayer);
                            PlayerList[nPlayer].nWeaponState = 1;
                            break;
                        }
                        else if (nWeapon == kWeaponGrenade)
                        {
                            if (!WeaponInfo[nWeapon].bUsesAmmo || PlayerList[nPlayer].nAmmo[WeaponInfo[nWeapon].nAmmoType])
                            {
                                PlayerList[nPlayer].nWeaponState = 0;
                                break;
                            }
                            else
                            {
                                SelectNewWeapon(nPlayer);
                                PlayerList[nPlayer].nWeaponState = 5;

                                PlayerList[nPlayer].nWeaponFrame = SeqSize[WeaponInfo[kWeaponGrenade].b[0] + SeqOffsets[nSeq]] - 1; // CHECKME
                                goto loc_flag; // FIXME
                            }
                        }
                        else
                        {
                            if (PlayerList[nPlayer].bIsFiring && WeaponCanFire(nPlayer)) {
                                PlayerList[nPlayer].nWeaponState = 2;
                                break;
                            }

                            if (WeaponInfo[nWeapon].b[4] == -1)
                            {
                                PlayerList[nPlayer].nWeaponState = 1;
                                break;
                            }

                            if (nWeapon == kWeaponFlamer && (nSectFlag & kSectUnderwater))
                            {
                                PlayerList[nPlayer].nWeaponState = 1;
                            }
                            else
                            {
                                PlayerList[nPlayer].nWeaponState = 4;
                            }
                        }
                        break;
                    }

                    case 4:
                    {
                        PlayerList[nPlayer].nWeaponState = 1;
                        break;
                    }

                    case 5:
                    {
                        PlayerList[nPlayer].nCurrentWeapon = PlayerList[nPlayer].nNewWeapon;

                        nWeapon = PlayerList[nPlayer].nCurrentWeapon;

                        PlayerList[nPlayer].nWeaponState = 0;
                        PlayerList[nPlayer].nNewWeapon = -1;

                        SetWeaponStatus(nPlayer);
                        break;
                    }
                }

                // loc_26FC5
                var_3C = SeqOffsets[WeaponInfo[nWeapon].nSeq] + WeaponInfo[nWeapon].b[PlayerList[nPlayer].nWeaponState];
                PlayerList[nPlayer].nWeaponFrame = 0;
            }
            else
            {
                if (PlayerList[nPlayer].nWeaponState == 5)
                {
                    PlayerList[nPlayer].nCurrentWeapon = PlayerList[nPlayer].nNewWeapon;

                    nWeapon = PlayerList[nPlayer].nCurrentWeapon;

                    PlayerList[nPlayer].nNewWeapon = -1;
                    PlayerList[nPlayer].nWeaponState = 0;
                }
                else
                {
                    PlayerList[nPlayer].nWeaponState = 5;
                }

                PlayerList[nPlayer].nWeaponFrame = 0;
                continue;
            }
        } // end of if (PlayerList[nPlayer].field_34 >= SeqSize[var_3C])

loc_flag:

        // loc_27001
        short nFrameFlag = seq_GetFrameFlag(var_3C, PlayerList[nPlayer].nWeaponFrame);

        if (((!(nSectFlag & kSectUnderwater)) || nWeapon == kWeaponRing) && (nFrameFlag & 4))
        {
            BuildFlash(nPlayer, sprite[nPlayerSprite].sectnum, 512);
            AddFlash(
                sprite[nPlayerSprite].sectnum,
                sprite[nPlayerSprite].x,
                sprite[nPlayerSprite].y,
                sprite[nPlayerSprite].z,
                0);
        }

        if (nFrameFlag & 0x80)
        {
            int nAction = PlayerList[nPlayer].nAction;

            int var_38 = 1;

            if (nAction < 10 || nAction > 12) {
                var_38 = 0;
            }

            if (nPlayer == nLocalPlayer) {
                obobangle = bobangle = 512;
            }

            if (nWeapon == kWeaponFlamer && (!(nSectFlag & kSectUnderwater)))
            {
                nTemperature[nPlayer]++;

                if (nTemperature[nPlayer] > 50)
                {
                    nTemperature[nPlayer] = 0;
                    PlayerList[nPlayer].nWeaponState = 4;
                    PlayerList[nPlayer].nWeaponFrame = 0;
                }
            }

            short nAmmoType = WeaponInfo[nWeapon].nAmmoType;
            short nAngle = sprite[nPlayerSprite].ang;
            int theX = sprite[nPlayerSprite].x;
            int theY = sprite[nPlayerSprite].y;
            int theZ = sprite[nPlayerSprite].z;

            int ebp = Cos(nAngle) * (sprite[nPlayerSprite].clipdist << 3);
            int ebx = Sin(nAngle) * (sprite[nPlayerSprite].clipdist << 3);

            if (WeaponInfo[nWeapon].c)
            {
                int ecx;

                int theVal = (totalmoves + 101) & (WeaponInfo[nWeapon].c - 1);
                if (theVal & 1)
                    ecx = -theVal;
                else
                    ecx = theVal;

                int var_44 = (nAngle + 512) & kAngleMask;
                ebp += (Cos(var_44) >> 11) * ecx;
                ebx += (Sin(var_44) >> 11) * ecx;
            }

            int nHeight = (-GetSpriteHeight(nPlayerSprite)) >> 1;

            if (nAction < 6)
            {
                nHeight -= 1792;
            }
            else
            {
                if (!var_38)
                {
                    nHeight += 1024;
                }
                else {
                    nHeight -= 2560;
                }
            }

            short nSectorB = sprite[nPlayerSprite].sectnum;

            switch (nWeapon)
            {
                // loc_27266:
                case kWeaponSword:
                {
                    nHeight += (92 - fix16_to_int(sPlayerInput[nPlayer].horizon)) << 6;

                    theZ += nHeight;

                    int var_28;

                    if (PlayerList[nPlayer].nWeaponState == 2) {
                        var_28 = 6;
                    }
                    else {
                        var_28 = 9;
                    }

                    int cRange = CheckCloseRange(nPlayer, &theX, &theY, &theZ, &nSectorB);

                    if (cRange)
                    {
                        short nDamage = BulletInfo[kWeaponSword].nDamage;

                        if (nPlayerDouble[nPlayer]) {
                            nDamage *= 2;
                        }

                        if ((cRange & 0xC000) >= 0x8000)
                        {
                            if ((cRange & 0xC000) == 0x8000) // hit wall
                            {
                                // loc_2730E:
                                var_28 += 2;
                            }
                            else if ((cRange & 0xC000) == 0xC000) // hit sprite
                            {
                                short nSprite2 = cRange & 0x3FFF;

                                if (sprite[nSprite2].cstat & 0x50)
                                {
                                    var_28 += 2;
                                }
                                else if (sprite[nSprite2].statnum > 90 && sprite[nSprite2].statnum <= 199)
                                {
                                    runlist_DamageEnemy(nSprite2, nPlayerSprite, nDamage);

                                    if (sprite[nSprite2].statnum < 102) {
                                        var_28++;
                                    }
                                    else if (sprite[nSprite2].statnum == 102)
                                    {
                                        // loc_27370:
                                        BuildAnim(-1, 12, 0, theX, theY, theZ, nSectorB, 30, 0);
                                    }
                                    else if (sprite[nSprite2].statnum == kStatExplodeTrigger) {
                                        var_28 += 2;
                                    }
                                    else {
                                        var_28++;
                                    }
                                }
                                else
                                {
                                    // loc_27370:
                                    BuildAnim(-1, 12, 0, theX, theY, theZ, nSectorB, 30, 0);
                                }
                            }
                        }
                    }

                    // loc_27399:
                    PlayerList[nPlayer].nWeaponState = var_28;
                    PlayerList[nPlayer].nWeaponFrame = 0;
                    break;
                }
                case kWeaponFlamer:
                {
                    if (nSectFlag & kSectUnderwater)
                    {
                        DoBubbles(nPlayer);
                        PlayerList[nPlayer].nWeaponState = 1;
                        PlayerList[nPlayer].nWeaponFrame = 0;
                        StopSpriteSound(nPlayerSprite);
                        break;
                    }
                    else
                    {
                        if (var_38) {
                            nHeight += 768;
                        }
                        else {
                            nHeight -= 2560;
                        }

                        // fall through to case 1 (kWeaponPistol)
                        fallthrough__;
                    }
                }

                case kWeaponM60:
                {
                    if (nWeapon == kWeaponM60) { // hack(?) to do fallthrough from kWeapon3 into kWeaponPistol without doing the nQuake[] change
                        nQuake[nPlayer] = 128;
                    }
                    // fall through
                    fallthrough__;
                }
                case kWeaponPistol:
                {
                    int var_50 = (fix16_to_int(sPlayerInput[nPlayer].horizon) - 92) << 2;
                    nHeight -= var_50;

                    if (sPlayerInput[nPlayer].nTarget >= 0)
                    {
                                                assert(sprite[sPlayerInput[nPlayer].nTarget].sectnum < kMaxSectors);
                        var_50 = sPlayerInput[nPlayer].nTarget + 10000;
                    }

                    BuildBullet(nPlayerSprite, nAmmoType, ebp, ebx, nHeight, nAngle, var_50, var_1C);
                    break;
                }

                case kWeaponGrenade:
                {
                    ThrowGrenade(nPlayer, ebp, ebx, nHeight - 2560, fix16_to_int(sPlayerInput[nPlayer].horizon) - 92);
                    break;
                }
                case kWeaponStaff:
                {
                    BuildSnake(nPlayer, nHeight);
                    nQuake[nPlayer] = 512;

                    nXDamage[nPlayer] -= Sin(sprite[nPlayerSprite].ang + 512) << 9;
                    nYDamage[nPlayer] -= Sin(sprite[nPlayerSprite].ang) << 9;
                    break;
                }
                case kWeaponRing:
                    break;

                case kWeaponMummified:
                {
                    short nDamage = BulletInfo[kWeaponMummified].nDamage;
                    if (nPlayerDouble[nPlayer]) {
                        nDamage *= 2;
                    }

                    runlist_RadialDamageEnemy(nPlayerSprite, nDamage, BulletInfo[kWeaponMummified].nRadius);
                    break;
                }
            }

            // end of switch, nw_break_2:
            if (nWeapon < kWeaponMummified)
            {
                if (nWeapon != kWeaponGrenade)
                {
                    if (WeaponInfo[nWeapon].bUsesAmmo) {
                        AddAmmo(nPlayer, nAmmoType, -1);
                    }

                    if (nWeapon == kWeaponM60) {
                        nPlayerClip[nPlayer]--;
                    }
                    else if (nWeapon == kWeaponPistol) {
                        nPistolClip[nPlayer]--;
                    }
                }

                if (!WeaponInfo[nWeapon].bUsesAmmo || PlayerList[nPlayer].nAmmo[WeaponInfo[nWeapon].nAmmoType])
                {
                    if (nWeapon == kWeaponM60 && nPlayerClip[nPlayer] <= 0)
                    {
                        PlayerList[nPlayer].nWeaponState = 3;
                        PlayerList[nPlayer].nWeaponFrame = 0;
                        // goto loc_27609:
                    }
                }
                else if (nWeapon != kWeaponGrenade)
                {
                    SelectNewWeapon(nPlayer);
                    // go to loc_27609:
                }
            }
        }
    }
}

void DrawWeapons(int smooth)
{
    if (bCamera) {
        return;
    }

    short nWeapon = PlayerList[nLocalPlayer].nCurrentWeapon;
    if (nWeapon < -1) {
        return;
    }

    short var_34 = PlayerList[nLocalPlayer].nWeaponState;

    short var_30 = SeqOffsets[WeaponInfo[nWeapon].nSeq];

    short var_28 = var_30 + WeaponInfo[nWeapon].b[var_34];

    int8_t nShade = sector[initsect].ceilingshade;

    int nDouble = nPlayerDouble[nLocalPlayer];
    int nPal = kPalNormal;

    if (nDouble)
    {
        if (word_96E26) {
            nPal = kPalRedBrite;
        }

        word_96E26 = word_96E26 == 0;
    }

    nPal = RemapPLU(nPal);

    int nVal = totalvel[nLocalPlayer] >> 1;

    // CHECKME - not & 0x7FF?
    int nBobAngle = angle_interpolate16(obobangle, bobangle, smooth);
    int	yOffset = (nVal * (sintable[nBobAngle & 0x3FF] >> 8)) >> 9;

    int xOffset = 0;

    if (var_34 == 1)
    {
        xOffset = ((Sin(nBobAngle + 512) >> 8) * nVal) >> 8;
    }
    else
    {
        xOffset = 0;
        obobangle = bobangle = 512;
    }

    if (nWeapon == 3 && var_34 == 1) {
        // If the weapon is the flamer, draw the pilot light that sits at the muzzle.
        seq_DrawPilotLightSeq(xOffset, yOffset);
    }
    else if ((nWeapon == 8 || nWeapon == 9) && windowxy2.y != 0)
    {
        // If the current "weapon" is either the normal or explosive death arm (which are treated as "weapons"),
        // and the window's Y does not equal zero (to avoid inadvertent division by zero below), we determine
        // special X offsets, since these two produce "gun sequences" that are cut off on the side for screen
        // ratios that are wider than standard/4:3.
        //
        // Rot/mummy and burn death arm "weapons" don't suffer from this issue (they have intrinsically wider art).
        //
        // The xOffset calculated below is later added to the standard X offset of 160 when the "gun sequence"
        // is being drawn.
        float screenRatio = (float)windowxy2.x / windowxy2.y; // Safe from division by zero.
        float newX        = ceil(120 * screenRatio);

        // For standard 4:3 ratio (1.333333) or slimmer, we want to add nothing (take the max between zero and
        // xOffset), all wider ratios must offset.
        //
        // Examples which result in no change:
        // 120 * 1.25       == 150, so (150 - 160) == -10 offset (slimmer than 4:3)
        // 120 * 1.333333   == 160, so (160 - 160) == 0 offset (this is 4:3)
        //
        // Examples which result in positive offsets (all wider than 4:3):
        // 120 * 1.5        == 180, so (180 - 160) == +20 offset
        // 120 * 1.6        == 192, so (192 - 160) == +32 offset
        // 120 * 1.706667   == 205, so (205 - 160) == +45 offset
        // 120 * 1.777778   == 214, so (214 - 160) == +54 offset
        xOffset = max(0, int(newX) - 160);
    }

    if (nWeapon < 0) {
        nShade = sprite[PlayerList[nLocalPlayer].nSprite].shade;
    }

    seq_DrawGunSequence(var_28, PlayerList[nLocalPlayer].nWeaponFrame, xOffset, yOffset, nShade, nPal);

    if (nWeapon != kWeaponM60)
        return;

    switch (var_34)
    {
        default:
            return;

        case 0:
        {
            int nClip = nPlayerClip[nLocalPlayer];

            if (nClip <= 0)
                return;

            int nSeqOffset;

            if (nClip <= 3)
            {
                nSeqOffset = var_30 + 1;
            }
            else if (nClip <= 6)
            {
                nSeqOffset = var_30 + 2;
            }
            else if (nClip <= 25)
            {
                nSeqOffset = var_30 + 3;
            }
            else if (nClip > 25)
            {
                nSeqOffset = var_30 + 4;
            }

            seq_DrawGunSequence(nSeqOffset, PlayerList[nLocalPlayer].nWeaponFrame, xOffset, yOffset, nShade, nPal);
            return;
        }
        case 1:
        {
            int nClip = nPlayerClip[nLocalPlayer];

            short edx = (nClip % 3) * 4;

            if (nClip <= 0) {
                return;
            }

            seq_DrawGunSequence(var_30 + 8, edx, xOffset, yOffset, nShade, nPal);

            if (nClip <= 3) {
                return;
            }

            seq_DrawGunSequence(var_30 + 9, edx, xOffset, yOffset, nShade, nPal);

            if (nClip <= 6) {
                return;
            }

            seq_DrawGunSequence(var_30 + 10, edx, xOffset, yOffset, nShade, nPal);

            if (nClip <= 25) {
                return;
            }

            seq_DrawGunSequence(var_30 + 11, edx, xOffset, yOffset, nShade, nPal);
            return;
        }
        case 2:
        {
            int nClip = nPlayerClip[nLocalPlayer];

            short dx = PlayerList[nLocalPlayer].nWeaponFrame;

            if (nClip <= 0) {
                return;
            }

            seq_DrawGunSequence(var_30 + 8, dx, xOffset, yOffset, nShade, nPal);

            if (nClip <= 3) {
                return;
            }

            seq_DrawGunSequence(var_30 + 9, dx, xOffset, yOffset, nShade, nPal);

            if (nClip <= 6) {
                return;
            }

            seq_DrawGunSequence(var_30 + 10, dx, xOffset, yOffset, nShade, nPal);

            if (nClip <= 25) {
                return;
            }

            seq_DrawGunSequence(var_30 + 11, dx, xOffset, yOffset, nShade, nPal);
            return;
        }

        case 3:
        case 4:
            return;

        case 5:
        {
            int nClip = nPlayerClip[nLocalPlayer];

            short ax = PlayerList[nLocalPlayer].nWeaponFrame;

            if (nClip <= 0) {
                return;
            }

            int nSeqOffset;

            if (nClip <= 3)
            {
                nSeqOffset = var_30 + 20;
            }
            else if (nClip <= 6)
            {
                nSeqOffset = var_30 + 19;
            }
            else if (nClip <= 25)
            {
                nSeqOffset = var_30 + 18;
            }
            else
            {
                nSeqOffset = var_30 + 17;
            }

            seq_DrawGunSequence(nSeqOffset, ax, xOffset, yOffset, nShade, nPal);
            return;
        }
    }
}

class GunLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void GunLoadSave::Load()
{
    Read(&nTemperature[nLocalPlayer], sizeof(nTemperature[nLocalPlayer]));
    Read(&word_96E26, sizeof(word_96E26));
}

void GunLoadSave::Save()
{
    Write(&nTemperature[nLocalPlayer], sizeof(nTemperature[nLocalPlayer]));
    Write(&word_96E26, sizeof(word_96E26));
}

static GunLoadSave* myLoadSave;

void GunLoadSaveConstruct()
{
    myLoadSave = new GunLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT

This file is part of PCExhumed.

PCExhumed is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef __gun_h__
#define __gun_h__

#include "compat.h"
#include "sequence.h"

#define kMaxWeapons		7

enum
{
    kWeaponSword = 0,
    kWeaponPistol,
    kWeaponM60,
    kWeaponFlamer,
    kWeaponGrenade,
    kWeaponStaff,
    kWeaponRing,
    kWeaponMummified
};

struct Weapon
{
    short nSeq;
    short b[12]; // seq offsets?
    short nAmmoType;
    short c;
    short bUsesAmmo;
    short bFireUnderwater;
};

extern Weapon WeaponInfo[];
extern short nTemperature[];

void RestoreMinAmmo(short nPlayer);
void FillWeapons(short nPlayer);
void ResetPlayerWeapons(short nPlayer);
void InitWeapons();
void SelectNextWeapon();
void SelectPreviousWeapon();
void SetNewWeapon(short nPlayer, short nWeapon);
void SetNewWeaponImmediate(short nPlayer, short nWeapon);
void SetNewWeaponIfBetter(short nPlayer, short nWeapon);
void SelectNewWeapon(short nPlayer);
void StopFiringWeapon(short nPlayer);
void FireWeapon(short nPlayer);
void CheckClip(short nPlayer);
void MoveWeapons(short nPlayer);
void DrawWeapons(int smooth);

#endif