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

This file is part of NBlood.

NBlood 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 "common_game.h"

#include "actor.h"
#include "ai.h"
#include "blood.h"
#include "callback.h"
#include "config.h"
#include "db.h"
#include "dude.h"
#include "endgame.h"
#include "eventq.h"
#include "fx.h"
#include "gameutil.h"
#include "globals.h"
#include "levels.h"
#include "player.h"
#include "seq.h"
#include "sfx.h"
#include "sound.h"
#include "trig.h"
#include "triggers.h"
#include "view.h"
#ifdef NOONE_EXTENSIONS
#include "nnexts.h"
#include "aiunicult.h"
#endif

void fxFlameLick(int nSprite) // 0
{
    spritetype *pSprite = &sprite[nSprite];
    int nXSprite = pSprite->extra;
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    for (int i = 0; i < 3; i++)
    {
        int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>3;
        int nAngle = Random(2048);
        int dx = mulscale30(nDist, Cos(nAngle));
        int dy = mulscale30(nDist, Sin(nAngle));
        int x = pSprite->x + dx;
        int y = pSprite->y + dy;
        int z = bottom-Random(bottom-top);
        spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, z);
        if (pFX)
        {
            xvel[pFX->index] = xvel[nSprite] + Random2(-dx);
            yvel[pFX->index] = yvel[nSprite] + Random2(-dy);
            zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
        }
    }
    if (pXSprite->burnTime > 0)
        evPost(nSprite, 3, 5, kCallbackFXFlameLick);
}

void Remove(int nSprite) // 1
{
    spritetype *pSprite = &sprite[nSprite];
    evKill(nSprite, 3);
    if (pSprite->extra > 0)
        seqKill(3, pSprite->extra);
    sfxKill3DSound(pSprite, 0, -1);
    DeleteSprite(nSprite);
}

void FlareBurst(int nSprite) // 2
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    spritetype *pSprite = &sprite[nSprite];
    int nAngle = getangle(xvel[nSprite], yvel[nSprite]);
    int nRadius = 0x55555;
    for (int i = 0; i < 8; i++)
    {
        spritetype *pSpawn = actSpawnSprite(pSprite, 5);
        pSpawn->picnum = 2424;
        pSpawn->shade = -128;
        pSpawn->xrepeat = pSpawn->yrepeat = 32;
        pSpawn->type = kMissileFlareAlt;
        pSpawn->clipdist = 2;
        pSpawn->owner = pSprite->owner;
        int nAngle2 = (i<<11)/8;
        int dx = 0;
        int dy = mulscale30r(nRadius, Sin(nAngle2));
        int dz = mulscale30r(nRadius, -Cos(nAngle2));
        if (i&1)
        {
            dy >>= 1;
            dz >>= 1;
        }
        RotateVector(&dx, &dy, nAngle);
        xvel[pSpawn->index] += dx;
        yvel[pSpawn->index] += dy;
        zvel[pSpawn->index] += dz;
        evPost(pSpawn->index, 3, 960, kCallbackRemove);
    }
    evPost(nSprite, 3, 0, kCallbackRemove);
}

