code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "build.h"

#include "names2.h"
#include "panel.h"
#include "game.h"
#include "network.h"
#include "tags.h"
#include "slidor.h"
#include "sector.h"
#include "interp.h"
#include "text.h"
#include "sprite.h"

void ReverseSlidor(short SpriteNum)
{
    USERp u = User[SpriteNum];
    ROTATORp r;

    r = u->rotator;

    // if paused go ahead and start it up again
    if (u->Tics)
    {
        u->Tics = 0;
        SetSlidorActive(SpriteNum);
        return;
    }

    // moving toward to OFF pos
    if (r->tgt == 0)
    {
        r->tgt = r->open_dest;
    }
    else if (r->tgt == r->open_dest)
    {
        r->tgt = 0;
    }

    r->vel = -r->vel;
}

SWBOOL
SlidorSwitch(short match, short setting)
{
    SPRITEp sp;
    short i,nexti;
    SWBOOL found = FALSE;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_DEFAULT], i, nexti)
    {
        sp = &sprite[i];

        if (sp->lotag == TAG_SPRITE_SWITCH_VATOR && sp->hitag == match)
        {
            found = TRUE;
            AnimateSwitch(sp, setting);
        }
    }

    return found;
}

void SetSlidorActive(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;
    ROTATORp r;

    r = u->rotator;

    DoSlidorInterp(SpriteNum, setinterpolation);

    // play activate sound
    DoSoundSpotMatch(SP_TAG2(sp), 1, SOUND_OBJECT_TYPE);

    SET(u->Flags, SPR_ACTIVE);
    u->Tics = 0;

    // moving to the OFF position
    if (r->tgt == 0)
        VatorSwitch(SP_TAG2(sp), OFF);
    else
        VatorSwitch(SP_TAG2(sp), ON);
}

void SetSlidorInactive(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;

    DoSlidorInterp(SpriteNum, stopinterpolation);

    // play inactivate sound
    DoSoundSpotMatch(SP_TAG2(sp), 2, SOUND_OBJECT_TYPE);

    RESET(u->Flags, SPR_ACTIVE);
}

// called for operation from the space bar
short DoSlidorOperate(PLAYERp pp, short sectnum)
{
    short match;

    match = sector[sectnum].hitag;

    if (match > 0)
    {
        if (TestSlidorMatchActive(match))
            return -1;
        else
            return DoSlidorMatch(pp, match, TRUE);
    }

    return -1;
}

// called from switches and triggers
// returns first vator found
short
DoSlidorMatch(PLAYERp pp, short match, SWBOOL manual)
{
    USERp fu;
    SPRITEp fsp;
    short sectnum;
    short first_vator = -1;

    short i,nexti;

    //SlidorSwitch(match, ON);

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_SLIDOR], i, nexti)
    {
        fsp = &sprite[i];

        if (SP_TAG1(fsp) == SECT_SLIDOR && SP_TAG2(fsp) == match)
        {
            fu = User[i];

            // single play only vator
            // SWBOOL 8 must be set for message to display
            if (TEST_BOOL4(fsp) && (gNet.MultiGameType == MULTI_GAME_COMMBAT || gNet.MultiGameType == MULTI_GAME_AI_BOTS))
            {
                if (pp && TEST_BOOL11(fsp)) PutStringInfo(pp,"This only opens in single play.");
                continue;
            }

            // switch trigger only
            if (SP_TAG3(fsp) == 1)
            {
                // tried to manually operat a switch/trigger only
                if (manual)
                    continue;
            }

            if (first_vator == -1)
                first_vator = i;

            sectnum = fsp->sectnum;

            if (pp && SectUser[sectnum] && SectUser[sectnum]->stag == SECT_LOCK_DOOR && SectUser[sectnum]->number)
            {
                short key_num;

                key_num = SectUser[sectnum]->number;

#if 0
                if (pp->HasKey[key_num - 1])
                {
                    int i;
                    for (i=0; i<numsectors; i++)
                    {
                        if (SectUser[i] && SectUser[i]->stag == SECT_LOCK_DOOR && SectUser[i]->number == key_num)
                            SectUser[i]->number = 0;  // unlock all doors of this type
                    }
                    UnlockKeyLock(key_num);
                }
                else
#endif
                {
                    PutStringInfo(pp, KeyDoorMessage[key_num - 1]);
                    return -1;
                }
            }

            if (TEST(fu->Flags, SPR_ACTIVE))
            {
                ReverseSlidor(i);
                continue;
            }

            SetSlidorActive(i);
        }
    }

    return first_vator;
}

SWBOOL
TestSlidorMatchActive(short match)
{
    USERp fu;
    SPRITEp fsp;

    short i,nexti;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_SLIDOR], i, nexti)
    {
        fsp = &sprite[i];

        if (SP_TAG1(fsp) == SECT_SLIDOR && SP_TAG2(fsp) == match)
        {
            fu = User[i];

            // Does not have to be inactive to be operated
            if (TEST_BOOL6(fsp))
                continue;

            if (TEST(fu->Flags, SPR_ACTIVE) || fu->Tics)
                return TRUE;
        }
    }

    return FALSE;
}

void DoSlidorInterp(short SpriteNum, INTERP_FUNCp interp_func)
{
    short w, pw, startwall, endwall;

    w = startwall = sector[sprite[SpriteNum].sectnum].wallptr;
    endwall = startwall + sector[sprite[SpriteNum].sectnum].wallnum - 1;

    do
    {
        switch (wall[w].lotag)
        {
        case TAG_WALL_SLIDOR_LEFT:
        {
            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            uint16_t const nextwall = wall[w].nextwall;
            if (nextwall >= MAXWALLS)
            {
                // white wall - move 4 points
                interp_func(&wall[w].x);
                interp_func(&wall[pw].x);
                interp_func(&wall[wall[w].point2].x);
                interp_func(&wall[wall[wall[w].point2].point2].x);
            }
            else
            {
                // red wall - move 2 points
                interp_func(&wall[w].x);
                interp_func(&wall[wall[nextwall].point2].x);
                interp_func(&wall[wall[w].point2].x);
                interp_func(&wall[wall[wall[wall[w].point2].nextwall].point2].x);
            }

            break;
        }

        case TAG_WALL_SLIDOR_RIGHT:
        {
            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            uint16_t const nextwall = wall[w].nextwall;
            if (nextwall >= MAXWALLS)
            {
                // white wall - move 4 points
                interp_func(&wall[w].x);
                interp_func(&wall[pw].x);
                interp_func(&wall[wall[w].point2].x);
                interp_func(&wall[wall[wall[w].point2].point2].x);
            }
            else
            {
                // red wall - move 2 points
                interp_func(&wall[w].x);
                interp_func(&wall[wall[nextwall].point2].x);
                interp_func(&wall[wall[w].point2].x);
                interp_func(&wall[wall[wall[wall[w].point2].nextwall].point2].x);
            }

            break;
        }

        case TAG_WALL_SLIDOR_UP:
        {
            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            uint16_t const nextwall = wall[w].nextwall;
            if (nextwall >= MAXWALLS)
            {
                interp_func(&wall[w].y);
                interp_func(&wall[pw].y);
                interp_func(&wall[wall[w].point2].y);
                interp_func(&wall[wall[wall[w].point2].point2].y);
            }
            else
            {
                interp_func(&wall[w].y);
                interp_func(&wall[wall[nextwall].point2].y);
                interp_func(&wall[wall[w].point2].y);
                interp_func(&wall[wall[wall[wall[w].point2].nextwall].point2].y);
            }

            break;
        }

        case TAG_WALL_SLIDOR_DOWN:
        {
            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            uint16_t const nextwall = wall[w].nextwall;
            if (nextwall >= MAXWALLS)
            {
                interp_func(&wall[w].y);
                interp_func(&wall[pw].y);
                interp_func(&wall[wall[w].point2].y);
                interp_func(&wall[wall[wall[w].point2].point2].y);
            }
            else
            {
                interp_func(&wall[w].y);
                interp_func(&wall[wall[nextwall].point2].y);
                interp_func(&wall[wall[w].point2].y);
                interp_func(&wall[wall[wall[wall[w].point2].nextwall].point2].y);
            }

            break;
        }
        }

        w = wall[w].point2;
    }
    while (w != startwall);
}

int DoSlidorMoveWalls(short SpriteNum, int amt)
{
    short w, pw, startwall, endwall;

    w = startwall = sector[sprite[SpriteNum].sectnum].wallptr;
    endwall = startwall + sector[sprite[SpriteNum].sectnum].wallnum - 1;

    do
    {
        switch (wall[w].lotag)
        {
        case TAG_WALL_SLIDOR_LEFT:

            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            if ((uint16_t)wall[w].nextwall >= MAXWALLS)
            {
                // white wall - move 4 points
                wall[w].x -= amt;
                wall[pw].x -= amt;
                wall[wall[w].point2].x -= amt;
                wall[wall[wall[w].point2].point2].x -= amt;
            }
            else
            {
                // red wall - move 2 points
                dragpoint(w, wall[w].x - amt, wall[w].y, 0);
                dragpoint(wall[w].point2, wall[wall[w].point2].x - amt, wall[wall[w].point2].y, 0);
            }

            break;

        case TAG_WALL_SLIDOR_RIGHT:

            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            if ((uint16_t)wall[w].nextwall >= MAXWALLS)
            {
                // white wall - move 4 points
                wall[w].x += amt;
                wall[pw].x += amt;
                wall[wall[w].point2].x += amt;
                wall[wall[wall[w].point2].point2].x += amt;
            }
            else
            {
                // red wall - move 2 points
                dragpoint(w, wall[w].x + amt, wall[w].y, 0);
                dragpoint(wall[w].point2, wall[wall[w].point2].x + amt, wall[wall[w].point2].y, 0);
            }

            break;

        case TAG_WALL_SLIDOR_UP:

            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            if ((uint16_t)wall[w].nextwall >= MAXWALLS)
            {
                wall[w].y -= amt;
                wall[pw].y -= amt;
                wall[wall[w].point2].y -= amt;
                wall[wall[wall[w].point2].point2].y -= amt;
            }
            else
            {
                dragpoint(w, wall[w].x, wall[w].y - amt, 0);
                dragpoint(wall[w].point2, wall[wall[w].point2].x, wall[wall[w].point2].y - amt, 0);
            }

            break;

        case TAG_WALL_SLIDOR_DOWN:

            // prev wall
            pw = w - 1;
            if (w < startwall)
                pw = endwall;

            if ((uint16_t)wall[w].nextwall >= MAXWALLS)
            {
                wall[w].y += amt;
                wall[pw].y += amt;
                wall[wall[w].point2].y += amt;
                wall[wall[wall[w].point2].point2].y += amt;
            }
            else
            {
                dragpoint(w, wall[w].x, wall[w].y + amt, 0);
                dragpoint(wall[w].point2, wall[wall[w].point2].x, wall[wall[w].point2].y + amt, 0);
            }

            break;
        }

        w = wall[w].point2;
    }
    while (w != startwall);

    return 0;
}

