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 "bullet.h"
#include "runlist.h"
#include "anims.h"
#include "sequence.h"
#include "exhumed.h"
#include "sound.h"
#include "init.h"
#include "move.h"
#include "player.h"
#include "trigdat.h"
#include "random.h"
#include "gun.h"
#include "names.h"
#include "lighting.h"
#include "object.h"
#include "save.h"
#include <string.h>
#include <assert.h>
#ifndef __WATCOMC__
//#include <cmath>
#else
//#include <math.h>
#include <stdlib.h>
#endif

#define kMaxBullets		500

short BulletFree[kMaxBullets];

// 32 bytes
struct Bullet
{
    short nSeq;
    short nFrame;
    short nSprite;
    short field_6;
    short field_8;
    short nType;
    short field_C;
    short field_E;
    uint16_t field_10;
    uint8_t field_12;
    uint8_t field_13;
    int x;
    int y;
    int z;
};

Bullet BulletList[kMaxBullets];
short nBulletEnemy[kMaxBullets];
int nBulletsFree;
int lasthitz, lasthitx, lasthity;
short lasthitsect, lasthitsprite, lasthitwall;

int nBulletCount = 0;
short nRadialBullet = 0;

bulletInfo BulletInfo[] = {
    { 25,   1,    20, -1, -1, 13, 0,  0, -1 },
    { 25,  -1, 65000, -1, 31, 73, 0,  0, -1 },
    { 15,  -1, 60000, -1, 31, 73, 0,  0, -1 },
    { 5,   15,  2000, -1, 14, 38, 4,  5,  3 },
    { 250, 100, 2000, -1, 33, 34, 4, 20, -1 },
    { 200, -1,  2000, -1, 20, 23, 4, 10, -1 },
    { 200, -1, 60000, 68, 68, -1, -1, 0, -1 },
    { 300,  1,     0, -1, -1, -1, 0, 50, -1 },
    { 18,  -1,  2000, -1, 18, 29, 4,  0, -1 },
    { 20,  -1,  2000, 37, 11, 30, 4,  0, -1 },
    { 25,  -1,  3000, -1, 44, 36, 4, 15, 90 },
    { 30,  -1,  1000, -1, 52, 53, 4, 20, 48 },
    { 20,  -1,  3500, -1, 54, 55, 4, 30, -1 },
    { 10,  -1,  5000, -1, 57, 76, 4,  0, -1 },
    { 40,  -1,  1500, -1, 63, 38, 4, 10, 40 },
    { 20,  -1,  2000, -1, 60, 12, 0,  0, -1 },
    { 5,   -1, 60000, -1, 31, 76, 0,  0, -1 }
};

void InitBullets()
{
    nBulletCount = 0;

    for (int i = 0; i < kMaxBullets; i++) {
        BulletFree[i] = i;
    }

    nBulletsFree = kMaxBullets;

    memset(nBulletEnemy, -1, sizeof(nBulletEnemy));
}

short GrabBullet()
{
    nBulletsFree--;
    return BulletFree[nBulletsFree];
}

void DestroyBullet(short nBullet)
{
    short nSprite = BulletList[nBullet].nSprite;

    runlist_DoSubRunRec(BulletList[nBullet].field_6);
    runlist_DoSubRunRec(sprite[nSprite].lotag - 1);
    runlist_SubRunRec(BulletList[nBullet].field_8);

    StopSpriteSound(nSprite);

    mydeletesprite(nSprite);

    BulletFree[nBulletsFree] = nBullet;
    nBulletsFree++;
}

void IgniteSprite(int nSprite)
{
    sprite[nSprite].hitag += 2;

    int nAnim = BuildAnim(-1, 38, 0, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum, 40, 20);
    short nAnimSprite = GetAnimSprite(nAnim);

    sprite[nAnimSprite].hitag = nSprite;
    changespritestat(nAnimSprite, kStatIgnited);

    short yRepeat = (tilesiz[sprite[nAnimSprite].picnum].y * 32) / nFlameHeight;
    if (yRepeat < 1)
        yRepeat = 1;

    sprite[nAnimSprite].yrepeat = (uint8_t)yRepeat;
}

void BulletHitsSprite(Bullet *pBullet, short nBulletSprite, short nHitSprite, int x, int y, int z, int nSector)
{
    assert(nSector >= 0 && nSector < kMaxSectors);

    bulletInfo *pBulletInfo = &BulletInfo[pBullet->nType];

    short nStat = sprite[nHitSprite].statnum;

    switch (pBullet->nType)
    {
        case 3:
        {
            if (nStat > 107 || nStat == kStatAnubisDrum) {
                return;
            }

            sprite[nHitSprite].hitag++;

            if (sprite[nHitSprite].hitag == 15) {
                IgniteSprite(nHitSprite);
            }

            if (!RandomSize(2)) {
                BuildAnim(-1, pBulletInfo->field_C, 0, x, y, z, nSector, 40, pBulletInfo->nFlags);
            }

            return;
        }
        case 14:
        {
            if (nStat > 107 || nStat == kStatAnubisDrum) {
                return;
            }
            // else - fall through to below cases
            fallthrough__;
        }
        case 1:
        case 2:
        case 8:
        case 9:
        case 12:
        case 13:
        case 15:
        case 16:
        {
            // loc_29E59
            if (!nStat || nStat > 98) {
                break;
            }

            short nSprite = pBullet->nSprite;
            spritetype *pSprite = &sprite[nSprite];
            spritetype *pHitSprite = &sprite[nHitSprite];

            if (nStat == kStatAnubisDrum)
            {
                short nAngle = (pSprite->ang + 256) - RandomSize(9);

                pHitSprite->xvel = Cos(nAngle) << 1;
                pHitSprite->yvel = Sin(nAngle) << 1;
                pHitSprite->zvel = (-(RandomSize(3) + 1)) << 8;
            }
            else
            {
                int xVel = pHitSprite->xvel;
                int yVel = pHitSprite->yvel;

                pHitSprite->xvel = Cos(pSprite->ang) >> 2;
                pHitSprite->yvel = Sin(pSprite->ang) >> 2;

                MoveCreature(nHitSprite);

                pHitSprite->xvel = xVel;
                pHitSprite->yvel = yVel;
            }

            break;
        }

        default:
            break;
    }

    // BHS_switchBreak:
    short nDamage = pBulletInfo->nDamage;

    if (pBullet->field_13 > 1) {
        nDamage *= 2;
    }

    runlist_DamageEnemy(nHitSprite, nBulletSprite, nDamage);

    if (nStat <= 90 || nStat >= 199)
    {
        BuildAnim(-1, pBulletInfo->field_C, 0, x, y, z, nSector, 40, pBulletInfo->nFlags);
        return;
    }

    switch (nStat)
    {
        case kStatDestructibleSprite:
            break;
        case kStatAnubisDrum:
        case 102:
        case kStatExplodeTrigger:
        case kStatExplodeTarget:
            BuildAnim(-1, 12, 0, x, y, z, nSector, 40, 0);
            break;
        default:
            BuildAnim(-1, 39, 0, x, y, z, nSector, 40, 0);
            if (pBullet->nType > 2)
            {
                BuildAnim(-1, pBulletInfo->field_C, 0, x, y, z, nSector, 40, pBulletInfo->nFlags);
            }
            break;
    }
}

void BackUpBullet(int *x, int *y, short nAngle)
{
    *x -= Cos(nAngle) >> 11;
    *y -= Sin(nAngle) >> 11;
}

int MoveBullet(short nBullet)
{
    short hitsect = -1;
    short hitwall = -1;
    short hitsprite = -1;

    Bullet *pBullet = &BulletList[nBullet];
    short nType = pBullet->nType;
    bulletInfo *pBulletInfo = &BulletInfo[nType];

    short nSprite = BulletList[nBullet].nSprite;
    spritetype *pSprite = &sprite[nSprite];

    int x = pSprite->x;
    int y = pSprite->y;
    int z = pSprite->z; // ebx
    short nSectFlag = SectFlag[pSprite->sectnum];

    int x2, y2, z2;

    int nVal;

    if (pBullet->field_10 < 30000)
    {
        short nEnemySprite = nBulletEnemy[nBullet];
        if (nEnemySprite > -1)
        {
            if (!(sprite[nEnemySprite].cstat & 0x101))
                nBulletEnemy[nBullet] = -1;
            else
            {
                nVal = AngleChase(nSprite, nEnemySprite, pBullet->field_10, 0, 16);
                goto MOVEEND;
            }
        }
        if (nType == 3)
        {
            if (pBullet->field_E < 8)
            {
                pSprite->xrepeat -= 1;
                pSprite->yrepeat += 8;

                pBullet->z -= 200;

                if (pSprite->shade < 90) {
                    pSprite->shade += 35;
                }

                if (pBullet->field_E == 3)
                {
                    pBullet->nSeq = 45;
                    pBullet->nFrame = 0;
                    pSprite->xrepeat = 40;
                    pSprite->yrepeat = 40;
                    pSprite->shade = 0;
                    pSprite->z += 512;
                }
            }
            else
            {
                pSprite->xrepeat += 4;
                pSprite->yrepeat += 4;
            }
        }

        nVal = movesprite(nSprite, pBullet->x, pBullet->y, pBullet->z, pSprite->clipdist >> 1, pSprite->clipdist >> 1, CLIPMASK1);

MOVEEND:
        if (nVal)
        {
            x2 = pSprite->x;
            y2 = pSprite->y;
            z2 = pSprite->z;
            hitsect = pSprite->sectnum;

            if (nVal & 0x30000)
            {
                hitwall = nVal & 0x3FFF;
                goto HITWALL;
            }
            else
            {
                switch (nVal & 0xc000)
                {
                case 0x8000:
                    hitwall = nVal & 0x3FFF;
                    goto HITWALL;
                case 0xc000:
                    hitsprite = nVal & 0x3FFF;
                    goto HITSPRITE;
                }
            }
        }

        // sprite[nSprite].sectnum may have changed since we set nSectFlag ?
        short nFlagVal = nSectFlag ^ SectFlag[pSprite->sectnum];
        if (nFlagVal & kSectUnderwater)
        {
            DestroyBullet(nBullet);
            nVal = 1;
        }

        if (nVal == 0 && nType != 15 && nType != 3)
        {
            AddFlash(sprite[nSprite].sectnum, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, 0);

            if (sprite[nSprite].pal != 5) {
                sprite[nSprite].pal = 1;
            }
        }
    }
    else
    {
        nVal = 1;

        if (nBulletEnemy[nBullet] > -1)
        {
            hitsprite = nBulletEnemy[nBullet];
            x2 = sprite[hitsprite].x;
            y2 = sprite[hitsprite].y;
            z2 = sprite[hitsprite].z - (GetSpriteHeight(hitsprite) >> 1);
            hitsect = sprite[hitsprite].sectnum;
        }
        else
        {
            vec3_t startPos = { x, y, z };
            hitdata_t hitData;
            int dz;
            if (bVanilla)
                dz = -Sin(pBullet->field_C) * 8;
            else
                dz = -pBullet->field_C * 512;
            hitscan(&startPos, pSprite->sectnum, Cos(pSprite->ang), Sin(pSprite->ang), dz, &hitData, CLIPMASK1);
            x2 = hitData.x;
            y2 = hitData.y;
            z2 = hitData.z;
            hitsprite = hitData.sprite;
            hitsect = hitData.sect;
            hitwall = hitData.wall;
        }

        lasthitx = x2;
        lasthity = y2;
        lasthitz = z2;
        lasthitsect = hitsect;
        lasthitwall = hitwall;
        lasthitsprite = hitsprite;

        if (hitsprite > -1)
        {
HITSPRITE:
            if (pSprite->pal == 5 && sprite[hitsprite].statnum == 100)
            {
                short nPlayer = GetPlayerFromSprite(hitsprite);
                if (!PlayerList[nPlayer].bIsMummified)
                {
                    PlayerList[nPlayer].bIsMummified = kTrue;
                    SetNewWeapon(nPlayer, kWeaponMummified);
                }
            }
            else
            {
                BulletHitsSprite(pBullet, pSprite->owner, hitsprite, x2, y2, z2, hitsect);
            }
        }
        else if (hitwall > -1)
        {
HITWALL:
            if (wall[hitwall].picnum == kEnergy1)
            {
                short nSector = wall[hitwall].nextsector;
                if (nSector > -1)
                {
                    short nDamage = BulletInfo[pBullet->nType].nDamage;
                    if (pBullet->field_13 > 1) {
                        nDamage *= 2;
                    }

                    short nNormal = GetWallNormal(hitwall) & kAngleMask;

                    runlist_DamageEnemy(sector[nSector].extra, nNormal, nDamage);
                }
            }
        }

        if (hitsect > -1) // NOTE: hitsect can be -1. this check wasn't in original code. TODO: demo compatiblity?
        {
            if (hitsprite < 0 && hitwall < 0)
            {
                if ((SectBelow[hitsect] >= 0 && (SectFlag[SectBelow[hitsect]] & kSectUnderwater)) || SectDepth[hitsect])
                {
                    pSprite->x = x2;
                    pSprite->y = y2;
                    pSprite->z = z2;
                    BuildSplash(nSprite, hitsect);
                }
                else
                {
                    BuildAnim(-1, pBulletInfo->field_C, 0, x2, y2, z2, hitsect, 40, pBulletInfo->nFlags);
                }
            }
            else
            {
                if (hitwall >= 0)
                {
                    BackUpBullet(&x2, &y2, pSprite->ang);

                    if (nType != 3 || RandomSize(2) == 0)
                    {
                        int zOffset = RandomSize(8) << 3;

                        if (!RandomBit()) {
                            zOffset = -zOffset;
                        }

                        // draws bullet puff on walls when they're shot
                        BuildAnim(-1, pBulletInfo->field_C, 0, x2, y2, z2 + zOffset, hitsect, 40, pBulletInfo->nFlags);
                    }
                }
                else
                {
                    pSprite->x = x2;
                    pSprite->y = y2;
                    pSprite->z = z2;

                    mychangespritesect(nSprite, hitsect);
                }

                if (BulletInfo[nType].nRadius)
                {
                    nRadialBullet = nType;

                    runlist_RadialDamageEnemy(nSprite, pBulletInfo->nDamage, pBulletInfo->nRadius);

                    nRadialBullet = -1;

                    AddFlash(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 128);
                }
            }
        }

        DestroyBullet(nBullet);
    }

    return nVal;
}

