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 "compat.h"
#include "keyboard.h"
#include "control.h"
#include "init.h"
#include "runlist.h"
#include "switch.h"
#include "object.h"
#include "aistuff.h"
#include "ramses.h"
#include "player.h"
#include "mummy.h"
#include "move.h"
#include "ra.h"
#include "view.h"
#include "runlist.h"
#include "engine.h"
#include "sound.h"
#include "exhumed.h"
#include "config.h"
#include "items.h"
#include "light.h"
#include "map.h"
#include "menu.h"
#include "lighting.h"
#include "anims.h"
#include "input.h"
#include "util.h"
#include <stdio.h>
#include <string.h>

enum
{
    kTagRamses = 61,
};

ClockTicks ototalclock = 0;

int initx, inity, initz;
short inita, initsect;

short nCurChunkNum = 0;

short nBodyGunSprite[50];
int movefifoend;
int movefifopos;

short nCurBodyGunNum;

short SectSoundSect[kMaxSectors] = { 0 };
short SectSound[kMaxSectors]     = { 0 };
short SectFlag[kMaxSectors]      = { 0 };
int   SectDepth[kMaxSectors]     = { 0 };
int   SectAbove[kMaxSectors]     = { 0 };
short SectDamage[kMaxSectors]    = { 0 };
short SectSpeed[kMaxSectors]     = { 0 };
int   SectBelow[kMaxSectors]     = { 0 };

uint8_t bIsVersion6 = kTrue;

#if 0
// definitions for map version 6 structures
#pragma pack(1)

// 37 bytes
struct Sector_6
{
    uint16_t wallptr, wallnum;
    short ceilingpicnum, floorpicnum;
    short ceilingheinum, floorheinum;
    int ceilingz, floorz;
    int8_t ceilingshade, floorshade;
    uint8_t ceilingxpanning, floorxpanning;
    uint8_t ceilingypanning, floorypanning;
    uint8_t ceilingstat, floorstat;
    uint8_t ceilingpal, floorpal;
    uint8_t visibility;
    short lotag, hitag, extra;
};

struct Wall_6
{
    int x, y;
    short point2, nextsector, nextwall;
    short picnum, overpicnum;
    int8_t shade;
    uint8_t pal;
    short cstat;
    uint8_t xrepeat, yrepeat, xpanning, ypanning;
    short lotag, hitag, extra;
};

// 43 bytes
struct Sprite_6
{
    int x, y, z;
    short cstat;
    int8_t shade;
    uint8_t pal, clipdist;
    uint8_t xrepeat, yrepeat;
    int8_t xoffset, yoffset;
    short picnum, ang, xvel, yvel, zvel, owner;
    short sectnum, statnum;
    short lotag, hitag, extra;
};

#pragma pack()

static Sector_6 sector_6[1024];
static Wall_6   wall_6[8192];
static Sprite_6 sprite_6[4096];
#endif

bool LoadLevel(int nMap)
{
    char fileName[BMAX_PATH];

    initspritelists();

    if (bUserMap)
    {
        nMap = 0;
        Bstrncpy(fileName, gUserMapFilename, BMAX_PATH);
    }
    else
    {
        Bsnprintf(fileName, sizeof(fileName), "lev%d.map", nMap);
    }

    // init stuff
    {
        StopAllSounds();
        nCreaturesLeft = 0;
        nFreeze = 0;
        nSpiritSprite = -1;

        InitLion();
        InitRexs();
        InitSets();
        InitQueens();
        InitRoachs();
        InitWasps();
        InitRats();
        InitBullets();
        InitWeapons();
        InitGrenades();
        InitAnims();
        InitSnakes();
        InitFishes();
        InitLights();
        InitMap();
        InitBubbles();
        InitObjects();
        InitLava();
        InitPushBlocks();
        InitAnubis();
        InitSpider();
        InitMummy();
        InitScorp();
        InitPlayer();
        InitItems();
        InitInput();

        if (nMap == kMap20) {
            InitEnergyTile();
        }
    }

    if (nMap > 15)
    {
        nSwitchSound = 35;
        nStoneSound = 23;
        nElevSound = 51;
        nStopSound = 35;
    }
    else
    {
        nSwitchSound = 33;
        nStoneSound = 23;
        nElevSound = 23;
        nStopSound = 66;
    }

    if (nMap < 0) {
        return false;
    }

#if 0
    {
        // going to load the map without loadboard() - to take care of version 6 to 7 map conversion
        //int hFile = kopen4load(fileName_1, 1);
        int hFile = kopen4load(fileName_1, 0);
        //	int hFile = open(fileName_1, O_BINARY | O_RDONLY);
        if (hFile == -1) {
            return false;
        }

        int version;

        kread(hFile, &version, sizeof(version));
        if (version != 6) {
            bIsVersion6 = kFalse;
        }

        initspritelists();

        memset(show2dsector, 0, sizeof(show2dsector));
        memset(show2dsprite, 0, sizeof(show2dsprite));
        memset(show2dwall,   0, sizeof(show2dwall));

        // replacement for build's loadboard()
        kread(hFile, &initx, 4);
        kread(hFile, &inity, 4);
        kread(hFile, &initz, 4);
        kread(hFile, &inita, 2);
        kread(hFile, &initsect, 2);

        // sectors
        short nSectors;
        kread(hFile, &nSectors, sizeof(nSectors));

        if (bIsVersion6) {
            kread(hFile, sector_6, sizeof(Sector_6) * nSectors);
        }
        else {
            kread(hFile, sector, sizeof(SECTOR) * nSectors);
        }

        // walls
        short nWalls;
        kread(hFile, &nWalls, sizeof(nWalls));

        if (bIsVersion6) {
            kread(hFile, wall_6, sizeof(Wall_6) * nWalls);
        }
        else {
            kread(hFile, wall, sizeof(WALL) * nWalls);
        }

        // sprites
        short nSprites;
        kread(hFile, &nSprites, sizeof(nSprites));

        if (bIsVersion6) {
            kread(hFile, sprite_6, sizeof(Sprite_6) * nSprites);
        }
        else {
            kread(hFile, sprite, sizeof(SPRITE) * nSprites);
        }

        // set engine variables
        numsectors = nSectors;
        numwalls = nWalls;

        // load in our version 6 structs to the engines v7 structs if required
        if (bIsVersion6)
        {
            for (int nSector = 0; nSector < nSectors; nSector++)
            {
                sector[nSector].ceilingz = sector_6[nSector].ceilingz;
                sector[nSector].floorz = sector_6[nSector].floorz;
                sector[nSector].wallptr = sector_6[nSector].wallptr;
                sector[nSector].wallnum = sector_6[nSector].wallnum;
                sector[nSector].ceilingpicnum = sector_6[nSector].ceilingpicnum;
                sector[nSector].ceilingheinum = Max(Min(((int)sector_6[nSector].ceilingheinum) << 5, 32767), -32768);

                if ((sector_6[nSector].ceilingstat & 2) == 0) {
                    sector[nSector].ceilingheinum = 0;
                }

                sector[nSector].ceilingshade = sector_6[nSector].ceilingshade;
                sector[nSector].ceilingpal = sector_6[nSector].ceilingpal;
                sector[nSector].ceilingxpanning = sector_6[nSector].ceilingxpanning;
                sector[nSector].ceilingypanning = sector_6[nSector].ceilingypanning;
                sector[nSector].floorpicnum = sector_6[nSector].floorpicnum;
                sector[nSector].floorheinum = Max(Min(((int)sector_6[nSector].floorheinum) << 5, 32767), -32768);

                if ((sector_6[nSector].floorstat & 2) == 0) {
                    sector[nSector].floorheinum = 0;
                }

                sector[nSector].floorshade = sector_6[nSector].floorshade;
                sector[nSector].floorpal = sector_6[nSector].floorpal;
                sector[nSector].floorxpanning = sector_6[nSector].floorxpanning;
                sector[nSector].floorypanning = sector_6[nSector].floorypanning;
                sector[nSector].ceilingstat = sector_6[nSector].ceilingstat;
                sector[nSector].floorstat = sector_6[nSector].floorstat;
                sector[nSector].visibility = sector_6[nSector].visibility;
                sector[nSector].filler = 0;
                sector[nSector].lotag = sector_6[nSector].lotag;
                sector[nSector].hitag = sector_6[nSector].hitag;
                sector[nSector].extra = sector_6[nSector].extra;
            }

            for (int nWall = 0; nWall < nWalls; nWall++)
            {
                wall[nWall].x = wall_6[nWall].x;
                wall[nWall].y = wall_6[nWall].y;
                wall[nWall].point2 = wall_6[nWall].point2;
                wall[nWall].nextwall = wall_6[nWall].nextwall;
                wall[nWall].nextsector = wall_6[nWall].nextsector;
                wall[nWall].cstat = wall_6[nWall].cstat;
                wall[nWall].picnum = wall_6[nWall].picnum;
                wall[nWall].overpicnum = wall_6[nWall].overpicnum;
                wall[nWall].shade = wall_6[nWall].shade;
                wall[nWall].pal = wall_6[nWall].pal;
                wall[nWall].xrepeat = wall_6[nWall].xrepeat;
                wall[nWall].yrepeat = wall_6[nWall].yrepeat;
                wall[nWall].xpanning = wall_6[nWall].xpanning;
                wall[nWall].ypanning = wall_6[nWall].ypanning;
                wall[nWall].lotag = wall_6[nWall].lotag;
                wall[nWall].hitag = wall_6[nWall].hitag;
                wall[nWall].extra = wall_6[nWall].extra;
            }

            for (int nSprite = 0; nSprite < nSprites; nSprite++)
            {
                sprite[nSprite].x = sprite_6[nSprite].x;
                sprite[nSprite].y = sprite_6[nSprite].y;
                sprite[nSprite].z = sprite_6[nSprite].z;
                sprite[nSprite].cstat = sprite_6[nSprite].cstat;
                sprite[nSprite].picnum = sprite_6[nSprite].picnum;
                sprite[nSprite].shade = sprite_6[nSprite].shade;
                sprite[nSprite].pal = sprite_6[nSprite].pal;
                sprite[nSprite].clipdist = sprite_6[nSprite].clipdist;
                sprite[nSprite].filler = 0;
                sprite[nSprite].xrepeat = sprite_6[nSprite].xrepeat;
                sprite[nSprite].yrepeat = sprite_6[nSprite].yrepeat;
                sprite[nSprite].xoffset = sprite_6[nSprite].xoffset;
                sprite[nSprite].yoffset = sprite_6[nSprite].yoffset;
                sprite[nSprite].sectnum = sprite_6[nSprite].sectnum;
                sprite[nSprite].statnum = sprite_6[nSprite].statnum;
                sprite[nSprite].ang = sprite_6[nSprite].ang;
                sprite[nSprite].owner = sprite_6[nSprite].owner;
                sprite[nSprite].xvel = sprite_6[nSprite].xvel;
                sprite[nSprite].yvel = sprite_6[nSprite].yvel;
                sprite[nSprite].zvel = sprite_6[nSprite].zvel;
                sprite[nSprite].lotag = sprite_6[nSprite].lotag;
                sprite[nSprite].hitag = sprite_6[nSprite].hitag;
                sprite[nSprite].extra = sprite_6[nSprite].extra;
            }
        }

        for (int nSprite = 0; nSprite < nSprites; nSprite++) {
            insertsprite(sprite[nSprite].sectnum, sprite[nSprite].statnum);
        }

        updatesector(initx, inity, &initsect);

        kclose(hFile);
        hFile = -1;
    }
    // loadboard has finished
#endif
    vec3_t startPos;
    int status = engineLoadBoard(fileName, 0, &startPos, &inita, &initsect);
    if (status == -2)
        status = engineLoadBoardV5V6(fileName, 0, &startPos, &inita, &initsect);
    if (status < 0)
    {
        OSD_Printf(OSD_ERROR "Map \"%s\" not found or invalid map version!\n", fileName);
        return false;
    }

    initx = startPos.x;
    inity = startPos.y;
    initz = startPos.z;

#ifdef YAX_ENABLE
    yax_update(1);
#endif

    int i;

    for (i = 0; i < kMaxPlayers; i++)
    {
        PlayerList[i].nSprite = -1;
    }

    psky_t* pSky = tileSetupSky(0);

    pSky->tileofs[0] = 0;
    pSky->tileofs[1] = 0;
    pSky->tileofs[2] = 0;
    pSky->tileofs[3] = 0;
    pSky->yoffs = 256;
    pSky->lognumtiles = 2;
    pSky->horizfrac = 65536;
    pSky->yscale = 65536;
    parallaxtype = 2;
    g_visibility = 2048;
    flash = 0;
    precache();

    LoadObjects();

    levelnum = nMap;

    return true;
}