int DoSlidorInstantClose(short SpriteNum)
{
    SPRITEp sp = &sprite[SpriteNum];
    short w, startwall;
    int diff;

    w = startwall = sector[sprite[SpriteNum].sectnum].wallptr;

    do
    {
        switch (wall[w].lotag)
        {
        case TAG_WALL_SLIDOR_LEFT:
            diff = wall[w].x - sp->x;
            DoSlidorMoveWalls(SpriteNum, diff);
            break;

        case TAG_WALL_SLIDOR_RIGHT:
            diff = wall[w].x - sp->x;
            DoSlidorMoveWalls(SpriteNum, -diff);
            break;

        case TAG_WALL_SLIDOR_UP:
            diff = wall[w].y - sp->y;
            DoSlidorMoveWalls(SpriteNum, diff);
            break;

        case TAG_WALL_SLIDOR_DOWN:
            diff = wall[w].y - sp->y;
            DoSlidorMoveWalls(SpriteNum, -diff);
            break;
        }

        w = wall[w].point2;
    }
    while (w != startwall);

    return 0;
}

int DoSlidorMove(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;
    ROTATORp r;
    int old_pos;
    SWBOOL kill = FALSE;

    r = u->rotator;

    // Example - ang pos moves from 0 to 512 <<OR>> from 0 to -512

    old_pos = r->pos;

    // control SPEED of swinging
    if (r->pos < r->tgt)
    {
        // Increment swing angle
        r->pos += r->speed;
        r->speed += r->vel;

        // if the other way make it equal
        if (r->pos > r->tgt)
            r->pos = r->tgt;
    }

    if (r->pos > r->tgt)
    {
        // Increment swing angle
        r->pos -= r->speed;
        r->speed += r->vel;

        // if the other way make it equal
        if (r->pos < r->tgt)
            r->pos = r->tgt;
    }

    if (r->pos == r->tgt)
    {
        // If ang is OPEN
        if (r->pos == r->open_dest)
        {
            // new tgt is CLOSED (0)
            r->tgt = 0;
            r->vel = -r->vel;
            SetSlidorInactive(SpriteNum);

            if (SP_TAG6(sp) && !TEST_BOOL8(sp))
                DoMatchEverything(NULL, SP_TAG6(sp), -1);

            // wait a bit and close it
            if (u->WaitTics)
                u->Tics = u->WaitTics;
        }
        else
        // If ang is CLOSED then
        if (r->pos == 0)
        {
            short match = SP_TAG2(sp);

            // new tgt is OPEN (open)
            r->speed = r->orig_speed;
            r->vel = labs(r->vel);

            r->tgt = r->open_dest;
            SetSlidorInactive(SpriteNum);

            RESET_BOOL8(sp);

            // set owner swith back to OFF
            // only if ALL vators are inactive
            if (!TestSlidorMatchActive(match))
            {
                //SlidorSwitch(match, OFF);
            }

            if (SP_TAG6(sp) && TEST_BOOL8(sp))
                DoMatchEverything(NULL, SP_TAG6(sp), -1);
        }

        if (TEST_BOOL2(sp))
            kill = TRUE;
    }
    else
    {
        // if heading for the OFF (original) position and should NOT CRUSH
        if (TEST_BOOL3(sp) && r->tgt == 0)
        {
            int i,nexti;
            SPRITEp bsp;
            USERp bu;
            SWBOOL found = FALSE;

            TRAVERSE_SPRITE_SECT(headspritesect[sp->sectnum], i, nexti)
            {
                bsp = &sprite[i];
                bu = User[i];

                if (bu && TEST(bsp->cstat, CSTAT_SPRITE_BLOCK) && TEST(bsp->extra, SPRX_PLAYER_OR_ENEMY))
                {
                    // found something blocking so reverse to ON position
                    ReverseSlidor(SpriteNum);
                    SET_BOOL8(sp); // tell vator that something blocking door
                    found = TRUE;
                    break;
                }
            }

            if (!found)
            {
                short pnum;
                PLAYERp pp;
                // go ahead and look for players clip box bounds
                TRAVERSE_CONNECT(pnum)
                {
                    pp = Player + pnum;

                    if (pp->lo_sectp == &sector[sp->sectnum] ||
                        pp->hi_sectp == &sector[sp->sectnum])
                    {
                        ReverseSlidor(SpriteNum);

                        u->vel_rate = -u->vel_rate;
                        found = TRUE;
                    }
                }
            }
        }
    }

    DoSlidorMoveWalls(SpriteNum, r->pos - old_pos);

    if (kill)
    {
        SetSlidorInactive(SpriteNum);
        KillSprite(SpriteNum);
        return 0;
    }

    return 0;
}

int DoSlidor(short SpriteNum)
{
    DoSlidorMove(SpriteNum);

    return 0;
}

#include "saveable.h"

static saveable_code saveable_slidor_code[] =
{
    SAVE_CODE(ReverseSlidor),
    SAVE_CODE(SlidorSwitch),
    SAVE_CODE(SetSlidorActive),
    SAVE_CODE(SetSlidorInactive),
    SAVE_CODE(DoSlidorOperate),
    SAVE_CODE(DoSlidorMatch),
    SAVE_CODE(TestSlidorMatchActive),
    SAVE_CODE(DoSlidorInterp),
    SAVE_CODE(DoSlidorMoveWalls),
    SAVE_CODE(DoSlidorInstantClose),
    SAVE_CODE(DoSlidorMove),
    SAVE_CODE(DoSlidor),
};

saveable_module saveable_slidor =
{
    // code
    saveable_slidor_code,
    SIZ(saveable_slidor_code),

    // data
    NULL,0
};


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#ifndef SLIDOR_PUBLIC_
#define SLIDOR_PUBLIC_

short DoSlidorMatch(PLAYERp pp, short match, SWBOOL);
SWBOOL TestSlidorMatchActive(short match);
void InterpSectorSprites(short sectnum, SWBOOL state);

typedef void INTERP_FUNC (int *);
typedef INTERP_FUNC *INTERP_FUNCp;

void SetSlidorActive(short SpriteNum);
void DoSlidorInterp(short, INTERP_FUNCp);

#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "compat.h"
#include "build.h"
#include "cache1d.h"

#include "keys.h"

#include "names2.h"
#include "mytypes.h"
#include "fx_man.h"
#include "music.h"
#include "al_midi.h"
#include "gamedefs.h"
#include "config.h"

#include "panel.h"
#include "game.h"
#include "sounds.h"
#include "ai.h"
#include "network.h"

#include "cache.h"
#include "text.h"
#include "rts.h"
#include "menus.h"
#include "config.h"

#ifdef _WIN32
#include "winlayer.h"
#endif

extern USERp User[MAXSPRITES];
void DumpSounds(void);

// Parentally locked sounds list
int PLocked_Sounds[] =
{
    483,328,334,335,338,478,450,454,452,453,456,457,458,455,460,462,
    461,464,466,465,467,463,342,371,254,347,350,432,488,489,490,76,339,
    499,500,506,479,480,481,482,78,600,467,548,547,544,546,545,542,541,540,
    539,536,529,525,522,521,515,516,612,611,589,625,570,569,567,565,
    558,557
};

uint8_t RedBookSong[40] =
{
    2,4,9,12,10, // Title and ShareWare levels
    5,6,8,11,12,5,10,4,6,9,7,10,8,7,9,10,11,5, // Registered levels
    11,8,7,13,5,6,  // Deathmatch levels
    13 // Fight boss
};

// Global vars used by ambient sounds to set spritenum of ambient sounds for later lookups in
// the sprite array so FAFcansee can know the sound sprite's current sector location
SWBOOL Use_SoundSpriteNum = FALSE;
int16_t SoundSpriteNum = -1;  // Always set this back to -1 for proper validity checking!

SWBOOL MusicInitialized = FALSE;
SWBOOL FxInitialized = FALSE;

void SoundCallBack(unsigned int num);
SWBOOL LoadSong(const char *track);

#define MUSIC_ID -65536

#define NUM_SAMPLES 10

const char *BitNames[2] =
{
    "8-bit", "16-bit"
};

const char *ChannelNames[2] =
{
    "Mono", "Stereo"
};

const char *VoiceNames[8] =
{
    "1", "2", "3", "4", "5", "6", "7", "8"
};

int music;
int soundfx;
int num_voices;

int NumSounds = 0;

int angle;
int distance;
int voice;

int loopflag;

typedef enum
{
    SongTypeNone,
    SongTypeMIDI,
    SongTypeWave,
} SongType_t;

char *SongPtr = NULL;
int SongLength = 0;
char *SongName = NULL;
int8_t SongTrack = 0;
SongType_t SongType = SongTypeNone;
int SongVoice = -1;
extern SWBOOL DemoMode;

//
// Includes digi.h to build the table
//

#define DIGI_TABLE
VOC_INFO voc[] =
{
#include "digi.h"
};

#undef  DIGI_TABLE

//
// Includes ambient.h to build the table of ambient sounds for game
//

#define AMBIENT_TABLE
AMB_INFO ambarray[] =
{
#include "ambient.h"
};
#undef  AMBIENT_TABLE
#define MAX_AMBIENT_SOUNDS 82

#define MAXSONGS        10              // This is the max songs per episode

SWBOOL OpenSound(VOC_INFOp vp, int *handle, int *length);
int ReadSound(int handle, VOC_INFOp vp, int length);

// 3d sound engine function prototype
VOC3D_INFOp Insert3DSound(void);

#if 0
// DEBUG
char *globsndata[DIGI_MAX], *globvpdata[DIGI_MAX];
int glength[DIGI_MAX];
#endif

/*
===================
=
= My stuff
=
===================
*/

int PlayerPainVocs[] =
{
    DIGI_PLAYERPAIN1,
    DIGI_PLAYERPAIN2,
    DIGI_PLAYERPAIN3,
    DIGI_PLAYERPAIN4,
    DIGI_PLAYERPAIN5
};

// Don't have these sounds yet
int PlayerLowHealthPainVocs[] =
{
    DIGI_HURTBAD1,
    DIGI_HURTBAD2,
    DIGI_HURTBAD3,
    DIGI_HURTBAD4,
    DIGI_HURTBAD5
};

int TauntAIVocs[] =
{
    DIGI_TAUNTAI1,
    DIGI_TAUNTAI2,
    DIGI_TAUNTAI3,
    DIGI_TAUNTAI4,
    DIGI_TAUNTAI5,
    DIGI_TAUNTAI6,
    DIGI_TAUNTAI7,
    DIGI_TAUNTAI8,
    DIGI_TAUNTAI9,
    DIGI_TAUNTAI10,
    DIGI_COWABUNGA,
    DIGI_NOCHARADE,
    DIGI_TIMETODIE,
    DIGI_EATTHIS,
    DIGI_FIRECRACKERUPASS,
    DIGI_HOLYCOW,
    DIGI_HAHA2,
    DIGI_HOLYPEICESOFCOW,
    DIGI_HOLYSHIT,
    DIGI_HOLYPEICESOFSHIT,
    DIGI_PAYINGATTENTION,
    DIGI_EVERYBODYDEAD,
    DIGI_KUNGFU,
    DIGI_HOWYOULIKEMOVE,
    DIGI_HAHA3,
    DIGI_NOMESSWITHWANG,
    DIGI_RAWREVENGE,
    DIGI_YOULOOKSTUPID,
    DIGI_TINYDICK,
    DIGI_NOTOURNAMENT,
    DIGI_WHOWANTSWANG,
    DIGI_MOVELIKEYAK,
    DIGI_ALLINREFLEXES
};