void SetBulletEnemy(short nBullet, short nEnemy)
{
    if (nBullet >= 0) {
        nBulletEnemy[nBullet] = nEnemy;
    }
}

int GetBulletSprite(int nBullet)
{
    assert(nBullet >= 0 && nBullet < kMaxBullets);
    return BulletList[nBullet].nSprite;
}

int BuildBullet(short nSprite, int nType, int UNUSED(ebx), int UNUSED(ecx), int val1, int nAngle, int val2, int val3)
{
    Bullet sBullet;
    bulletInfo *pBulletInfo = &BulletInfo[nType];

    if (pBulletInfo->field_4 > 30000)
    {
        if (val2 >= 10000)
        {
            val2 -= 10000;

            short nTargetSprite = val2;
            spritetype *pTargetSprite = &sprite[nTargetSprite];

//			assert(sprite[nTargetSprite].sectnum <= kMaxSectors);

            if (pTargetSprite->cstat & 0x101)
            {
                sBullet.nType = nType;
                sBullet.field_13 = val3;

                sBullet.nSprite = insertsprite(sprite[nSprite].sectnum, 200);
                sprite[sBullet.nSprite].ang = nAngle;

                int nHeight = GetSpriteHeight(nTargetSprite);

                assert(sprite[nTargetSprite].sectnum >= 0 && sprite[nTargetSprite].sectnum < kMaxSectors);

                BulletHitsSprite(&sBullet, nSprite, nTargetSprite, pTargetSprite->x, pTargetSprite->y, pTargetSprite->z - (nHeight >> 1), pTargetSprite->sectnum);
                mydeletesprite(sBullet.nSprite);
                return -1;
            }
            else
            {
                val2 = 0;
            }
        }
    }

    if (!nBulletsFree) {
        return -1;
    }

    short nSector;

    if (sprite[nSprite].statnum == 100)
    {
        nSector = nPlayerViewSect[GetPlayerFromSprite(nSprite)];
    }
    else
    {
        nSector = sprite[nSprite].sectnum;
    }

    short nBulletSprite = insertsprite(nSector, 200);
    int nHeight = GetSpriteHeight(nSprite);
    nHeight = nHeight - (nHeight >> 2);

    if (val1 == -1) {
        val1 = -nHeight;
    }

    sprite[nBulletSprite].x = sprite[nSprite].x;
    sprite[nBulletSprite].y = sprite[nSprite].y;
    sprite[nBulletSprite].z = sprite[nSprite].z;

    // why is this done here???
    assert(nBulletSprite >= 0 && nBulletSprite < kMaxSprites);

    short nBullet = GrabBullet();
    Bullet *pBullet = &BulletList[nBullet];

    nBulletEnemy[nBullet] = -1;

    sprite[nBulletSprite].cstat = 0;
    sprite[nBulletSprite].shade = -64;

    if (pBulletInfo->nFlags & 4) {
        sprite[nBulletSprite].pal = 4;
    }
    else {
        sprite[nBulletSprite].pal = 0;
    }

    sprite[nBulletSprite].clipdist = 25;

    short nRepeat = pBulletInfo->xyRepeat;
    if (nRepeat < 0) {
        nRepeat = 30;
    }

    sprite[nBulletSprite].xrepeat = nRepeat;
    sprite[nBulletSprite].yrepeat = nRepeat;
    sprite[nBulletSprite].xoffset = 0;
    sprite[nBulletSprite].yoffset = 0;
    sprite[nBulletSprite].ang = nAngle;
    sprite[nBulletSprite].xvel = 0;
    sprite[nBulletSprite].yvel = 0;
    sprite[nBulletSprite].zvel = 0;
    sprite[nBulletSprite].owner = nSprite;
    sprite[nBulletSprite].lotag = runlist_HeadRun() + 1;
    sprite[nBulletSprite].extra = -1;
    sprite[nBulletSprite].hitag = 0;

//	GrabTimeSlot(3);

    pBullet->field_10 = 0;
    pBullet->field_E = pBulletInfo->field_2;
    pBullet->nFrame  = 0;

    short nSeq;

    if (pBulletInfo->field_8 != -1)
    {
        pBullet->field_12 = 0;
        nSeq = pBulletInfo->field_8;
    }
    else
    {
        pBullet->field_12 = 1;
        nSeq = pBulletInfo->nSeq;
    }

    pBullet->nSeq = nSeq;

    sprite[nBulletSprite].picnum = seq_GetSeqPicnum(nSeq, 0, 0);

    if (nSeq == kSeqBullet) {
        sprite[nBulletSprite].cstat |= 0x8000;
    }

    pBullet->field_C = val2;
    pBullet->nType = nType;
    pBullet->nSprite = nBulletSprite;
    pBullet->field_6 = runlist_AddRunRec(sprite[nBulletSprite].lotag - 1, nBullet, kRunBullet);
    pBullet->field_8 = runlist_AddRunRec(NewRun, nBullet, kRunBullet);
    pBullet->field_13 = val3;
    sprite[nBulletSprite].z += val1;

    int var_18;

    nSector = sprite[nBulletSprite].sectnum;

    while (sprite[nBulletSprite].z < sector[nSector].ceilingz)
    {
        if (SectAbove[nSector] == -1)
        {
            sprite[nBulletSprite].z = sector[nSector].ceilingz;
            break;
        }

        nSector = SectAbove[nSector];
        mychangespritesect(nBulletSprite, nSector);
    }

    if (val2 < 10000)
    {
        var_18 = ((-Sin(val2)) * pBulletInfo->field_4) >> 11;
    }
    else
    {
        val2 -= 10000;

        short nTargetSprite = val2;

        if ((unsigned int)pBulletInfo->field_4 > 30000)
        {
            nBulletEnemy[nBullet] = nTargetSprite;
        }
        else
        {
            nHeight = GetSpriteHeight(nTargetSprite);

            if (sprite[nTargetSprite].statnum == 100)
            {
                nHeight -= nHeight >> 2;
            }
            else
            {
                nHeight -= nHeight >> 1;
            }

            int var_20 = sprite[nTargetSprite].z - nHeight;

            int x, y;

            if (nSprite != -1 && sprite[nSprite].statnum != 100)
            {
                x = sprite[nTargetSprite].x;
                y = sprite[nTargetSprite].y;

                if (sprite[nTargetSprite].statnum != 100)
                {
                    x += (sprite[nTargetSprite].xvel * 20) >> 6;
                    y += (sprite[nTargetSprite].yvel * 20) >> 6;
                }
                else
                {
                    int nPlayer = GetPlayerFromSprite(nTargetSprite);
                    if (nPlayer > -1)
                    {
                        x += nPlayerDX[nPlayer] * 15;
                        y += nPlayerDY[nPlayer] * 15;
                    }
                }

                x -= sprite[nBulletSprite].x;
                y -= sprite[nBulletSprite].y;

                nAngle = GetMyAngle(x, y);
                sprite[nSprite].ang = nAngle;
            }
            else
            {
                // loc_2ABA3:
                x = sprite[nTargetSprite].x - sprite[nBulletSprite].x;
                y = sprite[nTargetSprite].y - sprite[nBulletSprite].y;
            }

            int nSqrt = lsqrt(y*y + x*x);
            if ((unsigned int)nSqrt > 0)
            {
                var_18 = ((var_20 - sprite[nBulletSprite].z) * pBulletInfo->field_4) / nSqrt;
            }
            else
            {
                var_18 = 0;
            }
        }
    }

    pBullet->z = 0;
    pBullet->x = (sprite[nSprite].clipdist << 2) * Cos(nAngle);
    pBullet->y = (sprite[nSprite].clipdist << 2) * Sin(nAngle);
    nBulletEnemy[nBullet] = -1;

    if (MoveBullet(nBullet))
    {
        nBulletSprite = -1;
    }
    else
    {
        pBullet->field_10 = pBulletInfo->field_4;
        pBullet->x = (Cos(nAngle) >> 3) * pBulletInfo->field_4;
        pBullet->y = (Sin(nAngle) >> 3) * pBulletInfo->field_4;
        pBullet->z = var_18 >> 3;
    }

    return nBullet;
}

