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 "qav.h"
#include "tile.h"
#include "sfx.h"
#include "sound.h"

#define kMaxClients 64
static void (*clientCallback[kMaxClients])(int, void *);
static int nClients;

int qavRegisterClient(void(*pClient)(int, void *))
{
    dassert(nClients < kMaxClients);
    clientCallback[nClients] = pClient;

    return nClients++;
}

static void DrawFrame(int x, int y, TILE_FRAME *pTile, int stat, int shade, int palnum, int uniqid = 0)
{
    stat |= pTile->stat;
    int angle = pTile->angle;
    int olduniqid = guniqhudid;
    if (stat & 0x100)
    {
        angle = (angle+1024)&2047;
        stat &= ~0x100;
        stat ^= 0x4;
    }

    if (stat & kQavOrientationLeft)
        stat |= 256;

    if (!(stat & kQavOrientationQ16))
    {
        x <<= 16;
        y <<= 16;
    }

    if (stat & RS_LERP)
        guniqhudid = uniqid;
    else
        guniqhudid = 0;

    stat &= ~(kQavOrientationLeft | kQavOrientationQ16);
    if (palnum <= 0)
        palnum = pTile->palnum;
    rotatesprite(x + (pTile->x << 16), y + (pTile->y << 16), pTile->z, angle,
                 pTile->picnum, ClipRange(pTile->shade + shade, -128, 127), palnum, stat,
                 windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y);
    guniqhudid = olduniqid;
}

void QAV::Draw(int ticks, int stat, int shade, int palnum, int uniqid)
{
    dassert(ticksPerFrame > 0);
    int nFrame = ticks / ticksPerFrame;
    dassert(nFrame >= 0 && nFrame < nFrames);
    FRAMEINFO *pFrame = &frames[nFrame];
    for (int i = 0; i < 8; i++)
    {
        if (pFrame->tiles[i].picnum > 0)
            DrawFrame(x, y, &pFrame->tiles[i], stat, shade, palnum, uniqid + i);
    }
}

void QAV::Play(int start, int end, int nCallback, void *pData)
{
    dassert(ticksPerFrame > 0);
    int frame;
    int ticks;
    if (start < 0)
        frame = (start + 1) / ticksPerFrame;
    else
        frame = start / ticksPerFrame + 1;

    for (ticks = ticksPerFrame * frame; ticks <= end; frame++, ticks += ticksPerFrame)
    {
        if (frame >= 0 && frame < nFrames)
        {
            FRAMEINFO *pFrame = &frames[frame];
            SOUNDINFO *pSound = &pFrame->sound;

            // by NoOne: handle Sound kill flags
            if (!VanillaMode() && pSound->sndFlags > 0 && pSound->sndFlags <= kFlagSoundKillAll) {
                for (int i = 0; i < nFrames; i++) {
                    FRAMEINFO* pFrame2 = &frames[i];
                    SOUNDINFO* pSound2 = &pFrame2->sound;
                    if (pSound2->sound != 0) {
                        if (pSound->sndFlags != kFlagSoundKillAll && pSound2->priority != pSound->priority) continue;
                        else if (nSprite >= 0) {
                            // We need stop all sounds in a range
                            for (int a = 0; a <= pSound2->sndRange; a++)
                                sfxKill3DSound(&sprite[nSprite], -1, pSound2->sound + a);
                        } else {
                            sndKillAllSounds();
                        }
                    }
                }
            }

            if (pSound->sound > 0) {
                int sound = pSound->sound;

                // by NoOne: add random rage sound feature
                if (pSound->sndRange > 0 && !VanillaMode())
                    sound += Random((pSound->sndRange == 1) ? 2 : pSound->sndRange);

                if (nSprite == -1) PlaySound(sound);
                else PlaySound3D(&sprite[nSprite], sound, 16+pSound->priority, 6);
            }

            if (pFrame->nCallbackId > 0 && nCallback != -1) {
                clientCallback[nCallback](pFrame->nCallbackId, pData);
            }
        }
    }
}

void QAV::Preload(void)
{
    for (int i = 0; i < nFrames; i++)
    {
        for (int j = 0; j < 8; j++)
        {
            if (frames[i].tiles[j].picnum >= 0)
                tilePreloadTile(frames[i].tiles[j].picnum);
        }
    }
}

void QAV::Precache(void)
{
    for (int i = 0; i < nFrames; i++)
    {
        for (int j = 0; j < 8; j++)
        {
            if (frames[i].tiles[j].picnum >= 0)
                tilePrecacheTile(frames[i].tiles[j].picnum, 0);
        }
    }
}


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 "build.h"
#include "common_game.h"
#include "blood.h"

#define kQavOrientationLeft 16384
#define kQavOrientationQ16 32768

enum weaponuniqhudid_t
{
    W_WEAPON_START = 1,
    W_WEAPON_END = W_WEAPON_START + 7,
    W_DRIP_START,
    W_DRIP_END = W_DRIP_START + 7,
    W_NNEXT_START,
    W_NNEXT_END = W_NNEXT_START + 7,

    W_END,
};

#pragma pack(push, 1)

// by NoOne: add sound flags
enum
{
    kFlagSoundKill = 0x01, // mute QAV sounds of same priority
    kFlagSoundKillAll = 0x02, //  mute all QAV sounds

};

struct TILE_FRAME
{
    int picnum;
    int x;
    int y;
    int z;
    int stat;
    signed char shade;
    char palnum;
    unsigned short angle;
};

struct SOUNDINFO
{
    int sound;
    unsigned char priority;
    unsigned char sndFlags; // (by NoOne) Various sound flags
    unsigned char sndRange; // (by NoOne) Random sound range
    char reserved[1];
};

struct FRAMEINFO
{
    int nCallbackId; // 0
    SOUNDINFO sound; // 4
    TILE_FRAME tiles[8]; // 12
};

struct QAV
{
    char pad1[8]; // 0
    int nFrames; // 8
    int ticksPerFrame; // C
    int at10; // 10
    int x; // 14
    int y; // 18
    int nSprite; // 1c
    //SPRITE *pSprite; // 1c
    char pad3[4]; // 20
    FRAMEINFO frames[1]; // 24
    void Draw(int ticks, int stat, int shade, int palnum, int uniqid = 0);
    void Play(int, int, int, void *);
    void Preload(void);
    void Precache(void);

    void PlaySound(int nSound);
    void PlaySound3D(spritetype *pSprite, int nSound, int a3, int a4);
};

#pragma pack(pop)

int qavRegisterClient(void(*pClient)(int, void *));

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

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "common_game.h"

#include "qheap.h"

void InstallFenceposts(HEAPNODE *n)
{
    char *address = (char*)n;
    memset(address + 0x20, 0xcc, 0x10);
    memset(address + n->size - 0x10, 0xcc, 0x10);
}

void CheckFenceposts(HEAPNODE *n)
{
    char *data = (char*)n + 0x20;
    for (int i = 0; i < 0x10; i++)
    {
        if (data[i] != 0xcc)
        {
            ThrowError("Block underwritten");
        }
    }
    data = (char*)n + n->size - 0x10;
    for (int i = 0; i < 0x10; i++)
    {
        if (data[i] != 0xcc)
        {
            ThrowError("Block overwritten");
        }
    }
}

QHeap::QHeap(int heapSize)
{
    dassert(heapSize > 0);
    size = heapSize;
    while (size > 0 && (heapPtr = malloc(size)) == NULL)
    {
        size -= 0x1000;
    }
    if (!heapPtr)
    {
        ThrowError("Allocation failure\n");
    }
    heap.isFree = false;
    freeHeap.isFree = false;
    HEAPNODE *node = (HEAPNODE*)(((intptr_t)heapPtr + 0xf) & ~0xf);
    heap.next = heap.prev = node;
    node->next = node->prev = &heap;
    freeHeap.freeNext = freeHeap.freePrev = node;
    node->freeNext = node->freePrev = &freeHeap;
    node->isFree = true;
    node->size = size & ~0xf;
}

QHeap::~QHeap(void)
{
    Check();
    free(heapPtr);
    heapPtr = NULL;
}

void QHeap::Check(void)
{
    HEAPNODE *node = heap.next;
    while (node != &heap)
    {
        if (!node->isFree)
        {
            CheckFenceposts(node);
        }
        node = node->next;
    }
}

void QHeap::Debug(void)
{
#if 0
    char s[4];
    FILE *f = fopen("MEMFRAG.TXT", "wt");
    if (!f)
    {
        return;
    }
    HEAPNODE *node = heap.next;
    while (node != &heap)
    {
        if (node->isFree)
        {
            fprintf(f, "%P %10d FREE", node, node->size);
        }
        else
        {
            char *data = (char*)node + 0x20;
            for (int i = 0; i < 4; i++)
            {
                if (isalpha(data[i]))
                {
                    s[i] = data[i];
                }
                else
                {
                    s[i] = '_';
                }
            }
            fprintf(f, "%P %10d %4s", node, node->size, s);
        }
    }
    fclose(f);
#endif
}

void *QHeap::Alloc(int blockSize)
{
    dassert(blockSize > 0);
    dassert(heapPtr != NULL);
    if (blockSize > 0)
    {
        blockSize = ((blockSize + 0xf) & ~0xf) + 0x40;
        HEAPNODE *freeNode = freeHeap.freeNext;
        while (freeNode != &freeHeap)
        {
            dassert(freeNode->isFree);
            if (blockSize <= freeNode->size)
            {
                if (blockSize + 0x30 <= freeNode->size)
                {
                    freeNode->size -= blockSize;
                    HEAPNODE *nextNode = (HEAPNODE *)((char*)freeNode + freeNode->size);
                    nextNode->size = blockSize;
                    nextNode->prev = freeNode;
                    nextNode->next = freeNode->next;
                    nextNode->prev->next = nextNode;
                    nextNode->next->prev = nextNode;
                    nextNode->isFree = false;
                    InstallFenceposts(nextNode);
                    return (void*)((char*)nextNode + 0x30);
                }
                else
                {
                    freeNode->freePrev->freeNext = freeNode->freeNext;
                    freeNode->freeNext->freePrev = freeNode->freePrev;
                    freeNode->isFree = false;
                    InstallFenceposts(freeNode);
                    return (void*)((char*)freeNode + 0x30);
                }
            }
            freeNode = freeNode->freeNext;
        }
    }
    return NULL;
}

int QHeap::Free(void *p)
{
    if (!p)
    {
        return 0;
    }
    dassert(heapPtr != NULL);
    HEAPNODE *node = (HEAPNODE*)((char*)p - 0x30);
    if (node->isFree)
    {
        ThrowError("Free on bad or freed block");
    }
    CheckFenceposts(node);
    if (node->prev->isFree)
    {
        node->prev->size += node->size;
        node->prev->next = node->next;
        node->next->prev = node->prev;
        node = node->prev;
    }
    else
    {
        node->freeNext = freeHeap.freeNext;
        node->freePrev = &freeHeap;
        node->freePrev->freeNext = node;
        node->freeNext->freePrev = node;
        node->isFree = true;
    }
    HEAPNODE *nextNode = node->next;
    if (nextNode->isFree)
    {
        node->size += nextNode->size;
        nextNode->freePrev->freeNext = nextNode->freeNext;
        nextNode->freeNext->freePrev = nextNode->freePrev;
        nextNode->prev->next = nextNode->next;
        nextNode->next->prev = nextNode->prev;
    }
    return node->size - 0x40;
}

#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

#ifdef USE_QHEAP

struct HEAPNODE
{
    HEAPNODE *prev;
    HEAPNODE *next;
    int size;
    bool isFree;
    HEAPNODE *freePrev;
    HEAPNODE *freeNext;
};

class QHeap
{
public:
    QHeap(int heapSize);
    ~QHeap(void);

    void Check(void);
    void Debug(void);
    void *Alloc(int);
    int Free(void *p);

    void *heapPtr;
    HEAPNODE heap;
    HEAPNODE freeHeap;
    int size;
};

#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.
*/
//-------------------------------------------------------------------------
#include "compat.h"
#include "build.h"
#include "editor.h"
#include "common_game.h"
#include "crc32.h"

