code:
#ifndef __whani_h__
#define __whani_h__

void animateobjs(Player *plr);

#endif

repo_name: orochi/nblood

license: mit


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

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

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

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

struct grpfile_t const* g_selectedGrp;

int32_t g_gameType = GAMEFLAG_WH1;
int     g_addonNum = 0;

int r_showfps;

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

// grp/con handling

static const char* defaultgamegrp = "witchaven.pk3";
static const char* defaultdeffilename = "witchaven.def";

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

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

void clearScriptNamePtr(void)
{
    Xfree(g_scriptNamePtr);
    // g_scriptNamePtr assumed to be assigned to right after
}

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

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

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

//////////

// Set up new-style multi-psky handling.
void G_InitMultiPsky(int CLOUDYOCEAN__DYN, int MOONSKY1__DYN, int BIGORBIT1__DYN, int LA__DYN)
{
    // When adding other multi-skies, take care that the tileofs[] values are
    // <= PSKYOFF_MAX. (It can be increased up to MAXPSKYTILES, but should be
    // set as tight as possible.)

    // The default sky properties (all others are implicitly zero):
    psky_t* sky = tileSetupSky(DEFAULTPSKY);
    sky->lognumtiles = 3;
    sky->horizfrac = 32768;

    // CLOUDYOCEAN
    // Aligns with the drawn scene horizon because it has one itself.
    sky = tileSetupSky(CLOUDYOCEAN__DYN);
    sky->lognumtiles = 3;
    sky->horizfrac = 65536;

    // MOONSKY1
    //        earth          mountain   mountain         sun
    sky = tileSetupSky(MOONSKY1__DYN);
    sky->lognumtiles = 3;
    sky->horizfrac = 32768;
    sky->tileofs[6] = 1;
    sky->tileofs[1] = 2;
    sky->tileofs[4] = 2;
    sky->tileofs[2] = 3;

    // BIGORBIT1   // orbit
    //       earth1         2           3           moon/sun
    sky = tileSetupSky(BIGORBIT1__DYN);
    sky->lognumtiles = 3;
    sky->horizfrac = 32768;
    sky->tileofs[5] = 1;
    sky->tileofs[6] = 2;
    sky->tileofs[7] = 3;
    sky->tileofs[2] = 4;

    // LA // la city
    //       earth1         2           3           moon/sun
    sky = tileSetupSky(LA__DYN);
    sky->lognumtiles = 3;
    sky->horizfrac = 16384 + 1024;
    sky->tileofs[0] = 1;
    sky->tileofs[1] = 2;
    sky->tileofs[2] = 1;
    sky->tileofs[3] = 3;
    sky->tileofs[4] = 4;
    sky->tileofs[5] = 0;
    sky->tileofs[6] = 2;
    sky->tileofs[7] = 3;

#if 0
    // This assertion should hold. See note above.
    for (bssize_t i = 0; i < pskynummultis; ++i)
        for (bssize_t j = 0; j < (1 << multipsky[i].lognumtiles); ++j)
            Bassert(multipsky[i].tileofs[j] <= PSKYOFF_MAX);
#endif
}

void G_SetupGlobalPsky(void)
{
    int skyIdx = 0;

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

    g_pskyidx = skyIdx;
}

//////////

static char g_rootDir[BMAX_PATH];

int g_useCwd;
int32_t g_groupFileHandle;

static struct strllist* CommandPaths, * CommandGrps;

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

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

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

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

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

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

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

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

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

void G_ScanGroups(void)
{
    ScanGroups();

    g_selectedGrp = NULL;

    char const* const currentGrp = G_GrpFile();

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

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

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

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

    return i;
}

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

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

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

    return i;
}

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

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

        char path[BMAX_PATH];

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

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

    const char* grpfile;
    int32_t i;

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

        clearGrpNamePtr();
        g_grpNamePtr = dup_filename(grpfile);

        grpinfo_t const* const type = g_selectedGrp->type;

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

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

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

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

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

    loaddefinitions_game(G_DefFile(), TRUE);

    struct strllist* s;

    int const bakpathsearchmode = pathsearchmode;
    pathsearchmode = 1;

    while (CommandGrps)
    {
        int32_t j;

        s = CommandGrps->next;

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

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

void G_AddSearchPaths(void)
{
#ifndef EDUKE32_TOUCH_DEVICES
#if defined __linux__ || defined EDUKE32_BSD

#elif defined EDUKE32_OSX

#elif defined (_WIN32)
    char buf[BMAX_PATH] = { 0 };
    DWORD bufsize;
    bool found = false;

    // Witchaven - GOG.com
    bufsize = sizeof(buf);
    if (!found && Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\1315510047)", "path", buf, &bufsize))
    {
        Bsnprintf(buf, sizeof(buf), "%s/Enhanced/GAME/WHAVEN", buf);
        addsearchpath(buf);
        found = true;
    }

    // Witchaven 2 - GOG.com
    bufsize = sizeof(buf);
    if (!found && Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\1073977251)", "path", buf, &bufsize))
    {
        Bsnprintf(buf, sizeof(buf), "%s/Enhanced/GAME/WHAVEN2", buf);
        addsearchpath(buf);
        found = true;
    }

#endif
#endif
}

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

//////////

GrowArray<char*> g_scriptModules;

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

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

    Bstrcpy(buf, buffer);

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

    s->str = Xstrdup(buf);

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

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

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

void G_AddCon(const char* buffer)
{
    clearScriptNamePtr();
    g_scriptNamePtr = dup_filename(buffer);
    initprintf("Using CON file \"%s\".\n", g_scriptNamePtr);
}

void G_AddConModule(const char* buffer)
{
    g_scriptModules.append(Xstrdup(buffer));
}

//////////

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

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

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

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

        fnlist_clearnames(&fnlist);
    }
}

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

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

//////////

void G_LoadLookups(void)
{
    int32_t j;
    buildvfs_kfd fp;

    if ((fp = kopen4loadfrommod("lookup.dat", 0)) == buildvfs_kfd_invalid)
        if ((fp = kopen4loadfrommod("lookup.dat", 1)) == buildvfs_kfd_invalid)
            return;

    j = paletteLoadLookupTable(fp);

    if (j < 0)
    {
        if (j == -1)
            initprintf("ERROR loading \"lookup.dat\": failed reading enough data.\n");

        return kclose(fp);
    }

    uint8_t paldata[768];

    for (j = 1; j <= 5; j++)
    {
        // Account for TITLE and REALMS swap between basepal number and on-disk order.
        int32_t basepalnum = (j == 3 || j == 4) ? 4 + 3 - j : j;

        if (kread_and_test(fp, paldata, 768))
            return kclose(fp);

        for (unsigned char& k : paldata)
            k <<= 2;

        paletteSetColorTable(basepalnum, paldata);
    }

    kclose(fp);
}

//////////

#ifdef FORMAT_UPGRADE_ELIGIBLE
int g_maybeUpgradeSoundFormats = 1;

static buildvfs_kfd S_TryFormats(char* const testfn, char* const fn_suffix, char const searchfirst)
{
    if (!g_maybeUpgradeSoundFormats)
        return buildvfs_kfd_invalid;

    static char const* extensions[] =
    {
#ifdef HAVE_FLAC
        ".flac",
#endif
#ifdef HAVE_VORBIS
        ".ogg",
#endif
    };

    for (char const* ext : extensions)
    {
        Bstrcpy(fn_suffix, ext);
        buildvfs_kfd const fp = kopen4loadfrommod(testfn, searchfirst);
        if (fp != buildvfs_kfd_invalid)
            return fp;
    }

    return buildvfs_kfd_invalid;
}

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

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

        buildvfs_kfd const fp = S_TryFormats(testfn, fn_end, searchfirst);
        if (fp != buildvfs_kfd_invalid)
            return fp;
    }
    else
    {
        extension = fn_end;
    }

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

    return buildvfs_kfd_invalid;
}

buildvfs_kfd S_OpenAudio(const char* fn, char searchfirst, uint8_t const ismusic)
{
    buildvfs_kfd const origfp = kopen4loadfrommod(fn, searchfirst);
#ifndef USE_PHYSFS
    char const* const origparent = origfp != buildvfs_kfd_invalid ? kfileparent(origfp) : NULL;
    uint32_t const    parentlength = origparent != NULL ? Bstrlen(origparent) : 0;

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

    // look in ./
    // ex: ./grabbag.mid
    Bstrcpy(testfn, fn);
    buildvfs_kfd fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
    if (fp != buildvfs_kfd_invalid)
        goto success;

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

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

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

    Xfree(testfn);
    return origfp;

success:
    Xfree(testfn);
    kclose(origfp);
    return fp;
}