int PlayerGetItemVocs[] =
{
    DIGI_GOTITEM1,
    DIGI_HAHA1,
    DIGI_BANZAI,
    DIGI_COWABUNGA,
    DIGI_TIMETODIE
};

int PlayerYellVocs[] =
{
    DIGI_PLAYERYELL1,
    DIGI_PLAYERYELL2,
    DIGI_PLAYERYELL3
};

extern char lumplockbyte[];

#if 0
// DEBUG
void CheckSndData(char *file, int line)
{
    short i;

    //return;

    for (i = 0; i<DIGI_MAX; i++)
    {
        if (!globsndata[i] && !globvpdata[i]) continue;

        if (memcmp(globsndata[i], globvpdata[i], glength[i]) != 0)
        {
            printf("%s %d\n",file,line);
            printf("CheckSndData: Data is not the same! num = %d",i);
            exit(0);
        }
    }
}
#endif

//
// Routine called when a sound is finished playing
//

void
SoundCallBack(intptr_t num)
{
    VOC_INFOp vp;

    if ((int) num == MUSIC_ID)
    {
        return;
    }

    // RTS sounds are negative
    if ((int)num < 0)
    {
        ASSERT(-num < 11);
        lumplockbyte[-num]--;
        return;
    }

    vp = &voc[num];

    // Update counter
    //vp->playing--;
    vp->lock--;
}

//

void
ClearSoundLocks(void)
{
    unsigned i;

    for (i = 0; i < SIZ(voc); i++)
    {
        if (voc[i].lock >= 200)
            voc[i].lock = 199;
    }

    for (i = 0; i < 11; i++)
    {
        if (lumplockbyte[i] >= 200)
            lumplockbyte[i] = 199;
    }
}

void
UnInitSound(void)
{
    SoundShutdown();
    MusicShutdown();
}

void
InitFX(void)
{
    VOC_INFOp vp;

#if 0
    // DEBUG
    short i;
    for (i=0; i<DIGI_MAX; i++)
    {
        globsndata[i] = globvpdata[i] = NULL;
        glength[i] = 0;
    }
#endif

    //ExternalSoundMod();

    // Select which cards to use
    SoundStartup();

    // Get the current volume of the music and sound fx
    gs.SoundVolume = FX_GetVolume();

    for (vp = voc; vp < &voc[SIZ(voc)]; vp++)
    {
        vp->playing = 0;
    }

    // Set up our fx callback so we can display the sounds that are playing
    FX_SetCallBack(SoundCallBack);
}

void
InitMusic(void)
{
    // Select which cards to use
    MusicStartup();
    //SendGeneralMidiSysX();
}

void
ExternalSoundMod(void)
{
    FILE *fin;
    VOC_INFOp vp;
    char name[40];
    char new_name[40];
//    int pri;
    int pitch_lo, pitch_hi;
    int ret;

    fin = fopen("swextern.snd", "r");

    if (!fin)
        return;

    while (TRUE)
    {
        ret = fscanf(fin, "%s %s %d %d", name, new_name, &pitch_lo, &pitch_hi);

        if (ret == EOF)
            break;

        for (vp = voc; vp < &voc[SIZ(voc)]; vp++)
        {
#if 0
            if (!vp->name)
                continue;
#endif

            if (!Bstrcasecmp(name, vp->name))
            {
                // vp->priority = pri;
                strcpy(vp->name, new_name);
                vp->pitch_lo = pitch_lo;
                vp->pitch_hi = pitch_hi;
            }
        }
    }

    fclose(fin);
}

extern short Level;

static inline SWBOOL LoadSongUpgrade(char const * fn)
{
#if defined HAVE_FLAC || defined HAVE_VORBIS
    static char const * extensions[] =
    {
#ifdef HAVE_FLAC
        ".flac",
#endif
#ifdef HAVE_VORBIS
        ".ogg",
#endif
    };

    size_t const len = strlen(fn);
    auto testfn = (char *)Xmalloc(len+5); // 5 = strlen(".flac")
    memcpy(testfn, fn, len+1);
    char * suffix = strrchr(testfn, '.');
    if (suffix == nullptr)
        suffix = testfn + len;

    for (char const * ext : extensions)
    {
        strcpy(suffix, ext);
        SWBOOL const result = LoadSong(testfn);
        if (result)
        {
            Xfree(testfn);
            return result;
        }
    }

    Xfree(testfn);
#endif

    return LoadSong(fn);
}

SWBOOL
PlaySong(char *song_file_name, int cdaudio_track, SWBOOL loop, SWBOOL restart)
{
    if (!gs.MusicOn)
    {
        return FALSE;
    }

    if (DemoMode)
        return FALSE;

    if (!restart)
    {
        if (SongType == SongTypeWave)
        {
            if (SongTrack > 0 && SongTrack == cdaudio_track)
            {
                // ogg replacement for a CD track
                return TRUE;
            }
            else if (SongName && song_file_name && !strcmp(SongName, song_file_name))
            {
                return TRUE;
            }
        }
        else if (SongType == SongTypeMIDI)
        {
            if (SongName && song_file_name && !strcmp(SongName, song_file_name))
            {
                return TRUE;
            }
        }
    }

    StopSong();

    if (!SW_SHAREWARE)
    {
        if (cdaudio_track >= 0)
        {
            char waveformtrack[MAXWAVEFORMTRACKLENGTH];
            Bstrncpy(waveformtrack, gs.WaveformTrackName, MAXWAVEFORMTRACKLENGTH - 1);

            char *numPos = Bstrstr(waveformtrack, "??");

            if (numPos && (numPos-waveformtrack) < MAXWAVEFORMTRACKLENGTH - 2)
            {
                static const char *tracktypes[] = { ".flac", ".ogg" };
                const size_t tracknamebaselen = Bstrlen(waveformtrack);
                size_t i;

                numPos[0] = '0' + (cdaudio_track / 10) % 10;
                numPos[1] = '0' + cdaudio_track % 10;

                for (i = 0; i < ARRAY_SIZE(tracktypes); ++i)
                {
                    waveformtrack[tracknamebaselen] = '\0';
                    Bstrncat(waveformtrack, tracktypes[i], MAXWAVEFORMTRACKLENGTH - 1);

                    if (LoadSong(waveformtrack))
                    {
                        SongVoice = FX_Play(SongPtr, SongLength, loop ? FX_LOOP : FX_ONESHOT, 0, 0,
                                                gs.MusicVolume, gs.MusicVolume, gs.MusicVolume, FX_MUSIC_PRIORITY, fix16_one, MUSIC_ID);
                        if (SongVoice > FX_Ok)
                        {
                            SongType = SongTypeWave;
                            SongTrack = cdaudio_track;
                            Xfree(SongName);
                            SongName = Xstrdup(waveformtrack);
                            return TRUE;
                        }
                    }
                }

                buildprintf("Can't find CD track %i!\n", cdaudio_track);
            }
            else
            {
                buildprintf("Make sure to have \"??\" as a placeholder for the track number in your WaveformTrackName!\n");
                buildprintf("  e.g. WaveformTrackName = \"MUSIC/Track??\"\n");
            }
        }
    }

    if (!song_file_name || !LoadSongUpgrade(song_file_name))
    {
        return FALSE;
    }

    if (!memcmp(SongPtr, "MThd", 4))
    {
        MUSIC_PlaySong(SongPtr, SongLength, MUSIC_LoopSong);
        SongType = SongTypeMIDI;
        Xfree(SongName);
        SongName = Xstrdup(song_file_name);
        return TRUE;
    }
    else
    {
        SongVoice = FX_Play(SongPtr, SongLength, loop ? FX_LOOP : FX_ONESHOT, 0, 0,
                                gs.MusicVolume, gs.MusicVolume, gs.MusicVolume, FX_MUSIC_PRIORITY, fix16_one, MUSIC_ID);
        if (SongVoice > FX_Ok)
        {
            SongType = SongTypeWave;
            Xfree(SongName);
            SongName = Xstrdup(song_file_name);
            return TRUE;
        }
    }

    return FALSE;
}

void
StopFX(void)
{
    FX_StopAllSounds();
}

void
StopSong(void)
{
    if (DemoMode)
        return;

    if (SongType == SongTypeWave && SongVoice > 0)
    {
        FX_StopSound(SongVoice);
        SongVoice = 0;
    }
    else if (SongType == SongTypeMIDI)
    {
        MUSIC_StopSong();
    }
    SongType = SongTypeNone;

    DO_FREE_AND_NULL(SongName);
    SongTrack = 0;

    FreeMem(SongPtr);
    SongPtr = nullptr;
    SongLength = 0;
}

void
PauseSong(SWBOOL pauseon)
{
    if (!gs.MusicOn) return;

    if (SongType == SongTypeWave && SongVoice > 0)
    {
        FX_PauseVoice(SongVoice, pauseon);
    }
}

void
SetSongVolume(int volume)
{
    if (SongType == SongTypeWave && SongVoice > 0)
        FX_SetPan(SongVoice, volume, volume, volume);

    MUSIC_SetVolume(volume);
}

SWBOOL
SongIsPlaying(void)
{
    if (!gs.MusicOn)
        return FALSE;

    if (SongType == SongTypeWave && SongVoice >= 0)
        return FX_SoundActive(SongVoice);

    return FALSE;
}

void
StopSound(void)
{
    StopFX();
    StopSong();
}

//
// Sound Distance Calculation
//

#define MAXLEVLDIST 19000   // The higher the number, the further away you can hear sound

short
SoundDist(int x, int y, int z, int basedist)
{
    double tx, ty, tz;
    double sqrdist,retval;
    double decay,decayshift;
    extern short screenpeek;

#define DECAY_CONST 4000

    tx = fabs(Player[screenpeek].posx - x);
    ty = fabs(Player[screenpeek].posy - y);
    tz = fabs((Player[screenpeek].posz - z) >> 4);

    // Use the Pythagreon Theorem to compute the magnitude of a 3D vector
    sqrdist = fabs(tx*tx + ty*ty + tz*tz);
    retval = sqrt(sqrdist);

    if (basedist < 0) // if basedist is negative
    {
        short i;

        decayshift=2;
        decay = labs(basedist) / DECAY_CONST;

        for (i=0; i<decay; i++)
            decayshift *= 2;

        if (fabs(double(basedist)/decayshift) >= retval)
            retval = 0;
        else
            retval *= decay;
    }
    else
    {
        if (basedist > retval)
            retval = 0;
        else
            retval -= basedist;
    }

    retval = retval * 256 / MAXLEVLDIST;

    if (retval < 0) retval = 0;
    if (retval > 255) retval = 255;

    return retval;
}

//
// Angle calcuations - may need to be checked to make sure they are right
//