void FuncBullet(int a, int UNUSED(b), int nRun)
{
    short nBullet = RunData[nRun].nVal;
    assert(nBullet >= 0 && nBullet < kMaxBullets);

    short nSeq = SeqOffsets[BulletList[nBullet].nSeq];
    short nSprite = BulletList[nBullet].nSprite;

    int nMessage = a & kMessageMask;

    switch (nMessage)
    {
        case 0x20000:
        {
            short nFlag = FrameFlag[SeqBase[nSeq] + BulletList[nBullet].nFrame];

            seq_MoveSequence(nSprite, nSeq, BulletList[nBullet].nFrame);

            if (nFlag & 0x80)
            {
                BuildAnim(-1, 45, 0, sprite[nSprite].x, sprite[nSprite].y, sprite[nSprite].z, sprite[nSprite].sectnum, sprite[nSprite].xrepeat, 0);
            }

            BulletList[nBullet].nFrame++;
            if (BulletList[nBullet].nFrame >= SeqSize[nSeq])
            {
                if (!BulletList[nBullet].field_12)
                {
                    BulletList[nBullet].nSeq = BulletInfo[BulletList[nBullet].nType].nSeq;
                    BulletList[nBullet].field_12++;
                }

                BulletList[nBullet].nFrame = 0;
            }

            if (BulletList[nBullet].field_E != -1 && --BulletList[nBullet].field_E == 0)
            {
                DestroyBullet(nBullet);
            }
            else
            {
                MoveBullet(nBullet);
            }
            break;
        }

        case 0x90000:
        {
            short nSprite2 = a & 0xFFFF;
            tsprite[nSprite2].statnum = 1000;

            if (BulletList[nBullet].nType == 15)
            {
                seq_PlotArrowSequence(nSprite2, nSeq, BulletList[nBullet].nFrame);
            }
            else
            {
                seq_PlotSequence(nSprite2, nSeq, BulletList[nBullet].nFrame, 0);
                tsprite[nSprite2].owner = -1;
            }
            break;
        }

        case 0xA0000:
            break;

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

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

void BulletLoadSave::Load()
{
    Read(BulletFree, sizeof(BulletFree));
    Read(BulletList, sizeof(BulletList));
    Read(nBulletEnemy, sizeof(nBulletEnemy));

    Read(&nBulletsFree, sizeof(nBulletsFree));
    Read(&lasthitz, sizeof(lasthitz));
    Read(&lasthitx, sizeof(lasthitx));
    Read(&lasthity, sizeof(lasthity));
    Read(&lasthitsect, sizeof(lasthitsect));
    Read(&lasthitsprite, sizeof(lasthitsprite));
    Read(&lasthitwall, sizeof(lasthitwall));
    Read(&nBulletCount, sizeof(nBulletCount));
    Read(&nRadialBullet, sizeof(nRadialBullet));
}

void BulletLoadSave::Save()
{
    Write(BulletFree, sizeof(BulletFree));
    Write(BulletList, sizeof(BulletList));
    Write(nBulletEnemy, sizeof(nBulletEnemy));

    Write(&nBulletsFree, sizeof(nBulletsFree));
    Write(&lasthitz, sizeof(lasthitz));
    Write(&lasthitx, sizeof(lasthitx));
    Write(&lasthity, sizeof(lasthity));
    Write(&lasthitsect, sizeof(lasthitsect));
    Write(&lasthitsprite, sizeof(lasthitsprite));
    Write(&lasthitwall, sizeof(lasthitwall));
    Write(&nBulletCount, sizeof(nBulletCount));
    Write(&nRadialBullet, sizeof(nRadialBullet));
}

static BulletLoadSave* myLoadSave;

void BulletLoadSaveConstruct()
{
    myLoadSave = new BulletLoadSave();
}


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

#include "aistuff.h"

// 32 bytes
struct bulletInfo
{
    short nDamage; // 0
    short field_2; // 2
    int field_4;   // 4
    short field_8; // 8
    short nSeq; // 10
    short field_C; // 12
    short nFlags;
    short nRadius; // damage radius
    short xyRepeat;
};

extern bulletInfo BulletInfo[];

extern short nRadialBullet;
extern short lasthitsect;
extern int lasthitz;
extern int lasthitx;
extern int lasthity;

void InitBullets();
short GrabBullet();
void DestroyBullet(short nRun);
int MoveBullet(short nBullet);
void SetBulletEnemy(short nBullet, short nEnemy);
int BuildBullet(short nSprite, int nType, int ebx, int ecx, int val1, int nAngle, int val2, int val3);
int GetBulletSprite(int nBullet);
void IgniteSprite(int nSprite);
void FuncBullet(int, int, int);
void BackUpBullet(int *x, int *y, short nAngle);

#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 "build.h"
#include "compat.h"
#include "baselayer.h"
#include "cd.h"
#include "fx_man.h"
#include "sound.h"
#include "exhumed.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>

static char *pTrack = NULL;
static int trackhandle = -1;
int nLastVolumeSet = 0;

/* TODO

Currently playing music must keep playing on return to map screen or exit from training level

*/

void setCDaudiovolume(int val)
{
    if (trackhandle > 0) {
        FX_SetPan(trackhandle, val, val, val);
    }
}

bool playCDtrack(int nTrack, bool bLoop)
{
//  if (!MusicToggle)
//      return true;

    static const char *tracknameformats[] = { "Track%02d", "exhumed%02d", "track%02d" };

    if (nTrack < 2) {
        return false;
    }

    StopCD();

    char filename[BMAX_PATH];
    int32_t hFile = -1;

    for (auto fmt : tracknameformats)
    {
        Bsnprintf(filename, sizeof(filename), fmt, nTrack);
        hFile = S_OpenAudio(filename, 0, 1);
        if (hFile >= 0)
            break;
    }

    if (hFile < 0) {
        OSD_Printf("Error opening music track %02d\n", nTrack);
        return false;
    }

    int32_t nFileLen = kfilelength(hFile);

    pTrack = (char*)Xaligned_alloc(16, nFileLen);
    if (pTrack == NULL)
    {
        OSD_Printf("Error allocating music track data memory for %s\n", filename);
        kclose(hFile);
        return false;
    }

    int nRead = kread(hFile, pTrack, nFileLen);
    if (nRead != nFileLen)
    {
        OSD_Printf("Error reading music track data for %s\n", filename);
        Xaligned_free(pTrack);
        pTrack = NULL;
        kclose(hFile);
        return false;
    }

    kclose(hFile);

    trackhandle = FX_Play(pTrack, nRead, bLoop ? 0 : -1, 0, 0, MusicVolume, MusicVolume, MusicVolume, FX_MUSIC_PRIORITY, fix16_one, MUSIC_ID);
    if (trackhandle <= FX_Ok)
    {
        OSD_Printf("Error playing music track %s\n", filename);
        if (pTrack)
        {
            Xaligned_free(pTrack);
            pTrack = NULL;
        }
        return false;
    }

    return true;
}

void StartfadeCDaudio()
{
    if (CDplaying()) {
        nLastVolumeSet = MusicVolume;
    }
}

int StepFadeCDaudio()
{
    if (!CDplaying()) {
        return 0;
    }

    if (nLastVolumeSet <= 0) {
        return 0;
    }

    nLastVolumeSet -= 8;

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

    setCDaudiovolume(nLastVolumeSet);

    if (nLastVolumeSet == 0) {
        StopCD();
    }

    return 1;
}

bool CDplaying()
{
    if (trackhandle <= 0) {
        return false;
    }
    else {
        return FX_SoundActive(trackhandle);
    }
}

void StopCD()
{
    if (trackhandle > 0) {
        FX_StopSound(trackhandle);
        trackhandle = -1;
    }

    if (pTrack)
    {
        Xaligned_free(pTrack);
        pTrack = NULL;
    }
}

int fadecdaudio()
{
    StartfadeCDaudio();

    while (1)
    {
        if (!StepFadeCDaudio())
        {
            return 1;
        }
        else
        {
            WaitTicks(1);
        }
    }

    return 1;
}


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

void setCDaudiovolume(int val);
bool playCDtrack(int nTrack, bool bLoop);
void StartfadeCDaudio();
int StepFadeCDaudio();
bool CDplaying();
void StopCD();
int fadecdaudio();

#endif


code:
//
// Common non-engine code/data for EDuke32 and Mapster32
//

#include "compat.h"
#include "build.h"
#include "baselayer.h"
#include "palette.h"
#include "grpscan.h"
#include "vfs.h"
#include "texcache.h"

#ifdef _WIN32
# include "windows_inc.h"
# include "winbits.h"
#elif defined __APPLE__
# include "osxbits.h"
#endif

#include "common.h"
#include "common_game.h"

struct grpfile_t const* g_selectedGrp;

int32_t g_gameType = GAMEFLAG_POWERSLAVE;

int r_showfps;

// g_gameNamePtr can point to one of: grpfiles[].name (string literal), string
// literal, malloc'd block (XXX: possible leak)
const char* g_gameNamePtr = NULL;

// grp handling

static const char* defaultgamegrp = "STUFF.DAT";
static const char* defaultdeffilename = "exhumed.def";

// g_grpNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
char* g_grpNamePtr = NULL;

void clearGrpNamePtr(void)
{
    Xfree(g_grpNamePtr);
    // g_grpNamePtr assumed to be assigned to right after
}

const char* G_DefaultGrpFile(void)
{
    return defaultgamegrp;
}
const char* G_DefaultDefFile(void)
{
    return defaultdeffilename;
}

const char* G_GrpFile(void)
{
    return (g_grpNamePtr == NULL)?G_DefaultGrpFile():g_grpNamePtr;
}

const char* G_DefFile(void)
{
    return (g_defNamePtr == NULL)?G_DefaultDefFile():g_defNamePtr;
}

//////////

static char g_rootDir[BMAX_PATH];

int g_useCwd;
int32_t g_groupFileHandle;

static struct strllist* CommandPaths, * CommandGrps;

void G_ExtPreInit(int32_t argc, char const* const* argv)
{
    g_useCwd = G_CheckCmdSwitch(argc, argv, "-usecwd");

    #ifdef _WIN32
    GetModuleFileName(NULL, g_rootDir, BMAX_PATH);
    Bcorrectfilename(g_rootDir, 1);
    //buildvfs_chdir(g_rootDir);
    #else
    buildvfs_getcwd(g_rootDir, BMAX_PATH);
    strcat(g_rootDir, "/");
    #endif
}

void G_ExtInit(void)
{
    char cwd[BMAX_PATH];

    #ifdef EDUKE32_OSX
    char* appdir = Bgetappdir();
    addsearchpath(appdir);
    Xfree(appdir);
    #endif

    #ifdef USE_PHYSFS
    strncpy(cwd, PHYSFS_getBaseDir(), ARRAY_SIZE(cwd));
    cwd[ARRAY_SIZE(cwd) - 1] = '\0';
    #else
    if (buildvfs_getcwd(cwd, ARRAY_SIZE(cwd)) && Bstrcmp(cwd, "/") != 0)
        #endif
        addsearchpath(cwd);

    // TODO:
    if (CommandPaths)
    {
        int32_t i;
        struct strllist* s;
        while (CommandPaths)
        {
            s = CommandPaths->next;
            i = addsearchpath(CommandPaths->str);
            if (i < 0)
            {
                initprintf("Failed adding %s for game data: %s\n", CommandPaths->str,
                    i == -1?"not a directory":"no such directory");
            }

            Xfree(CommandPaths->str);
            Xfree(CommandPaths);
            CommandPaths = s;
        }
    }

    #if defined(_WIN32) && !defined(EDUKE32_STANDALONE)
    if (buildvfs_exists("user_profiles_enabled"))
        #else
    if (g_useCwd == 0 && !buildvfs_exists("user_profiles_disabled"))
        #endif
    {
        char* homedir;
        int32_t asperr;

        if ((homedir = Bgethomedir()))
        {
            Bsnprintf(cwd, ARRAY_SIZE(cwd), "%s/"
                #if defined(_WIN32)
                APPNAME
                #elif defined(GEKKO)
                "apps/" APPBASENAME
                #else
                ".config/" APPBASENAME
                #endif
                , homedir);
            asperr = addsearchpath(cwd);
            if (asperr == -2)
            {
                if (buildvfs_mkdir(cwd, S_IRWXU) == 0) asperr = addsearchpath(cwd);
                else asperr = -1;
            }
            if (asperr == 0)
                buildvfs_chdir(cwd);
            Xfree(homedir);
        }
    }
}

void G_ScanGroups(void)
{
    ScanGroups();

    g_selectedGrp = NULL;

    char const* const currentGrp = G_GrpFile();

    for (grpfile_t const* fg = foundgrps; fg; fg = fg->next)
    {
        if (!Bstrcasecmp(fg->filename, currentGrp))
        {
            g_selectedGrp = fg;
            break;
        }
    }

    if (g_selectedGrp == NULL)
        g_selectedGrp = foundgrps;
}

static int32_t G_TryLoadingGrp(char const* const grpfile)
{
    int32_t i;

    if ((i = initgroupfile(grpfile)) == -1)
        initprintf("Warning: could not find main data file \"%s\"!\n", grpfile);
    else
        initprintf("Using \"%s\" as main game data file.\n", grpfile);

    return i;
}

static int32_t G_LoadGrpDependencyChain(grpfile_t const* const grp)
{
    if (!grp)
        return -1;

    if (grp->type->dependency && grp->type->dependency != grp->type->crcval)
        G_LoadGrpDependencyChain(FindGroup(grp->type->dependency));

    int32_t const i = G_TryLoadingGrp(grp->filename);

    return i;
}

void G_LoadGroups(int32_t autoload)
{
    if (g_modDir[0] != '/')
    {
        char cwd[BMAX_PATH];

        Bstrcat(g_rootDir, g_modDir);
        addsearchpath(g_rootDir);
        //        addsearchpath(mod_dir);

        char path[BMAX_PATH];

        if (buildvfs_getcwd(cwd, BMAX_PATH))
        {
            Bsnprintf(path, sizeof(path), "%s/%s", cwd, g_modDir);
            if (!Bstrcmp(g_rootDir, path))
            {
                if (addsearchpath(path) == -2)
                    if (buildvfs_mkdir(path, S_IRWXU) == 0)
                        addsearchpath(path);
            }
        }

        #ifdef USE_OPENGL
        Bsnprintf(path, sizeof(path), "%s/%s", g_modDir, TEXCACHEFILE);
        Bstrcpy(TEXCACHEFILE, path);
        #endif
    }

    const char* grpfile;
    int32_t i;

    if ((i = G_LoadGrpDependencyChain(g_selectedGrp)) != -1)
    {
        grpfile = g_selectedGrp->filename;

        clearGrpNamePtr();
        g_grpNamePtr = dup_filename(grpfile);

        grpinfo_t const* const type = g_selectedGrp->type;

        g_gameType = type->game;
        g_gameNamePtr = type->name;

        if (type->defname && g_defNamePtr == NULL)
            g_defNamePtr = dup_filename(type->defname);
    }
    else
    {
        grpfile = G_GrpFile();
        i = G_TryLoadingGrp(grpfile);
    }

    if (autoload)
    {
        G_LoadGroupsInDir("autoload");

        if (i != -1)
            G_DoAutoload(grpfile);
    }

    if (g_modDir[0] != '/')
        G_LoadGroupsInDir(g_modDir);

    loaddefinitions_game(G_DefFile(), TRUE);

    struct strllist* s;

    int const bakpathsearchmode = pathsearchmode;
    pathsearchmode = 1;

    while (CommandGrps)
    {
        int32_t j;

        s = CommandGrps->next;

        if ((j = initgroupfile(CommandGrps->str)) == -1)
            initprintf("Could not find file \"%s\".\n", CommandGrps->str);
        else
        {
            g_groupFileHandle = j;
            initprintf("Using file \"%s\" as game data.\n", CommandGrps->str);
            if (autoload)
                G_DoAutoload(CommandGrps->str);
        }

        Xfree(CommandGrps->str);
        Xfree(CommandGrps);
        CommandGrps = s;
    }
    pathsearchmode = bakpathsearchmode;
}

#ifndef EDUKE32_TOUCH_DEVICES
#if defined __linux__ || defined EDUKE32_BSD
static void Exhumed_Add_GOG_Linux(const char * path)
{
    char buf[BMAX_PATH];

    Bsnprintf(buf, sizeof(buf), "%s/game/data", path);
    addsearchpath(buf);
    Bsnprintf(buf, sizeof(buf), "%s/game/data/MUSIC", path);
    addsearchpath(buf);
}
static void Exhumed_Add_Steam_Linux(const char *path)
{
    char buf[BMAX_PATH];

    // PowerSlave (DOS Classic Edition) - Steam
    Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/PowerslaveCE", path);
    addsearchpath(buf);
    Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/PowerslaveCE/PWRSLAVE", path);
    addsearchpath(buf);
    Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/PowerslaveCE/PWRSLAVE/MUSIC", path);
    addsearchpath(buf);
}
#endif
#endif

void G_AddSearchPaths(void)
{
#ifndef EDUKE32_TOUCH_DEVICES
#if defined __linux__ || defined EDUKE32_BSD
    char buf[BMAX_PATH];
    char *homepath = Bgethomedir();

    // PowerSlave (DOS Classic Edition) - Steam
    Bsnprintf(buf, sizeof(buf), "%s/.steam/steam", homepath);
    Exhumed_Add_Steam_Linux(buf);

    Bsnprintf(buf, sizeof(buf), "%s/.steam/steam/steamapps/libraryfolders.vdf", homepath);
    Paths_ParseSteamLibraryVDF(buf, Exhumed_Add_Steam_Linux);

    // Powerslave - GOG.com
    Bsnprintf(buf, sizeof(buf), "%s/GOG Games/Powerslave English", homepath);
    Exhumed_Add_GOG_Linux(buf);
    Paths_ParseXDGDesktopFilesFromGOG(homepath, "Powerslave_English", Exhumed_Add_GOG_Linux);

    Xfree(homepath);

    addsearchpath("/usr/share/games/pcexhumed");
    addsearchpath("/usr/local/share/games/pcexhumed");
#elif defined EDUKE32_OSX
    char buf[BMAX_PATH];
    int32_t i;
    char* applications[] = { osx_getapplicationsdir(0), osx_getapplicationsdir(1) };
    char* support[] = { osx_getsupportdir(0), osx_getsupportdir(1) };
    char* documents[] = { osx_getdocumentsdir(0), osx_getdocumentsdir(1) };

    for (i = 0; i < 2; i++)
    {
        // Powerslave - GOG.com
        Bsnprintf(buf, sizeof(buf), "%s/Powerslave.app/Contents/Resources/game", applications[i]);
        addsearchpath(buf);
        Bsnprintf(buf, sizeof(buf), "%s/Powerslave.app/Contents/Resources/game", documents[i]);
        addsearchpath(buf);
        Bsnprintf(buf, sizeof(buf), "%s/Powerslave.app/Contents/Resources/game/Powerslave.app/Contents/Resources/game", applications[i]);
        addsearchpath(buf);
    }

    for (i = 0; i < 2; i++)
    {
        Bsnprintf(buf, sizeof(buf), "%s/PCExhumed", support[i]);
        addsearchpath(buf);
    }

    for (i = 0; i < 2; i++)
    {
        Xfree(applications[i]);
        Xfree(support[i]);
        Xfree(documents[i]);
    }
#elif defined (_WIN32)
    char buf[BMAX_PATH] = { 0 };
    DWORD bufsize;

    // PowerSlave (DOS Classic Edition) - Steam
    bufsize = sizeof(buf);
    if (Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 1260020)", "InstallLocation", buf, &bufsize))
    {
        char* const suffix = buf + bufsize - 1;
        DWORD const remaining = sizeof(buf) - bufsize;

        Bstrncpy(suffix, "/PWRSLAVE", remaining);
        addsearchpath(buf);
    }

    // Powerslave - GOG.com
    bufsize = sizeof(buf);
    if (Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\2132611980)", "path", buf, &bufsize))
    {
        addsearchpath(buf);
    }

#endif
#endif
}

void G_CleanupSearchPaths(void)
{
//    removesearchpaths_withuser(SEARCHPATH_REMOVE);
}

//////////

void G_AddGroup(const char* buffer)
{
    char buf[BMAX_PATH];

    struct strllist* s = (struct strllist*)Xcalloc(1, sizeof(struct strllist));

    Bstrcpy(buf, buffer);

    if (Bstrchr(buf, '.') == 0)
        Bstrcat(buf, ".grp");

    s->str = Xstrdup(buf);

    if (CommandGrps)
    {
        struct strllist* t;
        for (t = CommandGrps; t->next; t = t->next);
        t->next = s;
        return;
    }
    CommandGrps = s;
}

void G_AddPath(const char* buffer)
{
    struct strllist* s = (struct strllist*)Xcalloc(1, sizeof(struct strllist));
    s->str = Xstrdup(buffer);

    if (CommandPaths)
    {
        struct strllist* t;
        for (t = CommandPaths; t->next; t = t->next);
        t->next = s;
        return;
    }
    CommandPaths = s;
}

//////////

// loads all group (grp, zip, pk3/4) files in the given directory
void G_LoadGroupsInDir(const char* dirname)
{
    static const char* extensions[] = {"*.grp", "*.zip", "*.ssi", "*.pk3", "*.pk4"};
    char buf[BMAX_PATH];
    fnlist_t fnlist = FNLIST_INITIALIZER;

    for (auto& extension : extensions)
    {
        BUILDVFS_FIND_REC* rec;

        fnlist_getnames(&fnlist, dirname, extension, -1, 0);

        for (rec = fnlist.findfiles; rec; rec = rec->next)
        {
            Bsnprintf(buf, sizeof(buf), "%s/%s", dirname, rec->name);
            initprintf("Using group file \"%s\".\n", buf);
            initgroupfile(buf);
        }

        fnlist_clearnames(&fnlist);
    }
}

void G_DoAutoload(const char* dirname)
{
    char buf[BMAX_PATH];

    Bsnprintf(buf, sizeof(buf), "autoload/%s", dirname);
    G_LoadGroupsInDir(buf);
}

//////////

#ifdef FORMAT_UPGRADE_ELIGIBLE

static int32_t S_TryFormats(char* const testfn, char* const fn_suffix, char const searchfirst)
{
    #ifdef HAVE_FLAC
    {
        Bstrcpy(fn_suffix, ".flac");
        int32_t const fp = kopen4loadfrommod(testfn, searchfirst);
        if (fp >= 0)
            return fp;
    }
    #endif

    #ifdef HAVE_VORBIS
    {
        Bstrcpy(fn_suffix, ".ogg");
        int32_t const fp = kopen4loadfrommod(testfn, searchfirst);
        if (fp >= 0)
            return fp;
    }
    #endif

    return -1;
}

static int32_t S_TryExtensionReplacements(char* const testfn, char const searchfirst, uint8_t const ismusic)
{
    char* extension = Bstrrchr(testfn, '.');
    char* const fn_end = Bstrchr(testfn, '\0');

    // ex: grabbag.voc --> grabbag_voc.*
    if (extension != NULL)
    {
        *extension = '_';

        int32_t const fp = S_TryFormats(testfn, fn_end, searchfirst);
        if (fp >= 0)
            return fp;
    }
    else
    {
        extension = fn_end;
    }

    // ex: grabbag.mid --> grabbag.*
    if (ismusic)
    {
        int32_t const fp = S_TryFormats(testfn, extension, searchfirst);
        if (fp >= 0)
            return fp;
    }

    return -1;
}

int32_t S_OpenAudio(const char* fn, char searchfirst, uint8_t const ismusic)
{
    int32_t const     origfp = kopen4loadfrommod(fn, searchfirst);
    char const* const origparent = origfp != -1 ? kfileparent(origfp) : NULL;
    uint32_t const    parentlength = origparent != NULL ? Bstrlen(origparent) : 0;

    auto testfn = (char*)Xmalloc(Bstrlen(fn) + 12 + parentlength); // "music/" + overestimation of parent minus extension + ".flac" + '\0'

    // look in ./
    // ex: ./grabbag.mid
    Bstrcpy(testfn, fn);
    int32_t fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
    if (fp >= 0)
        goto success;

    // look in ./music/<file's parent GRP name>/
    // ex: ./music/duke3d/grabbag.mid
    // ex: ./music/nwinter/grabbag.mid
    if (origparent != NULL)
    {
        char const* const parentextension = Bstrrchr(origparent, '.');
        uint32_t const namelength = parentextension != NULL ? (unsigned)(parentextension - origparent) : parentlength;

        Bsprintf(testfn, "music/%.*s/%s", namelength, origparent, fn);
        fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
        if (fp >= 0)
            goto success;
    }

    // look in ./music/
    // ex: ./music/grabbag.mid
    Bsprintf(testfn, "music/%s", fn);
    fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
    if (fp >= 0)
        goto success;

    fp = origfp;
success:
    Xfree(testfn);
    if (fp != origfp)
        kclose(origfp);

    return fp;
}

#endif


code:
//
// Definitions of common game-only data structures/functions
// (and declarations of data appearing in both)
// for EDuke32 and Mapster32
//

#ifndef EDUKE32_COMMON_GAME_H_
#define EDUKE32_COMMON_GAME_H_

#include "build.h"

#include "collections.h"
#include "grpscan.h"

#include "vfs.h"

#if defined HAVE_FLAC || defined HAVE_VORBIS
# define FORMAT_UPGRADE_ELIGIBLE
extern int32_t S_OpenAudio(const char* fn, char searchfirst, uint8_t ismusic);
#else
# define S_OpenAudio(fn, searchfirst, ismusic) kopen4loadfrommod(fn, searchfirst)
#endif

#define G_ModDirSnprintf(buf, size, basename, ...)                                                                                \
    (((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/" basename, g_modDir, ##__VA_ARGS__) : Bsnprintf(buf, size, basename, ##__VA_ARGS__)) \
     >= ((int32_t)size) - 1)

#define G_ModDirSnprintfLite(buf, size, basename) \
    ((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/%s", g_modDir, basename) : Bsnprintf(buf, size, "%s", basename))

#ifdef __cplusplus
extern "C" {
    #endif

    extern int g_useCwd;

    #ifndef APPNAME
    #define APPNAME             "PCExhumed"
    #endif

    #ifndef APPBASENAME
    #define APPBASENAME         "pcexhumed"
    #endif

    extern struct grpfile_t const* g_selectedGrp;

    extern int32_t g_gameType;
    extern int     g_addonNum;

    #define POWERSLAVE  (g_gameType & GAMEFLAG_POWERSLAVE)
    #define EXHUMED     (g_gameType & GAMEFLAG_EXHUMED)
    #define ISDEMOVER   (g_gameType & GAMEFLAG_DEMO)

    enum basepal_t
    {
        BASEPAL = 0,
        ANIMPAL,
        BASEPALCOUNT
    };

    #define OSDTEXT_DEFAULT   "^00"
    #define OSDTEXT_DARKRED   "^00"
    #define OSDTEXT_GREEN     "^00"
    #define OSDTEXT_RED       "^00"
    #define OSDTEXT_YELLOW    "^00"

    #define OSDTEXT_BRIGHT    "^S0"

    #define OSD_ERROR OSDTEXT_DARKRED OSDTEXT_BRIGHT

    extern const char* g_gameNamePtr;

    extern char* g_grpNamePtr;
    extern char* g_scriptNamePtr;
    extern char* g_rtsNamePtr;

    extern const char* G_DefaultGrpFile(void);
    extern const char* G_GrpFile(void);

    extern const char* G_DefaultConFile(void);
    extern const char* G_ConFile(void);

    extern GrowArray<char*> g_scriptModules;

    extern void G_AddCon(const char* buffer);
    extern void G_AddConModule(const char* buffer);

    extern void clearGrpNamePtr(void);
    extern void clearScriptNamePtr(void);

    extern int loaddefinitions_game(const char* fn, int32_t preload);

    extern void G_AddSearchPaths(void);
    extern void G_CleanupSearchPaths(void);

    extern int32_t g_groupFileHandle;

    //////////

    extern void G_InitMultiPsky(int CLOUDYOCEAN__DYN, int MOONSKY1__DYN, int BIGORBIT1__DYN, int LA__DYN);
    extern void G_SetupGlobalPsky(void);

    //////////

    extern char g_modDir[BMAX_PATH];
    extern buildvfs_kfd kopen4loadfrommod(const char* filename, char searchfirst);
    extern void G_CleanupSearchPaths(void);

    extern void G_ExtPreInit(int32_t argc, char const* const* argv);
    extern void G_ExtInit(void);
    extern void G_ScanGroups(void);
    extern void G_LoadGroups(int32_t autoload);

    extern const char* G_GetInstallPath(int32_t insttype);

    //////////

    void G_LoadGroupsInDir(const char* dirname);
    void G_DoAutoload(const char* dirname);

    //////////

    extern void G_LoadLookups(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 "compat.h"
#include "renderlayer.h"
#include "_control.h"
#include "build.h"
#include "cache1d.h"
#include "keyboard.h"
#include "control.h"
#include "exhumed.h"
#include "typedefs.h"
#include "scriplib.h"
#include "view.h"

#include "config.h"

#include <string>
//#include <io.h>
#include <stdio.h>
#include <time.h>

const char gamefunctions[kMaxGameFunctions][kMaxGameFuncLen] =
{
    "Move_Forward",
    "Move_Backward",
    "Turn_Left",
    "Turn_Right",
    "Strafe",
    "Strafe_Left",
    "Strafe_Right",
    "Run",
    "Jump",
    "Crouch",
    "Fire",
    "Open",
    "Look_Up",
    "Look_Down",
    "Look_Straight",
    "Aim_Up",
    "Aim_Down",
    "SendMessage",
    "Weapon_1",
    "Weapon_2",
    "Weapon_3",
    "Weapon_4",
    "Weapon_5",
    "Weapon_6",
    "Weapon_7",
    "Pause",
    "Map",
    "Zoom_In",
    "Zoom_Out",
    "Gamma_Correction",
    "Escape",
    "Shrink_Screen",
    "Enlarge_Screen",
    "Inventory",
    "Inventory_Left",
    "Inventory_Right",
    "Show_Console",
    "Mouse_Aiming",
    "Toggle_Crosshair",
    "Next_Weapon",
    "Previous_Weapon",
    "AutoRun",
    "Map_Follow_Mode",
	"Third_Person_View",
};

const char keydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen] =
{
    "W", "Kpad8",
    "S", "Kpad2",
    "Left", "Kpad4",
    "Right", "KPad6",
    "LAlt", "RAlt",
    "A", "",
    "D", "",
    "LShift", "RShift",
    "Space", "",
    "LCtrl", "",
    "RCtrl", "",
    "E", "",
    "PgUp", "",
    "PgDn", "",
    "Home", "",
    "Insert", "",
    "Delete", "",
    "T", "",
    "1", "",
    "2", "",
    "3", "",
    "4", "",
    "5", "",
    "6", "",
    "7", "",
    "Pause", "",
    "Tab", "",
    "=", "",
    "-", "",
    "F11", "",
    "Escape", "",
    "Kpad-", "",
    "Kpad+", "",
    "Enter", "",
    "[", "",
    "]", "",
    "`", "",
    "U", "",
    "I", "",
    "'", "",
    ";", "",
    "CapLck", "",
    "F", "",
	"F7", "",
};

const char oldkeydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen] =
{
    "Up", "Kpad8",
    "Down", "Kpad2",
    "Left", "Kpad4",
    "Right", "KPad6",
    "LAlt", "RAlt",
    ",", "",
    ".", "",
    "LShift", "RShift",
    "A", "",
    "Z", "",
    "LCtrl", "RCtrl",
    "Space", "",
    "PgUp", "",
    "PgDn", "",
    "Home", "",
    "Insert", "",
    "Delete", "",
    "T", "",
    "1", "",
    "2", "",
    "3", "",
    "4", "",
    "5", "",
    "6", "",
    "7", "",
    "Pause", "",
    "Tab", "",
    "=", "",
    "-", "",
    "F11", "",
    "Escape", "",
    "Kpad-", "",
    "Kpad+", "",
    "Enter", "",
    "[", "",
    "]", "",
    "`", "",
    "U", "",
    "I", "",
    "'", "",
    ";", "",
    "CapLck", "",
    "F", "",
	"F7", "",
};

static const char *mousedefaults[MAXMOUSEBUTTONS] =
{
    "Fire",
    "Strafe",
    "Move_Forward"
    "",
    "",
    "",
};

static const char *mouseclickeddefaults[MAXMOUSEBUTTONS] =
{
};

static const char* mouseanalogdefaults[MAXMOUSEAXES] =
{
    "analog_strafing",
    "analog_moving",
};

static const char* mousedigitaldefaults[MAXMOUSEDIGITAL] =
{
};

ud_setup_t gSetup;

char setupfilename[BMAX_PATH] = {kSetupFilename};

int lMouseSens = 32;
unsigned int dword_1B82E0 = 0;

int32_t FXVolume;
int32_t MusicVolume;
int32_t SoundToggle;
int32_t MusicToggle;
int32_t MixRate;
int32_t MidiPort;
int32_t NumVoices;
int32_t NumChannels;
int32_t NumBits;
int32_t ReverseStereo;
int32_t MusicDevice;
int32_t FXDevice;
int32_t ControllerType;

int32_t gShowCrosshair;

int32_t scripthandle;
int32_t setupread;
// TODO: implement precaching toggle
int32_t useprecache;
int32_t MouseDeadZone, MouseBias;

// JBF 20031211: Store the input settings because
// (currently) mact can't regurgitate them
int32_t MouseFunctions[MAXMOUSEBUTTONS][2];
int32_t MouseDigitalFunctions[MAXMOUSEAXES][2];
int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2];
int32_t JoystickDigitalFunctions[MAXJOYAXES][2];
int32_t JoystickAnalogueAxes[MAXJOYAXES];
int32_t JoystickAnalogueScale[MAXJOYAXES];
int32_t JoystickAnalogueInvert[MAXJOYAXES];
int32_t JoystickAnalogueDead[MAXJOYAXES];
int32_t JoystickAnalogueSaturate[MAXJOYAXES];
uint8_t KeyboardKeys[kMaxGameFunctions][2];

int32_t MAXCACHE1DSIZE = (96*1024*1024);

void SetupGameButtons()
{
    CONTROL_DefineFlag(gamefunc_Move_Forward,           kFalse);
    CONTROL_DefineFlag(gamefunc_Move_Backward,          kFalse);
    CONTROL_DefineFlag(gamefunc_Turn_Left,              kFalse);
    CONTROL_DefineFlag(gamefunc_Turn_Right,             kFalse);
    CONTROL_DefineFlag(gamefunc_Strafe,                 kFalse);
    CONTROL_DefineFlag(gamefunc_Strafe_Left,            kFalse);
    CONTROL_DefineFlag(gamefunc_Strafe_Right,           kFalse);
    CONTROL_DefineFlag(gamefunc_Jump,                   kFalse);
    CONTROL_DefineFlag(gamefunc_Crouch,                 kFalse);
    CONTROL_DefineFlag(gamefunc_Fire,                   kFalse);
    CONTROL_DefineFlag(gamefunc_Open,                   kFalse);
    CONTROL_DefineFlag(gamefunc_Aim_Up,                 kFalse);
    CONTROL_DefineFlag(gamefunc_Aim_Down,               kFalse);
    CONTROL_DefineFlag(gamefunc_Look_Up,                kFalse);
    CONTROL_DefineFlag(gamefunc_Look_Down,              kFalse);
    CONTROL_DefineFlag(gamefunc_Look_Straight,          kFalse);
    CONTROL_DefineFlag(gamefunc_Run,                    kFalse);
    CONTROL_DefineFlag(gamefunc_SendMessage,            kFalse);
    CONTROL_DefineFlag(gamefunc_Weapon_1,               kFalse);
    CONTROL_DefineFlag(gamefunc_Weapon_2,               kFalse);
    CONTROL_DefineFlag(gamefunc_Weapon_3,               kFalse);
    CONTROL_DefineFlag(gamefunc_Weapon_4,               kFalse);
    CONTROL_DefineFlag(gamefunc_Weapon_5,               kFalse);
    CONTROL_DefineFlag(gamefunc_Weapon_6,               kFalse);
    CONTROL_DefineFlag(gamefunc_Weapon_7,               kFalse);
    CONTROL_DefineFlag(gamefunc_Pause,                  kFalse);
    CONTROL_DefineFlag(gamefunc_Map,                    kFalse);
    CONTROL_DefineFlag(gamefunc_Gamma_Correction,       kFalse);
    CONTROL_DefineFlag(gamefunc_Escape,                 kFalse);
    CONTROL_DefineFlag(gamefunc_Shrink_Screen,          kFalse);
    CONTROL_DefineFlag(gamefunc_Enlarge_Screen,         kFalse);
    CONTROL_DefineFlag(gamefunc_Zoom_In,                kFalse);
    CONTROL_DefineFlag(gamefunc_Zoom_Out,               kFalse);
    CONTROL_DefineFlag(gamefunc_Inventory_Left,         kFalse);
    CONTROL_DefineFlag(gamefunc_Inventory_Right,        kFalse);
//  CONTROL_DefineFlag(gamefunc_Mouseview,              kFalse);
    CONTROL_DefineFlag(gamefunc_Inventory,              kFalse);
    CONTROL_DefineFlag(gamefunc_Mouse_Aiming,           kFalse);
    CONTROL_DefineFlag(gamefunc_Toggle_Crosshair,       kFalse);
    CONTROL_DefineFlag(gamefunc_Next_Weapon,            kFalse);
    CONTROL_DefineFlag(gamefunc_Previous_Weapon,        kFalse);
    CONTROL_DefineFlag(gamefunc_AutoRun,                kFalse);
    CONTROL_DefineFlag(gamefunc_Map_Follow_Mode,        kFalse);
    CONTROL_DefineFlag(gamefunc_Third_Person_View,      kFalse);
}

hashtable_t h_gamefuncs    = { kMaxGameFunctions<<1, NULL };

int32_t CONFIG_FunctionNameToNum(const char *func)
{
    if (!func)
        return -1;

    return hash_findcase(&h_gamefuncs, func);
}

static char const * CONFIG_FunctionNumToName(int32_t func)
{
    if ((unsigned)func >= (unsigned)kMaxGameFunctions)
        return "";
    return gamefunctions[func];
}

int32_t CONFIG_AnalogNameToNum(const char *func)
{
    if (!func)
        return -1;
    else if (!Bstrcasecmp(func, "analog_turning"))
        return analog_turning;
    else if (!Bstrcasecmp(func, "analog_strafing"))
        return analog_strafing;
    else if (!Bstrcasecmp(func, "analog_moving"))
        return analog_moving;
    else if (!Bstrcasecmp(func, "analog_lookingupanddown"))
        return analog_lookingupanddown;
    else
        return -1;
}

static char const * CONFIG_AnalogNumToName(int32_t func)
{
    switch (func)
    {
    case analog_turning:
        return "analog_turning";
    case analog_strafing:
        return "analog_strafing";
    case analog_moving:
        return "analog_moving";
    case analog_lookingupanddown:
        return "analog_lookingupanddown";
    }

    return "";
}

static void CONFIG_SetJoystickButtonFunction(int i, int j, int function)
{
    JoystickFunctions[i][j] = function;
    CONTROL_MapButton(function, i, j, controldevice_joystick);
}
static void CONFIG_SetJoystickAnalogAxisScale(int i, int scale)
{
    JoystickAnalogueScale[i] = scale;
    CONTROL_SetAnalogAxisScale(i, scale, controldevice_joystick);
}
static void CONFIG_SetJoystickAnalogAxisInvert(int i, int invert)
{
    JoystickAnalogueInvert[i] = invert;
    CONTROL_SetAnalogAxisInvert(i, invert);
}
static void CONFIG_SetJoystickAnalogAxisDeadSaturate(int i, int dead, int saturate)
{
    JoystickAnalogueDead[i] = dead;
    JoystickAnalogueSaturate[i] = saturate;
    JOYSTICK_SetDeadZone(i, dead, saturate);
}
static void CONFIG_SetJoystickDigitalAxisFunction(int i, int j, int function)
{
    JoystickDigitalFunctions[i][j] = function;
    CONTROL_MapDigitalAxis(i, function, j);
}
static void CONFIG_SetJoystickAnalogAxisFunction(int i, int function)
{
    JoystickAnalogueAxes[i] = function;
    CONTROL_MapAnalogAxis(i, function);
}

void CONFIG_SetDefaultKeys(const char (*keyptr)[kMaxGameFuncLen], bool lazy/*=false*/)
{
    static char const s_gamefunc_[] = "gamefunc_";
    int constexpr strlen_gamefunc_  = ARRAY_SIZE(s_gamefunc_) - 1;

    if (!lazy)
    {
        Bmemset(KeyboardKeys, 0xff, sizeof(KeyboardKeys));
        CONTROL_ClearAllBinds();
    }

    for (int i=0; i < ARRAY_SSIZE(gamefunctions); ++i)
    {
        if (gamefunctions[i][0] == '\0')
            continue;

        auto &key = KeyboardKeys[i];

        int const default0 = KB_StringToScanCode(keyptr[i<<1]);
        int const default1 = KB_StringToScanCode(keyptr[(i<<1)+1]);

        // skip the function if the default key is already used
        // or the function is assigned to another key
        if (lazy && (key[0] != 0xff || (CONTROL_KeyIsBound(default0) && Bstrlen(CONTROL_KeyBinds[default0].cmdstr) > strlen_gamefunc_
                        && CONFIG_FunctionNameToNum(CONTROL_KeyBinds[default0].cmdstr + strlen_gamefunc_) >= 0)))
        {
#if 0 // defined(DEBUGGINGAIDS)
            if (key[0] != 0xff)
                initprintf("Skipping %s bound to %s\n", keyptr[i<<1], CONTROL_KeyBinds[default0].cmdstr);
#endif
            continue;
        }

        key[0] = default0;
        key[1] = default1;

        if (key[0])
            CONTROL_FreeKeyBind(key[0]);

        if (key[1])
            CONTROL_FreeKeyBind(key[1]);

        if (i == gamefunc_Show_Console)
            OSD_CaptureKey(key[0]);
        else
            CONFIG_MapKey(i, key[0], 0, key[1], 0);
    }
}

void CONFIG_SetDefaults()
{
    scripthandle = -1;
# if defined RENDERTYPESDL && SDL_MAJOR_VERSION > 1
    uint32_t inited = SDL_WasInit(SDL_INIT_VIDEO);
    if (inited == 0)
        SDL_Init(SDL_INIT_VIDEO);
    else if (!(inited & SDL_INIT_VIDEO))
        SDL_InitSubSystem(SDL_INIT_VIDEO);

    SDL_DisplayMode dm;
    if (SDL_GetDesktopDisplayMode(0, &dm) == 0)
    {
        gSetup.xdim = dm.w;
        gSetup.ydim = dm.h;
    }
    else
# endif
    {
        gSetup.xdim = 1024;
        gSetup.ydim = 768;
    }

#ifdef USE_OPENGL
    gSetup.bpp = 32;
#else
    gSetup.bpp = 8;
#endif

    // currently settings.cfg is only read after the startup window launches the game,
    // and rereading binds might be fickle so just enable this
    gSetup.usejoystick = 1;

    gSetup.forcesetup = 1;
    gSetup.noautoload = 1;
    gSetup.fullscreen = 1;
    gSetup.usemouse = 1;

	SoundToggle = 1;
    MixRate = 44100;
    FXVolume = 125;
    MusicToggle = 1;
    MusicVolume = 125;
    NumChannels = 2;
    NumBits = 16;
    NumVoices = 32;

    mouseaiming = 0;
    aimmode = 1;
    mouseflip = 1;
    runkey_mode = 0;
    auto_run = 1;

    gFov = 90;
    gShowCrosshair = 0;

    screensize = 0;
    nGamma = 2;

    CONFIG_SetDefaultKeys(keydefaults);

    memset(MouseFunctions, -1, sizeof(MouseFunctions));
    memset(MouseDigitalFunctions, -1, sizeof(MouseDigitalFunctions));
    memset(JoystickFunctions, -1, sizeof(JoystickFunctions));
    memset(JoystickDigitalFunctions, -1, sizeof(JoystickDigitalFunctions));

    CONTROL_MouseSensitivity = DEFAULTMOUSESENSITIVITY;

    for (int i=0; i<MAXMOUSEBUTTONS; i++)
    {
        MouseFunctions[i][0] = CONFIG_FunctionNameToNum(mousedefaults[i]);
        CONTROL_MapButton(MouseFunctions[i][0], i, 0, controldevice_mouse);
        if (i>=4) continue;
        MouseFunctions[i][1] = CONFIG_FunctionNameToNum(mouseclickeddefaults[i]);
        CONTROL_MapButton(MouseFunctions[i][1], i, 1, controldevice_mouse);
    }

    for (int i=0; i<MAXMOUSEAXES; i++)
    {
        MouseDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i*2]);
        MouseDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i*2+1]);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][0], 0);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][1], 1);
    }

    // TODO:
    //CONFIG_SetGameControllerDefaultsStandard();