void G_Polymer_UnInit(void) { }

static inline int32_t calc_smoothratio(ClockTicks totalclk, ClockTicks ototalclk)
{
    // if (!((ud.show_help == 0 && (!g_netServer && ud.multimode < 2) && ((g_player[myconnectindex].ps->gm & MODE_MENU) == 0)) ||
    //       (g_netServer || ud.multimode > 1) ||
    //       ud.recstat == 2) ||
    //     ud.pause_on)
    // {
    //     return 65536;
    // }

/* TODO
    if (bRecord || bPlayback || nFreeze != 0 || bCamera || bPause)
        return 65536;
*/

    int32_t rfreq = (refreshfreq != -1 ? refreshfreq : 60);
    uint64_t elapsedFrames = tabledivide64(((uint64_t)(totalclk - ototalclk).toScale16()) * rfreq, 65536 * 120);
#if 0
    //POGO: additional debug info for testing purposes
    OSD_Printf("Elapsed frames: %" PRIu64 ", smoothratio: %" PRIu64 "\n", elapsedFrames, tabledivide64(65536 * elapsedFrames * 30, rfreq));
#endif
    return clamp(tabledivide64(65536 * elapsedFrames * 30, rfreq), 0, 65536);
}

#endif


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

#ifndef EDUKE32_COMMON_GAME_H_
#define EDUKE32_COMMON_GAME_H_

#include "build.h"

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

#include "vfs.h"

#ifdef __cplusplus
extern "C" {
#endif

    extern int g_useCwd;

#ifndef APPNAME
#define APPNAME             "Witchaven"
#endif

#ifndef APPBASENAME
#define APPBASENAME         "witchaven"
#endif

#define GAMEFLAG_WH1        0x00000001
#define GAMEFLAG_WH2        0x00000002
#define GAMEFLAGMASK        0x000000FF // flags allowed from grpinfo

extern struct grpfile_t const* g_selectedGrp;

extern int32_t g_gameType;
extern int     g_addonNum;

#define WH1               (g_gameType & GAMEFLAG_WH1)
#define WH2               (g_gameType & GAMEFLAG_WH2)

enum Games_t {
    GAME_WH = 0,
    GAME_WH2,
    GAMECOUNT
};

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

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

#define OSDTEXT_DEFAULT   "^00"
#define OSDTEXT_DARKRED   "^10"
#define OSDTEXT_GREEN     "^11"
#define OSDTEXT_RED       "^21"
#define OSDTEXT_YELLOW    "^23"

#define OSDTEXT_BRIGHT    "^S0"

#define OSD_ERROR OSDTEXT_DARKRED OSDTEXT_BRIGHT

extern const char* g_gameNamePtr;

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

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

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

extern GrowArray<char*> g_scriptModules;

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

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

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

//////////

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

//////////

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

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

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

extern const char* G_GetInstallPath(int32_t insttype);

//////////

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

//////////

extern void G_LoadLookups(void);

//////////

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

#ifdef __cplusplus
}
#endif

#endif


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2020 EDuke32 developers and contributors
Copyright (C) 2020 sirlemonhead, Nuke.YKT
This file is part of EWitchaven.
EWitchaven is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "compat.h"
#include "renderlayer.h"
#include "_control.h"
#include "build.h"
#include "cache1d.h"
#include "keyboard.h"
#include "control.h"
#include "witchaven.h"
#include "scriplib.h"

#include "config.h"

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

const char gamefunctions[kMaxGameFunctions][kMaxGameFuncLen] =
{
  "Move_Forward",
  "Move_Backward",
  "Turn_Left",
  "Turn_Right",
  "Strafe",
  "Strafe_Left",
  "Strafe_Right",
  "Run",
  "Jump",
  "Crouch",
  "Fire",
  "Open",
  "Fly_Up",
  "Fly_Down",
  "Look_Up",
  "Look_Down",
  "Look_Straight",
  "Aim_Up",
  "Aim_Down",
  "SendMessage",
  "Weapon_0",
  "Weapon_1",
  "Weapon_2",
  "Weapon_3",
  "Weapon_4",
  "Weapon_5",
  "Weapon_6",
  "Weapon_7",
  "Weapon_8",
  "Weapon_9",
  "Spell_Left",
  "Spell_Right",
  "Cast_Spell",
  "Potion_Left",
  "Potion_Right",
  "Use_Potion",
  "Pause",
  "Map",
  "Zoom_In",
  "Zoom_Out",
  "Gamma_Correction",
  "Escape",
  "Shrink_Screen",
  "Enlarge_Screen",
  "Mouse_Sensitivity_Up",
  "Mouse_Sensitivity_Down",
  "Show_Console",
  "Mouse_Aiming",
  "Toggle_Crosshair"
};

const char keydefaults[kMaxGameFunctions * 2][kMaxGameFuncLen] =
{
   "W", "Kpad8",
   "S", "Kpad2",
   "Left", "Kpad4",
   "Right", "KPad6",
   "LAlt", "RAlt",
   "A", "",
   "D", "",
   "LShift", "RShift",
   "Space", "",
   "LCtrl", "",
   "RCtrl", "",
   "E", "",
   "Insert", "",
   "Delete", "",
   "PgUp", "",
   "PgDn", "",
   "Home", "",
   "", "", // aim up
   "", "", // aim down
   "T", "",
   "0", "",
   "1", "",
   "2", "",
   "3", "",
   "4", "",
   "5", "",
   "6", "",
   "7", "",
   "8", "",
   "9", "",
   "", "", // spell left
   "", "", // spell right
   "'", "", // cast spell
   "[", "", // potion left
   "]", "", // potion right
   "Enter", "",  // use potion
   "Pause", "",
   "Tab", "",
   "=", "",
   "-", "",
   "F11", "",
   "Escape", "",
   "Kpad-", "",
   "Kpad+", "",
   "", "", // sensitivity up
   "", "", // sensitivity down
   "`", "",
   "U", "",
   "I", "",
};

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

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

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

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

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

ud_setup_t gSetup;

char setupfilename[BMAX_PATH] = {kSetupFilename};

int lMouseSens = 32;

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

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

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

int32_t MAXCACHE1DSIZE = (96*1024*1024);

void SetupGameButtons()
{
    CONTROL_DefineFlag(gamefunc_Move_Forward,           FALSE);
    CONTROL_DefineFlag(gamefunc_Move_Backward,          FALSE);
    CONTROL_DefineFlag(gamefunc_Turn_Left,              FALSE);
    CONTROL_DefineFlag(gamefunc_Turn_Right,             FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe,                 FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe_Left,            FALSE);
    CONTROL_DefineFlag(gamefunc_Strafe_Right,           FALSE);
    CONTROL_DefineFlag(gamefunc_Run,                    FALSE);
    CONTROL_DefineFlag(gamefunc_Jump,                   FALSE);
    CONTROL_DefineFlag(gamefunc_Crouch,                 FALSE);
    CONTROL_DefineFlag(gamefunc_Fire,                   FALSE);
    CONTROL_DefineFlag(gamefunc_Open,                   FALSE);
    CONTROL_DefineFlag(gamefunc_Fly_Up,                 FALSE);
    CONTROL_DefineFlag(gamefunc_Fly_Down,               FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Up,                FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Down,              FALSE);
    CONTROL_DefineFlag(gamefunc_Look_Straight,          FALSE);
    CONTROL_DefineFlag(gamefunc_Aim_Up,                 FALSE);
    CONTROL_DefineFlag(gamefunc_Aim_Down,               FALSE);
    CONTROL_DefineFlag(gamefunc_SendMessage,            FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_1,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_2,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_3,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_4,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_5,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_6,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_7,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_8,               FALSE);
    CONTROL_DefineFlag(gamefunc_Weapon_9,               FALSE);
    CONTROL_DefineFlag(gamefunc_Spell_Left,             FALSE);
    CONTROL_DefineFlag(gamefunc_Spell_Right,            FALSE);
    CONTROL_DefineFlag(gamefunc_Cast_Spell,             FALSE);
    CONTROL_DefineFlag(gamefunc_Potion_Left,            FALSE);
    CONTROL_DefineFlag(gamefunc_Potion_Right,           FALSE);
    CONTROL_DefineFlag(gamefunc_Use_Potion,             FALSE);
    CONTROL_DefineFlag(gamefunc_Pause,                  FALSE);
    CONTROL_DefineFlag(gamefunc_Map,                    FALSE);
    CONTROL_DefineFlag(gamefunc_Zoom_In,                FALSE);
    CONTROL_DefineFlag(gamefunc_Zoom_Out,               FALSE);
    CONTROL_DefineFlag(gamefunc_Gamma_Correction,       FALSE);
    CONTROL_DefineFlag(gamefunc_Escape,                 FALSE);
    CONTROL_DefineFlag(gamefunc_Shrink_Screen,          FALSE);
    CONTROL_DefineFlag(gamefunc_Enlarge_Screen,         FALSE);
    CONTROL_DefineFlag(gamefunc_Mouse_Sensitivity_Up,   FALSE);
    CONTROL_DefineFlag(gamefunc_Mouse_Sensitivity_Down, FALSE);
    CONTROL_DefineFlag(gamefunc_Mouse_Aiming,           FALSE);
    CONTROL_DefineFlag(gamefunc_Toggle_Crosshair,       FALSE);
}

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

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

    return hash_findcase(&h_gamefuncs, func);
}

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

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

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

    return "";
}

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

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

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

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

        auto &key = KeyboardKeys[i];

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

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

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

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

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

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

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

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

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

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

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

    MixRate = 44100;
    FXVolume = 255;
    MusicVolume = 255;
    NumChannels = 2;
    NumBits = 16;
    NumVoices = 32;