short
SoundAngle(int x, int y)
{
    extern short screenpeek;

    short angle, delta_angle;

    angle = getangle(x - Player[screenpeek].posx, y - Player[screenpeek].posy);

    delta_angle = GetDeltaAngle(angle, fix16_to_int(Player[screenpeek].q16ang));

    // convert a delta_angle to a real angle if negative
    if (delta_angle < 0)
        delta_angle = NORM_ANGLE((1024 + delta_angle) + 1024);

    // convert 2048 degree angle to 128 degree angle
    return delta_angle >> 4;
}

int _PlayerSound(const char *file, int line, int num, int *x, int *y, int *z, Voc3D_Flags flags, PLAYERp pp)
//PlayerSound(int num, int *x, int *y, int *z, Voc3D_Flags flags, PLAYERp pp)
{
    VOC_INFOp vp;

    if (Prediction)
        return 0;

    if (pp < Player || pp >= Player + MAX_SW_PLAYERS)
    {
        TerminateGame();
        printf("Player Sound invalid player: file %s, line %d\n",file,line);
        exit(0);
    }

    PRODUCTION_ASSERT(pp >= Player && pp < Player+MAX_SW_PLAYERS);
    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);

    if (TEST(pp->Flags, PF_DEAD)) return 0; // You're dead, no talking!

    // If this is a player voice and he's already yacking, forget it.
    vp = &voc[num];
    if (vp == NULL)
    {
        TerminateGame();
        printf("vp == NULL in PlayerSound, num = %d\n",num);
        exit(0);
    }

    // Not a player voice, bail.
    if (vp->priority != PRI_PLAYERVOICE && vp->priority != PRI_PLAYERDEATH)
        return 0;

    // He wasn't talking, but he will be now.
    if (!pp->PlayerTalking)
    {
        pp->PlayerTalking = TRUE;
        pp->TalkVocnum = num;   // Set the voc number
        pp->TalkVocHandle = PlaySound(num, x, y, z, flags); // Play the sound
        if (pp->TalkVocHandle < 0)
        {
            pp->PlayerTalking = FALSE;
            pp->TalkVocnum = -1;
            pp->TalkVocHandle = -1;
        }
    }

    return 0;
}

void LockSound(int num)
{
    VOC_INFOp vp = &voc[num];
    // if data is not locked
    if (vp->lock <= CACHE_UNLOCK_MAX)
    {
        vp->lock = CACHE_LOCK_START;
    }
    else
    // if data is already locked
    {
        vp->lock++;
        if (vp->lock >= CACHE_LOCK_MAX || vp->lock == 0)
        {
            DumpSounds();
            TerminateGame();
            printf("lock > MAX, num = %d",num);
            exit(0);
        }
        //ASSERT(vp->lock < CACHE_LOCK_MAX);
        //ASSERT(vp->lock != 0);
    }
}

SWBOOL CacheSound(int num, int /*type*/)
{
    VOC_INFOp vp = &voc[num];

    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);

    // if no data we need to cache it in
    if (!vp->data)
    {
        int handle;
        int length;

        if (!OpenSound(vp, &handle, &length))
        {
            sprintf(ds,"Could not open sound %s, num %d, priority %d\n",vp->name,num,vp->priority);
            CON_ConMessage("%s", ds);
            return FALSE;
        }

        if (vp != NULL)
        {
            //FILE *fp;

            /*
            if (type == CACHE_SOUND_PLAY)
                // start it out locked at the min
                vp->lock = CACHE_LOCK_START;
            else
            if (type == CACHE_SOUND_PRECACHE)
                // start it out unlocked at the max
            */
            vp->lock = CACHE_UNLOCK_MAX;

            g_cache.allocateBlock((intptr_t*)&vp->data, length, &vp->lock);

#if 0
            // DEBUG
            globsndata[num] = AllocMem(length);
            glength[num] = length;

            fp = fopen(vp->name, "rb");
            if (fp != NULL)
            {
                fread(globsndata[num], length, 1, fp);
                ASSERT(globsndata[num] != NULL);
                fclose(fp);
            }
#endif
            ///////

            ASSERT(vp->data);
            ReadSound(handle, vp, length);

#if 0
            //DEBUG
            globvpdata[num] = vp->data;
            CheckSndData(__FILE__, __LINE__);
#endif
        }
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////////
// Play a sound
////////////////////////////////////////////////////////////////////////////

#define SOUND_UNIT  MAXLEVLDIST/255
// NOTE: If v3df_follow == 1, x,y,z are considered literal coordinates
int
PlaySound(int num, int *x, int *y, int *z, Voc3D_Flags flags)
{
    VOC_INFOp vp;
    VOC3D_INFOp v3p;
    int pitch = 0;
    short angle, sound_dist;
    int tx, ty, tz;
    uint8_t priority;
    SPRITEp sp=NULL;

    // DEBUG
    //extern SWBOOL Pachinko_Win_Cheat;

    // Don't play game sounds when in menus
    //if (UsingMenus && (*x!=0 || *y!=0 || *z!=0)) return(-1);

    // Weed out parental lock sounds if PLock is active
    if (gs.ParentalLock || Global_PLock)
    {
        unsigned i;

        for (i=0; i<sizeof(PLocked_Sounds); i++)
        {
            if (num == PLocked_Sounds[i])
                return -1;
        }
    }

    if (Prediction)
        return -1;

    if (!gs.FxOn)
        return -1;

    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);

    // Reset voice
    voice = -1;

    // This is used for updating looping sounds in Update3DSounds
    if (Use_SoundSpriteNum && SoundSpriteNum >= 0)
    {
        ASSERT(SoundSpriteNum >= 0 && SoundSpriteNum < MAXSPRITES);
        sp = &sprite[SoundSpriteNum];
    }

    if (gs.Ambient && TEST(flags,v3df_ambient) && !TEST(flags,v3df_nolookup))  // Look for invalid ambient numbers
    {
        if (num < 0 || num > MAX_AMBIENT_SOUNDS)
        {
            sprintf(ds,"Invalid or out of range ambient sound number %d\n",num);
            PutStringInfo(Player+screenpeek, ds);
            return -1;
        }
    }

    // Call queue management to add sound to play list.
    // 3D sound manager will update playing sound 10x per second until
    // the sound ends, at which time it is removed from both the 3D
    // sound list as well as the actual cache.
    v3p = Insert3DSound();

    // If the ambient flag is set, do a name conversion to point to actual
    // digital sound entry.
    v3p->num = num;
    v3p->priority = 0;
    v3p->FX_Ok = FALSE; // Hasn't played yet

    if (gs.Ambient && TEST(flags,v3df_ambient) && !TEST(flags,v3df_nolookup))
    {
        v3p->maxtics = STD_RANDOM_RANGE(ambarray[num].maxtics);
        flags |= ambarray[num].ambient_flags;   // Add to flags if any
        num = ambarray[num].diginame;
    }

    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);

    // Assign voc to voc pointer
    vp = &voc[num];
    if (UsingMenus && *x==0 && *y==0 && *z==0)  // Menus sound outdo everything
        priority = 100;
    else
        priority = vp->priority;
    v3p->vp = vp;

    // Assign voc info to 3d struct for future reference
    v3p->x = x;
    v3p->y = y;
    v3p->z = z;
    v3p->fx = *x;
    v3p->fy = *y;
    v3p->fz = *z;
    v3p->flags = flags;

    if (flags & v3df_follow)
    {
        tx = *x;
        ty = *y;
        if (!z)
            tz = 0;                     // Some sound calls don't have a z
        // value
        else
            tz = *z;
    }
    else
    {
        // Don't use pointers to coordinate values.
        tx = v3p->fx;
        ty = v3p->fy;
        tz = v3p->fz;
    }

    // Special case stuff for sounds being played in a level
    if (*x==0 && *y==0 && *z==0)
        tx = ty = tz = 0;

    if ((vp->voc_flags & vf_loop) && Use_SoundSpriteNum && SoundSpriteNum >= 0 && sp)
    {
        tx=sp->x;
        ty=sp->y;
        tz=sp->z;
        //CON_Message("Using sp to set tx=%ld,ty=%ld,tz=%ld",tx,ty,tz);
    }

    // Calculate sound angle
    if (flags & v3df_dontpan)               // If true, don't do panning
        angle = 0;
    else
        angle = SoundAngle(tx, ty);

    // Calculate sound distance
    if (tx == 0 && ty == 0 && tz == 0)
        sound_dist = 255;  // Special case for menus sounds,etc.
    else
        sound_dist = SoundDist(tx, ty, tz, vp->voc_distance);

    v3p->doplr_delta = sound_dist;      // Save of distance for doppler
    // effect

//  //DSPRINTF(ds,"sound dist = %d\n",sound_dist);
//  MONO_PRINT(ds);

    // Can the ambient sound see the player?  If not, tone it down some.
    if ((vp->voc_flags & vf_loop) && Use_SoundSpriteNum && SoundSpriteNum >= 0)
    {
        PLAYERp pp = Player+screenpeek;

        //MONO_PRINT("PlaySound:Checking sound cansee");
        if (!FAFcansee(tx, ty, tz, sp->sectnum,pp->posx, pp->posy, pp->posz, pp->cursectnum))
        {
            //MONO_PRINT("PlaySound:Reducing sound distance");
            sound_dist += ((sound_dist/2)+(sound_dist/4));  // Play more quietly
            if (sound_dist > 255) sound_dist = 255;

            // Special Cases
            if (num == DIGI_WHIPME) sound_dist = 255;
        }
    }

    // Assign ambient priorities based on distance
    if (gs.Ambient && TEST(flags, v3df_ambient))
    {
        v3p->priority = v3p->vp->priority - (sound_dist / 26);
        priority = v3p->priority;
    }

    if (!CacheSound(num, CACHE_SOUND_PLAY))
    {
        v3p->flags = v3df_kill;
        v3p->handle = -1;
        v3p->dist = 0;
        v3p->deleted = TRUE;            // Sound init failed, remove it!
        return -1;
    }

    LockSound(num);

    if (sound_dist < 5)
        angle = 0;

    // Check for pitch bending
    if (vp->pitch_lo > vp->pitch_hi)
        ASSERT(vp->pitch_lo <= vp->pitch_hi);

    if (vp->pitch_hi == vp->pitch_lo)
        pitch = vp->pitch_lo;
    else if (vp->pitch_hi != vp->pitch_lo)
        pitch = vp->pitch_lo + (STD_RANDOM_RANGE(vp->pitch_hi - vp->pitch_lo));

#if 0
    // DEBUG
    if (Pachinko_Win_Cheat)
    {
        CheckSndData(__FILE__, __LINE__);
        Pachinko_Win_Cheat = FALSE;
        CON_Message("S O U N D S   C H E C K E D");
    }