void fxFlareSpark(int nSprite) // 3
{
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pFX = gFX.fxSpawn(FX_28, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
    if (pFX)
    {
        xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
        yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
        zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
    }
    evPost(nSprite, 3, 4, kCallbackFXFlareSpark);
}

void fxFlareSparkLite(int nSprite) // 4
{
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pFX = gFX.fxSpawn(FX_28, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
    if (pFX)
    {
        xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
        yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
        zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
    }
    evPost(nSprite, 3, 12, kCallbackFXFlareSparkLite);
}

void fxZombieBloodSpurt(int nSprite) // 5
{
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    spritetype *pSprite = &sprite[nSprite];
    int nXSprite = pSprite->extra;
    dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
    XSPRITE *pXSprite = &xsprite[nXSprite];
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, top);
    if (pFX)
    {
        xvel[pFX->index] = xvel[nSprite] + Random2(0x11111);
        yvel[pFX->index] = yvel[nSprite] + Random2(0x11111);
        zvel[pFX->index] = zvel[nSprite] - 0x6aaaa;
    }
    if (pXSprite->data1 > 0)
    {
        evPost(nSprite, 3, 4, kCallbackFXZombieSpurt);
        pXSprite->data1 -= 4;
    }
    else if (pXSprite->data2 > 0)
    {
        evPost(nSprite, 3, 60, kCallbackFXZombieSpurt);
        pXSprite->data1 = 40;
        pXSprite->data2--;
    }
}

void fxBloodSpurt(int nSprite) // 6
{
    spritetype *pSprite = &sprite[nSprite];
    spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
    if (pFX)
    {
        pFX->ang = 0;
        xvel[pFX->index] = xvel[nSprite]>>8;
        yvel[pFX->index] = yvel[nSprite]>>8;
        zvel[pFX->index] = zvel[nSprite]>>8;
    }
    evPost(nSprite, 3, 6, kCallbackFXBloodSpurt);
}

void fxArcSpark(int nSprite) // 7
{
    spritetype* pSprite = &sprite[nSprite];
    spritetype* pFX = gFX.fxSpawn(FX_15, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
    if (pFX)
    {
        xvel[pFX->index] = xvel[nSprite] + Random2(0x10000);
        yvel[pFX->index] = yvel[nSprite] + Random2(0x10000);
        zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
    }
    evPost(nSprite, 3, 3, kCallbackFXArcSpark);
}

void fxDynPuff(int nSprite) // 8
{
    spritetype *pSprite = &sprite[nSprite];
    if (zvel[nSprite])
    {
        int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2;
        int x = pSprite->x + mulscale30(nDist, Cos(pSprite->ang-512));
        int y = pSprite->y + mulscale30(nDist, Sin(pSprite->ang-512));
        int z = pSprite->z;
        spritetype *pFX = gFX.fxSpawn(FX_7, pSprite->sectnum, x, y, z);
        if (pFX)
        {
            xvel[pFX->index] = xvel[nSprite];
            yvel[pFX->index] = yvel[nSprite];
            zvel[pFX->index] = zvel[nSprite];
        }
    }
    evPost(nSprite, 3, 12, kCallbackFXDynPuff);
}

void Respawn(int nSprite) // 9
{
    spritetype *pSprite = &sprite[nSprite];
    dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
    XSPRITE *pXSprite = &xsprite[pSprite->extra];

    if (pSprite->statnum != kStatRespawn && pSprite->statnum != kStatThing) {
        viewSetSystemMessage("Sprite #%d is not on Respawn or Thing list\n", nSprite);
        return;
    } else if (!(pSprite->flags & kHitagRespawn)) {
        viewSetSystemMessage("Sprite #%d does not have the respawn attribute\n", nSprite);
        return;
    }

    switch (pXSprite->respawnPending) {
        case 1: {
            int nTime = mulscale16(actGetRespawnTime(pSprite), 0x4000);
            pXSprite->respawnPending = 2;
            evPost(nSprite, 3, nTime, kCallbackRespawn);
            break;
        }
        case 2: {
            int nTime = mulscale16(actGetRespawnTime(pSprite), 0x2000);
            pXSprite->respawnPending = 3;
            evPost(nSprite, 3, nTime, kCallbackRespawn);
            break;
        }
        case 3: {
            dassert(pSprite->owner != kStatRespawn);
            dassert(pSprite->owner >= 0 && pSprite->owner < kMaxStatus);
            ChangeSpriteStat(nSprite, pSprite->owner);
            pSprite->type = pSprite->inittype;
            pSprite->owner = -1;
            pSprite->flags &= ~kHitagRespawn;
            xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
            pXSprite->respawnPending = 0;
            pXSprite->burnTime = 0;
            pXSprite->isTriggered = 0;
            if (IsDudeSprite(pSprite)) {
                if (!VanillaMode()) // remove a kill
                    gKillMgr.RemoveKill(pSprite);
                int nType = pSprite->type-kDudeBase;
                pSprite->x = baseSprite[nSprite].x;
                pSprite->y = baseSprite[nSprite].y;
                pSprite->z = baseSprite[nSprite].z;
                pSprite->cstat |= 0x1101;
                #ifdef NOONE_EXTENSIONS
                if (!gModernMap || pXSprite->sysData2 <= 0) pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4;
                else pXSprite->health = ClipRange(pXSprite->sysData2 << 4, 1, 65535);

                switch (pSprite->type) {
                    default:
                        pSprite->clipdist = getDudeInfo(nType + kDudeBase)->clipdist;
                        if (gSysRes.Lookup(getDudeInfo(nType + kDudeBase)->seqStartID, "SEQ"))
                            seqSpawn(getDudeInfo(nType + kDudeBase)->seqStartID, 3, pSprite->extra, -1);
                        break;
                    case kDudeModernCustom:
                        seqSpawn(genDudeSeqStartId(pXSprite), 3, pSprite->extra, -1);
                        break;
                }

                // return dude to the patrol state
                if (gModernMap && pXSprite->dudeFlag4) {
                    pXSprite->data3 = 0;
                    pXSprite->target = -1;
                }
                #else
                pSprite->clipdist = getDudeInfo(nType + kDudeBase)->clipdist;
                pXSprite->health = getDudeInfo(nType + kDudeBase)->startHealth << 4;
                if (gSysRes.Lookup(getDudeInfo(nType + kDudeBase)->seqStartID, "SEQ"))
                    seqSpawn(getDudeInfo(nType + kDudeBase)->seqStartID, 3, pSprite->extra, -1);
                #endif
                aiInitSprite(pSprite);
                pXSprite->key = 0;
            } else if (pSprite->type == kThingTNTBarrel) {
                pSprite->cstat |= CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN;
                pSprite->cstat &= (unsigned short)~CSTAT_SPRITE_INVISIBLE;
            }

            gFX.fxSpawn(FX_29, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
            sfxPlay3DSound(pSprite, 350, -1, 0);
            break;
        }
    }
}

void PlayerBubble(int nSprite) // 10
{
    spritetype *pSprite = &sprite[nSprite];
    if (IsPlayerSprite(pSprite))
    {
        PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
        dassert(pPlayer != NULL);
        if (!pPlayer->bubbleTime)
            return;
        int top, bottom;
        GetSpriteExtents(pSprite, &top, &bottom);
        for (int i = 0; i < (pPlayer->bubbleTime>>6); i++)
        {
            int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2;
            int nAngle = Random(2048);
            int x = pSprite->x + mulscale30(nDist, Cos(nAngle));
            int y = pSprite->y + mulscale30(nDist, Sin(nAngle));
            int z = bottom-Random(bottom-top);
            spritetype *pFX = gFX.fxSpawn((FX_ID)(FX_23+Random(3)), pSprite->sectnum, x, y, z);
            if (pFX)
            {
                xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
                yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
                zvel[pFX->index] = zvel[nSprite] + Random2(0x1aaaa);
            }
        }
        evPost(nSprite, 3, 4, kCallbackPlayerBubble);
    }
}

void EnemyBubble(int nSprite) // 11
{
    spritetype *pSprite = &sprite[nSprite];
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    for (int i = 0; i < (klabs(zvel[nSprite])>>18); i++)
    {
        int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2;
        int nAngle = Random(2048);
        int x = pSprite->x + mulscale30(nDist, Cos(nAngle));
        int y = pSprite->y + mulscale30(nDist, Sin(nAngle));
        int z = bottom-Random(bottom-top);
        spritetype *pFX = gFX.fxSpawn((FX_ID)(FX_23+Random(3)), pSprite->sectnum, x, y, z);
        if (pFX)
        {
            xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
            yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
            zvel[pFX->index] = zvel[nSprite] + Random2(0x1aaaa);
        }
    }
    evPost(nSprite, 3, 4, kCallbackEnemeyBubble);
}

void CounterCheck(int nSector) // 12
{
    dassert(nSector >= 0 && nSector < kMaxSectors);
    if (sector[nSector].type != kSectorCounter) return;
    if (sector[nSector].extra <= 0) return;

    XSECTOR *pXSector = &xsector[sector[nSector].extra];
    int nReq = pXSector->waitTimeA; int nType = pXSector->data; int nCount = 0;
    if (!nType || !nReq) return;

    for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite]) {
        if (sprite[nSprite].type == nType) nCount++;
    }

    if (nCount < nReq) {
        evPost(nSector, 6, 5, kCallbackCounterCheck);
        return;
    } else {
        //pXSector->waitTimeA = 0; //do not reset necessary objects counter to zero
        trTriggerSector(nSector, pXSector, kCmdOn, kCauserGame);
        pXSector->locked = 1; //lock sector, so it can be opened again later
    }
}

void FinishHim(int nSprite) // 13
{
    spritetype* pSprite = &sprite[nSprite];
    int nXSprite = pSprite->extra;
    XSPRITE* pXSprite = &xsprite[nXSprite];
    if (IsPlayerSprite(pSprite) && playerSeqPlaying(&gPlayer[pSprite->type - kDudePlayer1], 16) && pXSprite->target == gMe->nSprite)
        sndStartSample(3313, -1, 1, 0);
}

void fxBloodBits(int nSprite) // 14
{
    spritetype *pSprite = &sprite[nSprite];
    int ceilZ, ceilHit, floorZ, floorHit;
    GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0);
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    pSprite->z += floorZ-bottom;
    int nAngle = Random(2048);
    int nDist = Random(16)<<4;
    int x = pSprite->x+mulscale28(nDist, Cos(nAngle));
    int y = pSprite->y+mulscale28(nDist, Sin(nAngle));
    gFX.fxSpawn(FX_48, pSprite->sectnum, x, y, pSprite->z);
    if (pSprite->ang == 1024)
    {
        int nChannel = 28+(pSprite->index&2);
        dassert(nChannel < 32);
        sfxPlay3DSound(pSprite, 385, nChannel, 1);
    }
    if (Chance(0x5000))
    {
        spritetype *pFX = gFX.fxSpawn(FX_36, pSprite->sectnum, x, y, floorZ-64);
        if (pFX)
            pFX->ang = nAngle;
    }
    gFX.fxFree(nSprite);
}

void fxTeslaAlt(int nSprite) // 15
{
    spritetype* pSprite = &sprite[nSprite];
    spritetype* pFX = gFX.fxSpawn(FX_49, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
    if (pFX)
    {
        xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
        yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
        zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
    }
    evPost(nSprite, 3, 3, kCallbackFXTeslaAlt);
}

int tommySleeveSnd[] = { 608, 609, 611 }; // unused?
int sawedOffSleeveSnd[] = { 610, 612 };

void fxBouncingSleeve(int nSprite) // 16
{
    spritetype* pSprite = &sprite[nSprite]; int ceilZ, ceilHit, floorZ, floorHit;
    GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0);
    int top, bottom; GetSpriteExtents(pSprite, &top, &bottom);
    pSprite->z += floorZ - bottom;

    int zv = zvel[nSprite] - velFloor[pSprite->sectnum];

    if (zvel[nSprite] == 0) sleeveStopBouncing(pSprite);
    else if (zv > 0) {
        actFloorBounceVector((int*)& xvel[nSprite], (int*)& yvel[nSprite], &zv, pSprite->sectnum, 0x9000);
        zvel[nSprite] = zv;
        if (velFloor[pSprite->sectnum] == 0 && klabs(zvel[nSprite]) < 0x20000)  {
            sleeveStopBouncing(pSprite);
            return;
        }

        int nChannel = 28 + (pSprite->index & 2);
        dassert(nChannel < 32);

        // tommy sleeve
        if (pSprite->type >= FX_37 && pSprite->type <= FX_39) {
            Random(3);
            sfxPlay3DSound(pSprite, 608 + Random(2), nChannel, 1);

        // sawed-off sleeve
        } else {
            sfxPlay3DSound(pSprite, sawedOffSleeveSnd[Random(2)], nChannel, 1);
        }
    }

}