/* // TODO
    mouseaiming = 0;
    aimmode = 1;
    mouseflip = 1;
    runkey_mode = 0;
    auto_run = 1;

    gFov = 90;
*/
    CONFIG_SetDefaultKeys(keydefaults);

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

    CONTROL_MouseSensitivity = DEFAULTMOUSESENSITIVITY;

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

    for (int i=0; i<MAXMOUSEAXES; i++)
    {
        MouseAnalogueScale[i] = DEFAULTMOUSEANALOGUESCALE;
        CONTROL_SetAnalogAxisScale(i, MouseAnalogueScale[i], controldevice_mouse);

        MouseDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i*2]);
        MouseDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i*2+1]);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][0], 0);
        CONTROL_MapDigitalAxis(i, MouseDigitalFunctions[i][1], 1);

        MouseAnalogueAxes[i] = CONFIG_AnalogNameToNum(mouseanalogdefaults[i]);
        CONTROL_MapAnalogAxis(i, MouseAnalogueAxes[i]);
    }

    // TODO:
    //CONFIG_SetGameControllerDefaultsStandard();

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

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

    CONTROL_ClearAssignments();
    CONFIG_SetDefaults();

    setupread = 1;
    pathsearchmode = 1;

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

    pathsearchmode = 0;

    if (scripthandle < 0)
        return -1;

    SCRIPT_GetNumber(scripthandle, "Options", "GoreOn", &goreon);
    SCRIPT_GetNumber(scripthandle, "Options", "Difficulty", &difficulty);
    SCRIPT_GetNumber(scripthandle, "Options", "Brightness", &gbrightness);

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

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

    if (cachesize > MAXCACHE1DSIZE)
        MAXCACHE1DSIZE = cachesize;

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

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

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

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

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

    setupread = 1;
    return 0;
}

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

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

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

        int match = 0;

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

        tempbuf[0] = 0;

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

        int const len = Bstrlen(tempbuf);

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

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

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

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

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

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

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

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

        Bsprintf(str,"MouseAnalogScale%d",i);
        int32_t scale = MouseAnalogueScale[i];
        SCRIPT_GetNumber(scripthandle, "Controls", str, &scale);
        MouseAnalogueScale[i] = scale;
    }

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

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

    if (scripthandle < 0) return;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    buildvfs_FILE fp = buildvfs_fopen_write(filename);

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

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

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

        OSD_WriteAliases(fp);

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

        OSD_WriteCvars(fp);

        buildvfs_fclose(fp);

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

        return;
    }

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

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

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

    SCRIPT_PutNumber(scripthandle, "Options", "GoreOn", goreon, FALSE, TRUE);
    SCRIPT_PutNumber(scripthandle, "Options", "Difficulty", difficulty, FALSE, 2);
    SCRIPT_PutNumber(scripthandle, "Options", "Brightness", gbrightness, FALSE, 0);

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

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

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

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

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

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

    //if (!NAM_WW2GI)
    //{
    //    SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "Out",ud.lockout,FALSE,FALSE);
    //    SCRIPT_PutString(ud.config.scripthandle, "Screen Setup", "Password",ud.pwlockout);
    //}

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

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

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

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

        for (int i=0; i<MAXMOUSEAXES; i++)
        {
            if (CONFIG_AnalogNumToName(MouseAnalogueAxes[i]))
            {
                Bsprintf(buf, "MouseAnalogAxes%d", i);
                SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_AnalogNumToName(MouseAnalogueAxes[i]));
            }

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

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

            Bsprintf(buf, "MouseAnalogScale%d", i);
            SCRIPT_PutNumber(scripthandle, "Controls", buf, MouseAnalogueScale[i], FALSE, FALSE);
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

    SCRIPT_Save(scripthandle, setupfilename);

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

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


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2020 EDuke32 developers and contributors
Copyright (C) 2020 sirlemonhead, Nuke.YKT
This file is part of EWitchaven.
EWitchaven is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#ifndef __config_h__
#define __config_h__

#include "compat.h"

#define kMaxGameFuncLen     64

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

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

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

// default mouse scale
#define DEFAULTMOUSEANALOGUESCALE           65536

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

enum
{
    gamefunc_Move_Forward,
    gamefunc_Move_Backward,
    gamefunc_Turn_Left,
    gamefunc_Turn_Right,
    gamefunc_Strafe,
    gamefunc_Strafe_Left,
    gamefunc_Strafe_Right,
    gamefunc_Run,
    gamefunc_Jump,
    gamefunc_Crouch,
    gamefunc_Fire,
    gamefunc_Open,
    gamefunc_Fly_Up,
    gamefunc_Fly_Down,
    gamefunc_Look_Up,
    gamefunc_Look_Down,
    gamefunc_Look_Straight,
    gamefunc_Aim_Up,
    gamefunc_Aim_Down,
    gamefunc_SendMessage,
    gamefunc_Weapon_0,
    gamefunc_Weapon_1,
    gamefunc_Weapon_2,
    gamefunc_Weapon_3,
    gamefunc_Weapon_4,
    gamefunc_Weapon_5,
    gamefunc_Weapon_6,
    gamefunc_Weapon_7,
    gamefunc_Weapon_8,
    gamefunc_Weapon_9,
    gamefunc_Spell_Left,
    gamefunc_Spell_Right,
    gamefunc_Cast_Spell,
    gamefunc_Potion_Left,
    gamefunc_Potion_Right,
    gamefunc_Use_Potion,
    gamefunc_Pause,
    gamefunc_Map,
    gamefunc_Zoom_In,
    gamefunc_Zoom_Out,
    gamefunc_Gamma_Correction,
    gamefunc_Escape,
    gamefunc_Shrink_Screen,
    gamefunc_Enlarge_Screen,
    gamefunc_Mouse_Sensitivity_Up,
    gamefunc_Mouse_Sensitivity_Down,
    gamefunc_Show_Console,
    gamefunc_Mouse_Aiming,
    gamefunc_Toggle_Crosshair,
    kMaxGameFunctions
};

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

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

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

extern hashtable_t h_gamefuncs;

void SetupInput();

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

extern int lMouseSens;

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

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

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

extern int32_t MAXCACHE1DSIZE;

extern int32_t difficulty;
extern int32_t goreon;

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

#endif


code:
/****************************************************************************
            Witchaven

            WHFX.C

            Rafael Paiz

****************************************************************************/

#include "effects.h"
#include "player.h"
#include "sound.h"
#include "objects.h"
#include "witchaven.h"
#include "input.h"
#include "view.h"

#define LAVASIZ 128
#define LAVALOGSIZ 7
#define LAVAMAXDROPS 32

#define WATERSIZ 128
#define WATERLOGSIZ 7
#define WATERMAXDROPS 16 // bjd - this was 1 but contradicts the way arrays sized with this define were accessed

#define FLOORUNIT    16384L

int justwarpedfx = 0;
int lastbat = -1;

extern int justteleported;

extern short lavadrylandsector[32];
extern short lavadrylandcnt;