#if 0
    FXVolume       = 128;
    MusicVolume    = 128;
    ReverseStereo  = 0;
    ControllerType = controltype_keyboardandmouse;
    lMouseSens     = 8;
#endif
}

int CONFIG_ReadSetup()
{
    //char tempbuf[1024];

    CONTROL_ClearAssignments();
    CONFIG_SetDefaults();

    setupread = 1;
    pathsearchmode = 1;

    if (scripthandle < 0)
    {
        if (buildvfs_exists(setupfilename))  // JBF 20031211
            scripthandle = SCRIPT_Load(setupfilename);
        else if (buildvfs_exists(kSetupFilename))
        {
            int const i = wm_ynbox("Import Configuration Settings",
                "The configuration file \"%s\" was not found. "
                "Import configuration data from \"%s\"?",
                setupfilename, kSetupFilename);
            if (i)
                scripthandle = SCRIPT_Load(kSetupFilename);
        }
    }

    pathsearchmode = 0;

    if (scripthandle < 0)
        return -1;

    SCRIPT_GetNumber(scripthandle, "Setup", "ForceSetup", &gSetup.forcesetup);
    SCRIPT_GetNumber(scripthandle, "Setup", "NoAutoLoad", &gSetup.noautoload);

    int32_t cachesize;
    SCRIPT_GetNumber(scripthandle, "Setup", "CacheSize", &cachesize);

    if (cachesize > MAXCACHE1DSIZE)
        MAXCACHE1DSIZE = cachesize;

    if (g_noSetup == 0 && g_modDir[0] == '/')
    {
        SCRIPT_GetString(scripthandle, "Setup","ModDir",&g_modDir[0]);

        if (!buildvfs_isdir(g_modDir))
        {
            initprintf("Invalid mod dir in cfg!\n");
            Bsprintf(g_modDir,"/");
        }
    }

    SCRIPT_GetNumber(scripthandle, "Screen Setup", "MaxRefreshFreq", &maxrefreshfreq);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenBPP", &gSetup.bpp);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenHeight", &gSetup.ydim);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenMode", &gSetup.fullscreen);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenWidth", &gSetup.xdim);

    vec2_t windowPos;
    if (!SCRIPT_GetNumber(scripthandle, "Screen Setup", "WindowPosX", &windowPos.x)
        && !SCRIPT_GetNumber(scripthandle, "Screen Setup", "WindowPosY", &windowPos.y))
    {
        g_windowPos = windowPos;
        g_windowPosValid = true;
    }

    SCRIPT_GetNumber(scripthandle, "Screen Setup", "FullScreen", &bFullScreen);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenSize", &screensize);
    SCRIPT_GetNumber(scripthandle, "Screen Setup", "Gamma", &nGamma);

    if (screensize < 0 || screensize > 15) {
        screensize = 0;
    }

    if (gSetup.bpp < 8) gSetup.bpp = 32;

    setupread = 1;
    return 0;
}