void sleeveStopBouncing(spritetype* pSprite) {
    xvel[pSprite->index] = yvel[pSprite->index] = zvel[pSprite->index] = 0;
    if (pSprite->extra > 0) seqKill(3, pSprite->extra);
    sfxKill3DSound(pSprite, -1, -1);

    switch (pSprite->type) {
    case FX_37:
    case FX_38:
    case FX_39:
        pSprite->picnum = 2465;
        break;
    case FX_40:
    case FX_41:
    case FX_42:
        pSprite->picnum = 2464;
        break;
    }

    pSprite->type = FX_51; // static spent casing
    pSprite->xrepeat = pSprite->yrepeat = 10;
}

void returnFlagToBase(int nSprite) // 17
{
    spritetype* pSprite = &sprite[nSprite];
    if (pSprite->owner >= 0 && pSprite->owner < kMaxSprites)
    {
        spritetype* pOwner = &sprite[pSprite->owner];
        XSPRITE* pXOwner = &xsprite[pOwner->extra];
        switch (pSprite->type) {
            case kItemFlagA:
                trTriggerSprite(pOwner->index, pXOwner, kCmdOn, pOwner->index);
                sndStartSample(8003, 255, 2, 0);
                gBlueFlagDropped = false;
                viewSetMessage("Blue Flag returned to base.");
                break;
            case kItemFlagB:
                trTriggerSprite(pOwner->index, pXOwner, kCmdOn, pOwner->index);
                sndStartSample(8002, 255, 2, 0);
                gRedFlagDropped = false;
                viewSetMessage("Red Flag returned to base.");
                break;
        }
    }
    evPost(pSprite->index, 3, 0, kCallbackRemove);
}

void fxPodBloodSpray(int nSprite) // 18
{
    spritetype* pSprite = &sprite[nSprite];
    spritetype* pFX;
    if (pSprite->type == 53)
        pFX = gFX.fxSpawn(FX_53, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
    else
        pFX = gFX.fxSpawn(FX_54, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z);
    if (pFX)
    {
        pFX->ang = 0;
        xvel[pFX->index] = xvel[nSprite] >> 8;
        yvel[pFX->index] = yvel[nSprite] >> 8;
        zvel[pFX->index] = zvel[nSprite] >> 8;
    }
    evPost(nSprite, 3, 6, kCallbackFXPodBloodSpray);
}

void fxPodBloodSplat(int nSprite) // 19
{
    spritetype *pSprite = &sprite[nSprite];
    int ceilZ, ceilHit, floorZ, floorHit;
    GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0);
    int top, bottom;
    GetSpriteExtents(pSprite, &top, &bottom);
    pSprite->z += floorZ-bottom;
    int nAngle = Random(2048);
    int nDist = Random(16)<<4;
    int x = pSprite->x+mulscale28(nDist, Cos(nAngle));
    int y = pSprite->y+mulscale28(nDist, Sin(nAngle));
    if (pSprite->ang == 1024 && pSprite->type == 53)
    {
        int nChannel = 28+(pSprite->index&2);
        dassert(nChannel < 32);
        sfxPlay3DSound(pSprite, 385, nChannel, 1);
    }
    spritetype *pFX = NULL;
    if (pSprite->type == 53 || pSprite->type == kThingPodGreenBall)
    {
        if (Chance(0x500) || pSprite->type == kThingPodGreenBall)
            pFX = gFX.fxSpawn(FX_55, pSprite->sectnum, x, y, floorZ-64);
        if (pFX)
            pFX->ang = nAngle;
    }
    else
    {
        pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, floorZ-64);
        if (pFX)
            pFX->ang = nAngle;
    }
    gFX.fxFree(nSprite);
}

void LeechStateTimer(int nSprite) // 20
{
    spritetype *pSprite = &sprite[nSprite];
    if (pSprite->statnum == kStatThing && !(pSprite->flags & 32)) {
        switch (pSprite->type) {
            case kThingDroppedLifeLeech:
            #ifdef NOONE_EXTENSIONS
            case kModernThingEnemyLifeLeech:
            #endif
                xsprite[pSprite->extra].stateTimer = 0;
                break;
        }
    }
}

void sub_76A08(spritetype *pSprite, spritetype *pSprite2, PLAYER *pPlayer) // ???
{
    int top, bottom;
    int nSprite = pSprite->index;
    GetSpriteExtents(pSprite, &top, &bottom);
    pSprite->x = pSprite2->x;
    pSprite->y = pSprite2->y;
    pSprite->z = sector[pSprite2->sectnum].floorz-(bottom-pSprite->z);
    pSprite->ang = pSprite2->ang;
    ChangeSpriteSect(nSprite, pSprite2->sectnum);
    sfxPlay3DSound(pSprite2, 201, -1, 0);
    xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
    viewBackupSpriteLoc(nSprite, pSprite);
    if (pPlayer)
    {
        playerResetInertia(pPlayer);
        pPlayer->zViewVel = pPlayer->zWeaponVel = 0;
    }
}

void DropVoodoo(int nSprite) // unused
{
    spritetype *pSprite = &sprite[nSprite];
    int nOwner = actSpriteOwnerToSpriteId(pSprite);
    if (nOwner < 0 || nOwner >= kMaxSprites)
    {
        evPost(nSprite, 3, 0, kCallbackRemove);
        return;
    }
    spritetype *pOwner = &sprite[nOwner];
    PLAYER *pPlayer;
    if (IsPlayerSprite(pOwner))
        pPlayer = &gPlayer[pOwner->type-kDudePlayer1];
    else
        pPlayer = NULL;
    if (!pPlayer)
    {
        evPost(nSprite, 3, 0, kCallbackRemove);
        return;
    }
    pSprite->ang = getangle(pOwner->x-pSprite->x, pOwner->y-pSprite->y);
    int nXSprite = pSprite->extra;
    if (nXSprite > 0)
    {
        XSPRITE *pXSprite = &xsprite[nXSprite];
        if (pXSprite->data1 == 0)
        {
            evPost(nSprite, 3, 0, kCallbackRemove);
            return;
        }
        int nSprite2, nNextSprite;
        for (nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nNextSprite)
        {
            nNextSprite = nextspritestat[nSprite2];
            if (nOwner == nSprite2)
                continue;
            spritetype *pSprite2 = &sprite[nSprite2];
            int nXSprite2 = pSprite2->extra;
            if (nXSprite2 > 0 && nXSprite2 < kMaxXSprites)
            {
                XSPRITE *pXSprite2 = &xsprite[nXSprite2];
                PLAYER *pPlayer2;
                if (IsPlayerSprite(pSprite2))
                    pPlayer2 = &gPlayer[pSprite2->type-kDudePlayer1];
                else
                    pPlayer2 = NULL;
                if (pXSprite2->health > 0 && (pPlayer2 || pXSprite2->key == 0))
                {
                    if (pPlayer2)
                    {
                        if (gGameOptions.nGameType == kGameTypeCoop)
                            continue;
                        if (gGameOptions.nGameType == kGameTypeTeams && pPlayer->teamId == pPlayer2->teamId)
                            continue;
                        int t = 0x8000/ClipLow(gNetPlayers-1, 1);
                        if (!powerupCheck(pPlayer2, kPwUpDeathMask))
                            t += ((3200-pPlayer2->armor[2])<<15)/3200;
                        if (Chance(t) || nNextSprite < 0)
                        {
                            int nDmg = actDamageSprite(nOwner, pSprite2, kDamageSpirit, pXSprite->data1<<4);
                            pXSprite->data1 = ClipLow(pXSprite->data1-nDmg, 0);
                            sub_76A08(pSprite2, pSprite, pPlayer2);
                            evPost(nSprite, 3, 0, kCallbackRemove);
                            return;
                        }
                    }
                    else
                    {
                        int vd = 0x2666;
                        switch (pSprite2->type)
                        {
                        case kDudeBoneEel:
                        case kDudeBat:
                        case kDudeRat:
                        case kDudeTinyCaleb:
                        case kDudeBeast:
                            vd = 0x147;
                            break;
                        case kDudeZombieAxeBuried:
                        case kDudePodGreen:
                        case kDudeTentacleGreen:
                        case kDudePodFire:
                        case kDudeTentacleFire:
                        case kDudePodMother:
                        case kDudeTentacleMother:
                        case kDudeCerberusTwoHead:
                        case kDudeCerberusOneHead:
                        case kDudeTchernobog:
                        case kDudeBurningInnocent:
                        case kDudeBurningCultist:
                        case kDudeBurningZombieAxe:
                        case kDudeBurningZombieButcher:
                        case kDudeCultistReserved:
                        case kDudeZombieAxeLaying:
                        case kDudeInnocent:
                        case kDudeBurningTinyCaleb:
                        case kDudeBurningBeast:
                            vd = 0;
                            break;
                        }
                        if (vd && (Chance(vd) || nNextSprite < 0))
                        {
                            sub_76A08(pSprite2, pSprite, NULL);
                            evPost(nSprite, 3, 0, kCallbackRemove);
                            return;
                        }
                    }
                }
            }
        }
        pXSprite->data1 = ClipLow(pXSprite->data1-1, 0);
        evPost(nSprite, 3, 0, kCallbackRemove);
    }
}