static char lavabakpic[(LAVASIZ+2)*(LAVASIZ+2)], lavainc[LAVASIZ];
static int lavanumdrops, lavanumframes;
static int lavadropx[LAVAMAXDROPS], lavadropy[LAVAMAXDROPS];
static int lavadropsiz[LAVAMAXDROPS], lavadropsizlookup[LAVAMAXDROPS];
static int lavaradx[32][128], lavarady[32][128], lavaradcnt[32];

static char waterbakpic[(WATERSIZ+2)*(WATERSIZ+2)], waterinc[WATERSIZ];
static int waternumdrops, waternumframes;
static int waterdropx[WATERMAXDROPS], waterdropy[WATERMAXDROPS];
static int waterdropsiz[WATERMAXDROPS], waterdropsizlookup[WATERMAXDROPS];
static int waterradx[32][128], waterrady[32][128], waterradcnt[32];

extern short revolvesector[], revolveang[], revolveclip[], revolvecnt;
extern int revolvex[16][16], revolvey[16][16];
extern int revolvepivotx[], revolvepivoty[];

extern short xpanningsectorlist[], xpanningsectorcnt;
extern short ypanningwalllist[], ypanningwallcnt;
extern short floorpanninglist[], floorpanningcnt;
extern short skypanlist[], skypancnt;
extern short crushsectorlist[], crushsectorcnt;
extern short crushsectoranim[], crushsectordone[];
extern short bobbingsectorcnt, bobbingsectorlist[];
extern short warpsectorlist[], warpsectorcnt;

char revolvesyncstat;
short revolvesyncang, revolvesyncrotang;
int revolvesyncx, revolvesyncy;

extern int mapon;

void initlava()
{
    for (int x = -16; x <= 16; x++)
    {
        for (int y = -16; y <= 16; y++)
        {
            int r = ksqrt(x * x + y * y);
            lavaradx[r][lavaradcnt[r]] = x;
            lavarady[r][lavaradcnt[r]] = y;
            lavaradcnt[r]++;
        }
    }

    for (int z = 0; z < 16; z++)
        lavadropsizlookup[z] = 8 / (ksqrt(z) + 1);

    for (int z = 0; z < LAVASIZ; z++)
        lavainc[z] = klabs((((z ^ 17) >> 4) & 7) - 4) + 12;

    lavanumdrops = 0;
    lavanumframes = 0;
}

void movelava(char* dapic)
{
    char dat, * ptr;
    int32_t x, y, z, zx, dalavadropsiz, dadropsizlookup;
    intptr_t offs, offs2;
    int32_t dalavax, dalavay;

    z = 3;
    if (lavanumdrops + z >= LAVAMAXDROPS)
        z = LAVAMAXDROPS - lavanumdrops - 1;

    while (z >= 0)
    {
        lavadropx[lavanumdrops] = (rand() & (LAVASIZ - 1));
        lavadropy[lavanumdrops] = (rand() & (LAVASIZ - 1));
        lavadropsiz[lavanumdrops] = 1;
        lavanumdrops++;
        z--;
    }

    z = lavanumdrops - 1;
    while (z >= 0)
    {
        dadropsizlookup = lavadropsizlookup[lavadropsiz[z]] * (((z & 1) << 1) - 1);
        dalavadropsiz = lavadropsiz[z];
        dalavax = lavadropx[z]; dalavay = lavadropy[z];
        for (zx = lavaradcnt[lavadropsiz[z]] - 1; zx >= 0; zx--)
        {
            offs = (((lavaradx[dalavadropsiz][zx] + dalavax) & (LAVASIZ - 1)) << LAVALOGSIZ);
            offs += ((lavarady[dalavadropsiz][zx] + dalavay) & (LAVASIZ - 1));
            dapic[offs] += dadropsizlookup;

            if (dapic[offs] < 192)
                dapic[offs] = 192;
        }

        lavadropsiz[z]++;
        if (lavadropsiz[z] > 10)
        {
            lavanumdrops--;
            lavadropx[z] = lavadropx[lavanumdrops];
            lavadropy[z] = lavadropy[lavanumdrops];
            lavadropsiz[z] = lavadropsiz[lavanumdrops];
        }
        z--;
    }

    //Back up dapic with 1 pixel extra on each boundary
    //(to prevent anding for wrap-around)
    offs = ((intptr_t)dapic);
    offs2 = (LAVASIZ + 2) + 1 + ((intptr_t)lavabakpic);
    for (x = 0; x < LAVASIZ; x++)
    {
        copybuf((char*)offs, (char*)offs2, LAVASIZ >> 2);
        offs += LAVASIZ;
        offs2 += LAVASIZ + 2;
    }
    for (y = 0; y < LAVASIZ; y++)
    {
        lavabakpic[y + 1] = dapic[y + ((LAVASIZ - 1) << LAVALOGSIZ)];
        lavabakpic[y + 1 + (LAVASIZ + 1) * (LAVASIZ + 2)] = dapic[y];
    }
    for (x = 0; x < LAVASIZ; x++)
    {
        lavabakpic[(x + 1) * (LAVASIZ + 2)] = dapic[(x << LAVALOGSIZ) + (LAVASIZ - 1)];
        lavabakpic[(x + 1) * (LAVASIZ + 2) + (LAVASIZ + 1)] = dapic[x << LAVALOGSIZ];
    }
    lavabakpic[0] = dapic[LAVASIZ * LAVASIZ - 1];
    lavabakpic[LAVASIZ + 1] = dapic[LAVASIZ * (LAVASIZ - 1)];
    lavabakpic[(LAVASIZ + 2) * (LAVASIZ + 1)] = dapic[LAVASIZ - 1];
    lavabakpic[(LAVASIZ + 2) * (LAVASIZ + 2) - 1] = dapic[0];

    for (z = (LAVASIZ + 2) * (LAVASIZ + 2) - 4; z >= 0; z -= 4)
    {
        lavabakpic[z + 0] &= 31;
        lavabakpic[z + 1] &= 31;
        lavabakpic[z + 2] &= 31;
        lavabakpic[z + 3] &= 31;
    }

    for (x = LAVASIZ - 1; x >= 0; x--)
    {
        offs = (x + 1) * (LAVASIZ + 2) + 1;
        ptr = (char*)((x << LAVALOGSIZ) + (intptr_t)dapic);

        zx = ((x + lavanumframes) & (LAVASIZ - 1));

        offs2 = LAVASIZ - 1;
        for (y = offs; y < offs + LAVASIZ; y++)
        {
            dat = lavainc[(offs2--) & zx];
            dat += lavabakpic[y - (LAVASIZ + 2) - 1];
            dat += lavabakpic[y - (LAVASIZ + 2)];
            dat += lavabakpic[y - (LAVASIZ + 2) + 1];
            dat += lavabakpic[y - 1];
            dat += lavabakpic[y + 1];
            dat += lavabakpic[y + (LAVASIZ + 2)];
            dat += lavabakpic[y + (LAVASIZ + 2) - 1];
            *ptr++ = (dat >> 3) + 192;
        }
    }

    lavanumframes++;
}

void initwater()
{
    for (int x = -16; x <= 16; x++)
    {
        for (int y = -16; y <= 16; y++)
        {
            int r = ksqrt(x * x + y * y);
            waterradx[r][waterradcnt[r]] = x;
            waterrady[r][waterradcnt[r]] = y;
            waterradcnt[r]++;
        }
    }

    for (int z = 0; z < WATERMAXDROPS; z++)
        waterdropsizlookup[z] = 8 / (ksqrt(z) + 1);

    for (int z = 0; z < WATERSIZ; z++)
        waterinc[z] = klabs((((z ^ 17) >> 4) & 7) - 4) + 12;

    waternumdrops = 0;
    waternumframes = 0;
}