// wrapper for CONTROL_MapKey(), generates key bindings to reflect changes to keyboard setup
void CONFIG_MapKey(int which, kb_scancode key1, kb_scancode oldkey1, kb_scancode key2, kb_scancode oldkey2)
{
    char tempbuf[256];
    int const keys[] = { key1, key2, oldkey1, oldkey2 };
    char buf[2*kMaxGameFuncLen];

    if (which == gamefunc_Show_Console)
        OSD_CaptureKey(key1);

    for (int k = 0; (unsigned)k < ARRAY_SIZE(keys); k++)
    {
        if (keys[k] == 0xff || !keys[k])
            continue;

        int match = 0;

        for (; match < ARRAY_SSIZE(sctokeylut); ++match)
        {
            if (keys[k] == sctokeylut[match].sc)
                break;
        }

        tempbuf[0] = 0;

        for (int i=kMaxGameFunctions-1; i>=0; i--)
        {
            if (KeyboardKeys[i][0] == keys[k] || KeyboardKeys[i][1] == keys[k])
            {
                Bsprintf(buf, "gamefunc_%s; ", CONFIG_FunctionNumToName(i));
                Bstrcat(tempbuf,buf);
            }
        }

        int const len = Bstrlen(tempbuf);

        if (len >= 2)
        {
            tempbuf[len-2] = 0;  // cut off the trailing "; "
            CONTROL_BindKey(keys[k], tempbuf, 1, sctokeylut[match].key ? sctokeylut[match].key : "<?>");
        }
        else
        {
            CONTROL_FreeKeyBind(keys[k]);
        }
    }
}

