code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef sector_h_
#define sector_h_

#include "actors.h"  // actor_t
#include "gamevars.h"
#include "macros.h"
#include "dnames.h"  // for G_GetForcefieldPicnum()
#include "player.h"  // playerspawn_t

#ifdef __cplusplus
extern "C" {
#endif

#define MAXCYCLERS      1024
#define MAXANIMATES     1024
#define MAXANIMWALLS    512
#define MAXANIMPOINTS   2048

#define VIEWSCREEN_ACTIVE_DISTANCE 8192

typedef struct {
    int16_t wallnum, tag;
} animwalltype;

typedef struct {
    // this needs to have a copy of everything related to the map/actor state
    // see savegame.c
    int32_t g_animateGoal[MAXANIMATES], g_animateVel[MAXANIMATES], g_animateCnt;
    intptr_t g_animatePtr[MAXANIMATES];
    int32_t filler;
    vec2_t origins[MAXANIMPOINTS];
    int32_t randomseed, g_globalRandom;
    int32_t pskyidx;

    int16_t SpriteDeletionQueue[1024],g_spriteDeleteQueuePos;
    int16_t g_animateSect[MAXANIMATES];
    int16_t g_cyclers[MAXCYCLERS][6];
    int16_t g_mirrorWall[64], g_mirrorSector[64], g_mirrorCount;
    int16_t g_animWallCnt;
    int16_t g_cloudCnt,g_cloudSect[256],g_cloudX,g_cloudY;
    int16_t g_cyclerCnt;

    int32_t numsprites;
    int16_t tailspritefree;
    int16_t headspritesect[MAXSECTORS+1];
    int16_t headspritestat[MAXSTATUS+1];
    int16_t nextspritesect[MAXSPRITES];
    int16_t nextspritestat[MAXSPRITES];
    int16_t numsectors;
    int16_t numwalls;
    int16_t prevspritesect[MAXSPRITES];
    int16_t prevspritestat[MAXSPRITES];

    uint16_t g_earthquakeTime;
    int8_t g_playerSpawnCnt;

    uint8_t show2dsector[(MAXSECTORS+7)>>3];

    actor_t actor[MAXSPRITES];
    playerspawn_t g_playerSpawnPoints[MAXPLAYERS];
    animwalltype animwall[MAXANIMWALLS];
    usectortype sector[MAXSECTORS];
    spriteext_t spriteext[MAXSPRITES];
    uspritetype sprite[MAXSPRITES];
    uwalltype wall[MAXWALLS];
#ifndef NEW_MAP_FORMAT
    wallext_t wallext[MAXWALLS];
#endif
    intptr_t *vars[MAXGAMEVARS];
    intptr_t *arrays[MAXGAMEARRAYS];
    int32_t arraysiz[MAXGAMEARRAYS];
#ifdef YAX_ENABLE
    int32_t numyaxbunches;
# if !defined NEW_MAP_FORMAT
    int16_t yax_bunchnum[MAXSECTORS][2];
    int16_t yax_nextwall[MAXWALLS][2];
# endif
#endif
} mapstate_t;

extern void G_SaveMapState();
extern void G_RestoreMapState();

typedef struct {
    int32_t partime, designertime;
    char *name, *filename, *musicfn;
    mapstate_t *savedstate;
} map_t;

//extern map_t g_mapInfo[(MAXVOLUMES+1)*MAXLEVELS]; // +1 volume for "intro", "briefing" music

void G_ActivateBySector(int sect,int spriteNum);
int S_FindMusicSFX(int sectNum, int *sndptr);
int A_CallSound(int sectNum,int spriteNum);
int32_t A_CheckHitSprite(int spriteNum,int16_t *hitSprite);
void A_DamageObject_Duke3D(int spriteNum, int dmgSrc);
void A_DamageObject_Generic(int spriteNum, int dmgSrc);
void A_DamageObject(int spriteNum,int dmgSrc);
void A_DamageWall_Internal(int spr, int dawallnum, const vec3_t &pos, int weaponNum);
void A_DamageWall(int spr,int dawallnum,const vec3_t &pos,int weaponNum);
int __fastcall A_FindPlayer(spritetype const *pSprite,int32_t *dist);
void G_AlignWarpElevators(void);
int CheckDoorTile(int tileNum);
void G_AnimateCamSprite(int smoothRatio);
void G_AnimateWalls(void);
int G_ActivateWarpElevators(int s,int warpDir);
int G_CheckActivatorMotion(int lotag);
void G_DoSectorAnimations(void);
void G_OperateActivators(int lotag, int playerNum);
void G_OperateForceFields(int spriteNum,int wallTag);
void G_OperateMasterSwitches(int lotag);
void G_OperateRespawns(int lotag);
void G_OperateSectors(int sectNum,int spriteNum);
void P_HandleSharedKeys(int playerNum);
int GetAnimationGoal(const int32_t *animPtr);
int isanearoperator(int lotag);
int isanunderoperator(int lotag);
int P_ActivateSwitch(int playerNum, int wallOrSprite, int nSwitchType);
void P_CheckSectors(int playerNum);
void Sect_DamageFloor_Internal(int spriteNum, int sectNum);
void Sect_DamageFloor(int spriteNum, int sectNum);
void Sect_DamageCeiling_Internal(int spriteNum, int sectNum);
void Sect_DamageCeiling(int spriteNum, int sectNum);
int SetAnimation(int sectNum,int32_t *animPtr,int goalVal,int animVel);

#define FORCEFIELD_CSTAT (64+16+4+1)

// Returns W_FORCEFIELD if wall has a forcefield overpicnum, its overpicnum else.
static FORCE_INLINE int G_GetForcefieldPicnum(int const wallNum)
{
    int const tileNum = wall[wallNum].overpicnum;
    return tileNum == W_FORCEFIELD + 1 ? W_FORCEFIELD : tileNum;
}

// Returns the interpolated position of the camera that the player is looking
// through (using a viewscreen). <i> should be the player's ->newowner member.
static inline vec3_t G_GetCameraPosition(int32_t const i, int32_t const smoothratio)
{
    auto const cs = (uspriteptr_t)&sprite[i];
    const actor_t *const ca = &actor[i];

    return { ca->bpos.x + mulscale16(cs->x - ca->bpos.x, smoothratio),
             ca->bpos.y + mulscale16(cs->y - ca->bpos.y, smoothratio),
             ca->bpos.z + mulscale16(cs->z - ca->bpos.z, smoothratio) };
}

EXTERN_INLINE_HEADER int32_t G_GetPlayerInSector(int32_t const sect);

#ifdef __cplusplus
}
#endif

#if defined sector_c_ || !defined DISABLE_INLINING

EXTERN_INLINE int32_t G_GetPlayerInSector(int32_t const sect)
{
    for (int TRAVERSE_CONNECT(i))
        if ((unsigned)g_player[i].ps->i < MAXSPRITES && sprite[g_player[i].ps->i].sectnum == sect)
            return i;
    return -1;
}

#endif

#endif

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

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