void movewater(char* dapic)
{
    char dat, * ptr;
    int32_t x, y, z, zx, dawaterdropsiz, dadropsizlookup;
    intptr_t offs, offs2;
    int32_t dawaterx, dawatery;

    z = 3;
    if (waternumdrops + z >= WATERMAXDROPS)
        z = WATERMAXDROPS - waternumdrops - 1;

    while (z >= 0)
    {
        waterdropx[waternumdrops] = (rand() & (WATERSIZ - 1));
        waterdropy[waternumdrops] = (rand() & (WATERSIZ - 1));
        waterdropsiz[waternumdrops] = 1;
        waternumdrops++;
        z--;
    }

    z = waternumdrops - 1;

    while (z >= 0)
    {
        dadropsizlookup = waterdropsizlookup[waterdropsiz[z]] * (((z & 1) << 1) - 1);
        dawaterdropsiz = waterdropsiz[z];
        dawaterx = waterdropx[z]; dawatery = waterdropy[z];
        for (zx = waterradcnt[waterdropsiz[z]] - 1; zx >= 0; zx--)
        {
            offs = (((waterradx[dawaterdropsiz][zx] + dawaterx) & (WATERSIZ - 1)) << WATERLOGSIZ);
            offs += ((waterrady[dawaterdropsiz][zx] + dawatery) & (WATERSIZ - 1));
            dapic[offs] += dadropsizlookup;
            if (dapic[offs] < 224) dapic[offs] = 224;
        }

        waterdropsiz[z]++;
        if (waterdropsiz[z] > 10)
        {
            waternumdrops--;
            waterdropx[z] = waterdropx[waternumdrops];
            waterdropy[z] = waterdropy[waternumdrops];
            waterdropsiz[z] = waterdropsiz[waternumdrops];
        }
        z--;
    }

    offs = ((intptr_t)dapic);
    offs2 = (WATERSIZ + 2) + 1 + ((intptr_t)waterbakpic);

    for (x = 0; x < WATERSIZ; x++)
    {
        copybuf((char*)offs, (char*)offs2, WATERSIZ >> 2);
        offs += WATERSIZ;
        offs2 += WATERSIZ + 2;
    }
    for (y = 0; y < WATERSIZ; y++)
    {
        waterbakpic[y + 1] = dapic[y + ((WATERSIZ - 1) << WATERLOGSIZ)];
        waterbakpic[y + 1 + (WATERSIZ + 1) * (WATERSIZ + 2)] = dapic[y];
    }
    for (x = 0; x < WATERSIZ; x++)
    {
        waterbakpic[(x + 1) * (WATERSIZ + 2)] = dapic[(x << WATERLOGSIZ) + (WATERSIZ - 1)];
        waterbakpic[(x + 1) * (WATERSIZ + 2) + (WATERSIZ + 1)] = dapic[x << WATERLOGSIZ];
    }

    waterbakpic[0] = dapic[WATERSIZ * WATERSIZ - 1];
    waterbakpic[WATERSIZ + 1] = dapic[WATERSIZ * (WATERSIZ - 1)];
    waterbakpic[(WATERSIZ + 2) * (WATERSIZ + 1)] = dapic[WATERSIZ - 1];
    waterbakpic[(WATERSIZ + 2) * (WATERSIZ + 2) - 1] = dapic[0];

    for (z = (WATERSIZ + 2) * (WATERSIZ + 2) - 4; z >= 0; z -= 4)
    {
        waterbakpic[z + 0] &= 15;
        waterbakpic[z + 1] &= 15;
        waterbakpic[z + 2] &= 15;
        waterbakpic[z + 3] &= 15;
    }

    for (x = WATERSIZ - 1; x >= 0; x--)
    {
        offs = (x + 1) * (WATERSIZ + 2) + 1;
        ptr = (char*)((x << WATERLOGSIZ) + (intptr_t)dapic);

        zx = ((x + waternumframes) & (WATERSIZ - 1));

        offs2 = WATERSIZ - 1;
        for (y = offs; y < offs + WATERSIZ; y++)
        {
            dat = waterinc[(offs2--) & zx];
            dat += waterbakpic[y - (WATERSIZ + 2) - 1];
            dat += waterbakpic[y - (WATERSIZ + 2)];
            dat += waterbakpic[y - (WATERSIZ + 2) + 1];
            dat += waterbakpic[y - 1];
            dat += waterbakpic[y + 1];
            dat += waterbakpic[y + (WATERSIZ + 2) + 1];
            dat += waterbakpic[y + (WATERSIZ + 2)];
            dat += waterbakpic[y + (WATERSIZ + 2) - 1];
            *ptr++ = (dat >> 3) + 223;
        }
    }

    waternumframes++;
}

void skypanfx()
{
    for (int i = 0; i < skypancnt; i++) {
        sector[skypanlist[i]].ceilingxpanning = -((lockclock >> 2) & 255);
    }
}

void panningfx()
{
    Player* plr = &player[pyrn];

    //
    // n ne e se s sw w nw
    // 0  1 2  3 4  5 6  7
    //
    for (int i = 0; i < floorpanningcnt; i++)
    {
        short whichdir = sector[floorpanninglist[i]].lotag - 80;

        switch (whichdir)
        {
            case 0:
            sector[floorpanninglist[i]].floorypanning = ((lockclock >> 2) & 255);
            break;
            case 1:
            sector[floorpanninglist[i]].floorxpanning = -((lockclock >> 2) & 255);
            sector[floorpanninglist[i]].floorypanning = ((lockclock >> 2) & 255);
            break;
            case 2:
            sector[floorpanninglist[i]].floorxpanning = -((lockclock >> 2) & 255);
            break;
            case 3:
            sector[floorpanninglist[i]].floorxpanning = -((lockclock >> 2) & 255);
            sector[floorpanninglist[i]].floorypanning = -((lockclock >> 2) & 255);
            break;
            case 4:
            sector[floorpanninglist[i]].floorypanning = -((lockclock >> 2) & 255);
            break;
            case 5:
            sector[floorpanninglist[i]].floorxpanning = ((lockclock >> 2) & 255);
            sector[floorpanninglist[i]].floorypanning = -((lockclock >> 2) & 255);
            break;
            case 6:
            sector[floorpanninglist[i]].floorxpanning = ((lockclock >> 2) & 255);
            break;
            case 7:
            sector[floorpanninglist[i]].floorxpanning = ((lockclock >> 2) & 255);
            sector[floorpanninglist[i]].floorypanning = ((lockclock >> 2) & 255);
            break;
            default:
            sector[floorpanninglist[i]].floorxpanning = 0;
            sector[floorpanninglist[i]].floorypanning = 0;
            break;
        }
    }

    for (int i = 0; i < xpanningsectorcnt; i++)
    {
        short dasector = xpanningsectorlist[i];
        short startwall = sector[dasector].wallptr;
        short endwall = startwall + sector[dasector].wallnum - 1;
        for (int s = startwall; s <= endwall; s++)
        {
            wall[s].xpanning = ((lockclock >> 2) & 255);
        }
    }

    for (int i = 0; i < ypanningwallcnt; i++) {
        wall[ypanningwalllist[i]].ypanning = ~(lockclock & 255);
    }
}

void crushingfx()
{
    int j, s;
    int32_t daz, goalz;

    for (int i = 0; i < crushsectorcnt; i++)
    {
        s = crushsectorlist[i];

        if (crushsectordone[s] == 1)
        {
            switch (crushsectoranim[s])
            {
                case 1:
                if ((j = getanimationgoal(&sector[s].floorz)) < 0)
                {
                    setanimation(&sector[s].floorz, sector[s].ceilingz, 64);
                    crushsectordone[s] = 2;
                }
                break;
                case 2:
                if ((j = getanimationgoal(&sector[s].ceilingz)) < 0)
                {
                    setanimation(&sector[s].ceilingz, sector[s].floorz, 64);
                    crushsectordone[s] = 2;
                }
                break;
                case 3:
                if ((j = getanimationgoal(&sector[s].ceilingz)) < 0)
                {
                    daz = (sector[s].ceilingz + sector[s].floorz) >> 1;
                    setanimation(&sector[s].ceilingz, daz, 64);
                    setanimation(&sector[s].floorz, daz, 64);
                    crushsectordone[s] = 2;
                }
                break;
            }
        }

        if (crushsectordone[s] == 2)
        {
            switch (crushsectoranim[s])
            {
                case 1:
                if ((j = getanimationgoal(&sector[s].floorz)) < 0)
                {
                    goalz = sector[nextsectorneighborz(s, sector[s].floorz, 1, 1)].floorz;
                    setanimation(&sector[s].floorz, goalz, 64);
                    crushsectordone[s] = 1;
                }
                break;
                case 2:
                if ((j = getanimationgoal(&sector[s].ceilingz)) < 0)
                {
                    goalz = sector[nextsectorneighborz(s, sector[s].ceilingz, -1, -1)].ceilingz;
                    setanimation(&sector[s].ceilingz, goalz, 64);
                    crushsectordone[s] = 1;
                }
                break;
                case 3:
                if ((j = getanimationgoal(&sector[s].ceilingz)) < 0)
                {
                    goalz = sector[nextsectorneighborz(s, sector[s].ceilingz, -1, -1)].ceilingz;
                    setanimation(&sector[s].ceilingz, goalz, 64);
                    goalz = sector[nextsectorneighborz(s, sector[s].floorz, 1, 1)].floorz;
                    setanimation(&sector[s].floorz, goalz, 64);
                    crushsectordone[s] = 1;
                }
                break;
            }
        }
    }
}