void CONFIG_SetupMouse(void)
{
    if (scripthandle < 0)
        return;

    char str[80];
    char temp[80];

    for (int i=0; i<MAXMOUSEBUTTONS; i++)
    {
        Bsprintf(str,"MouseButton%d",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle,"Controls", str,temp))
            MouseFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str,"MouseButtonClicked%d",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle,"Controls", str,temp))
            MouseFunctions[i][1] = CONFIG_FunctionNameToNum(temp);
    }

    // map over the axes
    for (int i=0; i<MAXMOUSEAXES; i++)
    {
        Bsprintf(str,"MouseDigitalAxes%d_0",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str,temp))
            MouseDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str,"MouseDigitalAxes%d_1",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str,temp))
            MouseDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(temp);
    }

    for (int i=0; i<MAXMOUSEBUTTONS; i++)
    {
        CONTROL_MapButton(MouseFunctions[i][0], i, 0, controldevice_mouse);
        CONTROL_MapButton(MouseFunctions[i][1], i, 1,  controldevice_mouse);
    }
    for (int i=0; i<MAXMOUSEAXES; i++)
    {
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][0], 0);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][1], 1);
    }
}

void CONFIG_SetupJoystick(void)
{
    int32_t i;
    char str[80];
    char temp[80];
    int32_t scale;

    if (scripthandle < 0) return;

    for (i=0; i<MAXJOYBUTTONSANDHATS; i++)
    {
        Bsprintf(str,"ControllerButton%d",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle,"Controls", str,temp))
            JoystickFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str,"ControllerButtonClicked%d",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle,"Controls", str,temp))
            JoystickFunctions[i][1] = CONFIG_FunctionNameToNum(temp);
    }

    // map over the axes
    for (i=0; i<MAXJOYAXES; i++)
    {
        Bsprintf(str,"ControllerAnalogAxes%d",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str,temp))
            JoystickAnalogueAxes[i] = CONFIG_AnalogNameToNum(temp);

        Bsprintf(str,"ControllerDigitalAxes%d_0",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str,temp))
            JoystickDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str,"ControllerDigitalAxes%d_1",i);
        temp[0] = 0;
        if (!SCRIPT_GetString(scripthandle, "Controls", str,temp))
            JoystickDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(temp);

        Bsprintf(str,"ControllerAnalogScale%d",i);
        scale = JoystickAnalogueScale[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str,&scale);
        JoystickAnalogueScale[i] = scale;

        Bsprintf(str,"ControllerAnalogInvert%d",i);
        scale = JoystickAnalogueInvert[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str,&scale);
        JoystickAnalogueInvert[i] = scale;

        Bsprintf(str,"ControllerAnalogDead%d",i);
        scale = JoystickAnalogueDead[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str,&scale);
        JoystickAnalogueDead[i] = scale;

        Bsprintf(str,"ControllerAnalogSaturate%d",i);
        scale = JoystickAnalogueSaturate[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str,&scale);
        JoystickAnalogueSaturate[i] = scale;
    }

    for (i=0; i<MAXJOYBUTTONSANDHATS; i++)
    {
        CONTROL_MapButton(JoystickFunctions[i][0], i, 0, controldevice_joystick);
        CONTROL_MapButton(JoystickFunctions[i][1], i, 1,  controldevice_joystick);
    }
    for (i=0; i<MAXJOYAXES; i++)
    {
        CONTROL_MapAnalogAxis(i, JoystickAnalogueAxes[i]);
        CONTROL_MapDigitalAxis(i, JoystickDigitalFunctions[i][0], 0);
        CONTROL_MapDigitalAxis(i, JoystickDigitalFunctions[i][1], 1);
        CONTROL_SetAnalogAxisScale(i, JoystickAnalogueScale[i], controldevice_joystick);
        CONTROL_SetAnalogAxisInvert(i, JoystickAnalogueInvert[i]);
    }
}