#define KICK_HIT                         0
#define PISTOL_RICOCHET                  1
#define PISTOL_BODYHIT                   2
#define PISTOL_FIRE                      3
#define EJECT_CLIP                       4
#define INSERT_CLIP                      5
#define CHAINGUN_FIRE                    6
#define RPG_SHOOT                        7
#define POOLBALLHIT                      8
#define RPG_EXPLODE                      9
#define CAT_FIRE                        10
#define SHRINKER_FIRE                   11
#define ACTOR_SHRINKING                 12
#define PIPEBOMB_BOUNCE                 13
#define PIPEBOMB_EXPLODE                14
#define LASERTRIP_ONWALL                15
#define LASERTRIP_ARMING                16
#define LASERTRIP_EXPLODE               17
#define VENT_BUST                       18
#define GLASS_BREAKING                  19
#define GLASS_HEAVYBREAK                20
#define SHORT_CIRCUIT                   21
#define ITEM_SPLASH                     22
#define DUKE_BREATHING                  23
#define DUKE_EXHALING                   24
#define DUKE_GASP                       25
#define SLIM_RECOG                      26
// #define ENDSEQVOL3SND1                  27
#define DUKE_URINATE                    28
#define ENDSEQVOL3SND2                  29
#define ENDSEQVOL3SND3                  30
#define DUKE_PASSWIND                   32
#define DUKE_CRACK                      33
#define SLIM_ATTACK                     34
#define SOMETHINGHITFORCE               35
#define DUKE_DRINKING                   36
#define DUKE_KILLED1                    37
#define DUKE_GRUNT                      38
#define DUKE_HARTBEAT                   39
#define DUKE_ONWATER                    40
#define DUKE_DEAD                       41
#define DUKE_LAND                       42
#define DUKE_WALKINDUCTS                43
#define DUKE_GLAD                       44
#define DUKE_YES                        45
#define DUKE_HEHE                       46
#define DUKE_SHUCKS                     47
#define DUKE_UNDERWATER                 48
#define DUKE_JETPACK_ON                 49
#define DUKE_JETPACK_IDLE               50
#define DUKE_JETPACK_OFF                51
#define LIZTROOP_GROWL                  52
#define LIZTROOP_TALK1                  53
#define LIZTROOP_TALK2                  54
#define LIZTROOP_TALK3                  55
#define DUKETALKTOBOSS                  56
#define LIZCAPT_GROWL                   57
#define LIZCAPT_TALK1                   58
#define LIZCAPT_TALK2                   59
#define LIZCAPT_TALK3                   60
#define LIZARD_BEG                      61
#define LIZARD_PAIN                     62
#define LIZARD_DEATH                    63
#define LIZARD_SPIT                     64
#define DRONE1_HISSRATTLE               65
#define DRONE1_HISSSCREECH              66
#define DUKE_TIP2                       67
#define FLESH_BURNING                   68
#define SQUISHED                        69
#define TELEPORTER                      70
#define ELEVATOR_ON                     71
#define DUKE_KILLED3                    72
#define ELEVATOR_OFF                    73
#define DOOR_OPERATE1                   74
#define SUBWAY                          75
#define SWITCH_ON                       76
#define FAN                             77
#define DUKE_GETWEAPON3                 78
#define FLUSH_TOILET                    79
#define HOVER_CRAFT                     80
#define EARTHQUAKE                      81
#define INTRUDER_ALERT                  82
#define END_OF_LEVEL_WARN               83
#define ENGINE_OPERATING                84
#define REACTOR_ON                      85
#define COMPUTER_AMBIENCE               86
#define GEARS_GRINDING                  87
#define BUBBLE_AMBIENCE                 88
#define MACHINE_AMBIENCE                89
#define SEWER_AMBIENCE                  90
#define WIND_AMBIENCE                   91
#define SOMETHING_DRIPPING              92
#define STEAM_HISSING                   93
#define THEATER_BREATH                  94
#define BAR_MUSIC                       95
#define BOS1_ROAM                       96
#define BOS1_RECOG                      97
#define BOS1_ATTACK1                    98
#define BOS1_PAIN                       99
#define BOS1_DYING                     100
#define BOS2_ROAM                      101
#define BOS2_RECOG                     102
#define BOS2_ATTACK                    103
#define BOS2_PAIN                      104
#define BOS2_DYING                     105
#define GETATOMICHEALTH                106
#define DUKE_GETWEAPON2                107
#define BOS3_DYING                     108
#define SHOTGUN_FIRE                   109
#define PRED_ROAM                      110
#define PRED_RECOG                     111
#define PRED_ATTACK                    112
#define PRED_PAIN                      113
#define PRED_DYING                     114
#define CAPT_ROAM                      115
#define CAPT_ATTACK                    116
#define CAPT_RECOG                     117
#define CAPT_PAIN                      118
#define CAPT_DYING                     119
#define PIG_ROAM                       120
#define PIG_RECOG                      121
#define PIG_ATTACK                     122
#define PIG_PAIN                       123
#define PIG_DYING                      124
#define RECO_ROAM                      125
#define RECO_RECOG                     126
#define RECO_ATTACK                    127
#define RECO_PAIN                      128
#define RECO_DYING                     129
#define DRON_ROAM                      130
#define DRON_RECOG                     131
#define DRON_ATTACK1                   132
#define DRON_PAIN                      133
#define DRON_DYING                     134
#define COMM_ROAM                      135
#define COMM_RECOG                     136
#define COMM_ATTACK                    137
#define COMM_PAIN                      138
#define COMM_DYING                     139
#define OCTA_ROAM                      140
#define OCTA_RECOG                     141
#define OCTA_ATTACK1                   142
#define OCTA_PAIN                      143
#define OCTA_DYING                     144
#define TURR_ROAM                      145
#define TURR_RECOG                     146
#define TURR_ATTACK                    147
#define DUMPSTER_MOVE                  148
#define SLIM_DYING                     149
#define BOS3_ROAM                      150
#define BOS3_RECOG                     151
#define BOS3_ATTACK1                   152
#define BOS3_PAIN                      153
#define BOS1_ATTACK2                   154
#define COMM_SPIN                      155
#define BOS1_WALK                      156
#define DRON_ATTACK2                   157
#define THUD                           158
#define OCTA_ATTACK2                   159
#define WIERDSHOT_FLY                  160
#define TURR_PAIN                      161
#define TURR_DYING                     162
#define SLIM_ROAM                      163
#define LADY_SCREAM                    164
#define DOOR_OPERATE2                  165
#define DOOR_OPERATE3                  166
#define DOOR_OPERATE4                  167
#define BORNTOBEWILDSND                168
#define SHOTGUN_COCK                   169
#define GENERIC_AMBIENCE1              170
#define GENERIC_AMBIENCE2              171
#define GENERIC_AMBIENCE3              172
#define GENERIC_AMBIENCE4              173
#define GENERIC_AMBIENCE5              174
#define GENERIC_AMBIENCE6              175
#define BOS3_ATTACK2                   176
#define GENERIC_AMBIENCE17             177
#define GENERIC_AMBIENCE18             178
#define GENERIC_AMBIENCE19             179
#define GENERIC_AMBIENCE20             180
#define GENERIC_AMBIENCE21             181
#define GENERIC_AMBIENCE22             182
#define SECRETLEVELSND                 183
#define GENERIC_AMBIENCE8              184
#define GENERIC_AMBIENCE9              185
#define GENERIC_AMBIENCE10             186
#define GENERIC_AMBIENCE11             187
#define GENERIC_AMBIENCE12             188
#define GENERIC_AMBIENCE13             189
#define GENERIC_AMBIENCE14             190
#define GENERIC_AMBIENCE15             192
#define GENERIC_AMBIENCE16             193
#define FIRE_CRACKLE                   194
#define BONUS_SPEECH1                  195
#define BONUS_SPEECH2                  196
#define BONUS_SPEECH3                  197
#define PIG_CAPTURE_DUKE               198
#define BONUS_SPEECH4                  199
#define DUKE_LAND_HURT                 200
#define DUKE_HIT_STRIPPER1             201
#define DUKE_TIP1                      202
#define DUKE_KILLED2                   203
#define PRED_ROAM2                     204
#define PIG_ROAM2                      205
#define DUKE_GETWEAPON1                206
#define DUKE_SEARCH2                   207
#define DUKE_CRACK2                    208
#define DUKE_SEARCH                    209
#define DUKE_GET                       210
#define DUKE_LONGTERM_PAIN             211
#define MONITOR_ACTIVE                 212
#define NITEVISION_ONOFF               213
#define DUKE_HIT_STRIPPER2             214
#define DUKE_CRACK_FIRST               215
#define DUKE_USEMEDKIT                 216
#define DUKE_TAKEPILLS                 217
#define DUKE_PISSRELIEF                218
#define SELECT_WEAPON                  219
#define WATER_GURGLE                   220
#define DUKE_GETWEAPON4                221
#define JIBBED_ACTOR1                  222
#define JIBBED_ACTOR2                  223
#define JIBBED_ACTOR3                  224
#define JIBBED_ACTOR4                  225
#define JIBBED_ACTOR5                  226
#define JIBBED_ACTOR6                  227
#define JIBBED_ACTOR7                  228
#define DUKE_GOTHEALTHATLOW            229
#define BOSSTALKTODUKE                 230
#define WAR_AMBIENCE1                  231
#define WAR_AMBIENCE2                  232
#define WAR_AMBIENCE3                  233
#define WAR_AMBIENCE4                  234
#define WAR_AMBIENCE5                  235
#define WAR_AMBIENCE6                  236
#define WAR_AMBIENCE7                  237
#define WAR_AMBIENCE8                  238
#define WAR_AMBIENCE9                  239
#define WAR_AMBIENCE10                 240
#define ALIEN_TALK1                    241
#define ALIEN_TALK2                    242
#define EXITMENUSOUND                  243
#define FLY_BY                         244
#define DUKE_SCREAM                    245
#define SHRINKER_HIT                   246
#define RATTY                          247
#define INTO_MENU                      248
#define BONUSMUSIC                     249
#define DUKE_BOOBY                     250
#define DUKE_TALKTOBOSSFALL            251
#define DUKE_LOOKINTOMIRROR            252
#define PIG_ROAM3                      253
#define KILLME                         254
#define DRON_JETSND                    255
#define SPACE_DOOR1                    256
#define SPACE_DOOR2                    257
#define SPACE_DOOR3                    258
#define SPACE_DOOR4                    259
#define SPACE_DOOR5                    260
#define ALIEN_ELEVATOR1                261
#define VAULT_DOOR                     262
#define JIBBED_ACTOR13                 263
#define DUKE_GETWEAPON6                264
#define JIBBED_ACTOR8                  265
#define JIBBED_ACTOR9                  266
#define JIBBED_ACTOR10                 267
#define JIBBED_ACTOR11                 268
#define JIBBED_ACTOR12                 269
#define DUKE_KILLED4                   270
#define DUKE_KILLED5                   271
#define ALIEN_SWITCH1                  272
#define DUKE_STEPONFECES               273
#define DUKE_LONGTERM_PAIN2            274
#define DUKE_LONGTERM_PAIN3            275
#define DUKE_LONGTERM_PAIN4            276
#define COMPANB2                       277
#define KTIT                           278
#define HELICOP_IDLE                   279
#define STEPNIT                        280
#define SPACE_AMBIENCE1                281
#define SPACE_AMBIENCE2                282
#define SLIM_HATCH                     283
#define RIPHEADNECK                    284
#define FOUNDJONES                     285
#define ALIEN_DOOR1                    286
#define ALIEN_DOOR2                    287
#define ENDSEQVOL3SND4                 288
#define ENDSEQVOL3SND5                 289
#define ENDSEQVOL3SND6                 290
#define ENDSEQVOL3SND7                 291
#define ENDSEQVOL3SND8                 292
#define ENDSEQVOL3SND9                 293
#define WHIPYOURASS                    294
#define ENDSEQVOL2SND1                 295
#define ENDSEQVOL2SND2                 296
#define ENDSEQVOL2SND3                 297
#define ENDSEQVOL2SND4                 298
#define ENDSEQVOL2SND5                 299
#define ENDSEQVOL2SND6                 300
#define ENDSEQVOL2SND7                 301
#define GENERIC_AMBIENCE23             302
#define SOMETHINGFROZE                 303
#define DUKE_LONGTERM_PAIN5            304
#define DUKE_LONGTERM_PAIN6            305
#define DUKE_LONGTERM_PAIN7            306
#define DUKE_LONGTERM_PAIN8            307
#define WIND_REPEAT                    308
#define MYENEMY_ROAM                   309
#define MYENEMY_HURT                   310
#define MYENEMY_DEAD                   311
#define MYENEMY_SHOOT                  312
#define STORE_MUSIC                    313
#define STORE_MUSIC_BROKE              314
#define ACTOR_GROWING                  315
#define NEWBEAST_ROAM                  316
#define NEWBEAST_RECOG                 317
#define NEWBEAST_ATTACK                318
#define NEWBEAST_PAIN                  319
#define NEWBEAST_DYING                 320
#define NEWBEAST_SPIT                  321
#define VOL4_1                         322
#define SUPERMARKET                    323
#define MOUSEANNOY                     324
#define BOOKEM                         325
#define SUPERMARKETCRY                 326
#define DESTRUCT                       327
#define EATFOOD                        328
#define MAKEMYDAY                      329
#define WITNESSSTAND                   330
#define VACATIONSPEECH                 331
#define YIPPEE1                        332
#define YOHOO1                         333
#define YOHOO2                         334
#define DOLPHINSND                     335
#define TOUGHGALSND1                   336
#define TOUGHGALSND2                   337
#define TOUGHGALSND3                   338
#define TOUGHGALSND4                   339
#define TANK_ROAM                      340
#define BOS4_ROAM                      341
#define BOS4_RECOG                     342
#define BOS4_ATTACK                    343
#define BOS4_PAIN                      344
#define BOS4_DYING                     345
#define NEWBEAST_ATTACKMISS            346
#define VOL4_2                         347
#define COOKINGDEEPFRIER               348
#define WHINING_DOG                    349
#define DEAD_DOG                       350
#define LIGHTNING_SLAP                 351
#define THUNDER                        352
#define HAPPYMOUSESND1                 353
#define HAPPYMOUSESND2                 354
#define HAPPYMOUSESND3                 355
#define HAPPYMOUSESND4                 356
#define ALARM                          357
#define RAIN                           358
#define DTAG_GREENRUN                  359
#define DTAG_BROWNRUN                  360
#define DTAG_GREENSCORE                361
#define DTAG_BROWNSCORE                362
#define INTRO4_1                       363
#define INTRO4_2                       364
#define INTRO4_3                       365
#define INTRO4_4                       366
#define INTRO4_5                       367
#define INTRO4_6                       368
#define SCREECH                        369
#define BOSS4_DEADSPEECH               370
#define BOSS4_FIRSTSEE                 371
#define PARTY_SPEECH                   372
#define POSTAL_SPEECH                  373
#define TGSPEECH                       374
#define DOGROOMSPEECH                  375
#define SMACKED                        376
#define MDEVSPEECH                     377
#define AREA51SPEECH                   378
#define JEEPSOUND                      379
#define BIGDOORSLAM                    380
#define BOS4_LAY                       381
#define WAVESOUND                      382
#define ILLBEBACK                      383
#define VOL4ENDSND1                    384
#define VOL4ENDSND2                    385
#define EXPANDERHIT                    386
#define SNAKESPEECH                    387
#define EXPANDERSHOOT                  388
#define GETBACKTOWORK                  389
#define JIBBED_ACTOR14                 390
#define JIBBED_ACTOR15                 391
#define INTRO4_B                       392
#define BIGBANG                        393
#define HORNSND                        394
#define BELLSND                        395
#define GOAWAY                         396
#define JOKE                           397


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2016 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 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 "sounds.h"
#include "al_midi.h"
#include "compat.h"
#include "duke3d.h"
#include "renderlayer.h"  // for win_gethwnd()
#include "vfs.h"