void(*gCallback[kCallbackMax])(int) =
{
    fxFlameLick,
    Remove,
    FlareBurst,
    fxFlareSpark,
    fxFlareSparkLite,
    fxZombieBloodSpurt,
    fxBloodSpurt,
    fxArcSpark,
    fxDynPuff,
    Respawn,
    PlayerBubble,
    EnemyBubble,
    CounterCheck,
    FinishHim,
    fxBloodBits,
    fxTeslaAlt,
    fxBouncingSleeve,
    returnFlagToBase,
    fxPodBloodSpray,
    fxPodBloodSplat,
    LeechStateTimer,
    DropVoodoo, // unused
    #ifdef NOONE_EXTENSIONS
    callbackUniMissileBurst, // the code is in nnexts.cpp
    callbackMakeMissileBlocking, // the code is in nnexts.cpp
    callbackGenDudeUpdate, // the code is in nnexts.cpp
    #endif
};


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

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
#pragma once
void sleeveStopBouncing(spritetype* pSprite);

enum CALLBACK_ID {
    kCallbackNone = -1,
    kCallbackFXFlameLick = 0,
    kCallbackRemove = 1,
    kCallbackFXFlareBurst = 2,
    kCallbackFXFlareSpark = 3,
    kCallbackFXFlareSparkLite = 4,
    kCallbackFXZombieSpurt = 5,
    kCallbackFXBloodSpurt = 6,
    kCallbackFXArcSpark = 7,
    kCallbackFXDynPuff = 8,
    kCallbackRespawn = 9,
    kCallbackPlayerBubble = 10,
    kCallbackEnemeyBubble = 11,
    kCallbackCounterCheck = 12,
    kCallbackFinishHim = 13,
    kCallbackFXBloodBits = 14,
    kCallbackFXTeslaAlt = 15,
    kCallbackFXBouncingSleeve = 16,
    kCallbackReturnFlag = 17,
    kCallbackFXPodBloodSpray = 18,
    kCallbackFXPodBloodSplat = 19,
    kCallbackLeechStateTimer = 20,
    kCallbackDropVoodoo = 21, // unused
    #ifdef NOONE_EXTENSIONS
    kCallbackMissileBurst = 22,
    kCallbackMissileSpriteBlock = 23,
    kCallbackGenDudeUpdate      = 24,
    #endif
    kCallbackMax,
};

extern void (*gCallback[kCallbackMax])(int);

repo_name: orochi/nblood

license: mit


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

This file is part of NBlood.

NBlood 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 "common_game.h"
#include "blood.h"
#include "choke.h"
#include "globals.h"
#include "levels.h"
#include "player.h"
#include "qav.h"
#include "resource.h"

CChoke gChoke;

void CChoke::Init(char *pzFile, int _x, int _y, void (*pCallback)(PLAYER *))
{
    x = _x;
    y = _y;
    Process = pCallback;
    if (!hQav && pzFile)
    {
        hQav = gSysRes.Lookup(pzFile, "QAV");
        if (!hQav)
            ThrowError("Could not load QAV %s\n", pzFile);
        pQav = (QAV*)gSysRes.Lock(hQav);
        pQav->nSprite = -1;
        pQav->x = x;
        pQav->y = y;
        pQav->Preload();
        chokeTimeInit();
    }
}

void CChoke::Init(int qavId, void (*pCallback)(PLAYER *))
{
    Process = pCallback;
    if (!hQav && qavId != -1)
    {
        hQav = gSysRes.Lookup(qavId, "QAV");
        if (!hQav)
            ThrowError("Could not load QAV %d\n", qavId);
        pQav = (QAV*)gSysRes.Lock(hQav);
        pQav->nSprite = -1;
        pQav->x = x;
        pQav->y = y;
        pQav->Preload();
        chokeTimeInit();
    }
}

void CChoke::Init(char *pzFile, void (*pCallback)(PLAYER *))
{
    Process = pCallback;
    if (!hQav && pzFile)
    {
        hQav = gSysRes.Lookup(pzFile, "QAV");
        if (!hQav)
            ThrowError("Could not load QAV %s\n", pzFile);
        pQav = (QAV*)gSysRes.Lock(hQav);
        pQav->nSprite = -1;
        pQav->x = x;
        pQav->y = y;
        pQav->Preload();
        chokeTimeInit();
    }
}

void CChoke::Draw(int x, int y)
{
    if (!hQav)
        return;
    const ClockTicks bakClock = gFrameClock;
    gFrameClock = totalclock;
    pQav->x = x;
    pQav->y = y;
    int diff = (int)totalclock-clock;
    clock = (int)totalclock;
    duration -= diff;
    if (duration <= 0 || duration > pQav->at10)
        duration = pQav->at10;
    int vdi = pQav->at10-duration;
    pQav->Play(vdi-diff, vdi, -1, NULL);
    const int win1x = windowxy1.x;
    const int win1y = windowxy1.y;
    const int win2x = windowxy2.x;
    const int win2y = windowxy2.y;
    windowxy1.x = 0;
    windowxy1.y = 0;
    windowxy2.x = xdim-1;
    windowxy2.y = ydim-1;
    pQav->Draw(vdi, 10, 0, 0);
    windowxy1.x = win1x;
    windowxy1.y = win1y;
    windowxy2.x = win2x;
    windowxy2.y = win2y;
    gFrameClock = bakClock;
}

void CChoke::chokeTimeInit()
{
    duration = pQav->at10;
    clock = (int)totalclock;
}


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

This file is part of NBlood.

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

#include "common_game.h"
#include "player.h"
#include "qav.h"
#include "resource.h"

class CChoke
{
public:
    CChoke()
    {
        hQav = NULL;
        pQav = NULL;
        duration = 0;
        clock = 0;
        x = 0;
        y = 0;
        Process = NULL;
    };
    void Init(char *pzFile, int _x, int _y, void (*pCallback)(PLAYER *));
    void Init(int qavId, void (*pCallback)(PLAYER *));
    void Init(char *pzFile, void (*pCallback)(PLAYER *));
    void Draw(int x, int y);
    DICTNODE *hQav;
    QAV *pQav;
    int duration;
    int clock;
    int x;
    int y;
    void (*Process)(PLAYER *);
private:
    void chokeTimeInit(void);
};