void revolvefx()
{
    int32_t dax, day;
    Player* plr = &player[pyrn];

    for (int i = 0; i < revolvecnt; i++)
    {
        short startwall = sector[revolvesector[i]].wallptr;
        short endwall = startwall + sector[revolvesector[i]].wallnum - 1;

        revolveang[i] = ((revolveang[i] + 2048 - (synctics << 1)) & kAngleMask);

        for (int k = startwall; k <= endwall; k++)
        {
            vec2_t pivot;
            pivot.x = revolvepivotx[i];
            pivot.y = revolvepivoty[i];

            vec2_t p;
            p.x = revolvex[i][k - startwall];
            p.y = revolvey[i][k - startwall];

            vec2_t p2;
            rotatepoint(pivot, p, revolveang[i], &p2);

            dax = p2.x;
            day = p2.y;

            dragpoint(k, dax, day, 0); // CHECKME - flags
        }

        if (plr->sector == revolvesector[i])
        {
            revolvesyncang = plr->ang;
            revolvesyncrotang = 0;
            revolvesyncx = plr->x;
            revolvesyncy = plr->y;
            revolvesyncrotang = ((revolvesyncrotang + 2048 - (synctics << 1)) & kAngleMask);

            vec2_t pivot;
            pivot.x = revolvepivotx[i];
            pivot.y = revolvepivoty[i];

            vec2_t p;
            p.x = revolvesyncx;
            p.y = revolvesyncy;

            vec2_t p2;
            p2.x = plr->x;
            p2.y = plr->y;

            rotatepoint(pivot, p, revolvesyncrotang, &p2);

            plr->x = p2.x;
            plr->y = p2.y;

            plr->ang = ((revolvesyncang + revolvesyncrotang) & kAngleMask);
        }
    }
}

void bobbingsector()
{
    for (int i = 0; i < bobbingsectorcnt; i++)
    {
        int dasector = bobbingsectorlist[i];

        sector[dasector].floorz += Sin(lockclock << 4) >> 6;
    }
}

#define NEXTLEVEL 1
#define ENDOFDEMO 2

void teleporter()
{
    short dasector;
    short startwall, endwall;
    int i, j;
    int s;
    short daang;

    Player* plr = &player[pyrn];

    for (i = 0; i < warpsectorcnt; i++)
    {
        dasector = warpsectorlist[i];
        j = ((lockclock & 127) >> 2);

        if (j >= 16) j = 31 - j;
        {
            sector[dasector].ceilingshade = j;
            sector[dasector].floorshade = j;
            startwall = sector[dasector].wallptr;
            endwall = startwall + sector[dasector].wallnum - 1;
            for (s = startwall; s <= endwall; s++)
                wall[s].shade = j;
        }
    }

    if (sector[plr->sector].lotag == 10)
    {
        if (plr->sector != plr->oldsector)
        {
            daang = (short)plr->ang;
            warpfxsprite(plr->spritenum);
            warp(&plr->x, &plr->y, &plr->z, &daang, &plr->sector);
            warpfxsprite(plr->spritenum);
            plr->ang = (int)daang;
            justwarpedfx = 48;
            //JSA PLUTO
            playsound_loc(S_WARP, plr->x, plr->y);

            vec3_t pos;
            pos.x = plr->x;
            pos.y = plr->y;
            pos.z = plr->z + (32 << 8);

            setsprite(plr->spritenum, &pos);
        }
    }

    if (sector[plr->sector].lotag == 4002)
    {
        if (plr->treasure[8] == 1)
        {
            plr->treasure[8] = 0;
            if (plr->sector != plr->oldsector)
            {
                switch (sector[plr->sector].hitag)
                {
                    case NEXTLEVEL:
                    justteleported = 1;
                    mapon++;
                    vel = 0;
                    angvel = 0;
                    svel = 0;
                    playsound_loc(S_CHAINDOOR1, plr->x, plr->y);
                    loadnewlevel(mapon);
                    warpfxsprite(plr->spritenum);
                    // cleans up the keys (ivorykey etc)
                    plr->treasure[14] = plr->treasure[15] = plr->treasure[16] = plr->treasure[17] = 0;
                    plr->treasure[4] = plr->treasure[5] = 0;
                    SND_CheckLoops();
                    break;
                    case ENDOFDEMO:
                    playsound_loc(S_THUNDER1, plr->x, plr->y);
                    justteleported = 1;
                    victory();
                    SND_CheckLoops();
                    break;
                }
            }
        }
        else
        {
            // player need pentagram to teleport
            StatusMessage(360, "ITEM NEEDED");
        }
    }
}

void warp(int32_t* x, int32_t* y, int32_t* z, short* daang, short* dasector)
{
    short startwall, endwall, s;
    int32_t i, dax, day;

    for (int i = 0; i < warpsectorcnt; i++)
    {
        if (sector[warpsectorlist[i]].hitag == sector[*dasector].hitag && warpsectorlist[i] != *dasector)
        {
            *dasector = warpsectorlist[i];
            break;
        }
    }

    startwall = sector[*dasector].wallptr;
    endwall = startwall + sector[*dasector].wallnum - 1;
    dax = 0, day = 0;

    for (s = startwall; s <= endwall; s++)
    {
        dax += wall[s].x, day += wall[s].y;
        if (wall[s].nextsector >= 0)
        {
            i = s;
        }
    }

    *x = dax / (endwall - startwall + 1);
    *y = day / (endwall - startwall + 1);
    *z = sector[*dasector].floorz - (32 << 8);
    updatesector(*x, *y, dasector);
    dax = ((wall[i].x + wall[wall[i].point2].x) >> 1);
    day = ((wall[i].y + wall[wall[i].point2].y) >> 1);
    *daang = getangle(dax - *x, day - *y);
}

void warpsprite(short spritenum)
{
    short dasectnum = sprite[spritenum].sectnum;

    warpfxsprite(spritenum);
    warp(&sprite[spritenum].x, &sprite[spritenum].y, &sprite[spritenum].z, &sprite[spritenum].ang, &dasectnum);

    warpfxsprite(spritenum);

    vec3_t pos;
    pos.x = sprite[spritenum].x;
    pos.y = sprite[spritenum].y;
    pos.z = sprite[spritenum].z;

    setsprite(spritenum, &pos);
}

#define GRATEROT   16
#define GRATEVERT  512

void ironbars()
{
    int32_t spritenum;
    int32_t ironbarmove;

    for (int i = 0; i < ironbarscnt; i++)
    {
        if (ironbarsdone[i] == 1)
        {
            spritenum = ironbarsanim[i];

            switch (sprite[ironbarsanim[i]].hitag)
            {
                case 1:
                {
                    sprite[ironbarsanim[i]].ang += synctics << 1;
                    if (sprite[ironbarsanim[i]].ang > 2047)
                        sprite[ironbarsanim[i]].ang -= 2047;
                    ironbarmove = ironbarsgoal[i] += synctics << 1;

                    vec3_t pos;
                    pos.x = sprite[spritenum].x;
                    pos.y = sprite[spritenum].y;
                    pos.z = sprite[spritenum].z;

                    setsprite(spritenum, &pos);
                    if (ironbarsgoal[i] > 512)
                    {
                        ironbarsgoal[i] = 0;
                        sprite[ironbarsanim[i]].hitag = 2;
                        ironbarsdone[i] = 0;
                    }
                    break;
                }
                case 2:
                {
                    sprite[ironbarsanim[i]].ang -= synctics << 1;
                    if (sprite[ironbarsanim[i]].ang < 0)
                        sprite[ironbarsanim[i]].ang += 2047;
                    ironbarsgoal[i] += synctics << 1;

                    vec3_t pos;
                    pos.x = sprite[spritenum].x;
                    pos.y = sprite[spritenum].y;
                    pos.z = sprite[spritenum].z;

                    setsprite(spritenum, &pos);
                    if (ironbarsgoal[i] > 512)
                    {
                        ironbarsgoal[i] = 0;
                        sprite[ironbarsanim[i]].hitag = 1;
                        ironbarsdone[i] = 0;
                    }
                    break;
                }
                case 3:
                {
                    sprite[ironbarsanim[i]].z -= synctics << 4;
                    if (sprite[ironbarsanim[i]].z < ironbarsgoal[i])
                    {
                        sprite[ironbarsanim[i]].z = ironbarsgoal[i];
                        sprite[ironbarsanim[i]].hitag = 4;
                        ironbarsdone[i] = 0;
                        ironbarsgoal[i] = sprite[ironbarsanim[i]].z + 6000;
                    }

                    vec3_t pos;
                    pos.x = sprite[spritenum].x;
                    pos.y = sprite[spritenum].y;
                    pos.z = sprite[spritenum].z;

                    setsprite(spritenum, &pos);
                    break;
                }
                case 4:
                {
                    sprite[ironbarsanim[i]].z += synctics << 4;
                    if (sprite[ironbarsanim[i]].z > ironbarsgoal[i])
                    {
                        sprite[ironbarsanim[i]].z = ironbarsgoal[i];
                        sprite[ironbarsanim[i]].hitag = 3;
                        ironbarsdone[i] = 0;
                        ironbarsgoal[i] = sprite[ironbarsanim[i]].z - 6000;
                    }

                    vec3_t pos;
                    pos.x = sprite[spritenum].x;
                    pos.y = sprite[spritenum].y;
                    pos.z = sprite[spritenum].z;

                    setsprite(spritenum, &pos);
                    break;
                }
            }
        }
    }
}