#include <atomic>

int32_t g_numEnvSoundsPlaying, g_highestSoundIdx;
static bool g_dukeTalk;

static char *MusicPtr;

int32_t MusicIsWaveform;
int32_t MusicVoice = -1;

static bool MusicPaused;
static bool SoundPaused;

static uint32_t localQueueIndex;
static uint32_t freeSlotQueue[MAXVOICES];
#ifndef NDEBUG
static std::atomic<int> freeSlotPendingCnt;
#endif
static std::atomic<uint32_t> freeSlotReadIndex, freeSlotWriteIndex;

sound_t **g_sounds;
sound_t nullsound;
voiceinfo_t nullvoice = { -1, FX_Ok, UINT16_MAX };

void S_AllocIndexes(int sndidx)
{
    Bassert(sndidx < MAXSOUNDS);

    if (sndidx > g_highestSoundIdx || !g_highestSoundIdx)
    {
        int const lastSize = g_highestSoundIdx;
        g_highestSoundIdx  = sndidx;

        g_sounds = (sound_t **)Xrealloc(g_sounds, (g_highestSoundIdx + 1) * sizeof(intptr_t));

        if (!lastSize)
            g_sounds[0] = &nullsound;

        for (int i = lastSize + 1; i <= g_highestSoundIdx; i++)
            g_sounds[i] = &nullsound;
    }
}

static FORCE_INLINE void S_FillVoiceInfo(voiceinfo_t *snd, int16_t const owner, int16_t const voice, uint16_t const dist)
{
    *snd = { owner, voice, dist };
}

void S_SoundStartup(void)
{
#ifdef _WIN32
    void *initdata = (void *) win_gethwnd(); // used for DirectSound
#else
    void *initdata = NULL;
#endif

    int status = FX_Init(ud.config.NumVoices, ud.config.NumChannels, ud.config.MixRate, initdata);

    if (status != FX_Ok)
    {
        LOG_F(ERROR, "Failed initializing sound subsystem: %s", FX_ErrorString(status));
        return;
    }

    freeSlotReadIndex = freeSlotWriteIndex = localQueueIndex = 0;
#ifndef NDEBUG
    freeSlotPendingCnt = 0;
#endif
    nullsound.voices = &nullvoice;

    if (!g_sounds)
    {
        Bassert(g_highestSoundIdx == 0);
        g_sounds = (sound_t **)Xmalloc(sizeof(intptr_t));
        g_sounds[0] = &nullsound;
    }

    for (int i = 0; i <= g_highestSoundIdx; ++i)
    {
        if (g_sounds[i] == &nullsound)
            continue;

        g_sounds[i]->playing = 0;
        g_sounds[i]->voices = &nullvoice;

#ifdef CACHING_DOESNT_SUCK
        g_sounds[i]->lock = CACHE1D_UNLOCKED;
#endif
    }

    FX_SetVolume(ud.config.FXVolume);
    //S_MusicVolume(ud.config.MusicVolume);

#ifdef ASS_REVERSESTEREO
    FX_SetReverseStereo(ud.config.ReverseStereo);
#endif
    FX_SetCallBack(S_Callback);
}

void S_SoundShutdown(void)
{
    if (MusicVoice >= 0)
        S_MusicShutdown();

    int status = FX_Shutdown();
    if (status != FX_Ok)
    {
        Bsprintf(tempbuf, "Failed tearing down sound subsystem: %s", FX_ErrorString(status));
        LOG_F(ERROR, "%s", tempbuf);
        G_GameExit(tempbuf);
    }
}