#include "globals.h"
#include "tile.h"
#include "screen.h"

int qanimateoffs(int a1, int a2)
{
    int offset = 0;
    if (a1 >= 0 && a1 < kMaxTiles)
    {
        int frames = picanm[a1].num;
        if (frames > 0)
        {
            int const frameClock = (int)(editstatus ? totalclocklock : gFrameClock);
            int vd;
            if ((a2&0xc000) == 0x8000)
                vd = (Bcrc32(&a2, 2, 0)+frameClock)>>(picanm[a1].sf&PICANM_ANIMSPEED_MASK);
            else
                vd = frameClock>>(picanm[a1].sf&PICANM_ANIMSPEED_MASK);
            switch (picanm[a1].sf&PICANM_ANIMTYPE_MASK)
            {
            case PICANM_ANIMTYPE_OSC:
                offset = vd % (2*frames);
                if (offset >= frames)
                    offset = 2*frames-offset;
                break;
            case PICANM_ANIMTYPE_FWD:
                offset = vd % (frames+1);
                break;
            case PICANM_ANIMTYPE_BACK:
                offset = -(vd % (frames+1));
                break;
            }
        }
    }
    return offset;
}

void qloadpalette()
{
    scrLoadPalette();
}

int32_t qgetpalookup(int32_t a1, int32_t a2)
{
    if (gFogMode)
        return ClipHigh(a1 >> 8, 15) * 16 + ClipRange(a2, 0, 15);
    else
        return ClipRange((a1 >> 8) + a2, 0, 63);
}

void HookReplaceFunctions(void)
{
    void qinitspritelists();
    int32_t qinsertsprite(int16_t nSector, int16_t nStat);
    int32_t qdeletesprite(int16_t nSprite);
    int32_t qchangespritesect(int16_t nSprite, int16_t nSector);
    int32_t qchangespritestat(int16_t nSprite, int16_t nStatus);
    int32_t qloadboard(const char* filename, char flags, vec3_t* dapos, int16_t* daang, int16_t* dacursectnum);
    int32_t qsaveboard(const char* filename, const vec3_t* dapos, int16_t daang, int16_t dacursectnum);
    animateoffs_replace = qanimateoffs;
    paletteLoadFromDisk_replace = qloadpalette;
    getpalookup_replace = qgetpalookup;
    initspritelists_replace = qinitspritelists;
    insertsprite_replace = qinsertsprite;
    deletesprite_replace = qdeletesprite;
    changespritesect_replace = qchangespritesect;
    changespritestat_replace = qchangespritestat;
    loadvoxel_replace = qloadvoxel;
    loadboard_replace = qloadboard;
    saveboard_replace = qsaveboard;
    bloodhack = true;
}


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 "compat.h"

int qanimateoffs(int a1, int a2);
void qloadpalette();
int32_t qgetpalookup(int32_t a1, int32_t a2);
void HookReplaceFunctions();


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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "crc32.h"
#include "compat.h"
#include "cache1d.h"
#ifdef WITHKPLIB
#include "kplib.h"
#endif
#include "common_game.h"

#include "misc.h"
#include "qheap.h"
#include "resource.h"

#if B_BIG_ENDIAN == 1
#include "qav.h"
#include "seq.h"
#include "sound.h"
#endif

CACHENODE Resource::purgeHead = { NULL, &purgeHead, &purgeHead, 0 };

#ifdef USE_QHEAP
QHeap *Resource::heap;
#endif
Resource::Resource(void)
{
    dict = NULL;
    indexName = NULL;
    indexId = NULL;
    buffSize = 0;
    count = 0;
    handle = -1;
    crypt = true;
}

Resource::~Resource(void)
{
    if (dict)
    {
        for (unsigned int i = 0; i < count; i++)
        {
            if (dict[i].type)
                Free(dict[i].type);
            if (dict[i].name)
                Free(dict[i].name);
            if (dict[i].path)
                Free(dict[i].path);
        }
        Free(dict);
        dict = NULL;
        buffSize = 0;
        count = 0;
    }
    if (handle != -1)
    {
        kclose(handle);
    }
}

void Resource::Init(const char *filename)
{
    RFFHeader header;
#ifdef USE_QHEAP
    dassert(heap != NULL);
#endif

    if (filename)
    {
        handle = kopen4loadfrommod(filename, 0);
        if (handle != -1)
        {
            int nFileLength = kfilelength(handle);
            dassert(nFileLength != -1);
            if (kread(handle, &header, sizeof(RFFHeader)) != sizeof(RFFHeader)
                || memcmp(header.sign, "RFF\x1a", 4))
            {
                ThrowError("RFF header corrupted");
            }
#if B_BIG_ENDIAN == 1
            header.version = B_LITTLE16(header.version);
            header.offset = B_LITTLE32(header.offset);
            header.filenum = B_LITTLE32(header.filenum);
#endif
            switch (header.version & 0xff00)
            {
            case 0x200:
                crypt = 0;
                break;
            case 0x300:
                crypt = 1;
                break;
            default:
                ThrowError("Unknown RFF version");
                break;
            }
            count = header.filenum;
            if (count)
            {
                buffSize = 1;
                while (count * 2 >= buffSize)
                {
                    buffSize *= 2;
                }
                dict = (DICTNODE*)Alloc(buffSize * sizeof(DICTNODE));
                memset(dict, 0, buffSize * sizeof(DICTNODE));
                DICTNODE_FILE *tdict = (DICTNODE_FILE*)Alloc(count*sizeof(DICTNODE_FILE));
                int r = klseek(handle, header.offset, SEEK_SET);
                dassert(r != -1);
                if ((uint32_t)kread(handle, tdict, count * sizeof(DICTNODE_FILE)) != count*sizeof(DICTNODE_FILE))
                {
                    ThrowError("RFF dictionary corrupted");
                }
                if (crypt)
                {
                    Crypt(tdict, count * sizeof(DICTNODE_FILE),
                        header.offset + (header.version & 0xff) * header.offset);
                }
                for (unsigned int i = 0; i < count; i++)
                {
                    dict[i].offset = B_LITTLE32(tdict[i].offset);
                    dict[i].size = B_LITTLE32(tdict[i].size);
                    dict[i].flags = tdict[i].flags;
                    int nTypeLength = strnlen(tdict[i].type, 3);
                    int nNameLength = strnlen(tdict[i].name, 8);
                    dict[i].type = (char*)Alloc(nTypeLength+1);
                    dict[i].name = (char*)Alloc(nNameLength+1);
                    strncpy(dict[i].type, tdict[i].type, min(3, nTypeLength));
                    strncpy(dict[i].name, tdict[i].name, min(8, nNameLength));
                    dict[i].path = NULL;
                    dict[i].type[nTypeLength] = 0;
                    dict[i].name[nNameLength] = 0;
                    dict[i].id = B_LITTLE32(tdict[i].id);
                    dict[i].buffer = NULL;
                }
                Free(tdict);
            }
        }
    }
    if (!dict)
    {
        buffSize = 16;
        dict = (DICTNODE*)Alloc(buffSize * sizeof(DICTNODE));
        memset(dict, 0, buffSize * sizeof(DICTNODE));
    }
    Reindex();
#if 0
    if (external)
    {
        char fname[BMAX_PATH];
        char type[BMAX_PATH];
        BDIR *dirr;
        struct Bdirent *dirent;
        dirr = Bopendir("./");
        if (dirr)
        {
            while (dirent = Breaddir(dirr))
            {
                if (!Bwildmatch(dirent->name, external))
                    continue;
                _splitpath(dirent->name, NULL, NULL, fname, type);
                if (type[0] == '.')
                {
                    AddExternalResource(fname, &type[1], dirent->size);
                }
                else
                {
                    AddExternalResource(fname, "", dirent->size);
                }
            }
            Bclosedir(dirr);
        }
#if 0
        _splitpath2(external, out, &dir, &node, NULL, NULL);
        _makepath(ext, dir, node, NULL, NULL);
        int status = _dos_findfirst(external, 0, &info);
        while (!status)
        {
            _splitpath2(info.name, out, NULL, NULL, &fname, &type);
            if (*type == '.')
            {
                AddExternalResource(*fname, (char*)(type + 1), info.size);
            }
            else
            {
                AddExternalResource(*fname, "", info.size);
            }
            status = _dos_findnext(&info);
        }
        _dos_findclose(&info);
#endif
    }
#endif
    for (unsigned int i = 0; i < count; i++)
    {
        if (dict[i].flags & DICT_LOCK)
        {
            Lock(&dict[i]);
        }
    }
    for (unsigned int i = 0; i < count; i++)
    {
        if (dict[i].flags & DICT_LOAD)
        {
            Load(&dict[i]);
        }
    }
}

void Resource::Flush(CACHENODE *h)
{
    if (h->ptr)
    {
#ifdef USE_QHEAP
        heap->Free(h->ptr);
#else
        delete[] (char*)h->ptr;
#endif

        h->ptr = NULL;
        if (h->lockCount == 0)
        {
            RemoveMRU(h);
            return;
        }
        h->lockCount = 0;
    }
}

void Resource::Purge(void)
{
    for (unsigned int i = 0; i < count; i++)
    {
        if (dict[i].ptr)
        {
            Flush((CACHENODE *)&dict[i]);
        }
    }
}

DICTNODE **Resource::Probe(const char *fname, const char *type)
{
    char name[BMAX_PATH];
    dassert(indexName != NULL);
    memset(name, 0, sizeof(name));
    strcpy(name, type);
    strcat(name, fname);
    dassert(dict != NULL);
    unsigned int hash = Bcrc32(name, strlen(name), 0) & (buffSize - 1);
    unsigned int i = hash;
    do
    {
        if (!indexName[i])
        {
            return &indexName[i];
        }
        if (!strcmp((*indexName[i]).type, type)
            && !strcmp((*indexName[i]).name, fname))
        {
            return &indexName[i];
        }
        if (++i == buffSize)
        {
            i = 0;
        }
    } while (i != hash);
    ThrowError("Linear probe failed to find match or unused node!");
    return NULL;
}

DICTNODE **Resource::Probe(unsigned int id, const char *type)
{
    struct {
        int id;
        char type[BMAX_PATH];
    } name;
    dassert(indexName != NULL);
    memset(&name, 0, sizeof(name));
    strcpy(name.type, type);
    name.id = id;
    dassert(dict != NULL);
    unsigned int hash = Bcrc32(&name, strlen(name.type)+sizeof(name.id), 0) & (buffSize - 1);
    unsigned int i = hash;
    do
    {
        if (!indexId[i])
        {
            return &indexId[i];
        }
        if (!strcmp((*indexId[i]).type, type)
            && (*indexId[i]).id == id)
        {
            return &indexId[i];
        }
        if (++i == buffSize)
        {
            i = 0;
        }
    } while (i != hash);
    ThrowError("Linear probe failed to find match or unused node!");
    return NULL;
}

void Resource::Reindex(void)
{
    if (indexName)
    {
        Free(indexName);
    }
    indexName = (DICTNODE **)Alloc(buffSize * sizeof(DICTNODE*));
    memset(indexName, 0, buffSize * sizeof(DICTNODE*));
    for (unsigned int i = 0; i < count; i++)
    {
        DICTNODE **node = Probe(dict[i].name, dict[i].type);
        *node = &dict[i];
    }

    if (indexId)
    {
        Free(indexId);
    }
    indexId = (DICTNODE **)Alloc(buffSize * sizeof(DICTNODE*));
    memset(indexId, 0, buffSize * sizeof(DICTNODE*));
    for (unsigned int i = 0; i < count; i++)
    {
        if (dict[i].flags & (DICT_ID|DICT_EXTERNAL))
        {
            DICTNODE **node = Probe(dict[i].id, dict[i].type);
            *node = &dict[i];
        }
    }
}

void Resource::Grow(void)
{
    buffSize *= 2;
    void *p = Alloc(buffSize * sizeof(DICTNODE));
    memset(p, 0, buffSize * sizeof(DICTNODE));
    memcpy(p, dict, count * sizeof(DICTNODE));
    Free(dict);
    dict = (DICTNODE*)p;
    Reindex();
}