void SetupInput()
{
    if (CONTROL_Startup(controltype_keyboardandmouse, &BGetTime, kTimerTicks))
    {
        ERRprintf("There was an error initializing the CONTROL system.\n");
        engineUnInit();
        Bexit(5);
    }
    SetupGameButtons();
    CONFIG_SetupMouse();
    CONFIG_SetupJoystick();

    CONTROL_JoystickEnabled = (gSetup.usejoystick && CONTROL_JoyPresent);
    CONTROL_MouseEnabled    = (gSetup.usemouse && CONTROL_MousePresent);

    // JBF 20040215: evil and nasty place to do this, but joysticks are evil and nasty too
    for (int i=0; i<joystick.numAxes; i++)
        JOYSTICK_SetDeadZone(i,JoystickAnalogueDead[i],JoystickAnalogueSaturate[i]);
}

void CONFIG_WriteSettings(void) // save binds and aliases to <cfgname>_settings.cfg
{
    char filename[BMAX_PATH];
	/*
    if (!Bstrcmp(setupfilename, kSetupFilename))
        Bsprintf(filename, "settings.cfg");
    else
        Bsprintf(filename, "%s_settings.cfg", strtok(setupfilename, "."));
	*/

    Bsprintf(filename, "pcexhumed_cvars.cfg");

    buildvfs_FILE fp = buildvfs_fopen_write(filename);

    if (fp)
    {
        buildvfs_fputstr(fp, "// this file is automatically generated by ");
        buildvfs_fputstrptr(fp, AppProperName);
        buildvfs_fputstr(fp,"\nunbindall\n");

        for (int i=0; i<MAXBOUNDKEYS+MAXMOUSEBUTTONS; i++)
        {
            if (CONTROL_KeyIsBound(i))
            {
                buildvfs_fputstr(fp, "bind \"");
                buildvfs_fputstrptr(fp, CONTROL_KeyBinds[i].key);
                if (CONTROL_KeyBinds[i].repeat)
                    buildvfs_fputstr(fp, "\" \"");
                else
                    buildvfs_fputstr(fp, "\" norepeat \"");
                buildvfs_fputstrptr(fp, CONTROL_KeyBinds[i].cmdstr);
                buildvfs_fputstr(fp, "\"\n");
            }
        }

        for (int i=0; i<kMaxGameFunctions; ++i)
        {
            char const * name = CONFIG_FunctionNumToName(i);
            if (name && name[0] != '\0' && (KeyboardKeys[i][0] == 0xff || !KeyboardKeys[i][0]))
            {
                buildvfs_fputstr(fp, "unbound ");
                buildvfs_fputstrptr(fp, name);
                buildvfs_fputstr(fp, "\n");
            }
        }

        OSD_WriteAliases(fp);

        //if (g_crosshairSum != -1 && g_crosshairSum != DefaultCrosshairColors.r+(DefaultCrosshairColors.g<<8)+(DefaultCrosshairColors.b<<16))
        //{
        //    buildvfs_fputstr(fp, "crosshaircolor ");
        //    char buf[64];
        //    snprintf(buf, sizeof(buf), "%d %d %d\n", CrosshairColors.r, CrosshairColors.g, CrosshairColors.b);
        //    buildvfs_fputstrptr(fp, buf);
        //}

        OSD_WriteCvars(fp);

        buildvfs_fclose(fp);

        OSD_Printf("Wrote %s\n", filename);

        return;
    }

    OSD_Printf("Error writing %s: %s\n", filename, strerror(errno));
}

void CONFIG_WriteSetup(uint32_t flags)
{
    char buf[256];
    if (!setupread) return;

    if (scripthandle < 0)
        scripthandle = SCRIPT_Init(setupfilename);

    SCRIPT_PutNumber(scripthandle, "Setup", "CacheSize", MAXCACHE1DSIZE, FALSE, FALSE);
    //SCRIPT_PutNumber(scripthandle, "Setup", "ConfigVersion", BYTEVERSION_EDUKE32, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Setup", "ForceSetup", gSetup.forcesetup, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Setup", "NoAutoLoad", gSetup.noautoload, FALSE, FALSE);

//#ifdef POLYMER
//    SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "Polymer", glrendmode == REND_POLYMER, FALSE, FALSE);
//#endif

    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenBPP", gSetup.bpp, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenHeight", gSetup.ydim, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenMode", gSetup.fullscreen, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenWidth", gSetup.xdim, FALSE, FALSE);

    //if (g_grpNamePtr && !g_addonNum)
    //    SCRIPT_PutString(ud.config.scripthandle, "Setup", "SelectedGRP", g_grpNamePtr);

#ifdef STARTUP_SETUP_WINDOW
    if (g_noSetup == 0)
        SCRIPT_PutString(scripthandle, "Setup", "ModDir", &g_modDir[0]);
#endif
    // exit early after only updating the values that can be changed from the startup window
    if (flags & 1)
    {
        SCRIPT_Save(scripthandle, setupfilename);
        SCRIPT_Free(scripthandle);
        return;
    }

    SCRIPT_PutNumber(scripthandle, "Screen Setup", "MaxRefreshFreq", maxrefreshfreq, FALSE, FALSE);

    if (g_windowPosValid)
    {
        SCRIPT_PutNumber(scripthandle, "Screen Setup", "WindowPosX", g_windowPos.x, FALSE, FALSE);
        SCRIPT_PutNumber(scripthandle, "Screen Setup", "WindowPosY", g_windowPos.y, FALSE, FALSE);
    }

    SCRIPT_PutNumber(scripthandle, "Screen Setup", "FullScreen", bFullScreen, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "ScreenSize", screensize, FALSE, FALSE);
    SCRIPT_PutNumber(scripthandle, "Screen Setup", "Gamma", nGamma, FALSE, 2);

//#ifdef _WIN32
//    SCRIPT_PutNumber(ud.config.scripthandle, "Updates", "CheckForUpdates", ud.config.CheckForUpdates, FALSE, FALSE);
//    SCRIPT_PutNumber(ud.config.scripthandle, "Updates", "LastUpdateCheck", ud.config.LastUpdateCheck, FALSE, FALSE);
//#endif

    if (gSetup.usemouse)
    {
        for (int i=0; i<MAXMOUSEBUTTONS; i++)
        {
            if (CONFIG_FunctionNumToName(MouseFunctions[i][0]))
            {
                Bsprintf(buf, "MouseButton%d", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseFunctions[i][0]));
            }

            if (i >= (MAXMOUSEBUTTONS-2)) continue;

            if (CONFIG_FunctionNumToName(MouseFunctions[i][1]))
            {
                Bsprintf(buf, "MouseButtonClicked%d", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseFunctions[i][1]));
            }
        }

        for (int i=0; i<MAXMOUSEAXES; i++)
        {
            if (CONFIG_FunctionNumToName(MouseDigitalFunctions[i][0]))
            {
                Bsprintf(buf, "MouseDigitalAxes%d_0", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseDigitalFunctions[i][0]));
            }

            if (CONFIG_FunctionNumToName(MouseDigitalFunctions[i][1]))
            {
                Bsprintf(buf, "MouseDigitalAxes%d_1", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseDigitalFunctions[i][1]));
            }
        }
    }

    if (gSetup.usejoystick)
    {
        for (int dummy=0; dummy<MAXJOYBUTTONSANDHATS; dummy++)
        {
            if (CONFIG_FunctionNumToName(JoystickFunctions[dummy][0]))
            {
                Bsprintf(buf, "ControllerButton%d", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickFunctions[dummy][0]));
            }

            if (CONFIG_FunctionNumToName(JoystickFunctions[dummy][1]))
            {
                Bsprintf(buf, "ControllerButtonClicked%d", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickFunctions[dummy][1]));
            }
        }
        for (int dummy=0; dummy<MAXJOYAXES; dummy++)
        {
            if (CONFIG_AnalogNumToName(JoystickAnalogueAxes[dummy]))
            {
                Bsprintf(buf, "ControllerAnalogAxes%d", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_AnalogNumToName(JoystickAnalogueAxes[dummy]));
            }

            if (CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][0]))
            {
                Bsprintf(buf, "ControllerDigitalAxes%d_0", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][0]));
            }

            if (CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][1]))
            {
                Bsprintf(buf, "ControllerDigitalAxes%d_1", dummy);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(JoystickDigitalFunctions[dummy][1]));
            }

            Bsprintf(buf, "ControllerAnalogScale%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueScale[dummy], FALSE, FALSE);

            Bsprintf(buf, "ControllerAnalogInvert%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueInvert[dummy], FALSE, FALSE);

            Bsprintf(buf, "ControllerAnalogDead%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueDead[dummy], FALSE, FALSE);

            Bsprintf(buf, "ControllerAnalogSaturate%d", dummy);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, JoystickAnalogueSaturate[dummy], FALSE, FALSE);
        }
    }

    //SCRIPT_PutString(ud.config.scripthandle, "Comm Setup","PlayerName",&szPlayerName[0]);

    //SCRIPT_PutString(ud.config.scripthandle, "Comm Setup","RTSName",&ud.rtsname[0]);

 //   char commmacro[] = "CommbatMacro# ";

    //for (int dummy = 0; dummy < MAXRIDECULE; dummy++)
    //{
    //    commmacro[13] = dummy+'0';
    //    SCRIPT_PutString(ud.config.scripthandle, "Comm Setup",commmacro,&ud.ridecule[dummy][0]);
    //}

    SCRIPT_Save(scripthandle, setupfilename);

    if ((flags & 2) == 0)
        SCRIPT_Free(scripthandle);

    OSD_Printf("Wrote %s\n",setupfilename);
    CONFIG_WriteSettings();
    Bfflush(NULL);
}


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