#endif

    // Request playback and play it as a looping sound if flag is set.
    if (vp->voc_flags & vf_loop)
    {
        short loopvol=0;

        if ((loopvol = 255-sound_dist) <= 0)
            loopvol = 0;

        if (sound_dist < 255 || (flags & v3df_init))
        {
            voice = FX_Play((char *)vp->data, vp->datalen, 0, 0,
                                      pitch, loopvol, loopvol, loopvol, priority, fix16_one, num); // [JM] Check the volume parameter for correctness. !CHECKME!
        }
        else
            voice = -1;

    }
    else
    //if(!flags & v3df_init)  // If not initing sound, play it
    if (tx==0 && ty==0 && tz==0)     // It's a non-inlevel sound
    {
        voice = FX_Play((char *)vp->data, vp->datalen, -1, -1, pitch, 255, 255, 255, priority, fix16_one, num); // [JM] And here !CHECKME!
    }
    else     // It's a 3d sound
    {
        if (sound_dist < 255)
        {
            voice = FX_Play3D((char *)vp->data, vp->datalen, FX_ONESHOT, pitch, angle, sound_dist, priority, fix16_one, num); // [JM] And here !CHECKME!
        }
        else
            voice = -1;
    }

    // If sound played, update our counter
    if (voice > FX_Ok)
    {
        //vp->playing++;
        v3p->FX_Ok = TRUE;
    }
    else
    {
        vp->lock--;
    }

    // Assign voc info to 3d struct for future reference
    v3p->handle = voice;                // Save the current voc handle in struct
    v3p->dist = sound_dist;
    v3p->tics = 0;                      // Reset tics
    if (flags & v3df_init)
        v3p->flags ^= v3df_init;        // Turn init off now

    return voice;
}

void PlaySoundRTS(int rts_num)
{
    char *rtsptr;
    int voice=-1;

    if (RTS_NumSounds() <= 0 || !gs.FxOn)
        return;

    rtsptr = (char *)RTS_GetSound(rts_num - 1);

    ASSERT(rtsptr);

    voice = FX_Play3D(rtsptr, RTS_SoundLength(rts_num - 1), FX_ONESHOT, 0, 0, 0, 255, fix16_one, -rts_num); // [JM] Check the volume parameter for correctness. !CHECKME!

    if (voice <= FX_Ok)
    {
        lumplockbyte[rts_num]--;
    }
}

///////////////////////////////////////////////

SWBOOL
OpenSound(VOC_INFOp vp, int *handle, int *length)
{
    *handle = kopen4load(vp->name, 0);

    if (*handle == -1)
    {
        return FALSE;
    }

    *length = kfilelength(*handle);

    return TRUE;
}

int
ReadSound(int handle, VOC_INFOp vp, int length)
{
    if (kread(handle, vp->data, length) != length)
    {
        TerminateGame();
        printf("Error reading file '%s'.\n", vp->name);
        exit(0);
    }

    vp->datalen = length;

    kclose(handle);
    return 0;
}

SWBOOL
LoadSong(const char *filename)
{
    int handle;
    int size;
    char *ptr;

    if ((handle = kopen4load(filename, 0)) == -1)
    {
        return FALSE;
    }

    size = kfilelength(handle);

    ptr = (char *) AllocMem(size);
    if (ptr == NULL)
    {
        kclose(handle);
        return FALSE;
    }

    if (kread(handle, ptr, size) != size)
    {
        FreeMem(ptr);
        kclose(handle);
        return FALSE;
    }

    kclose(handle);

    SongPtr = ptr;
    SongLength = size;

    return TRUE;
}

void
SoundStartup(void)
{
    void *initdata = 0;

    // if they chose None lets return
    if (!FXToggle)
    {
        gs.FxOn = FALSE;
        return;
    }

#ifdef MIXERTYPEWIN
    initdata = (void *) win_gethwnd();
#endif

    //gs.FxOn = TRUE;

    int status = FX_Init(NumVoices, NumChannels, MixRate, initdata);
    if (status != FX_Ok)
    {
        buildprintf("Sound error: %s\n", FX_ErrorString(status));
        return;
    }

    FxInitialized = TRUE;
    FX_SetVolume(gs.SoundVolume);

#ifdef ASS_REVERSESTEREO
    if (gs.FlipStereo)
        FX_SetReverseStereo(!FX_GetReverseStereo());
#endif

    FX_SetCallBack(SoundCallBack);
}

/*
===================
=
= SoundShutdown
=
===================
*/

void
SoundShutdown(void)
{
    // if they chose None lets return
    if (!FXToggle)
    {
        return;
    }

    if (!FxInitialized)
        return;

    int status = FX_Shutdown();
    if (status != FX_Ok)
    {
        buildprintf("Sound error: %s\n", FX_ErrorString(status));
    }
}

/*
===================
=
= MusicStartup
=
===================
*/

void MusicStartup(void)
{
    // if they chose None lets return
    if (!MusicToggle)
    {
        gs.MusicOn = FALSE;
        return;
    }

    int status;
    if ((status = MUSIC_Init(MusicDevice)) == MUSIC_Ok)
    {
        if (MusicDevice == ASS_AutoDetect)
            MusicDevice = MIDI_GetDevice();
    }
    else if ((status = MUSIC_Init(ASS_AutoDetect)) == MUSIC_Ok)
    {
        MusicDevice = MIDI_GetDevice();
    }
    else
    {
        buildprintf("Music error: %s\n", MUSIC_ErrorString(status));
        gs.MusicOn = FALSE;
        return;
    }

    MusicInitialized = TRUE;
    MUSIC_SetVolume(gs.MusicVolume);

    auto const fil = kopen4load("swtimbr.tmb", 0);

    if (fil != buildvfs_kfd_invalid)
    {
        int l = kfilelength(fil);
        auto tmb = (uint8_t *)Xmalloc(l);
        kread(fil, tmb, l);
        AL_RegisterTimbreBank(tmb);
        Xfree(tmb);
        kclose(fil);
    }
}

void COVER_SetReverb(int amt)
{
    FX_SetReverb(amt);
}

/*
===================
=
= MusicShutdown
=
===================
*/

void
MusicShutdown(void)
{
    // if they chose None lets return
    if (!MusicToggle)
        return;

    if (!MusicInitialized)
        return;

    StopSong();

    int status = MUSIC_Shutdown();
    if (status != MUSIC_Ok)
    {
        buildprintf("Music error: %s\n", MUSIC_ErrorString(status));
    }
}

///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////
//
//  3D sound engine
//  Sound management routines that keep a list of
//  all sounds being played in a level.
//  Doppler and Panning effects are achieved here.
//
///////////////////////////////////////////////
// Declare and initialize linked list of vocs.
VOC3D_INFOp voc3dstart = NULL;
VOC3D_INFOp voc3dend = NULL;

//////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////
// Initialize new vocs in the 3D sound queue
///////////////////////////////////////////////
VOC3D_INFOp
InitNew3DSound(VOC3D_INFOp v3p)
{
    v3p->handle = -1;                    // Initialize handle to new sound
    // value
    v3p->owner = -1;
    v3p->deleted = FALSE;                // Used for when sound gets deleted

    return v3p;
}

///////////////////////////////////////////////
// Inserts new vocs in the 3D sound queue
///////////////////////////////////////////////
VOC3D_INFOp
Insert3DSound(void)
{
    VOC3D_INFOp vp, old;

    // Allocate memory for new sound
    // You can allocate new sounds as long as memory holds out.
    // If you run out of memory for sounds, you got problems anyway.
    vp = (VOC3D_INFOp) AllocMem(sizeof(VOC3D_INFO));
    ASSERT(vp != NULL);
    memset(vp,0xCC,sizeof(VOC3D_INFO)); // Zero out the memory

    if (!voc3dend)                      // First item in list
    {
        vp->next = vp->prev = NULL;
        voc3dend = vp;
        voc3dstart = vp;
        InitNew3DSound(vp);
        return vp;
    }

    old = voc3dend;                     // Put it on the end
    old->next = vp;
    vp->next = NULL;
    vp->prev = old;
    voc3dend = vp;

    InitNew3DSound(vp);
    return vp;

}

/////////////////////////////////////////////////////
// Deletes vocs in the 3D sound queue with no owners
/////////////////////////////////////////////////////
void
DeleteNoSoundOwner(short spritenum)
{
    VOC3D_INFOp vp, dp;

    vp = voc3dstart;

    while (vp)
    {
        dp = NULL;
        if (vp->owner == spritenum && vp->owner >= 0 && (vp->vp->voc_flags & vf_loop))
        {
            //DSPRINTF(ds,"Deleting owner %d\n",vp->owner);
            //MONO_PRINT(ds);

            // Make sure to stop active
            // sounds
            if (FX_SoundValidAndActive(vp->handle))
            {
                FX_StopSound(vp->handle);
                vp->handle = 0;
            }

#if 0
            // Clean up the sound active counter for cacheing locks
            if (vp->FX_Ok) // Only decrement if sound ever played
                vp->vp->playing--; // Decrement instance of sound playing
            if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
                vp->vp->lock = CACHE_UNLOCK_MAX;
#endif

            dp = vp;                    // Point to sound to be deleted

            if (vp->prev)
            {
                vp->prev->next = vp->next;
            }
            else
            {
                voc3dstart = vp->next;  // New first item
                if (voc3dstart)
                    voc3dstart->prev = NULL;
            }

            if (vp->next)
            {
                vp->next->prev = vp->prev;      // Middle element
            }
            else
            {
                voc3dend = vp->prev;    // Delete last element
            }
        }

        vp = vp->next;

        if (dp != NULL)
            FreeMem(dp);                // Return memory to heap
    }
}

// This is called from KillSprite to kill a follow sound with no valid sprite owner
// Stops and active sound with the follow bit set, even play once sounds.
void DeleteNoFollowSoundOwner(short spritenum)
{
    VOC3D_INFOp vp, dp;
    SPRITEp sp = &sprite[spritenum];

    vp = voc3dstart;

    while (vp)
    {
        dp = NULL;
        // If the follow flag is set, compare the x and y addresses.
        if ((vp->flags & v3df_follow) && vp->x == &sp->x && vp->y == &sp->y)
        {
            if (FX_SoundValidAndActive(vp->handle))
            {
                FX_StopSound(vp->handle);
                vp->handle = 0;
            }

#if 0
            if (vp->FX_Ok) // Only decrement if sound ever played
                vp->vp->playing--; // Decrement instance of sound playing
            if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
                vp->vp->lock = CACHE_UNLOCK_MAX;
#endif

            dp = vp;                    // Point to sound to be deleted

            if (vp->prev)
            {
                vp->prev->next = vp->next;
            }
            else
            {
                voc3dstart = vp->next;  // New first item
                if (voc3dstart)
                    voc3dstart->prev = NULL;
            }

            if (vp->next)
            {
                vp->next->prev = vp->prev;      // Middle element
            }
            else
            {
                voc3dend = vp->prev;    // Delete last element
            }
        }

        vp = vp->next;

        if (dp != NULL)
            FreeMem(dp);                // Return memory to heap
    }
}