void sectorsounds()
{
    uint16_t sec;
    int index;

    if (!SoundMode)
        return;

    Player* plr = &player[pyrn];

    sec = sector[plr->sector].extra;

    if (sec)
    {
        if (sec & 32768)
        {
            // loop on/off sector
            if (sec & 1)
            {
                // turn loop on if lsb is 1
                index = ((sec ^ 32769) >> 1);
                if (ambsoundarray[index].hsound == -1)
                    ambsoundarray[index].hsound = SND_PlaySound(ambsoundarray[index].soundnum, 0, 0, 0, -1);
            }
            else
            {
                // turn loop off if lsb is 0 and its playing
                index = ((sec ^ 32768) >> 1);
                if (ambsoundarray[index].hsound != -1)
                {
                    SND_StopLoop(ambsoundarray[index].hsound);
                    ambsoundarray[index].hsound = -1;
                }
            }
        }
        else
        {
            if (plr->z <= sector[plr->sector].floorz - (8 << 8))
                playsound_loc(sec, plr->x, plr->y);
        }
    }
}

int scarytime=-1;
int scarysize=0;

void scary()
{
    if (rand() > 32600 && rand() > 32600 && scarytime < 0)
    {
        scarytime = 180;
        scarysize = 30;
        SND_PlaySound(S_SCARYDUDE, 0, 0, 0, 0);
    }

    if (scarytime >= 0)
    {
        scarytime -= synctics << 1;
        scarysize += synctics << 1;
        if (scarytime > 140 && scarytime < 180)
            rotatesprite_fs(320 << 15, 200 << 15, scarysize << 9, 0, SCARY, 0, 0, 1 + 2);
        if (scarytime > 120 && scarytime < 139)
            rotatesprite_fs(320 << 15, 200 << 15, scarysize << 9, 0, SCARY + 1, 0, 0, 1 + 2);
        if (scarytime > 100 && scarytime < 119)
            rotatesprite_fs(320 << 15, 200 << 15, scarysize << 9, 0, SCARY + 2, 0, 0, 1 + 2);
        if (scarytime > 0 && scarytime < 99)
            rotatesprite_fs(320 << 15, 200 << 15, scarysize << 9, 0, SCARY + 3, 0, 0, 1 + 2);
    }
}

void dofx()
{
    lavadryland();
    scary();

    if (revolvecnt > 0)
        revolvefx();

    panningfx();
    teleporter();
    bobbingsector();

    if (ironbarscnt > 0)
        ironbars();

    if ((gotpic[ANILAVA >> 3] & (1 << (ANILAVA & 7))) > 0)
    {
        gotpic[ANILAVA >> 3] &= ~(1 << (ANILAVA & 7));
        if (waloff[ANILAVA] != -1)
        {
            movelava((char*)waloff[ANILAVA]);
        }
    }

    if ((gotpic[HEALTHWATER >> 3] & (1 << (HEALTHWATER & 7))) > 0)
    {
        gotpic[HEALTHWATER >> 3] &= ~(1 << (HEALTHWATER & 7));
        if (waloff[HEALTHWATER] != -1)
        {
            movewater((char*)waloff[HEALTHWATER]);
        }
    }

    thesplash();
    thunder();
    cracks();
}

int thunderflash;
int thundertime;

void thunder()
{
    if (thunderflash == 0)
    {
        if ((gotpic[SKY >> 3] & (1 << (SKY & 7))) > 0)
        {
            gotpic[SKY >> 3] &= ~(1 << (SKY & 7));
            if (waloff[SKY] != -1)
            {
                visibility = 1024;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY2 >> 3] & (1 << (SKY2 & 7))) > 0)
        {
            gotpic[SKY2 >> 3] &= ~(1 << (SKY2 & 7));
            if (waloff[SKY2] != -1)
            {
                visibility = 768;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY3 >> 3] & (1 << (SKY3 & 7))) > 0)
        {
            gotpic[SKY3 >> 3] &= ~(1 << (SKY3 & 7));
            if (waloff[SKY3] != -1)
            {
                visibility = 512;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY4 >> 3] & (1 << (SKY4 & 7))) > 0)
        {
            gotpic[SKY4 >> 3] &= ~(1 << (SKY4 & 7));
            if (waloff[SKY4] != -1)
            {
                visibility = 512;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY5 >> 3] & (1 << (SKY5 & 7))) > 0)
        {
            gotpic[SKY5 >> 3] &= ~(1 << (SKY5 & 7));
            if (waloff[SKY5] != -1)
            {
                visibility = 1024;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY6 >> 3] & (1 << (SKY6 & 7))) > 0)
        {
            gotpic[SKY6 >> 3] &= ~(1 << (SKY6 & 7));
            if (waloff[SKY6] != -1)
            {
                visibility = 512;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY7 >> 3] & (1 << (SKY7 & 7))) > 0)
        {
            gotpic[SKY7 >> 3] &= ~(1 << (SKY7 & 7));
            if (waloff[SKY7] != -1)
            {
                visibility = 512;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY8 >> 3] & (1 << (SKY8 & 7))) > 0)
        {
            gotpic[SKY8 >> 3] &= ~(1 << (SKY8 & 7));
            if (waloff[SKY8] != -1)
            {
                visibility = 1024;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY9 >> 3] & (1 << (SKY9 & 7))) > 0)
        {
            gotpic[SKY9 >> 3] &= ~(1 << (SKY9 & 7));
            if (waloff[SKY9] != -1)
            {
                visibility = 2048;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else if ((gotpic[SKY10 >> 3] & (1 << (SKY10 & 7))) > 0)
        {
            gotpic[SKY10 >> 3] &= ~(1 << (SKY10 & 7));
            if (waloff[SKY10] != -1)
            {
                visibility = 1024;
                if (rand() > 32700)
                {
                    thunderflash = 1;
                    thundertime = 120;
                }
            }
        }
        else
            visibility = 1024;
    }
    else
    {
        thundertime -= synctics;
        if (thundertime < 0)
        {
            thunderflash = 0;
            brightness = gbrightness;
            setbrightness(brightness);

            SND_Sound(S_THUNDER1 + (rand() % 4));
            visibility = 1024;
        }
    }

    if (thunderflash == 1)
    {
        if (waloff[SKY] != -1)
        {
            int val = rand() % 4;
            brightness += val;

            switch (val)
            {
                case 0:
                visibility = 2048;
                break;
                case 1:
                visibility = 1024;
                break;
                case 2:
                visibility = 512;
                break;
                case 3:
                visibility = 256;
                break;
                default:
                visibility = 4096;
                break;
            }

            if (brightness > 8) {
                brightness = 0;
            }

            setbrightness(brightness);
        }
    }
}

void thesplash()
{
    Player* plr = &player[pyrn];

    if (sector[plr->sector].floorpicnum == WATER ||
        sector[plr->sector].floorpicnum == LAVA ||
        sector[plr->sector].floorpicnum == SLIME)
    {
        if (plr->sector != plr->oldsector)
        {
            if (sector[plr->sector].floorpicnum == WATER || sector[plr->sector].floorpicnum == SLIME)
                makeasplash(SPLASHAROO, plr);
            else
                makeasplash(LAVASPLASH, plr);
        }
    }
}