void Resource::AddExternalResource(const char *name, const char *type, int id, int flags, const char *pzDirectory)
{
    char name2[BMAX_PATH], type2[BMAX_PATH], filename[BMAX_PATH], path[BMAX_PATH];

    if (Bstrlen(type) > 0)
        Bsnprintf(filename, BMAX_PATH-1, "%s.%s", name, type);
    else
        Bsnprintf(filename, BMAX_PATH-1, "%s", name);

    if (pzDirectory)
        Bsnprintf(path, BMAX_PATH-1, "%s/%s", pzDirectory, filename);
    else
        Bstrncpy(path, filename, BMAX_PATH-1);

    int fhandle = kopen4loadfrommod(filename, 0);
    if (fhandle == -1)
        return;
    int size = kfilelength(fhandle);
    kclose(fhandle);
    strcpy(name2, name);
    strcpy(type2, type);
    Bstrupr(name2);
    Bstrupr(type2);
    dassert(dict != NULL);
    DICTNODE **index = Probe(name2, type2);
    dassert(index != NULL);
    DICTNODE *node = *index;
    if (node && (node->flags & DICT_EXTERNAL))
        return;
    if (!node)
    {
        if (2 * count >= buffSize)
        {
            Grow();
        }
        node = &dict[count++];
        index = Probe(name2, type2);
        *index = node;
        if (node->type)
        {
            Free(node->type);
            node->type = NULL;
        }
        if (node->name)
        {
            Free(node->name);
            node->name = NULL;
        }
        if (node->path)
        {
            Free(node->path);
            node->path = NULL;
        }
        int nTypeLength = strlen(type2);
        int nNameLength = strlen(name2);
        int nPathLength = strlen(path);
        node->type = (char*)Alloc(nTypeLength+1);
        node->name = (char*)Alloc(nNameLength+1);
        node->path = (char*)Alloc(nPathLength+1);
        strcpy(node->type, type2);
        strcpy(node->name, name2);
        strcpy(node->path, path);
        node->id = -1;
    }
    node->size = size;
    node->flags = DICT_EXTERNAL | flags;
    node->buffer = NULL;
    Flush(node);
    if (id >= 0)
    {
        index = Probe(id, type2);
        dassert(index != NULL);
        DICTNODE *node = *index;
        if (!node)
        {
            if (2 * count >= buffSize)
            {
                Grow();
            }
            node = &dict[count++];
            index = Probe(id, type2);
            *index = node;
        }
        if (node->type)
        {
            Free(node->type);
            node->type = NULL;
        }
        if (node->name)
        {
            Free(node->name);
            node->name = NULL;
        }
        if (node->path)
        {
            Free(node->path);
            node->path = NULL;
        }
        int nTypeLength = strlen(type2);
        int nNameLength = strlen(name2);
        int nPathLength = strlen(path);
        node->type = (char*)Alloc(nTypeLength+1);
        node->name = (char*)Alloc(nNameLength+1);
        node->path = (char*)Alloc(nPathLength+1);
        strcpy(node->type, type2);
        strcpy(node->name, name2);
        strcpy(node->path, path);
        node->id = id;
        node->size = size;
        node->flags = DICT_EXTERNAL | flags;
        node->buffer = NULL;
        Flush(node);
    }
}

void Resource::AddFromBuffer(const char* name, const char* type, char* data, int size, int id, int flags)
{
    char name2[BMAX_PATH], type2[BMAX_PATH];

    char *pHeapData = (char*)Alloc(size);
    if (!pHeapData)
        return;
    Bmemcpy(pHeapData, data, size);
    strcpy(name2, name);
    strcpy(type2, type);
    Bstrupr(name2);
    Bstrupr(type2);
    dassert(dict != NULL);
    DICTNODE **index = Probe(name2, type2);
    dassert(index != NULL);
    DICTNODE *node = *index;
    if (!node)
    {
        if (2 * count >= buffSize)
        {
            Grow();
        }
        node = &dict[count++];
        index = Probe(name2, type2);
        *index = node;
        if (node->type)
        {
            Free(node->type);
            node->type = NULL;
        }
        if (node->name)
        {
            Free(node->name);
            node->name = NULL;
        }
        if (node->path)
        {
            Free(node->path);
            node->path = NULL;
        }
        int nTypeLength = strlen(type2);
        int nNameLength = strlen(name2);
        node->type = (char*)Alloc(nTypeLength+1);
        node->name = (char*)Alloc(nNameLength+1);
        strcpy(node->type, type2);
        strcpy(node->name, name2);
        node->id = -1;
    }
    node->size = size;
    node->flags = DICT_BUFFER | flags;
    node->buffer = pHeapData;
    Flush(node);
    if (id >= 0)
    {
        index = Probe(id, type2);
        dassert(index != NULL);
        DICTNODE *node = *index;
        if (!node)
        {
            if (2 * count >= buffSize)
            {
                Grow();
            }
            node = &dict[count++];
            index = Probe(id, type2);
            *index = node;
        }
        if (node->type)
        {
            Free(node->type);
            node->type = NULL;
        }
        if (node->name)
        {
            Free(node->name);
            node->name = NULL;
        }
        if (node->path)
        {
            Free(node->path);
            node->path = NULL;
        }
        int nTypeLength = strlen(type2);
        int nNameLength = strlen(name2);
        node->type = (char*)Alloc(nTypeLength+1);
        node->name = (char*)Alloc(nNameLength+1);
        strcpy(node->type, type2);
        strcpy(node->name, name2);
        node->id = id;
        node->size = size;
        node->flags = DICT_BUFFER | flags;
        node->buffer = pHeapData;
        Flush(node);
    }
}

void *Resource::Alloc(int nSize)
{
#ifdef USE_QHEAP
    dassert(heap != NULL);
    dassert(nSize != 0);
    void *p = heap->Alloc(nSize);
    if (p)
    {
        return p;
    }
    for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
    {
        dassert(node->lockCount == 0);
        dassert(node->ptr != NULL);
        int nFree = heap->Free(node->ptr);
        node->ptr = NULL;
        RemoveMRU(node);
        if (nSize <= nFree)
        {
            p = Alloc(nSize);
            dassert(p != NULL);
            return p;
        }
    }
    ThrowError("Out of memory!");
    return NULL;
#else
    dassert(nSize != 0);
    void* p = new char[nSize];
    if (p)
    {
        return p;
    }
    for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
    {
        dassert(node->lockCount == 0);
        dassert(node->ptr != NULL);
        delete[] (char*)node->ptr;
        node->ptr = NULL;
        RemoveMRU(node);
        p = new char[nSize];
        if (p)
            return p;
    }
    ThrowError("Out of memory!");
    return NULL;
#endif
}

void Resource::Free(void *p)
{
#ifdef USE_QHEAP
    dassert(heap != NULL);
    dassert(p != NULL);
    heap->Free(p);
#else
    dassert(p != NULL);
    delete[] (char*)p;
#endif
}

DICTNODE *Resource::Lookup(const char *name, const char *type)
{
    char name2[BMAX_PATH], type2[BMAX_PATH];
    dassert(name != NULL);
    dassert(type != NULL);
    //if (strlen(name) > 8 || strlen(type) > 3) return NULL;
    // Try to load external resource first
    AddExternalResource(name, type);
    strcpy(name2, name);
    strcpy(type2, type);
    Bstrupr(type2);
    Bstrupr(name2);
    return *Probe(name2, type2);
}

DICTNODE *Resource::Lookup(unsigned int id, const char *type)
{
    char type2[BMAX_PATH];
    dassert(type != NULL);
    //if (strlen(type) > 3) return NULL;
    strcpy(type2, type);
    Bstrupr(type2);
    return *Probe(id, type2);
}

void Resource::Read(DICTNODE *n)
{
    dassert(n != NULL);
    Read(n, n->ptr);
}

void Resource::Read(DICTNODE *n, void *p)
{
    char filename[BMAX_PATH];
    dassert(n != NULL);
    if (n->flags & DICT_EXTERNAL)
    {
        if (n->path)
            Bstrncpy(filename, n->path, BMAX_PATH-1);
        else
            Bsnprintf(filename, BMAX_PATH-1, "%s.%s", n->name, n->type);
        int fhandle = kopen4loadfrommod(filename, 0);
        if (fhandle == -1 || (uint32_t)kread(fhandle, p, n->size) != n->size)
        {
            ThrowError("Error reading external resource (%i)", errno);
        }
        kclose(fhandle);
    }
    else if (n->flags & DICT_BUFFER)
    {
        Bmemcpy(p, n->buffer, n->size);
    }
    else
    {
        int r = klseek(handle, n->offset, SEEK_SET);
        if (r == -1)
        {
            ThrowError("Error seeking to resource!");
        }
        if ((uint32_t)kread(handle, p, n->size) != n->size)
        {
            ThrowError("Error loading resource!");
        }
        if (n->flags & DICT_CRYPT)
        {
            int size;
            if (n->size > 0x100)
            {
                size = 0x100;
            }
            else
            {
                size = n->size;
            }
            Crypt(p, size, 0);
        }
#if B_BIG_ENDIAN == 1
        if (!Bstrcmp(n->type, "QAV"))
        {
            QAV *qav = (QAV*)p;
            qav->nFrames = B_LITTLE32(qav->nFrames);
            qav->ticksPerFrame = B_LITTLE32(qav->ticksPerFrame);
            qav->at10 = B_LITTLE32(qav->at10);
            qav->x = B_LITTLE32(qav->x);
            qav->y = B_LITTLE32(qav->y);
            qav->nSprite = B_LITTLE32(qav->nSprite);
            for (int i = 0; i < qav->nFrames; i++)
            {
                FRAMEINFO *pFrame = &qav->frames[i];
                SOUNDINFO *pSound = &pFrame->sound;
                pFrame->nCallbackId = B_LITTLE32(pFrame->nCallbackId);
                pSound->sound = B_LITTLE32(pSound->sound);
                for (int j = 0; j < 8; j++)
                {
                    TILE_FRAME *pTile = &pFrame->tiles[j];
                    pTile->picnum = B_LITTLE32(pTile->picnum);
                    pTile->x = B_LITTLE32(pTile->x);
                    pTile->y = B_LITTLE32(pTile->y);
                    pTile->z = B_LITTLE32(pTile->z);
                    pTile->stat = B_LITTLE32(pTile->stat);
                    pTile->angle = B_LITTLE16(pTile->angle);
                }
            }
        }
        else if (!Bstrcmp(n->type, "SEQ"))
        {
            Seq *pSeq = (Seq*)p;
            pSeq->version = B_LITTLE16(pSeq->version);
            pSeq->nFrames = B_LITTLE16(pSeq->nFrames);
            pSeq->ticksPerFrame = B_LITTLE16(pSeq->ticksPerFrame);
            pSeq->nSoundID = B_LITTLE16(pSeq->nSoundID);
            pSeq->flags = B_LITTLE32(pSeq->flags);
            for (int i = 0; i < pSeq->nFrames; i++)
            {
                SEQFRAME *pFrame = &pSeq->frames[i];
                BitReader bitReader((char *)pFrame, sizeof(SEQFRAME));
                SEQFRAME swapFrame;
                swapFrame.tile = bitReader.readUnsigned(12);
                swapFrame.transparent = bitReader.readBit();
                swapFrame.transparent2 = bitReader.readBit();
                swapFrame.blockable = bitReader.readBit();
                swapFrame.hittable = bitReader.readBit();
                swapFrame.xrepeat = bitReader.readUnsigned(8);
                swapFrame.yrepeat = bitReader.readUnsigned(8);
                swapFrame.shade = bitReader.readSigned(8);
                swapFrame.pal = bitReader.readUnsigned(5);
                swapFrame.at5_5 = bitReader.readBit();
                swapFrame.at5_6 = bitReader.readBit();
                swapFrame.at5_7 = bitReader.readBit();
                swapFrame.at6_0 = bitReader.readBit();
                swapFrame.at6_1 = bitReader.readBit();
                swapFrame.invisible = bitReader.readBit();
                swapFrame.at6_3 = bitReader.readBit();
                swapFrame.at6_4 = bitReader.readBit();
                swapFrame.tile2 = bitReader.readUnsigned(4);
                swapFrame.soundRange = bitReader.readUnsigned(4);
                swapFrame.surfaceSound = bitReader.readBit();
                swapFrame.reserved = bitReader.readUnsigned(2);
                *pFrame = swapFrame;
            }
        }
        else if (!Bstrcmp(n->type, "SFX"))
        {
            SFX *pSFX = (SFX*)p;
            pSFX->relVol = B_LITTLE32(pSFX->relVol);
            pSFX->pitch = B_LITTLE32(pSFX->pitch);
            pSFX->pitchRange = B_LITTLE32(pSFX->pitchRange);
            pSFX->format = B_LITTLE32(pSFX->format);
            pSFX->loopStart = B_LITTLE32(pSFX->loopStart);
        }
#endif
    }
}