void S_MusicStartup(void)
{
    int status;
    if ((status = MUSIC_Init(ud.config.MusicDevice)) == MUSIC_Ok)
    {
        if (ud.config.MusicDevice == ASS_AutoDetect)
            ud.config.MusicDevice = MIDI_GetDevice();
    }
    else if ((status = MUSIC_Init(ASS_AutoDetect)) == MUSIC_Ok)
    {
        ud.config.MusicDevice = MIDI_GetDevice();
    }
    else
    {
        LOG_F(ERROR, "Failed initializing music subsystem: %s", MUSIC_ErrorString(status));
        return;
    }

    MUSIC_SetVolume(ud.config.MusicVolume);

    auto const fil = kopen4load("d3dtimbr.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 S_MusicShutdown(void)
{
    S_StopMusic();

    int status = MUSIC_Shutdown();
    if (status != MUSIC_Ok)
        LOG_F(ERROR, "Failed tearing down music subsystem: %s", MUSIC_ErrorString(status));
}

void S_PauseMusic(bool paused)
{
    if (MusicPaused == paused || (MusicIsWaveform && MusicVoice < 0))
        return;

    MusicPaused = paused;

    if (MusicIsWaveform)
    {
        FX_PauseVoice(MusicVoice, paused);
        return;
    }

    if (paused)
        MUSIC_Pause();
    else
        MUSIC_Continue();
}

void S_PauseSounds(bool paused)
{
    if (SoundPaused == paused)
        return;

    SoundPaused = paused;

    for (int i = 0; i <= g_highestSoundIdx; ++i)
    {
        if (g_sounds[i]->voices == &nullvoice)
            continue;

        for (int j = 0; j < MAXSOUNDINSTANCES; ++j)
        {
            auto &voice = g_sounds[i]->voices[j];
            if (bitmap_test(&g_sounds[i]->playing, j) && voice.handle > FX_Ok)
                FX_PauseVoice(voice.handle, paused);
        }
    }
}

void S_MusicVolume(int32_t volume)
{
    if (MusicIsWaveform && MusicVoice >= 0)
        FX_SetPan(MusicVoice, volume, volume, volume);

    MUSIC_SetVolume(volume);
}

void S_RestartMusic(void)
{
    if (ud.recstat != 2 && g_player[myconnectindex].ps->gm&MODE_GAME)
    {
        S_PlayLevelMusicOrNothing(g_musicIndex);
    }
    else if (G_GetLogoFlags() & LOGO_PLAYMUSIC)
    {
        S_PlaySpecialMusicOrNothing(MUS_INTRO);
    }
}

void S_MenuSound(void)
{
#ifndef EDUKE32_STANDALONE
    static int SoundNum;
    int const menusnds[] = {
        LASERTRIP_EXPLODE, DUKE_GRUNT,       DUKE_LAND_HURT,   CHAINGUN_FIRE, SQUISHED,      KICK_HIT,
        PISTOL_RICOCHET,   PISTOL_BODYHIT,   PISTOL_FIRE,      SHOTGUN_FIRE,  BOS1_WALK,     RPG_EXPLODE,
        PIPEBOMB_BOUNCE,   PIPEBOMB_EXPLODE, NITEVISION_ONOFF, RPG_SHOOT,     SELECT_WEAPON,
    };
    int s = VM_OnEventWithReturn(EVENT_OPENMENUSOUND, g_player[screenpeek].ps->i, screenpeek, FURY ? -1 : menusnds[SoundNum++ % ARRAY_SIZE(menusnds)]);
#else
    int s = VM_OnEventWithReturn(EVENT_OPENMENUSOUND, g_player[screenpeek].ps->i, screenpeek, -1);
#endif
    if (s != -1)
        S_PlaySound(s);
}

static int S_PlayMusic(const char *fn)
{
    if (!ud.config.MusicToggle)
        return 0;

    if (fn == NULL)
        return 1;

    buildvfs_kfd fp = S_OpenAudio(fn, 0, 1);
    if (EDUKE32_PREDICT_FALSE(fp == buildvfs_kfd_invalid))
    {
        LOG_F(ERROR, "Unable to play %s: file not found.",fn);
        return 2;
    }

    int32_t MusicLen = kfilelength(fp);

    if (EDUKE32_PREDICT_FALSE(MusicLen < 4))
    {
        LOG_F(ERROR, "Unable to play %s: no data.",fn);
        kclose(fp);
        return 3;
    }

    char * MyMusicPtr = (char *)Xaligned_alloc(16, MusicLen);
    int MyMusicSize = kread(fp, MyMusicPtr, MusicLen);

    if (EDUKE32_PREDICT_FALSE(MyMusicSize != MusicLen))
    {
        LOG_F(ERROR, "Unable to play %s: read %d of %d bytes expected.", fn, MyMusicSize, MusicLen);
        kclose(fp);
        ALIGNED_FREE_AND_NULL(MyMusicPtr);
        return 4;
    }

    kclose(fp);

    if (!Bmemcmp(MyMusicPtr, "MThd", 4))
    {
        int32_t retval = MUSIC_PlaySong(MyMusicPtr, MyMusicSize, MUSIC_LoopSong, fn);

        if (retval != MUSIC_Ok)
        {
            ALIGNED_FREE_AND_NULL(MyMusicPtr);
            return 5;
        }

        if (MusicIsWaveform && MusicVoice >= 0)
        {
            FX_StopSound(MusicVoice);
            MusicVoice = -1;
        }

        MusicIsWaveform = 0;
        ALIGNED_FREE_AND_NULL(MusicPtr);
        MusicPtr    = MyMusicPtr;
        g_musicSize = MyMusicSize;
    }
    else
    {
        int MyMusicVoice = FX_Play(MyMusicPtr, MusicLen, 0, 0, 0, ud.config.MusicVolume, ud.config.MusicVolume, ud.config.MusicVolume,
                                   FX_MUSIC_PRIORITY, fix16_one, MUSIC_ID);

        if (MyMusicVoice <= FX_Ok)
        {
            ALIGNED_FREE_AND_NULL(MyMusicPtr);
            return 5;
        }

        if (MusicIsWaveform && MusicVoice >= 0)
            FX_StopSound(MusicVoice);

        MUSIC_StopSong();

        MusicVoice      = MyMusicVoice;
        MusicIsWaveform = 1;
        ALIGNED_FREE_AND_NULL(MusicPtr);
        MusicPtr    = MyMusicPtr;
        g_musicSize = MyMusicSize;
    }

    return 0;
}

static void S_SetMusicIndex(unsigned int m)
{
    g_musicIndex = m;
    ud.music_episode = m / MAXLEVELS;
    ud.music_level   = m % MAXLEVELS;
}

bool S_TryPlayLevelMusic(unsigned int m)
{
    ud.returnvar[0] = m / MAXLEVELS;
    ud.returnvar[1] = m % MAXLEVELS;

    int retval = VM_OnEvent(EVENT_PLAYLEVELMUSICSLOT, g_player[myconnectindex].ps->i, myconnectindex);

    if (retval < 0)
        return false;

    char const * musicfn = g_mapInfo[m].musicfn;

    if (musicfn != NULL)
    {
        if (!S_PlayMusic(musicfn))
        {
            S_SetMusicIndex(m);
            return false;
        }
    }

    return true;
}

void S_PlayLevelMusicOrNothing(unsigned int m)
{
    if (S_TryPlayLevelMusic(m))
    {
        S_StopMusic();
        S_SetMusicIndex(m);
    }
}

int S_TryPlaySpecialMusic(unsigned int m)
{
    char const * musicfn = g_mapInfo[m].musicfn;
    if (musicfn != NULL)
    {
        if (!S_PlayMusic(musicfn))
        {
            S_SetMusicIndex(m);
            return 0;
        }
    }

    return 1;
}

void S_PlaySpecialMusicOrNothing(unsigned int m)
{
    if (S_TryPlaySpecialMusic(m))
    {
        S_StopMusic();
        S_SetMusicIndex(m);
    }
}

void S_ContinueLevelMusic(void)
{
    VM_OnEvent(EVENT_CONTINUELEVELMUSICSLOT, g_player[myconnectindex].ps->i, myconnectindex);
}

int32_t S_GetMusicPosition(void)
{
    int32_t position = 0;

    if (MusicIsWaveform)
        FX_GetPosition(MusicVoice, &position);

    return position;
}

void S_SetMusicPosition(int32_t position)
{
    if (MusicIsWaveform)
        FX_SetPosition(MusicVoice, position);
}

void S_StopMusic(void)
{
    MusicPaused = 0;

    if (MusicIsWaveform && MusicVoice >= 0)
    {
        FX_StopSound(MusicVoice);
        MusicVoice = -1;
        MusicIsWaveform = 0;
    }

    MUSIC_StopSong();

    ALIGNED_FREE_AND_NULL(MusicPtr);
    g_musicSize = 0;
}

void S_Cleanup(void)
{
    while (localQueueIndex != freeSlotReadIndex.load(std::memory_order_acquire))
    {
        uint32_t num = freeSlotQueue[++localQueueIndex & (MAXVOICES-1)];

        // I don't think this can actually happen
        while (num == 0xdeadbeef)
        {
            num = freeSlotQueue[(localQueueIndex-1) & (MAXVOICES-1)];
            LOG_F(ERROR, "Unexpected magic beef!");
        }

        freeSlotQueue[localQueueIndex & (MAXVOICES-1)] = 0xdeadbeef;
#ifndef NDEBUG
        int cnt = freeSlotPendingCnt.fetch_sub(1, std::memory_order_relaxed);
        Bassert(cnt >= 0);
#endif
        // negative index is RTS playback
        if ((int32_t)num < 0)
        {
            int const rtsindex = klabs((int32_t)num);

            Bassert((unsigned)rtsindex < sizeof(rts_lumplockbyte));
            if (rts_lumplockbyte[rtsindex] >= CACHE1D_LOCKED)
                --rts_lumplockbyte[rtsindex];
            continue;
        }

        // num + (MAXSOUNDS*MAXSOUNDINSTANCES) is a sound played globally
        // for which there was no open slot to keep track of the voice
        if (num >= (MAXSOUNDS*MAXSOUNDINSTANCES))
        {
            if (g_sounds[num-(MAXSOUNDS*MAXSOUNDINSTANCES)]->flags & SF_TALK)
                g_dukeTalk = false;
#ifdef CACHING_DOESNT_SUCK
            --g_soundlocks[num-(MAXSOUNDS*MAXSOUNDINSTANCES)];
#endif
            continue;
        }

        int const voiceindex = num & (MAXSOUNDINSTANCES - 1);

        num = (num - voiceindex) / MAXSOUNDINSTANCES;

        auto &snd   = g_sounds[num];
        auto &voice = snd->voices[voiceindex];
        int const spriteNum = voice.owner;

        DVLOG_IF_F(LOG_DEBUG, !bitmap_test(&snd->playing, voiceindex), "Sound %d index %d is not playing", num, voiceindex);
        bitmap_clear(&snd->playing, voiceindex);

        if (snd->flags & SF_TALK && g_dukeTalk)
            g_dukeTalk = false;

        // MUSICANDSFX uses t_data[0] to control restarting the sound
        // CLEAR_SOUND_T0
        if (spriteNum != -1 && S_IsAmbientSFX(spriteNum) && sector[SECT(spriteNum)].lotag < 3)  // ST_2_UNDERWATER
            actor[spriteNum].t_data[0] = 0;

        S_FillVoiceInfo(&voice, -1, 0, UINT16_MAX);

#ifdef CACHING_DOESNT_SUCK
        --g_soundlocks[num];
#endif
    }
}

// returns number of bytes read
int32_t S_LoadSound(int num)
{
    if ((unsigned)num > (unsigned)g_highestSoundIdx || EDUKE32_PREDICT_FALSE(g_sounds[num] == &nullsound))
        return 0;

    auto &snd = g_sounds[num];

    buildvfs_kfd fp = S_OpenAudio(snd->filename, g_loadFromGroupOnly, 0);

    if (EDUKE32_PREDICT_FALSE(fp == buildvfs_kfd_invalid))
    {
        DVLOG_F(LOG_DEBUG, "Unable to load sound #%d: file %s not found.", num, snd->filename);
        return 0;
    }

    int32_t l = kfilelength(fp);
    g_sounds[num]->lock = CACHE1D_PERMANENT;
    snd->len = l;
    g_cache.allocateBlock((intptr_t *)&snd->ptr, l, (char *)&g_sounds[num]->lock);
    l = kread(fp, snd->ptr, l);
    kclose(fp);

    return l;
}

void cacheAllSounds(void)
{
    if (!g_sounds)
        return;

    for (int i=0, j=0; i <= g_highestSoundIdx; ++i)
    {
        if (g_sounds[i] != &nullsound && g_sounds[i]->ptr == nullptr)
        {
            S_LoadSound(i);
            j += g_sounds[i]->len;
            if (j >= 4 * 1024 * 1024)
                gameHandleEvents(), j = 0;
        }
    }
}

static int32_t S_DefineAudioIfSupported(char** fn, const char* name)
{
#if !defined HAVE_FLAC || !defined HAVE_VORBIS
    const char* extension = Bstrrchr(name, '.');
# if !defined HAVE_FLAC
    if (extension && !Bstrcasecmp(extension, ".flac"))
        return -2;
# endif
# if !defined HAVE_VORBIS
    if (extension && !Bstrcasecmp(extension, ".ogg"))
        return -2;
# endif
#endif
    realloc_copy(fn, name);
    return 0;
}

int32_t S_DefineSound(int sndidx, const char *name, int minpitch, int maxpitch, int priority, int type, int distance, float volume)
{
    if (EDUKE32_PREDICT_FALSE((unsigned)sndidx >= MAXSOUNDS))
        return -1;

    S_AllocIndexes(sndidx);

    if (g_sounds[sndidx] == &nullsound)
        g_sounds[sndidx] = (sound_t *)Xcalloc(1, sizeof(sound_t));

    auto &snd = g_sounds[sndidx];

    if (snd->filename == NULL)
    {
        int const len = Bstrlen(name);
        snd->filename = (char *)Xcalloc(len+1, sizeof(uint8_t));
        Bstrcpy(snd->filename, name);
    }

    if (S_DefineAudioIfSupported(&snd->filename, name))
        return -1;

    snd->minpitch   = clamp(minpitch, INT16_MIN, INT16_MAX);
    snd->maxpitch   = clamp(maxpitch, INT16_MIN, INT16_MAX);
    snd->priority   = priority & 0xFF;
    snd->flags       = type & ~SF_ONEINST_INTERNAL;
    snd->distOffset = clamp(distance, INT16_MIN, INT16_MAX);
    snd->volume     = volume * fix16_one;
    snd->voices     = &nullvoice;

    if (snd->flags & SF_LOOP)
        snd->flags |= SF_ONEINST_INTERNAL;

    return 0;
}

// Returns:
//   0: all OK
//  -1: ID declaration was invalid:
int32_t S_DefineMusic(const char* ID, const char* name)
{
    int32_t sel = MUS_FIRST_SPECIAL;

    Bassert(ID != NULL);

    if (!Bstrcmp(ID, "intro"))
    {
        // nothing
    }
    else if (!Bstrcmp(ID, "briefing"))
    {
        sel++;
    }
    else if (!Bstrcmp(ID, "loading"))
    {
        sel += 2;
    }
    else if (!Bstrcmp(ID, "usermap"))
    {
        sel += 3;
    }
    else
    {
        sel = G_GetMusicIdx(ID);
        if (sel < 0)
            return -1;
    }

    return S_DefineAudioIfSupported(&g_mapInfo[sel].musicfn, name);
}

static inline int S_GetPitch(int num)
{
    auto const &snd   = g_sounds[num];
    int const   range = klabs(snd->maxpitch - snd->minpitch);

    return (range == 0) ? snd->minpitch : min(snd->minpitch, snd->maxpitch) + wrand() % range;
}

static int S_GetSlot(int soundNum)
{
    int slot = 0;
    int bestslot = 0;
    uint16_t dist = 0;
    int position = 0;

    auto &snd = g_sounds[soundNum];

    S_Cleanup();

    do
    {
        if (bitmap_test(&snd->playing, slot) == 0) return slot;

        auto &voice = snd->voices[slot];
        int   fxpos = 0;

        if (FX_SoundValidAndActive(voice.handle))
        {
            if (voice.dist == dist)
                FX_GetPosition(voice.handle, &fxpos);

            if (voice.dist > dist || (voice.dist == dist && fxpos > position))
            {
                dist     = voice.dist;
                position = fxpos;
                bestslot = slot;
            }
        }
    }
    while (++slot < MAXSOUNDINSTANCES);

    if (!FX_SoundValidAndActive(snd->voices[bestslot].handle))
        return bestslot;

    slot = bestslot;
    FX_StopSound(snd->voices[slot].handle);
    S_Cleanup();

    return slot;
}

static FORCE_INLINE int S_GetAngle(int ang, const vec3_t &cam, const vec3_t &pos)
{
    return (2048 + ang - getangle(cam.x - pos.x, cam.y - pos.y)) & 2047;
}

static int S_CalcDistAndAng(int spriteNum, int soundNum, int sectNum, int angle,
                             const vec3_t &cam, const vec3_t &pos, int *distPtr, int *angPtr)
{
    int sndang = 0, sndist = 0, explosion = 0;

    if (PN(spriteNum) == APLAYER && P_Get(spriteNum) == screenpeek)
        goto sound_further_processing;

    sndang = S_GetAngle(angle, cam, pos);
    sndist = FindDistance3D(cam.x-pos.x, cam.y-pos.y, (cam.z-pos.z));

#ifdef SPLITSCREEN_MOD_HACKS
    if (g_fakeMultiMode==2)
    {
        // HACK for splitscreen mod: take the min of sound distances
        // to 1st and 2nd player.

        if (PN(spriteNum) == APLAYER && P_Get(spriteNum) == 1)
        {
            sndist = sndang = 0;
            goto sound_further_processing;
        }

        {
            const vec3_t &cam2 = g_player[1].ps->pos;
            int32_t sndist2 = FindDistance3D(cam2.x-pos.x, cam2.y-pos.y, (cam2.z-pos.z));

            if (sndist2 < sndist)
            {
                sectNum = g_player[1].ps->cursectnum;
                angle = fix16_to_int(g_player[1].ps->q16ang);

                sndist = sndist2;
                sndang = S_GetAngle(angle, cam2, pos);
            }
        }
    }
#endif

    if ((g_sounds[soundNum]->flags & (SF_GLOBAL|SF_DTAG)) != SF_GLOBAL && S_IsAmbientSFX(spriteNum) && (sector[SECT(spriteNum)].lotag&0xff) < 9)  // ST_9_SLIDING_ST_DOOR
        sndist = divscale14(sndist, SHT(spriteNum)+1);

sound_further_processing:
    sndist += g_sounds[soundNum]->distOffset;
    if (sndist < 0)
        sndist = 0;

#ifndef EDUKE32_STANDALONE
    if (!FURY && ((unsigned)sectNum >= (unsigned)numsectors || (unsigned)SECT(spriteNum) >= (unsigned)numsectors
        || (sndist && PN(spriteNum) != MUSICANDSFX
            && (!sectorsareconnected(sectNum, SECT(spriteNum))
                || !cansee(cam.x, cam.y, cam.z - (24 << 8), sectNum, SX(spriteNum), SY(spriteNum), SZ(spriteNum) - (24 << 8), SECT(spriteNum))))))
        sndist += sndist>>5;
#else
    UNREFERENCED_PARAMETER(sectNum);
#endif

    if ((g_sounds[soundNum]->flags & (SF_GLOBAL|SF_DTAG)) == (SF_GLOBAL|SF_DTAG))
    {
#ifndef EDUKE32_STANDALONE
boost:
#endif
        int const sdist = g_sounds[soundNum]->distOffset > 0 ? g_sounds[soundNum]->distOffset : 6144;

        explosion = true;

        if (sndist > sdist)
            sndist = sdist;
    }

#ifndef EDUKE32_STANDALONE
    else if (!FURY)
    {
        switch (soundGetMapping(soundNum))
        {
            case PIPEBOMB_EXPLODE__:
            case LASERTRIP_EXPLODE__:
            case RPG_EXPLODE__:
                goto boost;
        }
    }
#endif

    if ((g_sounds[soundNum]->flags & (SF_GLOBAL|SF_DTAG)) == SF_GLOBAL || sndist < ((255-LOUDESTVOLUME) << 6))
        sndist = ((255-LOUDESTVOLUME) << 6);

    *distPtr = sndist;
    *angPtr  = sndang;

    return explosion;
}

void S_AllocVoiceInfo(sound_t *snd)
{
    if (snd->voices == &nullvoice)
    {
        snd->voices = (voiceinfo_t*) Xcalloc(MAXSOUNDINSTANCES, sizeof(voiceinfo_t));
        for (int i=0; i<MAXSOUNDINSTANCES; ++i)
            snd->voices[i] = nullvoice;
    }
}

int S_PlaySound3D(int num, int spriteNum, const vec3_t& pos)
{
    int const sndNum = VM_OnEventWithReturn(EVENT_SOUND, spriteNum, screenpeek, num);

    if ((sndNum == -1 && num != -1) || !ud.config.SoundToggle) // check that the user returned -1, but only if -1 wasn't playing already (in which case, warn)
        return -1;

    if (EDUKE32_PREDICT_FALSE(!S_SoundIsValid(sndNum)))
    {
        LOG_F(WARNING, "Invalid sound #%d", sndNum);
        return -1;
    }

    auto &snd = g_sounds[sndNum];
    auto const pPlayer = g_player[myconnectindex].ps;

    if (((snd->flags & SF_ADULT) && ud.lockout) || (unsigned)spriteNum >= MAXSPRITES || (pPlayer->gm & MODE_MENU) || !FX_VoiceAvailable(snd->priority)
        || (pPlayer->timebeforeexit > 0 && pPlayer->timebeforeexit <= GAMETICSPERSEC * 3))
        return -1;

    S_AllocVoiceInfo(snd);

    // Duke talk
    if (snd->flags & SF_TALK)
    {
        if ((g_netServer || ud.multimode > 1) && PN(spriteNum) == APLAYER && P_Get(spriteNum) != screenpeek) // other player sound
        {
            if ((ud.config.VoiceToggle & 4) != 4)
                return -1;
        }
        // don't play if any Duke talk sounds are already playing
        else if (g_dukeTalk || !(ud.config.VoiceToggle & 1))
            return -1;
    }
    else if ((snd->flags & (SF_DTAG|SF_GLOBAL)) == SF_DTAG)  // Duke-Tag sound
    {
        int const voice = S_PlaySound(sndNum);

        if (voice <= FX_Ok)
            return -1;

        int slot = 0;
        while (slot < MAXSOUNDINSTANCES && snd->voices[slot].handle != voice)
            slot++;

        if (EDUKE32_PREDICT_FALSE(slot >= MAXSOUNDINSTANCES))
        {
            LOG_F(WARNING, "S_PlaySound3D: slot >= MAXSOUNDINSTANCES!");
            return -1;
        }

        snd->voices[slot].owner = spriteNum;

        return voice;
    }

    int32_t    sndist, sndang;
    int const  explosionp = S_CalcDistAndAng(spriteNum, sndNum, CAMERA(sect), fix16_to_int(CAMERA(q16ang)), CAMERA(pos), pos, &sndist, &sndang);
    int        pitch      = S_GetPitch(sndNum);
    auto const pOther     = g_player[screenpeek].ps;

#ifdef SPLITSCREEN_MOD_HACKS
    if (g_fakeMultiMode==2)
    {
        // splitscreen HACK
        if (g_player[1].ps->i == spriteNum)
            pOther = g_player[1].ps;
}
#endif

    if (pOther->sound_pitch)
        pitch += pOther->sound_pitch;

    if (explosionp)
    {
        if (pOther->cursectnum > -1 && sector[pOther->cursectnum].lotag == ST_2_UNDERWATER)
            pitch -= 1024;
    }
    else
    {
#if 0
        if (sndist > 32767 && PN(spriteNum) != MUSICANDSFX && (snd->mode & (SF_LOOP|SF_MSFX)) == 0)
            return -1;
#endif

        if (pOther->cursectnum > -1 && sector[pOther->cursectnum].lotag == ST_2_UNDERWATER
            && (snd->flags & SF_TALK) == 0)
            pitch = -768;
    }

    if (snd->playing && PN(spriteNum) != MUSICANDSFX)
        S_StopEnvSound(sndNum, spriteNum);

#ifdef CACHING_DOESNT_SUCK
    if (++g_soundlocks[sndNum] < CACHE1D_LOCKED)
        g_soundlocks[sndNum] = CACHE1D_LOCKED;
#endif
retry:
    int const sndSlot = S_GetSlot(sndNum);

    if (sndSlot >= MAXSOUNDINSTANCES)
    {
        LOG_F(WARNING, "S_PlaySound3D: sndSlot >= MAXSOUNDINSTANCES!");
error:
#ifdef CACHING_DOESNT_SUCK
        g_soundlocks[sndNum]--;
#endif
        return -1;
    }

    int const repeatp = (snd->flags & SF_LOOP);

    if (repeatp && (snd->flags & SF_ONEINST_INTERNAL) && snd->playing)
        goto error; // OK, this one isn't actually an error

    if (bitmap_test(&snd->playing, sndSlot))
    {
        DLOG_F(WARNING, "S_PlaySound3D: slot %d for sound %d already filled", sndSlot, sndNum);
        goto retry;
    }

    bitmap_set(&snd->playing, sndSlot);

    S_FillVoiceInfo(&snd->voices[sndSlot], spriteNum, -1, sndist >> 6);

    if (snd->flags & SF_TALK)
        g_dukeTalk = true;

    int const voice = FX_Play3D(snd->ptr, snd->len, repeatp ? FX_LOOP : FX_ONESHOT, pitch, sndang >> 4, sndist >> 6,
                                                        snd->priority, snd->volume, (sndNum * MAXSOUNDINSTANCES) + sndSlot);

    if (voice <= FX_Ok)
    {
        LOG_F(ERROR, "Unable to play %s: unsupported format or file corrupt.", snd->filename);
        bitmap_clear(&snd->playing, sndSlot);
        goto error;
    }

    snd->voices[sndSlot].handle = voice;

    return voice;
}

int S_PlaySound(int num)
{
    int sndnum = VM_OnEventWithReturn(EVENT_SOUND, g_player[screenpeek].ps->i, screenpeek, num);

    if ((sndnum == -1 && num != -1) || !ud.config.SoundToggle) // check that the user returned -1, but only if -1 wasn't playing already (in which case, warn)
        return -1;

    if (EDUKE32_PREDICT_FALSE(!S_SoundIsValid(sndnum)))
    {
        LOG_F(WARNING, "Invalid sound #%d", sndnum);
        return -1;
    }

    num = sndnum;

    auto &snd = g_sounds[num];

    if ((!(ud.config.VoiceToggle & 1) && (snd->flags & SF_TALK)) || ((snd->flags & SF_ADULT) && ud.lockout) || !FX_VoiceAvailable(snd->priority))
        return -1;

    int const pitch = S_GetPitch(num);

#ifdef CACHING_DOESNT_SUCK
    if (++g_soundlocks[num] < CACHE1D_LOCKED)
        g_soundlocks[num] = CACHE1D_LOCKED;
#endif

    S_AllocVoiceInfo(snd);

    sndnum = S_GetSlot(num);

    if (sndnum >= MAXSOUNDINSTANCES)
    {
        LOG_F(WARNING, "S_PlaySound: sndnum >= MAXSOUNDINSTANCES!");
error:
#ifdef CACHING_DOESNT_SUCK
        g_soundlocks[num]--;
#endif
        return -1;
    }

    if (bitmap_test(&snd->playing, sndnum))
    {
        LOG_F(WARNING, "S_PlaySound: slot %d for sound %d already filled", sndnum, num);
        goto error;
    }

    bitmap_set(&snd->playing, sndnum);

    if (snd->flags & SF_TALK)
        g_dukeTalk = true;

    int const voice = (snd->flags & SF_LOOP) ? FX_Play(snd->ptr, snd->len, 0, -1, pitch, LOUDESTVOLUME, LOUDESTVOLUME,
                                                  LOUDESTVOLUME, snd->len, snd->volume, (num * MAXSOUNDINSTANCES) + sndnum)
                                        : FX_Play3D(snd->ptr, snd->len, FX_ONESHOT, pitch, 0, 255 - LOUDESTVOLUME, snd->priority, snd->volume,
                                                    (num * MAXSOUNDINSTANCES) + sndnum);

    if (voice <= FX_Ok)
    {
        LOG_F(ERROR, "Error attempting to play %s", snd->filename);
        bitmap_clear(&snd->playing, sndnum);
        goto error;
    }

    snd->voices[sndnum].handle = voice;

    return voice;
}

int A_PlaySound(int soundNum, int spriteNum)
{
    return (unsigned)spriteNum >= MAXSPRITES ? S_PlaySound(soundNum) :
        S_PlaySound3D(soundNum, spriteNum, sprite[spriteNum].xyz);
}

void S_StopEnvSound(int soundNum, int spriteNum)
{
    if (EDUKE32_PREDICT_FALSE(!S_SoundIsValid(soundNum)) || g_sounds[soundNum]->playing == 0)
        return;

    int j;

    do
    {
        for (j=0; j < MAXSOUNDINSTANCES; ++j)
        {
            if (bitmap_test(&g_sounds[soundNum]->playing, j) == 0)
                continue;

            auto &voice = g_sounds[soundNum]->voices[j];

            if ((spriteNum == -1 && voice.handle > FX_Ok) || (spriteNum != -1 && voice.owner == spriteNum))
            {
                Bassert(spriteNum == -1 || voice.handle > FX_Ok);
                if (EDUKE32_PREDICT_FALSE(spriteNum >= 0 && voice.handle <= FX_Ok))
                    LOG_F(ERROR, "Invalid voice for sound #%d index #%d! (%d)", soundNum, j, voice.handle);
                else if (FX_SoundValidAndActive(voice.handle))
                    FX_StopSound(voice.handle);
                else S_Cleanup();
                break;
            }
        }
    } while (j < MAXSOUNDINSTANCES);
}

// Do not remove this or make it inline.
void S_StopAllSounds(void)
{
    FX_StopAllSounds();
    S_Cleanup();

    for (int i = 0; i <= g_highestSoundIdx; ++i)
    {
        if (g_sounds[i] == &nullsound)
            continue;

        g_sounds[i]->playing = 0;

        if (g_sounds[i]->voices != &nullvoice)
            for (int j = 0; j < MAXSOUNDINSTANCES; ++j)
                g_sounds[i]->voices[j] = nullvoice;

#ifdef CACHING_DOESNT_SUCK
        g_sounds[i]->lock = CACHE1D_UNLOCKED;
#endif
    }

    g_dukeTalk = false;
}

void S_ChangeSoundPitch(int soundNum, int spriteNum, int pitchoffset)
{
    if (EDUKE32_PREDICT_FALSE(g_sounds[soundNum] == &nullsound) || g_sounds[soundNum]->playing == 0)
        return;

    for (int j=0; j < MAXSOUNDINSTANCES; ++j)
    {
        if (bitmap_test(&g_sounds[soundNum]->playing, j) == 0)
            continue;

        auto &voice = g_sounds[soundNum]->voices[j];

        if ((spriteNum == -1 && voice.handle > FX_Ok) || (spriteNum != -1 && voice.owner == spriteNum))
        {
            Bassert(spriteNum == -1 || voice.handle > FX_Ok);
            if (EDUKE32_PREDICT_FALSE(spriteNum >= 0 && voice.handle <= FX_Ok))
                LOG_F(ERROR, "Invalid voice for sound #%d index #%d! (%d)", soundNum, j, voice.handle);
            else
                FX_SetPitch(voice.handle, pitchoffset);
        }
    }
}

void S_Update(void)
{
    if ((g_player[myconnectindex].ps->gm & (MODE_GAME|MODE_DEMO)) == 0)
        return;

    g_numEnvSoundsPlaying = 0;

    const vec3_t *c;
    int32_t ca,cs;

    if (ud.camerasprite == -1)
    {
        if (ud.overhead_on != 2)
        {
            c = &CAMERA(pos);
            cs = CAMERA(sect);
            ca = fix16_to_int(CAMERA(q16ang));
        }
        else
        {
            auto pPlayer = g_player[screenpeek].ps;
            c = &pPlayer->pos;
            cs = pPlayer->cursectnum;
            ca = fix16_to_int(pPlayer->q16ang);
        }
    }
    else
    {
        c = &sprite[ud.camerasprite].xyz;
        cs = sprite[ud.camerasprite].sectnum;
        ca = sprite[ud.camerasprite].ang;
    }

    int sndnum = 0;

    S_Cleanup();

    do
    {
        if (g_sounds[sndnum] == &nullsound || g_sounds[sndnum]->playing == 0)
            continue;

        int spriteNum, sndist, sndang;

        for (int j = 0; j < MAXSOUNDINSTANCES; ++j)
        {
            auto &voice = g_sounds[sndnum]->voices[j];

            if (bitmap_test(&g_sounds[sndnum]->playing, j) == 0)
                continue;

            // this is incremented here as a way to track how long the sound has been playing for ownerless sounds
            if ((unsigned)(spriteNum = voice.owner) >= MAXSPRITES)
            {
                if (voice.dist < UINT16_MAX)
                    voice.dist++;
                continue;
            }

            // AMBIENT_SOUND
            if (S_IsAmbientSFX(spriteNum))
                g_numEnvSoundsPlaying++;

            S_CalcDistAndAng(spriteNum, sndnum, cs, ca, *c, sprite[spriteNum].xyz, &sndist, &sndang);
            voice.dist = sndist >> 6;
            FX_Pan3D(voice.handle, sndang >> 4, sndist >> 6);
        }
    } while (++sndnum <= g_highestSoundIdx);
}

// when playing back a new sound needs an existing sound to be stopped first
// S_Callback() can be called from either the audio thread when a sound ends, or the main thread
// this is essentially a multiple producer single consumer queue
void S_Callback(intptr_t num)
{
    if ((int32_t)num == MUSIC_ID)
        return;

    if ((unsigned)num < (MAXSOUNDS * MAXSOUNDINSTANCES))
    {
        int const voiceindex = num & (MAXSOUNDINSTANCES-1);
        int anum = (num - voiceindex) / MAXSOUNDINSTANCES;

        if (!bitmap_test(&g_sounds[anum]->playing, voiceindex))
        {
            LOG_F(WARNING, "Got callback for sound #%d index #%d that wasn't playing!", anum, voiceindex);
            return;
        }
    }

#ifndef NDEBUG
    int cnt = freeSlotPendingCnt.fetch_add(1, std::memory_order_relaxed);
    Bassert(cnt < MAXVOICES);
#endif

    freeSlotQueue[(freeSlotWriteIndex.fetch_add(1, std::memory_order_relaxed) + 1) & (MAXVOICES-1)] = (uint32_t)num;
    freeSlotReadIndex.fetch_add(1, std::memory_order_release);
}

void S_ClearSoundLocks(void)
{
#ifdef CACHING_DOESNT_SUCK
    int32_t i;
    int32_t const msp = g_highestSoundIdx;

    for (native_t i = 0; i < 11; ++i)
        if (rts_lumplockbyte[i] >= CACHE1D_LOCKED)
            rts_lumplockbyte[i] = CACHE1D_UNLOCKED;

    int32_t const msp = g_highestSoundIdx;

    for (native_t i = 0; i <= msp; ++i)
        if (g_soundlocks[i] >= CACHE1D_LOCKED)
            g_soundlocks[i] = CACHE1D_UNLOCKED;
#endif
}

int A_CheckSoundPlaying(int spriteNum, int soundNum)
{
    if (EDUKE32_PREDICT_FALSE((unsigned)soundNum > (unsigned)g_highestSoundIdx)) return 0;

    auto &snd = g_sounds[soundNum];

    if (snd->playing && spriteNum >= 0)
    {
        for (int j = 0; j < MAXSOUNDINSTANCES; ++j)
            if (bitmap_test(&snd->playing, j) && snd->voices[j].owner == spriteNum)
                return 1;
    }

    return (spriteNum == -1) ? (snd->playing != 0) : 0;
}

// Check if actor <i> is playing any sound.
int A_CheckAnySoundPlaying(int spriteNum)
{
    int const msp = g_highestSoundIdx;

    for (int i = 0; i <= msp; ++i)
    {
        auto &snd = g_sounds[i];

        for (int j = 0; j < MAXSOUNDINSTANCES; ++j)
            if (bitmap_test(&snd->playing, j) && snd->voices[j].owner == spriteNum)
                return 1;
    }

    return 0;
}

int S_CheckSoundPlaying(int soundNum)
{
    if (EDUKE32_PREDICT_FALSE((unsigned)soundNum > (unsigned)g_highestSoundIdx)) return false;
    return (g_sounds[soundNum]->playing != 0);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

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

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

#ifndef sounds_public_h_
#define sounds_public_h_

#include "build.h"
#include "compat.h"
#include "sounds_common.h"
#include "dnames.h"

#ifdef __cplusplus
extern "C" {
#endif

#define MAXSOUNDS           16384
#define MAXSOUNDINSTANCES   8
#define LOUDESTVOLUME       111
#define MUSIC_ID            -65536
#define MAXVOICES           128

typedef struct
{
    int16_t  owner;
    int16_t  handle;
    uint16_t dist;
} voiceinfo_t;

typedef struct
{
    voiceinfo_t *voices;

    char *  ptr;
    char *  filename;
    int32_t len;
    fix16_t volume;
    int16_t minpitch, maxpitch, distOffset;
    uint8_t  playing;
    uint8_t flags;
    char    priority;
    char    lock;
} sound_t;

EDUKE32_STATIC_ASSERT(MAXSOUNDINSTANCES <= sizeof(((sound_t *)0)->playing) << 3);

extern voiceinfo_t nullvoice;
extern sound_t nullsound;
extern sound_t **g_sounds;
extern int32_t g_numEnvSoundsPlaying, g_highestSoundIdx;

extern int32_t MusicIsWaveform, MusicVoice;

static FORCE_INLINE bool S_SoundIsValid(int soundNum)
{
    return (unsigned)soundNum <= (unsigned)g_highestSoundIdx && g_sounds[soundNum] && g_sounds[soundNum] != &nullsound && g_sounds[soundNum]->ptr;
}

static FORCE_INLINE bool S_IsAmbientSFX(int const spriteNum) { return (sprite[spriteNum].picnum == MUSICANDSFX && sprite[spriteNum].lotag < 999); }

int A_CheckSoundPlaying(int spriteNum,int soundNum);
int A_PlaySound(int soundNum, int spriteNum);
void S_Callback(intptr_t num);
int A_CheckAnySoundPlaying(int spriteNum);
int S_CheckSoundPlaying(int soundNum);
void S_Cleanup(void);
void S_ClearSoundLocks(void);
int32_t S_LoadSound(uint32_t num);
void cacheAllSounds(void);
int32_t S_DefineSound(int sndidx, const char* name, int minpitch, int maxpitch, int priority, int type, int distance, float volume);
int32_t S_DefineMusic(const char* ID, const char* name);
void S_MenuSound(void);
void S_MusicShutdown(void);
void S_MusicStartup(void);
void S_MusicVolume(int32_t volume);
void S_RestartMusic(void);
void S_PauseMusic(bool paused);
void S_PauseSounds(bool paused);
bool S_TryPlayLevelMusic(unsigned int m);
void S_PlayLevelMusicOrNothing(unsigned int);
int S_TryPlaySpecialMusic(unsigned int);
void S_PlaySpecialMusicOrNothing(unsigned int);
void S_ContinueLevelMusic(void);
int S_PlaySound(int num);
int S_PlaySound3D(int num, int spriteNum, const vec3_t &pos);
void S_SoundShutdown(void);
void S_AllocIndexes(int sndidx);
void S_SoundStartup(void);
void S_StopEnvSound(int sndNum,int sprNum);
void S_StopAllSounds(void);
void S_StopMusic(void);
void S_Update(void);
void S_ChangeSoundPitch(int soundNum, int spriteNum, int pitchoffset);
int32_t S_GetMusicPosition(void);
void S_SetMusicPosition(int32_t position);

#ifdef __cplusplus
}
#endif

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2013 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef EDUKE32_SOUNDS_COMMON_H
#define EDUKE32_SOUNDS_COMMON_H

// Sound flags
enum {
    SF_LOOP = 1,
    SF_MSFX = 2,
    SF_TALK = 4,
    SF_ADULT = 8,
    SF_GLOBAL = 16,
    SF_ONEINST_INTERNAL = 32,

    SF_DTAG = 128,
};

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

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

// Stripped sounds.c for use in Mapster32, breaks all ties to game & music

#include "compat.h"
#include "baselayer.h"

#include "fx_man.h"
#include "osd.h"

#include "cache1d.h"
#include "build.h"  // vec3_t
#include "editor.h"

#include "macros.h"
#include "common.h"

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

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

#include "vfs.h"

#define LOUDESTVOLUME 150
#define MUSICANDSFX 5

static char SM32_havesound = 0;

static char SoundToggle = 1;
int32_t NumVoices = 32;
int32_t MixRate = 44100;

int32_t g_numEnvSoundsPlaying;

void S_Callback(intptr_t);

int16_t g_definedsndnum[MAXSOUNDS];
int16_t g_sndnum[MAXSOUNDS];
int32_t g_numsounds = 0;

/*
===================
=
= SoundStartup
=
===================
*/

int32_t S_SoundStartup(void)
{
    int32_t status;
    void *initdata = 0;

    // TODO: read config
    int32_t FXVolume=220, /*NumVoices=32,*/ NumChannels=2;

#ifdef _WIN32
    initdata = (void *) win_gethwnd(); // used for DirectSound
#endif

    initprintf("Initializing sound... ");

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

    FX_SetVolume(FXVolume);
#ifdef ASS_REVERSESTEREO
    FX_SetReverseStereo(0);
#endif
    FX_SetCallBack(S_Callback);

    SM32_havesound = 1;

    return 0;
}

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

void S_SoundShutdown(void)
{
    if (!SM32_havesound)
        return;

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

int32_t S_LoadSound(uint32_t num)
{
    if (!SM32_havesound) return 0;
    if (num >= MAXSOUNDS || SoundToggle == 0) return 0;

    if (g_sounds[num].filename == NULL)
    {
        OSD_Printf(OSD_ERROR "Sound (#%d) not defined!\n",num);
        return 0;
    }

    buildvfs_kfd fp = S_OpenAudio(g_sounds[num].filename, 0, 0);
    if (fp == buildvfs_kfd_invalid)
    {
        OSD_Printf(OSDTEXT_RED "Sound %s(#%d) not found!\n",g_sounds[num].filename,num);
        return 0;
    }

    int32_t l = kfilelength(fp);
    g_sounds[num].soundsiz = l;

    g_sounds[num].lock = CACHE1D_LOCKED;

    g_cache.allocateBlock((intptr_t *)&g_sounds[num].ptr,l,(char *)&g_sounds[num].lock);
    kread(fp, g_sounds[num].ptr , l);
    kclose(fp);
    return 1;
}

int32_t S_DefineSound(int sndidx, const char * filename, const char * definedname, int minpitch, int maxpitch, int priority, int type, int distance, float volume)
{
    if ((unsigned)sndidx >= MAXSOUNDS)
        return -1;

    auto & snd = g_sounds[sndidx];
    int duplicate = 0;

    if (snd.filename)
    {
        duplicate = 1;
        Xfree(snd.filename);
    }
    snd.filename = Xstrdup(filename);
    // Hopefully noone does memcpy(..., g_sounds[].filename, BMAX_PATH)

    if (snd.definedname)
    {
        duplicate = 1;
        Xfree(snd.definedname);
    }
    if (duplicate)
        initprintf("warning: duplicate sound #%d, overwriting\n", sndidx);

    if (!duplicate)
    {
        if ((unsigned)g_numsounds >= MAXSOUNDS)
            return -2;
        g_sndnum[g_numsounds] = g_definedsndnum[g_numsounds] = sndidx;
        g_numsounds++;
    }

    snd.definedname = Xstrdup(definedname);  // we want to keep it for display purposes
    snd.ps = clamp(minpitch, INT16_MIN, INT16_MAX);
    snd.pe = clamp(maxpitch, INT16_MIN, INT16_MAX);
    snd.pr = priority & 0xFF;
    snd.m = type & ~SF_ONEINST_INTERNAL;
    snd.vo = clamp(distance, INT16_MIN, INT16_MAX);
    snd.volume = volume * fix16_one;

    if (snd.m & SF_LOOP)
        snd.m |= SF_ONEINST_INTERNAL;

    return 0;
}

int32_t S_PlaySound3D(int32_t const num, int32_t i, const vec3_t *pos)
{
    int32_t sndist, cx, cy, cz, j/*,k*/;
    int32_t pitche,pitchs,cs;
    int32_t voice, sndang, ca, pitch;

    //    if(num != 358) return 0;

    if (num >= MAXSOUNDS ||
            !SM32_havesound ||
            SoundToggle == 0)
        return -1;

    auto & snd = g_sounds[num];

    if (snd.num > 3 ||
//        ((snd.m & SF_ADULT) && ud.lockout) ||
            FX_VoiceAvailable(snd.pr) == 0)
        return -1;

    if (snd.m & SF_DTAG)
    {
        S_PlaySound(num);
        return 0;
    }

    if (snd.m & SF_TALK)
    {
        for (j=0; j<MAXSOUNDS; j++)
        {
            auto const & jsnd = g_sounds[j];
//            for (k=0; k<jsnd.num; k++)
            if ((jsnd.num > 0) && (jsnd.m & SF_TALK))
                return -1;
        }
    }

    cx = pos->x;
    cy = pos->y;
    cz = pos->z;
    cs = cursectnum;
    ca = ang;

    sndist = FindDistance3D((cx-pos->x),(cy-pos->y),(cz-pos->z));

    if (i >= 0 && (snd.m & SF_GLOBAL) == 0 && PN(i) == MUSICANDSFX && SLT(i) < 999 && (sector[SECT(i)].lotag&0xff) < 9)
        sndist = divscale14(sndist,(SHT(i)+1));

    pitchs = snd.ps;
    pitche = snd.pe;
    cx = klabs(pitche-pitchs);

    if (cx)
    {
        if (pitchs < pitche)
            pitch = pitchs + (wrand()%cx);
        else
            pitch = pitche + (wrand()%cx);
    }
    else
        pitch = pitchs;

    sndist += snd.vo;
    if (sndist < 0) sndist = 0;
    if (cs > -1 && sndist && PN(i) != MUSICANDSFX && !cansee(cx,cy,cz-(24<<8),cs,SX(i),SY(i),SZ(i)-(24<<8),SECT(i)))
        sndist += sndist>>5;
#if 0
        switch (num)
        {
        case PIPEBOMB_EXPLODE:
        case LASERTRIP_EXPLODE:
        case RPG_EXPLODE:
            if (sndist > (6144))
                sndist = 6144;
            if (g_player[screenpeek].ps->cursectnum > -1 && sector[g_player[screenpeek].ps->cursectnum].lotag == 2)
                pitch -= 1024;
            break;
        default:
#endif
    if (cursectnum > -1 && sector[cursectnum].lotag == 2 && (snd.m & SF_TALK) == 0)
        pitch = -768;
    if (sndist > 31444 && PN(i) != MUSICANDSFX)
        return -1;
//        break;
//    }

    if (snd.num > 0 && PN(i) != MUSICANDSFX)
    {
        if (snd.SoundOwner[0].ow == i) S_StopSound(num);
        else if (snd.num > 1) S_StopSound(num);
//        else if (A_CheckEnemySprite(&sprite[i]) && sprite[i].extra <= 0) S_StopSound(num);
    }

    sndang = 2048 + ca - getangle(cx-pos->x,cy-pos->y);
    sndang &= 2047;

    if (snd.ptr == 0)
    {
        if (S_LoadSound(num) == 0) return 0;
    }
    else
    {
        if (snd.lock < CACHE1D_LOCKED)
            snd.lock = CACHE1D_LOCKED;
        else
            snd.lock++;
    }

    if (snd.m & SF_GLOBAL) sndist = 0;

    if (sndist < ((255-LOUDESTVOLUME)<<6))
        sndist = ((255-LOUDESTVOLUME)<<6);

    if (snd.m & SF_LOOP)
    {
        if (snd.num > 0)
            return -1;

        voice = FX_Play(snd.ptr, snd.soundsiz, 0, -1,
                        pitch, sndist>>6, sndist>>6, 0, snd.pr, snd.volume, num);
    }
    else
    {
        voice = FX_Play3D(snd.ptr, snd.soundsiz, FX_ONESHOT,
                          pitch, sndang>>4, sndist>>6, snd.pr, snd.volume, num);
    }

    if (voice >= FX_Ok)
    {
        snd.SoundOwner[snd.num].ow = i;
        snd.SoundOwner[snd.num].voice = voice;
        snd.num++;
    }
    else
        snd.lock--;

    return voice;
}

void S_PlaySound(int32_t const num)
{
    int32_t pitch,pitche,pitchs,cx;
    int32_t voice;

    if (!SM32_havesound) return;
    if (SoundToggle==0) return;
    if ((unsigned)num >= MAXSOUNDS || !g_sounds[num].filename)
    {
        OSD_Printf("WARNING: invalid sound #%d\n",num);
        return;
    }

    auto & snd = g_sounds[num];

//    if ((snd.m & SF_ADULT) && ud.lockout) return;
    if (FX_VoiceAvailable(snd.pr) == 0) return;

    pitchs = snd.ps;
    pitche = snd.pe;
    cx = klabs(pitche-pitchs);

    if (cx)
    {
        if (pitchs < pitche)
            pitch = pitchs + (wrand()%cx);
        else
            pitch = pitche + (wrand()%cx);
    }
    else
        pitch = pitchs;

    if (snd.ptr == 0)
    {
        if (S_LoadSound(num) == 0) return;
    }
    else
    {
        if (snd.lock < CACHE1D_LOCKED)
            snd.lock = CACHE1D_LOCKED;
        else
            snd.lock++;
    }

    if (snd.m & SF_LOOP)
    {
        voice = FX_Play(snd.ptr, snd.soundsiz, 0, -1,
                        pitch,LOUDESTVOLUME,LOUDESTVOLUME,LOUDESTVOLUME,
                        snd.soundsiz, snd.volume, num);
    }
    else
    {
        voice = FX_Play3D(snd.ptr, snd.soundsiz, FX_ONESHOT,
                          pitch,0,255-LOUDESTVOLUME,snd.pr, snd.volume, num);
    }

    if (voice >= FX_Ok)
    {
        snd.SoundOwner[snd.num].ow = -1;
        snd.SoundOwner[snd.num].voice = voice;
        snd.num++;
        return;
    }
    snd.lock--;
}

int32_t A_PlaySound(uint32_t num, int32_t i)
{
    if (num >= MAXSOUNDS) return -1;
    if (i < 0)
    {
        S_PlaySound(num);
        return 0;
    }

    return S_PlaySound3D(num,i, &sprite[i].xyz);
}

void S_StopSound(int32_t num)
{
    if (num >= 0 && num < MAXSOUNDS)
        if (g_sounds[num].num > 0)
        {
            FX_StopSound(g_sounds[num].SoundOwner[g_sounds[num].num-1].voice);
            S_Callback(num);
        }
}

void S_StopEnvSound(int32_t num,int32_t i)
{
    int32_t j, k;

    if (num >= 0 && num < MAXSOUNDS)
        if (g_sounds[num].num > 0)
        {
            k = g_sounds[num].num;
            for (j=0; j<k; j++)
                if (g_sounds[num].SoundOwner[j].ow == i)
                {
                    FX_StopSound(g_sounds[num].SoundOwner[j].voice);
                    break;
                }
        }
}

// Do not remove this or make it inline.
void S_StopAllSounds(void)
{
    FX_StopAllSounds();
}

void S_Update(void)
{
    int32_t sndist, sx, sy, sz, cx, cy, cz;
    int32_t sndang,ca,j,k,i,cs;

    g_numEnvSoundsPlaying = 0;

    cx = pos.x;
    cy = pos.y;
    cz = pos.z;
    cs = cursectnum;
    ca = ang;

    for (j=0; j<MAXSOUNDS; j++)
        for (k=0; k<g_sounds[j].num; k++)
        {
            i = g_sounds[j].SoundOwner[k].ow;

            sx = sprite[i].x;
            sy = sprite[i].y;
            sz = sprite[i].z;

            sndang = 2048 + ca - getangle(cx-sx,cy-sy);
            sndang &= 2047;
            sndist = FindDistance3D((cx-sx),(cy-sy),(cz-sz));
            if (i >= 0 && (g_sounds[j].m & SF_GLOBAL) == 0 && PN(i) == MUSICANDSFX && SLT(i) < 999 && (sector[SECT(i)].lotag&0xff) < 9)
                sndist = divscale14(sndist,(SHT(i)+1));

            sndist += g_sounds[j].vo;
            if (sndist < 0) sndist = 0;

            if (cs > -1 && sndist && PN(i) != MUSICANDSFX && !cansee(cx,cy,cz-(24<<8),cs,sx,sy,sz-(24<<8),SECT(i)))
                sndist += sndist>>5;

            if (PN(i) == MUSICANDSFX && SLT(i) < 999)
                g_numEnvSoundsPlaying++;
            /*
                        switch (j)
                        {
                        case PIPEBOMB_EXPLODE:
                        case LASERTRIP_EXPLODE:
                        case RPG_EXPLODE:
                            if (sndist > (6144)) sndist = (6144);
                            break;
                        default:
            */
            if (sndist > 31444 && PN(i) != MUSICANDSFX)
            {
                S_StopSound(j);
                continue;
            }
//            }

            if (g_sounds[j].ptr == 0 && S_LoadSound(j) == 0) continue;
            if (g_sounds[j].m & SF_GLOBAL) sndist = 0;

            if (sndist < ((255-LOUDESTVOLUME)<<6))
                sndist = ((255-LOUDESTVOLUME)<<6);

            FX_Pan3D(g_sounds[j].SoundOwner[k].voice,sndang>>4,sndist>>6);
        }
}

void S_Callback(intptr_t num)
{
    int32_t i,j,k;

    k = g_sounds[num].num;

    if (k > 0)
    {
        if ((g_sounds[num].m & SF_GLOBAL) == 0)
            for (j=0; j<k; j++)
            {
                i = g_sounds[num].SoundOwner[j].ow;
                if (i < 0)
                    continue;

                if (sprite[i].picnum == MUSICANDSFX && sector[sprite[i].sectnum].lotag < 3 && sprite[i].lotag < 999)
                {
                    extern uint8_t g_ambiencePlaying[(MAXSPRITES+7)>>3];

                    g_ambiencePlaying[i>>3] &= ~pow2char[i&7];

                    if (j < k-1)
                    {
                        g_sounds[num].SoundOwner[j].voice = g_sounds[num].SoundOwner[k-1].voice;
                        g_sounds[num].SoundOwner[j].ow     = g_sounds[num].SoundOwner[k-1].ow;
                    }
                    break;
                }
            }

        g_sounds[num].num--;
        g_sounds[num].SoundOwner[k-1].ow = -1;
    }

    g_sounds[num].lock--;
}

void S_ClearSoundLocks(void)
{
    int32_t i;

    for (i=0; i<MAXSOUNDS; i++)
        if (g_sounds[i].lock >= CACHE1D_LOCKED)
            g_sounds[i].lock = CACHE1D_UNLOCKED;
}

int32_t A_CheckSoundPlaying(int32_t i, int32_t num)
{
    UNREFERENCED_PARAMETER(i);
    if (num < 0) num=0;	// FIXME
    return (g_sounds[num].num > 0);
}

int32_t S_CheckSoundPlaying(int32_t i, int32_t num)
{
    if (i == -1)
    {
        if (g_sounds[num].lock >= CACHE1D_LOCKED)
            return 1;
        return 0;
    }
    return g_sounds[num].num;
}

int32_t S_SoundsPlaying(int32_t i)
{
    int32_t j = MAXSOUNDS-1;
    for (; j>=0; j--)
        if (g_sounds[j].SoundOwner[0].ow == i)
            break;

    return j;
}

int32_t S_InvalidSound(int32_t num)
{
    return (unsigned) num >= MAXSOUNDS;
}

int32_t S_SoundFlags(int32_t num)
{
    return g_sounds[num].m;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef sounds_mapster32_h_
#define sounds_mapster32_h_

#include "build.h"
#include "sounds_common.h"

#pragma pack(push,1)

/// vvv sound structs from duke3d.h
typedef struct {
    int32_t voice;
    int32_t ow;
} SOUNDOWNER;

typedef struct {
    char *filename, *ptr;
    int32_t  length, num, soundsiz;
    fix16_t volume;
    SOUNDOWNER SoundOwner[4];
    int16_t ps,pe,vo;
    char pr,m;
    char lock;
    char *definedname;  // new
} sound_t;

#define MAXSOUNDS 16384
extern sound_t g_sounds[MAXSOUNDS];

extern int32_t g_numEnvSoundsPlaying;
extern int32_t NumVoices;

extern int16_t g_definedsndnum[MAXSOUNDS];  // maps parse order index to g_sounds index
extern int16_t g_sndnum[MAXSOUNDS];  // maps current order index to g_sounds index
extern int32_t g_numsounds;

int32_t S_SoundStartup(void);
void S_SoundShutdown(void);
int32_t S_DefineSound(int sndidx, const char * filename, const char * definedname, int minpitch, int maxpitch, int priority, int type, int distance, float volume);
int32_t S_PlaySound3D(int32_t, int32_t, const vec3_t*);
void S_PlaySound(int32_t);
int32_t A_PlaySound(uint32_t num, int32_t i);
void S_StopSound(int32_t num);
void S_StopEnvSound(int32_t num,int32_t i);
void S_StopAllSounds(void);
void S_Update(void);
int32_t A_CheckSoundPlaying(int32_t i, int32_t num);
int32_t S_CheckSoundPlaying(int32_t i, int32_t num);
void S_ClearSoundLocks(void);
int32_t S_SoundsPlaying(int32_t i);
int32_t S_InvalidSound(int32_t num);
int32_t S_SoundFlags(int32_t num);

#pragma pack(pop)

#endif