///////////////////////////////////////////////
// Deletes vocs in the 3D sound queue
///////////////////////////////////////////////
void
Delete3DSounds(void)
{
    VOC3D_INFOp vp, dp;
    PLAYERp pp;

    vp = voc3dstart;

    while (vp)
    {
        dp = NULL;
        if (vp->deleted)
        {
#if 0
            if (vp->FX_Ok) // Only decrement if sound ever played
                vp->vp->playing--; // Decrement instance of sound playing
            if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
                vp->vp->lock = CACHE_UNLOCK_MAX;
#endif

            //DSPRINTF(ds,"Delete3DSounds: deleting owner %d digi %d\n",vp->owner,vp->num);
            //MONO_PRINT(ds);
            // Reset Player talking flag if a voice was deleted
            //if(vp->num > DIGI_FIRSTPLAYERVOICE && vp->num < DIGI_LASTPLAYERVOICE)
            if (!vp->vp)
            {
                printf("Delete3DSounds(): NULL vp->vp\n");
            }
            else    // JBF: added null check
            if (vp->vp->priority == PRI_PLAYERVOICE || vp->vp->priority == PRI_PLAYERDEATH)
            {
                int16_t pnum;

                TRAVERSE_CONNECT(pnum)
                {
                    pp = &Player[pnum];

                    if (vp->num == pp->TalkVocnum)
                    {
                        pp->PlayerTalking = FALSE;
                        pp->TalkVocnum = -1;
                        pp->TalkVocHandle = -1;
                        //DSPRINTF(ds,"DELETED PLAYER VOICE VOC! NUM=%d\n",vp->num);
                        //MONO_PRINT(ds);
                    }
                }
            }

            dp = vp;                    // Point to sound to be deleted
            if (vp->prev)
            {
                vp->prev->next = vp->next;
            }
            else
            {
                voc3dstart = vp->next;  // New first item
                if (voc3dstart)
                    voc3dstart->prev = NULL;
            }

            if (vp->next)
            {
                vp->next->prev = vp->prev;      // Middle element
            }
            else
            {
                voc3dend = vp->prev;    // Delete last element
            }
        }

        vp = vp->next;

        if (dp != NULL)
        {
            FreeMem(dp);                // Return memory to heap
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// Play a sound
////////////////////////////////////////////////////////////////////////////

int
RandomizeAmbientSpecials(int handle)
{
#define MAXRNDAMB 12
    int ambrand[] =
    {
        56,57,58,59,60,61,62,63,64,65,66,67
    };
    short i;

    // If ambient sound is found in the array, randomly pick a new sound
    for (i=0; i<MAXRNDAMB; i++)
    {
        if (handle == ambrand[i])
            return ambrand[STD_RANDOM_RANGE(MAXRNDAMB-1)];
    }

    return handle;   // Give back the sound, no new one was found
}

void
DoTimedSound(VOC3D_INFOp p)
{
    p->tics += synctics;

    if (p->tics >= p->maxtics)
    {
        if (!FX_SoundValidAndActive(p->handle))
        {
            // Check for special case ambient sounds
            p->num = RandomizeAmbientSpecials(p->num);

            // Sound was bumped from active sounds list, try to play again.
            // Don't bother if voices are already maxed out.
            if (FX_SoundsPlaying() < NumVoices)
            {
                if (p->flags & v3df_follow)
                {
                    PlaySound(p->num, p->x, p->y, p->z, p->flags);
                    p->deleted = TRUE;  // Mark old sound for deletion
                }
                else
                {
                    PlaySound(p->num, &p->fx, &p->fy, &p->fz, p->flags);
                    p->deleted = TRUE;  // Mark old sound for deletion
                }
            }
        }

        p->tics = 0;
        //while (p->tics >= p->maxtics)  // Really stupid thing to do!
        //    {
        //    p->tics -= p->maxtics;
        //    }
    }
}

void
StopAmbientSound(void)
{
    VOC3D_INFOp p;
    extern SWBOOL InMenuLevel;

    if (InMenuLevel) return;

    p = voc3dstart;

    while (p)
    {
        // kill ambient sounds if Ambient is off
        if (TEST(p->flags,v3df_ambient))
            SET(p->flags, v3df_kill);

        if (p->flags & v3df_kill)
        {
            if (FX_SoundValidAndActive(p->handle))
            {
                FX_StopSound(p->handle); // Make sure to stop active sounds
                p->handle = 0;
            }

            p->deleted = TRUE;
        }

        p = p->next;
    }

    Delete3DSounds();
}

void
StartAmbientSound(void)
{
    short i,nexti;
    extern SWBOOL InMenuLevel;

    if (InMenuLevel) return; // Don't restart ambience if no level is active! Will crash game.

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_AMBIENT], i, nexti)
    {
        SPRITEp sp = &sprite[i];

        PlaySound(sp->lotag, &sp->x, &sp->y, &sp->z, v3df_ambient | v3df_init
                  | v3df_doppler | v3df_follow);
        Set3DSoundOwner(i);  // Ambient sounds need this to get sectnum for later processing
    }
}

///////////////////////////////////////////////
// Main function to update 3D sound array
///////////////////////////////////////////////
typedef struct
{
    VOC3D_INFOp p;
    short dist;
    uint8_t priority;
} TVOC_INFO, *TVOC_INFOp;

void
DoUpdateSounds3D(void)
{
    VOC3D_INFOp p;
    SWBOOL looping;
    int pitch = 0, pitchmax;
    short dist, angle;

    TVOC_INFO TmpVocArray[32];
    int i;
    static SWBOOL MoveSkip8 = 0;

    if (UsingMenus) return;

    // This function is already only call 10x per sec, this widdles it down even more!
    MoveSkip8 = (MoveSkip8 + 1) & 15;

    //CON_Message("Sounds Playing = %d",FX_SoundsPlaying());

    // Zero out the temporary array
    //memset(&TmpVocArray[0],0,sizeof(TmpVocArray));
    for (i=0; i<32; i++)
    {
        TmpVocArray[i].p = NULL;
        TmpVocArray[i].dist = 0;
        TmpVocArray[i].priority = 0;
    }

    p = voc3dstart;

    while (p)
    {
        ASSERT(p->num >= 0 && p->num < DIGI_MAX);

        looping = p->vp->voc_flags & vf_loop;

//      //DSPRINTF(ds,"sound %d FX_SoundActive = %d\n,",p->num,FX_SoundActive(p->handle));
//      MONO_PRINT(ds);

        // If sprite owner is dead, kill this sound as long as it isn't ambient
        if (looping && p->owner == -1 && !TEST(p->flags,v3df_ambient))
        {
            SET(p->flags, v3df_kill);
        }

        // Is the sound slated for death? Kill it, otherwise play it.
        if (p->flags & v3df_kill)
        {
            if (FX_SoundValidAndActive(p->handle))
            {
                FX_StopSound(p->handle); // Make sure to stop active sounds
                p->handle = 0;
            }

            //DSPRINTF(ds,"%d had v3df_kill.\n",p->num);
            //MONO_PRINT(ds);
            p->deleted = TRUE;
        }
        else
        {
            if (!FX_SoundValidAndActive(p->handle) && !looping)
            {
                if (p->flags & v3df_intermit)
                {
                    DoTimedSound(p);
                }
                else
                //if(p->owner == -1 && !TEST(p->flags,v3df_ambient))
                {
                    //DSPRINTF(ds,"%d is now inactive.\n",p->num);
                    //MONO_PRINT(ds);
                    p->deleted = TRUE;
                }
            }
            else if (FX_SoundValidAndActive(p->handle))
            {
                if (p->flags & v3df_follow)
                {
                    dist = SoundDist(*p->x, *p->y, *p->z, p->vp->voc_distance);
                    angle = SoundAngle(*p->x, *p->y);
                }
                else
                {
                    if (p->fx == 0 && p->fy == 0 && p->fz == 0)
                        dist = 0;
                    else
                        dist = SoundDist(p->fx, p->fy, p->fz, p->vp->voc_distance);
                    angle = SoundAngle(p->fx, p->fy);
                }

                // Can the ambient sound see the player?  If not, tone it down some.
                if ((p->vp->voc_flags & vf_loop) && p->owner != -1)
                {
                    PLAYERp pp = Player+screenpeek;
                    SPRITEp sp = &sprite[p->owner];

                    //MONO_PRINT("Checking sound cansee");
                    if (!FAFcansee(sp->x, sp->y, sp->z, sp->sectnum,pp->posx, pp->posy, pp->posz, pp->cursectnum))
                    {
                        //MONO_PRINT("Reducing sound distance");
                        dist += ((dist/2)+(dist/4));  // Play more quietly
                        if (dist > 255) dist = 255;

                        // Special cases
                        if (p->num == 76 && TEST(p->flags,v3df_ambient))
                        {
                            dist = 255; // Cut off whipping sound, it's secret
                        }

                    }
                }

                if (dist >= 255 && p->vp->voc_distance == DIST_NORMAL)
                {
                    FX_StopSound(p->handle);    // Make sure to stop active
                    p->handle = 0;
                    // sounds
                }
                else
                {
                    // Handle Panning Left and Right
                    if (!(p->flags & v3df_dontpan))
                        FX_Pan3D(p->handle, angle, dist);
                    else
                        FX_Pan3D(p->handle, 0, dist);

                    // Handle Doppler Effects
#define DOPPLERMAX  400
                    if (!(p->flags & v3df_doppler) && FX_SoundActive(p->handle))
                    {
                        pitch -= (dist - p->doplr_delta);

                        if (p->vp->pitch_lo != 0 && p->vp->pitch_hi != 0)
                        {
                            if (abs(p->vp->pitch_lo) > abs(p->vp->pitch_hi))
                                pitchmax = abs(p->vp->pitch_lo);
                            else
                                pitchmax = abs(p->vp->pitch_hi);

                        }
                        else
                            pitchmax = DOPPLERMAX;

                        if (pitch > pitchmax)
                            pitch = pitchmax;
                        if (pitch < -pitchmax)
                            pitch = -pitchmax;

                        p->doplr_delta = dist;  // Save new distance to
                        // struct
                        FX_SetPitch(p->handle, pitch);
                    }
                }
            }
            else if (!FX_SoundValidAndActive(p->handle) && looping)
            {
                if (p->flags & v3df_follow)
                {
                    dist = SoundDist(*p->x, *p->y, *p->z, p->vp->voc_distance);
                    angle = SoundAngle(*p->x, *p->y);
                }
                else
                {
                    dist = SoundDist(p->fx, p->fy, p->fz, p->vp->voc_distance);
                    angle = SoundAngle(p->fx, p->fy);
                }

                // Sound was bumped from active sounds list, try to play
                // again.
                // Don't bother if voices are already maxed out.
                // Sort looping vocs in order of priority and distance
                //if (FX_SoundsPlaying() < NumVoices && dist <= 255)
                if (dist <= 255)
                {
                    for (i=0; i<min((int)SIZ(TmpVocArray), NumVoices); i++)
                    {
                        if (p->priority >= TmpVocArray[i].priority)
                        {
                            if (!TmpVocArray[i].p || dist < TmpVocArray[i].dist)
                            {
                                ASSERT(p->num >= 0 && p->num < DIGI_MAX);
                                TmpVocArray[i].p = p;
                                TmpVocArray[i].dist = dist;
                                TmpVocArray[i].priority = p->priority;
                                break;
                            }
                        }
                    }
                }
            }                       // !FX_SoundActive
        }                           // if(p->flags & v3df_kill)

        p = p->next;
    }                               // while(p)

//  //DSPRINTF(ds,"Num vocs in list: %d, Sounds playing: %d\n",numelems,FX_SoundsPlaying());
//  MONO_PRINT(ds);

    // Process all the looping sounds that said they wanted to get back in
    // Only update these sounds 5x per second!  Woo hoo!, aren't we optimized now?
    //if(MoveSkip8==0)
    //    {
    for (i=0; i<min((int)SIZ(TmpVocArray), NumVoices); i++)
    {
//        int handle;

        p = TmpVocArray[i].p;

        //if (FX_SoundsPlaying() >= NumVoices || !p) break;
        if (!p) break;

        ASSERT(p->num >= 0 && p->num < DIGI_MAX);

        if (p->flags & v3df_follow)
        {
            if (p->owner == -1)
            {
                int enumber;
                enumber = p->num;
                TerminateGame();
                printf("Owner == -1 on looping sound with follow flag set!\n");
                printf("p->num = %d\n",enumber);
                exit(0);
            }

            Use_SoundSpriteNum = TRUE;
            SoundSpriteNum = p->owner;

            /*handle = */PlaySound(p->num, p->x, p->y, p->z, p->flags);
            //if(handle >= 0 || TEST(p->flags,v3df_ambient)) // After a valid PlaySound, it's ok to use voc3dend
            voc3dend->owner = p->owner; // Transfer the owner
            p->deleted = TRUE;

            Use_SoundSpriteNum = FALSE;
            SoundSpriteNum = -1;

            //MONO_PRINT("TmpVocArray playing a follow sound");
        }
        else
        {
            if (p->owner == -1)
            {
                int enumber;
                enumber = p->num;
                TerminateGame();
                printf("Owner == -1 on looping sound, no follow flag.\n");
                printf("p->num = %d\n",enumber);
                exit(0);
            }

            Use_SoundSpriteNum = TRUE;
            SoundSpriteNum = p->owner;

            /*handle = */PlaySound(p->num, &p->fx, &p->fy, &p->fz, p->flags);
            //if(handle >= 0 || TEST(p->flags,v3df_ambient))
            voc3dend->owner = p->owner; // Transfer the owner
            p->deleted = TRUE;

            Use_SoundSpriteNum = FALSE;
            SoundSpriteNum = -1;

            //MONO_PRINT("TmpVocArray playing a non-follow sound");
        }
    }
    //    } // MoveSkip8

    // Clean out any deleted sounds now
    Delete3DSounds();
}

//////////////////////////////////////////////////
// Terminate the sounds list
//////////////////////////////////////////////////
void
Terminate3DSounds(void)
{
    VOC3D_INFOp vp;

    vp = voc3dstart;

    while (vp)
    {
        if (vp->handle > 0)
            FX_StopSound(vp->handle);       // Make sure to stop active sounds
        vp->handle = 0;
        vp->deleted = TRUE;
        vp = vp->next;
    }

    Delete3DSounds();                   // Now delete all remaining sounds
    ClearSoundLocks();
}

void
DumpSounds(void)
{
    VOC3D_INFOp vp;

    vp = voc3dstart;

    while (vp)
    {
        if (TEST(vp->flags,v3df_ambient))
            sprintf(ds,"vp->num=%d, vp->owner=%d, vp->vp->lock=%d",ambarray[vp->num].diginame,vp->owner,vp->vp->lock);
        else
            sprintf(ds,"vp->num=%d, vp->owner=%d, vp->vp->lock=%d",vp->num,vp->owner,vp->vp->lock);
        DebugWriteString(ds);
        if (vp->owner >= 0)
        {
            SPRITEp sp = &sprite[vp->owner];
            sprintf(ds,"sp->picnum=%d, sp->hitag=%d, sp->lotag=%d, sp->owner=%d\n",TrackerCast(sp->picnum), TrackerCast(sp->hitag), TrackerCast(sp->lotag), TrackerCast(sp->owner));
            DebugWriteString(ds);
        }
        vp = vp->next;
    }
}

//////////////////////////////////////////////////
// Set owner to check when to kill looping sounds
// Must be called immediately after PlaySound call
// since this only assigns value to last sound
// on voc list
//////////////////////////////////////////////////
void
Set3DSoundOwner(short spritenum)
{
    VOC3D_INFOp p;

//  ASSERT(p->handle != -1); // Check for bogus sounds

    p = voc3dend;
    if (!p) return;

    // Queue up sounds with ambient flag even if they didn't play right away!
    if (p->handle != -1 || TEST(p->flags,v3df_ambient))
    {
        p->owner = spritenum;
    }
    else
    {
        p->deleted = TRUE;
        p->flags = v3df_kill;
    }
}

//////////////////////////////////////////////////
// Play a sound using special sprite setup
//////////////////////////////////////////////////
void
PlaySpriteSound(short spritenum, int attrib_ndx, Voc3D_Flags flags)
{
    SPRITEp sp = &sprite[spritenum];
    USERp u = User[spritenum];

    ASSERT(u);

//  //DSPRINTF(ds,"index = %d, digi num = %d\n",attrib_ndx,u->Attrib->Sounds[attrib_ndx]);
//  MONO_PRINT(ds);
    PlaySound(u->Attrib->Sounds[attrib_ndx], &sp->x, &sp->y, &sp->z, flags);
}

// vim:ts=4:sw=4:expandtab:


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

//****************************************************************************
//
// sounds.h
//
//****************************************************************************

#include "mytypes.h"

#ifndef sounds_public_
#define sounds_public_

void SoundStartup(void);
void SoundShutdown(void);
void MusicStartup(void);
void MusicShutdown(void);

// 3D sound engine declarations //////////////////////////////////////////////
// Flag settings used to turn on and off 3d sound options
enum
{
    v3df_none       = 0,    // Default, take no action, use all defaults
    v3df_follow     = 1,    // 1 = Do coordinate updates on sound
    // Use this only if the sprite won't be deleted soon
    v3df_kill       = 2,    // 1 = Sound is to be deleted
    v3df_doppler    = 4,    // 1 = Don't use doppler pitch variance
    v3df_dontpan        = 8,    // 1 = Don't do panning of sound
    v3df_ambient    = 16,   // 1 = Sound is ambient, use ambient struct info.
    v3df_intermit   = 32,   // 1 = Intermittant sound
    v3df_init       = 64,   // 1 = First pass of sound, don't play it.
    // This is mainly used for intermittent sounds
    v3df_nolookup   = 128   // don't use ambient table lookup
};
typedef int Voc3D_Flags;

struct VOCstruct;
typedef struct VOCstruct VOC_INFO, *VOC_INFOp;
struct VOC3Dstruct;
typedef struct VOC3Dstruct VOC3D_INFO, *VOC3D_INFOp;
struct ambientstruct;
typedef struct ambientstruct AMB_INFO, *AMB_INFOp;

extern VOC3D_INFOp voc3dstart;
extern VOC3D_INFOp voc3dend;

void DoUpdateSounds3D(void);
void Terminate3DSounds(void);

void Set3DSoundOwner(short spritenum);
void PlaySpriteSound(short spritenum, int attrib_ndx, Voc3D_Flags flags);
void DeleteNoSoundOwner(short spritenum);
void DeleteNoFollowSoundOwner(short spritenum);

SWBOOL CacheSound(int num, int type);
void COVER_SetReverb(int amt);
void UnInitSound(void);
void InitFX(void);
void InitMusic(void);
void StopFX(void);
void StopSong(void);
void PauseSong(SWBOOL pauseon);
void StopSound(void);
void StartAmbientSound(void);
void StopAmbientSound(void);
SWBOOL PlaySong(char *song_file_name, int cdaudio_track, SWBOOL loop, SWBOOL restart);
void SetSongVolume(int volume);
SWBOOL SongIsPlaying(void);
void PlaySoundRTS(int rts_num);
extern int8_t SongTrack;

//
// Standard VOC format information - generally don't need this
//

typedef struct
{
    uint8_t filler[0x1a];
    uint8_t type;
    uint32_t length;
    uint8_t filler2;
    uint8_t freq;
    uint8_t pack;
    uint8_t data[1];
} *VOC_HDRp;

// Ambient Sound Structure
struct ambientstruct
{
    int16_t name;
    int16_t diginame;
    Voc3D_Flags ambient_flags;
    int maxtics;                       // When tics reaches this number next
    // sound happens
};

// VOC File flag settings for digi_entries.
enum
{
    vf_normal = 0,
    vf_loop = 1
};
typedef int Voc_Flags;

//
// Table that describes the voc file and how it will be played
// Can be easily extended, but you may need to change digi.h
//

//struct STATEstruct;
//typedef struct VOCstruct VOC_INFO, *VOC_INFOp;

struct VOCstruct
{
    char name[14];                      // name of voc file on disk
    uint8_t* data;                         // pointer to voc data
    int datalen;                        // length of voc data
    int16_t pitch_lo;                     // lo pitch value
    int16_t pitch_hi;                     // hi pitch value
    uint8_t priority;                      // priority at which vocs are played
    int16_t voc_num;                      // Backward reference to parent sound
    int voc_distance;                   // Sound's distance effectiveness
    Voc_Flags voc_flags;                // Various allowable flag settings for voc
    char lock;                          // locking byte for caching
    uint8_t playing;                       // number of this type of sound currently playing
};

// JIMSOUND3D(tm) variables section //////////////////////////////////////////

struct VOC3Dstruct
{
    VOC_INFOp vp;                       // Pointer to the sound
    int *x;                            // Pointer to x coordinate
    int *y;                            // Pointer to y coordinate
    int *z;                            // Pointer to z coordinate
    int fx, fy, fz;                    // Non-Follow literal values
    Voc3D_Flags flags;                  // 3d voc sound flags
    int handle;                         // Current handle to the voc
    short doplr_delta;                  // Change in distance since last call
    VOC3D_INFOp prev, next;             // Linked voc list
    short owner;                        // Hold index into user array to
    // delete looping sounds
    int num;                            // Digital Entry number used for
    // callback of looping sounds
    // If sound is active but user == 0, stop the sound
    short dist;                         // Current distance of sound from player
    uint8_t priority;                      // Used to force a higher priority based on distance
    int tics;                          // Tics used to count to next sound occurance
    int maxtics;                       // Tics until next sound occurance
    // for intermittent sounds
    SWBOOL deleted;                       // Has sound been marked for deletion?
    SWBOOL FX_Ok;                         // Did this sound play ok?
};

extern VOC_INFO voc[];

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

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.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "build.h"

#include "names2.h"
#include "panel.h"
#include "game.h"
#include "tags.h"
#include "sector.h"
#include "sprite.h"

short DoSpikeMatch(short match);
SWBOOL TestSpikeMatchActive(short match);
int DoVatorMove(short SpriteNum, int *lptr);
void InterpSectorSprites(short sectnum, SWBOOL state);

void ReverseSpike(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;

    // if paused go ahead and start it up again
    if (u->Tics)
    {
        u->Tics = 0;
        SetSpikeActive(SpriteNum);
        return;
    }

    // moving toward to OFF pos
    if (u->z_tgt == u->oz)
    {
        if (sp->z == u->oz)
            u->z_tgt = u->sz;
        else if (u->sz == u->oz)
            u->z_tgt = sp->z;
    }
    else if (u->z_tgt == u->sz)
    {
        if (sp->z == u->oz)
            u->z_tgt = sp->z;
        else if (u->sz == u->oz)
            u->z_tgt = u->sz;
    }

    u->vel_rate = -u->vel_rate;
}

SWBOOL
SpikeSwitch(short match, short setting)
{
    SPRITEp sp;
    short i,nexti;
    SWBOOL found = FALSE;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_DEFAULT], i, nexti)
    {
        sp = &sprite[i];

        if (sp->lotag == TAG_SPRITE_SWITCH_VATOR && sp->hitag == match)
        {
            found = TRUE;
            AnimateSwitch(sp, setting);
        }
    }

    return found;
}