void *Resource::Load(DICTNODE *h)
{
    dassert(h != NULL);
    if (h->ptr)
    {
        if (!h->lockCount)
        {
            RemoveMRU(h);

            h->prev = purgeHead.prev;
            purgeHead.prev->next = h;
            h->next = &purgeHead;
            purgeHead.prev = h;
        }
    }
    else
    {
        h->ptr = Alloc(h->size);
        Read(h);

        h->prev = purgeHead.prev;
        purgeHead.prev->next = h;
        h->next = &purgeHead;
        purgeHead.prev = h;
    }
    return h->ptr;
}

void *Resource::Load(DICTNODE *h, void *p)
{
    dassert(h != NULL);
    if (p)
    {
        Read(h, p);
    }
    return p;
}

void *Resource::Lock(DICTNODE *h)
{
    dassert(h != NULL);
    if (h->ptr)
    {
        if (h->lockCount == 0)
        {
            RemoveMRU(h);
        }
    }
    else
    {
        h->ptr = Alloc(h->size);
        Read(h);
    }

    h->lockCount++;
    return h->ptr;
}

void Resource::Unlock(DICTNODE *h)
{
    dassert(h != NULL);
    dassert(h->ptr != NULL);
    if (h->lockCount > 0)
    {
        h->lockCount--;
        if (h->lockCount == 0)
        {
            h->prev = purgeHead.prev;
            purgeHead.prev->next = h;
            h->next = &purgeHead;
            purgeHead.prev = h;
        }
    }
}

void Resource::Crypt(void *p, int length, unsigned short key)
{
    char *cp = (char*)p;
    for (int i = 0; i < length; i++, key++)
    {
        cp[i] ^= (key >> 1);
    }
}

void Resource::RemoveMRU(CACHENODE *h)
{
    h->prev->next = h->next;
    h->next->prev = h->prev;
}

void Resource::FNAddFiles(fnlist_t * fnlist, const char *pattern)
{
    char filename[BMAX_PATH];
    for (unsigned int i = 0; i < count; i++)
    {
        DICTNODE *pNode = &dict[i];
        if (pNode->flags & DICT_EXTERNAL)
            continue;
        sprintf(filename, "%s.%s", pNode->name, pNode->type);
        if (!Bwildmatch(filename, pattern))
            continue;
        switch (klistaddentry(&fnlist->findfiles, filename, BUILDVFS_FIND_FILE, BUILDVFS_SOURCE_GRP))
        {
        case -1:
            return;
        case 0:
            fnlist->numfiles++;
            break;
        }
    }
}

void Resource::PurgeCache(void)
{
#ifndef USE_QHEAP
    for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
    {
        DICTNODE *pDict = (DICTNODE*)node;
        if (!(pDict->flags & DICT_LOAD))
        {
            dassert(pDict->lockCount == 0);
            dassert(pDict->ptr != NULL);
            Free(pDict->ptr);
            pDict->ptr = NULL;
            RemoveMRU(pDict);
        }
    }
#endif
}

void Resource::PrecacheSounds(void)
{
    for (unsigned int i = 0; i < count; i++)
    {
        DICTNODE *pNode = &dict[i];
        if ((!strcmp(pNode->type, "RAW") || !strcmp(pNode->type, "SFX")) && !pNode->ptr)
        {
            Load(pNode);
            gameHandleEvents();
        }
    }
}

void Resource::RemoveNode(DICTNODE* pNode)
{
    Flush(pNode);
    if (pNode->name)
    {
        Free(pNode->name);
        pNode->name = NULL;
    }
    if (pNode->type)
    {
        Free(pNode->type);
        pNode->type = NULL;
    }
    if (pNode->path)
    {
        Free(pNode->path);
        pNode->path = NULL;
    }
    *pNode = dict[--count];
    Bmemset(&dict[count], 0, sizeof(DICTNODE));
    if (pNode->ptr && !pNode->lockCount)
    {
        pNode->prev->next = pNode;
        pNode->next->prev = pNode;
    }
    Reindex();
}


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.h"
#include "qheap.h"

#pragma pack(push, 1)

enum DICTFLAGS {
    DICT_ID = 1,
    DICT_EXTERNAL = 2,
    DICT_LOAD = 4,
    DICT_LOCK = 8,
    DICT_CRYPT = 16,
    DICT_BUFFER = 32,
};

struct RFFHeader
{
    char sign[4];
    short version;
    short pad1;
    unsigned int offset;
    unsigned int filenum;
    int pad2[4];
};

struct DICTNODE_FILE
{
    char unused1[16];
    unsigned int offset;
    unsigned int size;
    char unused2[8];
    char flags;
    char type[3];
    char name[8];
    int id;
};

#pragma pack(pop)

struct CACHENODE
{
    void *ptr;
    CACHENODE *prev;
    CACHENODE *next;
    int lockCount;
};

struct DICTNODE : CACHENODE
{
    unsigned int offset;
    unsigned int size;
    char flags;
    //char type[3];
    //char name[8];
    char *type;
    char *name;
    char *path;
    char *buffer;
    unsigned int id;
};

class Resource
{
public:
    Resource(void);
    ~Resource(void);

    void Init(const char *filename);
    static void Flush(CACHENODE *h);
    void Purge(void);
    DICTNODE **Probe(const char *fname, const char *type);
    DICTNODE **Probe(unsigned int id, const char *type);
    void Reindex(void);
    void Grow(void);
    void AddExternalResource(const char *name, const char *type, int id = -1, int flags = 0, const char* pzDirectory = NULL);
    void AddFromBuffer(const char* name, const char* type, char *data, int size, int id = -1, int flags = 0);
    static void *Alloc(int nSize);
    static void Free(void *p);
    DICTNODE *Lookup(const char *name, const char *type);
    DICTNODE *Lookup(unsigned int id, const char *type);
    void Read(DICTNODE *n);
    void Read(DICTNODE *n, void *p);
    void *Load(DICTNODE *h);
    void *Load(DICTNODE *h, void *p);
    void *Lock(DICTNODE *h);
    void Unlock(DICTNODE *h);
    void Crypt(void *p, int length, unsigned short key);
    static void RemoveMRU(CACHENODE *h);
    int Size(DICTNODE*h) { return h->size; }
    void FNAddFiles(fnlist_t *fnlist, const char *pattern);
    void PrecacheSounds(void);
    void PurgeCache(void);
    void RemoveNode(DICTNODE* pNode);

    DICTNODE *dict;
    DICTNODE **indexName;
    DICTNODE **indexId;
    unsigned int buffSize;
    unsigned int count;
    int handle;
    bool crypt;

#if USE_QHEAP
    static QHeap *heap;
#endif
    static CACHENODE purgeHead;
};


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 <string.h>
#include "a.h"
#include "build.h"
#include "colmatch.h"
#include "common_game.h"

#include "globals.h"
#include "config.h"
#include "gfx.h"
#include "resource.h"
#include "screen.h"

RGB StdPal[32] = {
    { 0, 0, 0 },
    { 0, 0, 170 },
    { 0, 170, 170 },
    { 0, 170, 170 },
    { 170, 0, 0 },
    { 170, 0, 170 },
    { 170, 85, 0 },
    { 170, 170, 170 },
    { 85, 85, 85 },
    { 85, 85, 255 },
    { 85, 255, 85 },
    { 85, 255, 255 },
    { 255, 85, 85 },
    { 255, 85, 255 },
    { 255, 255, 85 },
    { 255, 255, 255 },
    { 241, 241, 241 },
    { 226, 226, 226 },
    { 211, 211, 211 },
    { 196, 196, 196 },
    { 181, 181, 181 },
    { 166, 166, 166 },
    { 151, 151, 151 },
    { 136, 136, 136 },
    { 120, 120, 120 },
    { 105, 105, 105 },
    { 90, 90, 90 },
    { 75, 75, 75 },
    { 60, 60, 60 },
    { 45, 45, 45 },
    { 30, 30, 30 },
    { 15, 15, 15 }
};

LOADITEM PLU[15] = {
    { 0, "NORMAL" },
    { 1, "SATURATE" },
    { 2, "BEAST" },
    { 3, "TOMMY" },
    { 4, "SPIDER3" },
    { 5, "GRAY" },
    { 6, "GRAYISH" },
    { 7, "SPIDER1" },
    { 8, "SPIDER2" },
    { 9, "FLAME" },
    { 10, "COLD" },
    { 11, "P1" },
    { 12, "P2" },
    { 13, "P3" },
    { 14, "P4" }
};

LOADITEM PAL[5] = {
    { 0, "BLOOD" },
    { 1, "WATER" },
    { 2, "BEAST" },
    { 3, "SEWER" },
    { 4, "INVULN1" }
};

bool DacInvalid = true;
static char(*gammaTable)[256];
RGB curDAC[256];
RGB baseDAC[256];
static RGB fromDAC[256];
static RGB toRGB;
static RGB *palTable[5];
static int curPalette;
static int curGamma;
int gGammaLevels;
bool gFogMode = false;
char gStdColor[32];
int32_t gBrightness;

char scrFindClosestColor(int red, int green, int blue)
{
    int dist = 0x7fffffff;
    int best;
    for (int i = 0; i < 256; i++)
    {
        int sum = (palette[i*3+1]-green)*(palette[i*3+1]-green);
        if (sum >= dist) continue;
        sum += (palette[i*3+0]-red)*(palette[i*3+0]-red);
        if (sum >= dist) continue;
        sum += (palette[i*3+2]-blue)*(palette[i*3+2]-blue);
        if (sum >= dist) continue;
        best = i;
        dist = sum;
        if (sum == 0)
            break;
    }
    return best;
}

void scrCreateStdColors(void)
{
    for (int i = 0; i < 32; i++)
        gStdColor[i] = scrFindClosestColor(StdPal[i].red, StdPal[i].green, StdPal[i].blue);
}

void scrResetPalette(void)
{
    if (palTable[0] == nullptr)
        return;

    paletteSetColorTable(0, (uint8_t*)palTable[0]);
}

void gSetDacRange(int start, int end, RGB *pPal)
{
    UNREFERENCED_PARAMETER(start);
    UNREFERENCED_PARAMETER(end);
    if (videoGetRenderMode() == REND_CLASSIC)
    {
        memcpy(palette, pPal, sizeof(palette));
        videoSetPalette(gBrightness>>2, 0, 0);
    }
}

void scrLoadPLUs(void)
{
    if (gFogMode)
    {
        DICTNODE *pFog = gSysRes.Lookup("FOG", "FLU");
        if (!pFog)
            ThrowError("FOG.FLU not found");
        palookup[0] = (char*)gSysRes.Lock(pFog);
        for (int i = 0; i < 15; i++)
            palookup[PLU[i].id] = palookup[0];
        parallaxvisibility = 3072;
        return;
    }

    // load default palookups
    for (int i = 0; i < 15; i++) {
        DICTNODE *pPlu = gSysRes.Lookup(PLU[i].name, "PLU");
        if (!pPlu)
            ThrowError("%s.PLU not found", PLU[i].name);
        if (pPlu->size / 256 != 64)
            ThrowError("Incorrect PLU size");
        palookup[PLU[i].id] = (char*)gSysRes.Lock(pPlu);
    }

    // by NoOne: load user palookups
    for (int i = kUserPLUStart; i < MAXPALOOKUPS; i++) {
        DICTNODE* pPlu = gSysRes.Lookup(i, "PLU");
        if (!pPlu) continue;
        else if (pPlu->size / 256 != 64) { consoleSysMsg("Incorrect filesize of PLU#%d", i); }
        else palookup[i] = (char*)gSysRes.Lock(pPlu);
    }

#ifdef USE_OPENGL
    palookupfog[1].r = 255;
    palookupfog[1].g = 255;
    palookupfog[1].b = 255;
#endif
}