void ResetEngine()
{
    SetOverscan(BASEPAL);

    EraseScreen(-1);

    resettiming();

    totalclock  = 0;
    ototalclock = totalclock;
    localclock  = (int)totalclock;

    numframes = 0;
}

void InstallEngine()
{
    // initgroupfile("stuff.dat");

    char *cwd;

    if (g_modDir[0] != '/' && (cwd = buildvfs_getcwd(NULL, 0)))
    {
        buildvfs_chdir(g_modDir);
        if (artLoadFiles("tiles%03i.art", MAXCACHE1DSIZE) < 0)
        {
            buildvfs_chdir(cwd);
            if (artLoadFiles("tiles%03i.art", MAXCACHE1DSIZE) < 0)
                bail2dos("Failed loading art.");
        }
        buildvfs_chdir(cwd);
#ifndef __ANDROID__ //This crashes on *some* Android devices. Small onetime memory leak. TODO fix above function
        Xfree(cwd);
#endif
    }
    else if (artLoadFiles("tiles%03i.art",MAXCACHE1DSIZE) < 0)
        bail2dos("Failed loading art.");

    // TEMP

    //nScreenWidth *= 2;
    //nScreenHeight *= 2;
    bHiRes = kTrue;
    // TEMP

    if (engineInit())
    {
        wm_msgbox("Fatal Engine Initialization Error",
                  "There was a problem initializing the engine: %s\n\nThe application will now close.", engineerrstr);
        //TODO:
        //G_Cleanup();
        ERRprintf("G_Startup: There was a problem initializing the engine: %s\n", engineerrstr);
        exit(6);
    }
    if (videoSetGameMode(gSetup.fullscreen, gSetup.xdim, gSetup.ydim, gSetup.bpp, 0) < 0)
    {
        initprintf("Failure setting video mode %dx%dx%d %s! Trying next mode...\n", gSetup.xdim, gSetup.ydim,
                    gSetup.bpp, gSetup.fullscreen ? "fullscreen" : "windowed");

        int resIdx = 0;

        for (int i=0; i < validmodecnt; i++)
        {
            if (validmode[i].xdim == gSetup.xdim && validmode[i].ydim == gSetup.ydim)
            {
                resIdx = i;
                break;
            }
        }

        int const savedIdx = resIdx;
        int bpp = gSetup.bpp;

        while (videoSetGameMode(0, validmode[resIdx].xdim, validmode[resIdx].ydim, bpp, 0) < 0)
        {
            initprintf("Failure setting video mode %dx%dx%d windowed! Trying next mode...\n",
                        validmode[resIdx].xdim, validmode[resIdx].ydim, bpp);

            if (++resIdx == validmodecnt)
            {
                if (bpp == 8)
                    bail2dos("Fatal error: unable to set any video mode!");

                resIdx = savedIdx;
                bpp = 8;
            }
        }

        gSetup.xdim = validmode[resIdx].xdim;
        gSetup.ydim = validmode[resIdx].ydim;
        gSetup.bpp  = bpp;
    }

    LoadPaletteLookups();

    enginecompatibilitymode = ENGINE_19950829;
}

void RemoveEngine()
{
    engineUnInit();
    uninitgroupfile();
}

void SetBelow(short nCurSector, short nBelowSector)
{
    SectBelow[nCurSector] = nBelowSector;
}

void SetAbove(short nCurSector, short nAboveSector)
{
    SectAbove[nCurSector] = nAboveSector;
}

void SnapSectors(short nSectorA, short nSectorB, short b)
{
    // edx - nSectorA
    // eax - nSectorB

    short nWallA = sector[nSectorA].wallptr;
    short nWallB = sector[nSectorB].wallptr;

    short num1 = sector[nSectorA].wallnum;
    short num2 = sector[nSectorB].wallnum;

    int nCount = 0;

    while (num1 > nCount)
    {
        short dx = nWallB;

        int esi = 0x7FFFFFF;
        int edi = esi;

        int x = wall[nWallA].x;
        int y = wall[nWallA].y;

        int var_14 = 0;

        int nCount2 = 0;

        while (nCount2 < num2)
        {
            int eax = x - wall[dx].x;
            int ebx = y - wall[dx].y;

            if (eax < 0) {
                eax = -eax;
            }

            int var_38 = eax;

            if (ebx < 0) {
                ebx = -ebx;
            }

            int var_3C = ebx;

            var_38 += var_3C;

            eax = esi;
            if (eax < 0) {
                eax = -eax;
            }

            var_3C = eax;

            eax = edi;
//			int var_34 = edi;
            if (eax < 0) {
                eax = -eax;
            }

            int var_34 = eax;

            var_34 += var_3C;

            if (var_38 < var_34)
            {
                esi = x - wall[dx].x;
                edi = y - wall[dx].y;
                var_14 = dx;
            }

            dx++;
            nCount2++;
        }

        dragpoint(var_14, wall[var_14].x + esi, wall[var_14].y + edi, 0);

        nCount++;
        nWallA++;
    }

    if (b) {
        sector[nSectorB].ceilingz = sector[nSectorA].floorz;
    }

    if (SectFlag[nSectorA] & 0x1000) {
        SnapBobs(nSectorA, nSectorB);
    }
}

void InitSectFlag()
{
    for (int i = 0; i < kMaxSectors; i++)
    {
        SectSoundSect[i] = -1;
        SectSound[i] = -1;
        SectAbove[i] = -1;
        SectBelow[i] = -1;
        SectDepth[i] = 0;
        SectFlag[i]  = 0;
        SectSpeed[i] = 0;
        SectDamage[i] = 0;
    }
}