void SetSpikeActive(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;
    SECTORp sectp = &sector[sp->sectnum];

    if (TEST(sp->cstat, CSTAT_SPRITE_YFLIP))
        short_setinterpolation(&sectp->ceilingheinum);
    else
        short_setinterpolation(&sectp->floorheinum);

    InterpSectorSprites(sp->sectnum, ON);

    // play activate sound
    DoSoundSpotMatch(SP_TAG2(sp), 1, SOUND_OBJECT_TYPE);

    SET(u->Flags, SPR_ACTIVE);
    u->Tics = 0;

    // moving to the ON position
    if (u->z_tgt == sp->z)
        VatorSwitch(SP_TAG2(sp), ON);
    else
    // moving to the OFF position
    if (u->z_tgt == u->sz)
        VatorSwitch(SP_TAG2(sp), OFF);
}

void SetSpikeInactive(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;
    SECTORp sectp = &sector[sp->sectnum];

    if (TEST(sp->cstat, CSTAT_SPRITE_YFLIP))
        short_stopinterpolation(&sectp->ceilingheinum);
    else
        short_stopinterpolation(&sectp->floorheinum);

    InterpSectorSprites(sp->sectnum, OFF);

    // play activate sound
    DoSoundSpotMatch(SP_TAG2(sp), 2, SOUND_OBJECT_TYPE);

    RESET(u->Flags, SPR_ACTIVE);
}