#ifdef USE_OPENGL
glblend_t const bloodglblend =
{
    {
        { 1.f/3.f, BLENDFACTOR_SRC_ALPHA, BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 0 },
        { 2.f/3.f, BLENDFACTOR_SRC_ALPHA, BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 0 },
    },
};
#endif

void scrLoadPalette(void)
{
    paletteInitClosestColorScale(30, 59, 11);
    paletteInitClosestColorGrid();
    paletteloaded = 0;
    initprintf("Loading palettes\n");
    for (int i = 0; i < 5; i++)
    {
        DICTNODE *pPal = gSysRes.Lookup(PAL[i].name, "PAL");
        if (!pPal)
            ThrowError("%s.PAL not found (RFF files may be wrong version)", PAL[i].name);
        palTable[PAL[i].id] = (RGB*)gSysRes.Lock(pPal);
        paletteSetColorTable(PAL[i].id, (uint8_t*)palTable[PAL[i].id]);
    }
    memcpy(palette, palTable[0], sizeof(palette));
    numshades = 64;
    paletteloaded |= PALETTE_MAIN;
    scrLoadPLUs();
    paletteloaded |= PALETTE_SHADE;
    initprintf("Loading translucency table\n");
    DICTNODE *pTrans = gSysRes.Lookup("TRANS", "TLU");
    if (!pTrans)
        ThrowError("TRANS.TLU not found");
    blendtable[0] = (char*)gSysRes.Lock(pTrans);
    paletteloaded |= PALETTE_TRANSLUC;

#ifdef USE_OPENGL
    for (auto & x : glblend)
        x = bloodglblend;

    for (int i = 0; i < MAXPALOOKUPS; i++)
        palookupfogfactor[i] = 1.f;
#endif

    paletteInitClosestColorMap((uint8_t*)palTable[0]);
    palettePostLoadTables();
    // Make color index 255 of palette black.
    for (int i = 0; i < 5; i++)
    {
        if (basepaltable[i] != NULL)
            Bmemset(&basepaltable[i][255 * 3], 0, 3);
    }
    palettePostLoadLookups();
}

void scrSetPalette(int palId)
{
    curPalette = palId;
    scrSetGamma(0/*curGamma*/);
}

void scrSetGamma(int nGamma)
{
    dassert(nGamma < gGammaLevels);
    curGamma = nGamma;
    for (int i = 0; i < 256; i++)
    {
        baseDAC[i].red = gammaTable[curGamma][palTable[curPalette][i].red];
        baseDAC[i].green = gammaTable[curGamma][palTable[curPalette][i].green];
        baseDAC[i].blue = gammaTable[curGamma][palTable[curPalette][i].blue];
    }
    DacInvalid = 1;
}

void scrSetupFade(char red, char green, char blue)
{
    memcpy(fromDAC, curDAC, sizeof(fromDAC));
    toRGB.red = red;
    toRGB.green = green;
    toRGB.blue = blue;
}

void scrSetupUnfade(void)
{
    memcpy(fromDAC, baseDAC, sizeof(fromDAC));
}

void scrFadeAmount(int amount)
{
	for (int i = 0; i < 256; i++)
	{
		curDAC[i].red = interpolate(fromDAC[i].red, toRGB.red, amount);
        curDAC[i].green = interpolate(fromDAC[i].green, toRGB.green, amount);
        curDAC[i].blue = interpolate(fromDAC[i].blue, toRGB.blue, amount);
	}
	gSetDacRange(0, 256, curDAC);
}

void scrSetDac(void)
{
	if (DacInvalid)
		gSetDacRange(0, 256, baseDAC);
	DacInvalid = 0;
}

void scrInit(void)
{
    initprintf("Initializing engine\n");
#ifdef USE_OPENGL
    glrendmode = REND_POLYMOST;
#endif
    engineInit();
    curPalette = 0;
    curGamma = 0;
    initprintf("Loading gamma correction table\n");
    DICTNODE *pGamma = gSysRes.Lookup("gamma", "DAT");
    if (!pGamma)
        ThrowError("Gamma table not found");
    gGammaLevels = pGamma->size / 256;
    gammaTable = (char(*)[256])gSysRes.Lock(pGamma);
}

void scrUnInit(void)
{
    memset(palookup, 0, sizeof(palookup));
    memset(blendtable, 0, sizeof(blendtable));
    engineUnInit();
}

void scrSetGameMode(int vidMode, int XRes, int YRes, int nBits)
{
    videoResetMode();
    //videoSetGameMode(vidMode, XRes, YRes, nBits, 0);
    if (videoSetGameMode(vidMode, XRes, YRes, nBits, 0) < 0)
    {
        initprintf("Failure setting video mode %dx%dx%d %s! Trying next mode...\n", XRes, YRes,
                    nBits, vidMode ? "fullscreen" : "windowed");

        int resIdx = 0;

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

        int const savedIdx = resIdx;
        int bpp = nBits;

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

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

                resIdx = savedIdx;
                bpp = 8;
            }
        }

        gSetup.xdim = validmode[resIdx].xdim;
        gSetup.ydim = validmode[resIdx].ydim;
        gSetup.bpp  = bpp;
    }
    videoClearViewableArea(0);
    scrNextPage();
    scrSetPalette(curPalette);
    gfxSetClip(0, 0, xdim, ydim);
}

void scrNextPage(void)
{
    videoNextPage();
}


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

struct LOADITEM {
    int id;
    const char *name;
};

#pragma pack(push, 1)
struct RGB {
    char red, green, blue;
};
#pragma pack(pop)
extern char gStdColor[32];

extern bool DacInvalid;
extern RGB curDAC[256];
extern RGB baseDAC[256];
extern int gGammaLevels;
extern bool gFogMode;
extern int32_t gBrightness;
void scrCreateStdColors(void);
void scrResetPalette(void);
void gSetDacRange(int start, int end, RGB *pPal);
void scrLoadPLUs(void);
void scrLoadPalette(void);
void scrSetPalette(int palId);
void scrSetGamma(int nGamma);
void scrSetupFade(char red, char green, char blue);
void scrSetupUnfade(void);
void scrFadeAmount(int amount);
void scrSetDac(void);
void scrInit(void);
void scrUnInit(void);
void scrSetGameMode(int vidMode, int XRes, int YRes, int nBits);
void scrNextPage(void);


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

extern int shadeCount;
extern short shadeList[kMaxXSectors];
extern int panCount;
extern short panList[kMaxXSectors];

void DoSectorLighting(void);
void UndoSectorLighting(void);
void DoSectorPanning(void);
void InitSectorFX(void);


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 <string.h>
#include "build.h"
#include "common_game.h"

#include "blood.h"
#include "db.h"
#include "eventq.h"
#include "globals.h"
#include "levels.h"
#include "loadsave.h"
#include "sfx.h"
#include "sound.h"
#include "seq.h"
#include "gameutil.h"
#include "actor.h"
#include "tile.h"
#include "view.h"

#define kMaxClients 256
#define kMaxSequences 1024

static ACTIVE activeList[kMaxSequences];
static int activeCount = 0;
static int nClients = 0;
static void(*clientCallback[kMaxClients])(int, int);

int seqRegisterClient(void(*pClient)(int, int))
{
    dassert(nClients < kMaxClients);
    clientCallback[nClients] = pClient;
    return nClients++;
}

void Seq::Preload(void)
{
    if (memcmp(signature, "SEQ\x1a", 4) != 0)
        ThrowError("Invalid sequence");
    if ((version & 0xff00) != 0x300)
        ThrowError("Obsolete sequence version");
    for (int i = 0; i < nFrames; i++)
        tilePreloadTile(seqGetTile(&frames[i]));
}

void Seq::Precache(void)
{
    if (memcmp(signature, "SEQ\x1a", 4) != 0)
        ThrowError("Invalid sequence");
    if ((version & 0xff00) != 0x300)
        ThrowError("Obsolete sequence version");
    for (int i = 0; i < nFrames; i++)
        tilePrecacheTile(seqGetTile(&frames[i]));
}

void seqPrecacheId(int id)
{
    DICTNODE *hSeq = gSysRes.Lookup(id, "SEQ");
    if (!hSeq)
        return;
    Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
    pSeq->Precache();
    gSysRes.Unlock(hSeq);
}

SEQINST siWall[kMaxXWalls];
SEQINST siCeiling[kMaxXSectors];
SEQINST siFloor[kMaxXSectors];
SEQINST siSprite[kMaxXSprites];
SEQINST siMasked[kMaxXWalls];

void UpdateSprite(int nXSprite, SEQFRAME *pFrame)
{
    dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
    int nSprite = xsprite[nXSprite].reference;
    dassert(nSprite >= 0 && nSprite < kMaxSprites);
    spritetype *pSprite = &sprite[nSprite];
    dassert(pSprite->extra == nXSprite);
    if (pSprite->flags & 2)
    {
        if (tilesiz[pSprite->picnum].y != tilesiz[seqGetTile(pFrame)].y || picanm[pSprite->picnum].yofs != picanm[seqGetTile(pFrame)].yofs
            || (pFrame->yrepeat && pFrame->yrepeat != pSprite->yrepeat))
            pSprite->flags |= 4;
    }
    pSprite->picnum = seqGetTile(pFrame);
    if (pFrame->pal)
        pSprite->pal = pFrame->pal;
    pSprite->shade = pFrame->shade;

    int scale = xsprite[nXSprite].scale; // SEQ size scaling
    if (pFrame->xrepeat) {
        if (scale) pSprite->xrepeat = ClipRange(mulscale8(pFrame->xrepeat, scale), 0, 255);
        else pSprite->xrepeat = pFrame->xrepeat;
    }

    if (pFrame->yrepeat) {
        if (scale) pSprite->yrepeat = ClipRange(mulscale8(pFrame->yrepeat, scale), 0, 255);
        else pSprite->yrepeat = pFrame->yrepeat;
    }

    if (pFrame->transparent)
        pSprite->cstat |= 2;
    else
        pSprite->cstat &= ~2;
    if (pFrame->transparent2)
        pSprite->cstat |= 512;
    else
        pSprite->cstat &= ~512;
    if (pFrame->blockable)
        pSprite->cstat |= 1;
    else
        pSprite->cstat &= ~1;
    if (pFrame->hittable)
        pSprite->cstat |= 256;
    else
        pSprite->cstat &= ~256;
    if (pFrame->invisible)
        pSprite->cstat |= 32768;
    else
        pSprite->cstat &= (unsigned short)~32768;
    if (pFrame->at6_0)
        pSprite->cstat |= 4096;
    else
        pSprite->cstat &= ~4096;
    if (pFrame->at5_6)
        pSprite->flags |= 256;
    else
        pSprite->flags &= ~256;
    if (pFrame->at5_7)
        pSprite->flags |= 8;
    else
        pSprite->flags &= ~8;
    if (pFrame->at6_3)
        pSprite->flags |= 1024;
    else
        pSprite->flags &= ~1024;
    if (pFrame->at6_4)
        pSprite->flags |= 2048;
    else
        pSprite->flags &= ~2048;
}

void UpdateWall(int nXWall, SEQFRAME *pFrame)
{
    dassert(nXWall > 0 && nXWall < kMaxXWalls);
    int nWall = xwall[nXWall].reference;
    dassert(nWall >= 0 && nWall < kMaxWalls);
    walltype *pWall = &wall[nWall];
    dassert(pWall->extra == nXWall);
    pWall->picnum = seqGetTile(pFrame);
    if (pFrame->pal)
        pWall->pal = pFrame->pal;
    if (pFrame->transparent)
        pWall->cstat |= 128;
    else
        pWall->cstat &= ~128;
    if (pFrame->transparent2)
        pWall->cstat |= 512;
    else
        pWall->cstat &= ~512;
    if (pFrame->blockable)
        pWall->cstat |= 1;
    else
        pWall->cstat &= ~1;
    if (pFrame->hittable)
        pWall->cstat |= 64;
    else
        pWall->cstat &= ~64;
}