#include "compat.h"

#include "_control.h"
#include "control.h"
#include "keyboard.h"

#define kMaxGameFuncLen     64

// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
#define MAXJOYBUTTONS 32
#define MAXJOYBUTTONSANDHATS (MAXJOYBUTTONS+4)

// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
#define MAXMOUSEAXES 2
#define MAXMOUSEDIGITAL (MAXMOUSEAXES*2)

// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
#define MAXJOYAXES 9
#define MAXJOYDIGITAL (MAXJOYAXES*2)

// default mouse scale
#define DEFAULTMOUSEANALOGUESCALE           65536

// default joystick settings
#define DEFAULTJOYSTICKANALOGUESCALE        65536
#define DEFAULTJOYSTICKANALOGUEDEAD         2000
#define DEFAULTJOYSTICKANALOGUESATURATE     9500

enum {
	gamefunc_Move_Forward,
	gamefunc_Move_Backward,
	gamefunc_Turn_Left,
	gamefunc_Turn_Right,
	gamefunc_Strafe,
	gamefunc_Strafe_Left,
	gamefunc_Strafe_Right,
	gamefunc_Run,
	gamefunc_Jump,
	gamefunc_Crouch,
	gamefunc_Fire,
	gamefunc_Open,
	gamefunc_Look_Up,
	gamefunc_Look_Down,
	gamefunc_Look_Straight,
	gamefunc_Aim_Up,
	gamefunc_Aim_Down,
	gamefunc_SendMessage,
	gamefunc_Weapon_1,
	gamefunc_Weapon_2,
	gamefunc_Weapon_3,
	gamefunc_Weapon_4,
	gamefunc_Weapon_5,
	gamefunc_Weapon_6,
	gamefunc_Weapon_7,
	gamefunc_Pause,
	gamefunc_Map,
	gamefunc_Zoom_In,
	gamefunc_Zoom_Out,
	gamefunc_Gamma_Correction,
	gamefunc_Escape,
	gamefunc_Shrink_Screen,
	gamefunc_Enlarge_Screen,
	gamefunc_Inventory,
	gamefunc_Inventory_Left,
	gamefunc_Inventory_Right,
    gamefunc_Show_Console,
	gamefunc_Mouse_Aiming,
	gamefunc_Toggle_Crosshair,
    gamefunc_Next_Weapon,
    gamefunc_Previous_Weapon,
	gamefunc_AutoRun,
    gamefunc_Map_Follow_Mode,
	gamefunc_Third_Person_View,
	kMaxGameFunctions
};

extern const char gamefunctions[kMaxGameFunctions][kMaxGameFuncLen];
extern const char keydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen];
extern const char oldkeydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen];

typedef struct {
    int32_t usejoystick;
    int32_t usemouse;
    int32_t fullscreen;
    int32_t xdim;
    int32_t ydim;
    int32_t bpp;
    int32_t forcesetup;
    int32_t noautoload;
} ud_setup_t;

#define kSetupFilename  "pcexhumed.cfg"
extern char setupfilename[BMAX_PATH];

extern hashtable_t h_gamefuncs;

void SetupInput();

void LoadConfig();
int CONFIG_ReadSetup();
void CONFIG_WriteSetup(uint32_t flags);

extern int lMouseSens;

extern ud_setup_t gSetup;
extern int32_t scripthandle;
extern int32_t setupread;
extern int32_t useprecache;
extern int32_t MouseDeadZone, MouseBias;

extern int32_t FXVolume;
extern int32_t MusicVolume;
extern int32_t SoundToggle;
extern int32_t MusicToggle;
extern int32_t MixRate;
extern int32_t MidiPort;
extern int32_t NumVoices;
extern int32_t NumChannels;
extern int32_t NumBits;
extern int32_t ReverseStereo;
extern int32_t MusicDevice;
extern int32_t FXDevice;

extern int32_t gShowCrosshair;

// JBF 20031211: Store the input settings because
// (currently) mact can't regurgitate them
extern int32_t MouseFunctions[MAXMOUSEBUTTONS][2];
extern int32_t MouseDigitalFunctions[MAXMOUSEAXES][2];
extern int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2];
extern int32_t JoystickDigitalFunctions[MAXJOYAXES][2];
extern int32_t JoystickAnalogueAxes[MAXJOYAXES];
extern int32_t JoystickAnalogueScale[MAXJOYAXES];
extern int32_t JoystickAnalogueInvert[MAXJOYAXES];
extern int32_t JoystickAnalogueDead[MAXJOYAXES];
extern int32_t JoystickAnalogueSaturate[MAXJOYAXES];
extern uint8_t KeyboardKeys[kMaxGameFunctions][2];

extern int32_t MAXCACHE1DSIZE;

void CONFIG_SetDefaultKeys(const char(*keyptr)[kMaxGameFuncLen], bool lazy=false);
void CONFIG_MapKey(int which, kb_scancode key1, kb_scancode oldkey1, kb_scancode key2, kb_scancode oldkey2);
int32_t CONFIG_FunctionNameToNum(const char* func);

#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.
*/
//-------------------------------------------------------------------------

#ifndef __engine_h__
#define __engine_h__

#include "compat.h"
#include "build.h"
#include "pragmas.h"
#include "typedefs.h"
#include "trigdat.h"
#include "osd.h"

#define kMaxTiles	6144
#define kMaxSprites 4096
#define kMaxSectors 1024
#define kMaxWalls   8192
#define kMaxTiles   6144
#define kMaxVoxels	4096

enum
{
	kStatIgnited = 404
};

#define kMaxSpritesOnscreen		1024

#define kMaxPalookups 256
#define kMaxStatus   1024
//#define MAXPSKYTILES 256

typedef spritetype SPRITE;
typedef sectortype SECTOR;
typedef walltype WALL;

static inline int Sin(int angle)
{
    return sintable[angle & kAngleMask];
}

static inline int Cos(int angle)
{
    return sintable[(angle + 512) & kAngleMask];
}

int movesprite(short spritenum, int dx, int dy, int dz, int ceildist, int flordist, unsigned int clipmask);
void overwritesprite(int thex, int they, short tilenum, signed char shade, char stat, char dapalnum);
void precache();
void resettiming();
void printext(int x, int y, const char* buffer, short tilenum);
void kensetpalette(unsigned char *vgapal);

#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 <io.h>
//#include <fcntl.h>

// static int globhiz, globloz, globhihit, globlohit;

void overwritesprite(int thex, int they, short tilenum, signed char shade, char stat, char dapalnum)
{
#if 0
    rotatesprite(thex << 16, they << 16, 0x10000, (short)((flags & 8) << 7), tilenum, shade, dapalnum,
        (char)(((flags & 1 ^ 1) << 4) + (flags & 2) + ((flags & 4) >> 2) + ((flags & 16) >> 2) ^ ((flags & 8) >> 1)),
        windowx1, windowy1, windowx2, windowy2);
#endif
    // no animation
    uint8_t animbak = picanm[tilenum].sf;
    picanm[tilenum].sf = 0;
    int offx = 0, offy = 0;
    if (stat & 1)
    {
        offx -= tilesiz[tilenum].x>>1;
        if (stat & 8)
            offx += picanm[tilenum].xofs;
        else
            offx -= picanm[tilenum].xofs;
        offy -= (tilesiz[tilenum].y>>1)+picanm[tilenum].yofs;
    }
    if (stat&8)
        offx += tilesiz[tilenum].x;
    if (stat&16)
        offy += tilesiz[tilenum].y;
    thex += offx;
    they += offy;
    rotatesprite(thex << 16, they << 16, 65536L, (stat & 8) << 7, tilenum, shade, dapalnum,
        16 + (stat & 2) + ((stat & 4) >> 2) + (((stat & 16) >> 2) ^ ((stat & 8) >> 1)),
        windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y);
    picanm[tilenum].sf = animbak;
}

void permanentwritesprite(int thex, int they, short tilenum, signed char shade, int cx1, int cy1, int cx2, int cy2, char dapalnum)
{
    rotatesprite(thex << 16, they << 16, 65536L, 0, tilenum, shade, dapalnum, 8 + 16, cx1, cy1, cx2, cy2);
}

void resettiming()
{
    numframes = 0L;
    totalclock = 0L;
// TODO	totalclocklock = 0L;
}

void kensetpalette(unsigned char *vgapal)
{
    //setbrightness(0, (char*)vgapal, 4 | 2);
    // TODO
    Bmemcpy(palette, vgapal, 768);
    for (auto &i : palette)
        i <<= 2;
    videoSetPalette(0, 0, /*4 | */2);
#if 0
    char vesapal[1024];

    for(int i = 0; i < 256; i++)
    {
        vesapal[i*4+0] = vgapal[i*3+2];
        vesapal[i*4+1] = vgapal[i*3+1];
        vesapal[i*4+2] = vgapal[i*3+0];
        vesapal[i*4+3] = 0;
    }
#ifndef __WATCOMC__
     (0L, 256L, vesapal);
#endif

#endif
}

static int32_t xdim_to_320_16(int32_t x)
{
    const int32_t screenwidth = max(scale(240 << 16, xdim, ydim), 320 << 16);

    return scale(x, screenwidth, xdim) + (160 << 16) - (screenwidth >> 1);
}

static int32_t ydim_to_200_16(int32_t y)
{
    y = scale(y, 200 << 16, ydim);
    return divscale16(y - (200 << 15), rotatesprite_yxaspect) - rotatesprite_y_offset + (200 << 15);
}

static int32_t xdim_from_320_16(int32_t x)
{
    const int32_t screenwidth = max(scale(240 << 16, xdim, ydim), 320 << 16);
    return scale(x + (screenwidth >> 1) - (160 << 16), xdim, screenwidth);
}

static int32_t ydim_from_200_16(int32_t y)
{
    y = mulscale16(y + rotatesprite_y_offset - (200 << 15), rotatesprite_yxaspect) + (200 << 15);
    return scale(y, ydim, 200 << 16);
}

void printext(int x, int y, const char *buffer, short tilenum)
{
    int i;
    unsigned char ch;

    x = xdim_to_320_16(x);
    y = ydim_to_200_16(y);

    for (i = 0; buffer[i] != 0; i++)
    {
        ch = (unsigned char)buffer[i];
        rotatesprite(x - ((ch & 15) << (3 + 16)), y - ((ch >> 4) << (3 + 16)), 65536L, 0, tilenum, 0, 0, 2 + 8 + 16 + 128, xdim_from_320_16(x), ydim_from_200_16(y),
            xdim_from_320_16(x + (8 << 16)) - 1, ydim_from_200_16(y + (8 << 16)) - 1);

        x += (8 << 16);
    }
}

void precache()
{
    int i;

    for (i = 0; i < numsectors; i++)
    {
        short j = sector[i].ceilingpicnum;
        if (waloff[j] == 0) tileLoad(j);
        if (picanm[j].sf & PICANM_ANIMTYPE_MASK)
        {
            int k;
            for (k = 1; k <= picanm[j].num; k++)
                if (waloff[j+k] == 0) tileLoad(j+k);
        }
        j = sector[i].floorpicnum;
        if (waloff[j] == 0) tileLoad(j);
        if (picanm[j].sf & PICANM_ANIMTYPE_MASK)
        {
            int k;
            for (k = 1; k <= picanm[j].num; k++)
                if (waloff[j+k] == 0) tileLoad(j+k);
        }
    }

    for (i = 0; i < numwalls; i++)
    {
        short j = wall[i].picnum;
        if (waloff[j] == 0) tileLoad(j);
        if (picanm[j].sf & PICANM_ANIMTYPE_MASK)
        {
            int k;
            for (k = 1; k <= picanm[j].num; k++)
                if (waloff[j+k] == 0) tileLoad(j+k);
        }
    }

    for (i = 0; i < kMaxSprites; i++)
    {
        if (sprite[i].statnum < kMaxStatus)
        {
            short j = sprite[i].picnum;
            if (waloff[j] == 0) tileLoad(j);
            if (picanm[j].sf & PICANM_ANIMTYPE_MASK)
            {
                int k;
                for (k = 1; k <= picanm[j].num; k++)
                    if (waloff[j+k] == 0) tileLoad(j+k);
            }
        }
    }
}