void makeasplash(int picnum, Player* plr)
{
    int32_t j = insertsprite(plr->sector, MASPLASH);
    sprite[j].x = plr->x;
    sprite[j].y = plr->y;
    sprite[j].z = sector[plr->sector].floorz + (tilesiz[picnum].y << 8);
    sprite[j].cstat = 0;        //Hitscan does not hit other bullets
    sprite[j].picnum = picnum;
    sprite[j].shade = 0;
    sprite[j].pal = 0;
    sprite[j].xrepeat = 64;
    sprite[j].yrepeat = 64;
    sprite[j].owner = 0;
    sprite[j].clipdist = 16;
    sprite[j].lotag = 8;
    sprite[j].hitag = 0;

    switch (picnum)
    {
        case SPLASHAROO:
        playsound_loc(S_SPLASH1 + (krand() % 3), sprite[j].x, sprite[j].y);
        break;
        case LAVASPLASH:
        break;
    }

    movesprite(j, (Cos(sprite[j].ang) * synctics) << 3, (((int)sintable[sprite[j].ang]) * synctics) << 3, 0, 4 << 8, 4 << 8, 0);
}

void makemonstersplash(int picnum, int i)
{
    if (sprite[i].picnum == FISH)
        return;

    int32_t j = insertsprite(sprite[i].sectnum, MASPLASH);
    sprite[j].x = sprite[i].x;
    sprite[j].y = sprite[i].y;
    sprite[j].z = sector[sprite[i].sectnum].floorz + (tilesiz[picnum].y << 8);
    sprite[j].cstat = 0;        //Hitscan does not hit other bullets
    sprite[j].picnum = picnum;
    sprite[j].shade = 0;

    if (sector[sprite[i].sectnum].floorpal == 9)
        sprite[j].pal = 9;
    else
        sprite[j].pal = 0;

    sprite[j].xrepeat = 64;
    sprite[j].yrepeat = 64;
    sprite[j].owner = 0;
    sprite[j].clipdist = 16;
    sprite[j].lotag = 8;
    sprite[j].hitag = 0;

    switch (picnum)
    {
        case SPLASHAROO:
        if (rand() % 2)
        {
            if ((gotpic[WATER >> 3] & (1 << (WATER & 7))) > 0)
            {
                gotpic[WATER >> 3] &= ~(1 << (WATER & 7));
                if (waloff[WATER] != -1)
                {
                    if (rand() % 2)
                    {
                        playsound_loc(S_SPLASH1 + (krand() % 3), sprite[j].x, sprite[j].y);
                    }
                }
            }
        }
        if (rand() % 2)
        {
            if ((gotpic[SLIME >> 3] & (1 << (SLIME & 7))) > 0)
            {
                gotpic[SLIME >> 3] &= ~(1 << (SLIME & 7));
                if (waloff[SLIME] != -1)
                {
                    if (rand() % 2)
                    {
                        playsound_loc(S_SPLASH1 + (krand() % 3), sprite[j].x, sprite[j].y);
                    }
                }
            }
        }
        break;
        case LAVASPLASH:
        break;
    }
}

void bats(short k)
{
    int32_t j = insertsprite(sprite[k].sectnum, FLOCK);
    sprite[j].x = sprite[k].x;
    sprite[j].y = sprite[k].y;
    sprite[j].z = sprite[k].z;
    sprite[j].cstat = 0;
    sprite[j].picnum = BAT;
    sprite[j].shade = 0;
    sprite[j].xrepeat = 64;
    sprite[j].yrepeat = 64;
    sprite[j].ang = Sin(sprite[k].ang + ((krand() & 128) - 256));
    sprite[j].owner = k;
    sprite[j].clipdist = 16;
    sprite[j].lotag = 128;
    sprite[j].hitag = k;
    sprite[j].extra = 0;

    newstatus(j, FLOCK);

    if (sprite[k].extra == 1)
        lastbat = j;
}

void cracks()
{
    short datag;
    int32_t daz;
    int j, k;

    Player* plr = &player[pyrn];

    datag = sector[plr->sector].lotag;

    if (floorpanningcnt < 64)
    {
        if (datag >= 3500 && datag <= 3599)
        {
            sector[plr->sector].hitag = 0;
            daz = sector[plr->sector].floorz + (1024 * (sector[plr->sector].lotag - 3500));
            if ((j = setanimation(&sector[plr->sector].floorz, daz, 32)) >= 0)
            {
                sector[plr->sector].floorpicnum = LAVA1;
                sector[plr->sector].floorshade = -25;
                SND_PlaySound(S_CRACKING, 0, 0, 0, 0);
            }
            sector[plr->sector].lotag = 80;
            floorpanninglist[floorpanningcnt++] = plr->sector;
        }
    }

    if (datag >= 5100 && datag <= 5199)
    {
        sector[plr->sector].hitag = 0;

        daz = sector[plr->sector].floorz + (1024 * (sector[plr->sector].lotag - 5100));
        sector[plr->sector].lotag = 0;
    }

    if (datag >= 5200 && datag <= 5299)
    {
        sector[plr->sector].hitag = 0;

        daz = sector[plr->sector].floorz + (1024 * (sector[plr->sector].lotag - 5200));
        sector[plr->sector].lotag = 0;
    }

    if (datag == 3001)
    {
        sector[plr->sector].lotag = 0;

        for (k = 0; k < MAXSPRITES; k++)
        {
            if (sector[plr->sector].hitag == sprite[k].hitag)
            {
                sprite[k].lotag = 36;
                sprite[k].zvel = (krand() & 1024) + 512;
                newstatus(k, SHOVE);
            }
        }
    }
}

void lavadryland()
{
    short k;
    short s;
    int32_t daz;

    Player* plr = &player[pyrn];

    for (k = 0; k<lavadrylandcnt; k++)
    {
        s = lavadrylandsector[k];

        if (plr->sector==s&&sector[s].lotag>0)
        {
            sector[s].hitag = 0;

            switch (sector[s].floorpicnum)
            {
                case LAVA:
                case ANILAVA:
                case LAVA1:
                sector[s].floorpicnum = COOLLAVA;
                break;
                case SLIME:
                sector[s].floorpicnum = DRYSLIME;
                break;
                case WATER:
                case HEALTHWATER:
                sector[s].floorpicnum = DRYWATER;
                break;
                case LAVA2:
                sector[s].floorpicnum = COOLLAVA2;
                break;
            }

            daz = sector[s].floorz-(1024*(sector[s].lotag-900));

            sector[s].lotag = 0;
        }
    }
}

void warpfxsprite(int s)
{
    short daang;
    Player* plr = &player[pyrn];

    int32_t j = insertsprite(sprite[s].sectnum, WARPFX);

    sprite[j].x = sprite[s].x;
    sprite[j].y = sprite[s].y;
    sprite[j].z = sprite[s].z-(32<<8);
    sprite[j].cstat = 0;
    sprite[j].picnum = ANNIHILATE;

    if (s == plr->spritenum)
    {
        daang = plr->ang;
        sprite[j].ang = daang;
    }
    else
    {
        daang = sprite[s].ang;
        sprite[j].ang = daang;
    }

    sprite[j].xrepeat = 48;
    sprite[j].yrepeat = 48;
    sprite[j].clipdist = 16;
    sprite[j].extra = 0;
    sprite[j].shade = -31;
    sprite[j].xvel = ((krand()&256)-128);
    sprite[j].yvel = ((krand()&256)-128);
    sprite[j].zvel = ((krand()&256)-128);
    sprite[j].owner = s;
    sprite[j].lotag = 12;
    sprite[j].hitag = 0;
    sprite[j].pal = 0;

    int32_t daz = ((((int)sprite[j].zvel)*synctics)>>3);

    movesprite(j, (Cos(daang)*synctics)<<3, (Sin(daang)*synctics)<<3, daz, 4<<8, 4<<8, 1);
}


code:
#ifndef __effects_h__
#define __effects_h__

#include "player.h"

extern int lastbat;

void dofx();
void initwater();
void warpsprite(short spritenum);
void makemonstersplash(int picnum, int i);
void bats(short k);
void warpfxsprite(int s);
void makeasplash(int picnum, Player *plr);
void cracks();
void thunder();
void thesplash();
void scary();
void initlava();
void lavadryland();
void warp(int32_t* x, int32_t* y, int32_t* z, short* daang, short* dasector);
void sectorsounds();

#endif


code:
#include "build.h"
#include "witchaven.h"

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

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

void setbrightness(int32_t brightness)
{
     videoSetPalette(brightness, BASEPAL, 2|8|32);
}