void UpdateMasked(int nXWall, SEQFRAME *pFrame)
{
    dassert(nXWall > 0 && nXWall < kMaxXWalls);
    int nWall = xwall[nXWall].reference;
    dassert(nWall >= 0 && nWall < kMaxWalls);
    walltype *pWall = &wall[nWall];
    dassert(pWall->extra == nXWall);
    dassert(pWall->nextwall >= 0);
    walltype *pWallNext = &wall[pWall->nextwall];
    pWall->overpicnum = pWallNext->overpicnum = seqGetTile(pFrame);
    if (pFrame->pal)
        pWall->pal = pWallNext->pal = pFrame->pal;
    if (pFrame->transparent)
    {
        pWall->cstat |= 128;
        pWallNext->cstat |= 128;
    }
    else
    {
        pWall->cstat &= ~128;
        pWallNext->cstat &= ~128;
    }
    if (pFrame->transparent2)
    {
        pWall->cstat |= 512;
        pWallNext->cstat |= 512;
    }
    else
    {
        pWall->cstat &= ~512;
        pWallNext->cstat &= ~512;
    }
    if (pFrame->blockable)
    {
        pWall->cstat |= 1;
        pWallNext->cstat |= 1;
    }
    else
    {
        pWall->cstat &= ~1;
        pWallNext->cstat &= ~1;
    }
    if (pFrame->hittable)
    {
        pWall->cstat |= 64;
        pWallNext->cstat |= 64;
    }
    else
    {
        pWall->cstat &= ~64;
        pWallNext->cstat &= ~64;
    }
}

void UpdateFloor(int nXSector, SEQFRAME *pFrame)
{
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    int nSector = xsector[nXSector].reference;
    dassert(nSector >= 0 && nSector < kMaxSectors);
    sectortype *pSector = &sector[nSector];
    dassert(pSector->extra == nXSector);
    pSector->floorpicnum = seqGetTile(pFrame);
    pSector->floorshade = pFrame->shade;
    if (pFrame->pal)
        pSector->floorpal = pFrame->pal;
}

void UpdateCeiling(int nXSector, SEQFRAME *pFrame)
{
    dassert(nXSector > 0 && nXSector < kMaxXSectors);
    int nSector = xsector[nXSector].reference;
    dassert(nSector >= 0 && nSector < kMaxSectors);
    sectortype *pSector = &sector[nSector];
    dassert(pSector->extra == nXSector);
    pSector->ceilingpicnum = seqGetTile(pFrame);
    pSector->ceilingshade = pFrame->shade;
    if (pFrame->pal)
        pSector->ceilingpal = pFrame->pal;
}

void SEQINST::Update(ACTIVE *pActive)
{
    dassert(frameIndex < pSequence->nFrames);
    switch (pActive->type)
    {
    case 0:
        UpdateWall(pActive->xindex, &pSequence->frames[frameIndex]);
        break;
    case 1:
        UpdateCeiling(pActive->xindex , &pSequence->frames[frameIndex]);
        break;
    case 2:
        UpdateFloor(pActive->xindex, &pSequence->frames[frameIndex]);
        break;
    case 3:
    {
        UpdateSprite(pActive->xindex, &pSequence->frames[frameIndex]);
        if (pSequence->frames[frameIndex].at6_1) {

            int sound = pSequence->nSoundID;

            // by NoOne: add random sound range feature
            if (!VanillaMode() && pSequence->frames[frameIndex].soundRange > 0)
                sound += Random(((pSequence->frames[frameIndex].soundRange == 1) ? 2 : pSequence->frames[frameIndex].soundRange));

            sfxPlay3DSound(&sprite[xsprite[pActive->xindex].reference], sound, -1, 0);
        }

        // by NoOne: add surfaceSound trigger feature
        spritetype* pSprite = &sprite[xsprite[pActive->xindex].reference];
        if (!VanillaMode() && pSequence->frames[frameIndex].surfaceSound && zvel[pSprite->index] == 0 && xvel[pSprite->index] != 0) {

            if (gUpperLink[pSprite->sectnum] >= 0) break; // don't play surface sound for stacked sectors
            int surf = tileGetSurfType(pSprite->sectnum + 0x4000); if (!surf) break;
            static int surfSfxMove[15][4] = {
                /* {snd1, snd2, gameVolume, myVolume} */
                {800,801,80,25},
                {802,803,80,25},
                {804,805,80,25},
                {806,807,80,25},
                {808,809,80,25},
                {810,811,80,25},
                {812,813,80,25},
                {814,815,80,25},
                {816,817,80,25},
                {818,819,80,25},
                {820,821,80,25},
                {822,823,80,25},
                {824,825,80,25},
                {826,827,80,25},
                {828,829,80,25},
            };

            int sndId = surfSfxMove[surf][Random(2)];
            DICTNODE * hRes = gSoundRes.Lookup(sndId, "SFX"); SFX * pEffect = (SFX*)gSoundRes.Load(hRes);
            sfxPlay3DSoundCP(pSprite, sndId, -1, 0, 0, (surfSfxMove[surf][2] != pEffect->relVol) ? pEffect->relVol : surfSfxMove[surf][3]);
        }
        break;
    }
    case 4:
        UpdateMasked(pActive->xindex, &pSequence->frames[frameIndex]);
        break;
    }
    if (pSequence->frames[frameIndex].at5_5 && nCallbackID != -1)
        clientCallback[nCallbackID](pActive->type, pActive->xindex);
}

SEQINST * GetInstance(int nType, int nXIndex)
{
    switch (nType)
    {
    case 0:
        if (nXIndex > 0 && nXIndex < kMaxXWalls) return &siWall[nXIndex];
        break;
    case 1:
        if (nXIndex > 0 && nXIndex < kMaxXSectors) return &siCeiling[nXIndex];
        break;
    case 2:
        if (nXIndex > 0 && nXIndex < kMaxXSectors) return &siFloor[nXIndex];
        break;
    case 3:
        if (nXIndex > 0 && nXIndex < kMaxXSprites) return &siSprite[nXIndex];
        break;
    case 4:
        if (nXIndex > 0 && nXIndex < kMaxWalls) return &siMasked[nXIndex];
        break;
    }
    return NULL;
}

void UnlockInstance(SEQINST *pInst)
{
    dassert(pInst != NULL);
    dassert(pInst->hSeq != NULL);
    dassert(pInst->pSequence != NULL);
    gSysRes.Unlock(pInst->hSeq);
    pInst->hSeq = NULL;
    pInst->pSequence = NULL;
    pInst->isPlaying = 0;
}

void seqSpawn(int nSeq, int nType, int nXIndex, int nCallbackID)
{
    SEQINST *pInst = GetInstance(nType, nXIndex);
    if (!pInst) return;

    DICTNODE *hSeq = gSysRes.Lookup(nSeq, "SEQ");
    if (!hSeq)
        ThrowError("Missing sequence #%d", nSeq);

    int i = activeCount;
    if (pInst->isPlaying)
    {
        if (hSeq == pInst->hSeq)
            return;
        UnlockInstance(pInst);
        for (i = 0; i < activeCount; i++)
        {
            if (activeList[i].type == nType && activeList[i].xindex == nXIndex)
                break;
        }
        dassert(i < activeCount);
    }
    Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
    if (memcmp(pSeq->signature, "SEQ\x1a", 4) != 0)
        ThrowError("Invalid sequence %d", nSeq);
    if ((pSeq->version & 0xff00) != 0x300)
        ThrowError("Sequence %d is obsolete version", nSeq);
    if ((pSeq->version & 0xff) == 0x00)
    {
        for (int i = 0; i < pSeq->nFrames; i++)
            pSeq->frames[i].tile2 = 0;
    }
    pInst->isPlaying = 1;
    pInst->hSeq = hSeq;
    pInst->pSequence = pSeq;
    pInst->nSeq = nSeq;
    pInst->nCallbackID = nCallbackID;
    pInst->timeCount = pSeq->ticksPerFrame;
    pInst->frameIndex = 0;
    if (i == activeCount)
    {
        dassert(activeCount < kMaxSequences);
        activeList[activeCount].type = nType;
        activeList[activeCount].xindex = nXIndex;
        activeCount++;
    }
    pInst->Update(&activeList[i]);
}

void seqKill(int nType, int nXIndex)
{
    SEQINST *pInst = GetInstance(nType, nXIndex);
    if (!pInst || !pInst->isPlaying)
        return;
    int i;
    for (i = 0; i < activeCount; i++)
    {
        if (activeList[i].type == nType && activeList[i].xindex == nXIndex)
            break;
    }
    dassert(i < activeCount);
    activeCount--;
    activeList[i] = activeList[activeCount];
    pInst->isPlaying = 0;
    UnlockInstance(pInst);
}

void seqKillAll(void)
{
    for (int i = 0; i < kMaxXWalls; i++)
    {
        if (siWall[i].isPlaying)
            UnlockInstance(&siWall[i]);
        if (siMasked[i].isPlaying)
            UnlockInstance(&siMasked[i]);
    }
    for (int i = 0; i < kMaxXSectors; i++)
    {
        if (siCeiling[i].isPlaying)
            UnlockInstance(&siCeiling[i]);
        if (siFloor[i].isPlaying)
            UnlockInstance(&siFloor[i]);
    }
    for (int i = 0; i < kMaxXSprites; i++)
    {
        if (siSprite[i].isPlaying)
            UnlockInstance(&siSprite[i]);
    }
    activeCount = 0;
}

int seqGetStatus(int nType, int nXIndex)
{
    SEQINST *pInst = GetInstance(nType, nXIndex);
    if (pInst && pInst->isPlaying)
        return pInst->frameIndex;
    return -1;
}

int seqGetID(int nType, int nXIndex)
{
    SEQINST *pInst = GetInstance(nType, nXIndex);
    if (pInst)
        return pInst->nSeq;
    return -1;
}

void seqProcess(int nTicks)
{
    for (int i = 0; i < activeCount; i++)
    {
        SEQINST *pInst = GetInstance(activeList[i].type, activeList[i].xindex);
        Seq *pSeq = pInst->pSequence;
        dassert(pInst->frameIndex < pSeq->nFrames);
        pInst->timeCount -= nTicks;
        while (pInst->timeCount < 0)
        {
            pInst->timeCount += pSeq->ticksPerFrame;
            pInst->frameIndex++;
            if (pInst->frameIndex == pSeq->nFrames)
            {
                if (pSeq->flags & 1)
                    pInst->frameIndex = 0;
                else
                {
                    UnlockInstance(pInst);
                    if (pSeq->flags & 2)
                    {
                        switch (activeList[i].type)
                        {
                        case 3:
                        {
                            int nXSprite = activeList[i].xindex;
                            int nSprite = xsprite[nXSprite].reference;
                            dassert(nSprite >= 0 && nSprite < kMaxSprites);
                            evKill(nSprite, 3);
                            if ((sprite[nSprite].flags & kHitagRespawn) && sprite[nSprite].inittype >= kDudeBase && sprite[nSprite].inittype < kDudeMax)
                                evPost(nSprite, 3, gGameOptions.nMonsterRespawnTime, kCallbackRespawn);
                            else
                                DeleteSprite(nSprite);
                            break;
                        }
                        case 4:
                        {
                            int nXWall = activeList[i].xindex;
                            int nWall = xwall[nXWall].reference;
                            dassert(nWall >= 0 && nWall < kMaxWalls);
                            wall[nWall].cstat &= ~(8 + 16 + 32);
                            if (wall[nWall].nextwall != -1)
                                wall[wall[nWall].nextwall].cstat &= ~(8 + 16 + 32);
                            break;
                        }
                        }
                    }
                    activeList[i--] = activeList[--activeCount];
                    break;
                }
            }
            pInst->Update(&activeList[i]);
        }
    }
}

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