void ProcessSpriteTag(short nSprite, short nLotag, short nHitag)
{
    int nChannel = runlist_AllocChannel(nHitag % 1000);

    int nSpeed = nLotag / 1000;
    if (!nSpeed) {
        nSpeed = 1;
    }

    int nVal = nHitag;

    if (nLotag >= 900 && nLotag <= 949)
    {
        ProcessTrailSprite(nSprite, nLotag, nHitag);
        return;
    }

    // handle tags 6 to 60
    switch (nLotag)
    {
        case 8: // M-60 ammo belt
        {
            nVal = 3 * (nHitag / 3);
            // fall through to 6,7 etc
            fallthrough__;
        }
        case 6:
        case 7:
        case 9:
        case 10:
        case 11:
        case 15:
        case 17:
        case 18:
        case 19:
        case 20:
        case 21:
        case 22:
        case 23:
        case 24:
        case 26:
        case 28:
        case 29:
        case 30:
        case 31:
        case 32:
        case 33:
        case 34:
        case 35:
        case 36:
        case 37:
        case 39:
        case 40:
        case 41:
        case 42:
        case 43:
        case 44:
        case 45:
        case 46:
        case 47:
        case 48:
        case 49:
        case 50:
        case 51:
        case 52:
        case 53:
        case 54:
        case 55:
        case 56:
        case 57:
        case 58:
        case 60:
        {
            sprite[nSprite].hitag = nVal;
            changespritestat(nSprite, nLotag + 900);
            sprite[nSprite].cstat &= 0xFEFE;
            BuildItemAnim(nSprite);
            return;
        }
        case 12: // berry twig
        {
            sprite[nSprite].hitag = 40;
            changespritestat(nSprite, nLotag + 900);
            sprite[nSprite].cstat &= 0xFEFE;
            BuildItemAnim(nSprite);
            return;
        }
        case 13: // blood bowl
        {
            sprite[nSprite].hitag = 160;
            changespritestat(nSprite, nLotag + 900);
            sprite[nSprite].cstat &= 0xFEFE;
            BuildItemAnim(nSprite);
            return;
        }
        case 14: // venom bowl
        {
            sprite[nSprite].hitag = -200;
            changespritestat(nSprite, nLotag + 900);
            sprite[nSprite].cstat &= 0xFEFE;
            BuildItemAnim(nSprite);
            return;
        }

        case 16:
            // reserved
            mydeletesprite(nSprite);
            return;

        case 25:
        case 59:
        {
            // extra life or checkpoint scarab. Delete for multiplayer
            if (nNetPlayerCount != 0)
            {
                mydeletesprite(nSprite);
                return;
            }
            else
            {
                sprite[nSprite].hitag = nVal;
                changespritestat(nSprite, nLotag + 900);
                sprite[nSprite].cstat &= 0xFEFE;
                BuildItemAnim(nSprite);
                return;
            }
        }
        case 27:
        {
            sprite[nSprite].hitag = 1;
            changespritestat(nSprite, 9 + 900);
            sprite[nSprite].cstat &= 0xFEFE;
            BuildItemAnim(nSprite);
            return;
        }

        case 38: // raw energy
        {
            nVal++;
            nVal--; // CHECKME ??
            sprite[nSprite].hitag = nVal;
            changespritestat(nSprite, nLotag + 900);
            sprite[nSprite].cstat &= 0xFEFE;
            BuildItemAnim(nSprite);
            return;
        }
    }

    int v6 = nLotag % 1000;

    if (!bNoCreatures || v6 < 100 || v6 > 118)
    {
        if (v6 > 999) {
            mydeletesprite(nSprite);
            return;
        }

        switch (v6)
        {
            case 999:
            {
                AddFlicker(sprite[nSprite].sectnum, nSpeed);
                break;
            }
            case 998:
            {
                AddGlow(sprite[nSprite].sectnum, nSpeed);
                break;
            }
            case 118: // Anubis with drum
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildAnubis(nSprite, 0, 0, 0, 0, 0, 1);
                return;
            }
            case 117:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildWasp(nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 116:
            {
                BuildRat(nSprite, 0, 0, 0, 0, -1);
                return;
            }
            case 115: // Rat (eating)
            {
                BuildRat(nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 113:
            {
                BuildQueen(nSprite, 0, 0, 0, 0, 0, nChannel);
                return;
            }
            case 112:
            {
                BuildScorp(nSprite, 0, 0, 0, 0, 0, nChannel);
                return;
            }
            case 111:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildSet(nSprite, 0, 0, 0, 0, 0, nChannel);
                return;
            }
            case 108:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildLava(nSprite, 0, 0, 0, 0, 0, nChannel);
                return;
            }
            case 107:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildRex(nSprite, 0, 0, 0, 0, 0, nChannel);
                return;
            }
            case 106:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildFish(nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 105:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildSpider(nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 104:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildRoach(1, nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 103:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildRoach(0, nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 102:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildLion(nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 101:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildMummy(nSprite, 0, 0, 0, 0, 0);
                return;
            }
            case 100:
            {
                if (bNoCreatures) {
                    mydeletesprite(nSprite);
                    return;
                }

                BuildAnubis(nSprite, 0, 0, 0, 0, 0, 0);
                return;
            }
            case 99: // underwater type 2
            {
                short nSector = sprite[nSprite].sectnum;
                SetAbove(nSector, nHitag);
                SectFlag[nSector] |= kSectUnderwater;

                mydeletesprite(nSprite);
                return;
            }
            case 98:
            {
                short nSector = sprite[nSprite].sectnum;
                SetBelow(nSector, nHitag);
                SnapSectors(nSector, nHitag, 1);

                mydeletesprite(nSprite);
                return;
            }
            case 97:
            {
                AddSectorBob(sprite[nSprite].sectnum, nHitag, 1);

                mydeletesprite(nSprite);
                return;
            }
            case 96: // Lava sector
            {
                int nDamage = nHitag / 4;
                if (!nDamage) {
                    nDamage = 1;
                }

                short nSector = sprite[nSprite].sectnum;

                SectDamage[nSector] = nDamage;
                SectFlag[nSector] |= kSectLava;

                mydeletesprite(nSprite);
                return;
            }
            case 95:
            {
                AddSectorBob(sprite[nSprite].sectnum, nHitag, 0);

                mydeletesprite(nSprite);
                return;
            }
            case 94: // water
            {
                short nSector = sprite[nSprite].sectnum;
                SectDepth[nSector] = nHitag << 8;

                mydeletesprite(nSprite);
                return;
            }
            case 93:
            {
                BuildBubbleMachine(nSprite);
                return;
            }
            case 90:
            {
                BuildObject(nSprite, 3, nHitag);
                return;
            }
            case 79:
            case 89:
            {
                short nSector = sprite[nSprite].sectnum;

                SectSpeed[nSector] = nSpeed;
                SectFlag[nSector] |= sprite[nSprite].ang;

                mydeletesprite(nSprite);
                return;
            }
            case 88:
            {
                AddFlow(nSprite, nSpeed, 0);

                mydeletesprite(nSprite);
                return;
            }
            case 80: // underwater
            {
                short nSector = sprite[nSprite].sectnum;
                SectFlag[nSector] |= kSectUnderwater;

                mydeletesprite(nSprite);
                return;
            }
            case 78:
            {
                AddFlow(nSprite, nSpeed, 1);

                short nSector = sprite[nSprite].sectnum;
                SectFlag[nSector] |= 0x8000;

                mydeletesprite(nSprite);
                return;
            }
            case 77:
            {
                int nArrow = BuildArrow(nSprite, nSpeed);

                runlist_AddRunRec(sRunChannels[nChannel].a, nArrow, kRunTrap);
                return;
            }
            case 76: // Explosion Trigger (Exploding Fire Cauldron)
            {
                BuildObject(nSprite, 0, nHitag);
                return;
            }
            case 75: // Explosion Target (Cauldrons, fireballs and grenades will destroy nearby 75 sprites)
            {
                BuildObject(nSprite, 1, nHitag);
                return;
            }
            case 71:
            {
                int nFireball = BuildFireBall(nSprite, nHitag, nSpeed);

                runlist_AddRunRec(sRunChannels[nChannel].a, nFireball, kRunTrap);
                return;
            }
            case 70:
            {
                BuildDrip(nSprite);
                return;
            }
            case 63:
            {
                changespritestat(nSprite, 405);
                sprite[nSprite].cstat = 0x8000;
                return;
            }
            case 62:
            {
                nNetStartSprite[nNetStartSprites] = nSprite;
                sprite[nSprite].cstat = 0x8000;

                nNetStartSprites++;
                return;
            }
            case kTagRamses: // Ramses head
            {
                nSpiritSprite = nSprite;
                sprite[nSprite].cstat |= 0x8000;
                return;
            }
            default: // TODO - checkme!
            {
                mydeletesprite(nSprite);
                return;
            }
        }
    }

    mydeletesprite(nSprite);
}

void ExamineSprites()
{
    nNetStartSprites = 0;
    nCurStartSprite = 0;

    for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
    {
        int nStatus = sprite[nSprite].statnum;
        if (!nStatus)
        {
            short lotag = sprite[nSprite].lotag;
            short hitag = sprite[nSprite].hitag;

            if ((nStatus < kMaxStatus) && lotag)
            {
                sprite[nSprite].lotag = 0;
                sprite[nSprite].hitag = 0;

                ProcessSpriteTag(nSprite, lotag, hitag);
            }
            else
            {
                changespritestat(nSprite, 0);
            }
        }
    }

    if (nNetPlayerCount)
    {
        int nSprite = insertsprite(initsect, 0);
        sprite[nSprite].x = initx;
        sprite[nSprite].y = inity;
        sprite[nSprite].z = initz;
        sprite[nSprite].cstat = 0x8000;
        nNetStartSprite[nNetStartSprites] = nSprite;
        nNetStartSprites++;
    }
}

void LoadObjects()
{
    runlist_InitRun();
    runlist_InitChan();
    InitLink();
    InitPoint();
    InitSlide();
    InitSwitch();
    InitElev();
    InitWallFace();
    InitTimeSlot();
    InitSectFlag();

    for (int nSector = 0; nSector < numsectors; nSector++)
    {
        short hitag = sector[nSector].hitag;
        short lotag = sector[nSector].lotag;

        sector[nSector].hitag = 0;
        sector[nSector].lotag = 0;
        sector[nSector].extra = -1;

        if (hitag || lotag)
        {
            sector[nSector].lotag = runlist_HeadRun() + 1;
            sector[nSector].hitag = lotag;

            runlist_ProcessSectorTag(nSector, lotag, hitag);
        }
    }

    for (int nWall = 0; nWall < numwalls; nWall++)
    {
        wall[nWall].extra = -1;

        short lotag = wall[nWall].lotag;
        short hitag = wall[nWall].hitag;

        wall[nWall].lotag = 0;

        if (hitag || lotag)
        {
            wall[nWall].lotag = runlist_HeadRun() + 1;
            runlist_ProcessWallTag(nWall, lotag, hitag);
        }
    }

    ExamineSprites();
    PostProcess();
    InitRa();
    InitChunks();

    for (int nChannel = 0; nChannel < kMaxChannels; nChannel++)
    {
        runlist_ChangeChannel(nChannel, 0);
        runlist_ReadyChannel(nChannel);
    }

    nCamerax = initx;
    nCameray = inity;
    nCameraz = initz;
}


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 __init_h__
#define __init_h__

#include "compat.h"

#define kMap20	20

enum {
    kSectUnderwater = 0x2000,
    kSectLava = 0x4000,
};

extern ClockTicks ototalclock;

extern int initx;
extern int inity;
extern int initz;
extern short inita;
extern short initsect;

extern short nCurChunkNum;
extern short nBodyGunSprite[50];
extern int movefifoend;
extern int movefifopos;
extern short nCurBodyGunNum;

void SnapSectors(short nSectorA, short nSectorB, short b);

extern short SectSound[];
extern short SectDamage[];
extern short SectSpeed[];
extern int SectBelow[];
extern short SectFlag[];
extern int SectDepth[];
extern short SectSoundSect[];
extern int SectAbove[];

bool LoadLevel(int nMap);
void InstallEngine();
void ResetEngine();
void RemoveEngine();
void LoadObjects();

#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 "input.h"
#include "engine.h"
#include "exhumed.h"
#include "player.h"
#include "serial.h"
#include "network.h"
#include "keyboard.h"
#include "control.h"
#include "config.h"
#include "status.h"
#include <string.h>

int nNetMoves = 0;

short nInputStack = 0;

short bStackNode[kMaxPlayers];

short nTypeStack[kMaxPlayers];
PlayerInput sPlayerInput[kMaxPlayers];

int *pStackPtr;

// (nInputStack * 32) - 11;

void PushInput(PlayerInput *pInput, int edx)
{
    if (!bStackNode[edx])
    {
//		memcpy(sInputStack[nInputStack], pInput,
    }
}

int PopInput()
{
    if (!nInputStack)
        return -1;

    nInputStack--;

    // TEMP
    return 0;
}

void InitInput()
{
    memset(nTypeStack, 0, sizeof(nTypeStack));
    nInputStack = 0;
    memset(bStackNode, 0, sizeof(bStackNode));

//	pStackPtr = &sInputStack;
}

void ClearSpaceBar(short nPlayer)
{
    sPlayerInput[nPlayer].buttons &= 0x0FB;
    CONTROL_ClearButton(gamefunc_Open);
}

void GetLocalInput()
{
    int i;
    for (i = 6; i >= 0; i--)
    {
        if (BUTTON(gamefunc_Weapon_1+i))
            break;
    }
    i++;

    if (PlayerList[nLocalPlayer].nHealth)
    {
        lLocalButtons = (BUTTON(gamefunc_Crouch) << 4) | (BUTTON(gamefunc_Fire) << 3)
                      | (BUTTON(gamefunc_Jump)<<0) | (i<<13);
    }
    else
    {
        lLocalButtons = 0;
    }

    lLocalButtons |= BUTTON(gamefunc_Open) << 2;

// TODO	ExecRecord(&sPlayerInput[nLocalPlayer], sizeof(PlayerInput));
}

void BackupInput()
{

}

void SendInput()
{

}

void LogoffPlayer(int nPlayer)
{
    if (nPlayer == nLocalPlayer)
        return;

    if (PlayerList[nPlayer].someNetVal == -1)
        return;

    memset(&sPlayerInput[nPlayer], 0, sizeof(sPlayerInput));

    sprite[nDoppleSprite[nPlayer]].cstat = 0x8000u;
    sprite[nPlayerFloorSprite[nPlayer]].cstat = 0x8000u;
    sprite[PlayerList[nPlayer].nSprite].cstat = 0x8000u;

    PlayerList[nPlayer].someNetVal = -1;

    StatusMessage(150, "Player %d has left the game", nPlayer);

// TODO	ClearPlayerInput(&sPlayerInput[nPlayer]);
    nNetPlayerCount--;
}

void UpdateInputs()
{
    nNetMoveFrames = moveframes;

    if (nNetPlayerCount)
    {
        if (bSerialPlay) {
            UpdateSerialInputs();
        }
        else {
            UpdateNetInputs();
        }

        nNetMoves++;

        if (!nNetMoves) {
            nNetMoves++;
        }
    }
}

/*
ClearSpaceBar_
    GetLocalInput_
    GetModemInput_
    BackupInput_
    SendInput_
    SendToUnAckd_
    LogoffPlayer_
    UpdateInputs_
    faketimerhandler_
*/

void ClearAllKeys()
{
    KB_ClearKeysDown();
    KB_FlushKeyboardQueue();
}

void WaitNoKey(int nSecs, void (*pFunc) (void))
{
    int nTotalTime = (kTimerTicks * nSecs) + (int)totalclock;

    while (nTotalTime > (int)totalclock)
    {
        HandleAsync();
        if (pFunc) {
            pFunc();
        }
    }
}

int WaitAnyKey(int nSecs)
{
    int nTotalTime = (int)totalclock + (kTimerTicks * nSecs);

    while (1)
    {
        HandleAsync();
        if (nTotalTime <= (int)totalclock || nSecs == -1) {
            return -1;
        }

        int i = 0;

        do
        {
            if (KB_KeyDown[i])
            {
                KB_KeyDown[i] = 0;
                return i;
            }

            i++;

        } while (i < 106);
    }
}

/*
    Name:  _nLocalPlayer
  Name:  _nNetPlayerCount
  Name:  _nModemPlayer
  Name:  _nNetMoves
  Name:  _lLocalButtons
  Name:  _lLocalCodes
  Name:  _nInputStack
  Name:  _bSyncNet
  Name:  _lStartupTime
  Name:  _bStackNode
  Name:  _sSync
  Name:  _nTypeStack
  Name:  _sPlayerInput
  Name:  _pStackPtr
  Name:  _sInputStack

  */


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 __input_h__
#define __input_h__

#include "compat.h"

enum {
    kButtonJump = 0x1,
    kButtonOpen = 0x4,
    kButtonFire = 0x8,
    kButtonCrouch = 0x10,
    kButtonCheatGuns = 0x20,
    kButtonCheatGodMode = 0x40,
    kButtonCheatKeys = 0x80,
    kButtonCheatItems = 0x100,
};

// 32 bytes
struct PlayerInput // TODO consider adjusting this for demo compatibility
{
    int xVel;
    int yVel;
    // short nAngle;
    fix16_t nAngle;
    uint16_t buttons;
    short nTarget;
    // uint8_t horizon;
    fix16_t horizon;
    int8_t nItem;
    int h;
    char i;
    char field_15[11];
};

void InitInput();
void ClearAllKeys();
void WaitNoKey(int nSecs, void (*pFunc) (void));
int WaitAnyKey(int nSecs);

void UpdateInputs();

void ClearSpaceBar(short nPlayer);

void GetLocalInput();

extern PlayerInput sPlayerInput[];
extern int nNetMoves;

#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 "items.h"
#include "anims.h"
#include "player.h"
#include "exhumed.h"
#include "light.h"
#include "lighting.h"
#include "sound.h"
#include "status.h"
#include "engine.h"
#include "random.h"
#include "init.h"
#include "input.h"
#include "object.h"
#include "save.h"

struct AnimInfo
{
    short a;
    short repeat;
};

AnimInfo nItemAnimInfo[] = {
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { 6, 64 },
    { -1, 48 },
    { 0, 64 },
    { 1, 64 },
    { -1, 32 },
    { 4, 64 },
    { 5, 64 },
    { 16, 64 },
    { 10, 64 },
    { -1, 32 },
    { 8, 64 },
    { 9, 64 },
    { -1, 40 },
    { -1, 32 },
    { 7, 64 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { 14, 64 },
    { 15, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { 17, 48 },
    { 18, 48 },
    { 19, 48 },
    { 20, 48 },
    { 24, 64 },
    { 21, 64 },
    { 23, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { 11, 30 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 },
    { -1, 32 }
};

short nItemMagic[] = { 500, 1000, 100, 500, 400, 200, 700, 0 };

/*

short something
short x/y repeat

*/

short nRegenerates;
short nFirstRegenerate;
short nMagicCount;

void BuildItemAnim(short nSprite)
{
    int nItem = sprite[nSprite].statnum - 906;

    if (nItemAnimInfo[nItem].a >= 0)
    {
        int nAnim = BuildAnim(nSprite, 41, nItemAnimInfo[nItem].a, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum, nItemAnimInfo[nItem].repeat, 20);
        int nAnimSprite = GetAnimSprite(nAnim);

        if (nItem == 44) {
            sprite[nAnimSprite].cstat |= 2;
        }

        changespritestat(nAnimSprite, sprite[nSprite].statnum);

        sprite[nAnimSprite].owner = nAnim;
        sprite[nAnimSprite].hitag = sprite[nSprite].hitag;
    }
    else
    {
        sprite[nSprite].owner = -1;
        sprite[nSprite].yrepeat = nItemAnimInfo[nItem].repeat;
        sprite[nSprite].xrepeat = nItemAnimInfo[nItem].repeat;
    }
}

void DestroyItemAnim(short nSprite)
{
    int16_t nAnim = sprite[nSprite].owner;

    if (nAnim >= 0) {
        DestroyAnim(nAnim);
    }
}

void ItemFlash()
{
    TintPalette(16, 16, 16);
}

void FillItems(short nPlayer)
{
    for (int i = 0; i < 6; i++)
    {
        PlayerList[nPlayer].items[i] = 5;
    }

    PlayerList[nPlayer].nMagic = kMaxMagic;

    if (nPlayer == nLocalPlayer)
    {
        ItemFlash();
        SetMagicFrame();
    }

    if (nPlayerItem[nPlayer] == -1) {
        SetPlayerItem(nPlayer, 0);
    }
}

void UseEye(short nPlayer)
{
    if (nPlayerInvisible[nPlayer] >= 0) {
        nPlayerInvisible[nPlayer] = 900;
    }

    int nSprite = PlayerList[nPlayer].nSprite;

    sprite[nSprite].cstat |= 0x8000;

    if (nPlayerFloorSprite[nPlayer] >= 0) {
        sprite[nSprite].cstat |= 0x8000;
    }

    if (nPlayer == nLocalPlayer)
    {
        ItemFlash();
        D3PlayFX(StaticSound[kSoundItemUse], nSprite);
    }
}

void UseMask(short nPlayer)
{
    PlayerList[nPlayer].nMaskAmount = 1350;
    PlayerList[nPlayer].nAir = 100;

    if (nPlayer == nLocalPlayer)
    {
        SetAirFrame();
        D3PlayFX(StaticSound[kSoundItemUse], PlayerList[nPlayer].nSprite);
    }
}

void UseTorch(short nPlayer)
{
    if (!nPlayerTorch[nPlayer]) {
        SetTorch(nPlayer, 1);
    }

    nPlayerTorch[nPlayer] = 900;
}

void UseHeart(short nPlayer)
{
    if (PlayerList[nPlayer].nHealth < kMaxHealth) {
        PlayerList[nPlayer].nHealth = kMaxHealth;
    }

    if (nPlayer == nLocalPlayer)
    {
        ItemFlash();
        SetHealthFrame(1);
        D3PlayFX(StaticSound[kSoundItemUse], PlayerList[nPlayer].nSprite);
    }
}

// invincibility
void UseScarab(short nPlayer)
{
    if (PlayerList[nPlayer].invincibility < 900) {
        PlayerList[nPlayer].invincibility = 900;
    }

    if (nPlayer == nLocalPlayer)
    {
        ItemFlash();
        D3PlayFX(StaticSound[kSoundItemUse], PlayerList[nPlayer].nSprite);
    }
}

// faster firing
void UseHand(short nPlayer)
{
    nPlayerDouble[nPlayer] = 1350;

    if (nPlayer == nLocalPlayer)
    {
        ItemFlash();
        D3PlayFX(StaticSound[kSoundItemUse], PlayerList[nPlayer].nSprite);
    }
}

void UseItem(short nPlayer, short nItem)
{
    switch (nItem)
    {
        case 0:
            UseHeart(nPlayer);
            break;
        case 1:
            UseScarab(nPlayer);
            break;
        case 2:
            UseTorch(nPlayer);
            break;
        case 3:
            UseHand(nPlayer);
            break;
        case 4:
            UseEye(nPlayer);
            break;
        case 5:
            UseMask(nPlayer);
            break;
        default:
            break;
    }

    PlayerList[nPlayer].items[nItem]--;
    int nItemCount = PlayerList[nPlayer].items[nItem];

    int nMagic = nItemMagic[nItem];

    if (nPlayer == nLocalPlayer)
    {
        BuildStatusAnim(156 + (nItemCount * 2), 0);
    }

    if (!nItemCount)
    {
        for (nItem = 0; nItem < 6; nItem++)
        {
            if (PlayerList[nPlayer].items[nItem] > 0) {
                break;
            }
        }

        if (nItem == 6) {
            nItem = -1;
        }
    }

    PlayerList[nPlayer].nMagic -= nMagic;
    SetPlayerItem(nPlayer, nItem);

    if (nPlayer == nLocalPlayer) {
        SetMagicFrame();
    }
}

void UseCurItem(short nPlayer)
{
    int nItem = nPlayerItem[nPlayer];

    if (nItem >= 0)
    {
        if (PlayerList[nPlayer].items[nItem] > 0)
        {
            if (nItemMagic[nItem] <= PlayerList[nPlayer].nMagic)
            {
                sPlayerInput[nPlayer].nItem = nItem;
            }
        }
    }
}

// TODO - bool return type?
int GrabItem(short nPlayer, short nItem)
{
    if (PlayerList[nPlayer].items[nItem] >= 5) {
        return 0;
    }

    PlayerList[nPlayer].items[nItem]++;

    if (nPlayerItem[nPlayer] < 0 || nItem == nPlayerItem[nPlayer]) {
        SetPlayerItem(nPlayer, nItem);
    }

    return 1;
}

void DropMagic(short nSprite)
{
    if (lFinaleStart) {
        return;
    }

    nMagicCount--;

    if (nMagicCount <= 0)
    {
        int nAnim = BuildAnim(
            -1,
            64,
            0,
            sprite[nSprite].x,
            sprite[nSprite].y,
            sprite[nSprite].z,
            sprite[nSprite].sectnum,
            48,
            4);

        int nAnimSprite = GetAnimSprite(nAnim);

        sprite[nAnimSprite].owner = nAnim;

        AddFlash(sprite[nAnimSprite].sectnum, sprite[nAnimSprite].x, sprite[nAnimSprite].y, sprite[nAnimSprite].z, 128);
        changespritestat(nAnimSprite, 950);

        nMagicCount = RandomSize(2);
    }
}

void InitItems()
{
    nRegenerates = 0;
    nFirstRegenerate = -1;
    nMagicCount = 0;
}

void StartRegenerate(short nSprite)
{
    spritetype *pSprite = &sprite[nSprite];

    int edi = -1;

    int nReg = nFirstRegenerate;

    int i = 0;

//	for (int i = 0; i < nRegenerates; i++)
    while (1)
    {
        if (i >= nRegenerates)
        {
            // ?? CHECKME
            pSprite->xvel = pSprite->xrepeat;
            pSprite->zvel = pSprite->shade;
            pSprite->yvel = pSprite->pal;
            break;
        }
        else
        {
            if (nReg != nSprite)
            {
                edi = nReg;
                nReg = sprite[nReg].ang;
                i++;
                continue;
            }
            else
            {
                if (edi == -1)
                {
                    nFirstRegenerate = pSprite->ang;
                }
                else
                {
                    sprite[edi].ang = sprite[nSprite].ang;
                }

                nRegenerates--;
            }
        }
    }

    pSprite->extra = 1350;
    pSprite->ang = nFirstRegenerate;

    if (levelnum <= kMap20)
    {
        pSprite->ang /= 5;
    }

    pSprite->cstat = 0x8000;
    pSprite->xrepeat = 1;
    pSprite->yrepeat = 1;
    pSprite->pal = 1;

    nRegenerates++;
    nFirstRegenerate = nSprite;
}

void DoRegenerates()
{
    int nSprite = nFirstRegenerate;

    for (int i = nRegenerates; i > 0; i--, nSprite = sprite[nSprite].ang)
    {
        if (sprite[nSprite].extra > 0)
        {
            sprite[nSprite].extra--;

            if (sprite[nSprite].extra <= 0)
            {
                BuildAnim(-1, 38, 0, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum, 64, 4);
                D3PlayFX(StaticSound[kSoundTorchOn], i);
            }
            else {
                continue;
            }
        }
        else
        {
            if (sprite[nSprite].xrepeat < sprite[nSprite].xvel)
            {
                sprite[nSprite].xrepeat += 2;
                sprite[nSprite].yrepeat += 2;
                continue;
            }
        }

        sprite[nSprite].zvel = 0;
        sprite[nSprite].yrepeat = sprite[nSprite].xvel;
        sprite[nSprite].xrepeat = sprite[nSprite].xvel;
        sprite[nSprite].pal  = sprite[nSprite].yvel;
        sprite[nSprite].yvel = sprite[nSprite].zvel; // setting to 0
        sprite[nSprite].xvel = sprite[nSprite].zvel; // setting to 0
        nRegenerates--;

        if (sprite[nSprite].statnum == kStatExplodeTrigger) {
            sprite[nSprite].cstat = 0x101;
        }
        else {
            sprite[nSprite].cstat = 0;
        }

        if (nRegenerates == 0) {
            nFirstRegenerate = -1;
        }
    }
}

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

void ItemsLoadSave::Load()
{
    Read(&nRegenerates, sizeof(nRegenerates));
    Read(&nFirstRegenerate, sizeof(nFirstRegenerate));
    Read(&nMagicCount, sizeof(nMagicCount));
}

void ItemsLoadSave::Save()
{
    Write(&nRegenerates, sizeof(nRegenerates));
    Write(&nFirstRegenerate, sizeof(nFirstRegenerate));
    Write(&nMagicCount, sizeof(nMagicCount));
}

static ItemsLoadSave* myLoadSave;

void ItemsLoadSaveConstruct()
{
    myLoadSave = new ItemsLoadSave();
}


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 __items_h__
#define __items_h__

enum
{
    kItemHeart = 0,
    kItemInvincibility,
    kItemTorch,
    kItemDoubleDamage,
    kItemInvisibility,
    kItemMask,
};

extern short nItemMagic[];

void BuildItemAnim(short nSprite);
void DestroyItemAnim(short nSprite);
void ItemFlash();
void FillItems(short nPlayer);
void UseEye(short nPlayer);
void UseMask(short nPlayer);
void UseTorch(short nPlayer);
void UseHeart(short nPlayer);
void UseScarab(short nPlayer);
void UseHand(short nPlayer);
void UseItem(short nPlayer, short nItem);
void UseCurItem(short nPlayer);
int GrabItem(short nPlayer, short nItem);
void DropMagic(short nSprite);
void InitItems();
void StartRegenerate(short nSprite);
void DoRegenerates();

#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 "engine.h"
#include "lavadude.h"
#include "random.h"
#include "runlist.h"
#include "sequence.h"
#include "exhumed.h"
#include "move.h"
#include "trigdat.h"
#include "move.h"
#include "bullet.h"
#include "sound.h"
#include "save.h"
#include <assert.h>

#define kMaxLavas   20

struct Lava
{
    short nSprite;
    short nRun;
    short nAction;
    short nTarget;
    short nHealth;
    short nFrame;
    short nChannel;
};

static actionSeq ActionSeq[] = {
    {0,  1},
    {0,  1},
    {1,  0},
    {10, 0},
    {19, 0},
    {28, 1},
    {29, 1},
    {33, 0},
    {42, 1}
};

short LavaCount = 0;

Lava LavaList[kMaxLavas];

void InitLava()
{
    LavaCount = 0;
}

int BuildLavaLimb(int nSprite, int edx, int ebx)
{
    short nSector = sprite[nSprite].sectnum;

    int nLimbSprite = insertsprite(nSector, 118);
    assert(nLimbSprite >= 0 && nLimbSprite < kMaxSprites);

    sprite[nLimbSprite].x = sprite[nSprite].x;
    sprite[nLimbSprite].y = sprite[nSprite].y;
    sprite[nLimbSprite].z = sprite[nSprite].z - RandomLong() % ebx;
    sprite[nLimbSprite].cstat = 0;
    sprite[nLimbSprite].shade = -127;
    sprite[nLimbSprite].pal = 1;
    sprite[nLimbSprite].xvel = (RandomSize(5) - 16) << 8;
    sprite[nLimbSprite].yvel = (RandomSize(5) - 16) << 8;
    sprite[nLimbSprite].zvel = 2560 - (RandomSize(5) << 8);
    sprite[nLimbSprite].xoffset = 0;
    sprite[nLimbSprite].yoffset = 0;
    sprite[nLimbSprite].xrepeat = 90;
    sprite[nLimbSprite].yrepeat = 90;
    sprite[nLimbSprite].picnum = (edx & 3) % 3;
    sprite[nLimbSprite].hitag = 0;
    sprite[nLimbSprite].lotag = runlist_HeadRun() + 1;
    sprite[nLimbSprite].clipdist = 0;

//	GrabTimeSlot(3);

    sprite[nLimbSprite].extra = -1;
    sprite[nLimbSprite].owner = runlist_AddRunRec(sprite[nLimbSprite].lotag - 1, nLimbSprite, kRunLavaLimb);
    sprite[nLimbSprite].hitag = runlist_AddRunRec(NewRun, nLimbSprite, kRunLavaLimb);

    return nLimbSprite;
}

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

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        case 0x20000:
        {
            sprite[nSprite].shade += 3;

            int nRet = movesprite(nSprite, sprite[nSprite].xvel << 12, sprite[nSprite].yvel << 12, sprite[nSprite].zvel, 2560, -2560, CLIPMASK1);

            if (nRet || sprite[nSprite].shade > 100)
            {
                sprite[nSprite].xvel = 0;
                sprite[nSprite].yvel = 0;
                sprite[nSprite].zvel = 0;

                runlist_DoSubRunRec(sprite[nSprite].owner);
                runlist_FreeRun(sprite[nSprite].lotag - 1);
                runlist_SubRunRec(sprite[nSprite].hitag);

                mydeletesprite(nSprite);
            }
            break;
        }

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, (SeqOffsets[kSeqLavag] + 30) + sprite[nSprite].picnum, 0, 1);
            break;
        }

        default:
            return;
    }
}

int BuildLava(short nSprite, int x, int y, int UNUSED(z), short nSector, short nAngle, int nChannel)
{
    short nLava = LavaCount;
    LavaCount++;

    if (nLava >= kMaxLavas) {
        return -1;
    }

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

        changespritestat(nSprite, 118);
    }

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

    sprite[nSprite].x = x;
    sprite[nSprite].y = y;
    sprite[nSprite].z = sector[nSector].floorz;
    sprite[nSprite].cstat = 0x8000;
    sprite[nSprite].xrepeat = 200;
    sprite[nSprite].yrepeat = 200;
    sprite[nSprite].shade = -12;
    sprite[nSprite].pal = 0;
    sprite[nSprite].clipdist = 127;
    sprite[nSprite].xoffset = 0;
    sprite[nSprite].yoffset = 0;
    sprite[nSprite].picnum = seq_GetSeqPicnum(kSeqLavag, ActionSeq[3].a, 0);
    sprite[nSprite].xvel = 0;
    sprite[nSprite].yvel = 0;
    sprite[nSprite].zvel = 0;
    sprite[nSprite].ang = nAngle;
    sprite[nSprite].hitag = 0;
    sprite[nSprite].lotag = runlist_HeadRun() + 1;

//	GrabTimeSlot(3);

    sprite[nSprite].extra = -1;

    LavaList[nLava].nAction = 0;
    LavaList[nLava].nHealth = 4000;
    LavaList[nLava].nSprite = nSprite;
    LavaList[nLava].nTarget = -1;
    LavaList[nLava].nChannel = nChannel;
    LavaList[nLava].nFrame = 0;

    sprite[nSprite].owner = runlist_AddRunRec(sprite[nSprite].lotag - 1, nLava, kRunLavaDude);
    LavaList[nLava].nRun = runlist_AddRunRec(NewRun, nLava, kRunLavaDude);

    nCreaturesLeft++;

    return nLava;
}

void FuncLava(int a, int nDamage, int nRun)
{
    short nLava = RunData[nRun].nVal;
    assert(nLava >= 0 && nLava < kMaxLavas);

    short nAction = LavaList[nLava].nAction;
    short nSeq = ActionSeq[nAction].a + SeqOffsets[kSeqLavag];
    short nSprite = LavaList[nLava].nSprite;

    int nMessage = a & kMessageMask;

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

        case 0x90000:
        {
            seq_PlotSequence(a & 0xFFFF, nSeq, LavaList[nLava].nFrame, ActionSeq[nAction].b);
            tsprite[a & 0xFFFF].owner = -1;
            return;
        }

        case 0xA0000:
        {
            return;
        }

        case 0x80000:
        {
            if (!nDamage) {
                return;
            }

            LavaList[nLava].nHealth -= nDamage;

            if (LavaList[nLava].nHealth <= 0)
            {
                LavaList[nLava].nHealth = 0;
                LavaList[nLava].nAction = 5;
                LavaList[nLava].nFrame  = 0;

                nCreaturesLeft--;

                sprite[nSprite].cstat &= 0xFEFE;
            }
            else
            {
                short nTarget = a & 0xFFFF;

                if (nTarget >= 0)
                {
                    if (sprite[nTarget].statnum < 199)
                    {
                        LavaList[nLava].nTarget = nTarget;
                    }
                }

                if (nAction == 3)
                {
                    if (!RandomSize(2))
                    {
                        LavaList[nLava].nAction = 4;
                        LavaList[nLava].nFrame  = 0;
                        sprite[nSprite].cstat = 0;
                    }
                }

                BuildLavaLimb(nSprite, totalmoves, 64000);
            }

            return;
        }

        case 0x20000:
        {
            sprite[nSprite].picnum = seq_GetSeqPicnum2(nSeq, LavaList[nLava].nFrame);
            int var_38 = LavaList[nLava].nFrame;

            short nFlag = FrameFlag[SeqBase[nSeq] + var_38];

            int var_1C;

            if (nAction)
            {
                seq_MoveSequence(nSprite, nSeq, var_38);

                LavaList[nLava].nFrame++;
                if (LavaList[nLava].nFrame >= SeqSize[nSeq])
                {
                    var_1C = 1;
                    LavaList[nLava].nFrame = 0;
                }
                else
                {
                    var_1C = 0;
                }
            }

            short nTarget = LavaList[nLava].nTarget;

            if (nTarget >= 0 && nAction < 4)
            {
                if (!(sprite[nTarget].cstat & 0x101) || sprite[nTarget].sectnum >= 1024)
                {
                    nTarget = -1;
                    LavaList[nLava].nTarget = -1;
                }
            }

            switch (nAction)
            {
                case 0:
                {
                    if ((nLava & 0x1F) == (totalmoves & 0x1F))
                    {
                        if (nTarget < 0)
                        {
                            nTarget = FindPlayer(nSprite, 76800);
                        }

                        PlotCourseToSprite(nSprite, nTarget);

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

                        if (nTarget >= 0 && !RandomSize(1))
                        {
                            LavaList[nLava].nTarget = nTarget;
                            LavaList[nLava].nAction = 2;
                            sprite[nSprite].cstat = 0x101;
                            LavaList[nLava].nFrame = 0;
                            break;
                        }
                    }

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

                    int nVal = movesprite(nSprite, sprite[nSprite].xvel << 8, sprite[nSprite].yvel << 8, 0, 0, 0, CLIPMASK0);

                    if (nSector != sprite[nSprite].sectnum)
                    {
                        changespritesect(nSprite, nSector);
                        sprite[nSprite].x = x;
                        sprite[nSprite].y = y;
                        sprite[nSprite].z = z;

                        sprite[nSprite].ang = (sprite[nSprite].ang + ((RandomWord() & 0x3FF) + 1024)) & kAngleMask;
                        sprite[nSprite].xvel = Cos(sprite[nSprite].ang);
                        sprite[nSprite].yvel = Sin(sprite[nSprite].ang);
                        break;
                    }

                    if (!nVal) {
                        break;
                    }

                    if ((nVal & 0xC000) == 0x8000)
                    {
                        sprite[nSprite].ang = (sprite[nSprite].ang + ((RandomWord() & 0x3FF) + 1024)) & kAngleMask;
                        sprite[nSprite].xvel = Cos(sprite[nSprite].ang);
                        sprite[nSprite].yvel = Sin(sprite[nSprite].ang);
                        break;
                    }
                    else if ((nVal & 0xC000) == 0xC000)
                    {
                        if ((nVal & 0x3FFF) == nTarget)
                        {
                            int nAng = getangle(sprite[nTarget].x - sprite[nSprite].x, sprite[nTarget].y - sprite[nSprite].y);
                            if (AngleDiff(sprite[nSprite].ang, nAng) < 64)
                            {
                                LavaList[nLava].nAction = 2;
                                LavaList[nLava].nFrame = 0;
                                sprite[nSprite].cstat = 0x101;
                                break;
                            }
                        }
                    }

                    break;
                }

                case 1:
                case 6:
                {
                    break;
                }

                case 2:
                {
                    if (var_1C)
                    {
                        LavaList[nLava].nAction = 3;
                        LavaList[nLava].nFrame = 0;

                        PlotCourseToSprite(nSprite, nTarget);

                        sprite[nSprite].cstat |= 0x101;
                    }

                    break;
                }

                case 3:
                {
                    if ((nFlag & 0x80) && nTarget > -1)
                    {
                        int nHeight = GetSpriteHeight(nSprite);
                        GetUpAngle(nSprite, -64000, nTarget, (-(nHeight >> 1)));

                        BuildBullet(nSprite, 10, Cos(sprite[nSprite].ang) << 8, Sin(sprite[nSprite].ang) << 8, -1, sprite[nSprite].ang, nTarget + 10000, 1);
                    }
                    else if (var_1C)
                    {
                        PlotCourseToSprite(nSprite, nTarget);
                        LavaList[nLava].nAction = 7;
                        LavaList[nLava].nFrame = 0;
                    }

                    break;
                }

                case 4:
                {
                    if (var_1C)
                    {
                        LavaList[nLava].nAction = 7;
                        sprite[nSprite].cstat &= 0xFEFE;
                    }

                    break;
                }

                case 5:
                {
                    if (nFlag & 0x40)
                    {
                       int nLimbSprite = BuildLavaLimb(nSprite, LavaList[nLava].nFrame, 64000);
                       D3PlayFX(StaticSound[kSoundSetLand], nLimbSprite);
                    }

                    if (LavaList[nLava].nFrame)
                    {
                        if (nFlag & 0x80)
                        {
                            for (int i = 0; i < 20; i++) {
                                BuildLavaLimb(nSprite, i, 64000);
                            }

                            runlist_ChangeChannel(LavaList[nLava].nChannel, 1);
                        }
                    }
                    else
                    {
                        for (int i = 0; i < 30; i++) {
                            BuildLavaLimb(nSprite, i, 256);
                        }

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

                    break;
                }

                case 7:
                {
                    if (var_1C)
                    {
                        LavaList[nLava].nAction = 8;
                        LavaList[nLava].nFrame = 0;
                    }
                    break;
                }

                case 8:
                {
                    if (var_1C)
                    {
                        LavaList[nLava].nAction = 0;
                        LavaList[nLava].nFrame = 0;
                        sprite[nSprite].cstat = 0x8000;
                    }
                    break;
                }
            }

            // loc_31521:
            sprite[nSprite].pal = 1;
        }
    }
}

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

void LavaDudeLoadSave::Load()
{
    Read(&LavaCount, sizeof(LavaCount));
    Read(&LavaList, sizeof(LavaList[0]) * LavaCount);
}

void LavaDudeLoadSave::Save()
{
    Write(&LavaCount, sizeof(LavaCount));
    Write(&LavaList, sizeof(LavaList[0]) * LavaCount);
}

static LavaDudeLoadSave* myLoadSave;

void LavaDudeLoadSaveConstruct()
{
    myLoadSave = new LavaDudeLoadSave();
}


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 __lavadude_h__
#define __lavadude_h__

#include "aistuff.h"

void InitLava();
int BuildLava(short nSprite, int x, int y, int z, short nSector, short nAngle, int nChannel);
int BuildLavaLimb(int nSprite, int edx, int ebx);
void FuncLavaLimb(int, int, int);
void FuncLava(int, int, int);

#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 "light.h"
#include "engine.h"
#include "exhumed.h"
#include "view.h"
#include "cd.h"
#include "lighting.h"
#include "baselayer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define kMaxGrads	12

const char *GradList[kMaxGrads] = {
    "normal.rmp",
    "nodim.rmp",
    "torch.rmp",
    "notorch.rmp",
    "brite.rmp",
    "redbrite.rmp",
    "grnbrite.rmp",
    "normal.rmp",
    "nodim.rmp",
    "torch.rmp",
    "notorch.rmp",
    "brite.rmp"
};

int rtint = 0;
int gtint = 0;
int btint = 0;
//char *origpalookup[kMaxPalookups];
//unsigned char curpal[768];
//unsigned char kenpal[768];
palette_t *fadedestpal;
palette_t *fadecurpal;
short nPalDelay;
short nPalDiff;
short overscanindex;
int bGreenPal = 0;

palette_t fadeintopal[256];

// keep a local copy of the palette that would have been sent to the VGA display adapter
//uint8_t vgaPalette[768];

void MyLoadPalette()
{
    //int hFile = kopen4load("PALETTE.DAT", 1);
    //if (hFile == -1)
    //{
    //    initprintf("Error reading palette 'PALETTE.DAT'\n");
    //    return;
    //}
    //
    //kread(hFile, kenpal, sizeof(kenpal));
    //kclose(hFile);
    videoSetPalette(0, BASEPAL, 0);
    SetOverscan(BASEPAL);
}

int LoadPaletteLookups()
{
    uint8_t buffer[256*64];
    numshades = 64;

    for (int i = 0; i < kMaxGrads; i++)
    {
        int hFile = kopen4loadfrommod(GradList[i], 0);
        if (hFile == -1)
        {
            initprintf("Error reading palette lookup '%s'\n", GradList[i]);
            return 0;
        }

        kread(hFile, buffer, 256*64);
        // TODO: dumb hack
        if (palookup[i])
            ALIGNED_FREE_AND_NULL(palookup[i]);
        paletteSetLookupTable(i, buffer);
        kclose(hFile);

        // origpalookup[i] = palookup[i];
        bGreenPal = 0;

#ifdef USE_OPENGL
        // Very rough approximation...
        palookupfogfactor[kPalNormal] = 1.f;
        palookupfogfactor[kPalNoDim] = 0.f;
        palookupfogfactor[kPalTorch] = 0.36f;
        palookupfogfactor[kPalNoTorch] = 0.15f;
        palookupfogfactor[kPalBrite] = 2.f;
        palookupfogfactor[kPalRedBrite] = 0.36f;
        palookupfog[kPalRedBrite] = { 248, 32, 0, 0 };
        hicsetpalettetint(kPalRedBrite, 255, 255, 255, 248, 32, 0, 0);
        palookupfog[kPalGreenBrite] = { 248, 32, 0, 0 };
        palookupfogfactor[kPalGreenBrite] = 0.f;
        hicsetpalettetint(kPalGreenBrite, 100, 200, 100, 0, 0, 0, 0);
        palookupfogfactor[kPalNormal2] = 1.f;
        palookupfogfactor[kPalNoDim2] = 0.f;
        palookupfogfactor[kPalTorch2] = 0.36f;
        palookupfogfactor[kPalNoTorch2] = 0.15f;
        palookupfogfactor[kPalBrite2] = 2.f;
#endif

    }

    return 1;
}

void SetGreenPal()
{
    bGreenPal = 1;
    // for (int i = 0; i < kMaxGrads; i++)
    // {
    //     palookup[i] = palookup[6];
    // }
    //
    // palookup[5] = origpalookup[5];
}

void RestoreGreenPal()
{
    bGreenPal = 0;
    // for (int i = 0; i < kMaxGrads; i++)
    // {
    //     palookup[i] = origpalookup[i];
    // }
}

int HavePLURemap()
{
    return bGreenPal || bTorch;
}

uint8_t RemapPLU(uint8_t pal)
{
    if (bGreenPal)
    {
        if (pal != kPalRedBrite)
            pal = kPalGreenBrite;
    }
    else if (bTorch)
    {
        switch (pal)
        {
        case kPalTorch:
            pal = kPalNoTorch;
            break;
        case kPalNoTorch:
            pal = kPalTorch;
            break;
        case kPalTorch2:
            pal = kPalNoTorch2;
            break;
        case kPalNoTorch2:
            pal = kPalTorch2;
            break;
        }
    }
    return pal;
}

void WaitVBL()
{
#ifdef __WATCOMC__
    while (!(inp(0x3da) & 8));
#endif
}

//void MySetPalette(unsigned char *palette)
//{
//    WaitVBL();
//
//    // TODO
//    kensetpalette(palette);
//
//    memcpy(vgaPalette, palette, sizeof(vgaPalette));
//}

//void GetCurPal(unsigned char *palette)
//{
//    if (!palette) {
//        memcpy(curpal, vgaPalette, sizeof(curpal));
//    }
//    else {
//        memcpy(palette, vgaPalette, sizeof(curpal));
//    }
//}

void GrabPalette()
{
    SetOverscan(BASEPAL);

    videoSetPalette(0, BASEPAL, 2+8);

    nPalDiff  = 0;
    nPalDelay = 0;

    rtint = 0;
    gtint = 0;
    btint = 0;
#ifdef USE_OPENGL
    videoTintBlood(0, 0, 0);
#endif
}

void BlackOut()
{
    for (int i = 0; i < 256; i++)
    {
        curpalettefaded[i].r = 0;
        curpalettefaded[i].g = 0;
        curpalettefaded[i].b = 0;
    }
    videoUpdatePalette(0, 256);
    g_lastpalettesum = -1;
#ifdef USE_OPENGL
    videoTintBlood(0, 0, 0);
#endif
}

void RestorePalette()
{
    videoSetPalette(0, BASEPAL, 2+8);
#ifdef USE_OPENGL
    videoTintBlood(0, 0, 0);
#endif
}

void WaitTicks(int nTicks)
{
    if (htimer)
    {
        nTicks += (int)totalclock;
        while (nTicks > (int)totalclock) { HandleAsync(); }
    }
    else
    {
        while (nTicks > 0) {
            nTicks--;
            WaitVBL();
        }
    }
}

// unused
void DoFadeToRed()
{
#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST)
    {
        videoTintBlood(-255, -255, -255);
        videoNextPage();
        return;
    }
#endif
    for (int i = 0; i < 256; i++)
    {
        if (curpalettefaded[i].g > 0)
        {
            curpalettefaded[i].g -= 4;
            if (curpalettefaded[i].g < 0)
                curpalettefaded[i].g = 0;
        }

        if (curpalettefaded[i].b > 0)
        {
            curpalettefaded[i].b -= 4;
            if (curpalettefaded[i].b < 0)
                curpalettefaded[i].b = 0;
        }
    }

    videoUpdatePalette(0, 256);
    g_lastpalettesum = -1;
}

void FadeToWhite()
{
    int ebx = 0;

#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST)
    {
        videoTintBlood(255, 255, 255);
        videoNextPage();
        return;
    }
#endif

    for (int i = 0; i < 64; i++)
    {
        palette_t *pPal = curpalettefaded;

        for (int j = 0; j < 256; j++)
        {
            if (pPal->r < 255)
            {
                pPal->r += 4;
                if (pPal->r > 255)
                    pPal->r = 255;
                ebx++;
            }
            if (pPal->g < 255)
            {
                pPal->g += 4;
                if (pPal->g > 255)
                    pPal->g = 255;
                ebx++;
            }
            if (pPal->b < 255)
            {
                pPal->b += 4;
                if (pPal->b > 255)
                    pPal->b = 255;
                ebx++;
            }
            pPal++;
        }

        videoUpdatePalette(0, 256);
        g_lastpalettesum = -1;
        WaitTicks(2);

        // need to page flip in each iteration of the loop for non DOS version
        videoNextPage();

        if (!ebx) {
            return;
        }
    }
}

void FadeOut(int bFadeMusic)
{
    if (bFadeMusic) {
        StartfadeCDaudio();
    }

#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST)
    {
        videoTintBlood(-255, -255, -255);
        videoNextPage();
    }
    else
#endif
    for (int i = 64; i > 0; i--)
    {
        int v4 = 0;
        palette_t *pPal = curpalettefaded;

        for (int j = 0; j < 256; j++)
        {
            if (pPal->r > 0)
            {
                pPal->r -= 4;
                if (pPal->r < 0)
                    pPal->r = 0;
                v4++;
            }
            if (pPal->g > 0)
            {
                pPal->g -= 4;
                if (pPal->g < 0)
                    pPal->g = 0;
                v4++;
            }
            if (pPal->b > 0)
            {
                pPal->b -= 4;
                if (pPal->b < 0)
                    pPal->b = 0;
                v4++;
            }
            pPal++;
        }

        videoUpdatePalette(0, 256);
        g_lastpalettesum = -1;
        WaitTicks(2);

        // need to page flip in each iteration of the loop for non DOS version
        videoNextPage();

        if (v4 == 0) {
            break;
        }

        if (bFadeMusic) {
            StepFadeCDaudio();
        }
    }

    if (bFadeMusic) {
        while (StepFadeCDaudio() != 0) {}
    }

    EraseScreen(overscanindex);
}

void StartFadeIn()
{
    //fadedestpal = curpalette;
    //fadecurpal = curpal;

    /*
    memcpy(fadeintopal, curpalette, sizeof(curpalette));
    memset(curpalettefaded, 0, sizeof(curpalettefaded));

    videoUpdatePalette(0, 256);
    g_lastpalettesum = -1;
    */
}

int DoFadeIn()
{
#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST)
    {
        paletteSetColorTable(curbasepal, basepaltable[BASEPAL]);
        videoSetPalette(0, curbasepal, 2+8);
        videoNextPage();
        return 0;
    }
#endif
    int v2 = 0;

    for (int i = 0; i < 256; i++)
    {
        if (curpalettefaded[i].r != curpalette[i].r)
        {
            v2++;
            int diff = curpalette[i].r - curpalettefaded[i].r;
            if (klabs(diff) < 4)
                curpalettefaded[i].r = curpalette[i].r;
            else
                curpalettefaded[i].r += 4 * ksgn(diff);
        }
        if (curpalettefaded[i].g != curpalette[i].g)
        {
            v2++;
            int diff = curpalette[i].g - curpalettefaded[i].g;
            if (klabs(diff) < 4)
                curpalettefaded[i].g = curpalette[i].g;
            else
                curpalettefaded[i].g += 4 * ksgn(diff);
        }
        if (curpalettefaded[i].b != curpalette[i].b)
        {
            v2++;
            int diff = curpalette[i].b - curpalettefaded[i].b;
            if (klabs(diff) < 4)
                curpalettefaded[i].b = curpalette[i].b;
            else
                curpalettefaded[i].b += 4 * ksgn(diff);
        }
    }

    videoUpdatePalette(0, 256);
    g_lastpalettesum = -1;

    return v2;
}

void FadeIn()
{
#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST)
    {
        videoSetPalette(0, BASEPAL, 2+8);
        videoNextPage();
        return;
    }
#endif

    StartFadeIn();

    int val;

    do
    {
        val = DoFadeIn();
        WaitTicks(2);

        // need to page flip in each iteration of the loop for non DOS version
        videoNextPage();
    } while (val);
}

void FixPalette()
{
    if (!nPalDiff) {
        return;
    }

    if (nPalDelay > 0)
    {
        nPalDelay--;
        return;
    }

    nPalDelay = 5;

#ifdef USE_OPENGL
    if (videoGetRenderMode() == REND_CLASSIC)
#endif
    for (int i = 0; i < 256; i++)
    {
        short nVal;

        nVal = curpalettefaded[i].r - curpalette[i].r;
        if (nVal > 0)
        {
            if (nVal > 20)
            {
                curpalettefaded[i].r -= 20;
            }
            else
            {
                curpalettefaded[i].r = curpalette[i].r;
            }
        }

        nVal = curpalettefaded[i].g - curpalette[i].g;
        if (nVal > 0)
        {
            if (nVal > 20)
            {
                curpalettefaded[i].g -= 20;
            }
            else
            {
                curpalettefaded[i].g = curpalette[i].g;
            }
        }

        nVal = curpalettefaded[i].b - curpalette[i].b;
        if (nVal > 0)
        {
            if (nVal > 20)
            {
                curpalettefaded[i].b -= 20;
            }
            else
            {
                curpalettefaded[i].b = curpalette[i].b;
            }
        }
    }

    nPalDiff -= 20;
    rtint -= 20;
    gtint -= 20;
    btint -= 20;

    if (rtint < 0) {
        rtint = 0;
    }

    if (gtint < 0) {
        gtint = 0;
    }

    if (btint < 0) {
        btint = 0;
    }

    if (nPalDiff < 0) {
        nPalDiff = 0;
    }

#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST) videoTintBlood(rtint, gtint, btint);
    else
#endif
    {
        videoUpdatePalette(0, 256);
        g_lastpalettesum = -1;
    }
}

void TintPalette(int r, int g, int b)
{
    palette_t *pPal = curpalettefaded;

    if (bCamera) {
        return;
    }

    // range limit R between 20 and 255 if positive
    if (r > 255) {
        r = 255;
    }
    else if (r > 0 && r < 20) {
        r = 20;
    }

    // range limit G between 20 and 255 if positive
    if (g > 255) {
        g = 255;
    }
    else if (g > 0 && g < 20) {
        g = 20;
    }

    // range limit B between 20 and 255 if positive
    if (b > 255) {
        b = 255;
    }
    else if (b > 0 && b < 20) {
        b = 20;
    }

    if (g > 0 && gtint > 32) {
        return;
    }

    gtint += g;

    if (r > 0 && rtint > 256) {
        return;
    }

    rtint += r;
    btint += b;

    // do not modify r, g or b variables from this point on
    int nVal;

    if (klabs(r) > klabs(g)) {
        nVal = klabs(r);
    }
    else {
        nVal = klabs(g);
    }

    if (nVal < klabs(b)) {
        nVal = klabs(b);
    }

    nPalDiff += nVal;

#ifdef USE_OPENGL
    if (videoGetRenderMode() >= REND_POLYMOST) videoTintBlood(rtint, gtint, btint);
    else
#endif
    for (int i = 0; i < 256; i++)
    {
        nVal = pPal->r + r;
        if (nVal > 255) {
            nVal = 255;
        }
        pPal->r = nVal;

        nVal = pPal->g + g;
        if (nVal > 255) {
            nVal = 255;
        }
        pPal->g = nVal;

        nVal = pPal->b + b;
        if (nVal > 255) {
            nVal = 255;
        }
        pPal->b = nVal;

        pPal++;
    }

    nPalDelay = 0;
}

void DoOverscanSet(short someval)
{
#ifdef __WATCOMC__
    union REGS regs;

    regs.h.al = 1;
    regs.h.ah = 0x10;
    regs.h.ch = someval;

    int386(0x10, &regs, &regs);

#endif
}

// unused
void SetWhiteOverscan()
{

}

void SetOverscan(int id)
{
    if (basepaltable[id] == NULL)
        return;
    uint8_t *palette = basepaltable[id];
    int edi = 1000;
    overscanindex = 0;

    for (int i = 0; i < 256; i++)
    {
        int ebx = 0;

        for (int j = 0; j < 3; j++)
        {
            uint8_t cl = *palette;
            palette++;
            ebx += cl;
        }

        if (ebx < edi)
        {
            edi = ebx;
            overscanindex = i;
        }
    }

    DoOverscanSet(overscanindex);
}


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 __light_h__
#define __light_h__

#include "compat.h"

enum
{
    kPalNormal = 0,
    kPalNoDim,
    kPalTorch,
    kPalNoTorch,
    kPalBrite,
    kPalRedBrite,
    kPalGreenBrite,
    kPalNormal2,
    kPalNoDim2,
    kPalTorch2,
    kPalNoTorch2,
    kPalBrite2
};

void MyLoadPalette();
int LoadPaletteLookups();
void WaitVBL();
int DoFadeIn();
void StartFadeIn();
void FadeIn();
void FadeOut(int bFadeMusic);
void RestorePalette();
void TintPalette(int r, int g, int b);
void GrabPalette();
void BlackOut();
void SetGreenPal();
void RestoreGreenPal();
void FixPalette();
void FadeToWhite();
int HavePLURemap();
uint8_t RemapPLU(uint8_t pal);

extern void DoOverscanSet(short someval);
void SetOverscan(int id);

//extern unsigned char kenpal[];
extern short overscanindex;

extern char *origpalookup[];

extern short nPalDiff;

#endif