extern CChoke gChoke;


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

This file is part of NBlood.

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

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

#include "compat.h"
#include "build.h"
#include "baselayer.h"
#include "palette.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"

// 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 "nblood.pk3";
}

const char *G_DefaultDefFile(void)
{
    return "blood.def";
}

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;
}

void G_SetupGlobalPsky(void)
{
    int skyIdx = 0;

    // NOTE: Loop must be running backwards for the same behavior as the game
    // (greatest sector index with matching parallaxed sky takes precedence).
    for (bssize_t i = numsectors - 1; i >= 0; i--)
    {
        if (sector[i].ceilingstat & 1)
        {
            skyIdx = getpskyidx(sector[i].ceilingpicnum);
            if (skyIdx > 0)
                break;
        }
    }

    g_pskyidx = skyIdx;
}

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);
    //chdir(g_rootDir);
#else
    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

    if (getcwd(cwd,BMAX_PATH) && Bstrcmp(cwd,"/") != 0)
        addsearchpath(cwd);

    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)
    if (!access("user_profiles_enabled", F_OK))
#else
    if (g_useCwd == 0 && access("user_profiles_disabled", F_OK))
#endif
    {
        char *homedir;
        int32_t asperr;

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

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;
}

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 (getcwd(cwd, BMAX_PATH))
        {
            Bsnprintf(path, sizeof(path), "%s/%s", cwd, g_modDir);
            if (!Bstrcmp(g_rootDir, path))
            {
                if (addsearchpath(path) == -2)
                    if (Bmkdir(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 = G_GrpFile();
    G_TryLoadingGrp(grpfile);

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

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

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

    if (g_defNamePtr == NULL)
    {
        const char *tmpptr = getenv("BLOODDEF");
        if (tmpptr)
        {
            clearDefNamePtr();
            g_defNamePtr = dup_filename(tmpptr);
            initprintf("Using \"%s\" as definitions file\n", g_defNamePtr);
        }
    }

    loaddefinitions_game(BLOODWIDESCREENDEF, TRUE);
    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 Blood_Add_GOG_OUWB_Linux(const char * path)
{
    char buf[BMAX_PATH];

    Bsnprintf(buf, sizeof(buf), "%s/data", path);
    addsearchpath(buf);
}
#endif

#if defined EDUKE32_OSX || defined __linux__ || defined EDUKE32_BSD
static void Blood_AddSteamPaths(const char *basepath)
{
    char buf[BMAX_PATH];

    // Blood: Fresh Supply - Steam
    Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Blood", basepath);
    addsearchpath(buf);

    // Blood: One Unit Whole Blood - Steam
    Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/One Unit Whole Blood", basepath);
    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();

    Bsnprintf(buf, sizeof(buf), "%s/.steam/steam", homepath);
    Blood_AddSteamPaths(buf);

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

    // Blood: One Unit Whole Blood - GOG.com
    Bsnprintf(buf, sizeof(buf), "%s/GOG Games/Blood One Unit Whole Blood", homepath);
    Blood_Add_GOG_OUWB_Linux(buf);
    Paths_ParseXDGDesktopFilesFromGOG(homepath, "Blood_One_Unit_Whole_Blood", Blood_Add_GOG_OUWB_Linux);

    Xfree(homepath);

    addsearchpath("/usr/share/games/nblood");
    addsearchpath("/usr/local/share/games/nblood");
#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) };

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

        Bsnprintf(buf, sizeof(buf), "%s/Steam/steamapps/libraryfolders.vdf", support[i]);
        Paths_ParseSteamLibraryVDF(buf, Blood_AddSteamPaths);
    }

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

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

    // Blood: One Unit Whole Blood - Steam
    bufsize = sizeof(buf);
    if (!found && Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 299030)", "InstallLocation", buf, &bufsize))
    {
        addsearchpath(buf);
        found = true;
    }

    // Blood: One Unit Whole Blood - GOG.com
    bufsize = sizeof(buf);
    if (!found && Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\1207658856)", "path", buf, &bufsize))
    {
        addsearchpath(buf);
        found = true;
    }
    bufsize = sizeof(buf);
    if (!found && Paths_ReadRegistryValue("SOFTWARE\\GOG.com\\GOGONEUNITONEBLOOD", "PATH", buf, &bufsize))
    {
        addsearchpath(buf);
        found = true;
    }

    // Blood: Fresh Supply - Steam
    bufsize = sizeof(buf);
    if (!found && Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 1010750)", "InstallLocation", buf, &bufsize))
    {
        char * const suffix = buf + bufsize - 1;
        DWORD const remaining = sizeof(buf) - bufsize;

        addsearchpath(buf);
        strncpy(suffix, "/addons/Cryptic Passage", remaining);
        addsearchpath(buf);
        found = true;
    }

    // Blood: Fresh Supply - GOG.com
    bufsize = sizeof(buf);
    if (!found && Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\1374469660)", "path", buf, &bufsize))
    {
        char * const suffix = buf + bufsize - 1;
        DWORD const remaining = sizeof(buf) - bufsize;

        addsearchpath(buf);
        strncpy(suffix, "/addons/Cryptic Passage", remaining);
        addsearchpath(buf);
        found = true;
    }
#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:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
#pragma once
#include "baselayer.h"
#include "build.h"
#include "cache1d.h"
#include "common.h"
#include "pragmas.h"
#include "misc.h"
#include "network.h"

extern int g_useCwd;
#ifndef APPNAME
#define APPNAME "NBlood"
#endif

#ifndef APPBASENAME
#define APPBASENAME "nblood"
#endif

#define BLOODWIDESCREENDEF "blood_widescreen.def"

#define BYTEVERSION 104
#define EXEVERSION 101

void _SetErrorLoc(const char *pzFile, int nLine);
void _ThrowError(const char *pzFormat, ...);
void __dassert(const char *pzExpr, const char *pzFile, int nLine);
void QuitGame(void);
void _consoleSysMsg(const char* pMessage, ...);

#define ThrowError(...) \
	{ \
		_SetErrorLoc(__FILE__,__LINE__); \
		_ThrowError(__VA_ARGS__); \
	}

// print error to console only
#define consoleSysMsg(...) \
	{ \
		_SetErrorLoc(__FILE__,__LINE__); \
		_consoleSysMsg(__VA_ARGS__); \
	}

#define dassert(x) if (!(x)) __dassert(#x,__FILE__,__LINE__)

#define kMaxSectors MAXSECTORS
#define kMaxWalls MAXWALLS
#define kMaxSprites MAXSPRITES

#define kMaxTiles MAXTILES
#define kMaxStatus MAXSTATUS
#define kMaxPlayers 8
#define kMaxViewSprites maxspritesonscreen

#define kMaxVoxels MAXVOXELS

#define kTicRate 120
#define kTicsPerFrame 4
#define kTicsPerSec (kTicRate/kTicsPerFrame)

#define TILTBUFFER 4078

#define kExplodeMax 8

#define kLensSize 80
#define kViewEffectMax 20

#define kNoTile -1

// defined by NoOne:
// -------------------------------

#define kMaxPAL 5
#define kUserPLUStart  15

#define kDmgFall 0
#define kDmgBurn 1
#define kDmgBullet 2
#define kDmgExplode 3
#define kDmgChoke 4
#define kDmgSpirit 5
#define kDmgElectric 6
#define kDmgMax 7

#define kCauserGame (kMaxSprites - 1)

// GAMETYPES //////////////////////////////////////////////////
enum {
kGameTypeSinglePlayer           = 0,
kGameTypeCoop                   = 1,
kGameTypeBloodBath              = 2,
kGameTypeTeams                  = 3,
};

// GAMEFLAGS //////////////////////////////////////////////////
enum {
kGameFlagNone                   =  0,
kGameFlagContinuing             =  1 << 0,
kGameFlagEnding                 =  1 << 1,
kGameFlagPlayIntro              =  1 << 2,
kGameFlagPlayOutro              =  1 << 3,
};

// MEDIUM /////////////////////////////////////////////////////
enum {
kMediumNormal                   = 0,
kMediumWater                    = 1,
kMediumGoo                      = 2,
};

// STATNUMS /////////////////////////////////////////////////////
enum {
kStatNothing                    = -1,
kStatDecoration                 = 0,
kStatFX                         = 1,
kStatExplosion                  = 2,
kStatItem                       = 3,
kStatThing                      = 4,
kStatProjectile                 = 5,
kStatDude                       = 6,
kStatInactive                   = 7, // inactive (ambush) dudes
kStatRespawn                    = 8,
kStatPurge                      = 9,
kStatMarker                     = 10,
kStatTraps                      = 11,
kStatAmbience                   = 12,
kStatSpares                     = 13,
kStatFlare                      = 14,
kStatDebris                     = 15,
kStatPathMarker                 = 16,
kStatFree                       = 1024,
};

// POWERUPS /////////////////////////////////////////////////////
enum {
kPwUpFeatherFall        = 12,
kPwUpShadowCloak        = 13,
kPwUpDeathMask          = 14,
kPwUpJumpBoots          = 15,
kPwUpTwoGuns            = 17,
kPwUpDivingSuit         = 18,
kPwUpGasMask            = 19,
kPwUpCrystalBall        = 21,
kPwUpDoppleganger       = 23,
kPwUpReflectShots       = 24,
kPwUpBeastVision        = 25,
kPwUpShadowCloakUseless = 26,
kPwUpDeliriumShroom     = 28,
kPwUpGrowShroom         = 29,
kPwUpShrinkShroom       = 30,
kPwUpDeathMaskUseless   = 31,
kPwUpAsbestArmor        = 39,
kMaxPowerUps            = 51,
};

enum {
    kExplosionSmall = 0,
    kExplosionStandard = 1,
    kExplosionLarge = 2,
    kExplosionFireball = 3,
    kExplosionSpray = 4,
    kExplosion5 = 5,
    kExplosion6 = 6,
    kExplosionNapalm = 7,
    kExplosionMax = 8
};

// SPRITE TYPES /////////////////////////////////////////////////
enum {
    kSpriteDecoration = 0,

    // markers
    kMarkerSPStart = 1,
    kMarkerMPStart = 2,
    kMarkerOff = 3,
    kMarkerOn = 4,
    kMarkerAxis = 5,
    kMarkerLowLink = 6,
    kMarkerUpLink = 7,
    kMarkerWarpDest = 8,
    kMarkerUpWater = 9,
    kMarkerLowWater = 10,
    kMarkerUpStack = 11,
    kMarkerLowStack = 12,
    kMarkerUpGoo = 13,
    kMarkerLowGoo = 14,
    kMarkerPath = 15,
    kMarkerDudeSpawn = 18,
    kMarkerEarthQuake = 19,

    // switches
    kSwitchBase = 20,
    kSwitchToggle = 20,
    kSwitchOneWay = 21,
    kSwitchCombo = 22,
    kSwitchPadlock = 23,
    kSwitchMax = 24,

    // decorations
    kDecorationTorch = 30,
    kDecorationCandle = 32,

    // (weapons)
    kItemWeaponBase = 40,
    kItemWeaponRandom = kItemWeaponBase,
    kItemWeaponSawedoff = 41,
    kItemWeaponTommygun = 42,
    kItemWeaponFlarePistol = 43,
    kItemWeaponVoodooDoll = 44,
    kItemWeaponTeslaCannon = 45,
    kItemWeaponNapalmLauncher = 46,
    kItemWeaponPitchfork = 47,
    kItemWeaponSprayCan = 48,
    kItemWeaponTNT = 49,
    kItemWeaponLifeLeech = 50,
    kItemWeaponMax = 51,

    // items (ammos)
    kItemAmmoBase = 60,
    kItemAmmoSprayCan = kItemAmmoBase,
    kItemAmmoTNTBundle = 62,
    kItemAmmoTNTBox = 63,
    kItemAmmoProxBombBundle = 64,
    kItemAmmoRemoteBombBundle = 65,
    kItemAmmoTrappedSoul = 66,
    kItemAmmoSawedoffFew = 67,
    kItemAmmoSawedoffBox = 68,
    kItemAmmoTommygunFew = 69,
    kItemAmmoVoodooDoll = 70,
    kItemAmmoTommygunDrum = 72,
    kItemAmmoTeslaCharge = 73,
    kItemAmmoFlares = 76,
    kItemAmmoGasolineCan = 79,
    kItemAmmoMax = 81,

    kItemBase = 100,

    // items (keys)
    kItemKeyBase = kItemBase,
    kItemKeySkull = kItemKeyBase,
    kItemKeyEye = 101,
    kItemKeyFire = 102,
    kItemKeyDagger = 103,
    kItemKeySpider = 104,
    kItemKeyMoon = 105,
    kItemKeyKey7 = 106,
    kItemKeyMax = 107,

    // items (health)
    kItemHealthDoctorBag = 107,
    kItemHealthMedPouch = 108,
    kItemHealthLifeEssense = 109,
    kItemHealthLifeSeed = 110,
    kItemHealthRedPotion = 111,

    // items (misc)
    kItemFeatherFall = 112,
    kItemShadowCloak = 113, // ltdInvisibility
    kItemDeathMask = 114, // invulnerability
    kItemJumpBoots = 115,
    kItemTwoGuns = 117,
    kItemDivingSuit = 118,
    kItemGasMask = 119,
    kItemCrystalBall = 121,
    kItemReflectShots = 124,
    kItemBeastVision = 125,
    kItemShroomDelirium = 128,

    kItemArmorAsbest = 139,
    kItemArmorBasic = 140,
    kItemArmorBody = 141,
    kItemArmorFire = 142,
    kItemArmorSpirit = 143,
    kItemArmorSuper = 144,

    kItemFlagABase = 145,
    kItemFlagBBase = 146,
    kItemFlagA = 147,
    kItemFlagB = 148,
    kItemMax = 151,

    // dudes
    kDudeBase = 200,
    kDudeCultistTommy = 201,
    kDudeCultistShotgun = 202,
    kDudeZombieAxeNormal = 203,
    kDudeZombieButcher = 204,
    kDudeZombieAxeBuried = 205,
    kDudeGargoyleFlesh = 206,
    kDudeGargoyleStone = 207,
    kDudeGargoyleStatueFlesh = 208,
    kDudeGargoyleStatueStone = 209,
    kDudePhantasm = 210,
    kDudeHellHound = 211,
    kDudeHand = 212,
    kDudeSpiderBrown = 213,
    kDudeSpiderRed = 214,
    kDudeSpiderBlack = 215,
    kDudeSpiderMother = 216,
    kDudeGillBeast = 217,
    kDudeBoneEel = 218,
    kDudeBat = 219,
    kDudeRat = 220,
    kDudePodGreen = 221,
    kDudeTentacleGreen = 222,
    kDudePodFire = 223,
    kDudeTentacleFire = 224,
    kDudePodMother = 225,
    kDudeTentacleMother = 226,
    kDudeCerberusTwoHead = 227,
    kDudeCerberusOneHead = 228,
    kDudeTchernobog = 229,
    kDudeCultistTommyProne = 230,
    kDudePlayer1 = 231,
    kDudePlayer2 = 232,
    kDudePlayer3 = 233,
    kDudePlayer4 = 234,
    kDudePlayer5 = 235,
    kDudePlayer6 = 236,
    kDudePlayer7 = 237,
    kDudePlayer8 = 238,
    kDudeBurningInnocent = 239,
    kDudeBurningCultist = 240,
    kDudeBurningZombieAxe = 241,
    kDudeBurningZombieButcher = 242,
    kDudeCultistReserved = 243, // unused
    kDudeZombieAxeLaying = 244,
    kDudeInnocent = 245,
    kDudeCultistShotgunProne = 246,
    kDudeCultistTesla = 247,
    kDudeCultistTNT = 248,
    kDudeCultistBeast = 249,
    kDudeTinyCaleb = 250,
    kDudeBeast = 251,
    kDudeBurningTinyCaleb = 252,
    kDudeBurningBeast = 253,
    kDudeVanillaMax = 254,
    kDudeMax = 256,

    kMissileBase = 300,
    kMissileButcherKnife = kMissileBase,
    kMissileFlareRegular = 301,
    kMissileTeslaAlt = 302,
    kMissileFlareAlt = 303,
    kMissileFlameSpray = 304,
    kMissileFireball = 305,
    kMissileTeslaRegular = 306,
    kMissileEctoSkull = 307,
    kMissileFlameHound = 308,
    kMissilePukeGreen = 309,
    kMissileUnused = 310,
    kMissileArcGargoyle = 311,
    kMissileFireballNapalm = 312,
    kMissileFireballCerberus = 313,
    kMissileFireballTchernobog = 314,
    kMissileLifeLeechRegular = 315,
    kMissileLifeLeechAltNormal = 316,
    kMissileLifeLeechAltSmall = 317,
    kMissileMax = 318,

    // things
    kThingBase = 400,
    kThingTNTBarrel = 400,
    kThingArmedProxBomb = 401,
    kThingArmedRemoteBomb = 402,
    kThingCrateFace = 405,
    kThingGlassWindow = 406,
    kThingFluorescent = 407,
    kThingWallCrack = 408,
    kThingSpiderWeb = 410,
    kThingMetalGrate = 411,
    kThingFlammableTree = 412,
    kTrapMachinegun = 413, // not really a thing, should be in traps instead
    kThingFallingRock = 414,
    kThingKickablePail = 415,
    kThingObjectGib = 416,
    kThingObjectExplode = 417,
    kThingArmedTNTStick = 418,
    kThingArmedTNTBundle = 419,
    kThingArmedSpray = 420,
    kThingBone = 421,
    kThingDripWater = 423,
    kThingDripBlood = 424,
    kThingBloodBits = 425,
    kThingBloodChunks = 426,
    kThingZombieHead = 427,
    kThingNapalmBall = 428,
    kThingPodFireBall = 429,
    kThingPodGreenBall = 430,
    kThingDroppedLifeLeech = 431,
    kThingVoodooHead = 432, // unused
    kThingMax = 436,

    // traps
    kTrapFlame = 452,
    kTrapSawCircular = 454,
    kTrapZapSwitchable = 456,
    kTrapExploder = 459,

    // generators
    kGenTrigger = 700,
    kGenDripWater = 701,
    kGenDripBlood = 702,
    kGenMissileFireball = 703,
    kGenMissileEctoSkull = 704,
    kGenDart = 705,
    kGenBubble = 706,
    kGenBubbleMulti = 707,

    // sound sprites
    kGenSound = 708,
    kSoundSector = 709,
    kSoundPlayer = 711,
};

// WALL TYPES /////////////////////////////////////////////////
enum {
    kWallBase = 500,
    kWallStack = 501,
    kWallGib = 511,
    kWallMax = 512,
};

// SECTOR TYPES /////////////////////////////////////////////////
enum {
    kSectorBase = 600,
    kSectorZMotion = 600,
    kSectorZMotionSprite = 602,
    kSectorTeleport = 604,
    kSectorPath = 612,
    kSectorRotateStep = 613,
    kSectorSlideMarked = 614,
    kSectorRotateMarked = 615,
    kSectorSlide = 616,
    kSectorRotate = 617,
    kSectorDamage = 618,
    kSectorCounter = 619,
    kSectorMax = 620,
};

// ai state types
enum {
kAiStateOther           = -1,
kAiStateIdle            =  0,
kAiStateGenIdle         =  1,
kAiStateMove            =  2,
kAiStateSearch          =  3,
kAiStateChase           =  4,
kAiStateRecoil          =  5,
kAiStateAttack          =  6,
#ifdef NOONE_EXTENSIONS
kAiStatePatrolBase      =  7,
kAiStatePatrolWaitL     =  kAiStatePatrolBase,
kAiStatePatrolWaitC,
kAiStatePatrolWaitW,
kAiStatePatrolMoveL,
kAiStatePatrolMoveC,
kAiStatePatrolMoveW,
kAiStatePatrolTurnL,
kAiStatePatrolTurnC,
kAiStatePatrolTurnW,
kAiStatePatrolMax,
#endif
};

// sprite attributes
#define kHitagAutoAim 0x0008
#define kHitagRespawn 0x0010
#define kHitagFree 0x0020
#define kHitagSmoke 0x0100

// sprite physics attributes
#define kPhysMove 0x0001 // affected by movement physics
#define kPhysGravity 0x0002 // affected by gravity
#define kPhysFalling 0x0004 // currently in z-motion

// sector cstat
#define kSecCParallax 0x01
#define kSecCSloped 0x02
#define kSecCSwapXY 0x04
#define kSecCExpand 0x08
#define kSecCFlipX 0x10
#define kSecCFlipY 0x20
#define kSecCFlipMask 0x34
#define kSecCRelAlign 0x40
#define kSecCFloorShade 0x8000

#define kAng5 28
#define kAng15 85
#define kAng30 170
#define kAng45 256
#define kAng60 341
#define kAng90 512
#define kAng120 682
#define kAng180 1024
#define kAng270 1536
#define kAng360 2048
#define kAngMask 2047

// -------------------------------

// NUKE-TODO:
#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

enum BLOOD_GLOBALFLAGS {
    BLOOD_FORCE_WIDELOADSCREEN = 1<<0,
};

enum searchpathtypes_t {
    SEARCHPATH_REMOVE = 1<<0,
};

extern char *g_grpNamePtr;

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

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

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

void G_LoadGroupsInDir(const char *dirname);
void G_DoAutoload(const char *dirname);
extern void G_LoadGroups(int32_t autoload);

extern void G_SetupGlobalPsky(void);

#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))

static inline int gameHandleEvents(void)
{
    netGetPackets();
    return handleevents();
}

#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

#pragma pack(push,1)

#if 0
struct sectortype
{
    short wallptr, wallnum;
    int ceilingz, floorz;
    unsigned short ceilingstat, floorstat;
    short ceilingpicnum, ceilingheinum;
    signed char ceilingshade;
    char ceilingpal, ceilingxpanning, ceilingypanning;
    short floorpicnum, floorheinum;
    signed char floorshade;
    char floorpal, floorxpanning, floorypanning;
    char visibility, filler;
    unsigned short lotag;
    short hitag, extra;
};

struct walltype
{
    int x, y;
    short point2, nextwall, nextsector;
    unsigned short cstat;
    short picnum, overpicnum;
    signed char shade;
    char pal, xrepeat, yrepeat, xpanning, ypanning;
    short lotag, hitag, extra;
};

struct spritetype
{
    int x, y, z;
    short cstat, picnum;
    signed char shade;
    char pal, clipdist, filler;
    unsigned char xrepeat, yrepeat;
    signed char xoffset, yoffset;
    short sectnum, statnum;
    short ang, owner, index, yvel, zvel;
    short type, hitag, extra;
};

struct PICANM {
    unsigned int animframes : 5;
    unsigned int at0_5 : 1;
    unsigned int animtype : 2;
    signed int xoffset : 8;
    signed int yoffset : 8;
    unsigned int animspeed : 4;
    unsigned int at3_4 : 3; // type
    unsigned int at3_7 : 1; // filler
};
#endif

struct LOCATION {
    int x, y, z;
    int ang;
};

struct POINT2D {
    int x, y;
};

struct POINT3D {
    int x, y, z;
};

struct VECTOR2D {
    int dx, dy;
};

struct Aim {
    int dx, dy, dz;
};

#pragma pack(pop)

inline int ksgnf(float f)
{
    if (f < 0)
        return -1;
    if (f > 0)
        return 1;
    return 0;
}

inline int IncBy(int a, int b)
{
    a += b;
    int q = a % b;
    a -= q;
    if (q < 0)
        a -= b;
    return a;
}

inline int DecBy(int a, int b)
{
    a--;
    int q = a % b;
    a -= q;
    if (q < 0)
        a -= b;
    return a;
}

#if 0
inline float IncByF(float a, float b)
{
    a += b;
    float q = fmod(a, b);
    a -= q;
    if (q < 0)
        a -= b;
    return a;
}

inline float DecByF(float a, float b)
{
    //a--;
    a -= fabs(b)*0.001;
    float q = fmod(a, b);
    a -= q;
    if (q < 0)
        a -= b;
    return a;
}
#endif

inline int ClipLow(int a, int b)
{
    if (a < b)
        return b;
    return a;
}

inline int ClipHigh(int a, int b)
{
    if (a >= b)
        return b;
    return a;
}

inline int ClipRange(int a, int b, int c)
{
    if (a < b)
        return b;
    if (a > c)
        return c;
    return a;
}

inline float ClipRangeF(float a, float b, float c)
{
    if (a < b)
        return b;
    if (a > c)
        return c;
    return a;
}

inline int interpolate(int a, int b, int c)
{
    return a+mulscale16(b-a,c);
}

inline int interpolateang(int a, int b, int c)
{
    return a+mulscale16(((b-a+1024)&2047)-1024, c);
}

inline fix16_t interpolateangfix16(fix16_t a, fix16_t b, int c)
{
    return a+mulscale16(((b-a+0x4000000)&0x7ffffff)-0x4000000, c);
}

inline char Chance(int a1)
{
    return wrand() < (a1>>1);
}

inline unsigned int Random(int a1)
{
    return mulscale15(wrand(), a1);
}

inline int Random2(int a1)
{
    return mulscale14(wrand(), a1)-a1;
}

inline int Random3(int a1)
{
    return mulscale15(wrand()+wrand(), a1) - a1;
}

inline unsigned int QRandom(int a1)
{
    return mulscale15(qrand(), a1);
}

inline int QRandom2(int a1)
{
    return mulscale14(qrand(), a1)-a1;
}

inline void SetBitString(char *pArray, int nIndex)
{
    pArray[nIndex>>3] |= 1<<(nIndex&7);
}

inline void ClearBitString(char *pArray, int nIndex)
{
    pArray[nIndex >> 3] &= ~(1 << (nIndex & 7));
}

inline char TestBitString(char *pArray, int nIndex)
{
    return pArray[nIndex>>3] & (1<<(nIndex&7));
}

inline int scale(int a1, int a2, int a3, int a4, int a5)
{
    return a4 + (a5-a4) * (a1-a2) / (a3-a2);
}

inline int mulscale16r(int a, int b)
{
    int64_t acc = 1<<(16-1);
    acc += ((int64_t)a) * b;
    return (int)(acc>>16);
}

inline int mulscale30r(int a, int b)
{
    int64_t acc = 1<<(30-1);
    acc += ((int64_t)a) * b;
    return (int)(acc>>30);
}

inline int dmulscale30r(int a, int b, int c, int d)
{
    int64_t acc = 1<<(30-1);
    acc += ((int64_t)a) * b;
    acc += ((int64_t)c) * d;
    return (int)(acc>>30);
}

inline int approxDist(int dx, int dy)
{
    dx = klabs(dx);
    dy = klabs(dy);
    if (dx > dy)
        dy = (3*dy)>>3;
    else
        dx = (3*dx)>>3;
    return dx+dy;
}

class Rect {
public:
    int x0, y0, x1, y1;
    Rect(int _x0, int _y0, int _x1, int _y1)
    {
        x0 = _x0; y0 = _y0; x1 = _x1; y1 = _y1;
    }
    bool isValid(void) const
    {
        return x0 < x1 && y0 < y1;
    }
    char isEmpty(void) const
    {
        return !isValid();
    }
    bool operator!(void) const
    {
        return isEmpty();
    }

    Rect & operator&=(Rect &pOther)
    {
        x0 = ClipLow(x0, pOther.x0);
        y0 = ClipLow(y0, pOther.y0);
        x1 = ClipHigh(x1, pOther.x1);
        y1 = ClipHigh(y1, pOther.y1);
        return *this;
    }

    void offset(int dx, int dy)
    {
        x0 += dx;
        y0 += dy;
        x1 += dx;
        y1 += dy;
    }

    int height()
    {
        return y1 - y0;
    }

    int width()
    {
        return x1 - x0;
    }

    bool inside(Rect& other)
    {
        return (x0 <= other.x0 && x1 >= other.x1 && y0 <= other.y0 && y1 >= other.y1);
    }

    bool inside(int x, int y)
    {
        return (x0 <= x && x1 > x && y0 <= y && y1 > y);
    }
};

class BitReader {
public:
    int nBitPos;
    int nSize;
    char *pBuffer;
    BitReader(char *_pBuffer, int _nSize, int _nBitPos) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = _nBitPos; nSize -= nBitPos>>3; }
    BitReader(char *_pBuffer, int _nSize) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = 0; }
    int readBit()
    {
        if (nSize <= 0)
            ThrowError("Buffer overflow");
        int bit = ((*pBuffer)>>nBitPos)&1;
        if (++nBitPos >= 8)
        {
            nBitPos = 0;
            pBuffer++;
            nSize--;
        }
        return bit;
    }
    void skipBits(int nBits)
    {
        nBitPos += nBits;
        pBuffer += nBitPos>>3;
        nSize -= nBitPos>>3;
        nBitPos &= 7;
        if ((nSize == 0 && nBitPos > 0) || nSize < 0)
            ThrowError("Buffer overflow");
    }
    unsigned int readUnsigned(int nBits)
    {
        unsigned int n = 0;
        dassert(nBits <= 32);
        for (int i = 0; i < nBits; i++)
            n += readBit()<<i;
        return n;
    }
    int readSigned(int nBits)
    {
        dassert(nBits <= 32);
        int n = (int)readUnsigned(nBits);
        n <<= 32-nBits;
        n >>= 32-nBits;
        return n;
    }
};

class BitWriter {
public:
    int nBitPos;
    int nSize;
    char *pBuffer;
    BitWriter(char *_pBuffer, int _nSize, int _nBitPos) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = _nBitPos; memset(pBuffer, 0, nSize); nSize -= nBitPos>>3; }
    BitWriter(char *_pBuffer, int _nSize) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = 0; memset(pBuffer, 0, nSize); }
    void writeBit(int bit)
    {
        if (nSize <= 0)
            ThrowError("Buffer overflow");
        *pBuffer |= bit<<nBitPos;
        if (++nBitPos >= 8)
        {
            nBitPos = 0;
            pBuffer++;
            nSize--;
        }
    }
    void skipBits(int nBits)
    {
        nBitPos += nBits;
        pBuffer += nBitPos>>3;
        nSize -= nBitPos>>3;
        nBitPos &= 7;
        if ((nSize == 0 && nBitPos > 0) || nSize < 0)
            ThrowError("Buffer overflow");
    }
    void write(int nValue, int nBits)
    {
        dassert(nBits <= 32);
        for (int i = 0; i < nBits; i++)
            writeBit((nValue>>i)&1);
    }
};