void SeqLoadSave::Load(void)
{
    Read(&siWall, sizeof(siWall));
    Read(&siMasked, sizeof(siMasked));
    Read(&siCeiling, sizeof(siCeiling));
    Read(&siFloor, sizeof(siFloor));
    Read(&siSprite, sizeof(siSprite));
    Read(&activeList, sizeof(activeList));
    Read(&activeCount, sizeof(activeCount));
    for (int i = 0; i < kMaxXWalls; i++)
    {
        siWall[i].hSeq = NULL;
        siMasked[i].hSeq = NULL;
        siWall[i].pSequence = NULL;
        siMasked[i].pSequence = NULL;
    }
    for (int i = 0; i < kMaxXSectors; i++)
    {
        siCeiling[i].hSeq = NULL;
        siFloor[i].hSeq = NULL;
        siCeiling[i].pSequence = NULL;
        siFloor[i].pSequence = NULL;
    }
    for (int i = 0; i < kMaxXSprites; i++)
    {
        siSprite[i].hSeq = NULL;
        siSprite[i].pSequence = NULL;
    }
    for (int i = 0; i < activeCount; i++)
    {
        SEQINST *pInst = GetInstance(activeList[i].type, activeList[i].xindex);
        if (pInst->isPlaying)
        {
            int nSeq = pInst->nSeq;
            DICTNODE *hSeq = gSysRes.Lookup(nSeq, "SEQ");
            if (!hSeq) {
                ThrowError("Missing sequence #%d", nSeq);
                continue;
            }
            Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
            if (memcmp(pSeq->signature, "SEQ\x1a", 4) != 0)
                ThrowError("Invalid sequence %d", nSeq);
            if ((pSeq->version & 0xff00) != 0x300)
                ThrowError("Sequence %d is obsolete version", nSeq);
            pInst->hSeq = hSeq;
            pInst->pSequence = pSeq;
        }
    }
}

void SeqLoadSave::Save(void)
{
    Write(&siWall, sizeof(siWall));
    Write(&siMasked, sizeof(siMasked));
    Write(&siCeiling, sizeof(siCeiling));
    Write(&siFloor, sizeof(siFloor));
    Write(&siSprite, sizeof(siSprite));
    Write(&activeList, sizeof(activeList));
    Write(&activeCount, sizeof(activeCount));
}

static SeqLoadSave *myLoadSave;

void SeqLoadSaveConstruct(void)
{
    myLoadSave = new SeqLoadSave();
}


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 "resource.h"

struct SEQFRAME {
    unsigned int tile : 12;
    unsigned int transparent  : 1; // transparent
    unsigned int transparent2 : 1; // transparent
    unsigned int blockable : 1; // blockable
    unsigned int hittable : 1; // hittable
    unsigned int xrepeat : 8; // xrepeat
    unsigned int yrepeat : 8; // yrepeat
    signed int shade : 8; // shade
    unsigned int pal : 5; // palette
    unsigned int at5_5 : 1; //
    unsigned int at5_6 : 1; //
    unsigned int at5_7 : 1; //
    unsigned int at6_0 : 1; //
    unsigned int at6_1 : 1; //
    unsigned int invisible : 1; // invisible
    unsigned int at6_3 : 1; //
    unsigned int at6_4 : 1; //
    unsigned int tile2 : 4;
    unsigned soundRange : 4; // (by NoOne) random sound range relative to global SEQ sound
    unsigned surfaceSound : 1; // (by NoOne) trigger surface sound when moving / touching
    unsigned reserved : 2;
};

struct Seq {
    char signature[4];
    short version;
    short nFrames; // at6
    short ticksPerFrame;
    short nSoundID;
    int flags;
    SEQFRAME frames[];
    void Preload(void);
    void Precache(void);
};

struct ACTIVE
{
    unsigned char type;
    unsigned short xindex;
};

struct SEQINST
{
    DICTNODE *hSeq;
    Seq *pSequence;
    int nSeq;
    int nCallbackID;
    short timeCount;
    unsigned char frameIndex;
    char isPlaying;
    void Update(ACTIVE *pActive);
};

inline int seqGetTile(SEQFRAME* pFrame)
{
    return pFrame->tile+(pFrame->tile2<<12);
}

int seqRegisterClient(void(*pClient)(int, int));
void seqPrecacheId(int id);
SEQINST* GetInstance(int nType, int nXIndex);
void UnlockInstance(SEQINST *pInst);
void seqSpawn(int nSeq, int nType, int nXIndex, int nCallbackID = -1);
void seqKill(int nType, int nXIndex);
void seqKillAll(void);
int seqGetStatus(int nType, int nXIndex);
int seqGetID(int nType, int nXIndex);
void seqProcess(int nTicks);


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 <string.h>
#include "build.h"
#include "compat.h"
#include "common_game.h"
#include "fx_man.h"

#include "config.h"
#include "gameutil.h"
#include "player.h"
#include "resource.h"
#include "sfx.h"
#include "sound.h"
#include "trig.h"

POINT2D earL, earR, earL0, earR0; // Ear position
VECTOR2D earVL, earVR; // Ear velocity ?
int lPhase, rPhase, lVol, rVol, lPitch, rPitch;

BONKLE Bonkle[256];
BONKLE *BonkleCache[256];

int nBonkles;

void sfxInit(void)
{
    for (int i = 0; i < 256; i++)
        BonkleCache[i] = &Bonkle[i];
    nBonkles = 0;
}

void sfxTerm()
{
}

int Vol3d(int angle, int dist)
{
    return dist - mulscale16(dist, 0x2000 - mulscale30(0x2000, Cos(angle)));
}

void Calc3DValues(BONKLE *pBonkle)
{
    int dx = pBonkle->curPos.x - gMe->pSprite->x;
    int dy = pBonkle->curPos.y - gMe->pSprite->y;
    int dz = pBonkle->curPos.z - gMe->pSprite->z;
    int angle = getangle(dx, dy);
    dx >>= 4;
    dy >>= 4;
    dz >>= 8;
    int distance = ksqrt(dx*dx + dy * dy + dz * dz);
    distance = ClipLow((distance >> 2) + (distance >> 3), 64);
    int v14, v18;
    v14 = v18 = scale(pBonkle->vol, 80, distance);
    int sinVal = Sin(angle);
    int cosVal = Cos(angle);
    int v8 = dmulscale30r(cosVal, pBonkle->curPos.x - pBonkle->oldPos.x, sinVal, pBonkle->curPos.y - pBonkle->oldPos.y);

    int distanceL = approxDist(pBonkle->curPos.x - earL.x, pBonkle->curPos.y - earL.y);
    lVol = Vol3d(angle - (gMe->pSprite->ang - 85), v18);
    int phaseLeft = mulscale16r(distanceL, pBonkle->format == 1 ? 4114 : 8228);
    lPitch = scale(pBonkle->pitch, dmulscale30r(cosVal, earVL.dx, sinVal, earVL.dy) + 5853, v8 + 5853);
    if (lPitch < 0 || lPitch > pBonkle->pitch * 4)
        lPitch = pBonkle->pitch;

    int distanceR = approxDist(pBonkle->curPos.x - earR.x, pBonkle->curPos.y - earR.y);
    rVol = Vol3d(angle - (gMe->pSprite->ang + 85), v14);
    int phaseRight = mulscale16r(distanceR, pBonkle->format == 1 ? 4114 : 8228);
    rPitch = scale(pBonkle->pitch, dmulscale30r(cosVal, earVR.dx, sinVal, earVR.dy) + 5853, v8 + 5853);
    if (rPitch < 0 || rPitch > pBonkle->pitch * 4)
        rPitch = pBonkle->pitch;

    int phaseMin = ClipHigh(phaseLeft, phaseRight);
    lPhase = phaseRight - phaseMin;
    rPhase = phaseLeft - phaseMin;
}

void sfxPlay3DSound(int x, int y, int z, int soundId, int nSector)
{
    if (!SoundToggle || soundId < 0) return;

    DICTNODE *hRes = gSoundRes.Lookup(soundId, "SFX");
    if (!hRes)return;

    SFX *pEffect = (SFX*)gSoundRes.Load(hRes);
    hRes = gSoundRes.Lookup(pEffect->rawName, "RAW");
    if (!hRes) return;

    int v1c, v18;
    v1c = v18 = mulscale16(pEffect->pitch, sndGetRate(pEffect->format));
    if (nBonkles >= 256)
        return;
    BONKLE *pBonkle = BonkleCache[nBonkles++];
    pBonkle->pSndSpr = NULL;
    pBonkle->curPos.x = x;
    pBonkle->curPos.y = y;
    pBonkle->curPos.z = z;
    pBonkle->sectnum = nSector;
    FindSector(x, y, z, &pBonkle->sectnum);
    pBonkle->oldPos = pBonkle->curPos;
    pBonkle->sfxId = soundId;
    pBonkle->hSnd = hRes;
    pBonkle->vol = pEffect->relVol;
    pBonkle->pitch = v18;
    pBonkle->format = pEffect->format;
    int size = hRes->size;
    char *pData = (char*)gSoundRes.Lock(hRes);
    Calc3DValues(pBonkle);
    int priority = 1;
    if (priority < lVol)
        priority = lVol;
    if (priority < rVol)
        priority = rVol;
    if (gStereo)
    {
        // MV_Lock();
        pBonkle->lChan = FX_PlayRaw(pData + lPhase, size - lPhase, lPitch, 0, lVol, lVol, 0, priority, fix16_one, (intptr_t)&pBonkle->lChan);
        pBonkle->rChan = FX_PlayRaw(pData + rPhase, size - rPhase, rPitch, 0, rVol, 0, rVol, priority, fix16_one, (intptr_t)&pBonkle->rChan);
        // MV_Unlock();
    }
    else
    {
        pBonkle->lChan = FX_PlayRaw(pData + lPhase, size - lPhase, v1c, 0, lVol, lVol, rVol, priority, fix16_one, (intptr_t)&pBonkle->lChan);
        pBonkle->rChan = 0;
    }
}

void sfxPlay3DSound(spritetype *pSprite, int soundId, int chanId, int nFlags)
{
    if (!SoundToggle)
        return;
    if (!pSprite)
        return;
    if (soundId < 0)
        return;
    DICTNODE *hRes = gSoundRes.Lookup(soundId, "SFX");
    if (!hRes)
        return;

    SFX *pEffect = (SFX*)gSoundRes.Load(hRes);
    hRes = gSoundRes.Lookup(pEffect->rawName, "RAW");
    if (!hRes)
        return;
    int size = hRes->size;
    if (size <= 0)
        return;
    int v14;
    v14 = mulscale16(pEffect->pitch, sndGetRate(pEffect->format));
    BONKLE *pBonkle = NULL;
    if (chanId >= 0)
    {
        int i;
        for (i = 0; i < nBonkles; i++)
        {
            pBonkle = BonkleCache[i];
            if (pBonkle->chanId == chanId && (pBonkle->pSndSpr == pSprite || (nFlags & 1) != 0))
            {
                if ((nFlags & 4) != 0 && pBonkle->chanId == chanId)
                    return;
                if ((nFlags & 2) != 0 && pBonkle->sfxId == soundId)
                    return;
                if (pBonkle->lChan > 0)
                    FX_StopSound(pBonkle->lChan);
                if (pBonkle->rChan > 0)
                    FX_StopSound(pBonkle->rChan);
                if (pBonkle->hSnd)
                {
                    gSoundRes.Unlock(pBonkle->hSnd);
                    pBonkle->hSnd = NULL;
                }
                break;
            }
        }
        if (i == nBonkles)
        {
            if (nBonkles >= 256)
                return;
            pBonkle = BonkleCache[nBonkles++];
        }
        pBonkle->pSndSpr = pSprite;
        pBonkle->chanId = chanId;
    }
    else
    {
        if (nBonkles >= 256)
            return;
        pBonkle = BonkleCache[nBonkles++];
        pBonkle->pSndSpr = NULL;
    }
    pBonkle->curPos.x = pSprite->x;
    pBonkle->curPos.y = pSprite->y;
    pBonkle->curPos.z = pSprite->z;
    pBonkle->sectnum = pSprite->sectnum;
    pBonkle->oldPos = pBonkle->curPos;
    pBonkle->sfxId = soundId;
    pBonkle->hSnd = hRes;
    pBonkle->vol = pEffect->relVol;
    pBonkle->pitch = v14;
    Calc3DValues(pBonkle);
    int priority = 1;
    if (priority < lVol)
        priority = lVol;
    if (priority < rVol)
        priority = rVol;
    int loopStart = pEffect->loopStart;
    int loopEnd = ClipLow(size - 1, 0);
    if (chanId < 0)
        loopStart = -1;
    // MV_Lock();
    char *pData = (char*)gSoundRes.Lock(hRes);
    if (loopStart >= 0)
    {
        if (gStereo)
        {
            pBonkle->lChan = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, lPitch, 0, lVol, lVol, 0, priority, fix16_one, (intptr_t)&pBonkle->lChan);
            pBonkle->rChan = FX_PlayLoopedRaw(pData + rPhase, size - rPhase, pData + loopStart, pData + loopEnd, rPitch, 0, rVol, 0, rVol, priority, fix16_one, (intptr_t)&pBonkle->rChan);
        }
        else
        {
            pBonkle->lChan = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, v14, 0, lVol, lVol, rVol, priority, fix16_one, (intptr_t)&pBonkle->lChan);
            pBonkle->rChan = 0;
        }
    }
    else
    {
        pData = (char*)gSoundRes.Lock(pBonkle->hSnd);
        if (gStereo)
        {
            pBonkle->lChan = FX_PlayRaw(pData + lPhase, size - lPhase, lPitch, 0, lVol, lVol, 0, priority, fix16_one, (intptr_t)&pBonkle->lChan);
            pBonkle->rChan = FX_PlayRaw(pData + rPhase, size - rPhase, rPitch, 0, rVol, 0, rVol, priority, fix16_one, (intptr_t)&pBonkle->rChan);
        }
        else
        {
            pBonkle->lChan = FX_PlayRaw(pData + lPhase, size - lPhase, v14, 0, lVol, lVol, rVol, priority, fix16_one, (intptr_t)&pBonkle->lChan);
            pBonkle->rChan = 0;
        }
    }
    // MV_Unlock();
}