// called for operation from the space bar
short DoSpikeOperate(short sectnum)
{
    SPRITEp fsp;
    short match;
    short i,nexti;

    TRAVERSE_SPRITE_SECT(headspritesect[sectnum], i, nexti)
    {
        fsp = &sprite[i];

        if (fsp->statnum == STAT_SPIKE && SP_TAG1(fsp) == SECT_SPIKE && SP_TAG3(fsp) == 0)
        {
            sectnum = fsp->sectnum;

            match = SP_TAG2(fsp);
            if (match > 0)
            {
                if (TestSpikeMatchActive(match))
                    return -1;
                else
                    return DoSpikeMatch(match);
            }

            SetSpikeActive(i);
            break;
        }
    }

    return i;
}

// called from switches and triggers
// returns first spike found
short
DoSpikeMatch(short match)
{
    USERp fu;
    SPRITEp fsp;
    short first_spike = -1;

    short i,nexti;

    //SpikeSwitch(match, ON);

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_SPIKE], i, nexti)
    {
        fsp = &sprite[i];

        if (SP_TAG1(fsp) == SECT_SPIKE && SP_TAG2(fsp) == match)
        {
            fu = User[i];

            if (first_spike == -1)
                first_spike = i;

            if (TEST(fu->Flags, SPR_ACTIVE))
            {
                ReverseSpike(i);
                continue;
            }

            SetSpikeActive(i);
        }
    }

    return first_spike;
}

SWBOOL
TestSpikeMatchActive(short match)
{
    USERp fu;
    SPRITEp fsp;

    short i,nexti;

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_SPIKE], i, nexti)
    {
        fsp = &sprite[i];

        if (SP_TAG1(fsp) == SECT_SPIKE && SP_TAG2(fsp) == match)
        {
            fu = User[i];

            // door war
            if (TEST_BOOL6(fsp))
                continue;

            if (TEST(fu->Flags, SPR_ACTIVE) || fu->Tics)
                return TRUE;
        }
    }

    return FALSE;
}

int DoSpikeMove(short SpriteNum, int *lptr)
{
    USERp u = User[SpriteNum];
    int zval;

    zval = *lptr;

    // if LESS THAN goal
    if (zval < u->z_tgt)
    {
        // move it DOWN
        zval += (synctics * u->jump_speed);

        u->jump_speed += u->vel_rate * synctics;

        // if the other way make it equal
        if (zval > u->z_tgt)
            zval = u->z_tgt;
    }

    // if GREATER THAN goal
    if (zval > u->z_tgt)
    {
        // move it UP
        zval -= (synctics * u->jump_speed);

        u->jump_speed += u->vel_rate * synctics;

        if (zval < u->z_tgt)
            zval = u->z_tgt;
    }

    *lptr = zval;

    return 0;
}

void SpikeAlign(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;

    // either work on single sector or all tagged in SOBJ
    if ((int8_t)SP_TAG7(sp) < 0)
    {
        if (TEST(sp->cstat, CSTAT_SPRITE_YFLIP))
            alignceilslope(sp->sectnum, sp->x, sp->y, u->zclip);
        else
            alignflorslope(sp->sectnum, sp->x, sp->y, u->zclip);
    }
    else
    {
        if (TEST(sp->cstat, CSTAT_SPRITE_YFLIP))
            SOBJ_AlignCeilingToPoint(&SectorObject[SP_TAG7(sp)], sp->x, sp->y, u->zclip);
        else
            SOBJ_AlignFloorToPoint(&SectorObject[SP_TAG7(sp)], sp->x, sp->y, u->zclip);
    }
}

void MoveSpritesWithSpike(short sectnum)
{
    SPRITEp sp;
    short i,nexti;
    int cz,fz;

    TRAVERSE_SPRITE_SECT(headspritesect[sectnum], i, nexti)
    {
        sp = &sprite[i];

        if (User[i])
            continue;

        if (TEST(sp->extra, SPRX_STAY_PUT_VATOR))
            continue;

        getzsofslope(sectnum, sp->x, sp->y, &cz, &fz);
        sp->z = fz;
    }
}

int DoSpike(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;
    int *lptr;

    // zclip = floor or ceiling z
    // oz = original z
    // z_tgt = target z - on pos
    // sz = starting z - off pos

    lptr = &u->zclip;

    DoSpikeMove(SpriteNum, lptr);
    MoveSpritesWithSpike(sp->sectnum);
    SpikeAlign(SpriteNum);

    // EQUAL this entry has finished
    if (*lptr == u->z_tgt)
    {
        // in the ON position
        if (u->z_tgt == sp->z)
        {
            // change target
            u->z_tgt = u->sz;
            u->vel_rate = -u->vel_rate;

            SetSpikeInactive(SpriteNum);

            if (SP_TAG6(sp))
                DoMatchEverything(NULL, SP_TAG6(sp), -1);
        }
        else
        // in the OFF position
        if (u->z_tgt == u->sz)
        {
            short match = SP_TAG2(sp);

            // change target
            u->jump_speed = u->vel_tgt;
            u->vel_rate = labs(u->vel_rate);
            u->z_tgt = sp->z;

            SetSpikeInactive(SpriteNum);

            // set owner swith back to OFF
            // only if ALL spikes are inactive
            if (!TestSpikeMatchActive(match))
            {
                //SpikeSwitch(match, OFF);
            }

            if (SP_TAG6(sp) && TEST_BOOL5(sp))
                DoMatchEverything(NULL, SP_TAG6(sp), -1);
        }

        // operate only once
        if (TEST_BOOL2(sp))
        {
            SetSpikeInactive(SpriteNum);
            KillSprite(SpriteNum);
            return 0;
        }

        // setup to go back to the original z
        if (*lptr != u->oz)
        {
            if (u->WaitTics)
                u->Tics = u->WaitTics;
        }
    }
    else // if (*lptr == u->z_tgt)
    {
        // if heading for the OFF (original) position and should NOT CRUSH
        if (TEST_BOOL3(sp) && u->z_tgt == u->oz)
        {
            int i,nexti;
            SPRITEp bsp;
            USERp bu;
            SWBOOL found = FALSE;

            TRAVERSE_SPRITE_SECT(headspritesect[sp->sectnum], i, nexti)
            {
                bsp = &sprite[i];
                bu = User[i];

                if (bu && TEST(bsp->cstat, CSTAT_SPRITE_BLOCK) && TEST(bsp->extra, SPRX_PLAYER_OR_ENEMY))
                {
                    ReverseSpike(SpriteNum);
                    found = TRUE;
                    break;
                }
            }

            if (!found)
            {
                short pnum;
                PLAYERp pp;
                // go ahead and look for players clip box bounds
                TRAVERSE_CONNECT(pnum)
                {
                    pp = Player + pnum;

                    if (pp->lo_sectp == &sector[sp->sectnum] ||
                        pp->hi_sectp == &sector[sp->sectnum])
                    {
                        ReverseSpike(SpriteNum);
                        found = TRUE;
                    }
                }
            }
        }
    }

    return 0;
}

int DoSpikeAuto(short SpriteNum)
{
    USERp u = User[SpriteNum];
    SPRITEp sp = u->SpriteP;
    int *lptr;

    lptr = &u->zclip;

    DoSpikeMove(SpriteNum, lptr);
    MoveSpritesWithSpike(sp->sectnum);
    SpikeAlign(SpriteNum);

    // EQUAL this entry has finished
    if (*lptr == u->z_tgt)
    {
        // in the UP position
        if (u->z_tgt == sp->z)
        {
            // change target
            u->z_tgt = u->sz;
            u->vel_rate = -u->vel_rate;
            u->Tics = u->WaitTics;

            if (SP_TAG6(sp))
                DoMatchEverything(NULL, SP_TAG6(sp), -1);
        }
        else
        // in the DOWN position
        if (u->z_tgt == u->sz)
        {
            // change target
            u->jump_speed = u->vel_tgt;
            u->vel_rate = labs(u->vel_rate);
            u->z_tgt = sp->z;
            u->Tics = u->WaitTics;

            if (SP_TAG6(sp) && TEST_BOOL5(sp))
                DoMatchEverything(NULL, SP_TAG6(sp), -1);
        }
    }

    return 0;
}

#include "saveable.h"

static saveable_code saveable_spike_code[] =
{
    SAVE_CODE(ReverseSpike),
    SAVE_CODE(SpikeSwitch),
    SAVE_CODE(SetSpikeActive),
    SAVE_CODE(SetSpikeInactive),
    SAVE_CODE(DoSpikeOperate),
    SAVE_CODE(DoSpikeMatch),
    SAVE_CODE(TestSpikeMatchActive),
    SAVE_CODE(DoSpikeMove),
    SAVE_CODE(SpikeAlign),
    SAVE_CODE(MoveSpritesWithSpike),
    SAVE_CODE(DoSpike),
    SAVE_CODE(DoSpikeAuto),
};

saveable_module saveable_spike =
{
    // code
    saveable_spike_code,
    SIZ(saveable_spike_code),

    // data
    NULL,0
};