// by NoOne: same as previous, but allows to set custom pitch for sound AND volume.
void sfxPlay3DSoundCP(spritetype* pSprite, int soundId, int chanId, int nFlags, int pitch, int volume)
{
    if (!SoundToggle || !pSprite || soundId < 0) return;
    DICTNODE* hRes = gSoundRes.Lookup(soundId, "SFX");
    if (!hRes) return;

    SFX* pEffect = (SFX*)gSoundRes.Load(hRes);
    hRes = gSoundRes.Lookup(pEffect->rawName, "RAW");
    if (!hRes) return;
    int size = hRes->size;
    if (size <= 0) return;

    if (pitch <= 0) pitch = pEffect->pitch;
    else pitch -= QRandom(pEffect->pitchRange);

    int v14;
    v14 = mulscale16(pitch, sndGetRate(pEffect->format));

    BONKLE * pBonkle = NULL;
    if (chanId >= 0)
    {
        int i;
        for (i = 0; i < nBonkles; i++)
        {
            pBonkle = BonkleCache[i];
            if (pBonkle->chanId == chanId && (pBonkle->pSndSpr == pSprite || (nFlags & 1) != 0))
            {
                if ((nFlags & 4) != 0 && pBonkle->chanId == chanId)
                    return;
                if ((nFlags & 2) != 0 && pBonkle->sfxId == soundId)
                    return;
                if (pBonkle->lChan > 0)
                    FX_StopSound(pBonkle->lChan);
                if (pBonkle->rChan > 0)
                    FX_StopSound(pBonkle->rChan);
                if (pBonkle->hSnd)
                {
                    gSoundRes.Unlock(pBonkle->hSnd);
                    pBonkle->hSnd = NULL;
                }
                break;
            }
        }
        if (i == nBonkles)
        {
            if (nBonkles >= 256)
                return;
            pBonkle = BonkleCache[nBonkles++];
        }
        pBonkle->pSndSpr = pSprite;
        pBonkle->chanId = chanId;
    }
    else
    {
        if (nBonkles >= 256)
            return;
        pBonkle = BonkleCache[nBonkles++];
        pBonkle->pSndSpr = NULL;
    }
    pBonkle->curPos.x = pSprite->x;
    pBonkle->curPos.y = pSprite->y;
    pBonkle->curPos.z = pSprite->z;
    pBonkle->sectnum = pSprite->sectnum;
    pBonkle->oldPos = pBonkle->curPos;
    pBonkle->sfxId = soundId;
    pBonkle->hSnd = hRes;
    pBonkle->vol = ((volume == 0) ? pEffect->relVol : ((volume == -1) ? 0 : ((volume > 255) ? 255 : volume)));
    pBonkle->pitch = v14;
    Calc3DValues(pBonkle);
    int priority = 1;
    if (priority < lVol)
        priority = lVol;
    if (priority < rVol)
        priority = rVol;
    int loopStart = pEffect->loopStart;
    int loopEnd = ClipLow(size - 1, 0);
    if (chanId < 0)
        loopStart = -1;
    // MV_Lock();
    char* pData = (char*)gSoundRes.Lock(hRes);
    if (loopStart >= 0)
    {
        if (gStereo)
        {
            pBonkle->lChan = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, lPitch, 0, lVol, lVol, 0, priority, fix16_one, (intptr_t)& pBonkle->lChan);
            pBonkle->rChan = FX_PlayLoopedRaw(pData + rPhase, size - rPhase, pData + loopStart, pData + loopEnd, rPitch, 0, rVol, 0, rVol, priority, fix16_one, (intptr_t)& pBonkle->rChan);
        }
        else
        {
            pBonkle->lChan = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, v14, 0, lVol, lVol, rVol, priority, fix16_one, (intptr_t)& pBonkle->lChan);
            pBonkle->rChan = 0;
        }
    }
    else
    {
        pData = (char*)gSoundRes.Lock(pBonkle->hSnd);
        if (gStereo)
        {
            pBonkle->lChan = FX_PlayRaw(pData + lPhase, size - lPhase, lPitch, 0, lVol, lVol, 0, priority, fix16_one, (intptr_t)& pBonkle->lChan);
            pBonkle->rChan = FX_PlayRaw(pData + rPhase, size - rPhase, rPitch, 0, rVol, 0, rVol, priority, fix16_one, (intptr_t)& pBonkle->rChan);
        }
        else
        {
            pBonkle->lChan = FX_PlayRaw(pData + lPhase, size - lPhase, v14, 0, lVol, lVol, rVol, priority, fix16_one, (intptr_t)& pBonkle->lChan);
            pBonkle->rChan = 0;
        }
    }
    // MV_Unlock();
}

EXTERN_INLINE void sfxKillSoundInternal(int i)
{
    BONKLE *pBonkle = BonkleCache[i];
    if (pBonkle->lChan > 0)
    {
        FX_EndLooping(pBonkle->lChan);
        FX_StopSound(pBonkle->lChan);
    }
    if (pBonkle->rChan > 0)
    {
        FX_EndLooping(pBonkle->rChan);
        FX_StopSound(pBonkle->rChan);
    }
    if (pBonkle->hSnd)
    {
        gSoundRes.Unlock(pBonkle->hSnd);
        pBonkle->hSnd = NULL;
    }
    BonkleCache[i] = BonkleCache[--nBonkles];
    BonkleCache[nBonkles] = pBonkle;
}

void sfxKill3DSound(spritetype *pSprite, int chanId, int soundId)
{
    if (!pSprite)
        return;
    for (int i = nBonkles - 1; i >= 0; i--)
    {
        BONKLE *pBonkle = BonkleCache[i];
        if (pBonkle->pSndSpr == pSprite && (chanId < 0 || chanId == pBonkle->chanId) && (soundId < 0 || soundId == pBonkle->sfxId))
        {
            sfxKillSoundInternal(i);
            break;
        }
    }
}

void sfxKillAllSounds(void)
{
    for (int i = nBonkles - 1; i >= 0; i--)
    {
        sfxKillSoundInternal(i);
    }
}

void sfxKillSpriteSounds(spritetype *pSprite)
{
    if (!pSprite)
        return;
    for (int i = nBonkles - 1; i >= 0; i--)
    {
        BONKLE *pBonkle = BonkleCache[i];
        if (pBonkle->pSndSpr == pSprite)
        {
            sfxKillSoundInternal(i);
        }
    }
}

void sfxUpdate3DSounds(void)
{
    int dx = mulscale30(Cos(gMe->pSprite->ang + 512), 43);
    earL0 = earL;
    int dy = mulscale30(Sin(gMe->pSprite->ang + 512), 43);
    earR0 = earR;
    earL.x = gMe->pSprite->x - dx;
    earL.y = gMe->pSprite->y - dy;
    earR.x = gMe->pSprite->x + dx;
    earR.y = gMe->pSprite->y + dy;
    earVL.dx = earL.x - earL0.x;
    earVL.dy = earL.y - earL0.y;
    earVR.dx = earR.x - earR0.x;
    earVR.dy = earR.y - earR0.y;
    for (int i = nBonkles - 1; i >= 0; i--)
    {
        BONKLE *pBonkle = BonkleCache[i];
        if (pBonkle->lChan > 0 || pBonkle->rChan > 0)
        {
            if (!pBonkle->hSnd)
                continue;
            if (pBonkle->pSndSpr)
            {
                pBonkle->oldPos = pBonkle->curPos;
                pBonkle->curPos.x = pBonkle->pSndSpr->x;
                pBonkle->curPos.y = pBonkle->pSndSpr->y;
                pBonkle->curPos.z = pBonkle->pSndSpr->z;
                pBonkle->sectnum = pBonkle->pSndSpr->sectnum;
            }
            Calc3DValues(pBonkle);
            // MV_Lock();
            if (pBonkle->lChan > 0)
            {
                if (pBonkle->rChan > 0)
                {
                    FX_SetPan(pBonkle->lChan, lVol, lVol, 0);
                    FX_SetFrequency(pBonkle->lChan, lPitch);
                }
                else
                    FX_SetPan(pBonkle->lChan, lVol, lVol, rVol);
            }
            if (pBonkle->rChan > 0)
            {
                FX_SetPan(pBonkle->rChan, rVol, 0, rVol);
                FX_SetFrequency(pBonkle->rChan, rPitch);
            }
            // MV_Unlock();
        }
        else
        {
            gSoundRes.Unlock(pBonkle->hSnd);
            pBonkle->hSnd = NULL;
            BonkleCache[i] = BonkleCache[--nBonkles];
            BonkleCache[nBonkles] = pBonkle;
        }
    }
}

void sfxSetReverb(bool toggle)
{
    if (toggle)
    {
        FX_SetReverb(128);
        FX_SetReverbDelay(10);
    }
    else
        FX_SetReverb(0);
}

void sfxSetReverb2(bool toggle)
{
    if (toggle)
    {
        FX_SetReverb(128);
        FX_SetReverbDelay(20);
    }
    else
        FX_SetReverb(0);
}


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 "db.h"

struct BONKLE
{
    int lChan; // in terms of audio, left channel is main channel when using mono
    int rChan;
    DICTNODE* hSnd;
    int sfxId;
    spritetype* pSndSpr;
    int chanId;
    int pitch;
    int vol;
    POINT3D curPos;
    POINT3D oldPos;
    int sectnum;
    int format;
};

extern BONKLE Bonkle[256];
extern BONKLE* BonkleCache[256];

void sfxInit(void);
void sfxTerm(void);
void sfxPlay3DSound(int x, int y, int z, int soundId, int nSector);
void sfxPlay3DSound(spritetype *pSprite, int soundId, int chanId = -1, int nFlags = 0);
void sfxPlay3DSoundCP(spritetype* pSprite, int soundId, int chanId = -1, int nFlags = 0, int pitch = 0, int volume = 0);
void sfxKill3DSound(spritetype *pSprite, int chanId = -1, int soundId = -1);
void sfxKillAllSounds(void);
void sfxKillSpriteSounds(spritetype *pSprite);
void sfxUpdate3DSounds(void);
void sfxSetReverb(bool toggle);
void sfxSetReverb2(bool toggle);